Thread: logfile subprocess and Fancy File Functions

logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
The attached patch and additional src/backend/postmaster/syslogger.c
implements the logfile subprocess as discussed.

TODO:
- documentation
- win32 code (forkexec) is included, but not tested (no build env)



Functions (all are superuser only):

int4 pg_reload_conf()
Sends SIGHUP to postmaster

bool pg_logfile_rotate()
initiates logfile rotation, same does SIGUSR1 to the syslogger
subprocess; returns true if logging is enabled

setof record pg_logfiles_ls()
lists all available logfiles, should we have a view as well?
CREATE VIEW pg_logfiles AS
SELECT ts, pid, fn
   FROM pg_logfiles_ls()
        AS pgls(ts timestamp, pid int4, fn text)


int8 pg_file_length(filename_text)
returns length of file, or -1 if non existent (no ERROR)

text pg_file_read(filename_text, startpos_int6, length_int8)
reads file

int8 pg_file_write(filename_text, data_text, append_bool)
writes file. creates or appends
to create, file must not exist, to append, file may exist.

bool pg_file_rename(filename_old_text, filenamenew_text)
rename file

bool pg_file_unlink(filename_text)
unlinks file. returns true/false if done (no ERROR)

bool pg_file_rename(filename_old_text,
    filename_new_text, filename_archive_text)
chain rename: new->archive, old->archive, example:

It should be quite safe to do

pg_file_write('postgresql.conf.tmp',
    '.....some stuff...', false);
pg_file_unlink('postgresql.conf.bak');
pg_file_rename('postgresql.conf.tmp',
    'postgresql.conf', 'postgresql.conf.bak');
pg_reload_conf();


Regards,
Andreas
/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * The system logger (syslogger) is new in Postgres 7.5. It catches all
 * stderr output from backends, the postmaster and subprocesses by
 * redirecting to a pipe, and writes it to a logfile and stderr if
 * configured.
 * It's possible to have size and age limits for the logfile configured
 * in postgresql.conf. If these limits are reached or passed, the
 * current logfile is closed and a new one is created (rotated).
 * The logfiles are stored in a subdirectory (configurable in
 * postgresql.conf), using an internal naming scheme that mangles
 * creation time and current postmaster pid.
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/pmsignal.h"
#include "storage/pg_shmem.h"
#include "storage/ipc.h"
#include "postmaster/syslogger.h"
#include "utils/ps_status.h"
#include "utils/guc.h"

/*
 * GUC parameters
 */
int            Log_RotationAge = 24*60;
int            Log_RotationSize  = 10*1024;
char *      Log_directory = "pg_log";


/*
 * Flags set by interrupt handlers for later service in the main loop.
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;

static pg_time_t    last_rotation_time = 0;


static void sigHupHandler(SIGNAL_ARGS);
static void rotationHandler(SIGNAL_ARGS);
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec();
#endif

static char* logfile_getname(pg_time_t timestamp);
static bool logfile_rotate(void);


FILE *realStdErr = NULL;
FILE *syslogFile = NULL;
int syslogPipe[2] = {0, 0};


/*
 * Main entry point for syslogger process
 * argc/argv parameters are valid only in EXEC_BACKEND case.
 */
void
SysLoggerMain(int argc, char *argv[])
{
    IsUnderPostmaster = true;
    MyProcPid = getpid();
    init_ps_display("system logger process", "", "");
    set_ps_display("");

#ifdef EXEC_BACKEND

    Assert(argc == 6);

    argv += 3;
    StrNCpy(postgres_exec_path,    argv++, MAXPGPATH);
    syslogPipe[0] = atoi(argv++);
    syslogPipe[1] = atoi(argv);

#endif

    /*
     * Properly accept or ignore signals the postmaster might send us
     *
     * Note: we ignore all termination signals, and wait for the postmaster
     * to die to catch as much pipe output as possible.
     */

    pqsignal(SIGHUP, sigHupHandler);    /* set flag to read config file */
    pqsignal(SIGINT,  SIG_IGN);
    pqsignal(SIGTERM, SIG_IGN);
    pqsignal(SIGQUIT, SIG_IGN);
    pqsignal(SIGALRM, SIG_IGN);
    pqsignal(SIGPIPE, SIG_IGN);
    pqsignal(SIGUSR1, rotationHandler);  /* request log rotation */
    pqsignal(SIGUSR2, SIG_IGN);

    /*
     * Reset some signals that are accepted by postmaster but not here
     */
    pqsignal(SIGCHLD, SIG_DFL);
    pqsignal(SIGTTIN, SIG_DFL);
    pqsignal(SIGTTOU, SIG_DFL);
    pqsignal(SIGCONT, SIG_DFL);
    pqsignal(SIGWINCH, SIG_DFL);

    PG_SETMASK(&UnBlockSig);

    /*
     * if we restarted, our stderr is redirected.
     * Direct it back to system stderr.
     */
    if (realStdErr != NULL)
    {
        if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
        {
            char *errstr = strerror(errno);
            /*
             * Now we have a real problem: we can't redirect to stderr,
             * and can't ereport it correctly (it would go into our queue
             * which will never be read
             * We're writing everywhere, hoping to leave at least some
             * hint of what happened.
             */

            fprintf(realStdErr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
            fprintf(stderr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);

            ereport(PANIC,
                    (errcode_for_file_access(),
                     (errmsg("Syslogger couldn't redirect its stderr to the saved stderr: %s", errstr))));
            exit(1);
        }

        realStdErr = NULL;
    }
    else
    {
        /* we'll never write that pipe */
        close(syslogPipe[1]);
    }

    /* remember age of initial logfile */
    last_rotation_time = time(NULL);

    /* main worker loop */
    for (;;)
    {
        pg_time_t    now;
        int            elapsed_secs;
        char        logbuffer[1024];
        char        bytesRead;
        fd_set        rfds;
        struct timeval timeout;
        int         rc;

        if (got_SIGHUP)
        {
            char *olddir=pstrdup(Log_directory);
            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

            /*
             * check if the log directory changed
             * in postgresql.conf. If so, we rotate to make sure
             * we're writing the logfiles where the backends
             * expect us to do so.
             */
            if (strcmp(Log_directory, olddir))
              rotation_requested = true;

            pfree(olddir);
        }

        if (!rotation_requested && last_rotation_time != 0 && Log_RotationAge > 0)
        {
            /*
             * Do an unforced rotation if too much time has elapsed
             * since the last one.
             */
            now = time(NULL);
            elapsed_secs = now - last_rotation_time;
            if (elapsed_secs >= Log_RotationAge * 60)
                rotation_requested = true;
        }

        if (!rotation_requested && Log_RotationSize > 0)
        {
            /*
             * Do an unforced rotation if file is too big
             */
            if (ftell(syslogFile) >= Log_RotationSize * 1024)
                rotation_requested = true;
        }


        if (rotation_requested)
        {
            if (!logfile_rotate())
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         (errmsg("logfile rotation failed, disabling auto rotation (SIGHUP to reenable): %m"))));

                Log_RotationAge = 0;
                Log_RotationSize = 0;
            }
            rotation_requested = false;
        }

        FD_ZERO(&rfds);
        FD_SET(syslogPipe[0], &rfds);
        timeout.tv_sec=1;
        timeout.tv_usec=0;

        /*
         * Check if data is present
         */
        rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout);

        PG_SETMASK(&UnBlockSig);
        if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds))
        {
            bytesRead = piperead(syslogPipe[0],
                                 logbuffer, sizeof(logbuffer));

            if (bytesRead > 0)
            {
                if (fwrite(logbuffer, 1, bytesRead, syslogFile) < 1)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             errmsg("fwrite to logfile failed in system logger: %m")));

                fflush(syslogFile);

                if (Log_destination & LOG_DESTINATION_STDERR)
                {
                    fwrite(logbuffer, 1, bytesRead, stderr);
                    fflush(stderr);
                }
            }
            else if (bytesRead < 0 && errno != EINTR)
            {
                ereport(FATAL,
                        (errcode_for_socket_access(),
                         errmsg("could not read from system logger pipe: %m")));
            }
            continue;
        }

        if (rc < 0 && errno != EINTR)
        {
            ereport(FATAL,
                    (errcode_for_socket_access(),
                     errmsg("select() failed in system logger: %m")));
            exit(1);
        }

        /*
         * If postmaster died, there's nothing to log any more.
         * We check this only after pipe timeouts to receive as much as possible
         * from the pipe.
         */
        if (!PostmasterIsAlive(true))
        {
            if (syslogFile)
                fclose(syslogFile);
            exit(1);
        }

    }
}


int
SysLogger_Start(void)
{
    pid_t sysloggerPid;
    pg_time_t now;
    char *filename;

    if (!(Log_destination & LOG_DESTINATION_FILE))
        return 0;

    /* create the pipe which will receive stderr output */
    if (!syslogPipe[0])
    {
        if (pgpipe(syslogPipe) < 0)
            ereport(FATAL,
                    (errcode_for_file_access(),
                     (errmsg("pipe for syslogging not created: %m"))));

        if (!set_noblock(syslogPipe[1]))
        {
            ereport(FATAL,
                    (errcode_for_socket_access(),
                     errmsg("could not set syslogging pipe to nonblocking mode: %m")));
        }
    }

    now = time(NULL);

    /*
     * The initial logfile is created right in the postmaster,
     * to insure that the logger process has a writable file.
     */
    filename = logfile_getname(now);

    /*
     * The file is opened for appending, in case the syslogger
     * is restarted right after a rotation.
     */
    syslogFile = fopen(filename, "a+");

    if (!syslogFile)
    {
        /*
         * if we can't open the syslog file for the syslogger process,
         * we try to redirect stderr back to have some logging.
         */
        ereport(WARNING,
                (errcode_for_file_access(),
                 (errmsg("error opening syslog file %s: %m", filename))));

        if (realStdErr != NULL)
        {
            if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
                ereport(FATAL,
                        (errcode_for_file_access(),
                         (errmsg("error redirecting stderr to default: %m"))));

            ereport(FATAL,
                    (errmsg("logfile output corrupted")));
        }

    }
    pfree(filename);

    fflush(stdout);
    fflush(stderr);

#ifdef __BEOS__
    /* Specific beos actions before backend startup */
    beos_before_backend_startup();
#endif

#ifdef EXEC_BACKEND
    switch ((sysloggerPid = syslogger_forkexec()))
#else
    switch ((sysloggerPid = fork()))
#endif
    {
        case -1:
#ifdef __BEOS__
            /* Specific beos actions */
            beos_backend_startup_failed();
#endif
            ereport(LOG,
                    (errmsg("could not fork system logger: %m")));
            return 0;

#ifndef EXEC_BACKEND
        case 0:
            /* in postmaster child ... */
#ifdef __BEOS__
            /* Specific beos actions after backend startup */
            beos_backend_startup();
#endif
            /* Close the postmaster's sockets */
            ClosePostmasterPorts();

            /* Drop our connection to postmaster's shared memory, as well */
            PGSharedMemoryDetach();

            /* do the work */
            SysLoggerMain(0, NULL);
            break;
#endif

        default:
            /* now we redirect stderr, if not done already */
            if (realStdErr == NULL)
            {
                int dh= dup(fileno(stderr));

                if (dh < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("stderr duplication failed: %m"))));

                realStdErr = fdopen(dh, "a");
                if (realStdErr == NULL)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("realstderr reopen failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stdout)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stdout pipe redirection failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stderr)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stderr pipe redirection failed: %m"))));


            }

            /* postmaster will never write the file; close it */
            fclose(syslogFile);
            syslogFile = NULL;
            return (int) sysloggerPid;
    }

    /* we should never reach here */
    return 0;
}


#ifdef EXEC_BACKEND
static pid_t
syslogger_forkexec()
{
    char *av[10];
    int ac = 0, bufc = 0, i;
    char numbuf[2][32];

    av[ac++] = "postgres";
    av[ac++] = "-forklog";
    av[ac++] = NULL;            /* filled in by postmaster_forkexec */

    /* postgres_exec_path is not passed by write_backend_variables */
    av[ac++] = postgres_exec_path;

    /* Pipe file ids (those not passed by write_backend_variables) */
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[0]);
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[1]);

    /* Add to the arg list */
    Assert(bufc <= lengthof(pgstatBuf));
    for (i = 0; i < bufc; i++)
        av[ac++] = numbuf[i];

    av[ac] = NULL;
    Assert(ac < lengthof(av));

    return postmaster_forkexec(ac, av);
}
#endif


/* --------------------------------
 *        logfile routines
 * --------------------------------
 */


/*
 * perform rotation
 */
bool
logfile_rotate(void)
{
    char *filename;
    pg_time_t now;
    FILE *fh;

    now = time(NULL);
    filename = logfile_getname(now);

    fh = fopen(filename, "a+");
    if (!fh)
    {
        /*
         * if opening the new file fails, the caller is responsible
         * for taking consequences. */

        pfree(filename);
        return false;
    }

    fclose(syslogFile);
    syslogFile = fh;

    last_rotation_time = now;

    /* official opening of the new logfile */
    ereport(NOTICE,
            (errcode(ERRCODE_WARNING),
             errmsg("Opened new log file %s", filename)));

    pfree(filename);
    return true;
}



/*
 * creates logfile name using timestamp information
 */

#define TIMESTAMPPATTERN "%Y-%m-%d_%H%M%S"

static char*
logfile_getname(pg_time_t timestamp)
{
    char *filetemplate;
    char *filename;


    if (is_absolute_path(Log_directory))
    {
        filetemplate = palloc(strlen(Log_directory
                                     ) +sizeof(TIMESTAMPPATTERN)+10 +2);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s_%05d.log",
                    Log_directory, TIMESTAMPPATTERN, PostmasterPid);
    }
    else
    {
        filetemplate = palloc(strlen(DataDir) + strlen(Log_directory)
                              +sizeof(TIMESTAMPPATTERN) +10 +3);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s/%s_%05d.log",
                    DataDir, Log_directory, TIMESTAMPPATTERN, PostmasterPid);
    }
    filename = palloc(MAXPGPATH);

    if (!filename || !filetemplate)
        ereport(FATAL,
                (errcode(ERRCODE_OUT_OF_MEMORY),
                 errmsg("Out of memory")));

    pg_strftime(filename, MAXPGPATH, filetemplate, pg_localtime(×tamp));

    pfree(filetemplate);

    return filename;
}

/* --------------------------------
 *        API helper routines
 * --------------------------------
 */

/*
 * Rotate log file
 */
bool
LogFileRotate(void)
{
    if (!(Log_destination & LOG_DESTINATION_FILE))
    {
        ereport(NOTICE,
                (errcode(ERRCODE_WARNING),
                 errmsg("no logfile configured; rotation not supported")));
        return false;
    }

    SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);

    return true;
}

/* --------------------------------
 *        signal handler routines
 * --------------------------------
 */

/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
{
    got_SIGHUP = true;
}

/* SIGUSR1: set flag to rotate logfile */
static void
rotationHandler(SIGNAL_ARGS)
{
    rotation_requested = true;
}
Index: src/backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.15
diff -u -r1.15 Makefile
--- src/backend/postmaster/Makefile    29 May 2004 22:48:19 -0000    1.15
+++ src/backend/postmaster/Makefile    17 Jul 2004 19:35:22 -0000
@@ -12,7 +12,7 @@
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global

-OBJS = postmaster.o bgwriter.o pgstat.o
+OBJS = postmaster.o bgwriter.o pgstat.o syslogger.o

 all: SUBSYS.o

Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.411
diff -u -r1.411 postmaster.c
--- src/backend/postmaster/postmaster.c    12 Jul 2004 19:14:56 -0000    1.411
+++ src/backend/postmaster/postmaster.c    17 Jul 2004 19:35:28 -0000
@@ -117,7 +117,7 @@
 #include "utils/ps_status.h"
 #include "bootstrap/bootstrap.h"
 #include "pgstat.h"
-
+#include "postmaster/syslogger.h"

 /*
  * List of active backends (or child processes anyway; we don't actually
@@ -199,6 +199,7 @@
 static pid_t StartupPID = 0,
             BgWriterPID = 0,
             PgStatPID = 0;
+pid_t       SysLoggerPID = 0;

 /* Startup/shutdown state */
 #define            NoShutdown        0
@@ -849,6 +850,12 @@
 #endif

     /*
+     * start logging to file
+     */
+
+    SysLoggerPID = SysLogger_Start();
+
+    /*
      * Reset whereToSendOutput from Debug (its starting state) to None.
      * This prevents ereport from sending log messages to stderr unless
      * the syslog/stderr switch permits.  We don't do this until the
@@ -1222,6 +1229,11 @@
             StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
             PgStatPID = pgstat_start();

+        /* If we have lost the system logger, try to start a new one */
+        if (SysLoggerPID == 0 &&
+            StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+            SysLoggerPID = SysLogger_Start();
+
         /*
          * Touch the socket and lock file at least every ten minutes, to ensure
          * that they are not removed by overzealous /tmp-cleaning tasks.
@@ -1760,6 +1772,8 @@
         SignalChildren(SIGHUP);
         if (BgWriterPID != 0)
             kill(BgWriterPID, SIGHUP);
+        if (SysLoggerPID != 0)
+            kill(SysLoggerPID, SIGHUP);
         /* PgStatPID does not currently need SIGHUP */
         load_hba();
         load_ident();
@@ -1822,7 +1836,6 @@
             if (PgStatPID != 0)
                 kill(PgStatPID, SIGQUIT);
             break;
-
         case SIGINT:
             /*
              * Fast Shutdown:
@@ -1884,6 +1897,7 @@
                 kill(PgStatPID, SIGQUIT);
             if (DLGetHead(BackendList))
                 SignalChildren(SIGQUIT);
+
             ExitPostmaster(0);
             break;
     }
@@ -2020,6 +2034,15 @@
             continue;
         }

+        /* was it the system logger, try to start a new one */
+        if (SysLoggerPID != 0 && pid == SysLoggerPID)
+        {
+            if (exitstatus != 0)
+                LogChildExit(LOG, gettext("system logger process"),
+                             pid, exitstatus);
+            SysLoggerPID = SysLogger_Start();
+            continue;
+        }
         /*
          * Else do standard backend child cleanup.
          */
@@ -2895,6 +2918,16 @@
         PgstatCollectorMain(argc, argv);
         proc_exit(0);
     }
+    if (strcmp(argv[1], "-forklog") == 0)
+    {
+        /* Close the postmaster's sockets */
+        ClosePostmasterPorts();
+
+        /* Do not want to attach to shared memory */
+
+        SysLoggerMain(argc, argv);
+        proc_exit(0);
+    }

     return 1;                    /* shouldn't get here */
 }
@@ -2951,6 +2984,10 @@
         if (Shutdown <= SmartShutdown)
             SignalChildren(SIGUSR1);
     }
+    if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) && SysLoggerPID != 0)
+    {
+        kill(SysLoggerPID, SIGUSR1);
+    }

     PG_SETMASK(&UnBlockSig);

Index: src/backend/utils/adt/misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v
retrieving revision 1.35
diff -u -r1.35 misc.c
--- src/backend/utils/adt/misc.c    2 Jul 2004 18:59:22 -0000    1.35
+++ src/backend/utils/adt/misc.c    17 Jul 2004 19:35:34 -0000
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include <sys/file.h>
+#include <unistd.h>
 #include <signal.h>
 #include <dirent.h>

@@ -26,6 +27,49 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "postmaster/syslogger.h"
+
+/*-----------------------
+ * some helper functions
+ */
+
+/*
+ * Return an absolute path. Argument may be absolute or
+ * relative to the DataDir.
+ */
+static char *absClusterPath(text *arg)
+{
+    char *filename;
+    int len=VARSIZE(arg) - VARHDRSZ;
+
+    filename = palloc(len+1);
+    memcpy(filename, VARDATA(arg), len);
+    filename[len] = 0;
+
+    if (is_absolute_path(filename))
+        return filename;
+    else
+    {
+        char *absname = palloc(strlen(DataDir)+len+2);
+        sprintf(absname, "%s/%s", DataDir, filename);
+        pfree(filename);
+        return absname;
+    }
+}
+
+
+/*
+ * check for superuser, bark if not.
+ */
+static void
+requireSuperuser(void)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser may access generic file functions"))));
+}
+


 /*
@@ -73,10 +117,7 @@

 static int pg_signal_backend(int pid, int sig)
 {
-    if (!superuser())
-        ereport(ERROR,
-                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 (errmsg("only superuser can signal other backends"))));
+    requireSuperuser();

     if (!IsBackendPid(pid))
     {
@@ -109,18 +150,34 @@
     PG_RETURN_INT32(pg_signal_backend(PG_GETARG_INT32(0),SIGINT));
 }

+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    if (kill(PostmasterPid, SIGHUP))
+    {
+        ereport(WARNING,
+                (errmsg("failed to send signal to postmaster: %m")));
+
+        PG_RETURN_INT32(0);
+    }
+
+    PG_RETURN_INT32(1);
+}
+

 typedef struct
 {
     char *location;
     DIR *dirdesc;
-} ts_db_fctx;
+} directory_fctx;

 Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
     FuncCallContext *funcctx;
     struct dirent *de;
-    ts_db_fctx *fctx;
+    directory_fctx *fctx;

     if (SRF_IS_FIRSTCALL())
     {
@@ -130,7 +187,7 @@
         funcctx=SRF_FIRSTCALL_INIT();
         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

-        fctx = palloc(sizeof(ts_db_fctx));
+        fctx = palloc(sizeof(directory_fctx));

         /*
          * size = path length + tablespace dirname length
@@ -164,7 +221,7 @@
     }

     funcctx=SRF_PERCALL_SETUP();
-    fctx = (ts_db_fctx*) funcctx->user_fctx;
+    fctx = (directory_fctx*) funcctx->user_fctx;

     if (!fctx->dirdesc)  /* not a tablespace */
         SRF_RETURN_DONE(funcctx);
@@ -202,3 +259,396 @@
     FreeDir(fctx->dirdesc);
     SRF_RETURN_DONE(funcctx);
 }
+
+
+
+/* ------------------------------------
+ * generic file handling functions
+ */
+
+Datum pg_file_length(PG_FUNCTION_ARGS)
+{
+    struct stat fst;
+    char *filename;
+    fst.st_size=0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (stat(filename, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", filename)));
+
+        PG_RETURN_INT64(-1);
+    }
+
+    PG_RETURN_INT64(fst.st_size);
+}
+
+
+Datum pg_file_read(PG_FUNCTION_ARGS)
+{
+    size_t size;
+    char *buf=0;
+    size_t nbytes;
+    int8 pos;
+    FILE *f;
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    pos = PG_GETARG_INT64(1);
+    size = PG_GETARG_INT64(2);
+
+    f = fopen(filename, "r");
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not open file %s for reading: %m", filename)));
+        PG_RETURN_NULL();
+    }
+
+    if (pos >= 0)
+        fseek(f, pos, SEEK_SET);
+    else
+        fseek(f, pos, SEEK_END);
+
+
+    buf = palloc(size + VARHDRSZ);
+
+    nbytes = fread(VARDATA(buf), 1, size, f);
+    if (nbytes < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not read file %s: %m", filename)));
+        PG_RETURN_NULL();
+    }
+    VARATT_SIZEP(buf) = nbytes + VARHDRSZ;
+    fclose(f);
+
+    PG_RETURN_TEXT_P(buf);
+}
+
+
+Datum pg_file_write(PG_FUNCTION_ARGS)
+{
+    FILE *f;
+    char *filename;
+    text *data;
+    int8 count = 0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    data = PG_GETARG_TEXT_P(1);
+
+    if (PG_ARGISNULL(2) || !PG_GETARG_BOOL(2))
+    {
+        struct stat fst;
+        if (stat(filename, &fst) >= 0)
+            ereport(ERROR,
+                    (ERRCODE_DUPLICATE_FILE,
+                     errmsg("file %s exists", filename)));
+
+        f = fopen(filename, "w");
+    }
+    else
+        f = fopen(filename, "a");
+
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could open file %s for writing: %m", filename)));
+    }
+
+    if (VARSIZE(data) != 0)
+    {
+        count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
+
+        if (count != VARSIZE(data))
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("error writing file %s: %m", filename)));
+    }
+    fclose(f);
+
+    PG_RETURN_INT64(count);
+}
+
+
+Datum pg_file_rename(PG_FUNCTION_ARGS)
+{
+    char *fn1, *fn2, *fn3;
+    int rc;
+
+    requireSuperuser();
+
+    fn1=absClusterPath(PG_GETARG_TEXT_P(0));
+    fn2=absClusterPath(PG_GETARG_TEXT_P(1));
+    if (PG_ARGISNULL(2))
+        fn3=0;
+    else
+        fn3=absClusterPath(PG_GETARG_TEXT_P(2));
+
+    struct stat fst;
+    if (stat(fn1, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn1)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+    if (fn3 && stat(fn2, &fst) < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn2)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+
+    rc = stat(fn3 ? fn3 : fn2, &fst);
+    if (rc >= 0 || errno != ENOENT)
+    {
+        ereport(ERROR,
+                (ERRCODE_DUPLICATE_FILE,
+                 errmsg("cannot rename: target file %s exists", fn3 ? fn3 : fn2)));
+    }
+
+    if (fn3)
+    {
+        if (rename(fn2, fn3) != 0)
+        {
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn2, fn3)));
+        }
+        if (rename(fn1, fn2) != 0)
+        {
+            ereport(WARNING,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn1, fn2)));
+
+            if (rename(fn3, fn2) != 0)
+            {
+                ereport(ERROR,
+                        (errcode_for_file_access(),
+                         errmsg("could not rename %s back to %s: %m", fn3, fn2)));
+            }
+            else
+            {
+                ereport(ERROR,
+                        (ERRCODE_UNDEFINED_FILE,
+                         errmsg("renaming %s to %s was reverted", fn2, fn3)));
+
+            }
+        }
+    }
+    if (rename(fn1, fn2) != 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not rename %s to %s: %m", fn1, fn2)));
+    }
+
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_file_unlink(PG_FUNCTION_ARGS)
+{
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (unlink(filename) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not unlink file %s", filename)));
+
+        PG_RETURN_BOOL(false);
+    }
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_dir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+        fctx->location = absClusterPath(PG_GETARG_TEXT_P(0));
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        if (PG_ARGISNULL(1) || !PG_GETARG_BOOL(1))
+        {
+            pfree(fctx->location);
+            fctx->location = 0;
+        }
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *name;
+        text *result;
+        int len;
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+            continue;
+        if (fctx->location)
+        {
+            char *path=palloc(strlen(fctx->location) + strlen(de->d_name) +2);
+            sprintf(path, "%s/%s", fctx->location, de->d_name);
+
+            name = path;
+        }
+        else
+            name = de->d_name;
+
+
+        len = strlen(name);
+        result = palloc(len + VARHDRSZ);
+        VARATT_SIZEP(result) = len + VARHDRSZ;
+        memcpy(VARDATA(result), name, len);
+
+        SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * logfile handling functions
+ */
+
+
+Datum pg_logfile_rotate(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    PG_RETURN_BOOL(LogFileRotate());
+}
+
+
+Datum pg_logfiles_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+        TupleDesc tupdesc;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+
+        if (is_absolute_path(Log_directory))
+            fctx->location = Log_directory;
+        else
+        {
+            fctx->location = palloc(strlen(DataDir) + strlen(Log_directory) +2);
+            sprintf(fctx->location, "%s/%s", DataDir, Log_directory);
+        }
+
+        tupdesc = CreateTemplateTupleDesc(3, false);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "pid",
+                           INT4OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "filename",
+                           TEXTOID, -1, 0);
+
+        funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *values[3];
+        HeapTuple tuple;
+        int year, month, day, hour, min, sec, pid, count;
+
+        // format: YYYY-MM-DD_HHMMSS_PPPPP.log
+        if (strlen(de->d_name) != 27)
+            continue;
+
+        count = sscanf(de->d_name, "%04d-%02d-%02d_%02d%02d%02d_%05d.log", &year, &month, &day, &hour, &min, &sec,
&pid);
+        if (count != 7)
+            continue;
+
+        values[0] = palloc(30);
+        values[1] = palloc(30);
+        values[2] = palloc(strlen(de->d_name) + strlen(fctx->location) + 2);
+
+        sprintf(values[0], "%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec);
+        sprintf(values[1], "%d", pid);
+        sprintf(values[2], "%s/%s", fctx->location, de->d_name);
+
+        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/error/elog.c,v
retrieving revision 1.142
diff -u -r1.142 elog.c
--- src/backend/utils/error/elog.c    24 Jun 2004 21:03:13 -0000    1.142
+++ src/backend/utils/error/elog.c    17 Jul 2004 19:35:37 -0000
@@ -84,6 +84,9 @@
 static void write_eventlog(int level, const char *line);
 #endif

+extern FILE *realStdErr;
+extern pid_t SysLoggerPID;
+
 /*
  * ErrorData holds the data accumulated during any one ereport() cycle.
  * Any non-NULL pointers must point to palloc'd data in ErrorContext.
@@ -1451,10 +1454,27 @@
         write_eventlog(eventlog_level, buf.data);
     }
 #endif   /* WIN32 */
-    /* Write to stderr, if enabled */
-    if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
+
+    /*
+     * Write to stderr. If Log_destination is file or stderr
+     * if file is target, the logger process will handle this
+     */
+    if ((Log_destination & (LOG_DESTINATION_STDERR | LOG_DESTINATION_FILE))
+        || whereToSendOutput == Debug)
     {
-        fprintf(stderr, "%s", buf.data);
+        if (SysLoggerPID == MyProcPid && realStdErr != 0)
+        {
+            /*
+             * If realStdErr is not null in the SysLogger process,
+             * there's something really wrong because stderr is probably
+             * redirected to the pipe. To avoid circular writes, we
+             * write to realStdErr which is hopefully the stderr the postmaster
+             * was started with.
+             */
+            fprintf(realStdErr, "%s", buf.data);
+        }
+        else
+            fprintf(stderr, "%s", buf.data) ;
     }

     pfree(buf.data);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.219
diff -u -r1.219 guc.c
--- src/backend/utils/misc/guc.c    12 Jul 2004 02:22:51 -0000    1.219
+++ src/backend/utils/misc/guc.c    17 Jul 2004 19:35:46 -0000
@@ -44,6 +44,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/syslogger.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1282,6 +1283,23 @@
         BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
     },

