#include <stdio.h>

#include "tmp/postgres.h"

RcsId("$Header: /private/postgres/src/parser/RCS/dbcommands.c,v 1.11 1992/06/28 03:46:48 mao Exp $");

#include "access/heapam.h"
#include "access/htup.h"
#include "access/relscan.h"
#include "utils/rel.h"
#include "utils/log.h"
#include "tmp/daemon.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_user.h"
#include "catalog/pg_database.h"


extern int NStriping;
extern char *PostgresHomes[];
extern char *GetDataHome();
extern char *PG_username;
extern char *DBName;

createdb(dbname)
    char *dbname;
{
    ObjectId db_id, user_id;
    char *path;
    char buf[512];
    int i;

    /*
     *  If this call returns, the database does not exist and we're allowed
     *  to create databases.
     */
    check_permissions("createdb", dbname, &db_id, &user_id);

    /* close virtual file descriptors so we can do system() calls */
    closeAllVfds();

    if (NStriping == 0) {
	path = GetDataHome();
	sprintf(buf, "mkdir %s/data/base/%s", path, dbname);
	system(buf);
	sprintf(buf, "cp %s/data/base/template1/* %s/data/base/%s",
                    path, path, dbname);
	system(buf);
    } else {
	for (i = 0; i < NStriping; i++)
	{
	    path = PostgresHomes[i];
	    sprintf(buf, "mkdir %s/data/base/%s", path, dbname);
	    system(buf);
	    sprintf(buf, "cp %s/data/base/template1/* %s/data/base/%s",
		    path, path, dbname);
	    system(buf);
	}
    }

    sprintf(buf, "append pg_database (datname = \"%s\"::char16, \
                  datdba = \"%d\"::oid, datpath = \"%s\"::text)",
		  dbname, user_id, dbname);
    pg_eval(buf, (char *) NULL, (ObjectId *) NULL, 0);
}

destroydb(dbname)
    char *dbname;
{
    ObjectId user_id, db_id;
    char *path;
    int i;
    char buf[512];

    /*
     *  If this call returns, the database exists and we're allowed to
     *  remove it.
     */
    check_permissions("destroydb", dbname, &db_id, &user_id);

    if (!ObjectIdIsValid(db_id)) {
	elog(FATAL, "impossible: pg_database instance with invalid OID.");
    }

    /* stop the vacuum daemon */
    stop_vacuum(dbname);

    /* remove the data directory */
    if (NStriping == 0) {
	path = GetDataHome();
	sprintf(buf, "rm -r %s/data/base/%s", path, dbname);
	system(buf);
    } else {
	for (i = 0; i < NStriping; i++) {
	    path = PostgresHomes[i];
	    sprintf(buf, "rm -r %s/data/base/%s", path, dbname);
	    system(buf);
	}
    }

    /* remove the pg_database tuple */
    sprintf(buf, "delete pg_database where pg_database.oid = \"%d\"::oid",
		  db_id);
    pg_eval(buf, (char *) NULL, (ObjectId *) NULL, 0);

    /* drop pages for this database that are in the shared buffer cache */
    DropBuffers(db_id);
}

HeapTuple
get_pg_usertup(command, username)
    char *command;
    char *username;
{
    Relation urel;
    HeapTuple usertup;
    HeapTuple tup;
    Buffer buf;
    HeapScanDesc scan;
	ScanKeyData scanKey;

    urel = heap_openr(Name_pg_user);
    if (!RelationIsValid(urel))
	elog(FATAL, "%s: cannot open %s.", command, Name_pg_user);

	ScanKeyEntryInitialize(&scanKey.data[0], 0,
						   Anum_pg_user_usename,
						   NameEqualRegProcedure, NameGetDatum(username));

    scan = heap_beginscan(urel, 0, NowTimeQual, 1, &scanKey);
    if (!HeapScanIsValid(scan))
	elog(WARN, "%s: cannot begin scan of pg_user.", command);

    /*
     *  since we want to return the tuple out of this proc, and we're
     *  going to close the relation, copy the tuple and return the copy.
     */
    tup = heap_getnext(scan, 0, &buf);

    if (HeapTupleIsValid(tup)) {
	usertup = (HeapTuple) palloctup(tup, buf, urel);
	ReleaseBuffer(buf);
    } else {
	elog(WARN, "No pg_user tuple for %s", username);
    }

    heap_endscan(scan);
    heap_close(urel);
    return (usertup);
}

HeapTuple
get_pg_dbtup(command, dbname, dbrel)
    char *command;
    char *dbname;
    Relation dbrel;
{
    HeapTuple dbtup;
    HeapTuple tup;
    Buffer buf;
    HeapScanDesc scan;
    ScanKeyData scanKey;

    ScanKeyEntryInitialize(&scanKey.data[0], 0, Anum_pg_database_datname,
				NameEqualRegProcedure, NameGetDatum(dbname));

    scan = heap_beginscan(dbrel, 0, NowTimeQual, 1, &scanKey);
    if (!HeapScanIsValid(scan))
	elog(WARN, "%s: cannot begin scan of pg_database.", command);

    /*
     *  since we want to return the tuple out of this proc, and we're
     *  going to close the relation, copy the tuple and return the copy.
     */
    tup = heap_getnext(scan, 0, &buf);

    if (HeapTupleIsValid(tup)) {
	dbtup = (HeapTuple) palloctup(tup, buf, dbrel);
	ReleaseBuffer(buf);
    } else
	dbtup = tup;

    heap_endscan(scan);
    return (dbtup);
}

