/*------------------------------------------------------------------------- * * 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.79 2001/10/19 17:03:08 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include #include #include #include #include #include #include #include #include #include #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; struct stat stat_buf; 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"); } /* * Check if the directory has group or world access. If so, reject. */ if (stat(new, &stat_buf) == -1) elog(FATAL, "could not read permissions of directory %s: %s", new, strerror(errno)); if (stat_buf.st_mode & (S_IRWXG | S_IRWXO)) elog(FATAL, "data directory %s has group or world access; permissions should be u=rwx (0700)", new); 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_client_encoding(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); } Datum pg_convert(PG_FUNCTION_ARGS) { elog(ERROR, "convert is not supported. To use convert, you need to enable multibyte capability"); return DirectFunctionCall1(textin, CStringGetDatum("")); } Datum pg_convert2(PG_FUNCTION_ARGS) { elog(ERROR, "convert is not supported. To use convert, you need to enable multibyte capability"); return DirectFunctionCall1(textin, CStringGetDatum("")); } #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); } void InitializeSessionUserIdStandalone(void) { /* This function should only be called in a single-user backend. */ AssertState(!IsUnderPostmaster); /* call only once */ AssertState(!OidIsValid(SessionUserId)); SetSessionUserId(BOOTSTRAP_USESYSID); AuthenticatedUserIsSuperuser = true; } /* * 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 ntries; int len; int encoded_pid; pid_t other_pid; pid_t my_pid = getpid(); /* * We need a loop here because of race conditions. But don't loop * forever (for example, a non-writable $PGDATA directory might cause * a failure that won't go away). 100 tries seems like plenty. */ for (ntries = 0; ; ntries++) { /* * 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) || ntries > 100) 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 != 2) elog(FATAL, "File %s does not contain valid data. 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); }