Peter Eisentraut 9b4bfbdc2c Handle reading of startup packet and authentication exchange after forking
a new postmaster child process.  This should eliminate problems with
authentication blocking (e.g., ident, SSL init) and also reduce problems
with the accept queue filling up under heavy load.

The option to send elog output to a different file per backend (postgres -o)
has been disabled for now because the initialization would have to happen
in a different order and it's not clear we want to keep this anyway.
2001-06-20 18:07:56 +00:00

879 lines
20 KiB
C

/*-------------------------------------------------------------------------
*
* miscinit.c
* miscellaneous initialization support stuff
*
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/init/miscinit.c,v 1.72 2001/06/20 18:07:56 petere Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/param.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <errno.h>
#include "catalog/catname.h"
#include "catalog/pg_shadow.h"
#include "libpq/libpq-be.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#ifdef CYR_RECODE
unsigned char RecodeForwTable[128];
unsigned char RecodeBackTable[128];
#endif
ProcessingMode Mode = InitProcessing;
/* Note: we rely on these to initialize as zeroes */
static char directoryLockFile[MAXPGPATH];
static char socketLockFile[MAXPGPATH];
/* ----------------------------------------------------------------
* ignoring system indexes support stuff
* ----------------------------------------------------------------
*/
static bool isIgnoringSystemIndexes = false;
/*
* IsIgnoringSystemIndexes
* True if ignoring system indexes.
*/
bool
IsIgnoringSystemIndexes()
{
return isIgnoringSystemIndexes;
}
/*
* IgnoreSystemIndexes
* Set true or false whether PostgreSQL ignores system indexes.
*
*/
void
IgnoreSystemIndexes(bool mode)
{
isIgnoringSystemIndexes = mode;
}
/* ----------------------------------------------------------------
* database path / name support stuff
* ----------------------------------------------------------------
*/
void
SetDatabasePath(const char *path)
{
if (DatabasePath)
{
free(DatabasePath);
DatabasePath = NULL;
}
/* use strdup since this is done before memory contexts are set up */
if (path)
{
DatabasePath = strdup(path);
AssertState(DatabasePath);
}
}
void
SetDatabaseName(const char *name)
{
if (DatabaseName)
{
free(DatabaseName);
DatabaseName = NULL;
}
/* use strdup since this is done before memory contexts are set up */
if (name)
{
DatabaseName = strdup(name);
AssertState(DatabaseName);
}
}
/*
* Set data directory, but make sure it's an absolute path. Use this,
* never set DataDir directly.
*/
void
SetDataDir(const char *dir)
{
char *new;
AssertArg(dir);
if (dir[0] != '/')
{
char *buf;
size_t buflen;
buflen = MAXPGPATH;
for (;;)
{
buf = malloc(buflen);
if (!buf)
elog(FATAL, "out of memory");
if (getcwd(buf, buflen))
break;
else if (errno == ERANGE)
{
free(buf);
buflen *= 2;
continue;
}
else
{
free(buf);
elog(FATAL, "cannot get current working directory: %m");
}
}
new = malloc(strlen(buf) + 1 + strlen(dir) + 1);
if (!new)
elog(FATAL, "out of memory");
sprintf(new, "%s/%s", buf, dir);
free(buf);
}
else
{
new = strdup(dir);
if (!new)
elog(FATAL, "out of memory");
}
if (DataDir)
free(DataDir);
DataDir = new;
}
/* ----------------------------------------------------------------
* MULTIBYTE stub code
*
* Even if MULTIBYTE is not enabled, these functions are necessary
* since pg_proc.h has references to them.
* ----------------------------------------------------------------
*/
#ifndef MULTIBYTE
Datum
getdatabaseencoding(PG_FUNCTION_ARGS)
{
return DirectFunctionCall1(namein, CStringGetDatum("SQL_ASCII"));
}
Datum
PG_encoding_to_char(PG_FUNCTION_ARGS)
{
return DirectFunctionCall1(namein, CStringGetDatum("SQL_ASCII"));
}
Datum
PG_char_to_encoding(PG_FUNCTION_ARGS)
{
PG_RETURN_INT32(0);
}
#endif
/* ----------------------------------------------------------------
* CYR_RECODE support
* ----------------------------------------------------------------
*/
#ifdef CYR_RECODE
#define MAX_TOKEN 80
/* Some standard C libraries, including GNU, have an isblank() function.
Others, including Solaris, do not. So we have our own.
*/
static bool
isblank(const char c)
{
return c == ' ' || c == 9 /* tab */ ;
}
static void
next_token(FILE *fp, char *buf, const int bufsz)
{
/*--------------------------------------------------------------------------
Grab one token out of fp. Tokens are strings of non-blank
characters bounded by blank characters, beginning of line, and end
of line. Blank means space or tab. Return the token as *buf.
Leave file positioned to character immediately after the token or
EOF, whichever comes first. If no more tokens on line, return null
string as *buf and position file to beginning of next line or EOF,
whichever comes first.
--------------------------------------------------------------------------*/
int c;
char *eb = buf + (bufsz - 1);
/* Move over inital token-delimiting blanks */
while (isblank(c = getc(fp)));
if (c != '\n')
{
/*
* build a token in buf of next characters up to EOF, eol, or
* blank.
*/
while (c != EOF && c != '\n' && !isblank(c))
{
if (buf < eb)
*buf++ = c;
c = getc(fp);
/*
* Put back the char right after the token (putting back EOF
* is ok)
*/
}
ungetc(c, fp);
}
*buf = '\0';
}
static void
read_through_eol(FILE *file)
{
int c;
do
c = getc(file);
while (c != '\n' && c != EOF);
}
void
SetCharSet()
{
FILE *file;
char *p,
c,
eof = false;
char *map_file;
char buf[MAX_TOKEN];
int i;
unsigned char FromChar,
ToChar;
char ChTable[80];
for (i = 0; i < 128; i++)
{
RecodeForwTable[i] = i + 128;
RecodeBackTable[i] = i + 128;
}
if (IsUnderPostmaster)
{
GetCharSetByHost(ChTable, MyProcPort->raddr.in.sin_addr.s_addr, DataDir);
p = ChTable;
}
else
p = getenv("PG_RECODETABLE");
if (p && *p != '\0')
{
map_file = malloc(strlen(DataDir) + strlen(p) + 2);
if (! map_file)
elog(FATAL, "out of memory");
sprintf(map_file, "%s/%s", DataDir, p);
file = AllocateFile(map_file, PG_BINARY_R);
if (file == NULL)
{
free(map_file);
return;
}
eof = false;
while (!eof)
{
c = getc(file);
ungetc(c, file);
if (c == EOF)
eof = true;
else
{
if (c == '#')
read_through_eol(file);
else
{
/* Read the FromChar */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
FromChar = strtoul(buf, 0, 0);
/* Read the ToChar */
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0')
{
ToChar = strtoul(buf, 0, 0);
RecodeForwTable[FromChar - 128] = ToChar;
RecodeBackTable[ToChar - 128] = FromChar;
}
read_through_eol(file);
}
}
}
}
FreeFile(file);
free(map_file);
}
}
char *
convertstr(unsigned char *buff, int len, int dest)
{
int i;
char *ch = buff;
for (i = 0; i < len; i++, buff++)
{
if (*buff > 127)
{
if (dest)
*buff = RecodeForwTable[*buff - 128];
else
*buff = RecodeBackTable[*buff - 128];
}
}
return ch;
}
#endif
/* ----------------------------------------------------------------
* User ID things
*
* The session user is determined at connection start and never
* changes. The current user may change when "setuid" functions
* are implemented. Conceptually there is a stack, whose bottom
* is the session user. You are yourself responsible to save and
* restore the current user id if you need to change it.
* ----------------------------------------------------------------
*/
static Oid CurrentUserId = InvalidOid;
static Oid SessionUserId = InvalidOid;
static bool AuthenticatedUserIsSuperuser = false;
/*
* This function is relevant for all privilege checks.
*/
Oid
GetUserId(void)
{
AssertState(OidIsValid(CurrentUserId));
return CurrentUserId;
}
void
SetUserId(Oid newid)
{
AssertArg(OidIsValid(newid));
CurrentUserId = newid;
}
/*
* This value is only relevant for informational purposes.
*/
Oid
GetSessionUserId(void)
{
AssertState(OidIsValid(SessionUserId));
return SessionUserId;
}
void
SetSessionUserId(Oid newid)
{
AssertArg(OidIsValid(newid));
SessionUserId = newid;
/* Current user defaults to session user. */
if (!OidIsValid(CurrentUserId))
CurrentUserId = newid;
}
void
InitializeSessionUserId(const char *username)
{
HeapTuple userTup;
/*
* Don't do scans if we're bootstrapping, none of the system catalogs
* exist yet, and they should be owned by postgres anyway.
*/
AssertState(!IsBootstrapProcessingMode());
/* call only once */
AssertState(!OidIsValid(SessionUserId));
userTup = SearchSysCache(SHADOWNAME,
PointerGetDatum(username),
0, 0, 0);
if (!HeapTupleIsValid(userTup))
elog(FATAL, "user \"%s\" does not exist", username);
SetSessionUserId(((Form_pg_shadow) GETSTRUCT(userTup))->usesysid);
AuthenticatedUserIsSuperuser = ((Form_pg_shadow) GETSTRUCT(userTup))->usesuper;
ReleaseSysCache(userTup);
}
/*
* Change session auth ID while running
*/
void SetSessionAuthorization(const char * username)
{
int32 userid;
if (!AuthenticatedUserIsSuperuser)
elog(ERROR, "permission denied");
userid = get_usesysid(username);
SetSessionUserId(userid);
SetUserId(userid);
}
/*
* Get user name from user id
*/
char *
GetUserName(Oid userid)
{
HeapTuple tuple;
char *result;
tuple = SearchSysCache(SHADOWSYSID,
ObjectIdGetDatum(userid),
0, 0, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "invalid user id %u", (unsigned) userid);
result = pstrdup(NameStr(((Form_pg_shadow) GETSTRUCT(tuple))->usename));
ReleaseSysCache(tuple);
return result;
}
/*-------------------------------------------------------------------------
* Interlock-file support
*
* These routines are used to create both a data-directory lockfile
* ($DATADIR/postmaster.pid) and a Unix-socket-file lockfile ($SOCKFILE.lock).
* Both kinds of files contain the same info:
*
* Owning process' PID
* Data directory path
*
* By convention, the owning process' PID is negated if it is a standalone
* backend rather than a postmaster. This is just for informational purposes.
* The path is also just for informational purposes (so that a socket lockfile
* can be more easily traced to the associated postmaster).
*
* A data-directory lockfile can optionally contain a third line, containing
* the key and ID for the shared memory block used by this postmaster.
*
* On successful lockfile creation, a proc_exit callback to remove the
* lockfile is automatically created.
*-------------------------------------------------------------------------
*/
/*
* proc_exit callback to remove a lockfile.
*/
static void
UnlinkLockFile(int status, Datum filename)
{
unlink((char *) DatumGetPointer(filename));
/* Should we complain if the unlink fails? */
}
/*
* Create a lockfile, if possible
*
* Call CreateLockFile with the name of the lockfile to be created.
* Returns true if successful, false if not (with a message on stderr).
*
* amPostmaster is used to determine how to encode the output PID.
* isDDLock and refName are used to determine what error message to produce.
*/
static bool
CreateLockFile(const char *filename, bool amPostmaster,
bool isDDLock, const char *refName)
{
int fd;
char buffer[MAXPGPATH + 100];
int len;
int encoded_pid;
pid_t other_pid;
pid_t my_pid = getpid();
/*
* We need a loop here because of race conditions.
*/
for (;;)
{
/*
* Try to create the lock file --- O_EXCL makes this atomic.
*/
fd = open(filename, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0)
break; /* Success; exit the retry loop */
/*
* Couldn't create the pid file. Probably it already exists.
*/
if (errno != EEXIST && errno != EACCES)
elog(FATAL, "Can't create lock file %s: %m", filename);
/*
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
fd = open(filename, O_RDONLY, 0600);
if (fd < 0)
{
if (errno == ENOENT)
continue; /* race condition; try again */
elog(FATAL, "Can't read lock file %s: %m", filename);
}
if ((len = read(fd, buffer, sizeof(buffer) - 1)) <= 0)
elog(FATAL, "Can't read lock file %s: %m", filename);
close(fd);
buffer[len] = '\0';
encoded_pid = atoi(buffer);
/* if pid < 0, the pid is for postgres, not postmaster */
other_pid = (pid_t) (encoded_pid < 0 ? -encoded_pid : encoded_pid);
if (other_pid <= 0)
elog(FATAL, "Bogus data in lock file %s", filename);
/*
* Check to see if the other process still exists
*
* Normally kill() will fail with ESRCH if the given PID doesn't
* exist. BeOS returns EINVAL for some silly reason, however.
*/
if (other_pid != my_pid)
{
if (kill(other_pid, 0) == 0 ||
(errno != ESRCH
#ifdef __BEOS__
&& errno != EINVAL
#endif
))
{
/* lockfile belongs to a live process */
fprintf(stderr, "Lock file \"%s\" already exists.\n",
filename);
if (isDDLock)
fprintf(stderr,
"Is another %s (pid %d) running in \"%s\"?\n",
(encoded_pid < 0 ? "postgres" : "postmaster"),
(int) other_pid, refName);
else
fprintf(stderr,
"Is another %s (pid %d) using \"%s\"?\n",
(encoded_pid < 0 ? "postgres" : "postmaster"),
(int) other_pid, refName);
return false;
}
}
/*
* No, the creating process did not exist. However, it could be
* that the postmaster crashed (or more likely was kill -9'd by a
* clueless admin) but has left orphan backends behind. Check for
* this by looking to see if there is an associated shmem segment
* that is still in use.
*/
if (isDDLock)
{
char *ptr;
unsigned long shmKey,
shmId;
ptr = strchr(buffer, '\n');
if (ptr != NULL &&
(ptr = strchr(ptr + 1, '\n')) != NULL)
{
ptr++;
if (sscanf(ptr, "%lu %lu", &shmKey, &shmId) == 2)
{
if (SharedMemoryIsInUse((IpcMemoryKey) shmKey,
(IpcMemoryId) shmId))
{
fprintf(stderr,
"Found a pre-existing shared memory block (ID %d) still in use.\n"
"If you're sure there are no old backends still running,\n"
"remove the shared memory block with ipcrm(1), or just\n"
"delete \"%s\".\n",
(int) shmId, filename);
return false;
}
}
}
}
/*
* Looks like nobody's home. Unlink the file and try again to
* create it. Need a loop because of possible race condition
* against other would-be creators.
*/
if (unlink(filename) < 0)
elog(FATAL, "Can't remove old lock file %s: %m"
"\n\tThe file seems accidentally left, but I couldn't remove it."
"\n\tPlease remove the file by hand and try again.",
filename);
}
/*
* Successfully created the file, now fill it.
*/
snprintf(buffer, sizeof(buffer), "%d\n%s\n",
amPostmaster ? (int) my_pid : -((int) my_pid),
DataDir);
errno = 0;
if (write(fd, buffer, strlen(buffer)) != strlen(buffer))
{
int save_errno = errno;
close(fd);
unlink(filename);
/* if write didn't set errno, assume problem is no disk space */
errno = save_errno ? save_errno : ENOSPC;
elog(FATAL, "Can't write lock file %s: %m", filename);
}
close(fd);
/*
* Arrange for automatic removal of lockfile at proc_exit.
*/
on_proc_exit(UnlinkLockFile, PointerGetDatum(strdup(filename)));
return true; /* Success! */
}
bool
CreateDataDirLockFile(const char *datadir, bool amPostmaster)
{
char lockfile[MAXPGPATH];
snprintf(lockfile, sizeof(lockfile), "%s/postmaster.pid", datadir);
if (!CreateLockFile(lockfile, amPostmaster, true, datadir))
return false;
/* Save name of lockfile for RecordSharedMemoryInLockFile */
strcpy(directoryLockFile, lockfile);
return true;
}
bool
CreateSocketLockFile(const char *socketfile, bool amPostmaster)
{
char lockfile[MAXPGPATH];
snprintf(lockfile, sizeof(lockfile), "%s.lock", socketfile);
if (!CreateLockFile(lockfile, amPostmaster, false, socketfile))
return false;
/* Save name of lockfile for TouchSocketLockFile */
strcpy(socketLockFile, lockfile);
return true;
}
/*
* Re-read the socket lock file. This should be called every so often
* to ensure that the lock file has a recent access date. That saves it
* from being removed by overenthusiastic /tmp-directory-cleaner daemons.
* (Another reason we should never have put the socket file in /tmp...)
*/
void
TouchSocketLockFile(void)
{
int fd;
char buffer[1];
/* Do nothing if we did not create a socket... */
if (socketLockFile[0] != '\0')
{
/* XXX any need to complain about errors here? */
fd = open(socketLockFile, O_RDONLY | PG_BINARY, 0);
if (fd >= 0)
{
read(fd, buffer, sizeof(buffer));
close(fd);
}
}
}
/*
* Append information about a shared memory segment to the data directory
* lock file (if we have created one).
*
* This may be called multiple times in the life of a postmaster, if we
* delete and recreate shmem due to backend crash. Therefore, be prepared
* to overwrite existing information. (As of 7.1, a postmaster only creates
* one shm seg anyway; but for the purposes here, if we did have more than
* one then any one of them would do anyway.)
*/
void
RecordSharedMemoryInLockFile(IpcMemoryKey shmKey, IpcMemoryId shmId)
{
int fd;
int len;
char *ptr;
char buffer[BLCKSZ];
/*
* Do nothing if we did not create a lockfile (probably because we are
* running standalone).
*/
if (directoryLockFile[0] == '\0')
return;
fd = open(directoryLockFile, O_RDWR | PG_BINARY, 0);
if (fd < 0)
{
elog(DEBUG, "Failed to rewrite %s: %m", directoryLockFile);
return;
}
len = read(fd, buffer, sizeof(buffer) - 100);
if (len <= 0)
{
elog(DEBUG, "Failed to read %s: %m", directoryLockFile);
close(fd);
return;
}
buffer[len] = '\0';
/*
* Skip over first two lines (PID and path).
*/
ptr = strchr(buffer, '\n');
if (ptr == NULL ||
(ptr = strchr(ptr + 1, '\n')) == NULL)
{
elog(DEBUG, "Bogus data in %s", directoryLockFile);
close(fd);
return;
}
ptr++;
/*
* Append shm key and ID. Format to try to keep it the same length
* always (trailing junk won't hurt, but might confuse humans).
*/
sprintf(ptr, "%9lu %9lu\n",
(unsigned long) shmKey, (unsigned long) shmId);
/*
* And rewrite the data. Since we write in a single kernel call, this
* update should appear atomic to onlookers.
*/
len = strlen(buffer);
errno = 0;
if (lseek(fd, (off_t) 0, SEEK_SET) != 0 ||
(int) write(fd, buffer, len) != len)
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
elog(DEBUG, "Failed to write %s: %m", directoryLockFile);
close(fd);
return;
}
close(fd);
}
/*-------------------------------------------------------------------------
* Version checking support
*-------------------------------------------------------------------------
*/
/*
* Determine whether the PG_VERSION file in directory `path' indicates
* a data version compatible with the version of this program.
*
* If compatible, return. Otherwise, elog(FATAL).
*/
void
ValidatePgVersion(const char *path)
{
char full_path[MAXPGPATH];
FILE *file;
int ret;
long file_major,
file_minor;
long my_major = 0,
my_minor = 0;
char *endptr;
const char *version_string = PG_VERSION;
my_major = strtol(version_string, &endptr, 10);
if (*endptr == '.')
my_minor = strtol(endptr + 1, NULL, 10);
snprintf(full_path, MAXPGPATH, "%s/PG_VERSION", path);
file = AllocateFile(full_path, "r");
if (!file)
{
if (errno == ENOENT)
elog(FATAL, "File %s is missing. This is not a valid data directory.", full_path);
else
elog(FATAL, "cannot open %s: %m", full_path);
}
ret = fscanf(file, "%ld.%ld", &file_major, &file_minor);
if (ret == EOF)
elog(FATAL, "cannot read %s: %m", full_path);
else if (ret != 2)
elog(FATAL, "`%s' does not have a valid format. You need to initdb.", full_path);
FreeFile(file);
if (my_major != file_major || my_minor != file_minor)
elog(FATAL, "The data directory was initialized by PostgreSQL version %ld.%ld, "
"which is not compatible with this version %s.",
file_major, file_minor, version_string);
}