postgresql/src/backend/commands/dbcommands.c
Peter Eisentraut 6dc249610a Code cleanup of user name and user id handling in the backend. The current
user is now defined in terms of the user id, the user name is only computed
upon request (for display purposes). This is kind of the opposite of the
previous state, which would maintain the user name and compute the user id
for permission checks.

Besides perhaps saving a few cycles (integer vs string), this now creates a
single point of attack for changing the user id during a connection, for
purposes of "setuid" functions, etc.
2000-09-06 14:15:31 +00:00

393 lines
10 KiB
C

/*-------------------------------------------------------------------------
*
* dbcommands.c
*
*
* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.60 2000/09/06 14:15:16 petere Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "commands/dbcommands.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "access/heapam.h"
#include "catalog/catname.h"
#include "catalog/pg_database.h"
#include "catalog/pg_shadow.h"
#include "commands/comment.h"
#include "miscadmin.h"
#include "storage/sinval.h" /* for DatabaseHasActiveBackends */
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/syscache.h"
/* non-export function prototypes */
static bool
get_user_info(Oid use_sysid, bool *use_super, bool *use_createdb);
static bool
get_db_info(const char *name, char *dbpath, Oid *dbIdP, int4 *ownerIdP);
/*
* CREATE DATABASE
*/
void
createdb(const char *dbname, const char *dbpath, int encoding)
{
char buf[2 * MAXPGPATH + 100];
char *loc;
char locbuf[512];
int ret;
bool use_super,
use_createdb;
Relation pg_database_rel;
HeapTuple tuple;
TupleDesc pg_database_dsc;
Datum new_record[Natts_pg_database];
char new_record_nulls[Natts_pg_database] = {' ', ' ', ' ', ' '};
if (!get_user_info(GetUserId(), &use_super, &use_createdb))
elog(ERROR, "current user name is invalid");
if (!use_createdb && !use_super)
elog(ERROR, "CREATE DATABASE: permission denied");
if (get_db_info(dbname, NULL, NULL, NULL))
elog(ERROR, "CREATE DATABASE: database \"%s\" already exists", dbname);
/* don't call this in a transaction block */
if (IsTransactionBlock())
elog(ERROR, "CREATE DATABASE: may not be called in a transaction block");
/* Generate directory name for the new database */
if (dbpath == NULL || strcmp(dbpath, dbname) == 0)
strcpy(locbuf, dbname);
else
snprintf(locbuf, sizeof(locbuf), "%s/%s", dbpath, dbname);
loc = ExpandDatabasePath(locbuf);
if (loc == NULL)
elog(ERROR,
"The database path '%s' is invalid. "
"This may be due to a character that is not allowed or because the chosen "
"path isn't permitted for databases", dbpath);
/*
* Insert a new tuple into pg_database
*/
pg_database_rel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
pg_database_dsc = RelationGetDescr(pg_database_rel);
/* Form tuple */
new_record[Anum_pg_database_datname - 1] = DirectFunctionCall1(namein,
CStringGetDatum(dbname));
new_record[Anum_pg_database_datdba - 1] = Int32GetDatum(GetUserId());
new_record[Anum_pg_database_encoding - 1] = Int32GetDatum(encoding);
new_record[Anum_pg_database_datpath - 1] = DirectFunctionCall1(textin,
CStringGetDatum(locbuf));
tuple = heap_formtuple(pg_database_dsc, new_record, new_record_nulls);
/*
* Update table
*/
heap_insert(pg_database_rel, tuple);
/*
* Update indexes (there aren't any currently)
*/
#ifdef Num_pg_database_indices
if (RelationGetForm(pg_database_rel)->relhasindex)
{
Relation idescs[Num_pg_database_indices];
CatalogOpenIndices(Num_pg_database_indices,
Name_pg_database_indices, idescs);
CatalogIndexInsert(idescs, Num_pg_database_indices, pg_database_rel,
tuple);
CatalogCloseIndices(Num_pg_database_indices, idescs);
}
#endif
heap_close(pg_database_rel, NoLock);
/*
* Close virtual file descriptors so the kernel has more available for
* the mkdir() and system() calls below.
*/
closeAllVfds();
/* Copy the template database to the new location */
if (mkdir(loc, S_IRWXU) != 0)
elog(ERROR, "CREATE DATABASE: unable to create database directory '%s': %s", loc, strerror(errno));
snprintf(buf, sizeof(buf), "cp %s%cbase%ctemplate1%c* '%s'",
DataDir, SEP_CHAR, SEP_CHAR, SEP_CHAR, loc);
ret = system(buf);
/* Some versions of SunOS seem to return ECHILD after a system() call */
#if defined(sun)
if (ret != 0 && errno != ECHILD)
#else
if (ret != 0)
#endif
{
/* Failed, so try to clean up the created directory ... */
snprintf(buf, sizeof(buf), "rm -rf '%s'", loc);
ret = system(buf);
#if defined(sun)
if (ret == 0 || errno == ECHILD)
#else
if (ret == 0)
#endif
elog(ERROR, "CREATE DATABASE: could not initialize database directory");
else
elog(ERROR, "CREATE DATABASE: Could not initialize database directory. Delete failed as well");
}
}
/*
* DROP DATABASE
*/
void
dropdb(const char *dbname)
{
int4 db_owner;
bool use_super;
Oid db_id;
char *path,
dbpath[MAXPGPATH],
buf[MAXPGPATH + 100];
Relation pgdbrel;
HeapScanDesc pgdbscan;
ScanKeyData key;
HeapTuple tup;
AssertArg(dbname);
if (strcmp(dbname, "template1") == 0)
elog(ERROR, "DROP DATABASE: May not be executed on the template1 database");
if (strcmp(dbname, DatabaseName) == 0)
elog(ERROR, "DROP DATABASE: Cannot be executed on the currently open database");
if (IsTransactionBlock())
elog(ERROR, "DROP DATABASE: May not be called in a transaction block");
if (!get_user_info(GetUserId(), &use_super, NULL))
elog(ERROR, "Current user name is invalid");
if (!get_db_info(dbname, dbpath, &db_id, &db_owner))
elog(ERROR, "DROP DATABASE: Database \"%s\" does not exist", dbname);
if (GetUserId() != db_owner && !use_super)
elog(ERROR, "DROP DATABASE: Permission denied");
path = ExpandDatabasePath(dbpath);
if (path == NULL)
elog(ERROR,
"The database path '%s' is invalid. "
"This may be due to a character that is not allowed or because the chosen "
"path isn't permitted for databases", path);
/*
* Obtain exclusive lock on pg_database. We need this to ensure that
* no new backend starts up in the target database while we are
* deleting it. (Actually, a new backend might still manage to start
* up, because it will read pg_database without any locking to
* discover the database's OID. But it will detect its error in
* ReverifyMyDatabase and shut down before any serious damage is done.
* See postinit.c.)
*/
pgdbrel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
/*
* Check for active backends in the target database.
*/
if (DatabaseHasActiveBackends(db_id))
{
heap_close(pgdbrel, AccessExclusiveLock);
elog(ERROR, "DROP DATABASE: Database \"%s\" is being accessed by other users", dbname);
}
/*
* Find the database's tuple by OID (should be unique, we trust).
*/
ScanKeyEntryInitialize(&key, 0, ObjectIdAttributeNumber,
F_OIDEQ, ObjectIdGetDatum(db_id));
pgdbscan = heap_beginscan(pgdbrel, 0, SnapshotNow, 1, &key);
tup = heap_getnext(pgdbscan, 0);
if (!HeapTupleIsValid(tup))
{
heap_close(pgdbrel, AccessExclusiveLock);
/*
* This error should never come up since the existence of the
* database is checked earlier
*/
elog(ERROR, "DROP DATABASE: Database \"%s\" doesn't exist despite earlier reports to the contrary",
dbname);
}
/* Delete any comments associated with the database */
DeleteComments(db_id);
/* Remove the database's tuple from pg_database */
heap_delete(pgdbrel, &tup->t_self, NULL);
heap_endscan(pgdbscan);
/*
* Close pg_database, but keep exclusive lock till commit to ensure
* that any new backend scanning pg_database will see the tuple dead.
*/
heap_close(pgdbrel, NoLock);
/*
* Drop pages for this database that are in the shared buffer cache.
* This is important to ensure that no remaining backend tries to
* write out a dirty buffer to the dead database later...
*/
DropBuffers(db_id);
/*
* Close virtual file descriptors so the kernel has more available for
* the system() call below.
*/
closeAllVfds();
/*
* Remove the database's subdirectory and everything in it.
*/
snprintf(buf, sizeof(buf), "rm -rf '%s'", path);
#if defined(sun)
if (system(buf) != 0 && errno != ECHILD)
#else
if (system(buf) != 0)
#endif
elog(NOTICE, "DROP DATABASE: The database directory '%s' could not be removed", path);
}
/*
* Helper functions
*/
static bool
get_db_info(const char *name, char *dbpath, Oid *dbIdP, int4 *ownerIdP)
{
Relation relation;
HeapTuple tuple;
ScanKeyData scanKey;
HeapScanDesc scan;
AssertArg(name);
relation = heap_openr(DatabaseRelationName, AccessExclusiveLock /* ??? */ );
ScanKeyEntryInitialize(&scanKey, 0, Anum_pg_database_datname,
F_NAMEEQ, NameGetDatum(name));
scan = heap_beginscan(relation, 0, SnapshotNow, 1, &scanKey);
if (!HeapScanIsValid(scan))
elog(ERROR, "Cannot begin scan of %s.", DatabaseRelationName);
tuple = heap_getnext(scan, 0);
if (HeapTupleIsValid(tuple))
{
text *tmptext;
bool isnull;
/* oid of the database */
if (dbIdP)
*dbIdP = tuple->t_data->t_oid;
/* uid of the owner */
if (ownerIdP)
{
*ownerIdP = (int4) heap_getattr(tuple,
Anum_pg_database_datdba,
RelationGetDescr(relation),
&isnull);
if (isnull)
*ownerIdP = -1; /* hopefully no one has that id already ;) */
}
/* database path (as registered in pg_database) */
if (dbpath)
{
tmptext = (text *) heap_getattr(tuple,
Anum_pg_database_datpath,
RelationGetDescr(relation),
&isnull);
if (!isnull)
{
Assert(VARSIZE(tmptext) - VARHDRSZ < MAXPGPATH);
strncpy(dbpath, VARDATA(tmptext), VARSIZE(tmptext) - VARHDRSZ);
*(dbpath + VARSIZE(tmptext) - VARHDRSZ) = '\0';
}
else
strcpy(dbpath, "");
}
}
else
{
if (dbIdP)
*dbIdP = InvalidOid;
}
heap_endscan(scan);
/* We will keep the lock on the relation until end of transaction. */
heap_close(relation, NoLock);
return HeapTupleIsValid(tuple);
}
static bool
get_user_info(Oid use_sysid, bool *use_super, bool *use_createdb)
{
HeapTuple utup;
utup = SearchSysCacheTuple(SHADOWSYSID,
ObjectIdGetDatum(use_sysid),
0, 0, 0);
if (!HeapTupleIsValid(utup))
return false;
if (use_super)
*use_super = ((Form_pg_shadow) GETSTRUCT(utup))->usesuper;
if (use_createdb)
*use_createdb = ((Form_pg_shadow) GETSTRUCT(utup))->usecreatedb;
return true;
}