diff options
Diffstat (limited to 'src/backend/utils/init/postinit.c')
-rw-r--r-- | src/backend/utils/init/postinit.c | 229 |
1 files changed, 110 insertions, 119 deletions
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 4ddc7f712af..0be0fd85438 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.165 2006/05/03 22:45:26 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.166 2006/05/04 16:07:29 tgl Exp $ * * *------------------------------------------------------------------------- @@ -16,14 +16,10 @@ #include "postgres.h" #include <fcntl.h> -#include <sys/file.h> -#include <math.h> #include <unistd.h> -#include "access/genam.h" #include "access/heapam.h" #include "catalog/catalog.h" -#include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/pg_database.h" @@ -42,7 +38,6 @@ #include "storage/smgr.h" #include "utils/acl.h" #include "utils/flatfiles.h" -#include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/portal.h" #include "utils/relcache.h" @@ -51,7 +46,7 @@ static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace); -static void ReverifyMyDatabase(const char *name, bool am_superuser); +static void CheckMyDatabase(const char *name, bool am_superuser); static void InitCommunication(void); static void ShutdownPostgres(int code, Datum arg); static bool ThereIsAtLeastOneRole(void); @@ -72,7 +67,7 @@ static bool ThereIsAtLeastOneRole(void); * file" copy of pg_database that is helpfully maintained by flatfiles.c. * This is subject to various race conditions, so after we have the * transaction infrastructure started, we have to recheck the information; - * see ReverifyMyDatabase. + * see InitPostgres. */ static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace) @@ -108,76 +103,35 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace) } /* - * ReverifyMyDatabase -- recheck info obtained by FindMyDatabase - * - * Since FindMyDatabase cannot lock pg_database, the information it read - * could be stale; for example we might have attached to a database that's in - * process of being destroyed by dropdb(). This routine is called after - * we have all the locking and other infrastructure running --- now we can - * check that we are really attached to a valid database. - * - * In reality, if dropdb() is running in parallel with our startup, - * it's pretty likely that we will have failed before now, due to being - * unable to read some of the system tables within the doomed database. - * This routine just exists to make *sure* we have not started up in an - * invalid database. If we quit now, we should have managed to avoid - * creating any serious problems. - * - * This is also a handy place to fetch the database encoding info out - * of pg_database. - * - * To avoid having to read pg_database more times than necessary - * during session startup, this place is also fitting to check CONNECT - * privilege and set up any database-specific configuration variables. + * CheckMyDatabase -- fetch information from the pg_database entry for our DB */ static void -ReverifyMyDatabase(const char *name, bool am_superuser) +CheckMyDatabase(const char *name, bool am_superuser) { - Relation pgdbrel; - SysScanDesc pgdbscan; - ScanKeyData key; HeapTuple tup; Form_pg_database dbform; - /* - * Because we grab RowShareLock here, we can be sure that dropdb() is not - * running in parallel with us (any more). - */ - pgdbrel = heap_open(DatabaseRelationId, RowShareLock); - - ScanKeyInit(&key, - Anum_pg_database_datname, - BTEqualStrategyNumber, F_NAMEEQ, - NameGetDatum(name)); - - pgdbscan = systable_beginscan(pgdbrel, DatabaseNameIndexId, true, - SnapshotNow, 1, &key); - - tup = systable_getnext(pgdbscan); - if (!HeapTupleIsValid(tup) || - HeapTupleGetOid(tup) != MyDatabaseId) - { - /* OOPS */ - heap_close(pgdbrel, RowShareLock); + /* Fetch our real pg_database row */ + tup = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dbform = (Form_pg_database) GETSTRUCT(tup); - /* - * The only real problem I could have created is to load dirty buffers - * for the dead database into shared buffer cache; if I did, some - * other backend will eventually try to write them and die in - * mdblindwrt. Flush any such pages to forestall trouble. - */ - DropDatabaseBuffers(MyDatabaseId); - /* Now I can commit hara-kiri with a clear conscience... */ + /* This recheck is strictly paranoia */ + if (strcmp(name, NameStr(dbform->datname)) != 0) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%s\", OID %u, has disappeared from pg_database", - name, MyDatabaseId))); - } - - dbform = (Form_pg_database) GETSTRUCT(tup); + errmsg("database \"%s\" has disappeared from pg_database", + name), + errdetail("Database OID %u now seems to belong to \"%s\".", + MyDatabaseId, NameStr(dbform->datname)))); /* - * These next checks are not enforced when in standalone mode, so that + * Check permissions to connect to the database. + * + * These checks are not enforced when in standalone mode, so that * there is a way to recover from disabling all access to all databases, * for example "UPDATE pg_database SET datallowconn = false;". * @@ -246,8 +200,8 @@ ReverifyMyDatabase(const char *name, bool am_superuser) Datum datum; bool isnull; - datum = heap_getattr(tup, Anum_pg_database_datconfig, - RelationGetDescr(pgdbrel), &isnull); + datum = SysCacheGetAttr(DATABASEOID, tup, Anum_pg_database_datconfig, + &isnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); @@ -256,8 +210,7 @@ ReverifyMyDatabase(const char *name, bool am_superuser) } } - systable_endscan(pgdbscan); - heap_close(pgdbrel, RowShareLock); + ReleaseSysCache(tup); } @@ -337,9 +290,11 @@ InitPostgres(const char *dbname, const char *username) bool bootstrap = IsBootstrapProcessingMode(); bool autovacuum = IsAutoVacuumProcess(); bool am_superuser; + char *fullpath; /* - * Set up the global variables holding database id and path. + * Set up the global variables holding database id and path. But note + * we won't actually try to touch the database just yet. * * We take a shortcut in the bootstrap case, otherwise we have to look up * the db name in pg_database. @@ -348,55 +303,24 @@ InitPostgres(const char *dbname, const char *username) { MyDatabaseId = TemplateDbOid; MyDatabaseTableSpace = DEFAULTTABLESPACE_OID; - SetDatabasePath(GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace)); } else { - char *fullpath; - - /* - * Formerly we validated DataDir here, but now that's done earlier. - */ - /* * Find oid and tablespace of the database we're about to open. Since * we're not yet up and running we have to use the hackish - * FindMyDatabase. + * FindMyDatabase, which looks in the flat-file copy of pg_database. */ if (!FindMyDatabase(dbname, &MyDatabaseId, &MyDatabaseTableSpace)) ereport(FATAL, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", dbname))); - - fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace); - - /* Verify the database path */ - - if (access(fullpath, F_OK) == -1) - { - if (errno == ENOENT) - ereport(FATAL, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%s\" does not exist", - dbname), - errdetail("The database subdirectory \"%s\" is missing.", - fullpath))); - else - ereport(FATAL, - (errcode_for_file_access(), - errmsg("could not access directory \"%s\": %m", - fullpath))); - } - - ValidatePgVersion(fullpath); - - SetDatabasePath(fullpath); } - /* - * Code after this point assumes we are in the proper directory! - */ + fullpath = GetDatabasePath(MyDatabaseId, MyDatabaseTableSpace); + + SetDatabasePath(fullpath); /* * Finish filling in the PGPROC struct, and add it to the ProcArray. @@ -459,11 +383,79 @@ InitPostgres(const char *dbname, const char *username) */ on_shmem_exit(ShutdownPostgres, 0); - /* start a new transaction here before access to db */ + /* + * Start a new transaction here before first access to db + */ if (!bootstrap) StartTransactionCommand(); /* + * Now that we have a transaction, we can take locks. Take a writer's + * lock on the database we are trying to connect to. If there is + * a concurrently running DROP DATABASE on that database, this will + * block us until it finishes (and has updated the flat file copy + * of pg_database). + * + * Note that the lock is not held long, only until the end of this + * startup transaction. This is OK since we are already advertising + * our use of the database in the PGPROC array; anyone trying a DROP + * DATABASE after this point will see us there. + * + * Note: use of RowExclusiveLock here is reasonable because we envision + * our session as being a concurrent writer of the database. If we had + * a way of declaring a session as being guaranteed-read-only, we could + * use AccessShareLock for such sessions and thereby not conflict against + * CREATE DATABASE. + */ + if (!bootstrap) + LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, + RowExclusiveLock); + + /* + * Recheck the flat file copy of pg_database to make sure the target + * database hasn't gone away. If there was a concurrent DROP DATABASE, + * this ensures we will die cleanly without creating a mess. + */ + if (!bootstrap) + { + Oid dbid2; + Oid tsid2; + + if (!FindMyDatabase(dbname, &dbid2, &tsid2) || + dbid2 != MyDatabaseId || tsid2 != MyDatabaseTableSpace) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", + dbname), + errdetail("It seems to have just been dropped or renamed."))); + } + + /* + * Now we should be able to access the database directory safely. + * Verify it's there and looks reasonable. + */ + if (!bootstrap) + { + if (access(fullpath, F_OK) == -1) + { + if (errno == ENOENT) + ereport(FATAL, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%s\" does not exist", + dbname), + errdetail("The database subdirectory \"%s\" is missing.", + fullpath))); + else + ereport(FATAL, + (errcode_for_file_access(), + errmsg("could not access directory \"%s\": %m", + fullpath))); + } + + ValidatePgVersion(fullpath); + } + + /* * It's now possible to do real access to the system catalogs. * * Replace faked-up relcache entries with correct info. @@ -499,22 +491,21 @@ InitPostgres(const char *dbname, const char *username) am_superuser = superuser(); } - /* set up ACL framework (so ReverifyMyDatabase can check permissions) */ + /* set up ACL framework (so CheckMyDatabase can check permissions) */ initialize_acl(); /* - * Unless we are bootstrapping, double-check that InitMyDatabaseInfo() got - * a correct result. We can't do this until all the database-access - * infrastructure is up. (Also, it wants to know if the user is a - * superuser, so the above stuff has to happen first.) + * Read the real pg_database row for our database, check permissions + * and set up database-specific GUC settings. We can't do this until all + * the database-access infrastructure is up. (Also, it wants to know if + * the user is a superuser, so the above stuff has to happen first.) */ if (!bootstrap) - ReverifyMyDatabase(dbname, am_superuser); + CheckMyDatabase(dbname, am_superuser); /* * Final phase of relation cache startup: write a new cache file if - * necessary. This is done after ReverifyMyDatabase to avoid writing a - * cache file into a dead database. + * necessary. (XXX this could be folded back into Phase2) */ RelationCacheInitializePhase3(); @@ -530,7 +521,7 @@ InitPostgres(const char *dbname, const char *username) /* * Initialize various default states that can't be set up until we've - * selected the active user and done ReverifyMyDatabase. + * selected the active user and gotten the right GUC settings. */ /* set default namespace search path */ @@ -587,13 +578,13 @@ ThereIsAtLeastOneRole(void) HeapScanDesc scan; bool result; - pg_authid_rel = heap_open(AuthIdRelationId, AccessExclusiveLock); + pg_authid_rel = heap_open(AuthIdRelationId, AccessShareLock); scan = heap_beginscan(pg_authid_rel, SnapshotNow, 0, NULL); result = (heap_getnext(scan, ForwardScanDirection) != NULL); heap_endscan(scan); - heap_close(pg_authid_rel, AccessExclusiveLock); + heap_close(pg_authid_rel, AccessShareLock); return result; } |