postgres/src/backend/commands/dbcommands.c
Tom Lane 0a7fb4e918 First round of changes for new fmgr interface. fmgr itself and the
key call sites are changed, but most called functions are still oldstyle.
An exception is that the PL managers are updated (so, for example, NULL
handling now behaves as expected in plperl and plpgsql functions).
NOTE initdb is forced due to added column in pg_proc.
2000-05-28 17:56:29 +00:00

403 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.55 2000/05/28 17:55:55 tgl 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 "access/htup.h"
#include "access/skey.h"
#include "access/xact.h"
#include "catalog/catname.h"
#include "catalog/indexing.h"
#include "catalog/pg_database.h"
#include "catalog/pg_shadow.h"
#include "commands/comment.h"
#include "miscadmin.h"
#include "storage/bufmgr.h" /* for DropBuffers */
#include "storage/fd.h" /* for closeAllVfds */
#include "storage/sinval.h" /* for DatabaseHasActiveBackends */
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/rel.h"
#include "utils/syscache.h"
/* non-export function prototypes */
static bool
get_user_info(const char *name, int4 *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];
int4 user_id;
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(GetPgUserName(), &user_id, &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);
/*
* close virtual file descriptors so the kernel has more available for
* the system() calls
*/
closeAllVfds();
/*
* 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] = NameGetDatum(namein(dbname));
new_record[Anum_pg_database_datdba - 1] = Int32GetDatum(user_id);
new_record[Anum_pg_database_encoding - 1] = Int32GetDatum(encoding);
new_record[Anum_pg_database_datpath - 1] = PointerGetDatum(textin(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);
/* 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);
#if defined(sun)
if (system(buf) != 0 && errno != ECHILD)
#else
if (system(buf) != 0)
#endif
{
int ret;
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 user_id,
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(GetPgUserName(), &user_id, &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 (user_id != 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);
/*
* close virtual file descriptors so the kernel has more available for
* the system() calls
*/
closeAllVfds();
/*
* 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);
/*
* 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(const char *name, int4 *use_sysid, bool *use_super, bool *use_createdb)
{
HeapTuple utup;
AssertArg(name);
utup = SearchSysCacheTuple(SHADOWNAME,
PointerGetDatum(name),
0, 0, 0);
if (!HeapTupleIsValid(utup))
return false;
if (use_sysid)
*use_sysid = ((Form_pg_shadow) GETSTRUCT(utup))->usesysid;
if (use_super)
*use_super = ((Form_pg_shadow) GETSTRUCT(utup))->usesuper;
if (use_createdb)
*use_createdb = ((Form_pg_shadow) GETSTRUCT(utup))->usecreatedb;
return true;
}