1 /*-------------------------------------------------------------------------
6 * Copyright (c) 2002-2008, PostgreSQL Global Development Group
8 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
13 *-------------------------------------------------------------------------
21 #include "catalog/pg_type.h"
23 #include "miscadmin.h"
24 #include "postmaster/syslogger.h"
25 #include "storage/fd.h"
26 #include "utils/builtins.h"
27 #include "utils/datetime.h"
43 Datum
pg_file_write(PG_FUNCTION_ARGS
);
44 Datum
pg_file_rename(PG_FUNCTION_ARGS
);
45 Datum
pg_file_unlink(PG_FUNCTION_ARGS
);
46 Datum
pg_logdir_ls(PG_FUNCTION_ARGS
);
48 PG_FUNCTION_INFO_V1(pg_file_write
);
49 PG_FUNCTION_INFO_V1(pg_file_rename
);
50 PG_FUNCTION_INFO_V1(pg_file_unlink
);
51 PG_FUNCTION_INFO_V1(pg_logdir_ls
);
59 /*-----------------------
60 * some helper functions
64 * Convert a "text" filename argument to C string, and check it's allowable.
66 * Filename may be absolute or relative to the DataDir, but we only allow
67 * absolute paths that match DataDir or Log_directory.
70 convert_and_check_filename(text
*arg
, bool logAllowed
)
72 char *filename
= text_to_cstring(arg
);
74 canonicalize_path(filename
); /* filename can change length here */
76 /* Disallow ".." in the path */
77 if (path_contains_parent_reference(filename
))
79 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
80 (errmsg("reference to parent directory (\"..\") not allowed"))));
82 if (is_absolute_path(filename
))
84 /* Allow absolute references within DataDir */
85 if (path_is_prefix_of_path(DataDir
, filename
))
87 /* The log directory might be outside our datadir, but allow it */
89 is_absolute_path(Log_directory
) &&
90 path_is_prefix_of_path(Log_directory
, filename
))
94 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
95 (errmsg("absolute path not allowed"))));
96 return NULL
; /* keep compiler quiet */
106 * check for superuser, bark if not.
109 requireSuperuser(void)
113 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
114 (errmsg("only superuser may access generic file functions"))));
119 /* ------------------------------------
120 * generic file handling functions
124 pg_file_write(PG_FUNCTION_ARGS
)
133 filename
= convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
134 data
= PG_GETARG_TEXT_P(1);
136 if (!PG_GETARG_BOOL(2))
140 if (stat(filename
, &fst
) >= 0)
142 (ERRCODE_DUPLICATE_FILE
,
143 errmsg("file \"%s\" exists", filename
)));
145 f
= fopen(filename
, "wb");
148 f
= fopen(filename
, "ab");
152 (errcode_for_file_access(),
153 errmsg("could not open file \"%s\" for writing: %m",
156 if (VARSIZE(data
) != 0)
158 count
= fwrite(VARDATA(data
), 1, VARSIZE(data
) - VARHDRSZ
, f
);
160 if (count
!= VARSIZE(data
) - VARHDRSZ
)
162 (errcode_for_file_access(),
163 errmsg("could not write file \"%s\": %m", filename
)));
167 PG_RETURN_INT64(count
);
172 pg_file_rename(PG_FUNCTION_ARGS
)
181 if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
184 fn1
= convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
185 fn2
= convert_and_check_filename(PG_GETARG_TEXT_P(1), false);
189 fn3
= convert_and_check_filename(PG_GETARG_TEXT_P(2), false);
191 if (access(fn1
, W_OK
) < 0)
194 (errcode_for_file_access(),
195 errmsg("file \"%s\" is not accessible: %m", fn1
)));
197 PG_RETURN_BOOL(false);
200 if (fn3
&& access(fn2
, W_OK
) < 0)
203 (errcode_for_file_access(),
204 errmsg("file \"%s\" is not accessible: %m", fn2
)));
206 PG_RETURN_BOOL(false);
209 rc
= access(fn3
? fn3
: fn2
, 2);
210 if (rc
>= 0 || errno
!= ENOENT
)
213 (ERRCODE_DUPLICATE_FILE
,
214 errmsg("cannot rename to target file \"%s\"",
220 if (rename(fn2
, fn3
) != 0)
223 (errcode_for_file_access(),
224 errmsg("could not rename \"%s\" to \"%s\": %m",
227 if (rename(fn1
, fn2
) != 0)
230 (errcode_for_file_access(),
231 errmsg("could not rename \"%s\" to \"%s\": %m",
234 if (rename(fn3
, fn2
) != 0)
237 (errcode_for_file_access(),
238 errmsg("could not rename \"%s\" back to \"%s\": %m",
244 (ERRCODE_UNDEFINED_FILE
,
245 errmsg("renaming \"%s\" to \"%s\" was reverted",
250 else if (rename(fn1
, fn2
) != 0)
253 (errcode_for_file_access(),
254 errmsg("could not rename \"%s\" to \"%s\": %m", fn1
, fn2
)));
257 PG_RETURN_BOOL(true);
262 pg_file_unlink(PG_FUNCTION_ARGS
)
268 filename
= convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
270 if (access(filename
, W_OK
) < 0)
273 PG_RETURN_BOOL(false);
276 (errcode_for_file_access(),
277 errmsg("file \"%s\" is not accessible: %m", filename
)));
280 if (unlink(filename
) < 0)
283 (errcode_for_file_access(),
284 errmsg("could not unlink file \"%s\": %m", filename
)));
286 PG_RETURN_BOOL(false);
288 PG_RETURN_BOOL(true);
293 pg_logdir_ls(PG_FUNCTION_ARGS
)
295 FuncCallContext
*funcctx
;
297 directory_fctx
*fctx
;
301 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE
),
302 (errmsg("only superuser can list the log directory"))));
304 if (strcmp(Log_filename
, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
306 (errcode(ERRCODE_INVALID_PARAMETER_VALUE
),
307 (errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
309 if (SRF_IS_FIRSTCALL())
311 MemoryContext oldcontext
;
314 funcctx
= SRF_FIRSTCALL_INIT();
315 oldcontext
= MemoryContextSwitchTo(funcctx
->multi_call_memory_ctx
);
317 fctx
= palloc(sizeof(directory_fctx
));
319 tupdesc
= CreateTemplateTupleDesc(2, false);
320 TupleDescInitEntry(tupdesc
, (AttrNumber
) 1, "starttime",
321 TIMESTAMPOID
, -1, 0);
322 TupleDescInitEntry(tupdesc
, (AttrNumber
) 2, "filename",
325 funcctx
->attinmeta
= TupleDescGetAttInMetadata(tupdesc
);
327 fctx
->location
= pstrdup(Log_directory
);
328 fctx
->dirdesc
= AllocateDir(fctx
->location
);
332 (errcode_for_file_access(),
333 errmsg("could not read directory \"%s\": %m",
336 funcctx
->user_fctx
= fctx
;
337 MemoryContextSwitchTo(oldcontext
);
340 funcctx
= SRF_PERCALL_SETUP();
341 fctx
= (directory_fctx
*) funcctx
->user_fctx
;
343 while ((de
= ReadDir(fctx
->dirdesc
, fctx
->location
)) != NULL
)
347 char timestampbuf
[32];
348 char *field
[MAXDATEFIELDS
];
349 char lowstr
[MAXDATELEN
+ 1];
352 ftype
[MAXDATEFIELDS
];
358 * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
360 if (strlen(de
->d_name
) != 32
361 || strncmp(de
->d_name
, "postgresql-", 11) != 0
362 || de
->d_name
[21] != '_'
363 || strcmp(de
->d_name
+ 28, ".log") != 0)
366 /* extract timestamp portion of filename */
367 strcpy(timestampbuf
, de
->d_name
+ 11);
368 timestampbuf
[17] = '\0';
370 /* parse and decode expected timestamp to verify it's OK format */
371 if (ParseDateTime(timestampbuf
, lowstr
, MAXDATELEN
, field
, ftype
, MAXDATEFIELDS
, &nf
))
374 if (DecodeDateTime(field
, ftype
, nf
, &dtype
, &date
, &fsec
, &tz
))
377 /* Seems the timestamp is OK; prepare and return tuple */
379 values
[0] = timestampbuf
;
380 values
[1] = palloc(strlen(fctx
->location
) + strlen(de
->d_name
) + 2);
381 sprintf(values
[1], "%s/%s", fctx
->location
, de
->d_name
);
383 tuple
= BuildTupleFromCStrings(funcctx
->attinmeta
, values
);
385 SRF_RETURN_NEXT(funcctx
, HeapTupleGetDatum(tuple
));
388 FreeDir(fctx
->dirdesc
);
389 SRF_RETURN_DONE(funcctx
);