Thread: logfile subprocess and Fancy File Functions
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
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; }
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 */
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
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
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
Andreas Pflug wrote: > How should the prefix be named? pgsql_ ? Make the file names configurable. -- Peter Eisentraut http://developer.postgresql.org/~petere/
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
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/
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
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
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/
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
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/
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'
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
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
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
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
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
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 */
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
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.
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>
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
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
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
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.
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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