Age Owner TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * adminpack.c
4 : *
5 : *
6 : * Copyright (c) 2002-2023, PostgreSQL Global Development Group
7 : *
8 : * Author: Andreas Pflug <pgadmin@pse-consulting.de>
9 : *
10 : * IDENTIFICATION
11 : * contrib/adminpack/adminpack.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include <sys/file.h>
18 : #include <sys/stat.h>
19 : #include <unistd.h>
20 :
21 : #include "catalog/pg_authid.h"
22 : #include "catalog/pg_type.h"
23 : #include "funcapi.h"
24 : #include "miscadmin.h"
25 : #include "postmaster/syslogger.h"
26 : #include "storage/fd.h"
27 : #include "utils/acl.h"
28 : #include "utils/builtins.h"
29 : #include "utils/datetime.h"
30 :
31 :
32 : #ifdef WIN32
33 :
34 : #ifdef rename
35 : #undef rename
36 : #endif
37 :
38 : #ifdef unlink
39 : #undef unlink
40 : #endif
41 : #endif
42 :
6158 tgl 43 CBC 1 : PG_MODULE_MAGIC;
44 :
45 1 : PG_FUNCTION_INFO_V1(pg_file_write);
1829 sfrost 46 4 : PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
1171 fujii 47 2 : PG_FUNCTION_INFO_V1(pg_file_sync);
6158 tgl 48 1 : PG_FUNCTION_INFO_V1(pg_file_rename);
1829 sfrost 49 2 : PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
6158 tgl 50 1 : PG_FUNCTION_INFO_V1(pg_file_unlink);
1829 sfrost 51 2 : PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
6158 tgl 52 1 : PG_FUNCTION_INFO_V1(pg_logdir_ls);
1829 sfrost 53 1 : PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);
54 :
55 : static int64 pg_file_write_internal(text *file, text *data, bool replace);
56 : static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
57 : static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
58 :
59 :
60 : /*-----------------------
61 : * some helper functions
62 : */
63 :
64 : /*
65 : * Convert a "text" filename argument to C string, and check it's allowable.
66 : *
67 : * Filename may be absolute or relative to the DataDir, but we only allow
68 : * absolute paths that match DataDir.
69 : */
70 : static char *
1150 michael 71 23 : convert_and_check_filename(text *arg)
72 : {
5493 tgl 73 23 : char *filename = text_to_cstring(arg);
74 :
5998 75 23 : canonicalize_path(filename); /* filename can change length here */
76 :
77 : /*
78 : * Members of the 'pg_write_server_files' role are allowed to access any
79 : * files on the server as the PG user, so no need to do any further checks
80 : * here.
81 : */
377 mail 82 23 : if (has_privs_of_role(GetUserId(), ROLE_PG_WRITE_SERVER_FILES))
1829 sfrost 83 19 : return filename;
84 :
85 : /*
86 : * User isn't a member of the pg_write_server_files role, so check if it's
87 : * allowable
88 : */
6158 tgl 89 4 : if (is_absolute_path(filename))
90 : {
91 : /* Allow absolute paths if within DataDir */
1150 michael 92 3 : if (!path_is_prefix_of_path(DataDir, filename))
4439 bruce 93 2 : ereport(ERROR,
94 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
95 : errmsg("absolute path not allowed")));
96 : }
97 1 : else if (!path_is_relative_and_below_cwd(filename))
98 1 : ereport(ERROR,
99 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
100 : errmsg("path must be in or below the data directory")));
101 :
102 1 : return filename;
103 : }
104 :
105 :
106 : /*
107 : * check for superuser, bark if not.
108 : */
109 : static void
6158 tgl 110 UBC 0 : requireSuperuser(void)
111 : {
112 0 : if (!superuser())
6031 bruce 113 0 : ereport(ERROR,
114 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
115 : errmsg("only superuser may access generic file functions")));
6158 tgl 116 0 : }
117 :
118 :
119 :
120 : /* ------------------------------------
121 : * pg_file_write - old version
122 : *
123 : * The superuser() check here must be kept as the library might be upgraded
124 : * without the extension being upgraded, meaning that in pre-1.1 installations
125 : * these functions could be called by any user.
126 : */
127 : Datum
6031 bruce 128 0 : pg_file_write(PG_FUNCTION_ARGS)
129 : {
1829 sfrost 130 0 : text *file = PG_GETARG_TEXT_PP(0);
131 0 : text *data = PG_GETARG_TEXT_PP(1);
132 0 : bool replace = PG_GETARG_BOOL(2);
6031 bruce 133 0 : int64 count = 0;
134 :
6158 tgl 135 0 : requireSuperuser();
136 :
1829 sfrost 137 0 : count = pg_file_write_internal(file, data, replace);
138 :
139 0 : PG_RETURN_INT64(count);
140 : }
141 :
142 : /* ------------------------------------
143 : * pg_file_write_v1_1 - Version 1.1
144 : *
145 : * As of adminpack version 1.1, we no longer need to check if the user
146 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
147 : * Users can then grant access to it based on their policies.
148 : *
149 : * Otherwise identical to pg_file_write (above).
150 : */
151 : Datum
1829 sfrost 152 CBC 8 : pg_file_write_v1_1(PG_FUNCTION_ARGS)
153 : {
154 8 : text *file = PG_GETARG_TEXT_PP(0);
155 8 : text *data = PG_GETARG_TEXT_PP(1);
156 8 : bool replace = PG_GETARG_BOOL(2);
157 8 : int64 count = 0;
158 :
159 8 : count = pg_file_write_internal(file, data, replace);
160 :
161 4 : PG_RETURN_INT64(count);
162 : }
163 :
164 : /* ------------------------------------
165 : * pg_file_write_internal - Workhorse for pg_file_write functions.
166 : *
167 : * This handles the actual work for pg_file_write.
168 : */
169 : static int64
170 8 : pg_file_write_internal(text *file, text *data, bool replace)
171 : {
172 : FILE *f;
173 : char *filename;
174 8 : int64 count = 0;
175 :
1150 michael 176 8 : filename = convert_and_check_filename(file);
177 :
1829 sfrost 178 5 : if (!replace)
179 : {
180 : struct stat fst;
181 :
6158 tgl 182 4 : if (stat(filename, &fst) >= 0)
6031 bruce 183 1 : ereport(ERROR,
184 : (errcode(ERRCODE_DUPLICATE_FILE),
185 : errmsg("file \"%s\" exists", filename)));
186 :
2219 noah 187 3 : f = AllocateFile(filename, "wb");
188 : }
189 : else
190 1 : f = AllocateFile(filename, "ab");
191 :
6158 tgl 192 4 : if (!f)
6158 tgl 193 UBC 0 : ereport(ERROR,
194 : (errcode_for_file_access(),
195 : errmsg("could not open file \"%s\" for writing: %m",
196 : filename)));
197 :
2219 noah 198 CBC 4 : count = fwrite(VARDATA_ANY(data), 1, VARSIZE_ANY_EXHDR(data), f);
199 4 : if (count != VARSIZE_ANY_EXHDR(data) || FreeFile(f))
2219 noah 200 UBC 0 : ereport(ERROR,
201 : (errcode_for_file_access(),
202 : errmsg("could not write file \"%s\": %m", filename)));
203 :
1829 sfrost 204 CBC 4 : return (count);
205 : }
206 :
207 : /* ------------------------------------
208 : * pg_file_sync
209 : *
210 : * We REVOKE EXECUTE on the function from PUBLIC.
211 : * Users can then grant access to it based on their policies.
212 : */
213 : Datum
1171 fujii 214 3 : pg_file_sync(PG_FUNCTION_ARGS)
215 : {
216 : char *filename;
217 : struct stat fst;
218 :
1150 michael 219 3 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
220 :
1171 fujii 221 3 : if (stat(filename, &fst) < 0)
222 1 : ereport(ERROR,
223 : (errcode_for_file_access(),
224 : errmsg("could not stat file \"%s\": %m", filename)));
225 :
226 2 : fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR);
227 :
228 2 : PG_RETURN_VOID();
229 : }
230 :
231 : /* ------------------------------------
232 : * pg_file_rename - old version
233 : *
234 : * The superuser() check here must be kept as the library might be upgraded
235 : * without the extension being upgraded, meaning that in pre-1.1 installations
236 : * these functions could be called by any user.
237 : */
238 : Datum
6031 bruce 239 UBC 0 : pg_file_rename(PG_FUNCTION_ARGS)
240 : {
241 : text *file1;
242 : text *file2;
243 : text *file3;
244 : bool result;
245 :
6158 tgl 246 0 : requireSuperuser();
247 :
248 0 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
249 0 : PG_RETURN_NULL();
250 :
1829 sfrost 251 0 : file1 = PG_GETARG_TEXT_PP(0);
252 0 : file2 = PG_GETARG_TEXT_PP(1);
253 :
6158 tgl 254 0 : if (PG_ARGISNULL(2))
1829 sfrost 255 0 : file3 = NULL;
256 : else
257 0 : file3 = PG_GETARG_TEXT_PP(2);
258 :
259 0 : result = pg_file_rename_internal(file1, file2, file3);
260 :
261 0 : PG_RETURN_BOOL(result);
262 : }
263 :
264 : /* ------------------------------------
265 : * pg_file_rename_v1_1 - Version 1.1
266 : *
267 : * As of adminpack version 1.1, we no longer need to check if the user
268 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
269 : * Users can then grant access to it based on their policies.
270 : *
271 : * Otherwise identical to pg_file_write (above).
272 : */
273 : Datum
1829 sfrost 274 CBC 3 : pg_file_rename_v1_1(PG_FUNCTION_ARGS)
275 : {
276 : text *file1;
277 : text *file2;
278 : text *file3;
279 : bool result;
280 :
281 3 : if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
1829 sfrost 282 UBC 0 : PG_RETURN_NULL();
283 :
1829 sfrost 284 CBC 3 : file1 = PG_GETARG_TEXT_PP(0);
285 3 : file2 = PG_GETARG_TEXT_PP(1);
286 :
287 3 : if (PG_ARGISNULL(2))
288 2 : file3 = NULL;
289 : else
290 1 : file3 = PG_GETARG_TEXT_PP(2);
291 :
292 3 : result = pg_file_rename_internal(file1, file2, file3);
293 :
294 3 : PG_RETURN_BOOL(result);
295 : }
296 :
297 : /* ------------------------------------
298 : * pg_file_rename_internal - Workhorse for pg_file_rename functions.
299 : *
300 : * This handles the actual work for pg_file_rename.
301 : */
302 : static bool
303 3 : pg_file_rename_internal(text *file1, text *file2, text *file3)
304 : {
305 : char *fn1,
306 : *fn2,
307 : *fn3;
308 : int rc;
309 :
1150 michael 310 3 : fn1 = convert_and_check_filename(file1);
311 3 : fn2 = convert_and_check_filename(file2);
312 :
1829 sfrost 313 3 : if (file3 == NULL)
1820 tgl 314 2 : fn3 = NULL;
315 : else
1150 michael 316 1 : fn3 = convert_and_check_filename(file3);
317 :
6158 tgl 318 3 : if (access(fn1, W_OK) < 0)
319 : {
320 1 : ereport(WARNING,
321 : (errcode_for_file_access(),
322 : errmsg("file \"%s\" is not accessible: %m", fn1)));
323 :
1829 sfrost 324 1 : return false;
325 : }
326 :
6158 tgl 327 2 : if (fn3 && access(fn2, W_OK) < 0)
328 : {
6158 tgl 329 UBC 0 : ereport(WARNING,
330 : (errcode_for_file_access(),
331 : errmsg("file \"%s\" is not accessible: %m", fn2)));
332 :
1829 sfrost 333 0 : return false;
334 : }
335 :
1820 tgl 336 CBC 2 : rc = access(fn3 ? fn3 : fn2, W_OK);
6158 337 2 : if (rc >= 0 || errno != ENOENT)
338 : {
6158 tgl 339 UBC 0 : ereport(ERROR,
340 : (errcode(ERRCODE_DUPLICATE_FILE),
341 : errmsg("cannot rename to target file \"%s\"",
342 : fn3 ? fn3 : fn2)));
343 : }
344 :
6158 tgl 345 CBC 2 : if (fn3)
346 : {
6031 bruce 347 1 : if (rename(fn2, fn3) != 0)
348 : {
6158 tgl 349 UBC 0 : ereport(ERROR,
350 : (errcode_for_file_access(),
351 : errmsg("could not rename \"%s\" to \"%s\": %m",
352 : fn2, fn3)));
353 : }
6158 tgl 354 CBC 1 : if (rename(fn1, fn2) != 0)
355 : {
6158 tgl 356 UBC 0 : ereport(WARNING,
357 : (errcode_for_file_access(),
358 : errmsg("could not rename \"%s\" to \"%s\": %m",
359 : fn1, fn2)));
360 :
361 0 : if (rename(fn3, fn2) != 0)
362 : {
363 0 : ereport(ERROR,
364 : (errcode_for_file_access(),
365 : errmsg("could not rename \"%s\" back to \"%s\": %m",
366 : fn3, fn2)));
367 : }
368 : else
369 : {
370 0 : ereport(ERROR,
371 : (errcode(ERRCODE_UNDEFINED_FILE),
372 : errmsg("renaming \"%s\" to \"%s\" was reverted",
373 : fn2, fn3)));
374 : }
375 : }
376 : }
6158 tgl 377 CBC 1 : else if (rename(fn1, fn2) != 0)
378 : {
6158 tgl 379 UBC 0 : ereport(ERROR,
380 : (errcode_for_file_access(),
381 : errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
382 : }
383 :
1829 sfrost 384 CBC 2 : return true;
385 : }
386 :
387 :
388 : /* ------------------------------------
389 : * pg_file_unlink - old version
390 : *
391 : * The superuser() check here must be kept as the library might be upgraded
392 : * without the extension being upgraded, meaning that in pre-1.1 installations
393 : * these functions could be called by any user.
394 : */
395 : Datum
6031 bruce 396 UBC 0 : pg_file_unlink(PG_FUNCTION_ARGS)
397 : {
398 : char *filename;
399 :
6158 tgl 400 0 : requireSuperuser();
401 :
1150 michael 402 0 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
403 :
6158 tgl 404 0 : if (access(filename, W_OK) < 0)
405 : {
6031 bruce 406 0 : if (errno == ENOENT)
407 0 : PG_RETURN_BOOL(false);
408 : else
409 0 : ereport(ERROR,
410 : (errcode_for_file_access(),
411 : errmsg("file \"%s\" is not accessible: %m", filename)));
412 : }
413 :
6158 tgl 414 0 : if (unlink(filename) < 0)
415 : {
416 0 : ereport(WARNING,
417 : (errcode_for_file_access(),
418 : errmsg("could not unlink file \"%s\": %m", filename)));
419 :
420 0 : PG_RETURN_BOOL(false);
421 : }
422 0 : PG_RETURN_BOOL(true);
423 : }
424 :
425 :
426 : /* ------------------------------------
427 : * pg_file_unlink_v1_1 - Version 1.1
428 : *
429 : * As of adminpack version 1.1, we no longer need to check if the user
430 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
431 : * Users can then grant access to it based on their policies.
432 : *
433 : * Otherwise identical to pg_file_unlink (above).
434 : */
435 : Datum
1829 sfrost 436 CBC 5 : pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
437 : {
438 : char *filename;
439 :
1150 michael 440 5 : filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
441 :
1829 sfrost 442 5 : if (access(filename, W_OK) < 0)
443 : {
444 2 : if (errno == ENOENT)
445 2 : PG_RETURN_BOOL(false);
446 : else
1829 sfrost 447 UBC 0 : ereport(ERROR,
448 : (errcode_for_file_access(),
449 : errmsg("file \"%s\" is not accessible: %m", filename)));
450 : }
451 :
1829 sfrost 452 CBC 3 : if (unlink(filename) < 0)
453 : {
1829 sfrost 454 UBC 0 : ereport(WARNING,
455 : (errcode_for_file_access(),
456 : errmsg("could not unlink file \"%s\": %m", filename)));
457 :
458 0 : PG_RETURN_BOOL(false);
459 : }
1829 sfrost 460 CBC 3 : PG_RETURN_BOOL(true);
461 : }
462 :
463 : /* ------------------------------------
464 : * pg_logdir_ls - Old version
465 : *
466 : * The superuser() check here must be kept as the library might be upgraded
467 : * without the extension being upgraded, meaning that in pre-1.1 installations
468 : * these functions could be called by any user.
469 : */
470 : Datum
1829 sfrost 471 UBC 0 : pg_logdir_ls(PG_FUNCTION_ARGS)
472 : {
6031 bruce 473 0 : if (!superuser())
6158 tgl 474 0 : ereport(ERROR,
475 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
476 : errmsg("only superuser can list the log directory")));
477 :
1829 sfrost 478 0 : return (pg_logdir_ls_internal(fcinfo));
479 : }
480 :
481 : /* ------------------------------------
482 : * pg_logdir_ls_v1_1 - Version 1.1
483 : *
484 : * As of adminpack version 1.1, we no longer need to check if the user
485 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
486 : * Users can then grant access to it based on their policies.
487 : *
488 : * Otherwise identical to pg_logdir_ls (above).
489 : */
490 : Datum
491 0 : pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
492 : {
493 0 : return (pg_logdir_ls_internal(fcinfo));
494 : }
495 :
496 : static Datum
497 0 : pg_logdir_ls_internal(FunctionCallInfo fcinfo)
498 : {
1119 tgl 499 0 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
500 : bool randomAccess;
501 : TupleDesc tupdesc;
502 : Tuplestorestate *tupstore;
503 : AttInMetadata *attinmeta;
504 : DIR *dirdesc;
505 : struct dirent *de;
506 : MemoryContext oldcontext;
507 :
6015 508 0 : if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
6158 509 0 : ereport(ERROR,
510 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
511 : errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
512 :
513 : /* check to see if caller supports us returning a tuplestore */
1119 514 0 : if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
515 0 : ereport(ERROR,
516 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
517 : errmsg("set-valued function called in context that cannot accept a set")));
518 0 : if (!(rsinfo->allowedModes & SFRM_Materialize))
519 0 : ereport(ERROR,
520 : (errcode(ERRCODE_SYNTAX_ERROR),
521 : errmsg("materialize mode required, but it is not allowed in this context")));
522 :
523 : /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
524 0 : oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
525 :
526 0 : tupdesc = CreateTemplateTupleDesc(2);
527 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
528 : TIMESTAMPOID, -1, 0);
529 0 : TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
530 : TEXTOID, -1, 0);
531 :
532 0 : randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
533 0 : tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
534 0 : rsinfo->returnMode = SFRM_Materialize;
535 0 : rsinfo->setResult = tupstore;
536 0 : rsinfo->setDesc = tupdesc;
537 :
538 0 : MemoryContextSwitchTo(oldcontext);
539 :
540 0 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
541 :
542 0 : dirdesc = AllocateDir(Log_directory);
543 0 : while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
544 : {
545 : char *values[2];
546 : HeapTuple tuple;
547 : char timestampbuf[32];
548 : char *field[MAXDATEFIELDS];
549 : char lowstr[MAXDATELEN + 1];
550 : int dtype;
551 : int nf,
552 : ftype[MAXDATEFIELDS];
553 : fsec_t fsec;
6031 bruce 554 0 : int tz = 0;
555 : struct pg_tm date;
556 : DateTimeErrorExtra extra;
557 :
558 : /*
559 : * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
560 : */
6158 tgl 561 UIC 0 : if (strlen(de->d_name) != 32
6015 tgl 562 UBC 0 : || strncmp(de->d_name, "postgresql-", 11) != 0
6158 563 0 : || de->d_name[21] != '_'
6015 564 0 : || strcmp(de->d_name + 28, ".log") != 0)
6031 bruce 565 0 : continue;
6158 tgl 566 EUB :
567 : /* extract timestamp portion of filename */
6015 tgl 568 UIC 0 : strcpy(timestampbuf, de->d_name + 11);
6015 tgl 569 UBC 0 : timestampbuf[17] = '\0';
6158 tgl 570 EUB :
571 : /* parse and decode expected timestamp to verify it's OK format */
6015 tgl 572 UIC 0 : if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
6031 bruce 573 UBC 0 : continue;
6158 tgl 574 EUB :
121 tgl 575 UNC 0 : if (DecodeDateTime(field, ftype, nf,
576 : &dtype, &date, &fsec, &tz, &extra))
6031 bruce 577 UBC 0 : continue;
578 :
6015 tgl 579 EUB : /* Seems the timestamp is OK; prepare and return tuple */
580 :
6015 tgl 581 UIC 0 : values[0] = timestampbuf;
1119 582 0 : values[1] = psprintf("%s/%s", Log_directory, de->d_name);
6158 tgl 583 EUB :
1119 tgl 584 UBC 0 : tuple = BuildTupleFromCStrings(attinmeta, values);
585 :
586 0 : tuplestore_puttuple(tupstore, tuple);
587 : }
6158 tgl 588 EUB :
1119 tgl 589 UIC 0 : FreeDir(dirdesc);
590 0 : return (Datum) 0;
6158 tgl 591 EUB : }
|