mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-29 20:42:09 +02:00
6dc249610a
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.
393 lines
10 KiB
C
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;
|
|
}
|