/Users/deen/code/yugabyte-db/src/postgres/src/backend/utils/adt/genfile.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * genfile.c |
4 | | * Functions for direct access to files |
5 | | * |
6 | | * |
7 | | * Copyright (c) 2004-2018, PostgreSQL Global Development Group |
8 | | * |
9 | | * Author: Andreas Pflug <pgadmin@pse-consulting.de> |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/backend/utils/adt/genfile.c |
13 | | * |
14 | | *------------------------------------------------------------------------- |
15 | | */ |
16 | | #include "postgres.h" |
17 | | |
18 | | #include <sys/file.h> |
19 | | #include <sys/stat.h> |
20 | | #include <unistd.h> |
21 | | #include <dirent.h> |
22 | | |
23 | | #include "access/htup_details.h" |
24 | | #include "access/xlog_internal.h" |
25 | | #include "catalog/pg_authid.h" |
26 | | #include "catalog/pg_type.h" |
27 | | #include "funcapi.h" |
28 | | #include "mb/pg_wchar.h" |
29 | | #include "miscadmin.h" |
30 | | #include "postmaster/syslogger.h" |
31 | | #include "storage/fd.h" |
32 | | #include "utils/builtins.h" |
33 | | #include "utils/memutils.h" |
34 | | #include "utils/timestamp.h" |
35 | | |
36 | | typedef struct |
37 | | { |
38 | | char *location; |
39 | | DIR *dirdesc; |
40 | | bool include_dot_dirs; |
41 | | } directory_fctx; |
42 | | |
43 | | |
44 | | /* |
45 | | * Convert a "text" filename argument to C string, and check it's allowable. |
46 | | * |
47 | | * Filename may be absolute or relative to the DataDir, but we only allow |
48 | | * absolute paths that match DataDir or Log_directory. |
49 | | * |
50 | | * This does a privilege check against the 'pg_read_server_files' role, so |
51 | | * this function is really only appropriate for callers who are only checking |
52 | | * 'read' access. Do not use this function if you are looking for a check |
53 | | * for 'write' or 'program' access without updating it to access the type |
54 | | * of check as an argument and checking the appropriate role membership. |
55 | | */ |
56 | | static char * |
57 | | convert_and_check_filename(text *arg) |
58 | 18 | { |
59 | 18 | char *filename; |
60 | | |
61 | 18 | filename = text_to_cstring(arg); |
62 | 18 | canonicalize_path(filename); /* filename can change length here */ |
63 | | |
64 | | /* |
65 | | * Members of the 'pg_read_server_files' role are allowed to access any |
66 | | * files on the server as the PG user, so no need to do any further checks |
67 | | * here. |
68 | | */ |
69 | 18 | if (is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_SERVER_FILES)) |
70 | 10 | return filename; |
71 | | |
72 | | /* User isn't a member of the default role, so check if it's allowable */ |
73 | 8 | if (is_absolute_path(filename)) |
74 | 8 | { |
75 | | /* Disallow '/a/b/data/..' */ |
76 | 8 | if (path_contains_parent_reference(filename)) |
77 | 8 | ereport(ERROR, |
78 | 8 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
79 | 8 | (errmsg("reference to parent directory (\"..\") not allowed")))); |
80 | | |
81 | | /* |
82 | | * Allow absolute paths if within DataDir or Log_directory, even |
83 | | * though Log_directory might be outside DataDir. |
84 | | */ |
85 | 8 | if (!path_is_prefix_of_path(DataDir, filename) && |
86 | 8 | (!is_absolute_path(Log_directory) || |
87 | 0 | !path_is_prefix_of_path(Log_directory, filename))) |
88 | 8 | ereport(ERROR, |
89 | 8 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
90 | 8 | (errmsg("absolute path not allowed")))); |
91 | 8 | } |
92 | 0 | else if (!path_is_relative_and_below_cwd(filename)) |
93 | 0 | ereport(ERROR, |
94 | 8 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
95 | 8 | (errmsg("path must be in or below the current directory")))); |
96 | | |
97 | 8 | return filename; |
98 | 8 | } |
99 | | |
100 | | |
101 | | /* |
102 | | * Read a section of a file, returning it as bytea |
103 | | * |
104 | | * Caller is responsible for all permissions checking. |
105 | | * |
106 | | * We read the whole of the file when bytes_to_read is negative. |
107 | | */ |
108 | | static bytea * |
109 | | read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read, |
110 | | bool missing_ok) |
111 | 10 | { |
112 | 10 | bytea *buf; |
113 | 10 | size_t nbytes; |
114 | 10 | FILE *file; |
115 | | |
116 | 10 | if (bytes_to_read < 0) |
117 | 8 | { |
118 | 8 | if (seek_offset < 0) |
119 | 0 | bytes_to_read = -seek_offset; |
120 | 8 | else |
121 | 8 | { |
122 | 8 | struct stat fst; |
123 | | |
124 | 8 | if (stat(filename, &fst) < 0) |
125 | 2 | { |
126 | 2 | if (missing_ok && errno == ENOENT) |
127 | 0 | return NULL; |
128 | 2 | else |
129 | 2 | ereport(ERROR, |
130 | 2 | (errcode_for_file_access(), |
131 | 2 | errmsg("could not stat file \"%s\": %m", filename))); |
132 | 2 | } |
133 | | |
134 | 8 | bytes_to_read = fst.st_size - seek_offset; |
135 | 8 | } |
136 | 8 | } |
137 | | |
138 | | /* not sure why anyone thought that int64 length was a good idea */ |
139 | 10 | if (bytes_to_read > (MaxAllocSize - VARHDRSZ)) |
140 | 10 | ereport(ERROR, |
141 | 10 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
142 | 10 | errmsg("requested length too large"))); |
143 | | |
144 | 10 | if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) |
145 | 2 | { |
146 | 2 | if (missing_ok && errno == ENOENT) |
147 | 2 | return NULL; |
148 | 2 | else |
149 | 2 | ereport(ERROR, |
150 | 2 | (errcode_for_file_access(), |
151 | 2 | errmsg("could not open file \"%s\" for reading: %m", |
152 | 2 | filename))); |
153 | 2 | } |
154 | | |
155 | 8 | if (fseeko(file, (off_t) seek_offset, |
156 | 8 | (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0) |
157 | 8 | ereport(ERROR, |
158 | 8 | (errcode_for_file_access(), |
159 | 8 | errmsg("could not seek in file \"%s\": %m", filename))); |
160 | | |
161 | 8 | buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ); |
162 | | |
163 | 8 | nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file); |
164 | | |
165 | 8 | if (ferror(file)) |
166 | 8 | ereport(ERROR, |
167 | 8 | (errcode_for_file_access(), |
168 | 8 | errmsg("could not read file \"%s\": %m", filename))); |
169 | | |
170 | 8 | SET_VARSIZE(buf, nbytes + VARHDRSZ); |
171 | | |
172 | 8 | FreeFile(file); |
173 | | |
174 | 8 | return buf; |
175 | 8 | } |
176 | | |
177 | | /* |
178 | | * Similar to read_binary_file, but we verify that the contents are valid |
179 | | * in the database encoding. |
180 | | */ |
181 | | static text * |
182 | | read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read, |
183 | | bool missing_ok) |
184 | 10 | { |
185 | 10 | bytea *buf; |
186 | | |
187 | 10 | buf = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok); |
188 | | |
189 | 10 | if (buf != NULL) |
190 | 6 | { |
191 | | /* Make sure the input is valid */ |
192 | 6 | pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); |
193 | | |
194 | | /* OK, we can cast it to text safely */ |
195 | 6 | return (text *) buf; |
196 | 6 | } |
197 | 4 | else |
198 | 4 | return NULL; |
199 | 10 | } |
200 | | |
201 | | /* |
202 | | * Read a section of a file, returning it as text |
203 | | * |
204 | | * This function is kept to support adminpack 1.0. |
205 | | */ |
206 | | Datum |
207 | | pg_read_file(PG_FUNCTION_ARGS) |
208 | 0 | { |
209 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
210 | 0 | int64 seek_offset = 0; |
211 | 0 | int64 bytes_to_read = -1; |
212 | 0 | bool missing_ok = false; |
213 | 0 | char *filename; |
214 | 0 | text *result; |
215 | |
|
216 | 0 | if (!superuser()) |
217 | 0 | ereport(ERROR, |
218 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
219 | 0 | (errmsg("must be superuser to read files with adminpack 1.0"), |
220 | 0 | errhint("Consider using pg_file_read(), which is part of core, instead.")))); |
221 | | |
222 | | /* handle optional arguments */ |
223 | 0 | if (PG_NARGS() >= 3) |
224 | 0 | { |
225 | 0 | seek_offset = PG_GETARG_INT64(1); |
226 | 0 | bytes_to_read = PG_GETARG_INT64(2); |
227 | |
|
228 | 0 | if (bytes_to_read < 0) |
229 | 0 | ereport(ERROR, |
230 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
231 | 0 | errmsg("requested length cannot be negative"))); |
232 | 0 | } |
233 | 0 | if (PG_NARGS() >= 4) |
234 | 0 | missing_ok = PG_GETARG_BOOL(3); |
235 | |
|
236 | 0 | filename = convert_and_check_filename(filename_t); |
237 | |
|
238 | 0 | result = read_text_file(filename, seek_offset, bytes_to_read, missing_ok); |
239 | 0 | if (result) |
240 | 0 | PG_RETURN_TEXT_P(result); |
241 | 0 | else |
242 | 0 | PG_RETURN_NULL(); |
243 | 0 | } |
244 | | |
245 | | /* |
246 | | * Read a section of a file, returning it as text |
247 | | * |
248 | | * No superuser check done here- instead privileges are handled by the |
249 | | * GRANT system. |
250 | | */ |
251 | | Datum |
252 | | pg_read_file_v2(PG_FUNCTION_ARGS) |
253 | 18 | { |
254 | 18 | text *filename_t = PG_GETARG_TEXT_PP(0); |
255 | 18 | int64 seek_offset = 0; |
256 | 18 | int64 bytes_to_read = -1; |
257 | 18 | bool missing_ok = false; |
258 | 18 | char *filename; |
259 | 18 | text *result; |
260 | | |
261 | | /* handle optional arguments */ |
262 | 18 | if (PG_NARGS() >= 3) |
263 | 10 | { |
264 | 10 | seek_offset = PG_GETARG_INT64(1); |
265 | 10 | bytes_to_read = PG_GETARG_INT64(2); |
266 | | |
267 | 10 | if (bytes_to_read < 0) |
268 | 10 | ereport(ERROR, |
269 | 10 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
270 | 10 | errmsg("requested length cannot be negative"))); |
271 | 10 | } |
272 | 18 | if (PG_NARGS() >= 4) |
273 | 10 | missing_ok = PG_GETARG_BOOL(3); |
274 | | |
275 | 18 | filename = convert_and_check_filename(filename_t); |
276 | | |
277 | 18 | result = read_text_file(filename, seek_offset, bytes_to_read, missing_ok); |
278 | 18 | if (result) |
279 | 18 | PG_RETURN_TEXT_P(result); |
280 | 18 | else |
281 | 12 | PG_RETURN_NULL(); |
282 | 18 | } |
283 | | |
284 | | /* |
285 | | * Read a section of a file, returning it as bytea |
286 | | */ |
287 | | Datum |
288 | | pg_read_binary_file(PG_FUNCTION_ARGS) |
289 | 0 | { |
290 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
291 | 0 | int64 seek_offset = 0; |
292 | 0 | int64 bytes_to_read = -1; |
293 | 0 | bool missing_ok = false; |
294 | 0 | char *filename; |
295 | 0 | bytea *result; |
296 | | |
297 | | /* handle optional arguments */ |
298 | 0 | if (PG_NARGS() >= 3) |
299 | 0 | { |
300 | 0 | seek_offset = PG_GETARG_INT64(1); |
301 | 0 | bytes_to_read = PG_GETARG_INT64(2); |
302 | |
|
303 | 0 | if (bytes_to_read < 0) |
304 | 0 | ereport(ERROR, |
305 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
306 | 0 | errmsg("requested length cannot be negative"))); |
307 | 0 | } |
308 | 0 | if (PG_NARGS() >= 4) |
309 | 0 | missing_ok = PG_GETARG_BOOL(3); |
310 | |
|
311 | 0 | filename = convert_and_check_filename(filename_t); |
312 | |
|
313 | 0 | result = read_binary_file(filename, seek_offset, |
314 | 0 | bytes_to_read, missing_ok); |
315 | 0 | if (result) |
316 | 0 | PG_RETURN_BYTEA_P(result); |
317 | 0 | else |
318 | 0 | PG_RETURN_NULL(); |
319 | 0 | } |
320 | | |
321 | | |
322 | | /* |
323 | | * Wrapper functions for the 1 and 3 argument variants of pg_read_file_v2() |
324 | | * and pg_binary_read_file(). |
325 | | * |
326 | | * These are necessary to pass the sanity check in opr_sanity, which checks |
327 | | * that all built-in functions that share the implementing C function take |
328 | | * the same number of arguments. |
329 | | */ |
330 | | Datum |
331 | | pg_read_file_off_len(PG_FUNCTION_ARGS) |
332 | 0 | { |
333 | 0 | return pg_read_file_v2(fcinfo); |
334 | 0 | } |
335 | | |
336 | | Datum |
337 | | pg_read_file_all(PG_FUNCTION_ARGS) |
338 | 8 | { |
339 | 8 | return pg_read_file_v2(fcinfo); |
340 | 8 | } |
341 | | |
342 | | Datum |
343 | | pg_read_binary_file_off_len(PG_FUNCTION_ARGS) |
344 | 0 | { |
345 | 0 | return pg_read_binary_file(fcinfo); |
346 | 0 | } |
347 | | |
348 | | Datum |
349 | | pg_read_binary_file_all(PG_FUNCTION_ARGS) |
350 | 0 | { |
351 | 0 | return pg_read_binary_file(fcinfo); |
352 | 0 | } |
353 | | |
354 | | /* |
355 | | * stat a file |
356 | | */ |
357 | | Datum |
358 | | pg_stat_file(PG_FUNCTION_ARGS) |
359 | 0 | { |
360 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
361 | 0 | char *filename; |
362 | 0 | struct stat fst; |
363 | 0 | Datum values[6]; |
364 | 0 | bool isnull[6]; |
365 | 0 | HeapTuple tuple; |
366 | 0 | TupleDesc tupdesc; |
367 | 0 | bool missing_ok = false; |
368 | | |
369 | | /* check the optional argument */ |
370 | 0 | if (PG_NARGS() == 2) |
371 | 0 | missing_ok = PG_GETARG_BOOL(1); |
372 | |
|
373 | 0 | filename = convert_and_check_filename(filename_t); |
374 | |
|
375 | 0 | if (stat(filename, &fst) < 0) |
376 | 0 | { |
377 | 0 | if (missing_ok && errno == ENOENT) |
378 | 0 | PG_RETURN_NULL(); |
379 | 0 | else |
380 | 0 | ereport(ERROR, |
381 | 0 | (errcode_for_file_access(), |
382 | 0 | errmsg("could not stat file \"%s\": %m", filename))); |
383 | 0 | } |
384 | | |
385 | | /* |
386 | | * This record type had better match the output parameters declared for me |
387 | | * in pg_proc.h. |
388 | | */ |
389 | 0 | tupdesc = CreateTemplateTupleDesc(6, false); |
390 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, |
391 | 0 | "size", INT8OID, -1, 0); |
392 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, |
393 | 0 | "access", TIMESTAMPTZOID, -1, 0); |
394 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, |
395 | 0 | "modification", TIMESTAMPTZOID, -1, 0); |
396 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 4, |
397 | 0 | "change", TIMESTAMPTZOID, -1, 0); |
398 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 5, |
399 | 0 | "creation", TIMESTAMPTZOID, -1, 0); |
400 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 6, |
401 | 0 | "isdir", BOOLOID, -1, 0); |
402 | 0 | BlessTupleDesc(tupdesc); |
403 | |
|
404 | 0 | memset(isnull, false, sizeof(isnull)); |
405 | |
|
406 | 0 | values[0] = Int64GetDatum((int64) fst.st_size); |
407 | 0 | values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime)); |
408 | 0 | values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime)); |
409 | | /* Unix has file status change time, while Win32 has creation time */ |
410 | 0 | #if !defined(WIN32) && !defined(__CYGWIN__) |
411 | 0 | values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); |
412 | 0 | isnull[4] = true; |
413 | | #else |
414 | | isnull[3] = true; |
415 | | values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); |
416 | | #endif |
417 | 0 | values[5] = BoolGetDatum(S_ISDIR(fst.st_mode)); |
418 | |
|
419 | 0 | tuple = heap_form_tuple(tupdesc, values, isnull); |
420 | |
|
421 | 0 | pfree(filename); |
422 | |
|
423 | 0 | PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); |
424 | 0 | } |
425 | | |
426 | | /* |
427 | | * stat a file (1 argument version) |
428 | | * |
429 | | * note: this wrapper is necessary to pass the sanity check in opr_sanity, |
430 | | * which checks that all built-in functions that share the implementing C |
431 | | * function take the same number of arguments |
432 | | */ |
433 | | Datum |
434 | | pg_stat_file_1arg(PG_FUNCTION_ARGS) |
435 | 0 | { |
436 | 0 | return pg_stat_file(fcinfo); |
437 | 0 | } |
438 | | |
439 | | /* |
440 | | * List a directory (returns the filenames only) |
441 | | */ |
442 | | Datum |
443 | | pg_ls_dir(PG_FUNCTION_ARGS) |
444 | 0 | { |
445 | 0 | FuncCallContext *funcctx; |
446 | 0 | struct dirent *de; |
447 | 0 | directory_fctx *fctx; |
448 | 0 | MemoryContext oldcontext; |
449 | |
|
450 | 0 | if (SRF_IS_FIRSTCALL()) |
451 | 0 | { |
452 | 0 | bool missing_ok = false; |
453 | 0 | bool include_dot_dirs = false; |
454 | | |
455 | | /* check the optional arguments */ |
456 | 0 | if (PG_NARGS() == 3) |
457 | 0 | { |
458 | 0 | if (!PG_ARGISNULL(1)) |
459 | 0 | missing_ok = PG_GETARG_BOOL(1); |
460 | 0 | if (!PG_ARGISNULL(2)) |
461 | 0 | include_dot_dirs = PG_GETARG_BOOL(2); |
462 | 0 | } |
463 | |
|
464 | 0 | funcctx = SRF_FIRSTCALL_INIT(); |
465 | 0 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
466 | |
|
467 | 0 | fctx = palloc(sizeof(directory_fctx)); |
468 | 0 | fctx->location = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); |
469 | |
|
470 | 0 | fctx->include_dot_dirs = include_dot_dirs; |
471 | 0 | fctx->dirdesc = AllocateDir(fctx->location); |
472 | |
|
473 | 0 | if (!fctx->dirdesc) |
474 | 0 | { |
475 | 0 | if (missing_ok && errno == ENOENT) |
476 | 0 | { |
477 | 0 | MemoryContextSwitchTo(oldcontext); |
478 | 0 | SRF_RETURN_DONE(funcctx); |
479 | 0 | } |
480 | 0 | else |
481 | 0 | ereport(ERROR, |
482 | 0 | (errcode_for_file_access(), |
483 | 0 | errmsg("could not open directory \"%s\": %m", |
484 | 0 | fctx->location))); |
485 | 0 | } |
486 | 0 | funcctx->user_fctx = fctx; |
487 | 0 | MemoryContextSwitchTo(oldcontext); |
488 | 0 | } |
489 | |
|
490 | 0 | funcctx = SRF_PERCALL_SETUP(); |
491 | 0 | fctx = (directory_fctx *) funcctx->user_fctx; |
492 | |
|
493 | 0 | while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) |
494 | 0 | { |
495 | 0 | if (!fctx->include_dot_dirs && |
496 | 0 | (strcmp(de->d_name, ".") == 0 || |
497 | 0 | strcmp(de->d_name, "..") == 0)) |
498 | 0 | continue; |
499 | | |
500 | 0 | SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(de->d_name)); |
501 | 0 | } |
502 | |
|
503 | 0 | FreeDir(fctx->dirdesc); |
504 | |
|
505 | 0 | SRF_RETURN_DONE(funcctx); |
506 | 0 | } |
507 | | |
508 | | /* |
509 | | * List a directory (1 argument version) |
510 | | * |
511 | | * note: this wrapper is necessary to pass the sanity check in opr_sanity, |
512 | | * which checks that all built-in functions that share the implementing C |
513 | | * function take the same number of arguments. |
514 | | */ |
515 | | Datum |
516 | | pg_ls_dir_1arg(PG_FUNCTION_ARGS) |
517 | 0 | { |
518 | 0 | return pg_ls_dir(fcinfo); |
519 | 0 | } |
520 | | |
521 | | /* Generic function to return a directory listing of files */ |
522 | | static Datum |
523 | | pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir) |
524 | 2 | { |
525 | 2 | FuncCallContext *funcctx; |
526 | 2 | struct dirent *de; |
527 | 2 | directory_fctx *fctx; |
528 | | |
529 | 2 | if (SRF_IS_FIRSTCALL()) |
530 | 1 | { |
531 | 1 | MemoryContext oldcontext; |
532 | 1 | TupleDesc tupdesc; |
533 | | |
534 | 1 | funcctx = SRF_FIRSTCALL_INIT(); |
535 | 1 | oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); |
536 | | |
537 | 1 | fctx = palloc(sizeof(directory_fctx)); |
538 | | |
539 | 1 | tupdesc = CreateTemplateTupleDesc(3, false); |
540 | 1 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", |
541 | 1 | TEXTOID, -1, 0); |
542 | 1 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, "size", |
543 | 1 | INT8OID, -1, 0); |
544 | 1 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, "modification", |
545 | 1 | TIMESTAMPTZOID, -1, 0); |
546 | 1 | funcctx->tuple_desc = BlessTupleDesc(tupdesc); |
547 | | |
548 | 1 | fctx->location = pstrdup(dir); |
549 | 1 | fctx->dirdesc = AllocateDir(fctx->location); |
550 | | |
551 | 1 | if (!fctx->dirdesc) |
552 | 1 | ereport(ERROR, |
553 | 1 | (errcode_for_file_access(), |
554 | 1 | errmsg("could not open directory \"%s\": %m", |
555 | 1 | fctx->location))); |
556 | | |
557 | 1 | funcctx->user_fctx = fctx; |
558 | 1 | MemoryContextSwitchTo(oldcontext); |
559 | 1 | } |
560 | | |
561 | 2 | funcctx = SRF_PERCALL_SETUP(); |
562 | 2 | fctx = (directory_fctx *) funcctx->user_fctx; |
563 | | |
564 | 5 | while ((de = ReadDir(fctx->dirdesc, fctx->location)) != NULL) |
565 | 4 | { |
566 | 4 | Datum values[3]; |
567 | 4 | bool nulls[3]; |
568 | 4 | char path[MAXPGPATH * 2]; |
569 | 4 | struct stat attrib; |
570 | 4 | HeapTuple tuple; |
571 | | |
572 | | /* Skip hidden files */ |
573 | 4 | if (de->d_name[0] == '.') |
574 | 2 | continue; |
575 | | |
576 | | /* Get the file info */ |
577 | 2 | snprintf(path, sizeof(path), "%s/%s", fctx->location, de->d_name); |
578 | 2 | if (stat(path, &attrib) < 0) |
579 | 2 | ereport(ERROR, |
580 | 2 | (errcode_for_file_access(), |
581 | 2 | errmsg("could not stat directory \"%s\": %m", dir))); |
582 | | |
583 | | /* Ignore anything but regular files */ |
584 | 2 | if (!S_ISREG(attrib.st_mode)) |
585 | 1 | continue; |
586 | | |
587 | 1 | values[0] = CStringGetTextDatum(de->d_name); |
588 | 1 | values[1] = Int64GetDatum((int64) attrib.st_size); |
589 | 1 | values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime)); |
590 | 1 | memset(nulls, 0, sizeof(nulls)); |
591 | | |
592 | 1 | tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); |
593 | 1 | SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); |
594 | 1 | } |
595 | | |
596 | 1 | FreeDir(fctx->dirdesc); |
597 | 1 | SRF_RETURN_DONE(funcctx); |
598 | 1 | } |
599 | | |
600 | | /* Function to return the list of files in the log directory */ |
601 | | Datum |
602 | | pg_ls_logdir(PG_FUNCTION_ARGS) |
603 | 0 | { |
604 | 0 | return pg_ls_dir_files(fcinfo, Log_directory); |
605 | 0 | } |
606 | | |
607 | | /* Function to return the list of files in the WAL directory */ |
608 | | Datum |
609 | | pg_ls_waldir(PG_FUNCTION_ARGS) |
610 | 2 | { |
611 | 2 | return pg_ls_dir_files(fcinfo, XLOGDIR); |
612 | 2 | } |