+    {
+      {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur after n minutes"),
+       NULL
+      },
+      &Log_RotationAge,
+      24*60, 0, INT_MAX, NULL, NULL
+    },
+    {
+      {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur if this size is reached (in kb)"),
+       NULL
+      },
+      &Log_RotationSize,
+      10*1024, 0, INT_MAX, NULL, NULL
+    },
+
     /* End-of-list marker */
     {
         {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
@@ -1615,13 +1633,23 @@
     {
         {"log_destination", PGC_POSTMASTER, LOGGING_WHERE,
          gettext_noop("Sets the target for log output."),
-         gettext_noop("Valid values are combinations of stderr, syslog "
+         gettext_noop("Valid values are combinations of stderr, file, syslog "
                       "and eventlog, depending on platform."),
          GUC_LIST_INPUT
         },
         &log_destination_string,
         "stderr", assign_log_destination, NULL
     },
+    {
+        {"log_directory", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("Sets the target directory for log output."),
+         gettext_noop("May be specified as relative to the cluster directory "
+                      "or as absolute path."),
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_directory,
+        "pg_log", NULL, NULL
+    },

 #ifdef HAVE_SYSLOG
     {
@@ -5067,6 +5095,8 @@

         if (pg_strcasecmp(tok,"stderr") == 0)
             newlogdest |= LOG_DESTINATION_STDERR;
+        else if (pg_strcasecmp(tok,"file") == 0)
+            newlogdest |= LOG_DESTINATION_FILE;
 #ifdef HAVE_SYSLOG
         else if (pg_strcasecmp(tok,"syslog") == 0)
             newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.115
diff -u -r1.115 postgresql.conf.sample
--- src/backend/utils/misc/postgresql.conf.sample    11 Jul 2004 21:48:25 -0000    1.115
+++ src/backend/utils/misc/postgresql.conf.sample    17 Jul 2004 19:35:47 -0000
@@ -157,9 +157,16 @@

 # - Where to Log -

-#log_destination = 'stderr'    # Valid values are combinations of stderr,
-                                # syslog and eventlog, depending on
-                                # platform.
+#log_destination = 'stderr' # Valid values are combinations of stderr, file,
+                            # syslog and eventlog, depending on platform.
+#log_directory = 'pg_log'   # subdirectory where logfiles are written
+                            # if 'file' log_destination is used.
+                            # May be specified absolute or relative to PGDATA
+#log_rotation_age = 1440    # Automatic rotation of logfiles will happen if
+                            # specified age in minutes is reached. 0 to disable.
+#log_rotation_size = 10240  # Automatic rotation of logfiles will happen if
+                            # specified size in kb is reached. 0 to disable.
+
 #syslog_facility = 'LOCAL0'
 #syslog_ident = 'postgres'

Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.342
diff -u -r1.342 pg_proc.h
--- src/include/catalog/pg_proc.h    12 Jul 2004 20:23:53 -0000    1.342
+++ src/include/catalog/pg_proc.h    17 Jul 2004 19:36:02 -0000
@@ -2819,6 +2819,8 @@
 DESCR("Terminate a backend process");
 DATA(insert OID = 2172 ( pg_cancel_backend              PGNSP PGUID 12 f f t f s 1 23 "23" _null_ pg_cancel_backend -
_null_)); 
 DESCR("Cancel running query on a backend process");
+DATA(insert OID = 2173 ( pg_reload_conf                PGNSP PGUID 12 f f t f s 1 23 "" _null_ pg_reload_conf - _null_
));
+DESCR("Reload postgresql.conf");

 DATA(insert OID = 1946 (  encode                        PGNSP PGUID 12 f f t f i 2 25 "17 25" _null_  binary_encode -
_null_)); 
 DESCR("Convert bytea value into some ascii-only text string");
@@ -3607,6 +3609,25 @@
 DATA(insert OID = 2556 ( pg_tablespace_databases    PGNSP PGUID 12 f f t t s 1 26 "26" _null_ pg_tablespace_databases
-_null_)); 
 DESCR("returns database oids in a tablespace");

+DATA(insert OID = 2557(  pg_file_length                   PGNSP PGUID 12 f f t f v 0 20 "25" _null_ pg_file_length -
_null_)); 
+DESCR("length of generic file");
+DATA(insert OID = 2558(  pg_file_read                   PGNSP PGUID 12 f f t f v 0 2275 "25 20 20" _null_ pg_file_read
-_null_ )); 
+DESCR("read contents of generic file");
+DATA(insert OID = 2559(  pg_file_write                   PGNSP PGUID 12 f f t f v 0 20 "25 25 16" _null_ pg_file_write
-_null_ )); 
+DESCR("write generic file");
+DATA(insert OID = 2560(  pg_file_rename                PGNSP PGUID 12 f f t f v 0 16 "25 25" _null_ pg_file_rename -
_null_)); 
+DESCR("rename generic file");
+DATA(insert OID = 2561(  pg_file_rename                PGNSP PGUID 12 f f t f v 0 16 "25 25 25" _null_ pg_file_rename
-_null_ )); 
+DESCR("rename generic file");
+DATA(insert OID = 2562(  pg_file_unlink                   PGNSP PGUID 12 f f t f v 0 16 "25" _null_ pg_file_unlink -
_null_)); 
+DESCR("remove generic file");
+DATA(insert OID = 2563(  pg_dir_ls                       PGNSP PGUID 12 f f t t v 0 25 "25 16" _null_ pg_dir_ls -
_null_)); 
+DESCR("list generic directory");
+
+DATA(insert OID = 2564(  pg_logfile_rotate               PGNSP PGUID 12 f f t f v 0 16 "" _null_ pg_logfile_rotate -
_null_)); 
+DESCR("rotate log file");
+DATA(insert OID = 2565(  pg_logfiles_ls                   PGNSP PGUID 12 f f t t v 0 2249 "" _null_ pg_logfiles_ls -
_null_)); 
+DESCR("list all available log files");

 /*
  * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.8
diff -u -r1.8 pmsignal.h
--- src/include/storage/pmsignal.h    29 May 2004 22:48:23 -0000    1.8
+++ src/include/storage/pmsignal.h    17 Jul 2004 19:36:03 -0000
@@ -24,6 +24,7 @@
 {
     PMSIGNAL_PASSWORD_CHANGE,    /* pg_pwd file has changed */
     PMSIGNAL_WAKEN_CHILDREN,    /* send a SIGUSR1 signal to all backends */
+    PMSIGNAL_ROTATE_LOGFILE,    /* send SIGUSR1 to syslogger to rotate logfile */

     NUM_PMSIGNALS                /* Must be last value of enum! */
 } PMSignalReason;
Index: src/include/utils/builtins.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.246
diff -u -r1.246 builtins.h
--- src/include/utils/builtins.h    12 Jul 2004 20:23:59 -0000    1.246
+++ src/include/utils/builtins.h    17 Jul 2004 19:36:05 -0000
@@ -362,8 +362,20 @@
 extern Datum current_database(PG_FUNCTION_ARGS);
 extern Datum pg_terminate_backend(PG_FUNCTION_ARGS);
 extern Datum pg_cancel_backend(PG_FUNCTION_ARGS);
+extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);

+extern Datum pg_logfile_rotate(PG_FUNCTION_ARGS);
+extern Datum pg_logfiles_ls(PG_FUNCTION_ARGS);
+
+extern Datum pg_file_length(PG_FUNCTION_ARGS);
+extern Datum pg_file_read(PG_FUNCTION_ARGS);
+extern Datum pg_file_write(PG_FUNCTION_ARGS);
+extern Datum pg_file_rename(PG_FUNCTION_ARGS);
+extern Datum pg_file_unlink(PG_FUNCTION_ARGS);
+
+extern Datum pg_dir_ls(PG_FUNCTION_ARGS);
+
 /* not_in.c */
 extern Datum int4notin(PG_FUNCTION_ARGS);
 extern Datum oidnotin(PG_FUNCTION_ARGS);
Index: src/include/utils/elog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/elog.h,v
retrieving revision 1.70
diff -u -r1.70 elog.h
--- src/include/utils/elog.h    6 Jul 2004 19:51:59 -0000    1.70
+++ src/include/utils/elog.h    17 Jul 2004 19:36:06 -0000
@@ -185,10 +185,10 @@
 #define LOG_DESTINATION_STDERR   1
 #define LOG_DESTINATION_SYSLOG   2
 #define LOG_DESTINATION_EVENTLOG 4
+#define LOG_DESTINATION_FILE     8

 /* Other exported functions */
 extern void DebugFileOpen(void);
-
 /*
  * Write errors to stderr (or by equal means when stderr is
  * not available). Used before ereport/elog can be used

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Updated patch which leaves postmaster runnable after the syslogger
terminated due to pipe problems.
Also includes an updated pg_proc.h which was totally f*cked up in my
previous posting.

Regards,
Andreas
? src/Makefile.global
? src/int2arrayveceq.diff
? src/backend/postgres
? src/backend/catalog/postgres.bki
? src/backend/catalog/postgres.description
? src/backend/postmaster/pm
? src/backend/postmaster/syslogger.c
? src/backend/storage/ipc/.new.ipc.c
? src/backend/utils/adt/arrayfuncs-ap.c
? src/backend/utils/adt/misc-ap.c
? src/backend/utils/adt/misc.target
? src/backend/utils/error/elog-ap.c
? src/backend/utils/mb/conversion_procs/conversion_create.sql
? src/backend/utils/mb/conversion_procs/ascii_and_mic/libascii_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/cyrillic_and_mic/libcyrillic_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_cn_and_mic/libeuc_cn_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_jp_and_sjis/libeuc_jp_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/euc_kr_and_mic/libeuc_kr_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/euc_tw_and_big5/libeuc_tw_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/latin2_and_win1250/liblatin2_and_win1250.so.0.0
? src/backend/utils/mb/conversion_procs/latin_and_mic/liblatin_and_mic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_ascii/libutf8_and_ascii.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_big5/libutf8_and_big5.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_cyrillic/libutf8_and_cyrillic.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_cn/libutf8_and_euc_cn.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_jp/libutf8_and_euc_jp.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_kr/libutf8_and_euc_kr.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_euc_tw/libutf8_and_euc_tw.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gb18030/libutf8_and_gb18030.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_gbk/libutf8_and_gbk.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859/libutf8_and_iso8859.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_iso8859_1/libutf8_and_iso8859_1.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_johab/libutf8_and_johab.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_sjis/libutf8_and_sjis.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_tcvn/libutf8_and_tcvn.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_uhc/libutf8_and_uhc.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win1250/libutf8_and_win1250.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win1256/libutf8_and_win1256.so.0.0
? src/backend/utils/mb/conversion_procs/utf8_and_win874/libutf8_and_win874.so.0.0
? src/bin/initdb/initdb
? src/bin/initlocation/initlocation
? src/bin/ipcclean/ipcclean
? src/bin/pg_config/pg_config
? src/bin/pg_controldata/pg_controldata
? src/bin/pg_ctl/pg_ctl
? src/bin/pg_dump/pg_dump
? src/bin/pg_dump/pg_dumpall
? src/bin/pg_dump/pg_restore
? src/bin/pg_encoding/pg_encoding
? src/bin/pg_id/pg_id
? src/bin/pg_resetxlog/pg_resetxlog
? src/bin/psql/psql
? src/bin/scripts/clusterdb
? src/bin/scripts/createdb
? src/bin/scripts/createlang
? src/bin/scripts/createuser
? src/bin/scripts/dropdb
? src/bin/scripts/droplang
? src/bin/scripts/dropuser
? src/bin/scripts/vacuumdb
? src/include/pg_config.h
? src/include/stamp-h
? src/include/postmaster/syslogger.h
? src/interfaces/ecpg/compatlib/libecpg_compat.so.1.1
? src/interfaces/ecpg/ecpglib/libecpg.so.4.1
? src/interfaces/ecpg/ecpglib/libecpg.so.4.2
? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.1.1
? src/interfaces/ecpg/pgtypeslib/libpgtypes.so.1.2
? src/interfaces/ecpg/preproc/ecpg
? src/interfaces/libpq/libpq.so.3.2
? src/interfaces/libpq/sav
? src/pl/plpgsql/src/libplpgsql.so.1.0
? src/pl/plpgsql/src/sav
? src/port/pg_config_paths.h
? src/timezone/zic
? src/tools/thread/thread_test.diff
Index: src/backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.15
diff -u -r1.15 Makefile
--- src/backend/postmaster/Makefile    29 May 2004 22:48:19 -0000    1.15
+++ src/backend/postmaster/Makefile    18 Jul 2004 22:13:57 -0000
@@ -12,7 +12,7 @@
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global

-OBJS = postmaster.o bgwriter.o pgstat.o
+OBJS = postmaster.o bgwriter.o pgstat.o syslogger.o

 all: SUBSYS.o

Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.411
diff -u -r1.411 postmaster.c
--- src/backend/postmaster/postmaster.c    12 Jul 2004 19:14:56 -0000    1.411
+++ src/backend/postmaster/postmaster.c    18 Jul 2004 22:14:03 -0000
@@ -117,7 +117,7 @@
 #include "utils/ps_status.h"
 #include "bootstrap/bootstrap.h"
 #include "pgstat.h"
-
+#include "postmaster/syslogger.h"

 /*
  * List of active backends (or child processes anyway; we don't actually
@@ -199,6 +199,7 @@
 static pid_t StartupPID = 0,
             BgWriterPID = 0,
             PgStatPID = 0;
+pid_t       SysLoggerPID = 0;

 /* Startup/shutdown state */
 #define            NoShutdown        0
@@ -849,6 +850,12 @@
 #endif

     /*
+     * start logging to file
+     */
+
+    SysLoggerPID = SysLogger_Start();
+
+    /*
      * Reset whereToSendOutput from Debug (its starting state) to None.
      * This prevents ereport from sending log messages to stderr unless
      * the syslog/stderr switch permits.  We don't do this until the
@@ -1222,6 +1229,11 @@
             StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
             PgStatPID = pgstat_start();

+        /* If we have lost the system logger, try to start a new one */
+        if (SysLoggerPID == 0 &&
+            StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+            SysLoggerPID = SysLogger_Start();
+
         /*
          * Touch the socket and lock file at least every ten minutes, to ensure
          * that they are not removed by overzealous /tmp-cleaning tasks.
@@ -1760,6 +1772,8 @@
         SignalChildren(SIGHUP);
         if (BgWriterPID != 0)
             kill(BgWriterPID, SIGHUP);
+        if (SysLoggerPID != 0)
+            kill(SysLoggerPID, SIGHUP);
         /* PgStatPID does not currently need SIGHUP */
         load_hba();
         load_ident();
@@ -1822,7 +1836,6 @@
             if (PgStatPID != 0)
                 kill(PgStatPID, SIGQUIT);
             break;
-
         case SIGINT:
             /*
              * Fast Shutdown:
@@ -1884,6 +1897,7 @@
                 kill(PgStatPID, SIGQUIT);
             if (DLGetHead(BackendList))
                 SignalChildren(SIGQUIT);
+
             ExitPostmaster(0);
             break;
     }
@@ -2020,6 +2034,15 @@
             continue;
         }

+        /* was it the system logger, try to start a new one */
+        if (SysLoggerPID != 0 && pid == SysLoggerPID)
+        {
+            if (exitstatus != 0)
+                LogChildExit(LOG, gettext("system logger process"),
+                             pid, exitstatus);
+            SysLoggerPID = SysLogger_Start();
+            continue;
+        }
         /*
          * Else do standard backend child cleanup.
          */
@@ -2895,6 +2918,16 @@
         PgstatCollectorMain(argc, argv);
         proc_exit(0);
     }
+    if (strcmp(argv[1], "-forklog") == 0)
+    {
+        /* Close the postmaster's sockets */
+        ClosePostmasterPorts();
+
+        /* Do not want to attach to shared memory */
+
+        SysLoggerMain(argc, argv);
+        proc_exit(0);
+    }

     return 1;                    /* shouldn't get here */
 }
@@ -2951,6 +2984,10 @@
         if (Shutdown <= SmartShutdown)
             SignalChildren(SIGUSR1);
     }
+    if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) && SysLoggerPID != 0)
+    {
+        kill(SysLoggerPID, SIGUSR1);
+    }

     PG_SETMASK(&UnBlockSig);

Index: src/backend/utils/adt/misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v
retrieving revision 1.35
diff -u -r1.35 misc.c
--- src/backend/utils/adt/misc.c    2 Jul 2004 18:59:22 -0000    1.35
+++ src/backend/utils/adt/misc.c    18 Jul 2004 22:14:04 -0000
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include <sys/file.h>
+#include <unistd.h>
 #include <signal.h>
 #include <dirent.h>

@@ -26,6 +27,49 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "postmaster/syslogger.h"
+
+/*-----------------------
+ * some helper functions
+ */
+
+/*
+ * Return an absolute path. Argument may be absolute or
+ * relative to the DataDir.
+ */
+static char *absClusterPath(text *arg)
+{
+    char *filename;
+    int len=VARSIZE(arg) - VARHDRSZ;
+
+    filename = palloc(len+1);
+    memcpy(filename, VARDATA(arg), len);
+    filename[len] = 0;
+
+    if (is_absolute_path(filename))
+        return filename;
+    else
+    {
+        char *absname = palloc(strlen(DataDir)+len+2);
+        sprintf(absname, "%s/%s", DataDir, filename);
+        pfree(filename);
+        return absname;
+    }
+}
+
+
+/*
+ * check for superuser, bark if not.
+ */
+static void
+requireSuperuser(void)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser may access generic file functions"))));
+}
+


 /*
@@ -73,10 +117,7 @@

 static int pg_signal_backend(int pid, int sig)
 {
-    if (!superuser())
-        ereport(ERROR,
-                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                 (errmsg("only superuser can signal other backends"))));
+    requireSuperuser();

     if (!IsBackendPid(pid))
     {
@@ -109,18 +150,34 @@
     PG_RETURN_INT32(pg_signal_backend(PG_GETARG_INT32(0),SIGINT));
 }

+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    if (kill(PostmasterPid, SIGHUP))
+    {
+        ereport(WARNING,
+                (errmsg("failed to send signal to postmaster: %m")));
+
+        PG_RETURN_INT32(0);
+    }
+
+    PG_RETURN_INT32(1);
+}
+

 typedef struct
 {
     char *location;
     DIR *dirdesc;
-} ts_db_fctx;
+} directory_fctx;

 Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
     FuncCallContext *funcctx;
     struct dirent *de;
-    ts_db_fctx *fctx;
+    directory_fctx *fctx;

     if (SRF_IS_FIRSTCALL())
     {
@@ -130,7 +187,7 @@
         funcctx=SRF_FIRSTCALL_INIT();
         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

-        fctx = palloc(sizeof(ts_db_fctx));
+        fctx = palloc(sizeof(directory_fctx));

         /*
          * size = path length + tablespace dirname length
@@ -164,7 +221,7 @@
     }

     funcctx=SRF_PERCALL_SETUP();
-    fctx = (ts_db_fctx*) funcctx->user_fctx;
+    fctx = (directory_fctx*) funcctx->user_fctx;

     if (!fctx->dirdesc)  /* not a tablespace */
         SRF_RETURN_DONE(funcctx);
@@ -202,3 +259,396 @@
     FreeDir(fctx->dirdesc);
     SRF_RETURN_DONE(funcctx);
 }
+
+
+
+/* ------------------------------------
+ * generic file handling functions
+ */
+
+Datum pg_file_length(PG_FUNCTION_ARGS)
+{
+    struct stat fst;
+    char *filename;
+    fst.st_size=0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (stat(filename, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", filename)));
+
+        PG_RETURN_INT64(-1);
+    }
+
+    PG_RETURN_INT64(fst.st_size);
+}
+
+
+Datum pg_file_read(PG_FUNCTION_ARGS)
+{
+    size_t size;
+    char *buf=0;
+    size_t nbytes;
+    int8 pos;
+    FILE *f;
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    pos = PG_GETARG_INT64(1);
+    size = PG_GETARG_INT64(2);
+
+    f = fopen(filename, "r");
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not open file %s for reading: %m", filename)));
+        PG_RETURN_NULL();
+    }
+
+    if (pos >= 0)
+        fseek(f, pos, SEEK_SET);
+    else
+        fseek(f, pos, SEEK_END);
+
+
+    buf = palloc(size + VARHDRSZ);
+
+    nbytes = fread(VARDATA(buf), 1, size, f);
+    if (nbytes < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not read file %s: %m", filename)));
+        PG_RETURN_NULL();
+    }
+    VARATT_SIZEP(buf) = nbytes + VARHDRSZ;
+    fclose(f);
+
+    PG_RETURN_TEXT_P(buf);
+}
+
+
+Datum pg_file_write(PG_FUNCTION_ARGS)
+{
+    FILE *f;
+    char *filename;
+    text *data;
+    int8 count = 0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    data = PG_GETARG_TEXT_P(1);
+
+    if (PG_ARGISNULL(2) || !PG_GETARG_BOOL(2))
+    {
+        struct stat fst;
+        if (stat(filename, &fst) >= 0)
+            ereport(ERROR,
+                    (ERRCODE_DUPLICATE_FILE,
+                     errmsg("file %s exists", filename)));
+
+        f = fopen(filename, "w");
+    }
+    else
+        f = fopen(filename, "a");
+
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could open file %s for writing: %m", filename)));
+    }
+
+    if (VARSIZE(data) != 0)
+    {
+        count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
+
+        if (count != VARSIZE(data))
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("error writing file %s: %m", filename)));
+    }
+    fclose(f);
+
+    PG_RETURN_INT64(count);
+}
+
+
+Datum pg_file_rename(PG_FUNCTION_ARGS)
+{
+    char *fn1, *fn2, *fn3;
+    int rc;
+
+    requireSuperuser();
+
+    fn1=absClusterPath(PG_GETARG_TEXT_P(0));
+    fn2=absClusterPath(PG_GETARG_TEXT_P(1));
+    if (PG_ARGISNULL(2))
+        fn3=0;
+    else
+        fn3=absClusterPath(PG_GETARG_TEXT_P(2));
+
+    struct stat fst;
+    if (stat(fn1, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn1)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+    if (fn3 && stat(fn2, &fst) < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn2)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+
+    rc = stat(fn3 ? fn3 : fn2, &fst);
+    if (rc >= 0 || errno != ENOENT)
+    {
+        ereport(ERROR,
+                (ERRCODE_DUPLICATE_FILE,
+                 errmsg("cannot rename: target file %s exists", fn3 ? fn3 : fn2)));
+    }
+
+    if (fn3)
+    {
+        if (rename(fn2, fn3) != 0)
+        {
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn2, fn3)));
+        }
+        if (rename(fn1, fn2) != 0)
+        {
+            ereport(WARNING,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn1, fn2)));
+
+            if (rename(fn3, fn2) != 0)
+            {
+                ereport(ERROR,
+                        (errcode_for_file_access(),
+                         errmsg("could not rename %s back to %s: %m", fn3, fn2)));
+            }
+            else
+            {
+                ereport(ERROR,
+                        (ERRCODE_UNDEFINED_FILE,
+                         errmsg("renaming %s to %s was reverted", fn2, fn3)));
+
+            }
+        }
+    }
+    if (rename(fn1, fn2) != 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not rename %s to %s: %m", fn1, fn2)));
+    }
+
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_file_unlink(PG_FUNCTION_ARGS)
+{
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (unlink(filename) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not unlink file %s", filename)));
+
+        PG_RETURN_BOOL(false);
+    }
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_dir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+        fctx->location = absClusterPath(PG_GETARG_TEXT_P(0));
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        if (PG_ARGISNULL(1) || !PG_GETARG_BOOL(1))
+        {
+            pfree(fctx->location);
+            fctx->location = 0;
+        }
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *name;
+        text *result;
+        int len;
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+            continue;
+        if (fctx->location)
+        {
+            char *path=palloc(strlen(fctx->location) + strlen(de->d_name) +2);
+            sprintf(path, "%s/%s", fctx->location, de->d_name);
+
+            name = path;
+        }
+        else
+            name = de->d_name;
+
+
+        len = strlen(name);
+        result = palloc(len + VARHDRSZ);
+        VARATT_SIZEP(result) = len + VARHDRSZ;
+        memcpy(VARDATA(result), name, len);
+
+        SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * logfile handling functions
+ */
+
+
+Datum pg_logfile_rotate(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    PG_RETURN_BOOL(LogFileRotate());
+}
+
+
+Datum pg_logfiles_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+        TupleDesc tupdesc;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+
+        if (is_absolute_path(Log_directory))
+            fctx->location = Log_directory;
+        else
+        {
+            fctx->location = palloc(strlen(DataDir) + strlen(Log_directory) +2);
+            sprintf(fctx->location, "%s/%s", DataDir, Log_directory);
+        }
+
+        tupdesc = CreateTemplateTupleDesc(3, false);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "pid",
+                           INT4OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "filename",
+                           TEXTOID, -1, 0);
+
+        funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *values[3];
+        HeapTuple tuple;
+        int year, month, day, hour, min, sec, pid, count;
+
+        // format: YYYY-MM-DD_HHMMSS_PPPPP.log
+        if (strlen(de->d_name) != 27)
+            continue;
+
+        count = sscanf(de->d_name, "%04d-%02d-%02d_%02d%02d%02d_%05d.log", &year, &month, &day, &hour, &min, &sec,
&pid);
+        if (count != 7)
+            continue;
+
+        values[0] = palloc(30);
+        values[1] = palloc(30);
+        values[2] = palloc(strlen(de->d_name) + strlen(fctx->location) + 2);
+
+        sprintf(values[0], "%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, min, sec);
+        sprintf(values[1], "%d", pid);
+        sprintf(values[2], "%s/%s", fctx->location, de->d_name);
+
+        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/error/elog.c,v
retrieving revision 1.142
diff -u -r1.142 elog.c
--- src/backend/utils/error/elog.c    24 Jun 2004 21:03:13 -0000    1.142
+++ src/backend/utils/error/elog.c    18 Jul 2004 22:14:07 -0000
@@ -84,6 +84,9 @@
 static void write_eventlog(int level, const char *line);
 #endif

+extern FILE *realStdErr;
+extern pid_t SysLoggerPID;
+
 /*
  * ErrorData holds the data accumulated during any one ereport() cycle.
  * Any non-NULL pointers must point to palloc'd data in ErrorContext.
@@ -1451,10 +1454,27 @@
         write_eventlog(eventlog_level, buf.data);
     }
 #endif   /* WIN32 */
-    /* Write to stderr, if enabled */
-    if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
+
+    /*
+     * Write to stderr. If Log_destination is file or stderr
+     * if file is target, the logger process will handle this
+     */
+    if ((Log_destination & (LOG_DESTINATION_STDERR | LOG_DESTINATION_FILE))
+        || whereToSendOutput == Debug)
     {
-        fprintf(stderr, "%s", buf.data);
+        if (SysLoggerPID == MyProcPid && realStdErr != 0)
+        {
+            /*
+             * If realStdErr is not null in the SysLogger process,
+             * there's something really wrong because stderr is probably
+             * redirected to the pipe. To avoid circular writes, we
+             * write to realStdErr which is hopefully the stderr the postmaster
+             * was started with.
+             */
+            fprintf(realStdErr, "%s", buf.data);
+        }
+        else
+            fprintf(stderr, "%s", buf.data) ;
     }

     pfree(buf.data);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.219
diff -u -r1.219 guc.c
--- src/backend/utils/misc/guc.c    12 Jul 2004 02:22:51 -0000    1.219
+++ src/backend/utils/misc/guc.c    18 Jul 2004 22:14:16 -0000
@@ -44,6 +44,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/syslogger.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1282,6 +1283,23 @@
         BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
     },

+    {
+      {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur after n minutes"),
+       NULL
+      },
+      &Log_RotationAge,
+      24*60, 0, INT_MAX, NULL, NULL
+    },
+    {
+      {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur if this size is reached (in kb)"),
+       NULL
+      },
+      &Log_RotationSize,
+      10*1024, 0, INT_MAX, NULL, NULL
+    },
+
     /* End-of-list marker */
     {
         {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
@@ -1615,13 +1633,23 @@
     {
         {"log_destination", PGC_POSTMASTER, LOGGING_WHERE,
          gettext_noop("Sets the target for log output."),
-         gettext_noop("Valid values are combinations of stderr, syslog "
+         gettext_noop("Valid values are combinations of stderr, file, syslog "
                       "and eventlog, depending on platform."),
          GUC_LIST_INPUT
         },
         &log_destination_string,
         "stderr", assign_log_destination, NULL
     },
+    {
+        {"log_directory", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("Sets the target directory for log output."),
+         gettext_noop("May be specified as relative to the cluster directory "
+                      "or as absolute path."),
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_directory,
+        "pg_log", NULL, NULL
+    },

 #ifdef HAVE_SYSLOG
     {
@@ -5067,6 +5095,8 @@

         if (pg_strcasecmp(tok,"stderr") == 0)
             newlogdest |= LOG_DESTINATION_STDERR;
+        else if (pg_strcasecmp(tok,"file") == 0)
+            newlogdest |= LOG_DESTINATION_FILE;
 #ifdef HAVE_SYSLOG
         else if (pg_strcasecmp(tok,"syslog") == 0)
             newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.115
diff -u -r1.115 postgresql.conf.sample
--- src/backend/utils/misc/postgresql.conf.sample    11 Jul 2004 21:48:25 -0000    1.115
+++ src/backend/utils/misc/postgresql.conf.sample    18 Jul 2004 22:14:17 -0000
@@ -157,9 +157,16 @@

 # - Where to Log -

-#log_destination = 'stderr'    # Valid values are combinations of stderr,
-                                # syslog and eventlog, depending on
-                                # platform.
+#log_destination = 'stderr' # Valid values are combinations of stderr, file,
+                            # syslog and eventlog, depending on platform.
+#log_directory = 'pg_log'   # subdirectory where logfiles are written
+                            # if 'file' log_destination is used.
+                            # May be specified absolute or relative to PGDATA
+#log_rotation_age = 1440    # Automatic rotation of logfiles will happen if
+                            # specified age in minutes is reached. 0 to disable.
+#log_rotation_size = 10240  # Automatic rotation of logfiles will happen if
+                            # specified size in kb is reached. 0 to disable.
+
 #syslog_facility = 'LOCAL0'
 #syslog_ident = 'postgres'

Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.342
diff -u -r1.342 pg_proc.h
--- src/include/catalog/pg_proc.h    12 Jul 2004 20:23:53 -0000    1.342
+++ src/include/catalog/pg_proc.h    18 Jul 2004 22:14:32 -0000
@@ -2819,6 +2819,8 @@
 DESCR("Terminate a backend process");
 DATA(insert OID = 2172 ( pg_cancel_backend              PGNSP PGUID 12 f f t f s 1 23 "23" _null_ pg_cancel_backend -
_null_)); 
 DESCR("Cancel running query on a backend process");
+DATA(insert OID = 2173 ( pg_reload_conf                PGNSP PGUID 12 f f t f s 1 23 "" _null_ pg_reload_conf - _null_
));
+DESCR("Reload postgresql.conf");

 DATA(insert OID = 1946 (  encode                        PGNSP PGUID 12 f f t f i 2 25 "17 25" _null_  binary_encode -
_null_)); 
 DESCR("Convert bytea value into some ascii-only text string");
@@ -3607,6 +3609,25 @@
 DATA(insert OID = 2556 ( pg_tablespace_databases    PGNSP PGUID 12 f f t t s 1 26 "26" _null_ pg_tablespace_databases
-_null_)); 
 DESCR("returns database oids in a tablespace");

+DATA(insert OID = 2557( pg_file_length                   PGNSP PGUID 12 f f t f v 1 20 "25" _null_ pg_file_length -
_null_)); 
+DESCR("length of generic file");
+DATA(insert OID = 2558( pg_file_read                   PGNSP PGUID 12 f f t f v 3 25 "25 20 20" _null_ pg_file_read -
_null_)); 
+DESCR("read contents of generic file");
+DATA(insert OID = 2559( pg_file_write                   PGNSP PGUID 12 f f t f v 3 20 "25 25 16" _null_ pg_file_write
-_null_ )); 
+DESCR("write generic file");
+DATA(insert OID = 2560( pg_file_rename                PGNSP PGUID 12 f f t f v 2 16 "25 25" _null_ pg_file_rename -
_null_)); 
+DESCR("rename generic file");
+DATA(insert OID = 2561( pg_file_rename                PGNSP PGUID 12 f f t f v 33 16 "25 25 25" _null_ pg_file_rename
-_null_ )); 
+DESCR("rename generic file");
+DATA(insert OID = 2562( pg_file_unlink                   PGNSP PGUID 12 f f t f v 1 16 "25" _null_ pg_file_unlink -
_null_)); 
+DESCR("remove generic file");
+DATA(insert OID = 2563( pg_dir_ls                       PGNSP PGUID 12 f f t t v 2 25 "25 16" _null_ pg_dir_ls -
_null_)); 
+DESCR("list generic directory");
+
+DATA(insert OID = 2564( pg_logfile_rotate               PGNSP PGUID 12 f f t f v 0 16 "" _null_ pg_logfile_rotate -
_null_)); 
+DESCR("rotate log file");
+DATA(insert OID = 2565( pg_logfiles_ls                   PGNSP PGUID 12 f f t t v 0 2249 "" _null_ pg_logfiles_ls -
_null_)); 
+DESCR("list all available log files");

 /*
  * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.8
diff -u -r1.8 pmsignal.h
--- src/include/storage/pmsignal.h    29 May 2004 22:48:23 -0000    1.8
+++ src/include/storage/pmsignal.h    18 Jul 2004 22:14:33 -0000
@@ -24,6 +24,7 @@
 {
     PMSIGNAL_PASSWORD_CHANGE,    /* pg_pwd file has changed */
     PMSIGNAL_WAKEN_CHILDREN,    /* send a SIGUSR1 signal to all backends */
+    PMSIGNAL_ROTATE_LOGFILE,    /* send SIGUSR1 to syslogger to rotate logfile */

     NUM_PMSIGNALS                /* Must be last value of enum! */
 } PMSignalReason;
Index: src/include/utils/builtins.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.246
diff -u -r1.246 builtins.h
--- src/include/utils/builtins.h    12 Jul 2004 20:23:59 -0000    1.246
+++ src/include/utils/builtins.h    18 Jul 2004 22:14:35 -0000
@@ -362,8 +362,20 @@
 extern Datum current_database(PG_FUNCTION_ARGS);
 extern Datum pg_terminate_backend(PG_FUNCTION_ARGS);
 extern Datum pg_cancel_backend(PG_FUNCTION_ARGS);
+extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);

+extern Datum pg_logfile_rotate(PG_FUNCTION_ARGS);
+extern Datum pg_logfiles_ls(PG_FUNCTION_ARGS);
+
+extern Datum pg_file_length(PG_FUNCTION_ARGS);
+extern Datum pg_file_read(PG_FUNCTION_ARGS);
+extern Datum pg_file_write(PG_FUNCTION_ARGS);
+extern Datum pg_file_rename(PG_FUNCTION_ARGS);
+extern Datum pg_file_unlink(PG_FUNCTION_ARGS);
+
+extern Datum pg_dir_ls(PG_FUNCTION_ARGS);
+
 /* not_in.c */
 extern Datum int4notin(PG_FUNCTION_ARGS);
 extern Datum oidnotin(PG_FUNCTION_ARGS);
Index: src/include/utils/elog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/elog.h,v
retrieving revision 1.70
diff -u -r1.70 elog.h
--- src/include/utils/elog.h    6 Jul 2004 19:51:59 -0000    1.70
+++ src/include/utils/elog.h    18 Jul 2004 22:14:36 -0000
@@ -185,10 +185,10 @@
 #define LOG_DESTINATION_STDERR   1
 #define LOG_DESTINATION_SYSLOG   2
 #define LOG_DESTINATION_EVENTLOG 4
+#define LOG_DESTINATION_FILE     8

 /* Other exported functions */
 extern void DebugFileOpen(void);
-
 /*
  * Write errors to stderr (or by equal means when stderr is
  * not available). Used before ereport/elog can be used
/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * The system logger (syslogger) is new in Postgres 7.5. It catches all
 * stderr output from backends, the postmaster and subprocesses by
 * redirecting to a pipe, and writes it to a logfile and stderr if
 * configured.
 * It's possible to have size and age limits for the logfile configured
 * in postgresql.conf. If these limits are reached or passed, the
 * current logfile is closed and a new one is created (rotated).
 * The logfiles are stored in a subdirectory (configurable in
 * postgresql.conf), using an internal naming scheme that mangles
 * creation time and current postmaster pid.
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/pmsignal.h"
#include "storage/pg_shmem.h"
#include "storage/ipc.h"
#include "postmaster/syslogger.h"
#include "utils/ps_status.h"
#include "utils/guc.h"

/*
 * GUC parameters
 */
int            Log_RotationAge = 24*60;
int            Log_RotationSize  = 10*1024;
char *      Log_directory = "pg_log";


/*
 * Flags set by interrupt handlers for later service in the main loop.
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;

static pg_time_t    last_rotation_time = 0;


static void sigHupHandler(SIGNAL_ARGS);
static void rotationHandler(SIGNAL_ARGS);
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec();
#endif

static char* logfile_getname(pg_time_t timestamp);
static bool logfile_rotate(void);


FILE *realStdErr = NULL;
FILE *syslogFile = NULL;
int syslogPipe[2] = {0, 0};


/*
 * Main entry point for syslogger process
 * argc/argv parameters are valid only in EXEC_BACKEND case.
 */
void
SysLoggerMain(int argc, char *argv[])
{
    IsUnderPostmaster = true;
    MyProcPid = getpid();
    init_ps_display("system logger process", "", "");
    set_ps_display("");

#ifdef EXEC_BACKEND

    Assert(argc == 6);

    argv += 3;
    StrNCpy(postgres_exec_path,    argv++, MAXPGPATH);
    syslogPipe[0] = atoi(argv++);
    syslogPipe[1] = atoi(argv);

#endif

    /*
     * Properly accept or ignore signals the postmaster might send us
     *
     * Note: we ignore all termination signals, and wait for the postmaster
     * to die to catch as much pipe output as possible.
     */

    pqsignal(SIGHUP, sigHupHandler);    /* set flag to read config file */
    pqsignal(SIGINT,  SIG_IGN);
    pqsignal(SIGTERM, SIG_IGN);
    pqsignal(SIGQUIT, SIG_IGN);
    pqsignal(SIGALRM, SIG_IGN);
    pqsignal(SIGPIPE, SIG_IGN);
    pqsignal(SIGUSR1, rotationHandler);  /* request log rotation */
    pqsignal(SIGUSR2, SIG_IGN);

    /*
     * Reset some signals that are accepted by postmaster but not here
     */
    pqsignal(SIGCHLD, SIG_DFL);
    pqsignal(SIGTTIN, SIG_DFL);
    pqsignal(SIGTTOU, SIG_DFL);
    pqsignal(SIGCONT, SIG_DFL);
    pqsignal(SIGWINCH, SIG_DFL);

    PG_SETMASK(&UnBlockSig);

    /*
     * if we restarted, our stderr is redirected.
     * Direct it back to system stderr.
     */
    if (realStdErr != NULL)
    {
        if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
        {
            char *errstr = strerror(errno);
            /*
             * Now we have a real problem: we can't redirect to stderr,
             * and can't ereport it correctly (it would go into our queue
             * which will never be read
             * We're writing everywhere, hoping to leave at least some
             * hint of what happened.
             */

            fprintf(realStdErr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
            fprintf(stderr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);

            ereport(PANIC,
                    (errcode_for_file_access(),
                     (errmsg("Syslogger couldn't redirect its stderr to the saved stderr: %s", errstr))));
            exit(1);
        }

        realStdErr = NULL;
    }

    /* we'll never write that pipe */
    close(syslogPipe[1]);
    syslogPipe[1] = 0;
    /* remember age of initial logfile */
    last_rotation_time = time(NULL);

    /* main worker loop */
    for (;;)
    {
        pg_time_t    now;
        int            elapsed_secs;
        char        logbuffer[1024];
        char        bytesRead;
        fd_set        rfds;
        struct timeval timeout;
        int         rc;

        if (got_SIGHUP)
        {
            char *olddir=pstrdup(Log_directory);
            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

            /*
             * check if the log directory changed
             * in postgresql.conf. If so, we rotate to make sure
             * we're writing the logfiles where the backends
             * expect us to do so.
             */
            if (strcmp(Log_directory, olddir))
              rotation_requested = true;

            pfree(olddir);
        }

        if (!rotation_requested && last_rotation_time != 0 && Log_RotationAge > 0)
        {
            /*
             * Do an unforced rotation if too much time has elapsed
             * since the last one.
             */
            now = time(NULL);
            elapsed_secs = now - last_rotation_time;
            if (elapsed_secs >= Log_RotationAge * 60)
                rotation_requested = true;
        }

        if (!rotation_requested && Log_RotationSize > 0)
        {
            /*
             * Do an unforced rotation if file is too big
             */
            if (ftell(syslogFile) >= Log_RotationSize * 1024)
                rotation_requested = true;
        }


        if (rotation_requested)
        {
            if (!logfile_rotate())
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         (errmsg("logfile rotation failed, disabling auto rotation (SIGHUP to reenable): %m"))));

                Log_RotationAge = 0;
                Log_RotationSize = 0;
            }
            rotation_requested = false;
        }

        FD_ZERO(&rfds);
        FD_SET(syslogPipe[0], &rfds);
        timeout.tv_sec=1;
        timeout.tv_usec=0;

        /*
         * Check if data is present
         */
        rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout);

        PG_SETMASK(&UnBlockSig);
        if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds))
        {
            bytesRead = piperead(syslogPipe[0],
                                 logbuffer, sizeof(logbuffer));

            if (bytesRead > 0)
            {
                if (fwrite(logbuffer, 1, bytesRead, syslogFile) < 1)
                {
                    ereport(COMMERROR,
                            (errcode_for_file_access(),
                             errmsg("fwrite to logfile failed in system logger: %m")));
                    exit(1);
                }
                fflush(syslogFile);

                if (Log_destination & LOG_DESTINATION_STDERR)
                {
                    fwrite(logbuffer, 1, bytesRead, stderr);
                    fflush(stderr);
                }
            }
            else if (bytesRead < 0 && errno != EINTR)
            {
                ereport(COMMERROR,
                        (errcode_for_socket_access(),
                         errmsg("could not read from system logger pipe: %m")));
                exit(1);
            }
            continue;
        }

        if (rc < 0 && errno != EINTR)
        {
            ereport(COMMERROR,
                    (errcode_for_socket_access(),
                     errmsg("select() failed in system logger: %m")));
            exit(1);
        }

        /*
         * If postmaster died, there's nothing to log any more.
         * We check this only after pipe timeouts to receive as much as possible
         * from the pipe.
         */
        if (!PostmasterIsAlive(true))
        {
            if (syslogFile)
                fclose(syslogFile);
            exit(1);
        }

    }
}


