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