LCOV - code coverage report
Current view: top level - contrib/adminpack - adminpack.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 81 175 46.3 %
Date: 2020-05-29 01:06:25 Functions: 17 24 70.8 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * adminpack.c
       4             :  *
       5             :  *
       6             :  * Copyright (c) 2002-2020, 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_authid.h"
      22             : #include "catalog/pg_type.h"
      23             : #include "funcapi.h"
      24             : #include "miscadmin.h"
      25             : #include "postmaster/syslogger.h"
      26             : #include "storage/fd.h"
      27             : #include "utils/acl.h"
      28             : #include "utils/builtins.h"
      29             : #include "utils/datetime.h"
      30             : 
      31             : 
      32             : #ifdef WIN32
      33             : 
      34             : #ifdef rename
      35             : #undef rename
      36             : #endif
      37             : 
      38             : #ifdef unlink
      39             : #undef unlink
      40             : #endif
      41             : #endif
      42             : 
      43           2 : PG_MODULE_MAGIC;
      44             : 
      45           2 : PG_FUNCTION_INFO_V1(pg_file_write);
      46           8 : PG_FUNCTION_INFO_V1(pg_file_write_v1_1);
      47           4 : PG_FUNCTION_INFO_V1(pg_file_sync);
      48           2 : PG_FUNCTION_INFO_V1(pg_file_rename);
      49           4 : PG_FUNCTION_INFO_V1(pg_file_rename_v1_1);
      50           2 : PG_FUNCTION_INFO_V1(pg_file_unlink);
      51           4 : PG_FUNCTION_INFO_V1(pg_file_unlink_v1_1);
      52           2 : PG_FUNCTION_INFO_V1(pg_logdir_ls);
      53           2 : PG_FUNCTION_INFO_V1(pg_logdir_ls_v1_1);
      54             : 
      55             : static int64 pg_file_write_internal(text *file, text *data, bool replace);
      56             : static bool pg_file_rename_internal(text *file1, text *file2, text *file3);
      57             : static Datum pg_logdir_ls_internal(FunctionCallInfo fcinfo);
      58             : 
      59             : 
      60             : /*-----------------------
      61             :  * some helper functions
      62             :  */
      63             : 
      64             : /*
      65             :  * Convert a "text" filename argument to C string, and check it's allowable.
      66             :  *
      67             :  * Filename may be absolute or relative to the DataDir, but we only allow
      68             :  * absolute paths that match DataDir.
      69             :  */
      70             : static char *
      71          46 : convert_and_check_filename(text *arg)
      72             : {
      73          46 :     char       *filename = text_to_cstring(arg);
      74             : 
      75          46 :     canonicalize_path(filename);    /* filename can change length here */
      76             : 
      77             :     /*
      78             :      * Members of the 'pg_write_server_files' role are allowed to access any
      79             :      * files on the server as the PG user, so no need to do any further checks
      80             :      * here.
      81             :      */
      82          46 :     if (is_member_of_role(GetUserId(), DEFAULT_ROLE_WRITE_SERVER_FILES))
      83          38 :         return filename;
      84             : 
      85             :     /* User isn't a member of the default role, so check if it's allowable */
      86           8 :     if (is_absolute_path(filename))
      87             :     {
      88             :         /* Disallow '/a/b/data/..' */
      89           6 :         if (path_contains_parent_reference(filename))
      90           2 :             ereport(ERROR,
      91             :                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
      92             :                      errmsg("reference to parent directory (\"..\") not allowed")));
      93             : 
      94             :         /* Allow absolute paths if within DataDir */
      95           4 :         if (!path_is_prefix_of_path(DataDir, filename))
      96           2 :             ereport(ERROR,
      97             :                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
      98             :                      errmsg("absolute path not allowed")));
      99             :     }
     100           2 :     else if (!path_is_relative_and_below_cwd(filename))
     101           2 :         ereport(ERROR,
     102             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     103             :                  errmsg("path must be in or below the current directory")));
     104             : 
     105           2 :     return filename;
     106             : }
     107             : 
     108             : 
     109             : /*
     110             :  * check for superuser, bark if not.
     111             :  */
     112             : static void
     113           0 : requireSuperuser(void)
     114             : {
     115           0 :     if (!superuser())
     116           0 :         ereport(ERROR,
     117             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     118             :                  errmsg("only superuser may access generic file functions")));
     119           0 : }
     120             : 
     121             : 
     122             : 
     123             : /* ------------------------------------
     124             :  * pg_file_write - old version
     125             :  *
     126             :  * The superuser() check here must be kept as the library might be upgraded
     127             :  * without the extension being upgraded, meaning that in pre-1.1 installations
     128             :  * these functions could be called by any user.
     129             :  */
     130             : Datum
     131           0 : pg_file_write(PG_FUNCTION_ARGS)
     132             : {
     133           0 :     text       *file = PG_GETARG_TEXT_PP(0);
     134           0 :     text       *data = PG_GETARG_TEXT_PP(1);
     135           0 :     bool        replace = PG_GETARG_BOOL(2);
     136           0 :     int64       count = 0;
     137             : 
     138           0 :     requireSuperuser();
     139             : 
     140           0 :     count = pg_file_write_internal(file, data, replace);
     141             : 
     142           0 :     PG_RETURN_INT64(count);
     143             : }
     144             : 
     145             : /* ------------------------------------
     146             :  * pg_file_write_v1_1 - Version 1.1
     147             :  *
     148             :  * As of adminpack version 1.1, we no longer need to check if the user
     149             :  * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
     150             :  * Users can then grant access to it based on their policies.
     151             :  *
     152             :  * Otherwise identical to pg_file_write (above).
     153             :  */
     154             : Datum
     155          16 : pg_file_write_v1_1(PG_FUNCTION_ARGS)
     156             : {
     157          16 :     text       *file = PG_GETARG_TEXT_PP(0);
     158          16 :     text       *data = PG_GETARG_TEXT_PP(1);
     159          16 :     bool        replace = PG_GETARG_BOOL(2);
     160          16 :     int64       count = 0;
     161             : 
     162          16 :     count = pg_file_write_internal(file, data, replace);
     163             : 
     164           8 :     PG_RETURN_INT64(count);
     165             : }
     166             : 
     167             : /* ------------------------------------
     168             :  * pg_file_write_internal - Workhorse for pg_file_write functions.
     169             :  *
     170             :  * This handles the actual work for pg_file_write.
     171             :  */
     172             : static int64
     173          16 : pg_file_write_internal(text *file, text *data, bool replace)
     174             : {
     175             :     FILE       *f;
     176             :     char       *filename;
     177          16 :     int64       count = 0;
     178             : 
     179          16 :     filename = convert_and_check_filename(file);
     180             : 
     181          10 :     if (!replace)
     182             :     {
     183             :         struct stat fst;
     184             : 
     185           8 :         if (stat(filename, &fst) >= 0)
     186           2 :             ereport(ERROR,
     187             :                     (errcode(ERRCODE_DUPLICATE_FILE),
     188             :                      errmsg("file \"%s\" exists", filename)));
     189             : 
     190           6 :         f = AllocateFile(filename, "wb");
     191             :     }
     192             :     else
     193           2 :         f = AllocateFile(filename, "ab");
     194             : 
     195           8 :     if (!f)
     196           0 :         ereport(ERROR,
     197             :                 (errcode_for_file_access(),
     198             :                  errmsg("could not open file \"%s\" for writing: %m",
     199             :                         filename)));
     200             : 
     201           8 :     count = fwrite(VARDATA_ANY(data), 1, VARSIZE_ANY_EXHDR(data), f);
     202           8 :     if (count != VARSIZE_ANY_EXHDR(data) || FreeFile(f))
     203           0 :         ereport(ERROR,
     204             :                 (errcode_for_file_access(),
     205             :                  errmsg("could not write file \"%s\": %m", filename)));
     206             : 
     207           8 :     return (count);
     208             : }
     209             : 
     210             : /* ------------------------------------
     211             :  * pg_file_sync
     212             :  *
     213             :  * We REVOKE EXECUTE on the function from PUBLIC.
     214             :  * Users can then grant access to it based on their policies.
     215             :  */
     216             : Datum
     217           6 : pg_file_sync(PG_FUNCTION_ARGS)
     218             : {
     219             :     char       *filename;
     220             :     struct stat fst;
     221             : 
     222           6 :     filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
     223             : 
     224           6 :     if (stat(filename, &fst) < 0)
     225           2 :         ereport(ERROR,
     226             :                 (errcode_for_file_access(),
     227             :                  errmsg("could not stat file \"%s\": %m", filename)));
     228             : 
     229           4 :     fsync_fname_ext(filename, S_ISDIR(fst.st_mode), false, ERROR);
     230             : 
     231           4 :     PG_RETURN_VOID();
     232             : }
     233             : 
     234             : /* ------------------------------------
     235             :  * pg_file_rename - old version
     236             :  *
     237             :  * The superuser() check here must be kept as the library might be upgraded
     238             :  * without the extension being upgraded, meaning that in pre-1.1 installations
     239             :  * these functions could be called by any user.
     240             :  */
     241             : Datum
     242           0 : pg_file_rename(PG_FUNCTION_ARGS)
     243             : {
     244             :     text       *file1;
     245             :     text       *file2;
     246             :     text       *file3;
     247             :     bool        result;
     248             : 
     249           0 :     requireSuperuser();
     250             : 
     251           0 :     if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
     252           0 :         PG_RETURN_NULL();
     253             : 
     254           0 :     file1 = PG_GETARG_TEXT_PP(0);
     255           0 :     file2 = PG_GETARG_TEXT_PP(1);
     256             : 
     257           0 :     if (PG_ARGISNULL(2))
     258           0 :         file3 = NULL;
     259             :     else
     260           0 :         file3 = PG_GETARG_TEXT_PP(2);
     261             : 
     262           0 :     result = pg_file_rename_internal(file1, file2, file3);
     263             : 
     264           0 :     PG_RETURN_BOOL(result);
     265             : }
     266             : 
     267             : /* ------------------------------------
     268             :  * pg_file_rename_v1_1 - Version 1.1
     269             :  *
     270             :  * As of adminpack version 1.1, we no longer need to check if the user
     271             :  * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
     272             :  * Users can then grant access to it based on their policies.
     273             :  *
     274             :  * Otherwise identical to pg_file_write (above).
     275             :  */
     276             : Datum
     277           6 : pg_file_rename_v1_1(PG_FUNCTION_ARGS)
     278             : {
     279             :     text       *file1;
     280             :     text       *file2;
     281             :     text       *file3;
     282             :     bool        result;
     283             : 
     284           6 :     if (PG_ARGISNULL(0) || PG_ARGISNULL(1))
     285           0 :         PG_RETURN_NULL();
     286             : 
     287           6 :     file1 = PG_GETARG_TEXT_PP(0);
     288           6 :     file2 = PG_GETARG_TEXT_PP(1);
     289             : 
     290           6 :     if (PG_ARGISNULL(2))
     291           4 :         file3 = NULL;
     292             :     else
     293           2 :         file3 = PG_GETARG_TEXT_PP(2);
     294             : 
     295           6 :     result = pg_file_rename_internal(file1, file2, file3);
     296             : 
     297           6 :     PG_RETURN_BOOL(result);
     298             : }
     299             : 
     300             : /* ------------------------------------
     301             :  * pg_file_rename_internal - Workhorse for pg_file_rename functions.
     302             :  *
     303             :  * This handles the actual work for pg_file_rename.
     304             :  */
     305             : static bool
     306           6 : pg_file_rename_internal(text *file1, text *file2, text *file3)
     307             : {
     308             :     char       *fn1,
     309             :                *fn2,
     310             :                *fn3;
     311             :     int         rc;
     312             : 
     313           6 :     fn1 = convert_and_check_filename(file1);
     314           6 :     fn2 = convert_and_check_filename(file2);
     315             : 
     316           6 :     if (file3 == NULL)
     317           4 :         fn3 = NULL;
     318             :     else
     319           2 :         fn3 = convert_and_check_filename(file3);
     320             : 
     321           6 :     if (access(fn1, W_OK) < 0)
     322             :     {
     323           2 :         ereport(WARNING,
     324             :                 (errcode_for_file_access(),
     325             :                  errmsg("file \"%s\" is not accessible: %m", fn1)));
     326             : 
     327           2 :         return false;
     328             :     }
     329             : 
     330           4 :     if (fn3 && access(fn2, W_OK) < 0)
     331             :     {
     332           0 :         ereport(WARNING,
     333             :                 (errcode_for_file_access(),
     334             :                  errmsg("file \"%s\" is not accessible: %m", fn2)));
     335             : 
     336           0 :         return false;
     337             :     }
     338             : 
     339           4 :     rc = access(fn3 ? fn3 : fn2, W_OK);
     340           4 :     if (rc >= 0 || errno != ENOENT)
     341             :     {
     342           0 :         ereport(ERROR,
     343             :                 (errcode(ERRCODE_DUPLICATE_FILE),
     344             :                  errmsg("cannot rename to target file \"%s\"",
     345             :                         fn3 ? fn3 : fn2)));
     346             :     }
     347             : 
     348           4 :     if (fn3)
     349             :     {
     350           2 :         if (rename(fn2, fn3) != 0)
     351             :         {
     352           0 :             ereport(ERROR,
     353             :                     (errcode_for_file_access(),
     354             :                      errmsg("could not rename \"%s\" to \"%s\": %m",
     355             :                             fn2, fn3)));
     356             :         }
     357           2 :         if (rename(fn1, fn2) != 0)
     358             :         {
     359           0 :             ereport(WARNING,
     360             :                     (errcode_for_file_access(),
     361             :                      errmsg("could not rename \"%s\" to \"%s\": %m",
     362             :                             fn1, fn2)));
     363             : 
     364           0 :             if (rename(fn3, fn2) != 0)
     365             :             {
     366           0 :                 ereport(ERROR,
     367             :                         (errcode_for_file_access(),
     368             :                          errmsg("could not rename \"%s\" back to \"%s\": %m",
     369             :                                 fn3, fn2)));
     370             :             }
     371             :             else
     372             :             {
     373           0 :                 ereport(ERROR,
     374             :                         (errcode(ERRCODE_UNDEFINED_FILE),
     375             :                          errmsg("renaming \"%s\" to \"%s\" was reverted",
     376             :                                 fn2, fn3)));
     377             :             }
     378             :         }
     379             :     }
     380           2 :     else if (rename(fn1, fn2) != 0)
     381             :     {
     382           0 :         ereport(ERROR,
     383             :                 (errcode_for_file_access(),
     384             :                  errmsg("could not rename \"%s\" to \"%s\": %m", fn1, fn2)));
     385             :     }
     386             : 
     387           4 :     return true;
     388             : }
     389             : 
     390             : 
     391             : /* ------------------------------------
     392             :  * pg_file_unlink - old version
     393             :  *
     394             :  * The superuser() check here must be kept as the library might be upgraded
     395             :  * without the extension being upgraded, meaning that in pre-1.1 installations
     396             :  * these functions could be called by any user.
     397             :  */
     398             : Datum
     399           0 : pg_file_unlink(PG_FUNCTION_ARGS)
     400             : {
     401             :     char       *filename;
     402             : 
     403           0 :     requireSuperuser();
     404             : 
     405           0 :     filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
     406             : 
     407           0 :     if (access(filename, W_OK) < 0)
     408             :     {
     409           0 :         if (errno == ENOENT)
     410           0 :             PG_RETURN_BOOL(false);
     411             :         else
     412           0 :             ereport(ERROR,
     413             :                     (errcode_for_file_access(),
     414             :                      errmsg("file \"%s\" is not accessible: %m", filename)));
     415             :     }
     416             : 
     417           0 :     if (unlink(filename) < 0)
     418             :     {
     419           0 :         ereport(WARNING,
     420             :                 (errcode_for_file_access(),
     421             :                  errmsg("could not unlink file \"%s\": %m", filename)));
     422             : 
     423           0 :         PG_RETURN_BOOL(false);
     424             :     }
     425           0 :     PG_RETURN_BOOL(true);
     426             : }
     427             : 
     428             : 
     429             : /* ------------------------------------
     430             :  * pg_file_unlink_v1_1 - Version 1.1
     431             :  *
     432             :  * As of adminpack version 1.1, we no longer need to check if the user
     433             :  * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
     434             :  * Users can then grant access to it based on their policies.
     435             :  *
     436             :  * Otherwise identical to pg_file_unlink (above).
     437             :  */
     438             : Datum
     439          10 : pg_file_unlink_v1_1(PG_FUNCTION_ARGS)
     440             : {
     441             :     char       *filename;
     442             : 
     443          10 :     filename = convert_and_check_filename(PG_GETARG_TEXT_PP(0));
     444             : 
     445          10 :     if (access(filename, W_OK) < 0)
     446             :     {
     447           4 :         if (errno == ENOENT)
     448           4 :             PG_RETURN_BOOL(false);
     449             :         else
     450           0 :             ereport(ERROR,
     451             :                     (errcode_for_file_access(),
     452             :                      errmsg("file \"%s\" is not accessible: %m", filename)));
     453             :     }
     454             : 
     455           6 :     if (unlink(filename) < 0)
     456             :     {
     457           0 :         ereport(WARNING,
     458             :                 (errcode_for_file_access(),
     459             :                  errmsg("could not unlink file \"%s\": %m", filename)));
     460             : 
     461           0 :         PG_RETURN_BOOL(false);
     462             :     }
     463           6 :     PG_RETURN_BOOL(true);
     464             : }
     465             : 
     466             : /* ------------------------------------
     467             :  * pg_logdir_ls - Old version
     468             :  *
     469             :  * The superuser() check here must be kept as the library might be upgraded
     470             :  * without the extension being upgraded, meaning that in pre-1.1 installations
     471             :  * these functions could be called by any user.
     472             :  */
     473             : Datum
     474           0 : pg_logdir_ls(PG_FUNCTION_ARGS)
     475             : {
     476           0 :     if (!superuser())
     477           0 :         ereport(ERROR,
     478             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     479             :                  errmsg("only superuser can list the log directory")));
     480             : 
     481           0 :     return (pg_logdir_ls_internal(fcinfo));
     482             : }
     483             : 
     484             : /* ------------------------------------
     485             :  * pg_logdir_ls_v1_1 - Version 1.1
     486             :  *
     487             :  * As of adminpack version 1.1, we no longer need to check if the user
     488             :  * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
     489             :  * Users can then grant access to it based on their policies.
     490             :  *
     491             :  * Otherwise identical to pg_logdir_ls (above).
     492             :  */
     493             : Datum
     494           0 : pg_logdir_ls_v1_1(PG_FUNCTION_ARGS)
     495             : {
     496           0 :     return (pg_logdir_ls_internal(fcinfo));
     497             : }
     498             : 
     499             : static Datum
     500           0 : pg_logdir_ls_internal(FunctionCallInfo fcinfo)
     501             : {
     502           0 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
     503             :     bool        randomAccess;
     504             :     TupleDesc   tupdesc;
     505             :     Tuplestorestate *tupstore;
     506             :     AttInMetadata *attinmeta;
     507             :     DIR        *dirdesc;
     508             :     struct dirent *de;
     509             :     MemoryContext oldcontext;
     510             : 
     511           0 :     if (strcmp(Log_filename, "postgresql-%Y-%m-%d_%H%M%S.log") != 0)
     512           0 :         ereport(ERROR,
     513             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     514             :                  errmsg("the log_filename parameter must equal 'postgresql-%%Y-%%m-%%d_%%H%%M%%S.log'")));
     515             : 
     516             :     /* check to see if caller supports us returning a tuplestore */
     517           0 :     if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
     518           0 :         ereport(ERROR,
     519             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     520             :                  errmsg("set-valued function called in context that cannot accept a set")));
     521           0 :     if (!(rsinfo->allowedModes & SFRM_Materialize))
     522           0 :         ereport(ERROR,
     523             :                 (errcode(ERRCODE_SYNTAX_ERROR),
     524             :                  errmsg("materialize mode required, but it is not allowed in this context")));
     525             : 
     526             :     /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
     527           0 :     oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
     528             : 
     529           0 :     tupdesc = CreateTemplateTupleDesc(2);
     530           0 :     TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
     531             :                        TIMESTAMPOID, -1, 0);
     532           0 :     TupleDescInitEntry(tupdesc, (AttrNumber) 2, "filename",
     533             :                        TEXTOID, -1, 0);
     534             : 
     535           0 :     randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
     536           0 :     tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
     537           0 :     rsinfo->returnMode = SFRM_Materialize;
     538           0 :     rsinfo->setResult = tupstore;
     539           0 :     rsinfo->setDesc = tupdesc;
     540             : 
     541           0 :     MemoryContextSwitchTo(oldcontext);
     542             : 
     543           0 :     attinmeta = TupleDescGetAttInMetadata(tupdesc);
     544             : 
     545           0 :     dirdesc = AllocateDir(Log_directory);
     546           0 :     while ((de = ReadDir(dirdesc, Log_directory)) != NULL)
     547             :     {
     548             :         char       *values[2];
     549             :         HeapTuple   tuple;
     550             :         char        timestampbuf[32];
     551             :         char       *field[MAXDATEFIELDS];
     552             :         char        lowstr[MAXDATELEN + 1];
     553             :         int         dtype;
     554             :         int         nf,
     555             :                     ftype[MAXDATEFIELDS];
     556             :         fsec_t      fsec;
     557           0 :         int         tz = 0;
     558             :         struct pg_tm date;
     559             : 
     560             :         /*
     561             :          * Default format: postgresql-YYYY-MM-DD_HHMMSS.log
     562             :          */
     563           0 :         if (strlen(de->d_name) != 32
     564           0 :             || strncmp(de->d_name, "postgresql-", 11) != 0
     565           0 :             || de->d_name[21] != '_'
     566           0 :             || strcmp(de->d_name + 28, ".log") != 0)
     567           0 :             continue;
     568             : 
     569             :         /* extract timestamp portion of filename */
     570           0 :         strcpy(timestampbuf, de->d_name + 11);
     571           0 :         timestampbuf[17] = '\0';
     572             : 
     573             :         /* parse and decode expected timestamp to verify it's OK format */
     574           0 :         if (ParseDateTime(timestampbuf, lowstr, MAXDATELEN, field, ftype, MAXDATEFIELDS, &nf))
     575           0 :             continue;
     576             : 
     577           0 :         if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
     578           0 :             continue;
     579             : 
     580             :         /* Seems the timestamp is OK; prepare and return tuple */
     581             : 
     582           0 :         values[0] = timestampbuf;
     583           0 :         values[1] = psprintf("%s/%s", Log_directory, de->d_name);
     584             : 
     585           0 :         tuple = BuildTupleFromCStrings(attinmeta, values);
     586             : 
     587           0 :         tuplestore_puttuple(tupstore, tuple);
     588             :     }
     589             : 
     590           0 :     FreeDir(dirdesc);
     591           0 :     return (Datum) 0;
     592             : }

Generated by: LCOV version 1.13