int
SysLogger_Start(void)
{
    pid_t sysloggerPid;
    pg_time_t now;
    char *filename;

    if (!(Log_destination & LOG_DESTINATION_FILE))
        return 0;

    /* create the pipe which will receive stderr output */
    if (!syslogPipe[0])
    {
        if (pgpipe(syslogPipe) < 0)
            ereport(FATAL,
                    (errcode_for_file_access(),
                     (errmsg("pipe for syslogging not created: %m"))));

        if (!set_noblock(syslogPipe[1]))
        {
            ereport(FATAL,
                    (errcode_for_socket_access(),
                     errmsg("could not set syslogging pipe to nonblocking mode: %m")));
        }
    }

    now = time(NULL);

    /*
     * The initial logfile is created right in the postmaster,
     * to insure that the logger process has a writable file.
     */
    filename = logfile_getname(now);

    /*
     * The file is opened for appending, in case the syslogger
     * is restarted right after a rotation.
     */
    syslogFile = fopen(filename, "a+");

    if (!syslogFile)
    {
        /*
         * if we can't open the syslog file for the syslogger process,
         * we try to redirect stderr back to have some logging.
         */
        ereport(WARNING,
                (errcode_for_file_access(),
                 (errmsg("error opening syslog file %s: %m", filename))));

        if (realStdErr != NULL)
        {
            if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
                ereport(FATAL,
                        (errcode_for_file_access(),
                         (errmsg("error redirecting stderr to default: %m"))));

            ereport(FATAL,
                    (errmsg("logfile output corrupted")));
        }

    }
    pfree(filename);

    fflush(stdout);
    fflush(stderr);

#ifdef __BEOS__
    /* Specific beos actions before backend startup */
    beos_before_backend_startup();
#endif

#ifdef EXEC_BACKEND
    switch ((sysloggerPid = syslogger_forkexec()))
#else
    switch ((sysloggerPid = fork()))
#endif
    {
        case -1:
#ifdef __BEOS__
            /* Specific beos actions */
            beos_backend_startup_failed();
#endif
            ereport(LOG,
                    (errmsg("could not fork system logger: %m")));
            return 0;

#ifndef EXEC_BACKEND
        case 0:
            /* in postmaster child ... */
#ifdef __BEOS__
            /* Specific beos actions after backend startup */
            beos_backend_startup();
#endif
            /* Close the postmaster's sockets */
            ClosePostmasterPorts();

            /* Drop our connection to postmaster's shared memory, as well */
            PGSharedMemoryDetach();

            /* do the work */
            SysLoggerMain(0, NULL);
            break;
#endif

        default:
            /* now we redirect stderr, if not done already */
            if (realStdErr == NULL)
            {
                int dh= dup(fileno(stderr));

                if (dh < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("stderr duplication failed: %m"))));

                realStdErr = fdopen(dh, "a");
                if (realStdErr == NULL)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("realstderr reopen failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stdout)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stdout pipe redirection failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stderr)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stderr pipe redirection failed: %m"))));
            }

            /* postmaster will never write the file; close it */
            fclose(syslogFile);
            syslogFile = NULL;
            return (int) sysloggerPid;
    }

    /* we should never reach here */
    return 0;
}


#ifdef EXEC_BACKEND
static pid_t
syslogger_forkexec()
{
    char *av[10];
    int ac = 0, bufc = 0, i;
    char numbuf[2][32];

    av[ac++] = "postgres";
    av[ac++] = "-forklog";
    av[ac++] = NULL;            /* filled in by postmaster_forkexec */

    /* postgres_exec_path is not passed by write_backend_variables */
    av[ac++] = postgres_exec_path;

    /* Pipe file ids (those not passed by write_backend_variables) */
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[0]);
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[1]);

    /* Add to the arg list */
    Assert(bufc <= lengthof(pgstatBuf));
    for (i = 0; i < bufc; i++)
        av[ac++] = numbuf[i];

    av[ac] = NULL;
    Assert(ac < lengthof(av));

    return postmaster_forkexec(ac, av);
}
#endif


/* --------------------------------
 *        logfile routines
 * --------------------------------
 */


/*
 * perform rotation
 */
bool
logfile_rotate(void)
{
    char *filename;
    pg_time_t now;
    FILE *fh;

    now = time(NULL);
    filename = logfile_getname(now);

    fh = fopen(filename, "a+");
    if (!fh)
    {
        /*
         * if opening the new file fails, the caller is responsible
         * for taking consequences. */

        pfree(filename);
        return false;
    }

    fclose(syslogFile);
    syslogFile = fh;

    last_rotation_time = now;

    /* official opening of the new logfile */
    ereport(NOTICE,
            (errcode(ERRCODE_WARNING),
             errmsg("Opened new log file %s", filename)));

    pfree(filename);
    return true;
}



/*
 * creates logfile name using timestamp information
 */

#define TIMESTAMPPATTERN "%Y-%m-%d_%H%M%S"

static char*
logfile_getname(pg_time_t timestamp)
{
    char *filetemplate;
    char *filename;


    if (is_absolute_path(Log_directory))
    {
        filetemplate = palloc(strlen(Log_directory
                                     ) +sizeof(TIMESTAMPPATTERN)+10 +2);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s_%05d.log",
                    Log_directory, TIMESTAMPPATTERN, PostmasterPid);
    }
    else
    {
        filetemplate = palloc(strlen(DataDir) + strlen(Log_directory)
                              +sizeof(TIMESTAMPPATTERN) +10 +3);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s/%s_%05d.log",
                    DataDir, Log_directory, TIMESTAMPPATTERN, PostmasterPid);
    }
    filename = palloc(MAXPGPATH);

    if (!filename || !filetemplate)
        ereport(FATAL,
                (errcode(ERRCODE_OUT_OF_MEMORY),
                 errmsg("Out of memory")));

    pg_strftime(filename, MAXPGPATH, filetemplate, pg_localtime(×tamp));

    pfree(filetemplate);

    return filename;
}

/* --------------------------------
 *        API helper routines
 * --------------------------------
 */

/*
 * Rotate log file
 */
bool
LogFileRotate(void)
{
    if (!(Log_destination & LOG_DESTINATION_FILE))
    {
        ereport(NOTICE,
                (errcode(ERRCODE_WARNING),
                 errmsg("no logfile configured; rotation not supported")));
        return false;
    }

    SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);

    return true;
}

/* --------------------------------
 *        signal handler routines
 * --------------------------------
 */

/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
{
    got_SIGHUP = true;
}

/* SIGUSR1: set flag to rotate logfile */
static void
rotationHandler(SIGNAL_ARGS)
{
    rotation_requested = true;
}

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Magnus Hagander wrote:

 > Super-minor nitpicking from just eyeing over the patch, not actually
 > checking how it works.

Reviewing the own code the most obvious things are overlooked.

 >
 > This patch changes the error message for pg_signal_backend() to "only
 > superuser may access generic file functions".
 >
 > I'm sure that was not intended.. You probably need to pass a parameter
 > to requireSuperuser() about what should go in the err msg.

Yes, seems I was a bit overenthusiastic...

 >
 > Also, I think you forgot to attach syslogger.h.

Indeed, attached is include/postmaster/syslogger.h

Regards,
Andreas
/*-------------------------------------------------------------------------
 *
 * syslogger.h
 *      Exports from postmaster/syslogger.c.
 *
 * Portions Copyright (c) 2004, PostgreSQL Global Development Group
 *
 * $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#ifndef _SYSLOGGER_H
#define _SYSLOGGER_H

#include "pgtime.h"

/* GUC options */
extern int        Log_RotationAge;
extern int        Log_RotationSize;
extern char *   Log_directory;



int SysLogger_Start(void);
void SysLoggerMain(int argc, char *argv[]);

extern bool LogFileRotate(void);

#endif   /* _SYSLOGGER_H */

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> Updated patch which leaves postmaster runnable after the syslogger
> terminated due to pipe problems.

Very nice.  You did a nice trick of reading the log filenames into a
timestamp field:

        count = sscanf(de->d_name, "%04d-%02d-%02d_%02d%02d%02d_%05d.log", &yea$

You only process files that match that pattern for pg_logfiles_ls()
(perhaps this should be pg_logdir_ls for consistency).  And you can then
process the timestamp field in queries.  Good idea.  What happens if a
filename matches the above pattern but isn't a valid timestamp?  Does
the function fail?

My only question is whether we need to allow a custom prefix for the
log filenames so they can be distinguished from other file names in a
user-supplied log directory, like /var/log, or would they always go into
a separate directory under there.  I think a prefix would be nice.

Of course this needs docs but I assume you are waiting to see it applied
first.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:
> Andreas Pflug wrote:
>
>>
>
> Very nice.  You did a nice trick of reading the log filenames into a
> timestamp field:
>
>         count = sscanf(de->d_name, "%04d-%02d-%02d_%02d%02d%02d_%05d.log", &yea$
>
> You only process files that match that pattern for pg_logfiles_ls()
> (perhaps this should be pg_logdir_ls for consistency).

Yup.

   And you can then
> process the timestamp field in queries.  Good idea.  What happens if a
> filename matches the above pattern but isn't a valid timestamp?  Does
> the function fail?

Right now, BuildTupleFromCString will fail for invalid timestamps.

I'm going to change that to pgsql's internal function (strptime seems a
bad idea though).

>
> My only question is whether we need to allow a custom prefix for the
> log filenames so they can be distinguished from other file names in a
> user-supplied log directory, like /var/log, or would they always go into
> a separate directory under there.  I think a prefix would be nice.

How should the prefix be named? pgsql_ ?

>
> Of course this needs docs but I assume you are waiting to see it applied
> first.

Not necessarily, but I'd like names etc. fixed before.

Regards,
Andreas

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> > process the timestamp field in queries.  Good idea.  What happens if a
> > filename matches the above pattern but isn't a valid timestamp?  Does
> > the function fail?
>
> Right now, BuildTupleFromCString will fail for invalid timestamps.
>
> I'm going to change that to pgsql's internal function (strptime seems a
> bad idea though).

Tom used abstimein() in xlog.c.

> > My only question is whether we need to allow a custom prefix for the
> > log filenames so they can be distinguished from other file names in a
> > user-supplied log directory, like /var/log, or would they always go into
> > a separate directory under there.  I think a prefix would be nice.
>
> How should the prefix be named? pgsql_ ?

I would default to that, yes, but allow the user to set it via GUC
variable.  You want to hard-code the time part of the file name so you
can load it into a timestamp, but we should give users control over a
prefix just in case they mix the files in a directory with log files
from other applications.

> > Of course this needs docs but I assume you are waiting to see it applied
> > first.
>
> Not necessarily, but I'd like names etc. fixed before.

Sure.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Peter Eisentraut
Date:
Andreas Pflug wrote:
> How should the prefix be named? pgsql_ ?

Make the file names configurable.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/


Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Andreas Pflug wrote:
> > How should the prefix be named? pgsql_ ?
>
> Make the file names configurable.

He has code to interpret the file names as timestamps that can be used
in queries.  If we allowed full user control over the file name, he
couldn't do that.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Peter Eisentraut
Date:
Bruce Momjian wrote:
> Peter Eisentraut wrote:
> > Andreas Pflug wrote:
> > > How should the prefix be named? pgsql_ ?
> >
> > Make the file names configurable.
>
> He has code to interpret the file names as timestamps that can be
> used in queries.  If we allowed full user control over the file name,
> he couldn't do that.

I can't see this working.  As you know, there are constantly people who
want to install and configure PostgreSQL in the weirdest ways.  If we
tell everybody, you log files must be named like this, it will start
all over again.

Maybe it would be better if the time stamps of the files are used as
time stamps in queries.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/


Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Bruce Momjian wrote:
> > Peter Eisentraut wrote:
> > > Andreas Pflug wrote:
> > > > How should the prefix be named? pgsql_ ?
> > >
> > > Make the file names configurable.
> >
> > He has code to interpret the file names as timestamps that can be
> > used in queries.  If we allowed full user control over the file name,
> > he couldn't do that.
>
> I can't see this working.  As you know, there are constantly people who
> want to install and configure PostgreSQL in the weirdest ways.  If we
> tell everybody, you log files must be named like this, it will start
> all over again.
>
> Maybe it would be better if the time stamps of the files are used as
> time stamps in queries.

I guess you could use the creation time of the inode as the start time,
yes, and then they could call the file names whatever they want.  We
could even add the modification time as the end time.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:
> Peter Eisentraut wrote:
>
>>Bruce Momjian wrote:
>>
>>>Peter Eisentraut wrote:
>>>
>>>>Andreas Pflug wrote:
>>>>
>>>>>How should the prefix be named? pgsql_ ?
>>>>
>>>>Make the file names configurable.
>>>
>>>He has code to interpret the file names as timestamps that can be
>>>used in queries.  If we allowed full user control over the file name,
>>>he couldn't do that.
>>
>>I can't see this working.  As you know, there are constantly people who
>>want to install and configure PostgreSQL in the weirdest ways.  If we
>>tell everybody, you log files must be named like this, it will start
>>all over again.
>>
>>Maybe it would be better if the time stamps of the files are used as
>>time stamps in queries.

Imagine an older logfile was edited with lets say emacs, which will
rename the old and create a new file. Or after log_directory was
changed, the files from the old location are copied to the new location.
This would garble the log_dir_ls output badly.

The logfilename currently also includes the postmaster's pid, there's no
file metadata that could take this information safely.

Apparently it's best to invent a log_file_prefix = 'pgsql_' guc variable.


>
>
> In fact one idea would be to add new stat() columns for
> creation/mod/access file times to the directory listing command.

Actually, a preliminary version of pg_dir_ls did also return some stat
data. I removed this, in favor of functions like pg_file_length.

SELECT fn, pg_file_length(fn)
   FROM pg_dir_ls('/etc', true) AS fn
  WHERE fn like '/etc/p%'

I certainly could supply a record-returning pg_dir_ls
(fn text, fullfn text, len int8, ctime timestamp, atime timestamp, mtime
timestamp)


Regards,
Andreas



Re: logfile subprocess and Fancy File Functions

From
Peter Eisentraut
Date:
Andreas Pflug wrote:
> Apparently it's best to invent a log_file_prefix = 'pgsql_' guc
> variable.

In any case, the prefix "postgresql-" is more in line with current
practice.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/


Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Peter Eisentraut wrote:
> Andreas Pflug wrote:
> > Apparently it's best to invent a log_file_prefix = 'pgsql_' guc
> > variable.
>
> In any case, the prefix "postgresql-" is more in line with current
> practice.

For logs I think pgsql_ is best because that filename is already going
to be long, and I don't usually like dashes in file names.  They look
too much like arguments, but tarballs use them and it looks OK there, I
guess.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Peter Eisentraut
Date:
Bruce Momjian wrote:
> For logs I think pgsql_ is best because that filename is already
> going to be long, and I don't usually like dashes in file names.
> They look too much like arguments, but tarballs use them and it looks
> OK there, I guess.

I wasn't talking about what looks best, I was talking about current
practice for log files.  From that you might be able to extrapolate
what other people have previously found to look best.

In any case, we're not using DOS and 12 inch monitors any more.  File
names can be as long as we want.

--
Peter Eisentraut
http://developer.postgresql.org/~petere/


Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Peter Eisentraut wrote:
> Bruce Momjian wrote:
>
>>For logs I think pgsql_ is best because that filename is already
>>going to be long, and I don't usually like dashes in file names.
>>They look too much like arguments, but tarballs use them and it looks
>>OK there, I guess.
>
>
> I wasn't talking about what looks best, I was talking about current
> practice for log files.  From that you might be able to extrapolate
> what other people have previously found to look best.
>
> In any case, we're not using DOS and 12 inch monitors any more.  File
> names can be as long as we want.
>

Before the thread concentrates too much on a decent default value, I'm
posting a fresh version of the patch, for some more discussion. Current
default for pg_logfile_prefix is 'postgresql-', may the committers
decide which name is The Perfect One.

All previous suggestions have been included, (nb: abstimein is not
usable, because it ereports(ERROR) on failure; we want to skip wrong
files gracefully, so I'm using ParseDateTime and DecodeDateTime instead).

I'd still need feedback on pg_dir_ls: should it merely return a setof
text, or should I enrich it to a record returning all stat data? After
spending another thought on it, I believe the more sql-like approach is
to deliver a full-featured record which is selected for the desired
data, not adding columns with functions.

Regards,
Andreas

/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * The system logger (syslogger) is new in Postgres 7.5. It catches all
 * stderr output from backends, the postmaster and subprocesses by
 * redirecting to a pipe, and writes it to a logfile and stderr if
 * configured.
 * It's possible to have size and age limits for the logfile configured
 * in postgresql.conf. If these limits are reached or passed, the
 * current logfile is closed and a new one is created (rotated).
 * The logfiles are stored in a subdirectory (configurable in
 * postgresql.conf), using an internal naming scheme that mangles
 * creation time and current postmaster pid.
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/pmsignal.h"
#include "storage/pg_shmem.h"
#include "storage/ipc.h"
#include "postmaster/syslogger.h"
#include "utils/ps_status.h"
#include "utils/guc.h"

/*
 * GUC parameters
 */
int            Log_RotationAge = 24*60;
int            Log_RotationSize  = 10*1024;
char *      Log_directory = "pg_log";
char *      Log_filename_prefix = "postgresql_";


/*
 * Flags set by interrupt handlers for later service in the main loop.
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;

static pg_time_t    last_rotation_time = 0;


static void sigHupHandler(SIGNAL_ARGS);
static void rotationHandler(SIGNAL_ARGS);
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec();
#endif

static char* logfile_getname(pg_time_t timestamp);
static bool logfile_rotate(void);


FILE *realStdErr = NULL;
FILE *syslogFile = NULL;
int syslogPipe[2] = {0, 0};


/*
 * Main entry point for syslogger process
 * argc/argv parameters are valid only in EXEC_BACKEND case.
 */
void
SysLoggerMain(int argc, char *argv[])
{
    IsUnderPostmaster = true;
    MyProcPid = getpid();
    init_ps_display("system logger process", "", "");
    set_ps_display("");

#ifdef EXEC_BACKEND

    Assert(argc == 6);

    argv += 3;
    StrNCpy(postgres_exec_path,    argv++, MAXPGPATH);
    syslogPipe[0] = atoi(argv++);
    syslogPipe[1] = atoi(argv);

#endif

    /*
     * Properly accept or ignore signals the postmaster might send us
     *
     * Note: we ignore all termination signals, and wait for the postmaster
     * to die to catch as much pipe output as possible.
     */

    pqsignal(SIGHUP, sigHupHandler);    /* set flag to read config file */
    pqsignal(SIGINT,  SIG_IGN);
    pqsignal(SIGTERM, SIG_IGN);
    pqsignal(SIGQUIT, SIG_IGN);
    pqsignal(SIGALRM, SIG_IGN);
    pqsignal(SIGPIPE, SIG_IGN);
    pqsignal(SIGUSR1, rotationHandler);  /* request log rotation */
    pqsignal(SIGUSR2, SIG_IGN);

    /*
     * Reset some signals that are accepted by postmaster but not here
     */
    pqsignal(SIGCHLD, SIG_DFL);
    pqsignal(SIGTTIN, SIG_DFL);
    pqsignal(SIGTTOU, SIG_DFL);
    pqsignal(SIGCONT, SIG_DFL);
    pqsignal(SIGWINCH, SIG_DFL);

    PG_SETMASK(&UnBlockSig);

    /*
     * if we restarted, our stderr is redirected.
     * Direct it back to system stderr.
     */
    if (realStdErr != NULL)
    {
        if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
        {
            char *errstr = strerror(errno);
            /*
             * Now we have a real problem: we can't redirect to stderr,
             * and can't ereport it correctly (it would go into our queue
             * which will never be read
             * We're writing everywhere, hoping to leave at least some
             * hint of what happened.
             */

            fprintf(realStdErr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
            fprintf(stderr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);

            ereport(PANIC,
                    (errcode_for_file_access(),
                     (errmsg("Syslogger couldn't redirect its stderr to the saved stderr: %s", errstr))));
            exit(1);
        }

        realStdErr = NULL;
    }

    /* we'll never write that pipe */
    close(syslogPipe[1]);
    syslogPipe[1] = 0;
    /* remember age of initial logfile */
    last_rotation_time = time(NULL);

    /* main worker loop */
    for (;;)
    {
        pg_time_t    now;
        int            elapsed_secs;
        char        logbuffer[1024];
        char        bytesRead;
        fd_set        rfds;
        struct timeval timeout;
        int         rc;

        if (got_SIGHUP)
        {
            char *olddir=pstrdup(Log_directory);
            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

            /*
             * check if the log directory changed
             * in postgresql.conf. If so, we rotate to make sure
             * we're writing the logfiles where the backends
             * expect us to do so.
             */
            if (strcmp(Log_directory, olddir))
              rotation_requested = true;

            pfree(olddir);
        }

        if (!rotation_requested && last_rotation_time != 0 && Log_RotationAge > 0)
        {
            /*
             * Do an unforced rotation if too much time has elapsed
             * since the last one.
             */
            now = time(NULL);
            elapsed_secs = now - last_rotation_time;
            if (elapsed_secs >= Log_RotationAge * 60)
                rotation_requested = true;
        }

        if (!rotation_requested && Log_RotationSize > 0)
        {
            /*
             * Do an unforced rotation if file is too big
             */
            if (ftell(syslogFile) >= Log_RotationSize * 1024)
                rotation_requested = true;
        }


        if (rotation_requested)
        {
            if (!logfile_rotate())
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         (errmsg("logfile rotation failed, disabling auto rotation (SIGHUP to reenable): %m"))));

                Log_RotationAge = 0;
                Log_RotationSize = 0;
            }
            rotation_requested = false;
        }

        FD_ZERO(&rfds);
        FD_SET(syslogPipe[0], &rfds);
        timeout.tv_sec=1;
        timeout.tv_usec=0;

        /*
         * Check if data is present
         */
        rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout);

        PG_SETMASK(&UnBlockSig);
        if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds))
        {
            bytesRead = piperead(syslogPipe[0],
                                 logbuffer, sizeof(logbuffer));

            if (bytesRead > 0)
            {
                if (fwrite(logbuffer, 1, bytesRead, syslogFile) < 1)
                {
                    ereport(COMMERROR,
                            (errcode_for_file_access(),
                             errmsg("fwrite to logfile failed in system logger: %m")));
                    exit(1);
                }
                fflush(syslogFile);

                if (Log_destination & LOG_DESTINATION_STDERR)
                {
                    fwrite(logbuffer, 1, bytesRead, stderr);
                    fflush(stderr);
                }
                continue;
            }
            else if (bytesRead < 0 && errno != EINTR)
            {
                ereport(COMMERROR,
                        (errcode_for_socket_access(),
                         errmsg("could not read from system logger pipe: %m")));
                exit(1);
            }
        }

        if (rc < 0 && errno != EINTR)
        {
            ereport(COMMERROR,
                    (errcode_for_socket_access(),
                     errmsg("select() failed in system logger: %m")));
            exit(1);
        }

        /*
         * If postmaster died, there's nothing to log any more.
         * We check this only after pipe timeouts to receive as much as possible
         * from the pipe.
         */
        if (!PostmasterIsAlive(true))
        {
            if (syslogFile)
                fclose(syslogFile);
            exit(1);
        }

    }
}


int
SysLogger_Start(void)
{
    pid_t sysloggerPid;
    pg_time_t now;
    char *filename;

    if (!(Log_destination & LOG_DESTINATION_FILE))
        return 0;

    /* create the pipe which will receive stderr output */
    if (!syslogPipe[0])
    {
        if (pgpipe(syslogPipe) < 0)
            ereport(FATAL,
                    (errcode_for_file_access(),
                     (errmsg("pipe for syslogging not created: %m"))));

        if (!set_noblock(syslogPipe[1]))
        {
            ereport(FATAL,
                    (errcode_for_socket_access(),
                     errmsg("could not set syslogging pipe to nonblocking mode: %m")));
        }
    }

    now = time(NULL);

    /*
     * The initial logfile is created right in the postmaster,
     * to insure that the logger process has a writable file.
     */
    filename = logfile_getname(now);

    /*
     * The file is opened for appending, in case the syslogger
     * is restarted right after a rotation.
     */
    syslogFile = fopen(filename, "a+");

    if (!syslogFile)
    {
        /*
         * if we can't open the syslog file for the syslogger process,
         * we try to redirect stderr back to have some logging.
         */
        ereport(WARNING,
                (errcode_for_file_access(),
                 (errmsg("error opening syslog file %s: %m", filename))));

        if (realStdErr != NULL)
        {
            if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
                ereport(FATAL,
                        (errcode_for_file_access(),
                         (errmsg("error redirecting stderr to default: %m"))));

            ereport(FATAL,
                    (errmsg("logfile output corrupted")));
        }

    }
    pfree(filename);

    fflush(stdout);
    fflush(stderr);

#ifdef __BEOS__
    /* Specific beos actions before backend startup */
    beos_before_backend_startup();
#endif

#ifdef EXEC_BACKEND
    switch ((sysloggerPid = syslogger_forkexec()))
#else
    switch ((sysloggerPid = fork()))
#endif
    {
        case -1:
#ifdef __BEOS__
            /* Specific beos actions */
            beos_backend_startup_failed();
#endif
            ereport(LOG,
                    (errmsg("could not fork system logger: %m")));
            return 0;

#ifndef EXEC_BACKEND
        case 0:
            /* in postmaster child ... */
#ifdef __BEOS__
            /* Specific beos actions after backend startup */
            beos_backend_startup();
#endif
            /* Close the postmaster's sockets */
            ClosePostmasterPorts();

            /* Drop our connection to postmaster's shared memory, as well */
            PGSharedMemoryDetach();

            /* do the work */
            SysLoggerMain(0, NULL);
            break;
#endif

        default:
            /* now we redirect stderr, if not done already */
            if (realStdErr == NULL)
            {
                int dh= dup(fileno(stderr));

                if (dh < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("stderr duplication failed: %m"))));

                realStdErr = fdopen(dh, "a");
                if (realStdErr == NULL)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("realstderr reopen failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stdout)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stdout pipe redirection failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stderr)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stderr pipe redirection failed: %m"))));
            }

            /* postmaster will never write the file; close it */
            fclose(syslogFile);
            syslogFile = NULL;
            return (int) sysloggerPid;
    }

    /* we should never reach here */
    return 0;
}


#ifdef EXEC_BACKEND
static pid_t
syslogger_forkexec()
{
    char *av[10];
    int ac = 0, bufc = 0, i;
    char numbuf[2][32];

    av[ac++] = "postgres";
    av[ac++] = "-forklog";
    av[ac++] = NULL;            /* filled in by postmaster_forkexec */

    /* postgres_exec_path is not passed by write_backend_variables */
    av[ac++] = postgres_exec_path;

    /* Pipe file ids (those not passed by write_backend_variables) */
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[0]);
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[1]);

    /* Add to the arg list */
    Assert(bufc <= lengthof(pgstatBuf));
    for (i = 0; i < bufc; i++)
        av[ac++] = numbuf[i];

    av[ac] = NULL;
    Assert(ac < lengthof(av));

    return postmaster_forkexec(ac, av);
}
#endif


