/src/postgres/src/backend/utils/adt/genfile.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * genfile.c |
4 | | * Functions for direct access to files |
5 | | * |
6 | | * |
7 | | * Copyright (c) 2004-2026, 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_tablespace_d.h" |
27 | | #include "catalog/pg_type.h" |
28 | | #include "funcapi.h" |
29 | | #include "mb/pg_wchar.h" |
30 | | #include "miscadmin.h" |
31 | | #include "postmaster/syslogger.h" |
32 | | #include "replication/slot.h" |
33 | | #include "storage/fd.h" |
34 | | #include "utils/acl.h" |
35 | | #include "utils/builtins.h" |
36 | | #include "utils/memutils.h" |
37 | | #include "utils/syscache.h" |
38 | | #include "utils/timestamp.h" |
39 | | |
40 | | |
41 | | /* |
42 | | * Convert a "text" filename argument to C string, and check it's allowable. |
43 | | * |
44 | | * Filename may be absolute or relative to the DataDir, but we only allow |
45 | | * absolute paths that match DataDir or Log_directory. |
46 | | * |
47 | | * This does a privilege check against the 'pg_read_server_files' role, so |
48 | | * this function is really only appropriate for callers who are only checking |
49 | | * 'read' access. Do not use this function if you are looking for a check |
50 | | * for 'write' or 'program' access without updating it to access the type |
51 | | * of check as an argument and checking the appropriate role membership. |
52 | | */ |
53 | | static char * |
54 | | convert_and_check_filename(text *arg) |
55 | 0 | { |
56 | 0 | char *filename; |
57 | |
|
58 | 0 | filename = text_to_cstring(arg); |
59 | 0 | canonicalize_path(filename); /* filename can change length here */ |
60 | | |
61 | | /* |
62 | | * Roles with privileges of the 'pg_read_server_files' role are allowed to |
63 | | * access any files on the server as the PG user, so no need to do any |
64 | | * further checks here. |
65 | | */ |
66 | 0 | if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES)) |
67 | 0 | return filename; |
68 | | |
69 | | /* |
70 | | * User isn't a member of the pg_read_server_files role, so check if it's |
71 | | * allowable |
72 | | */ |
73 | 0 | if (is_absolute_path(filename)) |
74 | 0 | { |
75 | | /* |
76 | | * Allow absolute paths if within DataDir or Log_directory, even |
77 | | * though Log_directory might be outside DataDir. |
78 | | */ |
79 | 0 | if (!path_is_prefix_of_path(DataDir, filename) && |
80 | 0 | (!is_absolute_path(Log_directory) || |
81 | 0 | !path_is_prefix_of_path(Log_directory, filename))) |
82 | 0 | ereport(ERROR, |
83 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
84 | 0 | errmsg("absolute path not allowed"))); |
85 | 0 | } |
86 | 0 | else if (!path_is_relative_and_below_cwd(filename)) |
87 | 0 | ereport(ERROR, |
88 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
89 | 0 | errmsg("path must be in or below the data directory"))); |
90 | | |
91 | 0 | return filename; |
92 | 0 | } |
93 | | |
94 | | |
95 | | /* |
96 | | * Read a section of a file, returning it as bytea |
97 | | * |
98 | | * Caller is responsible for all permissions checking. |
99 | | * |
100 | | * We read the whole of the file when bytes_to_read is negative. |
101 | | */ |
102 | | static bytea * |
103 | | read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read, |
104 | | bool missing_ok) |
105 | 0 | { |
106 | 0 | bytea *buf; |
107 | 0 | size_t nbytes = 0; |
108 | 0 | FILE *file; |
109 | | |
110 | | /* clamp request size to what we can actually deliver */ |
111 | 0 | if (bytes_to_read > (int64) (MaxAllocSize - VARHDRSZ)) |
112 | 0 | ereport(ERROR, |
113 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
114 | 0 | errmsg("requested length too large"))); |
115 | | |
116 | 0 | if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) |
117 | 0 | { |
118 | 0 | if (missing_ok && errno == ENOENT) |
119 | 0 | return NULL; |
120 | 0 | else |
121 | 0 | ereport(ERROR, |
122 | 0 | (errcode_for_file_access(), |
123 | 0 | errmsg("could not open file \"%s\" for reading: %m", |
124 | 0 | filename))); |
125 | 0 | } |
126 | | |
127 | 0 | if (fseeko(file, (off_t) seek_offset, |
128 | 0 | (seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0) |
129 | 0 | ereport(ERROR, |
130 | 0 | (errcode_for_file_access(), |
131 | 0 | errmsg("could not seek in file \"%s\": %m", filename))); |
132 | | |
133 | 0 | if (bytes_to_read >= 0) |
134 | 0 | { |
135 | | /* If passed explicit read size just do it */ |
136 | 0 | buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ); |
137 | |
|
138 | 0 | nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file); |
139 | 0 | } |
140 | 0 | else |
141 | 0 | { |
142 | | /* Negative read size, read rest of file */ |
143 | 0 | StringInfoData sbuf; |
144 | |
|
145 | 0 | initStringInfo(&sbuf); |
146 | | /* Leave room in the buffer for the varlena length word */ |
147 | 0 | sbuf.len += VARHDRSZ; |
148 | 0 | Assert(sbuf.len < sbuf.maxlen); |
149 | |
|
150 | 0 | while (!(feof(file) || ferror(file))) |
151 | 0 | { |
152 | 0 | size_t rbytes; |
153 | | |
154 | | /* Minimum amount to read at a time */ |
155 | 0 | #define MIN_READ_SIZE 4096 |
156 | | |
157 | | /* |
158 | | * If not at end of file, and sbuf.len is equal to MaxAllocSize - |
159 | | * 1, then either the file is too large, or there is nothing left |
160 | | * to read. Attempt to read one more byte to see if the end of |
161 | | * file has been reached. If not, the file is too large; we'd |
162 | | * rather give the error message for that ourselves. |
163 | | */ |
164 | 0 | if (sbuf.len == MaxAllocSize - 1) |
165 | 0 | { |
166 | 0 | char rbuf[1]; |
167 | |
|
168 | 0 | if (fread(rbuf, 1, 1, file) != 0 || !feof(file)) |
169 | 0 | ereport(ERROR, |
170 | 0 | (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
171 | 0 | errmsg("file length too large"))); |
172 | 0 | else |
173 | 0 | break; |
174 | 0 | } |
175 | | |
176 | | /* OK, ensure that we can read at least MIN_READ_SIZE */ |
177 | 0 | enlargeStringInfo(&sbuf, MIN_READ_SIZE); |
178 | | |
179 | | /* |
180 | | * stringinfo.c likes to allocate in powers of 2, so it's likely |
181 | | * that much more space is available than we asked for. Use all |
182 | | * of it, rather than making more fread calls than necessary. |
183 | | */ |
184 | 0 | rbytes = fread(sbuf.data + sbuf.len, 1, |
185 | 0 | (size_t) (sbuf.maxlen - sbuf.len - 1), file); |
186 | 0 | sbuf.len += rbytes; |
187 | 0 | nbytes += rbytes; |
188 | 0 | } |
189 | | |
190 | | /* Now we can commandeer the stringinfo's buffer as the result */ |
191 | 0 | buf = (bytea *) sbuf.data; |
192 | 0 | } |
193 | | |
194 | 0 | if (ferror(file)) |
195 | 0 | ereport(ERROR, |
196 | 0 | (errcode_for_file_access(), |
197 | 0 | errmsg("could not read file \"%s\": %m", filename))); |
198 | | |
199 | 0 | SET_VARSIZE(buf, nbytes + VARHDRSZ); |
200 | |
|
201 | 0 | FreeFile(file); |
202 | |
|
203 | 0 | return buf; |
204 | 0 | } |
205 | | |
206 | | /* |
207 | | * Similar to read_binary_file, but we verify that the contents are valid |
208 | | * in the database encoding. |
209 | | */ |
210 | | static text * |
211 | | read_text_file(const char *filename, int64 seek_offset, int64 bytes_to_read, |
212 | | bool missing_ok) |
213 | 0 | { |
214 | 0 | bytea *buf; |
215 | |
|
216 | 0 | buf = read_binary_file(filename, seek_offset, bytes_to_read, missing_ok); |
217 | |
|
218 | 0 | if (buf != NULL) |
219 | 0 | { |
220 | | /* Make sure the input is valid */ |
221 | 0 | pg_verifymbstr(VARDATA(buf), VARSIZE(buf) - VARHDRSZ, false); |
222 | | |
223 | | /* OK, we can cast it to text safely */ |
224 | 0 | return (text *) buf; |
225 | 0 | } |
226 | 0 | else |
227 | 0 | return NULL; |
228 | 0 | } |
229 | | |
230 | | /* |
231 | | * Read a section of a file, returning it as text |
232 | | * |
233 | | * No superuser check done here- instead privileges are handled by the |
234 | | * GRANT system. |
235 | | * |
236 | | * If read_to_eof is true, bytes_to_read must be -1, otherwise negative values |
237 | | * are not allowed for bytes_to_read. |
238 | | */ |
239 | | static text * |
240 | | pg_read_file_common(text *filename_t, int64 seek_offset, int64 bytes_to_read, |
241 | | bool read_to_eof, bool missing_ok) |
242 | 0 | { |
243 | 0 | if (read_to_eof) |
244 | 0 | Assert(bytes_to_read == -1); |
245 | 0 | else if (bytes_to_read < 0) |
246 | 0 | ereport(ERROR, |
247 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
248 | 0 | errmsg("requested length cannot be negative"))); |
249 | | |
250 | 0 | return read_text_file(convert_and_check_filename(filename_t), |
251 | 0 | seek_offset, bytes_to_read, missing_ok); |
252 | 0 | } |
253 | | |
254 | | /* |
255 | | * Read a section of a file, returning it as bytea |
256 | | * |
257 | | * Parameters are interpreted the same as pg_read_file_common(). |
258 | | */ |
259 | | static bytea * |
260 | | pg_read_binary_file_common(text *filename_t, |
261 | | int64 seek_offset, int64 bytes_to_read, |
262 | | bool read_to_eof, bool missing_ok) |
263 | 0 | { |
264 | 0 | if (read_to_eof) |
265 | 0 | Assert(bytes_to_read == -1); |
266 | 0 | else if (bytes_to_read < 0) |
267 | 0 | ereport(ERROR, |
268 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
269 | 0 | errmsg("requested length cannot be negative"))); |
270 | | |
271 | 0 | return read_binary_file(convert_and_check_filename(filename_t), |
272 | 0 | seek_offset, bytes_to_read, missing_ok); |
273 | 0 | } |
274 | | |
275 | | |
276 | | /* |
277 | | * Wrapper functions for the variants of SQL functions pg_read_file() and |
278 | | * pg_read_binary_file(). |
279 | | * |
280 | | * These are necessary to pass the sanity check in opr_sanity, which checks |
281 | | * that all built-in functions that share the implementing C function take |
282 | | * the same number of arguments. |
283 | | */ |
284 | | Datum |
285 | | pg_read_file_off_len(PG_FUNCTION_ARGS) |
286 | 0 | { |
287 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
288 | 0 | int64 seek_offset = PG_GETARG_INT64(1); |
289 | 0 | int64 bytes_to_read = PG_GETARG_INT64(2); |
290 | 0 | text *ret; |
291 | |
|
292 | 0 | ret = pg_read_file_common(filename_t, seek_offset, bytes_to_read, |
293 | 0 | false, false); |
294 | 0 | if (!ret) |
295 | 0 | PG_RETURN_NULL(); |
296 | | |
297 | 0 | PG_RETURN_TEXT_P(ret); |
298 | 0 | } |
299 | | |
300 | | Datum |
301 | | pg_read_file_off_len_missing(PG_FUNCTION_ARGS) |
302 | 0 | { |
303 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
304 | 0 | int64 seek_offset = PG_GETARG_INT64(1); |
305 | 0 | int64 bytes_to_read = PG_GETARG_INT64(2); |
306 | 0 | bool missing_ok = PG_GETARG_BOOL(3); |
307 | 0 | text *ret; |
308 | |
|
309 | 0 | ret = pg_read_file_common(filename_t, seek_offset, bytes_to_read, |
310 | 0 | false, missing_ok); |
311 | |
|
312 | 0 | if (!ret) |
313 | 0 | PG_RETURN_NULL(); |
314 | | |
315 | 0 | PG_RETURN_TEXT_P(ret); |
316 | 0 | } |
317 | | |
318 | | Datum |
319 | | pg_read_file_all(PG_FUNCTION_ARGS) |
320 | 0 | { |
321 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
322 | 0 | text *ret; |
323 | |
|
324 | 0 | ret = pg_read_file_common(filename_t, 0, -1, true, false); |
325 | |
|
326 | 0 | if (!ret) |
327 | 0 | PG_RETURN_NULL(); |
328 | | |
329 | 0 | PG_RETURN_TEXT_P(ret); |
330 | 0 | } |
331 | | |
332 | | Datum |
333 | | pg_read_file_all_missing(PG_FUNCTION_ARGS) |
334 | 0 | { |
335 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
336 | 0 | bool missing_ok = PG_GETARG_BOOL(1); |
337 | 0 | text *ret; |
338 | |
|
339 | 0 | ret = pg_read_file_common(filename_t, 0, -1, true, missing_ok); |
340 | |
|
341 | 0 | if (!ret) |
342 | 0 | PG_RETURN_NULL(); |
343 | | |
344 | 0 | PG_RETURN_TEXT_P(ret); |
345 | 0 | } |
346 | | |
347 | | Datum |
348 | | pg_read_binary_file_off_len(PG_FUNCTION_ARGS) |
349 | 0 | { |
350 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
351 | 0 | int64 seek_offset = PG_GETARG_INT64(1); |
352 | 0 | int64 bytes_to_read = PG_GETARG_INT64(2); |
353 | 0 | text *ret; |
354 | |
|
355 | 0 | ret = pg_read_binary_file_common(filename_t, seek_offset, bytes_to_read, |
356 | 0 | false, false); |
357 | 0 | if (!ret) |
358 | 0 | PG_RETURN_NULL(); |
359 | | |
360 | 0 | PG_RETURN_BYTEA_P(ret); |
361 | 0 | } |
362 | | |
363 | | Datum |
364 | | pg_read_binary_file_off_len_missing(PG_FUNCTION_ARGS) |
365 | 0 | { |
366 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
367 | 0 | int64 seek_offset = PG_GETARG_INT64(1); |
368 | 0 | int64 bytes_to_read = PG_GETARG_INT64(2); |
369 | 0 | bool missing_ok = PG_GETARG_BOOL(3); |
370 | 0 | text *ret; |
371 | |
|
372 | 0 | ret = pg_read_binary_file_common(filename_t, seek_offset, bytes_to_read, |
373 | 0 | false, missing_ok); |
374 | 0 | if (!ret) |
375 | 0 | PG_RETURN_NULL(); |
376 | | |
377 | 0 | PG_RETURN_BYTEA_P(ret); |
378 | 0 | } |
379 | | |
380 | | Datum |
381 | | pg_read_binary_file_all(PG_FUNCTION_ARGS) |
382 | 0 | { |
383 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
384 | 0 | text *ret; |
385 | |
|
386 | 0 | ret = pg_read_binary_file_common(filename_t, 0, -1, true, false); |
387 | |
|
388 | 0 | if (!ret) |
389 | 0 | PG_RETURN_NULL(); |
390 | | |
391 | 0 | PG_RETURN_BYTEA_P(ret); |
392 | 0 | } |
393 | | |
394 | | Datum |
395 | | pg_read_binary_file_all_missing(PG_FUNCTION_ARGS) |
396 | 0 | { |
397 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
398 | 0 | bool missing_ok = PG_GETARG_BOOL(1); |
399 | 0 | text *ret; |
400 | |
|
401 | 0 | ret = pg_read_binary_file_common(filename_t, 0, -1, true, missing_ok); |
402 | |
|
403 | 0 | if (!ret) |
404 | 0 | PG_RETURN_NULL(); |
405 | | |
406 | 0 | PG_RETURN_BYTEA_P(ret); |
407 | 0 | } |
408 | | |
409 | | /* |
410 | | * stat a file |
411 | | */ |
412 | | Datum |
413 | | pg_stat_file(PG_FUNCTION_ARGS) |
414 | 0 | { |
415 | 0 | text *filename_t = PG_GETARG_TEXT_PP(0); |
416 | 0 | char *filename; |
417 | 0 | struct stat fst; |
418 | 0 | Datum values[6]; |
419 | 0 | bool isnull[6]; |
420 | 0 | HeapTuple tuple; |
421 | 0 | TupleDesc tupdesc; |
422 | 0 | bool missing_ok = false; |
423 | | |
424 | | /* check the optional argument */ |
425 | 0 | if (PG_NARGS() == 2) |
426 | 0 | missing_ok = PG_GETARG_BOOL(1); |
427 | |
|
428 | 0 | filename = convert_and_check_filename(filename_t); |
429 | |
|
430 | 0 | if (stat(filename, &fst) < 0) |
431 | 0 | { |
432 | 0 | if (missing_ok && errno == ENOENT) |
433 | 0 | PG_RETURN_NULL(); |
434 | 0 | else |
435 | 0 | ereport(ERROR, |
436 | 0 | (errcode_for_file_access(), |
437 | 0 | errmsg("could not stat file \"%s\": %m", filename))); |
438 | 0 | } |
439 | | |
440 | | /* |
441 | | * This record type had better match the output parameters declared for me |
442 | | * in pg_proc.h. |
443 | | */ |
444 | 0 | tupdesc = CreateTemplateTupleDesc(6); |
445 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 1, |
446 | 0 | "size", INT8OID, -1, 0); |
447 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 2, |
448 | 0 | "access", TIMESTAMPTZOID, -1, 0); |
449 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 3, |
450 | 0 | "modification", TIMESTAMPTZOID, -1, 0); |
451 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 4, |
452 | 0 | "change", TIMESTAMPTZOID, -1, 0); |
453 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 5, |
454 | 0 | "creation", TIMESTAMPTZOID, -1, 0); |
455 | 0 | TupleDescInitEntry(tupdesc, (AttrNumber) 6, |
456 | 0 | "isdir", BOOLOID, -1, 0); |
457 | 0 | TupleDescFinalize(tupdesc); |
458 | 0 | BlessTupleDesc(tupdesc); |
459 | |
|
460 | 0 | memset(isnull, false, sizeof(isnull)); |
461 | |
|
462 | 0 | values[0] = Int64GetDatum((int64) fst.st_size); |
463 | 0 | values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_atime)); |
464 | 0 | values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_mtime)); |
465 | | /* Unix has file status change time, while Win32 has creation time */ |
466 | 0 | #if !defined(WIN32) && !defined(__CYGWIN__) |
467 | 0 | values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); |
468 | 0 | isnull[4] = true; |
469 | | #else |
470 | | isnull[3] = true; |
471 | | values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fst.st_ctime)); |
472 | | #endif |
473 | 0 | values[5] = BoolGetDatum(S_ISDIR(fst.st_mode)); |
474 | |
|
475 | 0 | tuple = heap_form_tuple(tupdesc, values, isnull); |
476 | |
|
477 | 0 | pfree(filename); |
478 | |
|
479 | 0 | PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); |
480 | 0 | } |
481 | | |
482 | | /* |
483 | | * stat a file (1 argument version) |
484 | | * |
485 | | * note: this wrapper is necessary to pass the sanity check in opr_sanity, |
486 | | * which checks that all built-in functions that share the implementing C |
487 | | * function take the same number of arguments |
488 | | */ |
489 | | Datum |
490 | | pg_stat_file_1arg(PG_FUNCTION_ARGS) |
491 | 0 | { |
492 | 0 | return pg_stat_file(fcinfo); |
493 | 0 | } |
494 | | |
495 | | /* |
496 | | * List a directory (returns the filenames only) |
497 | | */ |
498 | | Datum |
499 | | pg_ls_dir(PG_FUNCTION_ARGS) |
500 | 0 | { |
501 | 0 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
502 | 0 | char *location; |
503 | 0 | bool missing_ok = false; |
504 | 0 | bool include_dot_dirs = false; |
505 | 0 | DIR *dirdesc; |
506 | 0 | struct dirent *de; |
507 | |
|
508 | 0 | location = convert_and_check_filename(PG_GETARG_TEXT_PP(0)); |
509 | | |
510 | | /* check the optional arguments */ |
511 | 0 | if (PG_NARGS() == 3) |
512 | 0 | { |
513 | 0 | if (!PG_ARGISNULL(1)) |
514 | 0 | missing_ok = PG_GETARG_BOOL(1); |
515 | 0 | if (!PG_ARGISNULL(2)) |
516 | 0 | include_dot_dirs = PG_GETARG_BOOL(2); |
517 | 0 | } |
518 | |
|
519 | 0 | InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC); |
520 | |
|
521 | 0 | dirdesc = AllocateDir(location); |
522 | 0 | if (!dirdesc) |
523 | 0 | { |
524 | | /* Return empty tuplestore if appropriate */ |
525 | 0 | if (missing_ok && errno == ENOENT) |
526 | 0 | return (Datum) 0; |
527 | | /* Otherwise, we can let ReadDir() throw the error */ |
528 | 0 | } |
529 | | |
530 | 0 | while ((de = ReadDir(dirdesc, location)) != NULL) |
531 | 0 | { |
532 | 0 | Datum values[1]; |
533 | 0 | bool nulls[1]; |
534 | |
|
535 | 0 | if (!include_dot_dirs && |
536 | 0 | (strcmp(de->d_name, ".") == 0 || |
537 | 0 | strcmp(de->d_name, "..") == 0)) |
538 | 0 | continue; |
539 | | |
540 | 0 | values[0] = CStringGetTextDatum(de->d_name); |
541 | 0 | nulls[0] = false; |
542 | |
|
543 | 0 | tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, |
544 | 0 | values, nulls); |
545 | 0 | } |
546 | |
|
547 | 0 | FreeDir(dirdesc); |
548 | 0 | return (Datum) 0; |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * List a directory (1 argument version) |
553 | | * |
554 | | * note: this wrapper is necessary to pass the sanity check in opr_sanity, |
555 | | * which checks that all built-in functions that share the implementing C |
556 | | * function take the same number of arguments. |
557 | | */ |
558 | | Datum |
559 | | pg_ls_dir_1arg(PG_FUNCTION_ARGS) |
560 | 0 | { |
561 | 0 | return pg_ls_dir(fcinfo); |
562 | 0 | } |
563 | | |
564 | | /* |
565 | | * Generic function to return a directory listing of files. |
566 | | * |
567 | | * If the directory isn't there, silently return an empty set if missing_ok. |
568 | | * Other unreadable-directory cases throw an error. |
569 | | */ |
570 | | static Datum |
571 | | pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) |
572 | 0 | { |
573 | 0 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
574 | 0 | DIR *dirdesc; |
575 | 0 | struct dirent *de; |
576 | |
|
577 | 0 | InitMaterializedSRF(fcinfo, 0); |
578 | | |
579 | | /* |
580 | | * Now walk the directory. Note that we must do this within a single SRF |
581 | | * call, not leave the directory open across multiple calls, since we |
582 | | * can't count on the SRF being run to completion. |
583 | | */ |
584 | 0 | dirdesc = AllocateDir(dir); |
585 | 0 | if (!dirdesc) |
586 | 0 | { |
587 | | /* Return empty tuplestore if appropriate */ |
588 | 0 | if (missing_ok && errno == ENOENT) |
589 | 0 | return (Datum) 0; |
590 | | /* Otherwise, we can let ReadDir() throw the error */ |
591 | 0 | } |
592 | | |
593 | 0 | while ((de = ReadDir(dirdesc, dir)) != NULL) |
594 | 0 | { |
595 | 0 | Datum values[3]; |
596 | 0 | bool nulls[3]; |
597 | 0 | char path[MAXPGPATH * 2]; |
598 | 0 | struct stat attrib; |
599 | | |
600 | | /* Skip hidden files */ |
601 | 0 | if (de->d_name[0] == '.') |
602 | 0 | continue; |
603 | | |
604 | | /* Get the file info */ |
605 | 0 | snprintf(path, sizeof(path), "%s/%s", dir, de->d_name); |
606 | 0 | if (stat(path, &attrib) < 0) |
607 | 0 | { |
608 | | /* Ignore concurrently-deleted files, else complain */ |
609 | 0 | if (errno == ENOENT) |
610 | 0 | continue; |
611 | 0 | ereport(ERROR, |
612 | 0 | (errcode_for_file_access(), |
613 | 0 | errmsg("could not stat file \"%s\": %m", path))); |
614 | 0 | } |
615 | | |
616 | | /* Ignore anything but regular files */ |
617 | 0 | if (!S_ISREG(attrib.st_mode)) |
618 | 0 | continue; |
619 | | |
620 | 0 | values[0] = CStringGetTextDatum(de->d_name); |
621 | 0 | values[1] = Int64GetDatum((int64) attrib.st_size); |
622 | 0 | values[2] = TimestampTzGetDatum(time_t_to_timestamptz(attrib.st_mtime)); |
623 | 0 | memset(nulls, 0, sizeof(nulls)); |
624 | |
|
625 | 0 | tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); |
626 | 0 | } |
627 | | |
628 | 0 | FreeDir(dirdesc); |
629 | 0 | return (Datum) 0; |
630 | 0 | } |
631 | | |
632 | | /* Function to return the list of files in the log directory */ |
633 | | Datum |
634 | | pg_ls_logdir(PG_FUNCTION_ARGS) |
635 | 0 | { |
636 | 0 | return pg_ls_dir_files(fcinfo, Log_directory, false); |
637 | 0 | } |
638 | | |
639 | | /* Function to return the list of files in the WAL directory */ |
640 | | Datum |
641 | | pg_ls_waldir(PG_FUNCTION_ARGS) |
642 | 0 | { |
643 | 0 | return pg_ls_dir_files(fcinfo, XLOGDIR, false); |
644 | 0 | } |
645 | | |
646 | | /* |
647 | | * Generic function to return the list of files in pgsql_tmp |
648 | | */ |
649 | | static Datum |
650 | | pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid tblspc) |
651 | 0 | { |
652 | 0 | char path[MAXPGPATH]; |
653 | |
|
654 | 0 | if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(tblspc))) |
655 | 0 | ereport(ERROR, |
656 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
657 | 0 | errmsg("tablespace with OID %u does not exist", |
658 | 0 | tblspc))); |
659 | | |
660 | 0 | TempTablespacePath(path, tblspc); |
661 | 0 | return pg_ls_dir_files(fcinfo, path, true); |
662 | 0 | } |
663 | | |
664 | | /* |
665 | | * Function to return the list of temporary files in the pg_default tablespace's |
666 | | * pgsql_tmp directory |
667 | | */ |
668 | | Datum |
669 | | pg_ls_tmpdir_noargs(PG_FUNCTION_ARGS) |
670 | 0 | { |
671 | 0 | return pg_ls_tmpdir(fcinfo, DEFAULTTABLESPACE_OID); |
672 | 0 | } |
673 | | |
674 | | /* |
675 | | * Function to return the list of temporary files in the specified tablespace's |
676 | | * pgsql_tmp directory |
677 | | */ |
678 | | Datum |
679 | | pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS) |
680 | 0 | { |
681 | 0 | return pg_ls_tmpdir(fcinfo, PG_GETARG_OID(0)); |
682 | 0 | } |
683 | | |
684 | | /* |
685 | | * Function to return the list of files in the WAL archive status directory. |
686 | | */ |
687 | | Datum |
688 | | pg_ls_archive_statusdir(PG_FUNCTION_ARGS) |
689 | 0 | { |
690 | 0 | return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true); |
691 | 0 | } |
692 | | |
693 | | /* |
694 | | * Function to return the list of files in the WAL summaries directory. |
695 | | */ |
696 | | Datum |
697 | | pg_ls_summariesdir(PG_FUNCTION_ARGS) |
698 | 0 | { |
699 | 0 | return pg_ls_dir_files(fcinfo, XLOGDIR "/summaries", true); |
700 | 0 | } |
701 | | |
702 | | /* |
703 | | * Function to return the list of files in the PG_LOGICAL_SNAPSHOTS_DIR |
704 | | * directory. |
705 | | */ |
706 | | Datum |
707 | | pg_ls_logicalsnapdir(PG_FUNCTION_ARGS) |
708 | 0 | { |
709 | 0 | return pg_ls_dir_files(fcinfo, PG_LOGICAL_SNAPSHOTS_DIR, false); |
710 | 0 | } |
711 | | |
712 | | /* |
713 | | * Function to return the list of files in the PG_LOGICAL_MAPPINGS_DIR |
714 | | * directory. |
715 | | */ |
716 | | Datum |
717 | | pg_ls_logicalmapdir(PG_FUNCTION_ARGS) |
718 | 0 | { |
719 | 0 | return pg_ls_dir_files(fcinfo, PG_LOGICAL_MAPPINGS_DIR, false); |
720 | 0 | } |
721 | | |
722 | | /* |
723 | | * Function to return the list of files in the PG_REPLSLOT_DIR/<slot_name> |
724 | | * directory. |
725 | | */ |
726 | | Datum |
727 | | pg_ls_replslotdir(PG_FUNCTION_ARGS) |
728 | 0 | { |
729 | 0 | text *slotname_t; |
730 | 0 | char path[MAXPGPATH]; |
731 | 0 | char *slotname; |
732 | |
|
733 | 0 | slotname_t = PG_GETARG_TEXT_PP(0); |
734 | |
|
735 | 0 | slotname = text_to_cstring(slotname_t); |
736 | |
|
737 | 0 | if (!SearchNamedReplicationSlot(slotname, true)) |
738 | 0 | ereport(ERROR, |
739 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
740 | 0 | errmsg("replication slot \"%s\" does not exist", |
741 | 0 | slotname))); |
742 | | |
743 | 0 | snprintf(path, sizeof(path), "%s/%s", PG_REPLSLOT_DIR, slotname); |
744 | |
|
745 | | return pg_ls_dir_files(fcinfo, path, false); |
746 | 0 | } |