1 /*-------------------------------------------------------------------------
6 * Copyright (c) 2002-2013, PostgreSQL Global Development Group
8 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
11 * contrib/adminpack/adminpack.c
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 if (is_absolute_path(filename))
78 /* Disallow '/a/b/data/..' */
79 if (path_contains_parent_reference(filename))
81 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
82 (errmsg("reference to parent directory (\"..\") not allowed"))));
85 * Allow absolute paths if within DataDir or Log_directory, even
86 * though Log_directory might be outside DataDir.
88 if (!path_is_prefix_of_path(DataDir, filename) &&
89 (!logAllowed || !is_absolute_path(Log_directory) ||
90 !path_is_prefix_of_path(Log_directory, filename)))
92 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
93 (errmsg("absolute path not allowed"))));
95 else if (!path_is_relative_and_below_cwd(filename))
97 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
98 (errmsg("path must be in or below the current directory"))));
105 * check for superuser, bark if not.
108 requireSuperuser(void)
112 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
113 (errmsg("only superuser may access generic file functions"))));
118 /* ------------------------------------
119 * generic file handling functions
123 pg_file_write(PG_FUNCTION_ARGS)
132 filename = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
133 data = PG_GETARG_TEXT_P(1);
135 if (!PG_GETARG_BOOL(2))
139 if (stat(filename, &fst) >= 0)
141 (ERRCODE_DUPLICATE_FILE,
142 errmsg("file \"%s\" exists", filename)));
144 f = fopen(filename, "wb");
147 f = fopen(filename, "ab");
151 (errcode_for_file_access(),
152 errmsg("could not open file \"%s\" for writing: %m",
155 if (VARSIZE(data) != 0)
157 count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
159 if (count != VARSIZE(data) - VARHDRSZ)
161 (errcode_for_file_access(),
162 errmsg("could not write file \"%s\": %m", filename)));
166 PG_RETURN_INT64(count);
171 pg_file_rename(PG_FUNCTION_ARGS)
180 if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
183 fn1 = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
184 fn2 = convert_and_check_filename(PG_GETARG_TEXT_P(1), false);
188 fn3 = convert_and_check_filename(PG_GETARG_TEXT_P(2), false);
190 if (access(fn1, W_OK) < 0)
193 (errcode_for_file_access(),
194 errmsg("file \"%s\" is not accessible: %m", fn1)));
196 PG_RETURN_BOOL(false);
199 if (fn3 && access(fn2, W_OK) < 0)
202 (errcode_for_file_access(),
203 errmsg("file \"%s\" is not accessible: %m", fn2)));
205 PG_RETURN_BOOL(false);
208 rc = access(fn3 ? fn3 : fn2, 2);
209 if (rc >= 0 || errno != ENOENT)
212 (ERRCODE_DUPLICATE_FILE,
213 errmsg("cannot rename to target file \"%s\"",
219 if (rename(fn2, fn3) != 0)
222 (errcode_for_file_access(),
223 errmsg("could not rename \"%s\" to \"%s\": %m",
226 if (rename(fn1, fn2) != 0)
229 (errcode_for_file_access(),
230 errmsg("could not rename \"%s\" to \"%s\": %m",
233 if (rename(fn3, fn2) != 0)
236 (errcode_for_file_access(),
237 errmsg("could not rename \"%s\" back to \"%s\": %m",
243 (ERRCODE_UNDEFINED_FILE,
244 errmsg("renaming \"%s\" to \"%s\" was reverted",
249 else if (rename(fn1, fn2) != 0)
252 (errcode_for_file_access(),
253 errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
256 PG_RETURN_BOOL(true);
261 pg_file_unlink(PG_FUNCTION_ARGS)
267 filename = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
269 if (access(filename, W_OK) < 0)
272 PG_RETURN_BOOL(false);
275 (errcode_for_file_access(),
276 errmsg("file \"%s\" is not accessible: %m", filename)));
279 if (unlink(filename) < 0)
282 (errcode_for_file_access(),
283 errmsg("could not unlink file \"%s\": %m", filename)));
285 PG_RETURN_BOOL(false);
287 PG_RETURN_BOOL(true);
292 pg_logdir_ls(PG_FUNCTION_ARGS)
294 FuncCallContext *funcctx;
296 directory_fctx *fctx;
300 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
301 (errmsg("only superuser can list the log directory"))));
303 if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
305 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
306 (errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
308 if (SRF_IS_FIRSTCALL())
310 MemoryContext oldcontext;
313 funcctx = SRF_FIRSTCALL_INIT();
314 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
316 fctx = palloc(sizeof(directory_fctx));
318 tupdesc = CreateTemplateTupleDesc(2, false);
319 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
320 TIMESTAMPOID, -1, 0);
321 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
324 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
326 fctx->location = pstrdup(Log_directory);
327 fctx->dirdesc = AllocateDir(fctx->location);
331 (errcode_for_file_access(),
332 errmsg("could not read directory \"%s\": %m",
335 funcctx->user_fctx = fctx;
336 MemoryContextSwitchTo(oldcontext);
339 funcctx = SRF_PERCALL_SETUP();
340 fctx = (directory_fctx *) funcctx->user_fctx;
342 while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
346 char timestampbuf[32];
347 char *field[MAXDATEFIELDS];
348 char lowstr[MAXDATELEN + 1];
351 ftype[MAXDATEFIELDS];
357 * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
359 if (strlen(de->d_name) != 32
360 || strncmp(de->d_name, "postgresql-", 11) != 0
361 || de->d_name[21] != '_'
362 || strcmp(de->d_name + 28, ".log") != 0)
365 /* extract timestamp portion of filename */
366 strcpy(timestampbuf, de->d_name + 11);
367 timestampbuf[17] = '\0';
369 /* parse and decode expected timestamp to verify it's OK format */
370 if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
373 if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
376 /* Seems the timestamp is OK; prepare and return tuple */
378 values[0] = timestampbuf;
379 values[1] = psprintf("%s/%s", fctx->location, de->d_name);
381 tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
383 SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
386 FreeDir(fctx->dirdesc);
387 SRF_RETURN_DONE(funcctx);