/* --------------------------------
 *        logfile routines
 * --------------------------------
 */


/*
 * perform rotation
 */
bool
logfile_rotate(void)
{
    char *filename;
    pg_time_t now;
    FILE *fh;

    now = time(NULL);
    filename = logfile_getname(now);

    fh = fopen(filename, "a+");
    if (!fh)
    {
        /*
         * if opening the new file fails, the caller is responsible
         * for taking consequences. */

        pfree(filename);
        return false;
    }

    fclose(syslogFile);
    syslogFile = fh;

    last_rotation_time = now;

    /* official opening of the new logfile */
    ereport(NOTICE,
            (errcode(ERRCODE_WARNING),
             errmsg("Opened new log file %s", filename)));

    pfree(filename);
    return true;
}



/*
 * creates logfile name using timestamp information
 */

#define TIMESTAMPPATTERN "%Y-%m-%d_%H%M%S"

static char*
logfile_getname(pg_time_t timestamp)
{
    char *filetemplate;
    char *filename;


    if (is_absolute_path(Log_directory))
    {
      filetemplate = palloc(strlen(Log_directory)
                            + strlen(Log_filename_prefix)
                            + sizeof(TIMESTAMPPATTERN)+10 +2);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s%s_%05d.log",
                    Log_directory, Log_filename_prefix,
                    TIMESTAMPPATTERN, PostmasterPid);
    }
    else
    {
        filetemplate = palloc(strlen(DataDir) + strlen(Log_directory)
                              + strlen(Log_filename_prefix)
                              + sizeof(TIMESTAMPPATTERN) +10 +3);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s/%s%s_%05d.log",
                    DataDir, Log_directory, Log_filename_prefix,
                    TIMESTAMPPATTERN, PostmasterPid);
    }
    filename = palloc(MAXPGPATH);

    if (!filename || !filetemplate)
        ereport(FATAL,
                (errcode(ERRCODE_OUT_OF_MEMORY),
                 errmsg("Out of memory")));

    pg_strftime(filename, MAXPGPATH, filetemplate, pg_localtime(×tamp));

    pfree(filetemplate);

    return filename;
}

/* --------------------------------
 *        API helper routines
 * --------------------------------
 */

/*
 * Rotate log file
 */
bool
LogFileRotate(void)
{
    if (!(Log_destination & LOG_DESTINATION_FILE))
    {
        ereport(NOTICE,
                (errcode(ERRCODE_WARNING),
                 errmsg("no logfile configured; rotation not supported")));
        return false;
    }

    SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);

    return true;
}

/* --------------------------------
 *        signal handler routines
 * --------------------------------
 */

/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
{
    got_SIGHUP = true;
}

/* SIGUSR1: set flag to rotate logfile */
static void
rotationHandler(SIGNAL_ARGS)
{
    rotation_requested = true;
}
/*-------------------------------------------------------------------------
 *
 * syslogger.h
 *      Exports from postmaster/syslogger.c.
 *
 * Portions Copyright (c) 2004, PostgreSQL Global Development Group
 *
 * $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#ifndef _SYSLOGGER_H
#define _SYSLOGGER_H

#include "pgtime.h"

/* GUC options */
extern int        Log_RotationAge;
extern int        Log_RotationSize;
extern char *   Log_directory;
extern char *   Log_filename_prefix;


int SysLogger_Start(void);
void SysLoggerMain(int argc, char *argv[]);

extern bool LogFileRotate(void);

#endif   /* _SYSLOGGER_H */
Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.342
diff -u -r1.342 pg_proc.h
--- src/include/catalog/pg_proc.h    12 Jul 2004 20:23:53 -0000    1.342
+++ src/include/catalog/pg_proc.h    20 Jul 2004 21:31:56 -0000
@@ -2819,6 +2819,8 @@
 DESCR("Terminate a backend process");
 DATA(insert OID = 2172 ( pg_cancel_backend              PGNSP PGUID 12 f f t f s 1 23 "23" _null_ pg_cancel_backend -
_null_)); 
 DESCR("Cancel running query on a backend process");
+DATA(insert OID = 2173 ( pg_reload_conf                PGNSP PGUID 12 f f t f s 1 23 "" _null_ pg_reload_conf - _null_
));
+DESCR("Reload postgresql.conf");

 DATA(insert OID = 1946 (  encode                        PGNSP PGUID 12 f f t f i 2 25 "17 25" _null_  binary_encode -
_null_)); 
 DESCR("Convert bytea value into some ascii-only text string");
@@ -3607,6 +3609,25 @@
 DATA(insert OID = 2556 ( pg_tablespace_databases    PGNSP PGUID 12 f f t t s 1 26 "26" _null_ pg_tablespace_databases
-_null_)); 
 DESCR("returns database oids in a tablespace");

+DATA(insert OID = 2557( pg_file_length                   PGNSP PGUID 12 f f t f v 1 20 "25" _null_ pg_file_length -
_null_)); 
+DESCR("length of generic file");
+DATA(insert OID = 2558( pg_file_read                   PGNSP PGUID 12 f f t f v 3 25 "25 20 20" _null_ pg_file_read -
_null_)); 
+DESCR("read contents of generic file");
+DATA(insert OID = 2559( pg_file_write                   PGNSP PGUID 12 f f t f v 3 20 "25 25 16" _null_ pg_file_write
-_null_ )); 
+DESCR("write generic file");
+DATA(insert OID = 2560( pg_file_rename                PGNSP PGUID 12 f f t f v 2 16 "25 25" _null_ pg_file_rename -
_null_)); 
+DESCR("rename generic file");
+DATA(insert OID = 2561( pg_file_rename                PGNSP PGUID 12 f f t f v 33 16 "25 25 25" _null_ pg_file_rename
-_null_ )); 
+DESCR("rename generic file");
+DATA(insert OID = 2562( pg_file_unlink                   PGNSP PGUID 12 f f t f v 1 16 "25" _null_ pg_file_unlink -
_null_)); 
+DESCR("remove generic file");
+DATA(insert OID = 2563( pg_dir_ls                       PGNSP PGUID 12 f f t t v 2 25 "25 16" _null_ pg_dir_ls -
_null_)); 
+DESCR("list generic directory");
+
+DATA(insert OID = 2564( pg_logfile_rotate               PGNSP PGUID 12 f f t f v 0 16 "" _null_ pg_logfile_rotate -
_null_)); 
+DESCR("rotate log file");
+DATA(insert OID = 2565( pg_logdir_ls                   PGNSP PGUID 12 f f t t v 0 2249 "" _null_ pg_logdir_ls - _null_
));
+DESCR("list all available log files");

 /*
  * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.9
diff -u -r1.9 pmsignal.h
--- src/include/storage/pmsignal.h    19 Jul 2004 02:47:15 -0000    1.9
+++ src/include/storage/pmsignal.h    20 Jul 2004 21:31:57 -0000
@@ -25,7 +25,7 @@
     PMSIGNAL_PASSWORD_CHANGE,    /* pg_pwd file has changed */
     PMSIGNAL_WAKEN_CHILDREN,    /* send a SIGUSR1 signal to all backends */
     PMSIGNAL_WAKEN_ARCHIVER,    /* send a NOTIFY signal to xlog archiver */
-
+    PMSIGNAL_ROTATE_LOGFILE,    /* send SIGUSR1 to syslogger to rotate logfile */
     NUM_PMSIGNALS                /* Must be last value of enum! */
 } PMSignalReason;

Index: src/include/utils/builtins.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.246
diff -u -r1.246 builtins.h
--- src/include/utils/builtins.h    12 Jul 2004 20:23:59 -0000    1.246
+++ src/include/utils/builtins.h    20 Jul 2004 21:31:59 -0000
@@ -362,8 +362,20 @@
 extern Datum current_database(PG_FUNCTION_ARGS);
 extern Datum pg_terminate_backend(PG_FUNCTION_ARGS);
 extern Datum pg_cancel_backend(PG_FUNCTION_ARGS);
+extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);

+extern Datum pg_logfile_rotate(PG_FUNCTION_ARGS);
+extern Datum pg_logdir_ls(PG_FUNCTION_ARGS);
+
+extern Datum pg_file_length(PG_FUNCTION_ARGS);
+extern Datum pg_file_read(PG_FUNCTION_ARGS);
+extern Datum pg_file_write(PG_FUNCTION_ARGS);
+extern Datum pg_file_rename(PG_FUNCTION_ARGS);
+extern Datum pg_file_unlink(PG_FUNCTION_ARGS);
+
+extern Datum pg_dir_ls(PG_FUNCTION_ARGS);
+
 /* not_in.c */
 extern Datum int4notin(PG_FUNCTION_ARGS);
 extern Datum oidnotin(PG_FUNCTION_ARGS);
Index: src/include/utils/elog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/elog.h,v
retrieving revision 1.70
diff -u -r1.70 elog.h
--- src/include/utils/elog.h    6 Jul 2004 19:51:59 -0000    1.70
+++ src/include/utils/elog.h    20 Jul 2004 21:31:59 -0000
@@ -185,10 +185,10 @@
 #define LOG_DESTINATION_STDERR   1
 #define LOG_DESTINATION_SYSLOG   2
 #define LOG_DESTINATION_EVENTLOG 4
+#define LOG_DESTINATION_FILE     8

 /* Other exported functions */
 extern void DebugFileOpen(void);
-
 /*
  * Write errors to stderr (or by equal means when stderr is
  * not available). Used before ereport/elog can be used
Index: src/backend/catalog/system_views.sql
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/catalog/system_views.sql,v
retrieving revision 1.6
diff -u -r1.6 system_views.sql
--- src/backend/catalog/system_views.sql    26 Apr 2004 15:24:41 -0000    1.6
+++ src/backend/catalog/system_views.sql    20 Jul 2004 21:32:00 -0000
@@ -273,3 +273,8 @@
     DO INSTEAD NOTHING;

 GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
+
+CREATE VIEW pg_logdir_ls AS
+    SELECT *
+    FROM pg_logdir_ls() AS A
+    (filetime timestamp, pid int4, filename text);
Index: src/backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.16
diff -u -r1.16 Makefile
--- src/backend/postmaster/Makefile    19 Jul 2004 02:47:08 -0000    1.16
+++ src/backend/postmaster/Makefile    20 Jul 2004 21:32:01 -0000
@@ -12,7 +12,7 @@
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global

-OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o
+OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o syslogger.o

 all: SUBSYS.o

Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.412
diff -u -r1.412 postmaster.c
--- src/backend/postmaster/postmaster.c    19 Jul 2004 02:47:08 -0000    1.412
+++ src/backend/postmaster/postmaster.c    20 Jul 2004 21:32:07 -0000
@@ -118,7 +118,7 @@
 #include "utils/ps_status.h"
 #include "bootstrap/bootstrap.h"
 #include "pgstat.h"
-
+#include "postmaster/syslogger.h"

 /*
  * List of active backends (or child processes anyway; we don't actually
@@ -201,6 +201,7 @@
             BgWriterPID = 0,
             PgArchPID = 0,
             PgStatPID = 0;
+pid_t       SysLoggerPID = 0;

 /* Startup/shutdown state */
 #define            NoShutdown        0
@@ -852,6 +853,12 @@
 #endif

     /*
+     * start logging to file
+     */
+
+    SysLoggerPID = SysLogger_Start();
+
+    /*
      * Reset whereToSendOutput from Debug (its starting state) to None.
      * This prevents ereport from sending log messages to stderr unless
      * the syslog/stderr switch permits.  We don't do this until the
@@ -1230,6 +1237,11 @@
             StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
             PgStatPID = pgstat_start();

+        /* If we have lost the system logger, try to start a new one */
+        if (SysLoggerPID == 0 &&
+            StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+            SysLoggerPID = SysLogger_Start();
+
         /*
          * Touch the socket and lock file at least every ten minutes, to ensure
          * that they are not removed by overzealous /tmp-cleaning tasks.
@@ -1770,6 +1782,9 @@
             kill(BgWriterPID, SIGHUP);
         if (PgArchPID != 0)
             kill(PgArchPID, SIGHUP);
+        if (SysLoggerPID != 0)
+            kill(SysLoggerPID, SIGHUP);
+
         /* PgStatPID does not currently need SIGHUP */
         load_hba();
         load_ident();
@@ -1835,7 +1850,6 @@
             if (PgStatPID != 0)
                 kill(PgStatPID, SIGQUIT);
             break;
-
         case SIGINT:
             /*
              * Fast Shutdown:
@@ -1902,6 +1916,7 @@
                 kill(PgStatPID, SIGQUIT);
             if (DLGetHead(BackendList))
                 SignalChildren(SIGQUIT);
+
             ExitPostmaster(0);
             break;
     }
@@ -2059,6 +2074,15 @@
             continue;
         }

+        /* was it the system logger, try to start a new one */
+        if (SysLoggerPID != 0 && pid == SysLoggerPID)
+        {
+            if (exitstatus != 0)
+                LogChildExit(LOG, gettext("system logger process"),
+                             pid, exitstatus);
+            SysLoggerPID = SysLogger_Start();
+            continue;
+        }
         /*
          * Else do standard backend child cleanup.
          */
@@ -2956,6 +2980,16 @@
         PgstatCollectorMain(argc, argv);
         proc_exit(0);
     }
+    if (strcmp(argv[1], "-forklog") == 0)
+    {
+        /* Close the postmaster's sockets */
+        ClosePostmasterPorts();
+
+        /* Do not want to attach to shared memory */
+
+        SysLoggerMain(argc, argv);
+        proc_exit(0);
+    }

     return 1;                    /* shouldn't get here */
 }
@@ -3012,7 +3046,6 @@
         if (Shutdown <= SmartShutdown)
             SignalChildren(SIGUSR1);
     }
-
     if (PgArchPID != 0 && Shutdown == NoShutdown)
     {
         if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER))
@@ -3024,6 +3057,10 @@
             kill(PgArchPID, SIGUSR1);
         }
     }
+    if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) && SysLoggerPID != 0)
+    {
+        kill(SysLoggerPID, SIGUSR1);
+    }

     PG_SETMASK(&UnBlockSig);

Index: src/backend/utils/adt/misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v
retrieving revision 1.35
diff -u -r1.35 misc.c
--- src/backend/utils/adt/misc.c    2 Jul 2004 18:59:22 -0000    1.35
+++ src/backend/utils/adt/misc.c    20 Jul 2004 21:32:08 -0000
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include <sys/file.h>
+#include <unistd.h>
 #include <signal.h>
 #include <dirent.h>

@@ -26,6 +27,49 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "postmaster/syslogger.h"
+
+/*-----------------------
+ * some helper functions
+ */
+
+/*
+ * Return an absolute path. Argument may be absolute or
+ * relative to the DataDir.
+ */
+static char *absClusterPath(text *arg)
+{
+    char *filename;
+    int len=VARSIZE(arg) - VARHDRSZ;
+
+    filename = palloc(len+1);
+    memcpy(filename, VARDATA(arg), len);
+    filename[len] = 0;
+
+    if (is_absolute_path(filename))
+        return filename;
+    else
+    {
+        char *absname = palloc(strlen(DataDir)+len+2);
+        sprintf(absname, "%s/%s", DataDir, filename);
+        pfree(filename);
+        return absname;
+    }
+}
+
+
+/*
+ * check for superuser, bark if not.
+ */
+static void
+requireSuperuser(void)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser may access generic file functions"))));
+}
+


 /*
@@ -109,18 +153,37 @@
     PG_RETURN_INT32(pg_signal_backend(PG_GETARG_INT32(0),SIGINT));
 }

+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser can signal the postmaster"))));
+
+    if (kill(PostmasterPid, SIGHUP))
+    {
+        ereport(WARNING,
+                (errmsg("failed to send signal to postmaster: %m")));
+
+        PG_RETURN_INT32(0);
+    }
+
+    PG_RETURN_INT32(1);
+}
+

 typedef struct
 {
     char *location;
     DIR *dirdesc;
-} ts_db_fctx;
+} directory_fctx;

 Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
     FuncCallContext *funcctx;
     struct dirent *de;
-    ts_db_fctx *fctx;
+    directory_fctx *fctx;

     if (SRF_IS_FIRSTCALL())
     {
@@ -130,7 +193,7 @@
         funcctx=SRF_FIRSTCALL_INIT();
         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

-        fctx = palloc(sizeof(ts_db_fctx));
+        fctx = palloc(sizeof(directory_fctx));

         /*
          * size = path length + tablespace dirname length
@@ -164,7 +227,7 @@
     }

     funcctx=SRF_PERCALL_SETUP();
-    fctx = (ts_db_fctx*) funcctx->user_fctx;
+    fctx = (directory_fctx*) funcctx->user_fctx;

     if (!fctx->dirdesc)  /* not a tablespace */
         SRF_RETURN_DONE(funcctx);
@@ -202,3 +265,427 @@
     FreeDir(fctx->dirdesc);
     SRF_RETURN_DONE(funcctx);
 }
+
+
+
+/* ------------------------------------
+ * generic file handling functions
+ */
+
+Datum pg_file_length(PG_FUNCTION_ARGS)
+{
+    struct stat fst;
+    char *filename;
+    fst.st_size=0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (stat(filename, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", filename)));
+
+        PG_RETURN_INT64(-1);
+    }
+
+    PG_RETURN_INT64(fst.st_size);
+}
+
+
+Datum pg_file_read(PG_FUNCTION_ARGS)
+{
+    size_t size;
+    char *buf=0;
+    size_t nbytes;
+    int8 pos;
+    FILE *f;
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    pos = PG_GETARG_INT64(1);
+    size = PG_GETARG_INT64(2);
+
+    f = fopen(filename, "r");
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not open file %s for reading: %m", filename)));
+        PG_RETURN_NULL();
+    }
+
+    if (pos >= 0)
+        fseek(f, pos, SEEK_SET);
+    else
+        fseek(f, pos, SEEK_END);
+
+
+    buf = palloc(size + VARHDRSZ);
+
+    nbytes = fread(VARDATA(buf), 1, size, f);
+    if (nbytes < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not read file %s: %m", filename)));
+        PG_RETURN_NULL();
+    }
+    VARATT_SIZEP(buf) = nbytes + VARHDRSZ;
+    fclose(f);
+
+    PG_RETURN_TEXT_P(buf);
+}
+
+
+Datum pg_file_write(PG_FUNCTION_ARGS)
+{
+    FILE *f;
+    char *filename;
+    text *data;
+    int8 count = 0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    data = PG_GETARG_TEXT_P(1);
+
+    if (PG_ARGISNULL(2) || !PG_GETARG_BOOL(2))
+    {
+        struct stat fst;
+        if (stat(filename, &fst) >= 0)
+            ereport(ERROR,
+                    (ERRCODE_DUPLICATE_FILE,
+                     errmsg("file %s exists", filename)));
+
+        f = fopen(filename, "w");
+    }
+    else
+        f = fopen(filename, "a");
+
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could open file %s for writing: %m", filename)));
+    }
+
+    if (VARSIZE(data) != 0)
+    {
+        count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
+
+        if (count != VARSIZE(data))
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("error writing file %s: %m", filename)));
+    }
+    fclose(f);
+
+    PG_RETURN_INT64(count);
+}
+
+
+Datum pg_file_rename(PG_FUNCTION_ARGS)
+{
+    char *fn1, *fn2, *fn3;
+    int rc;
+
+    requireSuperuser();
+
+    fn1=absClusterPath(PG_GETARG_TEXT_P(0));
+    fn2=absClusterPath(PG_GETARG_TEXT_P(1));
+    if (PG_ARGISNULL(2))
+        fn3=0;
+    else
+        fn3=absClusterPath(PG_GETARG_TEXT_P(2));
+
+    struct stat fst;
+    if (stat(fn1, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn1)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+    if (fn3 && stat(fn2, &fst) < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn2)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+
+    rc = stat(fn3 ? fn3 : fn2, &fst);
+    if (rc >= 0 || errno != ENOENT)
+    {
+        ereport(ERROR,
+                (ERRCODE_DUPLICATE_FILE,
+                 errmsg("cannot rename: target file %s exists", fn3 ? fn3 : fn2)));
+    }
+
+    if (fn3)
+    {
+        if (rename(fn2, fn3) != 0)
+        {
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn2, fn3)));
+        }
+        if (rename(fn1, fn2) != 0)
+        {
+            ereport(WARNING,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn1, fn2)));
+
+            if (rename(fn3, fn2) != 0)
+            {
+                ereport(ERROR,
+                        (errcode_for_file_access(),
+                         errmsg("could not rename %s back to %s: %m", fn3, fn2)));
+            }
+            else
+            {
+                ereport(ERROR,
+                        (ERRCODE_UNDEFINED_FILE,
+                         errmsg("renaming %s to %s was reverted", fn2, fn3)));
+
+            }
+        }
+    }
+    if (rename(fn1, fn2) != 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not rename %s to %s: %m", fn1, fn2)));
+    }
+
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_file_unlink(PG_FUNCTION_ARGS)
+{
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (unlink(filename) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not unlink file %s", filename)));
+
+        PG_RETURN_BOOL(false);
+    }
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_dir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+        fctx->location = absClusterPath(PG_GETARG_TEXT_P(0));
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        if (PG_ARGISNULL(1) || !PG_GETARG_BOOL(1))
+        {
+            pfree(fctx->location);
+            fctx->location = 0;
+        }
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *name;
+        text *result;
+        int len;
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+            continue;
+        if (fctx->location)
+        {
+            char *path=palloc(strlen(fctx->location) + strlen(de->d_name) +2);
+            sprintf(path, "%s/%s", fctx->location, de->d_name);
+
+            name = path;
+        }
+        else
+            name = de->d_name;
+
+
+        len = strlen(name);
+        result = palloc(len + VARHDRSZ);
+        VARATT_SIZEP(result) = len + VARHDRSZ;
+        memcpy(VARDATA(result), name, len);
+
+        SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * logfile handling functions
+ */
+
+
+Datum pg_logfile_rotate(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    PG_RETURN_BOOL(LogFileRotate());
+}
+
+
+Datum pg_logdir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+        TupleDesc tupdesc;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+
+        if (is_absolute_path(Log_directory))
+            fctx->location = Log_directory;
+        else
+        {
+            fctx->location = palloc(strlen(DataDir) + strlen(Log_directory) +2);
+            sprintf(fctx->location, "%s/%s", DataDir, Log_directory);
+        }
+
+        tupdesc = CreateTemplateTupleDesc(3, false);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "pid",
+                           INT4OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "filename",
+                           TEXTOID, -1, 0);
+
+        funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *values[3];
+        HeapTuple tuple;
+        int prefixLen=strlen(Log_filename_prefix);
+
+        char       *field[MAXDATEFIELDS];
+        char        lowstr[MAXDATELEN + 1];
+        int            dtype;
+        int            nf, ftype[MAXDATEFIELDS];
+        fsec_t        fsec;
+        int            tz = 0;
+        struct pg_tm date;
+        int         i;
+
+        /*
+         * format as created in logfile_getname():
+         *        prefix_YYYY-MM-DD_HHMMSS_PPPPP.log
+         *   prefixLen   ^
+         *                 prefixLen+17   ^
+         *                       prefixLen+23   ^
+         */
+
+        if (strlen(de->d_name) != prefixLen + 27
+            || memcmp(de->d_name, Log_filename_prefix, prefixLen)
+            || de->d_name[prefixLen + 17] != '_'
+            || strcmp(de->d_name + prefixLen + 23, ".log"))
+              continue;
+
+        values[2] = palloc(strlen(fctx->location) + strlen(de->d_name) + 2);
+        sprintf(values[2], "%s/%s", fctx->location, de->d_name);
+
+        values[0] = de->d_name + prefixLen;       /* timestamp */
+        values[0][17] = 0;
+
+        values[1] = de->d_name + prefixLen + 18;  /* pid */
+        values[1][5] = 0;
+
+        /* check if pid is purely numeric as expected */
+        for (i = 0 ; i < 5 ; i++)
+            if (!isdigit(values[0][i]))
+                continue;
+
+        /* parse and decode expected timestamp */
+        if (ParseDateTime(values[0], lowstr, field, ftype, MAXDATEFIELDS, &nf))
+            continue;
+
+        if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+            continue;
+
+        /* Seems the format fits the expected format; feed it into the tuple */
+
+
+        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/error/elog.c,v
retrieving revision 1.142
diff -u -r1.142 elog.c
--- src/backend/utils/error/elog.c    24 Jun 2004 21:03:13 -0000    1.142
+++ src/backend/utils/error/elog.c    20 Jul 2004 21:32:11 -0000
@@ -84,6 +84,10 @@
 static void write_eventlog(int level, const char *line);
 #endif

+/* in syslogger.c */
+extern FILE *syslogFile;
+extern FILE *realStdErr;
+extern pid_t SysLoggerPID;
 /*
  * ErrorData holds the data accumulated during any one ereport() cycle.
  * Any non-NULL pointers must point to palloc'd data in ErrorContext.
@@ -1451,10 +1455,31 @@
         write_eventlog(eventlog_level, buf.data);
     }
 #endif   /* WIN32 */
-    /* Write to stderr, if enabled */
-    if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
+
+    /*
+     * Write to stderr. If Log_destination is file or stderr
+     * if file is target, the logger process will handle this
+     */
+    if ((Log_destination & (LOG_DESTINATION_STDERR | LOG_DESTINATION_FILE))
+        || whereToSendOutput == Debug)
     {
-        fprintf(stderr, "%s", buf.data);
+        if (SysLoggerPID == MyProcPid && realStdErr != 0)
+        {
+            /*
+             * If realStdErr is not null in the SysLogger process,
+             * there's something really wrong because stderr is probably
+             * redirected to the pipe. To avoid circular writes, we
+             * write to realStdErr which is hopefully the stderr the postmaster
+             * was started with.
+             */
+            fprintf(realStdErr, "%s", buf.data);
+        }
+        else
+            fprintf(stderr, "%s", buf.data) ;
+
+        /* syslogFile is open in SysLogger only */
+        if (syslogFile != 0)
+            fprintf(syslogFile, "%s", buf.data) ;
     }

     pfree(buf.data);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.221
diff -u -r1.221 guc.c
--- src/backend/utils/misc/guc.c    19 Jul 2004 21:39:47 -0000    1.221
+++ src/backend/utils/misc/guc.c    20 Jul 2004 21:32:20 -0000
@@ -44,6 +44,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/syslogger.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1285,6 +1286,23 @@
         BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
     },