/*
 *  check_permissions() -- verify that the user is permitted to do this.
 *
 *  If the user is not allowed to carry out this operation, this routine
 *  elog(WARN, ...)s, which will abort the xact.  As a side effect, the
 *  user's pg_user tuple OID is returned in userIdP and the target database's
 *  OID is returned in dbIdP.
 */

check_permissions(command, dbname, dbIdP, userIdP)
    char *command;
    char *dbname;
    ObjectId *dbIdP;
    ObjectId *userIdP;
{
    Relation urel, dbrel;
    HeapTuple dbtup, utup;
    ObjectId dbowner;
    ObjectId dbid;
    char use_createdb;
    bool dbfound;
    bool use_super;

    utup = get_pg_usertup(command, PG_username);
    *userIdP = ((Form_pg_user)GETSTRUCT(utup))->usesysid;
    use_super = ((Form_pg_user)GETSTRUCT(utup))->usesuper;
    use_createdb = ((Form_pg_user)GETSTRUCT(utup))->usecreatedb;

    /* Check to make sure user has permission to use createdb */
    if (!use_createdb) {
        elog(WARN, "User %s is not allowed to create/destroy databases",
             PG_username);
    }

    /* Make sure we are not mucking with the template database */
    if (!strcmp(dbname, "template1")) {
        elog(WARN, "%s cannot be executed on the template database.", command);
    }

    /* Check to make sure database is not the currently open database */
    if (!strcmp(dbname, DBName)) {
        elog(WARN, "%s cannot be executed on an open database", command);
    }

    /* Check to make sure database is owned by this user */

    /* 
     * need the reldesc to get the database owner out of dbtup 
     * and to set a write lock on it.
     */
    dbrel = heap_openr(Name_pg_database);

    if (!RelationIsValid(dbrel))
	elog(FATAL, "%s: cannot open %s.", command, Name_pg_database);

    /*
     * Acquire a write lock on pg_database from the beginning to avoid 
     * upgrading a read lock to a write lock.  Upgrading causes long delays 
     * when multiple 'createdb's or 'destroydb's are run simult. -mer 7/3/91
     */
    RelationSetLockForWrite(dbrel);
    dbtup = get_pg_dbtup(command, dbname, dbrel);
    dbfound = HeapTupleIsValid(dbtup);

    if (dbfound) {
	dbowner = (ObjectId) heap_getattr(dbtup, InvalidBuffer,
				          Anum_pg_database_datdba,
				          &(dbrel->rd_att),
				          (char *) NULL);
	*dbIdP = dbtup->t_oid;
    } else {
	*dbIdP = InvalidObjectId;
    }

    heap_close(dbrel);

    /*
     *  Now be sure that the user is allowed to do this.
     */

    if (dbfound && !strcmp(command, "createdb")) {

        elog(WARN, "createdb: database %s already exists.", dbname);

    } else if (!dbfound && !strcmp(command, "destroydb")) {

        elog(WARN, "destroydb: database %s does not exist.", dbname);

    } else if (dbfound && !strcmp(command, "destroydb")
	       && dbowner != *userIdP && use_super == false) {

        elog(WARN, "%s: database %s is not owned by you.", command, dbname);

    }
}

/*
 *  stop_vacuum() -- stop the vacuum daemon on the database, if one is
 *		     running.
 */
stop_vacuum(dbname)
    char *dbname;
{
    int i;
    char *path;
    char buf[512];
    char filename[256];
    FILE *fp;
    int pid;

    if (NStriping == 0) {
	path = GetDataHome();
	sprintf(filename, "%s/data/base/%s/%s.vacuum", path, dbname, dbname);
	if ((fp = fopen(filename, "r")) != (FILE *) NULL) {
	    fscanf(fp, "%d", &pid);
	    fclose(fp);
	    if (kill(pid, SIGKILLDAEMON1) < 0) {
		elog(WARN, "can't kill vacuum daemon (pid %d) on %s",
			   pid, dbname);
	    }
	}
    } else {
	for (i = 0; i < NStriping; i++) {
	    path = PostgresHomes[i];
	    sprintf(filename, "%s/data/base/%s/%s.vacuum",
			 path, dbname, dbname);
	    if ((fp = fopen(filename, "r")) != (FILE *) NULL) {
		fscanf(fp, "%d", &pid);
		fclose(fp);
		if (kill(pid, SIGKILLDAEMON1) < 0) {
		    elog(WARN, "can't kill vacuum daemon (pid %d) on %s",
			       pid, dbname);
		}
	    }
	}
    }
}
