]> granicus.if.org Git - postgresql/blob - contrib/adminpack/adminpack.c
Add use of asprintf()
[postgresql] / contrib / adminpack / adminpack.c
1 /*-------------------------------------------------------------------------
2  *
3  * adminpack.c
4  *
5  *
6  * Copyright (c) 2002-2013, 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_type.h"
22 #include "funcapi.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"
28
29
30 #ifdef WIN32
31
32 #ifdef rename
33 #undef rename
34 #endif
35
36 #ifdef unlink
37 #undef unlink
38 #endif
39 #endif
40
41 PG_MODULE_MAGIC;
42
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);
47
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);
52
53 typedef struct
54 {
55         char       *location;
56         DIR                *dirdesc;
57 } directory_fctx;
58
59 /*-----------------------
60  * some helper functions
61  */
62
63 /*
64  * Convert a "text" filename argument to C string, and check it's allowable.
65  *
66  * Filename may be absolute or relative to the DataDir, but we only allow
67  * absolute paths that match DataDir or Log_directory.
68  */
69 static char *
70 convert_and_check_filename(text *arg, bool logAllowed)
71 {
72         char       *filename = text_to_cstring(arg);
73
74         canonicalize_path(filename);    /* filename can change length here */
75
76         if (is_absolute_path(filename))
77         {
78                 /* Disallow '/a/b/data/..' */
79                 if (path_contains_parent_reference(filename))
80                         ereport(ERROR,
81                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
82                         (errmsg("reference to parent directory (\"..\") not allowed"))));
83
84                 /*
85                  * Allow absolute paths if within DataDir or Log_directory, even
86                  * though Log_directory might be outside DataDir.
87                  */
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)))
91                         ereport(ERROR,
92                                         (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
93                                          (errmsg("absolute path not allowed"))));
94         }
95         else if (!path_is_relative_and_below_cwd(filename))
96                 ereport(ERROR,
97                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
98                                  (errmsg("path must be in or below the current directory"))));
99
100         return filename;
101 }
102
103
104 /*
105  * check for superuser, bark if not.
106  */
107 static void
108 requireSuperuser(void)
109 {
110         if (!superuser())
111                 ereport(ERROR,
112                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
113                           (errmsg("only superuser may access generic file functions"))));
114 }
115
116
117
118 /* ------------------------------------
119  * generic file handling functions
120  */
121
122 Datum
123 pg_file_write(PG_FUNCTION_ARGS)
124 {
125         FILE       *f;
126         char       *filename;
127         text       *data;
128         int64           count = 0;
129
130         requireSuperuser();
131
132         filename = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
133         data = PG_GETARG_TEXT_P(1);
134
135         if (!PG_GETARG_BOOL(2))
136         {
137                 struct stat fst;
138
139                 if (stat(filename, &fst) >= 0)
140                         ereport(ERROR,
141                                         (ERRCODE_DUPLICATE_FILE,
142                                          errmsg("file \"%s\" exists", filename)));
143
144                 f = fopen(filename, "wb");
145         }
146         else
147                 f = fopen(filename, "ab");
148
149         if (!f)
150                 ereport(ERROR,
151                                 (errcode_for_file_access(),
152                                  errmsg("could not open file \"%s\" for writing: %m",
153                                                 filename)));
154
155         if (VARSIZE(data) != 0)
156         {
157                 count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
158
159                 if (count != VARSIZE(data) - VARHDRSZ)
160                         ereport(ERROR,
161                                         (errcode_for_file_access(),
162                                          errmsg("could not write file \"%s\": %m", filename)));
163         }
164         fclose(f);
165
166         PG_RETURN_INT64(count);
167 }
168
169
170 Datum
171 pg_file_rename(PG_FUNCTION_ARGS)
172 {
173         char       *fn1,
174                            *fn2,
175                            *fn3;
176         int                     rc;
177
178         requireSuperuser();
179
180         if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
181                 PG_RETURN_NULL();
182
183         fn1 = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
184         fn2 = convert_and_check_filename(PG_GETARG_TEXT_P(1), false);
185         if (PG_ARGISNULL(2))
186                 fn3 = 0;
187         else
188                 fn3 = convert_and_check_filename(PG_GETARG_TEXT_P(2), false);
189
190         if (access(fn1, W_OK) < 0)
191         {
192                 ereport(WARNING,
193                                 (errcode_for_file_access(),
194                                  errmsg("file \"%s\" is not accessible: %m", fn1)));
195
196                 PG_RETURN_BOOL(false);
197         }
198
199         if (fn3 && access(fn2, W_OK) < 0)
200         {
201                 ereport(WARNING,
202                                 (errcode_for_file_access(),
203                                  errmsg("file \"%s\" is not accessible: %m", fn2)));
204
205                 PG_RETURN_BOOL(false);
206         }
207
208         rc = access(fn3 ? fn3 : fn2, 2);
209         if (rc >= 0 || errno != ENOENT)
210         {
211                 ereport(ERROR,
212                                 (ERRCODE_DUPLICATE_FILE,
213                                  errmsg("cannot rename to target file \"%s\"",
214                                                 fn3 ? fn3 : fn2)));
215         }
216
217         if (fn3)
218         {
219                 if (rename(fn2, fn3) != 0)
220                 {
221                         ereport(ERROR,
222                                         (errcode_for_file_access(),
223                                          errmsg("could not rename \"%s\" to \"%s\": %m",
224                                                         fn2, fn3)));
225                 }
226                 if (rename(fn1, fn2) != 0)
227                 {
228                         ereport(WARNING,
229                                         (errcode_for_file_access(),
230                                          errmsg("could not rename \"%s\" to \"%s\": %m",
231                                                         fn1, fn2)));
232
233                         if (rename(fn3, fn2) != 0)
234                         {
235                                 ereport(ERROR,
236                                                 (errcode_for_file_access(),
237                                                  errmsg("could not rename \"%s\" back to \"%s\": %m",
238                                                                 fn3, fn2)));
239                         }
240                         else
241                         {
242                                 ereport(ERROR,
243                                                 (ERRCODE_UNDEFINED_FILE,
244                                                  errmsg("renaming \"%s\" to \"%s\" was reverted",
245                                                                 fn2, fn3)));
246                         }
247                 }
248         }
249         else if (rename(fn1, fn2) != 0)
250         {
251                 ereport(ERROR,
252                                 (errcode_for_file_access(),
253                                  errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
254         }
255
256         PG_RETURN_BOOL(true);
257 }
258
259
260 Datum
261 pg_file_unlink(PG_FUNCTION_ARGS)
262 {
263         char       *filename;
264
265         requireSuperuser();
266
267         filename = convert_and_check_filename(PG_GETARG_TEXT_P(0), false);
268
269         if (access(filename, W_OK) < 0)
270         {
271                 if (errno == ENOENT)
272                         PG_RETURN_BOOL(false);
273                 else
274                         ereport(ERROR,
275                                         (errcode_for_file_access(),
276                                          errmsg("file \"%s\" is not accessible: %m", filename)));
277         }
278
279         if (unlink(filename) < 0)
280         {
281                 ereport(WARNING,
282                                 (errcode_for_file_access(),
283                                  errmsg("could not unlink file \"%s\": %m", filename)));
284
285                 PG_RETURN_BOOL(false);
286         }
287         PG_RETURN_BOOL(true);
288 }
289
290
291 Datum
292 pg_logdir_ls(PG_FUNCTION_ARGS)
293 {
294         FuncCallContext *funcctx;
295         struct dirent *de;
296         directory_fctx *fctx;
297
298         if (!superuser())
299                 ereport(ERROR,
300                                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
301                                  (errmsg("only superuser can list the log directory"))));
302
303         if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
304                 ereport(ERROR,
305                                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
306                                  (errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'"))));
307
308         if (SRF_IS_FIRSTCALL())
309         {
310                 MemoryContext oldcontext;
311                 TupleDesc       tupdesc;
312
313                 funcctx = SRF_FIRSTCALL_INIT();
314                 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
315
316                 fctx = palloc(sizeof(directory_fctx));
317
318                 tupdesc = CreateTemplateTupleDesc(2, false);
319                 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
320                                                    TIMESTAMPOID, -1, 0);
321                 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
322                                                    TEXTOID, -1, 0);
323
324                 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
325
326                 fctx->location = pstrdup(Log_directory);
327                 fctx->dirdesc = AllocateDir(fctx->location);
328
329                 if (!fctx->dirdesc)
330                         ereport(ERROR,
331                                         (errcode_for_file_access(),
332                                          errmsg("could not read directory \"%s\": %m",
333                                                         fctx->location)));
334
335                 funcctx->user_fctx = fctx;
336                 MemoryContextSwitchTo(oldcontext);
337         }
338
339         funcctx = SRF_PERCALL_SETUP();
340         fctx = (directory_fctx *) funcctx->user_fctx;
341
342         while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL)
343         {
344                 char       *values[2];
345                 HeapTuple       tuple;
346                 char            timestampbuf[32];
347                 char       *field[MAXDATEFIELDS];
348                 char            lowstr[MAXDATELEN + 1];
349                 int                     dtype;
350                 int                     nf,
351                                         ftype[MAXDATEFIELDS];
352                 fsec_t          fsec;
353                 int                     tz = 0;
354                 struct pg_tm date;
355
356                 /*
357                  * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
358                  */
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)
363                         continue;
364
365                 /* extract timestamp portion of filename */
366                 strcpy(timestampbuf, de->d_name + 11);
367                 timestampbuf[17] = '\0';
368
369                 /* parse and decode expected timestamp to verify it's OK format */
370                 if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
371                         continue;
372
373                 if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
374                         continue;
375
376                 /* Seems the timestamp is OK; prepare and return tuple */
377
378                 values[0] = timestampbuf;
379                 values[1] = psprintf("%s/%s", fctx->location, de->d_name);
380
381                 tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
382
383                 SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
384         }
385
386         FreeDir(fctx->dirdesc);
387         SRF_RETURN_DONE(funcctx);
388 }