+    {
+      {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur after n minutes"),
+       NULL
+      },
+      &Log_RotationAge,
+      24*60, 0, INT_MAX, NULL, NULL
+    },
+    {
+      {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur if this size is reached (in kb)"),
+       NULL
+      },
+      &Log_RotationSize,
+      10*1024, 0, INT_MAX, NULL, NULL
+    },
+
     /* End-of-list marker */
     {
         {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
@@ -1627,13 +1645,32 @@
     {
         {"log_destination", PGC_POSTMASTER, LOGGING_WHERE,
          gettext_noop("Sets the target for log output."),
-         gettext_noop("Valid values are combinations of stderr, syslog "
+         gettext_noop("Valid values are combinations of stderr, file, syslog "
                       "and eventlog, depending on platform."),
          GUC_LIST_INPUT
         },
         &log_destination_string,
         "stderr", assign_log_destination, NULL
     },
+    {
+        {"log_directory", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("Sets the target directory for log output."),
+         gettext_noop("May be specified as relative to the cluster directory "
+                      "or as absolute path."),
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_directory,
+        "pg_log", NULL, NULL
+    },
+    {
+        {"log_filename_prefix", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("prefix for logfile names created in the log_directory."),
+         NULL,
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_filename_prefix,
+        "postgresql-", NULL, NULL
+    },

 #ifdef HAVE_SYSLOG
     {
@@ -5079,6 +5116,8 @@

         if (pg_strcasecmp(tok,"stderr") == 0)
             newlogdest |= LOG_DESTINATION_STDERR;
+        else if (pg_strcasecmp(tok,"file") == 0)
+            newlogdest |= LOG_DESTINATION_FILE;
 #ifdef HAVE_SYSLOG
         else if (pg_strcasecmp(tok,"syslog") == 0)
             newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.116
diff -u -r1.116 postgresql.conf.sample
--- src/backend/utils/misc/postgresql.conf.sample    19 Jul 2004 02:47:10 -0000    1.116
+++ src/backend/utils/misc/postgresql.conf.sample    20 Jul 2004 21:32:21 -0000
@@ -167,9 +167,17 @@

 # - Where to Log -

-#log_destination = 'stderr'    # Valid values are combinations of stderr,
-                                # syslog and eventlog, depending on
-                                # platform.
+#log_destination = 'stderr' # Valid values are combinations of stderr, file,
+                            # syslog and eventlog, depending on platform.
+#log_directory = 'pg_log'   # subdirectory where logfiles are written
+                            # if 'file' log_destination is used.
+                            # May be specified absolute or relative to PGDATA
+#log_filename_prefix = 'postgresql_' # prefix for logfile names
+#log_rotation_age = 1440    # Automatic rotation of logfiles will happen if
+                            # specified age in minutes is reached. 0 to disable.
+#log_rotation_size = 10240  # Automatic rotation of logfiles will happen if
+                            # specified size in kb is reached. 0 to disable.
+
 #syslog_facility = 'LOCAL0'
 #syslog_ident = 'postgres'


Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> > I wasn't talking about what looks best, I was talking about current
> > practice for log files.  From that you might be able to extrapolate
> > what other people have previously found to look best.
> >
> > In any case, we're not using DOS and 12 inch monitors any more.  File
> > names can be as long as we want.
> >
>
> Before the thread concentrates too much on a decent default value, I'm
> posting a fresh version of the patch, for some more discussion. Current
> default for pg_logfile_prefix is 'postgresql-', may the committers
> decide which name is The Perfect One.
>
> All previous suggestions have been included, (nb: abstimein is not
> usable, because it ereports(ERROR) on failure; we want to skip wrong
> files gracefully, so I'm using ParseDateTime and DecodeDateTime instead).
>
> I'd still need feedback on pg_dir_ls: should it merely return a setof
> text, or should I enrich it to a record returning all stat data? After
> spending another thought on it, I believe the more sql-like approach is
> to deliver a full-featured record which is selected for the desired
> data, not adding columns with functions.

This patch looks good to me.  As far as your question about pg_dir_ls
--- you already return multiple columns from pg_logdir_ls, so it seems
you would do the same for returning stat() information from pg_dir_ls,
right?

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> Peter Eisentraut wrote:
> > Bruce Momjian wrote:
> >
> >>For logs I think pgsql_ is best because that filename is already
> >>going to be long, and I don't usually like dashes in file names.
> >>They look too much like arguments, but tarballs use them and it looks
> >>OK there, I guess.
> >
> >
> > I wasn't talking about what looks best, I was talking about current
> > practice for log files.  From that you might be able to extrapolate
> > what other people have previously found to look best.
> >
> > In any case, we're not using DOS and 12 inch monitors any more.  File
> > names can be as long as we want.
> >
>
> Before the thread concentrates too much on a decent default value, I'm
> posting a fresh version of the patch, for some more discussion. Current
> default for pg_logfile_prefix is 'postgresql-', may the committers
> decide which name is The Perfect One.
>
> All previous suggestions have been included, (nb: abstimein is not
> usable, because it ereports(ERROR) on failure; we want to skip wrong
> files gracefully, so I'm using ParseDateTime and DecodeDateTime instead).
>
> I'd still need feedback on pg_dir_ls: should it merely return a setof
> text, or should I enrich it to a record returning all stat data? After
> spending another thought on it, I believe the more sql-like approach is
> to deliver a full-featured record which is selected for the desired
> data, not adding columns with functions.

Now that I look at it, you could remove pg_file_length() and allow
pg_dir_ls to show you the file sizes.  The only problem is that the
length is needed for the read API so you would need to use a WHERE
clause to pick the file where you want the size, rather than just pass
the file name.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> Before the thread concentrates too much on a decent default value, I'm
> posting a fresh version of the patch, for some more discussion. Current
> default for pg_logfile_prefix is 'postgresql-', may the committers
> decide which name is The Perfect One.
>
> All previous suggestions have been included, (nb: abstimein is not
> usable, because it ereports(ERROR) on failure; we want to skip wrong
> files gracefully, so I'm using ParseDateTime and DecodeDateTime instead).
>
> I'd still need feedback on pg_dir_ls: should it merely return a setof
> text, or should I enrich it to a record returning all stat data? After
> spending another thought on it, I believe the more sql-like approach is
> to deliver a full-featured record which is selected for the desired
> data, not adding columns with functions.

OK, new idea.  Forget about modifying pg_dir_ls().  Instead add
pg_file_stat the returns the file size, times.  You can then easily use
that for file size and times.  Also, if you want, add an is_dir boolean
so people can write functions that walk the directory tree.

I noticed we had a big logging discussion during 7.4 beta about logging
and log rotation.  This patch is clearly superior to the ideas we had at
that time.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:
> Andreas Pflug wrote:
>

>
>
> OK, new idea.  Forget about modifying pg_dir_ls().  Instead add
> pg_file_stat the returns the file size, times.  You can then easily use
> that for file size and times.  Also, if you want, add an is_dir boolean
> so people can write functions that walk the directory tree.

I now replaced pg_logfile_length, instead pg_logfile_stat(text) will
return a record (len int8, ctime timestamp, atime timestamp, mtime
timestamp, isdir bool).

For convenience, I'd like to have the function

CREATE FUNCTION pg_file_length(text) RETURNS int8
AS
$BODY$
SELECT len
   FROM pg_file_stat($1) AS stat
    (len int8, ctime timestamp,
    atime timestamp, mtime timestamp, isdir bool)
$BODY$ LANGUAGE SQL STRICT;

Where is the right place to put it?

Also, I wonder how to join pg_file_stat and pg_dir_ls to get a ls -l
like listing. Apparently I can't do that, unless I don't code pg_dir_ls
as returning records too, right?


>
> I noticed we had a big logging discussion during 7.4 beta about logging
> and log rotation.  This patch is clearly superior to the ideas we had at
> that time.
>

Currently, the discussion circles around file functions, not logging. If
you think that part is clean, how about committing it separately so it
can be tested/used (no problem if pg_logfile_rotate() isn't available
right from the start). I'll supply docs RSN.

Regards,
Andreas
Index: src/backend/catalog/system_views.sql
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/catalog/system_views.sql,v
retrieving revision 1.6
diff -u -r1.6 system_views.sql
--- src/backend/catalog/system_views.sql    26 Apr 2004 15:24:41 -0000    1.6
+++ src/backend/catalog/system_views.sql    21 Jul 2004 09:49:22 -0000
@@ -273,3 +273,8 @@
     DO INSTEAD NOTHING;

 GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
+
+CREATE VIEW pg_logdir_ls AS
+    SELECT *
+    FROM pg_logdir_ls() AS A
+    (filetime timestamp, pid int4, filename text);
Index: src/backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.16
diff -u -r1.16 Makefile
--- src/backend/postmaster/Makefile    19 Jul 2004 02:47:08 -0000    1.16
+++ src/backend/postmaster/Makefile    21 Jul 2004 09:49:23 -0000
@@ -12,7 +12,7 @@
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global

-OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o
+OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o syslogger.o

 all: SUBSYS.o

Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.412
diff -u -r1.412 postmaster.c
--- src/backend/postmaster/postmaster.c    19 Jul 2004 02:47:08 -0000    1.412
+++ src/backend/postmaster/postmaster.c    21 Jul 2004 09:49:29 -0000
@@ -118,7 +118,7 @@
 #include "utils/ps_status.h"
 #include "bootstrap/bootstrap.h"
 #include "pgstat.h"
-
+#include "postmaster/syslogger.h"

 /*
  * List of active backends (or child processes anyway; we don't actually
@@ -201,6 +201,7 @@
             BgWriterPID = 0,
             PgArchPID = 0,
             PgStatPID = 0;
+pid_t       SysLoggerPID = 0;

 /* Startup/shutdown state */
 #define            NoShutdown        0
@@ -852,6 +853,12 @@
 #endif

     /*
+     * start logging to file
+     */
+
+    SysLoggerPID = SysLogger_Start();
+
+    /*
      * Reset whereToSendOutput from Debug (its starting state) to None.
      * This prevents ereport from sending log messages to stderr unless
      * the syslog/stderr switch permits.  We don't do this until the
@@ -1230,6 +1237,11 @@
             StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
             PgStatPID = pgstat_start();

+        /* If we have lost the system logger, try to start a new one */
+        if (SysLoggerPID == 0 &&
+            StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+            SysLoggerPID = SysLogger_Start();
+
         /*
          * Touch the socket and lock file at least every ten minutes, to ensure
          * that they are not removed by overzealous /tmp-cleaning tasks.
@@ -1770,6 +1782,9 @@
             kill(BgWriterPID, SIGHUP);
         if (PgArchPID != 0)
             kill(PgArchPID, SIGHUP);
+        if (SysLoggerPID != 0)
+            kill(SysLoggerPID, SIGHUP);
+
         /* PgStatPID does not currently need SIGHUP */
         load_hba();
         load_ident();
@@ -1835,7 +1850,6 @@
             if (PgStatPID != 0)
                 kill(PgStatPID, SIGQUIT);
             break;
-
         case SIGINT:
             /*
              * Fast Shutdown:
@@ -1902,6 +1916,7 @@
                 kill(PgStatPID, SIGQUIT);
             if (DLGetHead(BackendList))
                 SignalChildren(SIGQUIT);
+
             ExitPostmaster(0);
             break;
     }
@@ -2059,6 +2074,15 @@
             continue;
         }

+        /* was it the system logger, try to start a new one */
+        if (SysLoggerPID != 0 && pid == SysLoggerPID)
+        {
+            if (exitstatus != 0)
+                LogChildExit(LOG, gettext("system logger process"),
+                             pid, exitstatus);
+            SysLoggerPID = SysLogger_Start();
+            continue;
+        }
         /*
          * Else do standard backend child cleanup.
          */
@@ -2956,6 +2980,16 @@
         PgstatCollectorMain(argc, argv);
         proc_exit(0);
     }
+    if (strcmp(argv[1], "-forklog") == 0)
+    {
+        /* Close the postmaster's sockets */
+        ClosePostmasterPorts();
+
+        /* Do not want to attach to shared memory */
+
+        SysLoggerMain(argc, argv);
+        proc_exit(0);
+    }

     return 1;                    /* shouldn't get here */
 }
@@ -3012,7 +3046,6 @@
         if (Shutdown <= SmartShutdown)
             SignalChildren(SIGUSR1);
     }
-
     if (PgArchPID != 0 && Shutdown == NoShutdown)
     {
         if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER))
@@ -3024,6 +3057,10 @@
             kill(PgArchPID, SIGUSR1);
         }
     }
+    if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) && SysLoggerPID != 0)
+    {
+        kill(SysLoggerPID, SIGUSR1);
+    }

     PG_SETMASK(&UnBlockSig);

Index: src/backend/utils/adt/misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v
retrieving revision 1.35
diff -u -r1.35 misc.c
--- src/backend/utils/adt/misc.c    2 Jul 2004 18:59:22 -0000    1.35
+++ src/backend/utils/adt/misc.c    21 Jul 2004 09:49:30 -0000
@@ -15,6 +15,7 @@
 #include "postgres.h"

 #include <sys/file.h>
+#include <unistd.h>
 #include <signal.h>
 #include <dirent.h>

@@ -26,6 +27,49 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "postmaster/syslogger.h"
+
+/*-----------------------
+ * some helper functions
+ */
+
+/*
+ * Return an absolute path. Argument may be absolute or
+ * relative to the DataDir.
+ */
+static char *absClusterPath(text *arg)
+{
+    char *filename;
+    int len=VARSIZE(arg) - VARHDRSZ;
+
+    filename = palloc(len+1);
+    memcpy(filename, VARDATA(arg), len);
+    filename[len] = 0;
+
+    if (is_absolute_path(filename))
+        return filename;
+    else
+    {
+        char *absname = palloc(strlen(DataDir)+len+2);
+        sprintf(absname, "%s/%s", DataDir, filename);
+        pfree(filename);
+        return absname;
+    }
+}
+
+
+/*
+ * check for superuser, bark if not.
+ */
+static void
+requireSuperuser(void)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser may access generic file functions"))));
+}
+


 /*
@@ -109,18 +153,37 @@
     PG_RETURN_INT32(pg_signal_backend(PG_GETARG_INT32(0),SIGINT));
 }

+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser can signal the postmaster"))));
+
+    if (kill(PostmasterPid, SIGHUP))
+    {
+        ereport(WARNING,
+                (errmsg("failed to send signal to postmaster: %m")));
+
+        PG_RETURN_INT32(0);
+    }
+
+    PG_RETURN_INT32(1);
+}
+

 typedef struct
 {
     char *location;
     DIR *dirdesc;
-} ts_db_fctx;
+} directory_fctx;

 Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
     FuncCallContext *funcctx;
     struct dirent *de;
-    ts_db_fctx *fctx;
+    directory_fctx *fctx;

     if (SRF_IS_FIRSTCALL())
     {
@@ -130,7 +193,7 @@
         funcctx=SRF_FIRSTCALL_INIT();
         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

-        fctx = palloc(sizeof(ts_db_fctx));
+        fctx = palloc(sizeof(directory_fctx));

         /*
          * size = path length + tablespace dirname length
@@ -164,7 +227,7 @@
     }

     funcctx=SRF_PERCALL_SETUP();
-    fctx = (ts_db_fctx*) funcctx->user_fctx;
+    fctx = (directory_fctx*) funcctx->user_fctx;

     if (!fctx->dirdesc)  /* not a tablespace */
         SRF_RETURN_DONE(funcctx);
@@ -202,3 +265,467 @@
     FreeDir(fctx->dirdesc);
     SRF_RETURN_DONE(funcctx);
 }
+
+
+
+/* ------------------------------------
+ * generic file handling functions
+ */
+
+
+Datum pg_file_stat(PG_FUNCTION_ARGS)
+{
+    AttInMetadata *attinmeta = NULL;
+    char *    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    struct stat fst;
+    int64 length;
+    char lenbuf[30];
+    char cbuf[30], abuf[30], mbuf[30], dbuf[]="f";
+    char *values[5]=
+      {lenbuf, cbuf, abuf, mbuf, dbuf};
+
+    pg_time_t timestamp;
+    HeapTuple tuple;
+
+    if (attinmeta == NULL)
+    {
+        TupleDesc tupdesc = CreateTemplateTupleDesc(5, false);
+
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "length",
+                           INT8OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "ctime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "atime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 4, "mtime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isdir",
+                           BOOLOID, -1, 0);
+
+        attinmeta = TupleDescGetAttInMetadata(tupdesc);
+    }
+
+    if (stat(filename, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", filename)));
+
+        PG_RETURN_NULL();
+    }
+
+    length = fst.st_size;
+    snprintf(lenbuf, 30, INT64_FORMAT, length);
+
+    timestamp = fst.st_ctime;
+    pg_strftime(cbuf, 30, "%F %T", pg_localtime(×tamp));
+
+    timestamp = fst.st_atime;
+    pg_strftime(abuf, 30, "%F %T", pg_localtime(×tamp));
+
+    timestamp = fst.st_mtime;
+    pg_strftime(mbuf, 30, "%F %T", pg_localtime(×tamp));
+
+    if (fst.st_mode & S_IFDIR)
+        dbuf[0] = 't';
+
+    tuple = BuildTupleFromCStrings(attinmeta, values);
+
+    PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
+}
+
+
+Datum pg_file_read(PG_FUNCTION_ARGS)
+{
+    size_t size;
+    char *buf=0;
+    size_t nbytes;
+    int64 pos;
+    FILE *f;
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    pos = PG_GETARG_INT64(1);
+    size = PG_GETARG_INT64(2);
+
+    f = fopen(filename, "r");
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not open file %s for reading: %m", filename)));
+        PG_RETURN_NULL();
+    }
+
+    if (pos >= 0)
+        fseek(f, pos, SEEK_SET);
+    else
+        fseek(f, pos, SEEK_END);
+
+
+    buf = palloc(size + VARHDRSZ);
+
+    nbytes = fread(VARDATA(buf), 1, size, f);
+    if (nbytes < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not read file %s: %m", filename)));
+        PG_RETURN_NULL();
+    }
+    VARATT_SIZEP(buf) = nbytes + VARHDRSZ;
+    fclose(f);
+
+    PG_RETURN_TEXT_P(buf);
+}
+
+
+Datum pg_file_write(PG_FUNCTION_ARGS)
+{
+    FILE *f;
+    char *filename;
+    text *data;
+    int64 count = 0;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+    data = PG_GETARG_TEXT_P(1);
+
+    if (PG_ARGISNULL(2) || !PG_GETARG_BOOL(2))
+    {
+        struct stat fst;
+        if (stat(filename, &fst) >= 0)
+            ereport(ERROR,
+                    (ERRCODE_DUPLICATE_FILE,
+                     errmsg("file %s exists", filename)));
+
+        f = fopen(filename, "w");
+    }
+    else
+        f = fopen(filename, "a");
+
+    if (!f)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could open file %s for writing: %m", filename)));
+    }
+
+    if (VARSIZE(data) != 0)
+    {
+        count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);
+
+        if (count != VARSIZE(data))
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("error writing file %s: %m", filename)));
+    }
+    fclose(f);
+
+    PG_RETURN_INT64(count);
+}
+
+
+Datum pg_file_rename(PG_FUNCTION_ARGS)
+{
+    char *fn1, *fn2, *fn3;
+    int rc;
+
+    requireSuperuser();
+
+    fn1=absClusterPath(PG_GETARG_TEXT_P(0));
+    fn2=absClusterPath(PG_GETARG_TEXT_P(1));
+    if (PG_ARGISNULL(2))
+        fn3=0;
+    else
+        fn3=absClusterPath(PG_GETARG_TEXT_P(2));
+
+    struct stat fst;
+    if (stat(fn1, &fst) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn1)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+    if (fn3 && stat(fn2, &fst) < 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not stat file %s: %m", fn2)));
+
+        PG_RETURN_BOOL(false);
+    }
+
+
+    rc = stat(fn3 ? fn3 : fn2, &fst);
+    if (rc >= 0 || errno != ENOENT)
+    {
+        ereport(ERROR,
+                (ERRCODE_DUPLICATE_FILE,
+                 errmsg("cannot rename: target file %s exists", fn3 ? fn3 : fn2)));
+    }
+
+    if (fn3)
+    {
+        if (rename(fn2, fn3) != 0)
+        {
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn2, fn3)));
+        }
+        if (rename(fn1, fn2) != 0)
+        {
+            ereport(WARNING,
+                    (errcode_for_file_access(),
+                     errmsg("could not rename %s to %s: %m", fn1, fn2)));
+
+            if (rename(fn3, fn2) != 0)
+            {
+                ereport(ERROR,
+                        (errcode_for_file_access(),
+                         errmsg("could not rename %s back to %s: %m", fn3, fn2)));
+            }
+            else
+            {
+                ereport(ERROR,
+                        (ERRCODE_UNDEFINED_FILE,
+                         errmsg("renaming %s to %s was reverted", fn2, fn3)));
+
+            }
+        }
+    }
+    if (rename(fn1, fn2) != 0)
+    {
+        ereport(ERROR,
+                (errcode_for_file_access(),
+                 errmsg("could not rename %s to %s: %m", fn1, fn2)));
+    }
+
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_file_unlink(PG_FUNCTION_ARGS)
+{
+    char *filename;
+
+    requireSuperuser();
+
+    filename = absClusterPath(PG_GETARG_TEXT_P(0));
+
+    if (unlink(filename) < 0)
+    {
+        ereport(WARNING,
+                (errcode_for_file_access(),
+                 errmsg("could not unlink file %s", filename)));
+
+        PG_RETURN_BOOL(false);
+    }
+    PG_RETURN_BOOL(true);
+}
+
+
+Datum pg_dir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+        fctx->location = absClusterPath(PG_GETARG_TEXT_P(0));
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        if (PG_ARGISNULL(1) || !PG_GETARG_BOOL(1))
+        {
+            pfree(fctx->location);
+            fctx->location = 0;
+        }
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *name;
+        text *result;
+        int len;
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+            continue;
+        if (fctx->location)
+        {
+            char *path=palloc(strlen(fctx->location) + strlen(de->d_name) +2);
+            sprintf(path, "%s/%s", fctx->location, de->d_name);
+
+            name = path;
+        }
+        else
+            name = de->d_name;
+
+
+        len = strlen(name);
+        result = palloc(len + VARHDRSZ);
+        VARATT_SIZEP(result) = len + VARHDRSZ;
+        memcpy(VARDATA(result), name, len);
+
+        SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
+
+
+/*
+ * logfile handling functions
+ */
+
+
+Datum pg_logfile_rotate(PG_FUNCTION_ARGS)
+{
+    requireSuperuser();
+
+    PG_RETURN_BOOL(LogFileRotate());
+}
+
+
+Datum pg_logdir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    requireSuperuser();
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+        TupleDesc tupdesc;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+
+        if (is_absolute_path(Log_directory))
+            fctx->location = Log_directory;
+        else
+        {
+            fctx->location = palloc(strlen(DataDir) + strlen(Log_directory) +2);
+            sprintf(fctx->location, "%s/%s", DataDir, Log_directory);
+        }
+
+        tupdesc = CreateTemplateTupleDesc(3, false);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "pid",
+                           INT4OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "filename",
+                           TEXTOID, -1, 0);
+
+        funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *values[3];
+        HeapTuple tuple;
+        int prefixLen=strlen(Log_filename_prefix);
+
+        char       *field[MAXDATEFIELDS];
+        char        lowstr[MAXDATELEN + 1];
+        int            dtype;
+        int            nf, ftype[MAXDATEFIELDS];
+        fsec_t        fsec;
+        int            tz = 0;
+        struct pg_tm date;
+        int         i;
+
+        /*
+         * format as created in logfile_getname():
+         *        prefix_YYYY-MM-DD_HHMMSS_PPPPP.log
+         *   prefixLen   ^
+         *                 prefixLen+17   ^
+         *                       prefixLen+23   ^
+         */
+
+        if (strlen(de->d_name) != prefixLen + 27
+            || memcmp(de->d_name, Log_filename_prefix, prefixLen)
+            || de->d_name[prefixLen + 17] != '_'
+            || strcmp(de->d_name + prefixLen + 23, ".log"))
+              continue;
+
+        values[2] = palloc(strlen(fctx->location) + strlen(de->d_name) + 2);
+        sprintf(values[2], "%s/%s", fctx->location, de->d_name);
+
+        values[0] = de->d_name + prefixLen;       /* timestamp */
+        values[0][17] = 0;
+
+        values[1] = de->d_name + prefixLen + 18;  /* pid */
+        values[1][5] = 0;
+
+        /* check if pid is purely numeric as expected */
+        for (i = 0 ; i < 5 ; i++)
+            if (!isdigit(values[0][i]))
+                continue;
+
+        /* parse and decode expected timestamp */
+        if (ParseDateTime(values[0], lowstr, field, ftype, MAXDATEFIELDS, &nf))
+            continue;
+
+        if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+            continue;
+
+        /* Seems the format fits the expected format; feed it into the tuple */
+
+
+        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/error/elog.c,v
retrieving revision 1.142
diff -u -r1.142 elog.c
--- src/backend/utils/error/elog.c    24 Jun 2004 21:03:13 -0000    1.142
+++ src/backend/utils/error/elog.c    21 Jul 2004 09:49:33 -0000
@@ -84,6 +84,10 @@
 static void write_eventlog(int level, const char *line);
 #endif

+/* in syslogger.c */
+extern FILE *syslogFile;
+extern FILE *realStdErr;
+extern pid_t SysLoggerPID;
 /*
  * ErrorData holds the data accumulated during any one ereport() cycle.
  * Any non-NULL pointers must point to palloc'd data in ErrorContext.
@@ -1451,10 +1455,31 @@
         write_eventlog(eventlog_level, buf.data);
     }
 #endif   /* WIN32 */
-    /* Write to stderr, if enabled */
-    if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
+
+    /*
+     * Write to stderr. If Log_destination is file or stderr
+     * if file is target, the logger process will handle this
+     */
+    if ((Log_destination & (LOG_DESTINATION_STDERR | LOG_DESTINATION_FILE))
+        || whereToSendOutput == Debug)
     {
-        fprintf(stderr, "%s", buf.data);
+        if (SysLoggerPID == MyProcPid && realStdErr != 0)
+        {
+            /*
+             * If realStdErr is not null in the SysLogger process,
+             * there's something really wrong because stderr is probably
+             * redirected to the pipe. To avoid circular writes, we
+             * write to realStdErr which is hopefully the stderr the postmaster
+             * was started with.
+             */
+            fprintf(realStdErr, "%s", buf.data);
+        }
+        else
+            fprintf(stderr, "%s", buf.data) ;
+
+        /* syslogFile is open in SysLogger only */
+        if (syslogFile != 0)
+            fprintf(syslogFile, "%s", buf.data) ;
     }

     pfree(buf.data);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.221
diff -u -r1.221 guc.c
--- src/backend/utils/misc/guc.c    19 Jul 2004 21:39:47 -0000    1.221
+++ src/backend/utils/misc/guc.c    21 Jul 2004 09:49:42 -0000
@@ -44,6 +44,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/syslogger.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1285,6 +1286,23 @@
         BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
     },

+    {
+      {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur after n minutes"),
+       NULL
+      },
+      &Log_RotationAge,
+      24*60, 0, INT_MAX, NULL, NULL
+    },
+    {
+      {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur if this size is reached (in kb)"),
+       NULL
+      },
+      &Log_RotationSize,
+      10*1024, 0, INT_MAX, NULL, NULL
+    },
+
     /* End-of-list marker */
     {
         {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
@@ -1627,13 +1645,32 @@
     {
         {"log_destination", PGC_POSTMASTER, LOGGING_WHERE,
          gettext_noop("Sets the target for log output."),
-         gettext_noop("Valid values are combinations of stderr, syslog "
+         gettext_noop("Valid values are combinations of stderr, file, syslog "
                       "and eventlog, depending on platform."),
          GUC_LIST_INPUT
         },
         &log_destination_string,
         "stderr", assign_log_destination, NULL
     },
+    {
+        {"log_directory", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("Sets the target directory for log output."),
+         gettext_noop("May be specified as relative to the cluster directory "
+                      "or as absolute path."),
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_directory,
+        "pg_log", NULL, NULL
+    },
+    {
+        {"log_filename_prefix", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("prefix for logfile names created in the log_directory."),
+         NULL,
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_filename_prefix,
+        "postgresql-", NULL, NULL
+    },

 #ifdef HAVE_SYSLOG
     {
@@ -5079,6 +5116,8 @@

         if (pg_strcasecmp(tok,"stderr") == 0)
             newlogdest |= LOG_DESTINATION_STDERR;
+        else if (pg_strcasecmp(tok,"file") == 0)
+            newlogdest |= LOG_DESTINATION_FILE;
 #ifdef HAVE_SYSLOG
         else if (pg_strcasecmp(tok,"syslog") == 0)
             newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.116
diff -u -r1.116 postgresql.conf.sample
--- src/backend/utils/misc/postgresql.conf.sample    19 Jul 2004 02:47:10 -0000    1.116
+++ src/backend/utils/misc/postgresql.conf.sample    21 Jul 2004 09:49:43 -0000
@@ -167,9 +167,17 @@

 # - Where to Log -

-#log_destination = 'stderr'    # Valid values are combinations of stderr,
-                                # syslog and eventlog, depending on
-                                # platform.
+#log_destination = 'stderr' # Valid values are combinations of stderr, file,
+                            # syslog and eventlog, depending on platform.
+#log_directory = 'pg_log'   # subdirectory where logfiles are written
+                            # if 'file' log_destination is used.
+                            # May be specified absolute or relative to PGDATA
+#log_filename_prefix = 'postgresql_' # prefix for logfile names
+#log_rotation_age = 1440    # Automatic rotation of logfiles will happen if
+                            # specified age in minutes is reached. 0 to disable.
+#log_rotation_size = 10240  # Automatic rotation of logfiles will happen if
+                            # specified size in kb is reached. 0 to disable.
+
 #syslog_facility = 'LOCAL0'
 #syslog_ident = 'postgres'

Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.342
diff -u -r1.342 pg_proc.h
--- src/include/catalog/pg_proc.h    12 Jul 2004 20:23:53 -0000    1.342
+++ src/include/catalog/pg_proc.h    21 Jul 2004 09:49:57 -0000
@@ -2819,6 +2819,8 @@
 DESCR("Terminate a backend process");
 DATA(insert OID = 2172 ( pg_cancel_backend              PGNSP PGUID 12 f f t f s 1 23 "23" _null_ pg_cancel_backend -
_null_)); 
 DESCR("Cancel running query on a backend process");
+DATA(insert OID = 2173 ( pg_reload_conf                PGNSP PGUID 12 f f t f s 1 23 "" _null_ pg_reload_conf - _null_
));
+DESCR("Reload postgresql.conf");

 DATA(insert OID = 1946 (  encode                        PGNSP PGUID 12 f f t f i 2 25 "17 25" _null_  binary_encode -
_null_)); 
 DESCR("Convert bytea value into some ascii-only text string");
@@ -3607,6 +3609,26 @@
 DATA(insert OID = 2556 ( pg_tablespace_databases    PGNSP PGUID 12 f f t t s 1 26 "26" _null_ pg_tablespace_databases
-_null_)); 
 DESCR("returns database oids in a tablespace");

+DATA(insert OID = 2557( pg_file_stat                   PGNSP PGUID 12 f f t f v 1 2249 "25" _null_ pg_file_stat -
_null_)); 
+DESCR("stat properties of generic file");
+DATA(insert OID = 2558( pg_file_read                   PGNSP PGUID 12 f f t f v 3 25 "25 20 20" _null_ pg_file_read -
_null_)); 
+DESCR("read contents of generic file");
+DATA(insert OID = 2559( pg_file_write                   PGNSP PGUID 12 f f t f v 3 20 "25 25 16" _null_ pg_file_write
-_null_ )); 
+DESCR("write generic file");
+DATA(insert OID = 2560( pg_file_rename                PGNSP PGUID 12 f f t f v 2 16 "25 25" _null_ pg_file_rename -
_null_)); 
+DESCR("rename generic file");
+DATA(insert OID = 2561( pg_file_rename                PGNSP PGUID 12 f f t f v 33 16 "25 25 25" _null_ pg_file_rename
-_null_ )); 
+DESCR("rename generic file");
+DATA(insert OID = 2562( pg_file_unlink                   PGNSP PGUID 12 f f t f v 1 16 "25" _null_ pg_file_unlink -
_null_)); 
+DESCR("remove generic file");
+
+DATA(insert OID = 2563( pg_dir_ls                       PGNSP PGUID 12 f f t t v 2 25 "25 16" _null_ pg_dir_ls -
_null_)); 
+DESCR("list generic directory");
+
+DATA(insert OID = 2564( pg_logfile_rotate               PGNSP PGUID 12 f f t f v 0 16 "" _null_ pg_logfile_rotate -
_null_)); 
+DESCR("rotate log file");
+DATA(insert OID = 2565( pg_logdir_ls                   PGNSP PGUID 12 f f t t v 0 2249 "" _null_ pg_logdir_ls - _null_
));
+DESCR("list all available log files");

 /*
  * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.9
diff -u -r1.9 pmsignal.h
--- src/include/storage/pmsignal.h    19 Jul 2004 02:47:15 -0000    1.9
+++ src/include/storage/pmsignal.h    21 Jul 2004 09:49:58 -0000
@@ -25,7 +25,7 @@
     PMSIGNAL_PASSWORD_CHANGE,    /* pg_pwd file has changed */
     PMSIGNAL_WAKEN_CHILDREN,    /* send a SIGUSR1 signal to all backends */
     PMSIGNAL_WAKEN_ARCHIVER,    /* send a NOTIFY signal to xlog archiver */
-
+    PMSIGNAL_ROTATE_LOGFILE,    /* send SIGUSR1 to syslogger to rotate logfile */
     NUM_PMSIGNALS                /* Must be last value of enum! */
 } PMSignalReason;

Index: src/include/utils/builtins.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.246
diff -u -r1.246 builtins.h
--- src/include/utils/builtins.h    12 Jul 2004 20:23:59 -0000    1.246
+++ src/include/utils/builtins.h    21 Jul 2004 09:50:00 -0000
@@ -362,8 +362,20 @@
 extern Datum current_database(PG_FUNCTION_ARGS);
 extern Datum pg_terminate_backend(PG_FUNCTION_ARGS);
 extern Datum pg_cancel_backend(PG_FUNCTION_ARGS);
+extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);

+extern Datum pg_logfile_rotate(PG_FUNCTION_ARGS);
+extern Datum pg_logdir_ls(PG_FUNCTION_ARGS);
+
+extern Datum pg_file_stat(PG_FUNCTION_ARGS);
+extern Datum pg_file_read(PG_FUNCTION_ARGS);
+extern Datum pg_file_write(PG_FUNCTION_ARGS);
+extern Datum pg_file_rename(PG_FUNCTION_ARGS);
+extern Datum pg_file_unlink(PG_FUNCTION_ARGS);
+
+extern Datum pg_dir_ls(PG_FUNCTION_ARGS);
+
 /* not_in.c */
 extern Datum int4notin(PG_FUNCTION_ARGS);
 extern Datum oidnotin(PG_FUNCTION_ARGS);
Index: src/include/utils/elog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/elog.h,v
retrieving revision 1.70
diff -u -r1.70 elog.h
--- src/include/utils/elog.h    6 Jul 2004 19:51:59 -0000    1.70
+++ src/include/utils/elog.h    21 Jul 2004 09:50:01 -0000
@@ -185,10 +185,10 @@
 #define LOG_DESTINATION_STDERR   1
 #define LOG_DESTINATION_SYSLOG   2
 #define LOG_DESTINATION_EVENTLOG 4
+#define LOG_DESTINATION_FILE     8

 /* Other exported functions */
 extern void DebugFileOpen(void);
-
 /*
  * Write errors to stderr (or by equal means when stderr is
  * not available). Used before ereport/elog can be used

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> Bruce Momjian wrote:
> > Andreas Pflug wrote:
> >
>
> >
> >
> > OK, new idea.  Forget about modifying pg_dir_ls().  Instead add
> > pg_file_stat the returns the file size, times.  You can then easily use
> > that for file size and times.  Also, if you want, add an is_dir boolean
> > so people can write functions that walk the directory tree.
>
> I now replaced pg_logfile_length, instead pg_logfile_stat(text) will
> return a record (len int8, ctime timestamp, atime timestamp, mtime
> timestamp, isdir bool).

You mean pg_file_stat(text), right?  That's what I see in your code.

> For convenience, I'd like to have the function
>
> CREATE FUNCTION pg_file_length(text) RETURNS int8
> AS
> $BODY$
> SELECT len
>    FROM pg_file_stat($1) AS stat
>     (len int8, ctime timestamp,
>     atime timestamp, mtime timestamp, isdir bool)
> $BODY$ LANGUAGE SQL STRICT;
>
> Where is the right place to put it?

Take a look at obj_description in include/catalog/pg_proc.h.  That
should be a good example.

> Also, I wonder how to join pg_file_stat and pg_dir_ls to get a ls -l
> like listing. Apparently I can't do that, unless I don't code pg_dir_ls
> as returning records too, right?

Ideally you want:

    select filename, pg_file_stat(filename)
    from pg_dir_ls()

or something like that.  However, I don't think you can have a function
call returning multiple values in the target list, and I can't figure
out how to pass an argument to the function if it is in the target list.
Ideas?

> > I noticed we had a big logging discussion during 7.4 beta about logging
> > and log rotation.  This patch is clearly superior to the ideas we had at
> > that time.
> >
>
> Currently, the discussion circles around file functions, not logging. If
> you think that part is clean, how about committing it separately so it
> can be tested/used (no problem if pg_logfile_rotate() isn't available
> right from the start). I'll supply docs RSN.

Is pg_logfile_rotate() not working?  You mean pg_file_length().

Seems we should get this stat() idea working first.  Adjusting catalog
entries once they are in CVS means a catalog bump for every catalog
change.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:


>>I now replaced pg_logfile_length, instead pg_logfile_stat(text) will
>>return a record (len int8, ctime timestamp, atime timestamp, mtime
>>timestamp, isdir bool).
>
>
> You mean pg_file_stat(text), right?

Of course.

>
>>For convenience, I'd like to have the function
>>
>>CREATE FUNCTION pg_file_length(text) RETURNS int8
  >
> Take a look at obj_description in include/catalog/pg_proc.h.  That
> should be a good example.

Done.

>
>>Also, I wonder how to join pg_file_stat and pg_dir_ls to get a ls -l
>>like listing. Apparently I can't do that, unless I don't code pg_dir_ls
>>as returning records too, right?
>
>
> Ideally you want:
>
>     select filename, pg_file_stat(filename)
>     from pg_dir_ls()
>
> or something like that.  However, I don't think you can have a function
> call returning multiple values in the target list, and I can't figure
> out how to pass an argument to the function if it is in the target list.
> Ideas?

I thought of
SELECT filename, len, ctime
   FROM pg_dir_ls('/etc') AS d (filename text...)
   JOIN pg_file_stat(filename) AS s(len int8, ....)
  WHERE filename like 's%'

but that wouldn't work either.

Hm, is it really worth thinking about this further. We won't contribute
a Konqueror plugin to browse a file server through a pgsql connection, I
believe...


>>Currently, the discussion circles around file functions, not logging. If
>>you think that part is clean, how about committing it separately so it
>>can be tested/used (no problem if pg_logfile_rotate() isn't available
>>right from the start). I'll supply docs RSN.
>
>
> Is pg_logfile_rotate() not working?  You mean pg_file_length().

pg_logfile_rotate() *is* working, it's just buried in a bunch of generic
file functions in adt/misc.c. My suggestion was to commit without
pg_proc.h, builtins.h and misc.c. For automatic logfile rotation, no
function is needed.
I now separated the generic file functions in a separate file
misc/adt/genfile.c. syslogger.c/h are still unchanged, appended for
convenience.

Regards,
Andreas
Index: src/backend/catalog/system_views.sql
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/catalog/system_views.sql,v
retrieving revision 1.6
diff -u -r1.6 system_views.sql
--- src/backend/catalog/system_views.sql    26 Apr 2004 15:24:41 -0000    1.6
+++ src/backend/catalog/system_views.sql    21 Jul 2004 17:26:06 -0000
@@ -273,3 +273,8 @@
     DO INSTEAD NOTHING;

 GRANT SELECT, UPDATE ON pg_settings TO PUBLIC;
+
+CREATE VIEW pg_logdir_ls AS
+    SELECT *
+    FROM pg_logdir_ls() AS A
+    (filetime timestamp, pid int4, filename text);
Index: src/backend/postmaster/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/Makefile,v
retrieving revision 1.16
diff -u -r1.16 Makefile
--- src/backend/postmaster/Makefile    19 Jul 2004 02:47:08 -0000    1.16
+++ src/backend/postmaster/Makefile    21 Jul 2004 17:26:07 -0000
@@ -12,7 +12,7 @@
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global

-OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o
+OBJS = postmaster.o bgwriter.o pgstat.o pgarch.o syslogger.o

 all: SUBSYS.o

Index: src/backend/postmaster/postmaster.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/postmaster/postmaster.c,v
retrieving revision 1.412
diff -u -r1.412 postmaster.c
--- src/backend/postmaster/postmaster.c    19 Jul 2004 02:47:08 -0000    1.412
+++ src/backend/postmaster/postmaster.c    21 Jul 2004 17:26:12 -0000
@@ -118,7 +118,7 @@
 #include "utils/ps_status.h"
 #include "bootstrap/bootstrap.h"
 #include "pgstat.h"
-
+#include "postmaster/syslogger.h"

 /*
  * List of active backends (or child processes anyway; we don't actually
@@ -201,6 +201,7 @@
             BgWriterPID = 0,
             PgArchPID = 0,
             PgStatPID = 0;
+pid_t       SysLoggerPID = 0;

 /* Startup/shutdown state */
 #define            NoShutdown        0
@@ -852,6 +853,12 @@
 #endif

     /*
+     * start logging to file
+     */
+
+    SysLoggerPID = SysLogger_Start();
+
+    /*
      * Reset whereToSendOutput from Debug (its starting state) to None.
      * This prevents ereport from sending log messages to stderr unless
      * the syslog/stderr switch permits.  We don't do this until the
@@ -1230,6 +1237,11 @@
             StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
             PgStatPID = pgstat_start();

+        /* If we have lost the system logger, try to start a new one */
+        if (SysLoggerPID == 0 &&
+            StartupPID == 0 && !FatalError && Shutdown == NoShutdown)
+            SysLoggerPID = SysLogger_Start();
+
         /*
          * Touch the socket and lock file at least every ten minutes, to ensure
          * that they are not removed by overzealous /tmp-cleaning tasks.
@@ -1770,6 +1782,9 @@
             kill(BgWriterPID, SIGHUP);
         if (PgArchPID != 0)
             kill(PgArchPID, SIGHUP);
+        if (SysLoggerPID != 0)
+            kill(SysLoggerPID, SIGHUP);
+
         /* PgStatPID does not currently need SIGHUP */
         load_hba();
         load_ident();
@@ -1835,7 +1850,6 @@
             if (PgStatPID != 0)
                 kill(PgStatPID, SIGQUIT);
             break;
-
         case SIGINT:
             /*
              * Fast Shutdown:
@@ -1902,6 +1916,7 @@
                 kill(PgStatPID, SIGQUIT);
             if (DLGetHead(BackendList))
                 SignalChildren(SIGQUIT);
+
             ExitPostmaster(0);
             break;
     }
@@ -2059,6 +2074,15 @@
             continue;
         }

+        /* was it the system logger, try to start a new one */
+        if (SysLoggerPID != 0 && pid == SysLoggerPID)
+        {
+            if (exitstatus != 0)
+                LogChildExit(LOG, gettext("system logger process"),
+                             pid, exitstatus);
+            SysLoggerPID = SysLogger_Start();
+            continue;
+        }
         /*
          * Else do standard backend child cleanup.
          */
@@ -2956,6 +2980,16 @@
         PgstatCollectorMain(argc, argv);
         proc_exit(0);
     }
+    if (strcmp(argv[1], "-forklog") == 0)
+    {
+        /* Close the postmaster's sockets */
+        ClosePostmasterPorts();
+
+        /* Do not want to attach to shared memory */
+
+        SysLoggerMain(argc, argv);
+        proc_exit(0);
+    }

     return 1;                    /* shouldn't get here */
 }
@@ -3012,7 +3046,6 @@
         if (Shutdown <= SmartShutdown)
             SignalChildren(SIGUSR1);
     }
-
     if (PgArchPID != 0 && Shutdown == NoShutdown)
     {
         if (CheckPostmasterSignal(PMSIGNAL_WAKEN_ARCHIVER))
@@ -3024,6 +3057,10 @@
             kill(PgArchPID, SIGUSR1);
         }
     }
+    if (CheckPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE) && SysLoggerPID != 0)
+    {
+        kill(SysLoggerPID, SIGUSR1);
+    }

     PG_SETMASK(&UnBlockSig);

Index: src/backend/utils/adt/Makefile
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/Makefile,v
retrieving revision 1.57
diff -u -r1.57 Makefile
--- src/backend/utils/adt/Makefile    1 Apr 2004 21:28:45 -0000    1.57
+++ src/backend/utils/adt/Makefile    21 Jul 2004 17:26:13 -0000
@@ -24,7 +24,7 @@
     tid.o timestamp.o varbit.o varchar.o varlena.o version.o xid.o \
     network.o mac.o inet_net_ntop.o inet_net_pton.o \
     ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
-    ascii.o quote.o pgstatfuncs.o encode.o
+    ascii.o quote.o pgstatfuncs.o encode.o genfile.o

 like.o: like.c like_match.c

Index: src/backend/utils/adt/misc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/adt/misc.c,v
retrieving revision 1.35
diff -u -r1.35 misc.c
--- src/backend/utils/adt/misc.c    2 Jul 2004 18:59:22 -0000    1.35
+++ src/backend/utils/adt/misc.c    21 Jul 2004 17:26:13 -0000
@@ -26,6 +26,7 @@
 #include "funcapi.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_tablespace.h"
+#include "postmaster/syslogger.h"


 /*
@@ -109,18 +110,37 @@
     PG_RETURN_INT32(pg_signal_backend(PG_GETARG_INT32(0),SIGINT));
 }

+Datum
+pg_reload_conf(PG_FUNCTION_ARGS)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser can signal the postmaster"))));
+
+    if (kill(PostmasterPid, SIGHUP))
+    {
+        ereport(WARNING,
+                (errmsg("failed to send signal to postmaster: %m")));
+
+        PG_RETURN_INT32(0);
+    }
+
+    PG_RETURN_INT32(1);
+}
+

 typedef struct
 {
     char *location;
     DIR *dirdesc;
-} ts_db_fctx;
+} directory_fctx;

 Datum pg_tablespace_databases(PG_FUNCTION_ARGS)
 {
     FuncCallContext *funcctx;
     struct dirent *de;
-    ts_db_fctx *fctx;
+    directory_fctx *fctx;

     if (SRF_IS_FIRSTCALL())
     {
@@ -130,7 +150,7 @@
         funcctx=SRF_FIRSTCALL_INIT();
         oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

-        fctx = palloc(sizeof(ts_db_fctx));
+        fctx = palloc(sizeof(directory_fctx));

         /*
          * size = path length + tablespace dirname length
@@ -164,7 +184,7 @@
     }

     funcctx=SRF_PERCALL_SETUP();
-    fctx = (ts_db_fctx*) funcctx->user_fctx;
+    fctx = (directory_fctx*) funcctx->user_fctx;

     if (!fctx->dirdesc)  /* not a tablespace */
         SRF_RETURN_DONE(funcctx);
@@ -202,3 +222,139 @@
     FreeDir(fctx->dirdesc);
     SRF_RETURN_DONE(funcctx);
 }
+
+
+/*
+ * logfile handling functions
+ */
+
+
+Datum pg_logfile_rotate(PG_FUNCTION_ARGS)
+{
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser can issue a logfile rotation command"))));
+
+    PG_RETURN_BOOL(LogFileRotate());
+}
+
+
+Datum pg_logdir_ls(PG_FUNCTION_ARGS)
+{
+    FuncCallContext *funcctx;
+    struct dirent *de;
+    directory_fctx *fctx;
+
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 (errmsg("only superuser can list the log directory"))));
+
+    if (SRF_IS_FIRSTCALL())
+    {
+        MemoryContext oldcontext;
+        TupleDesc tupdesc;
+
+        funcctx=SRF_FIRSTCALL_INIT();
+        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+        fctx = palloc(sizeof(directory_fctx));
+
+        if (is_absolute_path(Log_directory))
+            fctx->location = Log_directory;
+        else
+        {
+            fctx->location = palloc(strlen(DataDir) + strlen(Log_directory) +2);
+            sprintf(fctx->location, "%s/%s", DataDir, Log_directory);
+        }
+
+        tupdesc = CreateTemplateTupleDesc(3, false);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starttime",
+                           TIMESTAMPOID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "pid",
+                           INT4OID, -1, 0);
+        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "filename",
+                           TEXTOID, -1, 0);
+
+        funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
+
+        fctx->dirdesc = AllocateDir(fctx->location);
+
+        if (!fctx->dirdesc)
+            ereport(ERROR,
+                    (errcode_for_file_access(),
+                     errmsg("%s is not browsable: %m", fctx->location)));
+
+        funcctx->user_fctx = fctx;
+        MemoryContextSwitchTo(oldcontext);
+    }
+
+    funcctx=SRF_PERCALL_SETUP();
+    fctx = (directory_fctx*) funcctx->user_fctx;
+
+    if (!fctx->dirdesc)  /* not a readable directory  */
+        SRF_RETURN_DONE(funcctx);
+
+    while ((de = readdir(fctx->dirdesc)) != NULL)
+    {
+        char *values[3];
+        HeapTuple tuple;
+        int prefixLen=strlen(Log_filename_prefix);
+
+        char       *field[MAXDATEFIELDS];
+        char        lowstr[MAXDATELEN + 1];
+        int            dtype;
+        int            nf, ftype[MAXDATEFIELDS];
+        fsec_t        fsec;
+        int            tz = 0;
+        struct pg_tm date;
+        int         i;
+
+        /*
+         * format as created in logfile_getname():
+         *        prefix_YYYY-MM-DD_HHMMSS_PPPPP.log
+         *   prefixLen   ^
+         *                 prefixLen+17   ^
+         *                       prefixLen+23   ^
+         */
+
+        if (strlen(de->d_name) != prefixLen + 27
+            || memcmp(de->d_name, Log_filename_prefix, prefixLen)
+            || de->d_name[prefixLen + 17] != '_'
+            || strcmp(de->d_name + prefixLen + 23, ".log"))
+              continue;
+
+        values[2] = palloc(strlen(fctx->location) + strlen(de->d_name) + 2);
+        sprintf(values[2], "%s/%s", fctx->location, de->d_name);
+
+        values[0] = de->d_name + prefixLen;       /* timestamp */
+        values[0][17] = 0;
+
+        values[1] = de->d_name + prefixLen + 18;  /* pid */
+        values[1][5] = 0;
+
+        /* check if pid is purely numeric as expected */
+        for (i = 0 ; i < 5 ; i++)
+            if (!isdigit(values[0][i]))
+                continue;
+
+        /* parse and decode expected timestamp */
+        if (ParseDateTime(values[0], lowstr, field, ftype, MAXDATEFIELDS, &nf))
+            continue;
+
+        if (DecodeDateTime(field, ftype, nf, &dtype, &date, &fsec, &tz))
+            continue;
+
+        /* Seems the format fits the expected format; feed it into the tuple */
+
+
+        tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+
+        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+    }
+
+    FreeDir(fctx->dirdesc);
+    SRF_RETURN_DONE(funcctx);
+}
+
Index: src/backend/utils/error/elog.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/error/elog.c,v
retrieving revision 1.142
diff -u -r1.142 elog.c
--- src/backend/utils/error/elog.c    24 Jun 2004 21:03:13 -0000    1.142
+++ src/backend/utils/error/elog.c    21 Jul 2004 17:26:16 -0000
@@ -84,6 +84,10 @@
 static void write_eventlog(int level, const char *line);
 #endif

+/* in syslogger.c */
+extern FILE *syslogFile;
+extern FILE *realStdErr;
+extern pid_t SysLoggerPID;
 /*
  * ErrorData holds the data accumulated during any one ereport() cycle.
  * Any non-NULL pointers must point to palloc'd data in ErrorContext.
@@ -1451,10 +1455,31 @@
         write_eventlog(eventlog_level, buf.data);
     }
 #endif   /* WIN32 */
-    /* Write to stderr, if enabled */
-    if ((Log_destination & LOG_DESTINATION_STDERR) || whereToSendOutput == Debug)
+
+    /*
+     * Write to stderr. If Log_destination is file or stderr
+     * if file is target, the logger process will handle this
+     */
+    if ((Log_destination & (LOG_DESTINATION_STDERR | LOG_DESTINATION_FILE))
+        || whereToSendOutput == Debug)
     {
-        fprintf(stderr, "%s", buf.data);
+        if (SysLoggerPID == MyProcPid && realStdErr != 0)
+        {
+            /*
+             * If realStdErr is not null in the SysLogger process,
+             * there's something really wrong because stderr is probably
+             * redirected to the pipe. To avoid circular writes, we
+             * write to realStdErr which is hopefully the stderr the postmaster
+             * was started with.
+             */
+            fprintf(realStdErr, "%s", buf.data);
+        }
+        else
+            fprintf(stderr, "%s", buf.data) ;
+
+        /* syslogFile is open in SysLogger only */
+        if (syslogFile != 0)
+            fprintf(syslogFile, "%s", buf.data) ;
     }

     pfree(buf.data);
Index: src/backend/utils/misc/guc.c
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/guc.c,v
retrieving revision 1.221
diff -u -r1.221 guc.c
--- src/backend/utils/misc/guc.c    19 Jul 2004 21:39:47 -0000    1.221
+++ src/backend/utils/misc/guc.c    21 Jul 2004 17:26:26 -0000
@@ -44,6 +44,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parse_relation.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/syslogger.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -1285,6 +1286,23 @@
         BLCKSZ, BLCKSZ, BLCKSZ, NULL, NULL
     },

+    {
+      {"log_rotation_age", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur after n minutes"),
+       NULL
+      },
+      &Log_RotationAge,
+      24*60, 0, INT_MAX, NULL, NULL
+    },
+    {
+      {"log_rotation_size", PGC_SIGHUP, LOGGING_WHERE,
+       gettext_noop("Automatic logfile rotation will occur if this size is reached (in kb)"),
+       NULL
+      },
+      &Log_RotationSize,
+      10*1024, 0, INT_MAX, NULL, NULL
+    },
+
     /* End-of-list marker */
     {
         {NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL
@@ -1627,13 +1645,32 @@
     {
         {"log_destination", PGC_POSTMASTER, LOGGING_WHERE,
          gettext_noop("Sets the target for log output."),
-         gettext_noop("Valid values are combinations of stderr, syslog "
+         gettext_noop("Valid values are combinations of stderr, file, syslog "
                       "and eventlog, depending on platform."),
          GUC_LIST_INPUT
         },
         &log_destination_string,
         "stderr", assign_log_destination, NULL
     },
+    {
+        {"log_directory", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("Sets the target directory for log output."),
+         gettext_noop("May be specified as relative to the cluster directory "
+                      "or as absolute path."),
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_directory,
+        "pg_log", NULL, NULL
+    },
+    {
+        {"log_filename_prefix", PGC_SIGHUP, LOGGING_WHERE,
+         gettext_noop("prefix for logfile names created in the log_directory."),
+         NULL,
+         GUC_LIST_INPUT | GUC_REPORT
+        },
+        &Log_filename_prefix,
+        "postgresql-", NULL, NULL
+    },

 #ifdef HAVE_SYSLOG
     {
@@ -5079,6 +5116,8 @@

         if (pg_strcasecmp(tok,"stderr") == 0)
             newlogdest |= LOG_DESTINATION_STDERR;
+        else if (pg_strcasecmp(tok,"file") == 0)
+            newlogdest |= LOG_DESTINATION_FILE;
 #ifdef HAVE_SYSLOG
         else if (pg_strcasecmp(tok,"syslog") == 0)
             newlogdest |= LOG_DESTINATION_SYSLOG;
Index: src/backend/utils/misc/postgresql.conf.sample
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/backend/utils/misc/postgresql.conf.sample,v
retrieving revision 1.116
diff -u -r1.116 postgresql.conf.sample
--- src/backend/utils/misc/postgresql.conf.sample    19 Jul 2004 02:47:10 -0000    1.116
+++ src/backend/utils/misc/postgresql.conf.sample    21 Jul 2004 17:26:26 -0000
@@ -167,9 +167,17 @@

 # - Where to Log -

-#log_destination = 'stderr'    # Valid values are combinations of stderr,
-                                # syslog and eventlog, depending on
-                                # platform.
+#log_destination = 'stderr' # Valid values are combinations of stderr, file,
+                            # syslog and eventlog, depending on platform.
+#log_directory = 'pg_log'   # subdirectory where logfiles are written
+                            # if 'file' log_destination is used.
+                            # May be specified absolute or relative to PGDATA
+#log_filename_prefix = 'postgresql_' # prefix for logfile names
+#log_rotation_age = 1440    # Automatic rotation of logfiles will happen if
+                            # specified age in minutes is reached. 0 to disable.
+#log_rotation_size = 10240  # Automatic rotation of logfiles will happen if
+                            # specified size in kb is reached. 0 to disable.
+
 #syslog_facility = 'LOCAL0'
 #syslog_ident = 'postgres'

Index: src/include/catalog/pg_proc.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/catalog/pg_proc.h,v
retrieving revision 1.342
diff -u -r1.342 pg_proc.h
--- src/include/catalog/pg_proc.h    12 Jul 2004 20:23:53 -0000    1.342
+++ src/include/catalog/pg_proc.h    21 Jul 2004 17:26:41 -0000
@@ -2819,6 +2819,8 @@
 DESCR("Terminate a backend process");
 DATA(insert OID = 2172 ( pg_cancel_backend              PGNSP PGUID 12 f f t f s 1 23 "23" _null_ pg_cancel_backend -
_null_)); 
 DESCR("Cancel running query on a backend process");
+DATA(insert OID = 2173 ( pg_reload_conf                PGNSP PGUID 12 f f t f s 1 23 "" _null_ pg_reload_conf - _null_
));
+DESCR("Reload postgresql.conf");

 DATA(insert OID = 1946 (  encode                        PGNSP PGUID 12 f f t f i 2 25 "17 25" _null_  binary_encode -
_null_)); 
 DESCR("Convert bytea value into some ascii-only text string");
@@ -3607,6 +3609,30 @@
 DATA(insert OID = 2556 ( pg_tablespace_databases    PGNSP PGUID 12 f f t t s 1 26 "26" _null_ pg_tablespace_databases
-_null_)); 
 DESCR("returns database oids in a tablespace");

+/* logfile functions */
+DATA(insert OID = 2244( pg_logfile_rotate               PGNSP PGUID 12 f f t f v 0 16 "" _null_ pg_logfile_rotate -
_null_)); 
+DESCR("rotate log file");
+DATA(insert OID = 2245( pg_logdir_ls                   PGNSP PGUID 12 f f t t v 0 2249 "" _null_ pg_logdir_ls - _null_
));
+DESCR("list all available log files");
+
+/* generic file functions */
+DATA(insert OID = 2557( pg_file_stat                   PGNSP PGUID 12 f f t f v 1 2249 "25" _null_ pg_file_stat -
_null_)); 
+DESCR("stat properties of generic file");
+DATA(insert OID = 2558 ( pg_file_length                   PGNSP PGUID 14 f f t f v 1 20 "25" _null_ "SELECT len FROM
pg_file_stat($1)AS s(len int8, c timestamp, a timestamp, m timestamp, d bool)" - _null_ )); 
+DESCR("length of a generic file");
+DATA(insert OID = 2559( pg_file_read                   PGNSP PGUID 12 f f t f v 3 25 "25 20 20" _null_ pg_file_read -
_null_)); 
+DESCR("read contents of generic file");
+DATA(insert OID = 2560( pg_file_write                   PGNSP PGUID 12 f f t f v 3 20 "25 25 16" _null_ pg_file_write
-_null_ )); 
+DESCR("write generic file");
+DATA(insert OID = 2561( pg_file_rename                PGNSP PGUID 12 f f t f v 2 16 "25 25 25" _null_ pg_file_rename -
_null_)); 
+DESCR("rename generic file");
+DATA(insert OID = 2562( pg_file_rename                PGNSP PGUID 14 f f t f v 2 16 "25 25" _null_ "SELECT
pg_file_rename($1,$2,NULL)"- _null_ )); 
+DESCR("rename generic file");
+DATA(insert OID = 2563( pg_file_unlink                   PGNSP PGUID 12 f f t f v 1 16 "25" _null_ pg_file_unlink -
_null_)); 
+DESCR("remove generic file");
+
+DATA(insert OID = 2564( pg_dir_ls                       PGNSP PGUID 12 f f t t v 2 25 "25 16" _null_ pg_dir_ls -
_null_)); 
+DESCR("list generic directory");

 /*
  * Symbolic values for provolatile column: these indicate whether the result
Index: src/include/storage/pmsignal.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/storage/pmsignal.h,v
retrieving revision 1.9
diff -u -r1.9 pmsignal.h
--- src/include/storage/pmsignal.h    19 Jul 2004 02:47:15 -0000    1.9
+++ src/include/storage/pmsignal.h    21 Jul 2004 17:26:42 -0000
@@ -25,7 +25,7 @@
     PMSIGNAL_PASSWORD_CHANGE,    /* pg_pwd file has changed */
     PMSIGNAL_WAKEN_CHILDREN,    /* send a SIGUSR1 signal to all backends */
     PMSIGNAL_WAKEN_ARCHIVER,    /* send a NOTIFY signal to xlog archiver */
-
+    PMSIGNAL_ROTATE_LOGFILE,    /* send SIGUSR1 to syslogger to rotate logfile */
     NUM_PMSIGNALS                /* Must be last value of enum! */
 } PMSignalReason;

Index: src/include/utils/builtins.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/builtins.h,v
retrieving revision 1.246
diff -u -r1.246 builtins.h
--- src/include/utils/builtins.h    12 Jul 2004 20:23:59 -0000    1.246
+++ src/include/utils/builtins.h    21 Jul 2004 17:26:44 -0000
@@ -362,8 +362,20 @@
 extern Datum current_database(PG_FUNCTION_ARGS);
 extern Datum pg_terminate_backend(PG_FUNCTION_ARGS);
 extern Datum pg_cancel_backend(PG_FUNCTION_ARGS);
+extern Datum pg_reload_conf(PG_FUNCTION_ARGS);
 extern Datum pg_tablespace_databases(PG_FUNCTION_ARGS);

+extern Datum pg_logfile_rotate(PG_FUNCTION_ARGS);
+extern Datum pg_logdir_ls(PG_FUNCTION_ARGS);
+
+extern Datum pg_file_stat(PG_FUNCTION_ARGS);
+extern Datum pg_file_read(PG_FUNCTION_ARGS);
+extern Datum pg_file_write(PG_FUNCTION_ARGS);
+extern Datum pg_file_rename(PG_FUNCTION_ARGS);
+extern Datum pg_file_unlink(PG_FUNCTION_ARGS);
+
+extern Datum pg_dir_ls(PG_FUNCTION_ARGS);
+
 /* not_in.c */
 extern Datum int4notin(PG_FUNCTION_ARGS);
 extern Datum oidnotin(PG_FUNCTION_ARGS);
Index: src/include/utils/elog.h
===================================================================
RCS file: /projects/cvsroot/pgsql-server/src/include/utils/elog.h,v
retrieving revision 1.70
diff -u -r1.70 elog.h
--- src/include/utils/elog.h    6 Jul 2004 19:51:59 -0000    1.70
+++ src/include/utils/elog.h    21 Jul 2004 17:26:44 -0000
@@ -185,10 +185,10 @@
 #define LOG_DESTINATION_STDERR   1
 #define LOG_DESTINATION_SYSLOG   2
 #define LOG_DESTINATION_EVENTLOG 4
+#define LOG_DESTINATION_FILE     8

 /* Other exported functions */
 extern void DebugFileOpen(void);
-
 /*
  * Write errors to stderr (or by equal means when stderr is
  * not available). Used before ereport/elog can be used
/*-------------------------------------------------------------------------
 *
 * genfile.c
 *
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * IDENTIFICATION
 *      $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/file.h>
#include <unistd.h>
#include <dirent.h>

#include "miscadmin.h"
#include "storage/fd.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "funcapi.h"



typedef struct
{
    char *location;
    DIR *dirdesc;
} directory_fctx;

/*-----------------------
 * some helper functions
 */

/*
 * Return an absolute path. Argument may be absolute or
 * relative to the DataDir.
 */
static char *absClusterPath(text *arg)
{
    char *filename;
    int len=VARSIZE(arg) - VARHDRSZ;

    filename = palloc(len+1);
    memcpy(filename, VARDATA(arg), len);
    filename[len] = 0;

    if (is_absolute_path(filename))
        return filename;
    else
    {
        char *absname = palloc(strlen(DataDir)+len+2);
        sprintf(absname, "%s/%s", DataDir, filename);
        pfree(filename);
        return absname;
    }
}


/*
 * check for superuser, bark if not.
 */
static void
requireSuperuser(void)
{
    if (!superuser())
        ereport(ERROR,
                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                 (errmsg("only superuser may access generic file functions"))));
}



/* ------------------------------------
 * generic file handling functions
 */


Datum pg_file_stat(PG_FUNCTION_ARGS)
{
    AttInMetadata *attinmeta = NULL;
    char *    filename = absClusterPath(PG_GETARG_TEXT_P(0));
    struct stat fst;
    int64 length;
    char lenbuf[30];
    char cbuf[30], abuf[30], mbuf[30], dbuf[]="f";
    char *values[5] =
       { lenbuf, cbuf, abuf, mbuf, dbuf };

    pg_time_t timestamp;
    HeapTuple tuple;

    if (attinmeta == NULL)
    {
        TupleDesc tupdesc = CreateTemplateTupleDesc(5, false);

        TupleDescInitEntry(tupdesc, (AttrNumber) 1, "length",
                           INT8OID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber) 2, "ctime",
                           TIMESTAMPOID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber) 3, "atime",
                           TIMESTAMPOID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber) 4, "mtime",
                           TIMESTAMPOID, -1, 0);
        TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isdir",
                           BOOLOID, -1, 0);

        attinmeta = TupleDescGetAttInMetadata(tupdesc);
    }

    if (stat(filename, &fst) < 0)
    {
        ereport(WARNING,
                (errcode_for_file_access(),
                 errmsg("could not stat file %s: %m", filename)));

        strcpy(lenbuf, "-1");
        strcpy(cbuf, "NULL");
        strcpy(abuf, "NULL");
        strcpy(mbuf, "NULL");
    }
    else
    {
        length = fst.st_size;
        snprintf(lenbuf, 30, INT64_FORMAT, length);

        timestamp = fst.st_ctime;
        pg_strftime(cbuf, 30, "%F %T", pg_localtime(×tamp));

        timestamp = fst.st_atime;
        pg_strftime(abuf, 30, "%F %T", pg_localtime(×tamp));

        timestamp = fst.st_mtime;
        pg_strftime(mbuf, 30, "%F %T", pg_localtime(×tamp));

        if (fst.st_mode & S_IFDIR)
          dbuf[0] = 't';
    }
    tuple = BuildTupleFromCStrings(attinmeta, values);

    PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}


Datum pg_file_read(PG_FUNCTION_ARGS)
{
    size_t size;
    char *buf=0;
    size_t nbytes;
    int64 pos;
    FILE *f;
    char *filename;

    requireSuperuser();

    filename = absClusterPath(PG_GETARG_TEXT_P(0));
    pos = PG_GETARG_INT64(1);
    size = PG_GETARG_INT64(2);

    f = fopen(filename, "r");
    if (!f)
    {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not open file %s for reading: %m", filename)));
        PG_RETURN_NULL();
    }

    if (pos >= 0)
        fseek(f, pos, SEEK_SET);
    else
        fseek(f, pos, SEEK_END);


    buf = palloc(size + VARHDRSZ);

    nbytes = fread(VARDATA(buf), 1, size, f);
    if (nbytes < 0)
    {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not read file %s: %m", filename)));
        PG_RETURN_NULL();
    }
    VARATT_SIZEP(buf) = nbytes + VARHDRSZ;
    fclose(f);

    PG_RETURN_TEXT_P(buf);
}


Datum pg_file_write(PG_FUNCTION_ARGS)
{
    FILE *f;
    char *filename;
    text *data;
    int64 count = 0;

    requireSuperuser();

    filename = absClusterPath(PG_GETARG_TEXT_P(0));
    data = PG_GETARG_TEXT_P(1);

    if (PG_ARGISNULL(2) || !PG_GETARG_BOOL(2))
    {
        struct stat fst;
        if (stat(filename, &fst) >= 0)
            ereport(ERROR,
                    (ERRCODE_DUPLICATE_FILE,
                     errmsg("file %s exists", filename)));

        f = fopen(filename, "w");
    }
    else
        f = fopen(filename, "a");

    if (!f)
    {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could open file %s for writing: %m", filename)));
    }

    if (VARSIZE(data) != 0)
    {
        count = fwrite(VARDATA(data), 1, VARSIZE(data) - VARHDRSZ, f);

        if (count != VARSIZE(data))
            ereport(ERROR,
                    (errcode_for_file_access(),
                     errmsg("error writing file %s: %m", filename)));
    }
    fclose(f);

    PG_RETURN_INT64(count);
}


Datum pg_file_rename(PG_FUNCTION_ARGS)
{
    char *fn1, *fn2, *fn3;
    int rc;

    requireSuperuser();

    fn1=absClusterPath(PG_GETARG_TEXT_P(0));
    fn2=absClusterPath(PG_GETARG_TEXT_P(1));
    if (PG_ARGISNULL(2))
        fn3=0;
    else
        fn3=absClusterPath(PG_GETARG_TEXT_P(2));

    struct stat fst;
    if (stat(fn1, &fst) < 0)
    {
        ereport(WARNING,
                (errcode_for_file_access(),
                 errmsg("could not stat file %s: %m", fn1)));

        PG_RETURN_BOOL(false);
    }

    if (fn3 && stat(fn2, &fst) < 0)
    {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not stat file %s: %m", fn2)));

        PG_RETURN_BOOL(false);
    }


    rc = stat(fn3 ? fn3 : fn2, &fst);
    if (rc >= 0 || errno != ENOENT)
    {
        ereport(ERROR,
                (ERRCODE_DUPLICATE_FILE,
                 errmsg("cannot rename: target file %s exists", fn3 ? fn3 : fn2)));
    }

    if (fn3)
    {
        if (rename(fn2, fn3) != 0)
        {
            ereport(ERROR,
                    (errcode_for_file_access(),
                     errmsg("could not rename %s to %s: %m", fn2, fn3)));
        }
        if (rename(fn1, fn2) != 0)
        {
            ereport(WARNING,
                    (errcode_for_file_access(),
                     errmsg("could not rename %s to %s: %m", fn1, fn2)));

            if (rename(fn3, fn2) != 0)
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         errmsg("could not rename %s back to %s: %m", fn3, fn2)));
            }
            else
            {
                ereport(ERROR,
                        (ERRCODE_UNDEFINED_FILE,
                         errmsg("renaming %s to %s was reverted", fn2, fn3)));

            }
        }
    }
    if (rename(fn1, fn2) != 0)
    {
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not rename %s to %s: %m", fn1, fn2)));
    }

    PG_RETURN_BOOL(true);
}


Datum pg_file_unlink(PG_FUNCTION_ARGS)
{
    char *filename;

    requireSuperuser();

    filename = absClusterPath(PG_GETARG_TEXT_P(0));

    if (unlink(filename) < 0)
    {
        ereport(WARNING,
                (errcode_for_file_access(),
                 errmsg("could not unlink file %s", filename)));

        PG_RETURN_BOOL(false);
    }
    PG_RETURN_BOOL(true);
}


Datum pg_dir_ls(PG_FUNCTION_ARGS)
{
    FuncCallContext *funcctx;
    struct dirent *de;
    directory_fctx *fctx;

    requireSuperuser();

    if (SRF_IS_FIRSTCALL())
    {
        MemoryContext oldcontext;

        funcctx=SRF_FIRSTCALL_INIT();
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);

        fctx = palloc(sizeof(directory_fctx));
        fctx->location = absClusterPath(PG_GETARG_TEXT_P(0));

        fctx->dirdesc = AllocateDir(fctx->location);

        if (!fctx->dirdesc)
            ereport(ERROR,
                    (errcode_for_file_access(),
                     errmsg("%s is not browsable: %m", fctx->location)));

        if (PG_ARGISNULL(1) || !PG_GETARG_BOOL(1))
        {
            pfree(fctx->location);
            fctx->location = 0;
        }
        funcctx->user_fctx = fctx;
        MemoryContextSwitchTo(oldcontext);
    }

    funcctx=SRF_PERCALL_SETUP();
    fctx = (directory_fctx*) funcctx->user_fctx;

    if (!fctx->dirdesc)  /* not a readable directory  */
        SRF_RETURN_DONE(funcctx);

    while ((de = readdir(fctx->dirdesc)) != NULL)
    {
        char *name;
        text *result;
        int len;
        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
            continue;
        if (fctx->location)
        {
            char *path=palloc(strlen(fctx->location) + strlen(de->d_name) +2);
            sprintf(path, "%s/%s", fctx->location, de->d_name);

            name = path;
        }
        else
            name = de->d_name;


        len = strlen(name);
        result = palloc(len + VARHDRSZ);
        VARATT_SIZEP(result) = len + VARHDRSZ;
        memcpy(VARDATA(result), name, len);

        SRF_RETURN_NEXT(funcctx, PointerGetDatum(result));
    }

    FreeDir(fctx->dirdesc);
    SRF_RETURN_DONE(funcctx);
}


/*-------------------------------------------------------------------------
 *
 * syslogger.c
 *
 * The system logger (syslogger) is new in Postgres 7.5. It catches all
 * stderr output from backends, the postmaster and subprocesses by
 * redirecting to a pipe, and writes it to a logfile and stderr if
 * configured.
 * It's possible to have size and age limits for the logfile configured
 * in postgresql.conf. If these limits are reached or passed, the
 * current logfile is closed and a new one is created (rotated).
 * The logfiles are stored in a subdirectory (configurable in
 * postgresql.conf), using an internal naming scheme that mangles
 * creation time and current postmaster pid.
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * Copyright (c) 2004, PostgreSQL Global Development Group
 *
 *
 * IDENTIFICATION
 *      $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <signal.h>
#include <time.h>
#include <unistd.h>
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#include "storage/pmsignal.h"
#include "storage/pg_shmem.h"
#include "storage/ipc.h"
#include "postmaster/syslogger.h"
#include "utils/ps_status.h"
#include "utils/guc.h"

/*
 * GUC parameters
 */
int            Log_RotationAge = 24*60;
int            Log_RotationSize  = 10*1024;
char *      Log_directory = "pg_log";
char *      Log_filename_prefix = "postgresql_";


/*
 * Flags set by interrupt handlers for later service in the main loop.
 */
static volatile sig_atomic_t got_SIGHUP = false;
static volatile sig_atomic_t rotation_requested = false;

static pg_time_t    last_rotation_time = 0;


static void sigHupHandler(SIGNAL_ARGS);
static void rotationHandler(SIGNAL_ARGS);
#ifdef EXEC_BACKEND
static pid_t syslogger_forkexec();
#endif

static char* logfile_getname(pg_time_t timestamp);
static bool logfile_rotate(void);


FILE *realStdErr = NULL;
FILE *syslogFile = NULL;
int syslogPipe[2] = {0, 0};


/*
 * Main entry point for syslogger process
 * argc/argv parameters are valid only in EXEC_BACKEND case.
 */
void
SysLoggerMain(int argc, char *argv[])
{
    IsUnderPostmaster = true;
    MyProcPid = getpid();
    init_ps_display("system logger process", "", "");
    set_ps_display("");

#ifdef EXEC_BACKEND

    Assert(argc == 6);

    argv += 3;
    StrNCpy(postgres_exec_path,    argv++, MAXPGPATH);
    syslogPipe[0] = atoi(argv++);
    syslogPipe[1] = atoi(argv);

#endif

    /*
     * Properly accept or ignore signals the postmaster might send us
     *
     * Note: we ignore all termination signals, and wait for the postmaster
     * to die to catch as much pipe output as possible.
     */

    pqsignal(SIGHUP, sigHupHandler);    /* set flag to read config file */
    pqsignal(SIGINT,  SIG_IGN);
    pqsignal(SIGTERM, SIG_IGN);
    pqsignal(SIGQUIT, SIG_IGN);
    pqsignal(SIGALRM, SIG_IGN);
    pqsignal(SIGPIPE, SIG_IGN);
    pqsignal(SIGUSR1, rotationHandler);  /* request log rotation */
    pqsignal(SIGUSR2, SIG_IGN);

    /*
     * Reset some signals that are accepted by postmaster but not here
     */
    pqsignal(SIGCHLD, SIG_DFL);
    pqsignal(SIGTTIN, SIG_DFL);
    pqsignal(SIGTTOU, SIG_DFL);
    pqsignal(SIGCONT, SIG_DFL);
    pqsignal(SIGWINCH, SIG_DFL);

    PG_SETMASK(&UnBlockSig);

    /*
     * if we restarted, our stderr is redirected.
     * Direct it back to system stderr.
     */
    if (realStdErr != NULL)
    {
        if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
        {
            char *errstr = strerror(errno);
            /*
             * Now we have a real problem: we can't redirect to stderr,
             * and can't ereport it correctly (it would go into our queue
             * which will never be read
             * We're writing everywhere, hoping to leave at least some
             * hint of what happened.
             */

            fprintf(realStdErr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);
            fprintf(stderr, "PANIC: Syslogger couldn't redirect its stderr to the saved stderr: %s\n", errstr);

            ereport(PANIC,
                    (errcode_for_file_access(),
                     (errmsg("Syslogger couldn't redirect its stderr to the saved stderr: %s", errstr))));
            exit(1);
        }

        realStdErr = NULL;
    }

    /* we'll never write that pipe */
    close(syslogPipe[1]);
    syslogPipe[1] = 0;
    /* remember age of initial logfile */
    last_rotation_time = time(NULL);

    /* main worker loop */
    for (;;)
    {
        pg_time_t    now;
        int            elapsed_secs;
        char        logbuffer[1024];
        char        bytesRead;
        fd_set        rfds;
        struct timeval timeout;
        int         rc;

        if (got_SIGHUP)
        {
            char *olddir=pstrdup(Log_directory);
            got_SIGHUP = false;

            ProcessConfigFile(PGC_SIGHUP);

            /*
             * check if the log directory changed
             * in postgresql.conf. If so, we rotate to make sure
             * we're writing the logfiles where the backends
             * expect us to do so.
             */
            if (strcmp(Log_directory, olddir))
              rotation_requested = true;

            pfree(olddir);
        }

        if (!rotation_requested && last_rotation_time != 0 && Log_RotationAge > 0)
        {
            /*
             * Do an unforced rotation if too much time has elapsed
             * since the last one.
             */
            now = time(NULL);
            elapsed_secs = now - last_rotation_time;
            if (elapsed_secs >= Log_RotationAge * 60)
                rotation_requested = true;
        }

        if (!rotation_requested && Log_RotationSize > 0)
        {
            /*
             * Do an unforced rotation if file is too big
             */
            if (ftell(syslogFile) >= Log_RotationSize * 1024)
                rotation_requested = true;
        }


        if (rotation_requested)
        {
            if (!logfile_rotate())
            {
                ereport(ERROR,
                        (errcode_for_file_access(),
                         (errmsg("logfile rotation failed, disabling auto rotation (SIGHUP to reenable): %m"))));

                Log_RotationAge = 0;
                Log_RotationSize = 0;
            }
            rotation_requested = false;
        }

        FD_ZERO(&rfds);
        FD_SET(syslogPipe[0], &rfds);
        timeout.tv_sec=1;
        timeout.tv_usec=0;

        /*
         * Check if data is present
         */
        rc = select(syslogPipe[0]+1, &rfds, NULL, NULL, &timeout);

        PG_SETMASK(&UnBlockSig);
        if (rc > 0 && FD_ISSET(syslogPipe[0], &rfds))
        {
            bytesRead = piperead(syslogPipe[0],
                                 logbuffer, sizeof(logbuffer));

            if (bytesRead > 0)
            {
                if (fwrite(logbuffer, 1, bytesRead, syslogFile) < 1)
                {
                    ereport(COMMERROR,
                            (errcode_for_file_access(),
                             errmsg("fwrite to logfile failed in system logger: %m")));
                    exit(1);
                }
                fflush(syslogFile);

                if (Log_destination & LOG_DESTINATION_STDERR)
                {
                    fwrite(logbuffer, 1, bytesRead, stderr);
                    fflush(stderr);
                }
                continue;
            }
            else if (bytesRead < 0 && errno != EINTR)
            {
                ereport(COMMERROR,
                        (errcode_for_socket_access(),
                         errmsg("could not read from system logger pipe: %m")));
                exit(1);
            }
        }

        if (rc < 0 && errno != EINTR)
        {
            ereport(COMMERROR,
                    (errcode_for_socket_access(),
                     errmsg("select() failed in system logger: %m")));
            exit(1);
        }

        /*
         * If postmaster died, there's nothing to log any more.
         * We check this only after pipe timeouts to receive as much as possible
         * from the pipe.
         */
        if (!PostmasterIsAlive(true))
        {
            if (syslogFile)
                fclose(syslogFile);
            exit(1);
        }

    }
}


int
SysLogger_Start(void)
{
    pid_t sysloggerPid;
    pg_time_t now;
    char *filename;

    if (!(Log_destination & LOG_DESTINATION_FILE))
        return 0;

    /* create the pipe which will receive stderr output */
    if (!syslogPipe[0])
    {
        if (pgpipe(syslogPipe) < 0)
            ereport(FATAL,
                    (errcode_for_file_access(),
                     (errmsg("pipe for syslogging not created: %m"))));

        if (!set_noblock(syslogPipe[1]))
        {
            ereport(FATAL,
                    (errcode_for_socket_access(),
                     errmsg("could not set syslogging pipe to nonblocking mode: %m")));
        }
    }

    now = time(NULL);

    /*
     * The initial logfile is created right in the postmaster,
     * to insure that the logger process has a writable file.
     */
    filename = logfile_getname(now);

    /*
     * The file is opened for appending, in case the syslogger
     * is restarted right after a rotation.
     */
    syslogFile = fopen(filename, "a+");

    if (!syslogFile)
    {
        /*
         * if we can't open the syslog file for the syslogger process,
         * we try to redirect stderr back to have some logging.
         */
        ereport(WARNING,
                (errcode_for_file_access(),
                 (errmsg("error opening syslog file %s: %m", filename))));

        if (realStdErr != NULL)
        {
            if (dup2(fileno(realStdErr), fileno(stderr)) < 0)
                ereport(FATAL,
                        (errcode_for_file_access(),
                         (errmsg("error redirecting stderr to default: %m"))));

            ereport(FATAL,
                    (errmsg("logfile output corrupted")));
        }

    }
    pfree(filename);

    fflush(stdout);
    fflush(stderr);

#ifdef __BEOS__
    /* Specific beos actions before backend startup */
    beos_before_backend_startup();
#endif

#ifdef EXEC_BACKEND
    switch ((sysloggerPid = syslogger_forkexec()))
#else
    switch ((sysloggerPid = fork()))
#endif
    {
        case -1:
#ifdef __BEOS__
            /* Specific beos actions */
            beos_backend_startup_failed();
#endif
            ereport(LOG,
                    (errmsg("could not fork system logger: %m")));
            return 0;

#ifndef EXEC_BACKEND
        case 0:
            /* in postmaster child ... */
#ifdef __BEOS__
            /* Specific beos actions after backend startup */
            beos_backend_startup();
#endif
            /* Close the postmaster's sockets */
            ClosePostmasterPorts();

            /* Drop our connection to postmaster's shared memory, as well */
            PGSharedMemoryDetach();

            /* do the work */
            SysLoggerMain(0, NULL);
            break;
#endif

        default:
            /* now we redirect stderr, if not done already */
            if (realStdErr == NULL)
            {
                int dh= dup(fileno(stderr));

                if (dh < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("stderr duplication failed: %m"))));

                realStdErr = fdopen(dh, "a");
                if (realStdErr == NULL)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                             (errmsg("realstderr reopen failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stdout)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stdout pipe redirection failed: %m"))));

                if (dup2(syslogPipe[1], fileno(stderr)) < 0)
                    ereport(FATAL,
                            (errcode_for_file_access(),
                          (errmsg("stderr pipe redirection failed: %m"))));
            }

            /* postmaster will never write the file; close it */
            fclose(syslogFile);
            syslogFile = NULL;
            return (int) sysloggerPid;
    }

    /* we should never reach here */
    return 0;
}


#ifdef EXEC_BACKEND
static pid_t
syslogger_forkexec()
{
    char *av[10];
    int ac = 0, bufc = 0, i;
    char numbuf[2][32];

    av[ac++] = "postgres";
    av[ac++] = "-forklog";
    av[ac++] = NULL;            /* filled in by postmaster_forkexec */

    /* postgres_exec_path is not passed by write_backend_variables */
    av[ac++] = postgres_exec_path;

    /* Pipe file ids (those not passed by write_backend_variables) */
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[0]);
    snprintf(numbuf[bufc++],32,"%d",syslogPipe[1]);

    /* Add to the arg list */
    Assert(bufc <= lengthof(pgstatBuf));
    for (i = 0; i < bufc; i++)
        av[ac++] = numbuf[i];

    av[ac] = NULL;
    Assert(ac < lengthof(av));

    return postmaster_forkexec(ac, av);
}
#endif


/* --------------------------------
 *        logfile routines
 * --------------------------------
 */


/*
 * perform rotation
 */
bool
logfile_rotate(void)
{
    char *filename;
    pg_time_t now;
    FILE *fh;

    now = time(NULL);
    filename = logfile_getname(now);

    fh = fopen(filename, "a+");
    if (!fh)
    {
        /*
         * if opening the new file fails, the caller is responsible
         * for taking consequences. */

        pfree(filename);
        return false;
    }

    fclose(syslogFile);
    syslogFile = fh;

    last_rotation_time = now;

    /* official opening of the new logfile */
    ereport(NOTICE,
            (errcode(ERRCODE_WARNING),
             errmsg("Opened new log file %s", filename)));

    pfree(filename);
    return true;
}



/*
 * creates logfile name using timestamp information
 */

#define TIMESTAMPPATTERN "%Y-%m-%d_%H%M%S"

static char*
logfile_getname(pg_time_t timestamp)
{
    char *filetemplate;
    char *filename;


    if (is_absolute_path(Log_directory))
    {
      filetemplate = palloc(strlen(Log_directory)
                            + strlen(Log_filename_prefix)
                            + sizeof(TIMESTAMPPATTERN)+10 +2);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s%s_%05d.log",
                    Log_directory, Log_filename_prefix,
                    TIMESTAMPPATTERN, PostmasterPid);
    }
    else
    {
        filetemplate = palloc(strlen(DataDir) + strlen(Log_directory)
                              + strlen(Log_filename_prefix)
                              + sizeof(TIMESTAMPPATTERN) +10 +3);
        if (filetemplate)
            sprintf(filetemplate, "%s/%s/%s%s_%05d.log",
                    DataDir, Log_directory, Log_filename_prefix,
                    TIMESTAMPPATTERN, PostmasterPid);
    }
    filename = palloc(MAXPGPATH);

    if (!filename || !filetemplate)
        ereport(FATAL,
                (errcode(ERRCODE_OUT_OF_MEMORY),
                 errmsg("Out of memory")));

    pg_strftime(filename, MAXPGPATH, filetemplate, pg_localtime(×tamp));

    pfree(filetemplate);

    return filename;
}

/* --------------------------------
 *        API helper routines
 * --------------------------------
 */

/*
 * Rotate log file
 */
bool
LogFileRotate(void)
{
    if (!(Log_destination & LOG_DESTINATION_FILE))
    {
        ereport(NOTICE,
                (errcode(ERRCODE_WARNING),
                 errmsg("no logfile configured; rotation not supported")));
        return false;
    }

    SendPostmasterSignal(PMSIGNAL_ROTATE_LOGFILE);

    return true;
}

/* --------------------------------
 *        signal handler routines
 * --------------------------------
 */

/* SIGHUP: set flag to reload config file */
static void
sigHupHandler(SIGNAL_ARGS)
{
    got_SIGHUP = true;
}

/* SIGUSR1: set flag to rotate logfile */
static void
rotationHandler(SIGNAL_ARGS)
{
    rotation_requested = true;
}
/*-------------------------------------------------------------------------
 *
 * syslogger.h
 *      Exports from postmaster/syslogger.c.
 *
 * Portions Copyright (c) 2004, PostgreSQL Global Development Group
 *
 * $PostgreSQL: $
 *
 *-------------------------------------------------------------------------
 */
#ifndef _SYSLOGGER_H
#define _SYSLOGGER_H

#include "pgtime.h"

/* GUC options */
extern int        Log_RotationAge;
extern int        Log_RotationSize;
extern char *   Log_directory;
extern char *   Log_filename_prefix;


int SysLogger_Start(void);
void SysLoggerMain(int argc, char *argv[]);

extern bool LogFileRotate(void);

#endif   /* _SYSLOGGER_H */

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas Pflug wrote:
> > or something like that.  However, I don't think you can have a function
> > call returning multiple values in the target list, and I can't figure
> > out how to pass an argument to the function if it is in the target list.
> > Ideas?
>
> I thought of
> SELECT filename, len, ctime
>    FROM pg_dir_ls('/etc') AS d (filename text...)
>    JOIN pg_file_stat(filename) AS s(len int8, ....)
>   WHERE filename like 's%'
>
> but that wouldn't work either.
>
> Hm, is it really worth thinking about this further. We won't contribute
> a Konqueror plugin to browse a file server through a pgsql connection, I
> believe...

Here is what you can do:

    SELECT     filename,
        (SELECT file_len   FROM pg_file_stat(filename)),
        (SELECT file_ctime FROM pg_file_stat(filename)),
        (SELECT file_mtime FROM pg_file_stat(filename)),
        (SELECT file_atime FROM pg_file_stat(filename))
    FROM pg_dir_ls('/etc') AS d (filename text...)
    WHERE filename like 's%'

I don't think you can have a subquery in the target list that returns
more the one column so you have to do multiple SELECT calls.

> >>Currently, the discussion circles around file functions, not logging. If
> >>you think that part is clean, how about committing it separately so it
> >>can be tested/used (no problem if pg_logfile_rotate() isn't available
> >>right from the start). I'll supply docs RSN.
> >
> >
> > Is pg_logfile_rotate() not working?  You mean pg_file_length().
>
> pg_logfile_rotate() *is* working, it's just buried in a bunch of generic
> file functions in adt/misc.c. My suggestion was to commit without
> pg_proc.h, builtins.h and misc.c. For automatic logfile rotation, no
> function is needed.
> I now separated the generic file functions in a separate file
> misc/adt/genfile.c. syslogger.c/h are still unchanged, appended for
> convenience.

No.  We will have enough time for testing.  Let's get this right first.
If we leave it for later we will forget.

Are we done?  Seems pg_file_stat() works fine.  Do we need other
adjustments?

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:

>
>
> Here is what you can do:
>
>     SELECT     filename,
>         (SELECT file_len   FROM pg_file_stat(filename)),
>         (SELECT file_ctime FROM pg_file_stat(filename)),
>         (SELECT file_mtime FROM pg_file_stat(filename)),
>         (SELECT file_atime FROM pg_file_stat(filename))
>     FROM pg_dir_ls('/etc') AS d (filename text...)
>     WHERE filename like 's%'

Not really satisfying (pg_file_stat is volatile) but subselects give the
desired result.

>
> Are we done?  Seems pg_file_stat() works fine.  Do we need other
> adjustments?

The only single spot where performance could be improved is in
pg_file_stat, where attinmeta is created again and again; this may be
cached in a static memory context instead.

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Bruce Momjian wrote:
>
> Are we done?  Seems pg_file_stat() works fine.  Do we need other
> adjustments?

Here are the documentation changes.

Regards,
Andreas




Index: catalogs.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/catalogs.sgml,v
retrieving revision 2.89
diff -u -r2.89 catalogs.sgml
--- catalogs.sgml    4 Jul 2004 23:34:23 -0000    2.89
+++ catalogs.sgml    23 Jul 2004 12:16:47 -0000
@@ -3855,6 +3855,11 @@
      </row>

      <row>
+      <entry><link linkend="view-pg-logdir-ls"><structname>pg_logdir_ls</structname></link></entry>
+      <entry>log files in log directory</entry>
+     </row>
+
+     <row>
       <entry><link linkend="view-pg-rules"><structname>pg_rules</structname></link></entry>
       <entry>rules</entry>
      </row>
@@ -3943,6 +3948,50 @@
   </table>

  </sect1>
+ <sect1 id="view-pg-logdir-ls">
+  <title><structname>pg_logdir_ls</structname></title>
+
+  <indexterm zone="view-pg-logdir-ls">
+   <primary>pg_logdir_ls</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_logdir_ls</structname> provides access to
+    log files stored in the log directory.
+  </para>
+
+  <table>
+   <title><structname>pg_logdir_ls</> Columns</title>
+
+   <tgroup cols=3>
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>filetime</structfield></entry>
+      <entry><type>timestamp</type></entry>
+      <entry>timestamp of log file creation</entry>
+     </row>
+     <row>
+      <entry><structfield>pid</structfield></entry>
+      <entry><type>int4</type></entry>
+      <entry>process id of postmaster that created the logfile</entry>
+     </row>
+     <row>
+      <entry><structfield>filename</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>full pathname of log file</entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+ </sect1>

  <sect1 id="view-pg-locks">
   <title><structname>pg_locks</structname></title>
Index: func.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/func.sgml,v
retrieving revision 1.214
diff -u -r1.214 func.sgml
--- func.sgml    12 Jul 2004 20:23:47 -0000    1.214
+++ func.sgml    23 Jul 2004 12:17:06 -0000
@@ -2658,8 +2658,10 @@
      function fails and returns null.  To indicate the part of the
      pattern that should be returned on success, the pattern must contain
      two occurrences of the escape character followed by a double quote
-     (<literal>"</>).  The text matching the portion of the pattern
+     (<literal>"</>). The text matching the portion of the pattern
      between these markers is returned.
+     <!-- This comment is to stop misbehaving sgml highlighting from
+     previous " double qoutes -->
     </para>

    <para>
@@ -7455,6 +7457,41 @@
    </para>

    <indexterm zone="functions-misc">
+   <primary>pg_logdir_ls</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+   <primary>pg_logfile_rotate</primary>
+   </indexterm>
+   <para>
+    The functions shown in <xref linkend="functions-misc-logfile">
+    deal with the server log file if configured with log_destination
+    <quote>file</quote>.
+   </para>
+
+   <table id="functions-misc-logfile">
+    <title>Server Logfile Functions</title>
+    <tgroup cols="3">
+     <thead>
+      <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry><literal><function>pg_logfile_rotate</function>()</literal></entry>
+       <entry><type>bool</type></entry>
+       <entry>rotates the server log file</entry>
+      </row>
+      </tbody>
+</tgroup>
+</table>
+<para>
+       <function>pg_logfile_rotate</function> will force the logger
+       process to rotate log files. If logging to file was not enabled
+       ('file' in <literal>log_destination</> configuration
+       parameter), false will be returned.
+</para>
+
+   <indexterm zone="functions-misc">
     <primary>pg_cancel_backend</primary>
    </indexterm>

@@ -7463,6 +7500,10 @@
    </indexterm>

    <indexterm zone="functions-misc">
+    <primary>pg_reload_config</primary>
+   </indexterm>
+
+   <indexterm zone="functions-misc">
     <primary>signal</primary>
     <secondary sortas="backend">backend processes</secondary>
    </indexterm>
@@ -7497,6 +7538,13 @@
        <entry><type>int</type></entry>
        <entry>Terminate a backend process</entry>
       </row>
+      <row>
+       <entry>
+    <literal><function>pg_reload_config</function>()</literal>
+       </entry>
+       <entry><type>int</type></entry>
+       <entry>Reload configuration from postgresql.conf</entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
@@ -7508,6 +7556,196 @@
     <structname>pg_stat_activity</structname> view, or by listing the postgres
     processes on the server.
    </para>
+   <para>
+   <literal><function>pg_reload_config</function></literal> will send
+    a <literal>SIGHUP</> signal to all backends, forcing them to
+    reload their configuration from <literal>postgresql.conf</>.
+   </para>
+
+   <indexterm zone="functions-misc">
+    <primary>pg_file_stat</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_file_length</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_file_read</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_file_write</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_file_rename</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_file_unlink</primary>
+   </indexterm>
+   <indexterm zone="functions-misc">
+    <primary>pg_dir_ls</primary>
+   </indexterm>
+
+   <indexterm zone="functions-misc">
+    <primary>generic file</primary>
+   </indexterm>
+
+   <para>
+    The functions shown in <xref
+    linkend="functions-misc-file-table"> implement generic file access
+    and directory listing functions. Use of these functions is
+    restricted to superusers.
+   </para>
+
+   <table id="functions-misc-file-table">
+    <title>Generic File and Directory Access Functions</title>
+    <tgroup cols="3">
+     <thead>
+      <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry>
+      </row>
+     </thead>
+
+     <tbody>
+      <row>
+       <entry>
+        <literal><function>pg_file_stat</function>(<parameter>filename_text</parameter>)</literal>
+       </entry>
+       <entry><type>record</type></entry>
+       <entry>Retrieves file stat information</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_length</function>(<parameter>filename_text</parameter>)</literal>
+       </entry>
+       <entry><type>int8</type></entry>
+       <entry>Retrieves length of file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_read</function>(<parameter>filename_text</>, <parameter>offset_int8</>,
<parameter>length_int8</>)</literal>
+       </entry>
+       <entry><type>text</type></entry>
+       <entry>Retrieves contents of text file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_write</function>(<parameter>filename_text</>, <parameter>contents_text</>,
<parameter>append_bool</>)</literal>
+       </entry>
+       <entry><type>int8</type></entry>
+       <entry>Writes data to text file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_ren</function>(<parameter>oldname_text</>, <parameter>newname_text</>)</literal>
+       </entry>
+       <entry><type>bool</type></entry>
+       <entry>Renames file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_ren</function>(<parameter>oldname_text</>, <parameter>newname_text</>)</literal>
+       </entry>
+       <entry><type>bool</type></entry>
+       <entry>Renames file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_file_ren</function>(<parameter>oldname_text</>, <parameter>newname_text</>,
<parameter>archivename_text</>)</literal>
+       </entry>
+       <entry><type>bool</type></entry>
+       <entry>Renames file and previous file</entry>
+      </row>
+
+      <row>
+       <entry>
+        <literal><function>pg_file_unlink</function>(<parameter>filename_text</parameter>)</literal>
+       </entry>
+       <entry><type>bool</type></entry>
+       <entry>unlinks/deletes file</entry>
+      </row>
+      <row>
+       <entry>
+        <literal><function>pg_dir_ls</function>(<parameter>filename_text</>, <parameter>fullpath_bool</>)</literal>
+       </entry>
+       <entry><type>setof text</type></entry>
+       <entry>Retrieves list of filenames in a directory</entry>
+      </row>
+     </tbody>
+    </tgroup>
+   </table>
+
+   <para>
+    All file functions take a <parameter>filename_text</> argument,
+    which may specify an absolute path or is evaluated relative to the
+    <application>cluster directory</> <literal>$PG_DATA</>. While most
+    functions will throw an SQL error causing interrupting the
+    execution if problems are encountered,
+    <literal><function>pg_file_stat</function></literal>
+    and <literal><function>pg_file_length</function></literal> will
+    return -1 if the file is not found,
+    <literal><function>pg_file_rename</function></literal> and
+    <literal><function>pg_file_unlink</function></literal> will return
+    false if the files remain unchanged.
+   </para>
+   <para>
+    <literal><function>pg_file_stat</function></literal> returns
+    metafile information about a given file in a record. From this,
+    the current file length, time of creation, time of last acces, time
+    of last modification can be retrieved, as well as a flag if the
+    name specifies a directory or not. Example:
+<programlisting>
+SELECT len, ctime, atime, mtime, isdir
+  FROM pg_file_stat('postgresql.conf')
+    AS st(len int8,
+          ctime  timestamp, atime timestamp, mtime timestamp,
+          isdir bool);
+</programlisting>
+    If the file metadata is not accessible, the length is reported as -1
+    and the timestamp fields will be NULL.
+   </para>
+   <para>
+    <literal><function>pg_file_len</function></literal> returns the
+    length of the given file or -1 if the file metadata is not accessible.
+   </para>
+   <para>
+    <literal><function>pg_file_read</function></literal> returns the
+    contents of a text file partially or in whole. The
+    <parameter>offset_int8</> parameter specifies a file offset from
+    the beginning of the file, the <parameter>length_int8</> parameter
+    the maximum number of bytes to retrieve.
+   </para>
+   <para>
+    <literal><function>pg_file_write</function></literal> writes data
+    to a text file. If the <parameter>append_bool</> parameter is
+    true, the data is appended to an existing file; if it is false,
+    the file is created.
+   </para>
+   <para>
+    <literal><function>pg_file_rename</function></literal> comes in
+    two flavours. The first covers the standard case of renaming a file from
+    an old name to a new name, while the second variation will perform
+    chained renames: oldname is renamed to archivename, and oldname is
+    renamed to newname. This can be used to replace files in a safe way,
+    minimizing the risk of letting the system in an undefined
+    state. If renaming was done successfully, true is returned.
+   </para>
+   <para>
+    This Example replaces postgresql.conf with a new version, keeping the
+    previous version archived:
+<programlisting>
+SELECT pg_file_rename('postgresql.conf.tmp',
+                      'postgresql.conf',
+                      'postgresql.conf.bak');
+</programlisting>
+   </para>
+   <para>
+    <literal><function>pg_file_unlink</function></literal> will unlink
+    or delete the file. If successful, true is returned.
+   </para>
+   <para>
+    <literal><function>pg_dir_ls</function></literal> returns a set of
+    text containing filenames found in the directory. the
+    <parameter>fullpath_bool</> specifies if the returned text should
+    be extended to the full absolute path or contain the filename only.
+   </para>
   </sect1>

  <sect1 id="functions-array">
Index: maintenance.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/maintenance.sgml,v
retrieving revision 1.35
diff -u -r1.35 maintenance.sgml
--- maintenance.sgml    16 May 2004 19:34:46 -0000    1.35
+++ maintenance.sgml    23 Jul 2004 12:17:07 -0000
@@ -474,25 +474,56 @@
    performance.  Use a <literal>-</> at the start of the file name
    in the <application>syslog</> config file to disable this behavior.
   </para>
-
   <para>
-   You may find it more useful to pipe the
-   <systemitem>stderr</> of the <command>postmaster</> to some type of
-   log rotation program. If you start the server with
-   <command>pg_ctl</>, then the <systemitem>stderr</> of the <command>postmaster</command>
+   In <productname>PostgreSQL</> 7.5, a new internal logging rotation
+   subprocess has been implemented. To activate the logger process,
+   set the configurations parameter <literal>log_destination</> to
+   'file' in <filename>postgresql.conf</>. <productname>PostgreSQL</>
+   will write all logging output to text files in a subdirectory,
+   which can be configured with the configurations parameter
+   <literal>log_directory</>. To read the logfiles, you can access
+   them via the file system, or use the <link linkend=view-pg-logdir-ls>
+   <command>pg_logdir_ls</></link> view and the <link linkend="functions-misc-file-table">
+   <command>pg_file_read()</></link> function.
+  </para>
+  <para>
+   The log file names are created using an internal naming scheme,
+   that may be modified using the <literal>log_filename_prefix</>
+   parameter. The name is assembled according to the following scheme:
+<screen>
+NNNYYYY-MM-DD_HHMMSS_PPPPP.log
+</screen>
+   NNN is the user configurable <literal>log_filename_prefix</>; its
+   length may vary while the rest of the name will be of constant
+   length containing the file date in ANSI format, the time and the
+   process id of the <command>postmaster</>.
+  </para>
+  <para>
+   The <command>system logger</> process will redirect all stderr
+   output to the logfiles, as soon as it is running. Error messages
+   issued before the <command>system logger</> process is running will
+   still go to stderr, so make sure you still catch these startup
+   messages by redirecting the <command>postmaster's</> output.
+  </para>
+  <para>
+   There are some system generated error messages (e.g. load errors of
+   dynamically loaded modules) that are written to stderr, and can not
+   be caught using the 'syslog' destination. Only 'stderr' and 'file'
+   options guarantee to catch these runtime messages as well.
+  </para>
+  <para>
+   You may like to pipe the
+   <systemitem>stderr</> of the <command>postmaster</> to some other
+   log rotation program. If you start the server with <command>pg_ctl</>,
+   then the <systemitem>stderr</> of the <command>postmaster</command>
    is already redirected to <systemitem>stdout</>, so you just need a
    pipe command:

 <programlisting>
 pg_ctl start | rotatelogs /var/log/pgsql_log 86400
 </programlisting>
-
-   The <productname>PostgreSQL</> distribution doesn't include a
-   suitable log rotation program, but there are many available on the
-   Internet. For example, the <application>rotatelogs</application>
-   tool included in the <productname>Apache</productname> distribution
-   can be used with <productname>PostgreSQL</productname>.
   </para>
+
  </sect1>
 </chapter>

Index: monitoring.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/monitoring.sgml,v
retrieving revision 1.26
diff -u -r1.26 monitoring.sgml
--- monitoring.sgml    26 Mar 2004 03:18:28 -0000    1.26
+++ monitoring.sgml    23 Jul 2004 12:17:09 -0000
@@ -50,9 +50,11 @@

 <screen>
 $ ps auxww | grep ^postgres
-postgres   960  0.0  1.1  6104 1480 pts/1    SN   13:17   0:00 postmaster -i
-postgres   963  0.0  1.1  7084 1472 pts/1    SN   13:17   0:00 postgres: stats buffer process
-postgres   965  0.0  1.1  6152 1512 pts/1    SN   13:17   0:00 postgres: stats collector process
+postgres   960  0.0  1.2  8960 1280 pts/1    SN   13:17   0:00 postmaster -i
+postgres   962  0.0  0.4  6301 1421 pts/1    SN   13:17   0:00 postgres: system logger process
+postgres   964  0.0  0.6  6104 1480 pts/1    SN   13:17   0:00 postgres: writer process
+postgres   965  0.0  1.1  7084 1472 pts/1    SN   13:17   0:00 postgres: stats buffer process
+postgres   966  0.0  1.1  6152 1512 pts/1    SN   13:17   0:00 postgres: stats collector process
 postgres   998  0.0  2.3  6532 2992 pts/1    SN   13:18   0:00 postgres: tgl runbug 127.0.0.1 idle
 postgres  1003  0.0  2.4  6532 3128 pts/1    SN   13:19   0:00 postgres: tgl regression [local] SELECT waiting
 postgres  1016  0.1  2.4  6532 3080 pts/1    SN   13:19   0:00 postgres: tgl regression [local] idle in transaction
@@ -62,12 +64,18 @@
    platforms, as do the details of what is shown.  This example is from a
    recent Linux system.)  The first process listed here is the
    <application>postmaster</>, the master server process.  The command arguments
-   shown for it are the same ones given when it was launched.  The next two
-   processes implement the statistics collector, which will be described in
-   detail in the next section.  (These will not be present if you have set
-   the system not to start the statistics collector.)  Each of the remaining
-   processes is a server process handling one client connection.  Each such
-   process sets its command line display in the form
+   shown for it are the same ones given when it was launched. The
+   <application>writer process</> manages the dirty buffers, flushing
+   them to disk when appropriate. The <application>system logger</>
+   process catches message and error output of all <application>backends</>
+   and writes them to logfiles, rotating them if necessary. This process
+   will only be present if <literal>log_destination</> 'file' is selected.
+   The next two processes implement the statistics collector, which
+   will be described in  detail in the next section. (These will not
+   be present if you have set the system not to start the statistics
+   collector.)  Each of the remaining processes is a server process
+   handling one client connection.  Each such process sets its command
+   line display in the form

 <screen>
 postgres: <replaceable>user</> <replaceable>database</> <replaceable>host</> <replaceable>activity</>
Index: runtime.sgml
===================================================================
RCS file: /projects/cvsroot/pgsql-server/doc/src/sgml/runtime.sgml,v
retrieving revision 1.269
diff -u -r1.269 runtime.sgml
--- runtime.sgml    11 Jul 2004 00:18:40 -0000    1.269
+++ runtime.sgml    23 Jul 2004 12:17:19 -0000
@@ -1768,13 +1768,39 @@
       <term><varname>log_destination</varname> (<type>string</type>)</term>
       <listitem>
        <para>
-    <productname>PostgreSQL</productname> supports several methods
-     for loggning, including <systemitem>stderr</systemitem> and
-     <systemitem>syslog</systemitem>. On Windows,
-     <systemitem>eventlog</systemitem> is also supported. Set this
-     option to a list of desired log destinations separated by a
-     comma. The default is to log to <systemitem>stderr</systemitem>
-     only. This option must be set at server start.
+        <productname>PostgreSQL</> supports several methods for logging,
+        including <systemitem>stderr</>, <systemitem>file></> and
+        <systemitem>syslog</>. On Windows, <systemitem>eventlog</> is
+        also supported. Set this option to a list of desired log
+        destinations separated by a comma. The default is to log to
+        <systemitem>stderr</> only. This option must be set at server start.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-log-directory" xreflabel="log_directory">
+      <term><varname>log_directory</> (<type>string</>)</term>
+      <listitem>
+       <para>
+        If <systemitem>file</> is selected in <literal>log_destination</>,
+        <productname>PostgreSQL</> will write log files to this directory.
+        It may be specified as absolute path or relative to the
+        <application>cluster directory</>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-log-filename-prefix" xreflabel="log_filename_prefix">
+      <term><varname>log_directory</> (<type>string</>)</term>
+      <listitem>
+       <para>
+        If <systemitem>file</> is enabled by the <literal>log_destination</>
+        option, <productname>PostgreSQL</> will create the log files
+        according to a naming scheme that includes the timestamp of
+        the start time of the logfile, and the process id of the
+        <command>postmaster</>. This internally selected name is
+        prefixed by a user selectable string, which may be changed
+        using the <literal>log_filename_prefix</> option.
        </para>
       </listitem>
      </varlistentry>

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian wrote:
> Are we done?

Nope, the syslogger part of this is still a mess.  I don't want any
pg_logfile_rotate() function in there at all: its presence is a hangover
from a different design philosophy.  Nor pg_reload_conf(); where did
that come from?  (Hint: if you can edit postgresql.conf you do not need
a helper function to signal the postmaster.)  Nor the pg_logdir_ls view,
as that will malfunction completely if we aren't actually using the
syslogger facility, yet there's no graceful way to make it go away.

I also find the Log_destination setup to be less than carefully thought
out: what in the world does it mean to specify stderr and file as
distinct log destinations?  This design cannot support that, and doesn't
need to AFAICS.  What we probably want instead is a separate
redirect_stderr_to_files boolean (I'm sure a better name could be
thought of).

Also, while I'm aware that a superuser can persuade the backend to write
on anything, it doesn't follow that we should invent pg_file_write(),
pg_file_rename(), or pg_file_unlink().  Those are not needed for the
originally intended purpose of this patch and I think that they are just
invitations to trouble.  If you are aware that there are burglars out
there who know how to pick your door lock, do you then post directions
and tools to help on your door?

Finally, I can tell without even trying it that the present syslogger
code will fail miserably in EXEC_BACKEND case.  It's expecting
realStdErr to be inherited which it will not be.  I don't think the
notion of respawning the logger will work; we're just going to have to
assume it is as reliable as the postmaster is, and we only need launch
it once.  (BTW, did Magnus ever verify for us that redirecting stderr
into a pipe will work at all on Windows?  I think it should, but it
would be embarrassing to find out otherwise after we commit this
code...)

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Andreas Pflug
Date:
Tom Lane wrote:
> Bruce Momjian wrote:
>
>>Are we done?
>
>
> Nope, the syslogger part of this is still a mess.  I don't want any
> pg_logfile_rotate() function in there at all: its presence is a hangover
> from a different design philosophy.

No. As I mentioned earlier, there might be cases when a superuser wants
to issue the rotation to keep some stuff in a single log file.


>  Nor pg_reload_conf(); where did
> that come from?  (Hint: if you can edit postgresql.conf you do not need
> a helper function to signal the postmaster.)

Wrong. The generic file functions allow editing postgresql.conf without
having file access, and consequently you'd like to make that active
without consoling to the server.

>  Nor the pg_logdir_ls view,
> as that will malfunction completely if we aren't actually using the
> syslogger facility, yet there's no graceful way to make it go away.

? It will show nothing if there are no files, so what's the problem?

>
> I also find the Log_destination setup to be less than carefully thought
> out: what in the world does it mean to specify stderr and file as
> distinct log destinations?

stderr is simply untouched. Actually it works parallel.

This design cannot support that, and doesn't
> need to AFAICS.

I didn't want to wipe out the default logging method right away. Of
course, even log_destination=syslog might be redirected.

> What we probably want instead is a separate
> redirect_stderr_to_files boolean (I'm sure a better name could be
> thought of).
>
> Also, while I'm aware that a superuser can persuade the backend to write
> on anything, it doesn't follow that we should invent pg_file_write(),
> pg_file_rename(), or pg_file_unlink().  Those are not needed for the
> originally intended purpose of this patch

I proposed to separate them, they're indeed non-related.
What I'd like is

SELECT pg_file_unlink('postgresql.conf.bak');
SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
                       'postgresql.conf.bak');
SELECT pg_reload_conf();

> and I think that they are just
> invitations to trouble.  If you are aware that there are burglars out
> there who know how to pick your door lock, do you then post directions
> and tools to help on your door?

These are superuser only, and executed in the postgres user context.
We're offering a superuser to shoot himself into the foot wherever he
likes regarding system catalog etc. I wouldn't have a problem if paths
may only be relative to PG_DATA and .. is disallowed.


> Finally, I can tell without even trying it that the present syslogger
> code will fail miserably in EXEC_BACKEND case.

I don't have an EXEC_BACKEND environment, I already pointed out that
this has to be tested.

> It's expecting
> realStdErr to be inherited which it will not be.

Yes, this is probably one source of problems for EXEC_BACKEND.

> I don't think the
> notion of respawning the logger will work; we're just going to have to
> assume it is as reliable as the postmaster is, and we only need launch
> it once.

It works.

   (BTW, did Magnus ever verify for us that redirecting stderr
> into a pipe will work at all on Windows?

Actually, dup2 etc is documented perfectly for win32. This certainly
doesn't mean anything...
win32 system error messages are retrieved using GetLastError, not from
stderr, so problems are a bit different anyway.

Regards,
Andreas

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Also, while I'm aware that a superuser can persuade the backend to write
> on anything, it doesn't follow that we should invent pg_file_write(),
> pg_file_rename(), or pg_file_unlink().  Those are not needed for the
> originally intended purpose of this patch and I think that they are just
> invitations to trouble.  If you are aware that there are burglars out
> there who know how to pick your door lock, do you then post directions
> and tools to help on your door?

We already allow COPY to write anywhere.  Might as well give people some
utilities in case it is helpful.

I think the analogy is locking one door but leaving the other door
unlocked.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Stephan Szabo
Date:
On Fri, 23 Jul 2004, Andreas Pflug wrote:

> > What we probably want instead is a separate
> > redirect_stderr_to_files boolean (I'm sure a better name could be
> > thought of).
> >
> > Also, while I'm aware that a superuser can persuade the backend to write
> > on anything, it doesn't follow that we should invent pg_file_write(),
> > pg_file_rename(), or pg_file_unlink().  Those are not needed for the
> > originally intended purpose of this patch
>
> I proposed to separate them, they're indeed non-related.
> What I'd like is
>
> SELECT pg_file_unlink('postgresql.conf.bak');
> SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
> SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
>                        'postgresql.conf.bak');
> SELECT pg_reload_conf();

I personally don't think the above is the correct approach to allowing
configuration editing from remote. It seems like the wrong level for the
interface, and the file rename isn't atomic and pretending that it is may
prove to be dangerous.

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> Also, while I'm aware that a superuser can persuade the backend to write
>> on anything, it doesn't follow that we should invent pg_file_write(),
>> pg_file_rename(), or pg_file_unlink().

> I think the analogy is locking one door but leaving the other door
> unlocked.

Not only that, but posting a sign out front telling which door is
unlocked.

As for the analogy to COPY, the addition of unlink/rename to a hacker's
tool set renders the situation far more dangerous than if he only has
write.  Write will not allow him to hack write-protected files, but he
might be able to rename them out of the way and create new trojaned
versions...

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Tom Lane wrote:
> >> Also, while I'm aware that a superuser can persuade the backend to write
> >> on anything, it doesn't follow that we should invent pg_file_write(),
> >> pg_file_rename(), or pg_file_unlink().
>
> > I think the analogy is locking one door but leaving the other door
> > unlocked.
>
> Not only that, but posting a sign out front telling which door is
> unlocked.

Actually, my point was that the door is already unlocked (COPY), and
preventing write() because it would unlock a door isn't a valid issue.

> As for the analogy to COPY, the addition of unlink/rename to a hacker's
> tool set renders the situation far more dangerous than if he only has
> write.  Write will not allow him to hack write-protected files, but he
> might be able to rename them out of the way and create new trojaned
> versions...

Yes, I realized that later, that rename/unlink is based on the directory
permissions, not the file permissions.  That is clearly a new capability
that could be seen as opening a new door.

However, file creation via COPY is based on the directory permissions
too.

I do like a full API because I think it could be useful, but I guess we
have to decide if allowing more backend capabilities is reasonable.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Stephan Szabo <sszabo@megazone.bigpanda.com> writes:
> On Fri, 23 Jul 2004, Andreas Pflug wrote:
>> What I'd like is
>>
>> SELECT pg_file_unlink('postgresql.conf.bak');
>> SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
>> SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
>> 'postgresql.conf.bak');
>> SELECT pg_reload_conf();

> I personally don't think the above is the correct approach to allowing
> configuration editing from remote.

I'm pretty much against allowing configuration editing from remote
altogether.  It would raise the stakes tremendously in terms of what
an attacker can do once they've acquired a connection with superuser
rights.  Remember that the above could be applied to pg_hba.conf,
pg_ident.conf, etc just as well as postgresql.conf.  Not to mention
$HOME/.profile and other things the postgres user may own.

> It seems like the wrong level for the interface, and the file rename
> isn't atomic and pretending that it is may prove to be dangerous.

Well, editing postgresql.conf directly isn't very atomic either, with
most editors (which is why we made the postmaster only re-examine the
files upon SIGHUP).

A more cogent argument why remote editing is dangerous is that if you
screw up a config file, you may be unable to get in to fix your mistake.

I agree about the "level" issue though.  If we want to officially
support this, something involving a super-sized form of SET would be
a lot more supportable in the long run.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Stephan Szabo <sszabo@megazone.bigpanda.com> writes:
> > On Fri, 23 Jul 2004, Andreas Pflug wrote:
> >> What I'd like is
> >>
> >> SELECT pg_file_unlink('postgresql.conf.bak');
> >> SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
> >> SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
> >> 'postgresql.conf.bak');
> >> SELECT pg_reload_conf();
>
> > I personally don't think the above is the correct approach to allowing
> > configuration editing from remote.
>
> I'm pretty much against allowing configuration editing from remote
> altogether.  It would raise the stakes tremendously in terms of what
> an attacker can do once they've acquired a connection with superuser
> rights.  Remember that the above could be applied to pg_hba.conf,
> pg_ident.conf, etc just as well as postgresql.conf.  Not to mention
> $HOME/.profile and other things the postgres user may own.

Why can't they just use COPY to replace the contents of pg_hba.conf now?

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> As for the analogy to COPY, the addition of unlink/rename to a hacker's
>> tool set renders the situation far more dangerous than if he only has
>> write.  Write will not allow him to hack write-protected files, but he
>> might be able to rename them out of the way and create new trojaned
>> versions...

> Yes, I realized that later, that rename/unlink is based on the directory
> permissions, not the file permissions.  That is clearly a new capability
> that could be seen as opening a new door.

> However, file creation via COPY is based on the directory permissions
> too.

Right, but the point is that a write-protected file in a writable
directory is not vulnerable to an attacker armed only with write().
If he can do rename() or delete() then it *is* vulnerable.  This is
quite relevant to Postgres seeing that it's hardly practical to
make the $PGDATA directory non-writable to the postmaster, while one
might well think it worthwhile to make pg_hba.conf non-writable.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> I'm pretty much against allowing configuration editing from remote
>> altogether.

> Why can't they just use COPY to replace the contents of pg_hba.conf now?

If you've write-protected it, that doesn't work.

Also, COPY insists on an absolute path, and if the attacker doesn't know
the value of $PGDATA or $HOME then he'll have some difficulty doing
anything much with it.  I thought that the handy default of $PGDATA in
the proposed functions was pretty unwise all by itself --- if they don't
require absolute paths then that's still another new door we'll be opening.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Tom Lane wrote:
> >> As for the analogy to COPY, the addition of unlink/rename to a hacker's
> >> tool set renders the situation far more dangerous than if he only has
> >> write.  Write will not allow him to hack write-protected files, but he
> >> might be able to rename them out of the way and create new trojaned
> >> versions...
>
> > Yes, I realized that later, that rename/unlink is based on the directory
> > permissions, not the file permissions.  That is clearly a new capability
> > that could be seen as opening a new door.
>
> > However, file creation via COPY is based on the directory permissions
> > too.
>
> Right, but the point is that a write-protected file in a writable
> directory is not vulnerable to an attacker armed only with write().
> If he can do rename() or delete() then it *is* vulnerable.  This is
> quite relevant to Postgres seeing that it's hardly practical to
> make the $PGDATA directory non-writable to the postmaster, while one
> might well think it worthwhile to make pg_hba.conf non-writable.

Yes, I was quoting:

> >> SELECT pg_file_unlink('postgresql.conf.bak');
> >> SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
> >> SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
> >> 'postgresql.conf.bak');
> >> SELECT pg_reload_conf();

The pg_file_write() doesn't open any new security holes, only rename and
unlink.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Tom Lane wrote:
> >> I'm pretty much against allowing configuration editing from remote
> >> altogether.
>
> > Why can't they just use COPY to replace the contents of pg_hba.conf now?
>
> If you've write-protected it, that doesn't work.
>
> Also, COPY insists on an absolute path, and if the attacker doesn't know
> the value of $PGDATA or $HOME then he'll have some difficulty doing
> anything much with it.  I thought that the handy default of $PGDATA in
> the proposed functions was pretty unwise all by itself --- if they don't
> require absolute paths then that's still another new door we'll be opening.

I think he wanted $PGDATA default so a remote user could find the log
location without a problem.  I would prefer to see something specific in
the file name passed to identify PGDATA, but I don't have a problem with
the substitution.  Thinking we have security because they can't guess
pgdata seems like security through obscurity to me.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Andrew Dunstan
Date:

Tom Lane wrote:

>Stephan Szabo <sszabo@megazone.bigpanda.com> writes:
>
>
>>On Fri, 23 Jul 2004, Andreas Pflug wrote:
>>
>>
>>>What I'd like is
>>>
>>>SELECT pg_file_unlink('postgresql.conf.bak');
>>>SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
>>>SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
>>>'postgresql.conf.bak');
>>>SELECT pg_reload_conf();
>>>
>>>
>
>
>
>>I personally don't think the above is the correct approach to allowing
>>configuration editing from remote.
>>
>>
>
>I'm pretty much against allowing configuration editing from remote
>altogether.  It would raise the stakes tremendously in terms of what
>an attacker can do once they've acquired a connection with superuser
>rights.  Remember that the above could be applied to pg_hba.conf,
>pg_ident.conf, etc just as well as postgresql.conf.  Not to mention
>$HOME/.profile and other things the postgres user may own.
>
>
>
>>It seems like the wrong level for the interface, and the file rename
>>isn't atomic and pretending that it is may prove to be dangerous.
>>
>>
>
>Well, editing postgresql.conf directly isn't very atomic either, with
>most editors (which is why we made the postmaster only re-examine the
>files upon SIGHUP).
>
>A more cogent argument why remote editing is dangerous is that if you
>screw up a config file, you may be unable to get in to fix your mistake.
>
>I agree about the "level" issue though.  If we want to officially
>support this, something involving a super-sized form of SET would be
>a lot more supportable in the long run.
>
>
>
>

I agree. Aren't we rather too far into feature freeze to be debating
possible provision of a remote config facility and API?

If we provide it, I would suggest a setting that turns it off - maybe
even make that the default. (re arguments about COPY - maybe we should
be able to off server side COPY too).

cheers

andrew



Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andreas didn't ask for a full file API.  I suggested it because we were
already going to have some of the functionality.  If rename/unlink are
new problems, we can skip them and just add what Andreas needs right
now.

---------------------------------------------------------------------------

Andreas Pflug wrote:
> Tom Lane wrote:
> > Bruce Momjian wrote:
> >
> >>Are we done?
> >
> >
> > Nope, the syslogger part of this is still a mess.  I don't want any
> > pg_logfile_rotate() function in there at all: its presence is a hangover
> > from a different design philosophy.
>
> No. As I mentioned earlier, there might be cases when a superuser wants
> to issue the rotation to keep some stuff in a single log file.
>
>
> >  Nor pg_reload_conf(); where did
> > that come from?  (Hint: if you can edit postgresql.conf you do not need
> > a helper function to signal the postmaster.)
>
> Wrong. The generic file functions allow editing postgresql.conf without
> having file access, and consequently you'd like to make that active
> without consoling to the server.
>
> >  Nor the pg_logdir_ls view,
> > as that will malfunction completely if we aren't actually using the
> > syslogger facility, yet there's no graceful way to make it go away.
>
> ? It will show nothing if there are no files, so what's the problem?
>
> >
> > I also find the Log_destination setup to be less than carefully thought
> > out: what in the world does it mean to specify stderr and file as
> > distinct log destinations?
>
> stderr is simply untouched. Actually it works parallel.
>
> This design cannot support that, and doesn't
> > need to AFAICS.
>
> I didn't want to wipe out the default logging method right away. Of
> course, even log_destination=syslog might be redirected.
>
> > What we probably want instead is a separate
> > redirect_stderr_to_files boolean (I'm sure a better name could be
> > thought of).
> >
> > Also, while I'm aware that a superuser can persuade the backend to write
> > on anything, it doesn't follow that we should invent pg_file_write(),
> > pg_file_rename(), or pg_file_unlink().  Those are not needed for the
> > originally intended purpose of this patch
>
> I proposed to separate them, they're indeed non-related.
> What I'd like is
>
> SELECT pg_file_unlink('postgresql.conf.bak');
> SELECT pg_file_write('postgresql.conf.tmp', 'listen_addresses=...');
> SELECT pg_file_rename('postgresql.conf.tmp', 'postgresql.conf',
>                        'postgresql.conf.bak');
> SELECT pg_reload_conf();
>
> > and I think that they are just
> > invitations to trouble.  If you are aware that there are burglars out
> > there who know how to pick your door lock, do you then post directions
> > and tools to help on your door?
>
> These are superuser only, and executed in the postgres user context.
> We're offering a superuser to shoot himself into the foot wherever he
> likes regarding system catalog etc. I wouldn't have a problem if paths
> may only be relative to PG_DATA and .. is disallowed.
>
>
> > Finally, I can tell without even trying it that the present syslogger
> > code will fail miserably in EXEC_BACKEND case.
>
> I don't have an EXEC_BACKEND environment, I already pointed out that
> this has to be tested.
>
> > It's expecting
> > realStdErr to be inherited which it will not be.
>
> Yes, this is probably one source of problems for EXEC_BACKEND.
>
> > I don't think the
> > notion of respawning the logger will work; we're just going to have to
> > assume it is as reliable as the postmaster is, and we only need launch
> > it once.
>
> It works.
>
>    (BTW, did Magnus ever verify for us that redirecting stderr
> > into a pipe will work at all on Windows?
>
> Actually, dup2 etc is documented perfectly for win32. This certainly
> doesn't mean anything...
> win32 system error messages are retrieved using GetLastError, not from
> stderr, so problems are a bit different anyway.
>
> Regards,
> Andreas
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Andreas didn't ask for a full file API.  I suggested it because we were
> already going to have some of the functionality.  If rename/unlink are
> new problems, we can skip them and just add what Andreas needs right
> now.

Given the security worries that have been raised, and the fact that none
of this functionality existed in the patch as it stood at feature-freeze
time, I think there's more than sufficient reason to defer all the
writing stuff to a future release cycle.

I'd like to limit the functionality added now to just file-read and
directory-list commands; and perhaps we ought to go back to limiting
them to work on the configured log output directory rather than being
general purpose.  If they are general purpose, I'm going to want them to
take only absolute paths, which will make it harder to use them for
fetching the logs.  (Not impossible, since we could demand that the GUC
variable holding the log directory be an absolute path, but maybe it's
just better to stay away from the notion of a general file access API
until we've thought harder about the security implications.)

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Thinking we have security because they can't guess
> pgdata seems like security through obscurity to me.

Sure, but it's still a useful roadblock to throw in an attacker's way.

I spent many years doing computer security stuff, and one thing I
learned is that the more layers of security you can have, the better.
You don't put all your faith in any one roadblock; you erect a series
of them that an attacker will have to break through all of.  If some
of 'em are a little porous, that doesn't make 'em useless.

In today's context, I think the main point of requiring an attacker
to guess $PGDATA is that it helps avoid the "software monoculture"
syndrome.  If someone did manage to write a Postgres-based virus that
involved an exploit in this area, it could only spread to machines
that had the $PGDATA value the virus writer was expecting.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Andreas didn't ask for a full file API.  I suggested it because we were
> > already going to have some of the functionality.  If rename/unlink are
> > new problems, we can skip them and just add what Andreas needs right
> > now.
>
> Given the security worries that have been raised, and the fact that none
> of this functionality existed in the patch as it stood at feature-freeze
> time, I think there's more than sufficient reason to defer all the
> writing stuff to a future release cycle.

Agreed.

> I'd like to limit the functionality added now to just file-read and
> directory-list commands; and perhaps we ought to go back to limiting

Yes.

> them to work on the configured log output directory rather than being
> general purpose.  If they are general purpose, I'm going to want them to
> take only absolute paths, which will make it harder to use them for
> fetching the logs.  (Not impossible, since we could demand that the GUC
> variable holding the log directory be an absolute path, but maybe it's
> just better to stay away from the notion of a general file access API
> until we've thought harder about the security implications.)

Agreed it should be relative to the log directory, which may or not be
under PGDATA, and don't let them go up above it.  Is there any downside
to allowing absolute reads as well because COPY can already read
absolute files.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Thinking we have security because they can't guess
> > pgdata seems like security through obscurity to me.
>
> Sure, but it's still a useful roadblock to throw in an attacker's way.
>
> I spent many years doing computer security stuff, and one thing I
> learned is that the more layers of security you can have, the better.
> You don't put all your faith in any one roadblock; you erect a series
> of them that an attacker will have to break through all of.  If some
> of 'em are a little porous, that doesn't make 'em useless.
>
> In today's context, I think the main point of requiring an attacker
> to guess $PGDATA is that it helps avoid the "software monoculture"
> syndrome.  If someone did manage to write a Postgres-based virus that
> involved an exploit in this area, it could only spread to machines
> that had the $PGDATA value the virus writer was expecting.

As a super-user, could an attacker load a server-side language and
access the backend environment variable PGDATA.  Also look at this:

  test=> CREATE FUNCTION "xxx_call_handler" () RETURNS language_handler AS '$libdir/pltcl' LANGUAGE C;
  ERROR:  could not find function "xxx_call_handler" in file "/usr/var/local/postgres/lib/pltcl.so"

Notice the expansion of "$libdir".

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Tom Lane wrote:
>> Bruce Momjian <pgman@candle.pha.pa.us> writes:
>>> Thinking we have security because they can't guess
>>> pgdata seems like security through obscurity to me.
>>
>> Sure, but it's still a useful roadblock to throw in an attacker's way.

> As a super-user, could an attacker load a server-side language and
> access the backend environment variable PGDATA.

Only if it's set.  A security-conscious DBA might prefer to start the
postmaster with -D instead of setting an environment variable.  (In fact
I wonder whether we shouldn't make the postmaster unsetenv PGDATA after
it's set the data directory.  This would be good for debugging purposes,
ie catching any subprocesses that mistakenly assume PGDATA must be a
correct pointer to the datadir, as well as for security reasons.)

> Also look at this:

>   test=> CREATE FUNCTION "xxx_call_handler" () RETURNS language_handler AS '$libdir/pltcl' LANGUAGE C;
>   ERROR:  could not find function "xxx_call_handler" in file "/usr/var/local/postgres/lib/pltcl.so"

> Notice the expansion of "$libdir".

Yah, but that only tells him where libdir is, which shouldn't help him
much.  In any hardened installation, that directory tree won't be
writable by postgres at all.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Agreed it should be relative to the log directory, which may or not be
> under PGDATA, and don't let them go up above it.  Is there any downside
> to allowing absolute reads as well because COPY can already read
> absolute files.

Perhaps not from a security point of view, but I think it would be
rather bizarre for a general-purpose pg_read_file() function to default
to reading from the log directory.  From the point of view of having
a consistent API, it'd be better to call the functions something like
pg_read_logdirectory() and pg_read_logfile() and restrict them to the
log directory.  If we later decide we want to add a general
pg_read_file() operation, we won't have to contort its operation to
preserve compatibility with the log-fetching case.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Andrew Dunstan
Date:

Bruce Momjian wrote:

>As a super-user, could an attacker load a server-side language and
>access the backend environment variable PGDATA.
>
>

plperl won't do it, but plperlu will (as expected I guess). But the
superuser will have to jump through some explicit hoops in order to get
there, which is different from providing such facilities out of the box.

cheers

andrew

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Andrew Dunstan wrote:
>
>
> Bruce Momjian wrote:
>
> >As a super-user, could an attacker load a server-side language and
> >access the backend environment variable PGDATA.
> >
> >
>
> plperl won't do it, but plperlu will (as expected I guess). But the
> superuser will have to jump through some explicit hoops in order to get
> there, which is different from providing such facilities out of the box.

I am thinking they could easily use pgtcl.  I don't think the hoops are
very high.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Agreed it should be relative to the log directory, which may or not be
> > under PGDATA, and don't let them go up above it.  Is there any downside
> > to allowing absolute reads as well because COPY can already read
> > absolute files.
>
> Perhaps not from a security point of view, but I think it would be
> rather bizarre for a general-purpose pg_read_file() function to default
> to reading from the log directory.  From the point of view of having
> a consistent API, it'd be better to call the functions something like
> pg_read_logdirectory() and pg_read_logfile() and restrict them to the
> log directory.  If we later decide we want to add a general
> pg_read_file() operation, we won't have to contort its operation to
> preserve compatibility with the log-fetching case.

OK. There isn't much of value in $PGDATA anyway to read except the
config files, which have limited value.

I did think a file system walker application written using SQL would be
cool, but not if it is going to make us less secure.  I don't think it
does, but adding the function when no one is asking for it seems
backwards.

One issue is that the log files might be in /var/log with other server
logs.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Maybe we could allow unlink or rename if access() shows we own the file
or something.

---------------------------------------------------------------------------

Bruce Momjian wrote:
> Tom Lane wrote:
> > Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > > Agreed it should be relative to the log directory, which may or not be
> > > under PGDATA, and don't let them go up above it.  Is there any downside
> > > to allowing absolute reads as well because COPY can already read
> > > absolute files.
> >
> > Perhaps not from a security point of view, but I think it would be
> > rather bizarre for a general-purpose pg_read_file() function to default
> > to reading from the log directory.  From the point of view of having
> > a consistent API, it'd be better to call the functions something like
> > pg_read_logdirectory() and pg_read_logfile() and restrict them to the
> > log directory.  If we later decide we want to add a general
> > pg_read_file() operation, we won't have to contort its operation to
> > preserve compatibility with the log-fetching case.
>
> OK. There isn't much of value in $PGDATA anyway to read except the
> config files, which have limited value.
>
> I did think a file system walker application written using SQL would be
> cool, but not if it is going to make us less secure.  I don't think it
> does, but adding the function when no one is asking for it seems
> backwards.
>
> One issue is that the log files might be in /var/log with other server
> logs.
>
> --
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073
>
> ---------------------------(end of broadcast)---------------------------
> TIP 7: don't forget to increase your free space map settings
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Tom Lane
Date:
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> One issue is that the log files might be in /var/log with other server
> logs.

Probably not; it seems unlikely that the postgres user would have write
permission on /var/log, as it would have to have to do log rotation in
such a case.  I'd expect anyone putting PG logs under /var/log to create
a postgres-owned subdirectory for 'em.

            regards, tom lane

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > One issue is that the log files might be in /var/log with other server
> > logs.
>
> Probably not; it seems unlikely that the postgres user would have write
> permission on /var/log, as it would have to have to do log rotation in
> such a case.  I'd expect anyone putting PG logs under /var/log to create
> a postgres-owned subdirectory for 'em.

Oh, yea, right.

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
OK, let's go with something that is purely log file stuff.

---------------------------------------------------------------------------

Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Agreed it should be relative to the log directory, which may or not be
> > under PGDATA, and don't let them go up above it.  Is there any downside
> > to allowing absolute reads as well because COPY can already read
> > absolute files.
>
> Perhaps not from a security point of view, but I think it would be
> rather bizarre for a general-purpose pg_read_file() function to default
> to reading from the log directory.  From the point of view of having
> a consistent API, it'd be better to call the functions something like
> pg_read_logdirectory() and pg_read_logfile() and restrict them to the
> log directory.  If we later decide we want to add a general
> pg_read_file() operation, we won't have to contort its operation to
> preserve compatibility with the log-fetching case.
>
>             regards, tom lane
>
> ---------------------------(end of broadcast)---------------------------
> TIP 5: Have you checked our extensive FAQ?
>
>                http://www.postgresql.org/docs/faqs/FAQ.html
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Re: logfile subprocess and Fancy File Functions

From
Bruce Momjian
Date:
OK, let's go with something that is purely log stuff and we can revisit
in 7.6.  Ultimately I think we want postgresql.conf and pg_hba.conf to
be controllable perhaps at the SQL level rather than the function call
level so we need to scope the entire design for 7.6.  There is certainly
need for remote administration, and Andreas working on pgadmin is at the
front of that.

Andreas' original patch had code that processed the log file name and
threw it into a timestamp field in the function output.  If we are going
log-specific, it seems we should resurect that.

I have heard an idea that we could somehow allow SQL-level control over
pg_hba.conf and then dump the file rather than requiring manual editing,
and the same might be possible for other configuration files.

---------------------------------------------------------------------------

Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Agreed it should be relative to the log directory, which may or not be
> > under PGDATA, and don't let them go up above it.  Is there any downside
> > to allowing absolute reads as well because COPY can already read
> > absolute files.
>
> Perhaps not from a security point of view, but I think it would be
> rather bizarre for a general-purpose pg_read_file() function to default
> to reading from the log directory.  From the point of view of having
> a consistent API, it'd be better to call the functions something like
> pg_read_logdirectory() and pg_read_logfile() and restrict them to the
> log directory.  If we later decide we want to add a general
> pg_read_file() operation, we won't have to contort its operation to
> preserve compatibility with the log-fetching case.
>
>             regards, tom lane
>
> ---------------------------(end of broadcast)---------------------------
> TIP 5: Have you checked our extensive FAQ?
>
>                http://www.postgresql.org/docs/faqs/FAQ.html
>

--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073