From 11023eb1f5bd4fe6ddff652957848437b5d16f14 Mon Sep 17 00:00:00 2001 From: Bruce Momjian Date: Sun, 12 Dec 1999 05:15:10 +0000 Subject: [PATCH] Meanwhile, database names with single quotes in names don't work very well at all, and because of shell quoting rules this can't be fixed, so I put in error messages to that end. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, calling create or drop database in a transaction block is not so good either, because the file system mysteriously refuses to roll back rm calls on transaction aborts. :) So I put in checks to see if a transaction is in progress and signal an error. Also I put the whole call in a transaction of its own to be able to roll back changes to pg_database in case the file system operations fail. The alternative location issues I posted recently were untouched, awaiting the outcome of that discussion. Other than that, this should be much more fool-proof now. The docs I cleaned up as well. Peter Eisentraut Sernanders väg 10:115 --- doc/TODO | 2 +- doc/src/sgml/ref/create_database.sgml | 127 +++++++++++++++++++------- doc/src/sgml/ref/drop_database.sgml | 96 ++++++++++++++----- src/backend/commands/dbcommands.c | 90 +++++++++++++----- 4 files changed, 236 insertions(+), 79 deletions(-) diff --git a/doc/TODO b/doc/TODO index 0b23b5afd7..4971e43f9d 100644 --- a/doc/TODO +++ b/doc/TODO @@ -71,7 +71,7 @@ MISC * Fix btree to give a useful elog when key > 1/2 (page - overhead) * -pg_dump should preserve primary key information * plpgsql regression tests fail on BSD/OS -* database names with spaces fail +* -database names with spaces fail * insert of 0.0 into DECIMAL(4,4) field fails ENHANCEMENTS diff --git a/doc/src/sgml/ref/create_database.sgml b/doc/src/sgml/ref/create_database.sgml index 53c5861f03..5dc6dca9bc 100644 --- a/doc/src/sgml/ref/create_database.sgml +++ b/doc/src/sgml/ref/create_database.sgml @@ -1,5 +1,5 @@ @@ -20,7 +20,7 @@ Postgres documentation - 1999-07-20 + 1999-12-11 CREATE DATABASE name [ WITH LOCATION = 'dbpath' ] @@ -28,7 +28,7 @@ CREATE DATABASE name [ WITH LOCATIO - 1998-04-15 + 1999-12-11 Inputs @@ -48,7 +48,8 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable> [ WITH LOCATIO <term><replaceable class="parameter">dbpath</replaceable></term> <listitem> <para> - An alternate location for the new database. See below for caveats. + An alternate location where to store the new database in the filesystem. + See below for caveats. </para> </listitem> </varlistentry> @@ -58,7 +59,7 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable> [ WITH LOCATIO <refsect2 id="R2-SQL-CREATEDATABASE-2"> <refsect2info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect2info> <title> Outputs @@ -67,36 +68,81 @@ CREATE DATABASE <replaceable class="PARAMETER">name</replaceable> [ WITH LOCATIO <variablelist> <varlistentry> - <term><computeroutput> -CREATE DATABASE - </computeroutput></term> + <term><computeroutput>CREATE DATABASE</computeroutput></term> <listitem> <para> Message returned if the command completes successfully. </para> </listitem> </varlistentry> + <varlistentry> - <term><computeroutput> -WARN: createdb: database "<replaceable class="parameter">name</replaceable>" already exists. - </computeroutput></term> + <term><computeroutput>ERROR: user '<replaceable class="parameter">username</replaceable>' is not allowed to create/drop databases</computeroutput></term> <listitem> <para> - This occurs if <replaceable class="parameter">database</replaceable> specified already exists. + You must have the special CREATEDB privilege to create databases. + See <xref linkend="SQL-CREATEUSER" endterm="SQL-CREATEUSER-title">. </para> </listitem> </varlistentry> + + <varlistentry> - <term><computeroutput> -ERROR: Unable to create database directory <replaceable class="parameter">directory</replaceable> - </computeroutput></term> + <term><computeroutput>ERROR: createdb: database "<replaceable class="parameter">name</replaceable>" already exists</computeroutput></term> <listitem> <para> - There was a problem with creating the required directory; this operation will - need permissions for the <literal>postgres</literal> user on the specified location. + This occurs if a database with the <replaceable class="parameter">name</replaceable> + specified already exists. </para> </listitem> </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: Single quotes are not allowed in database names.</computeroutput></term> + <term><computeroutput>ERROR: Single quotes are not allowed in database paths.</computeroutput></term> + <listitem> + <para> + The database <replaceable class="parameter">name</replaceable> and + <replaceable class="parameter">dbpath</replaceable> cannot contain + single quotes. This is required so that the shell commands that + create the database directory can execute safely. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: The path 'xxx' is invalid.</computeroutput></term> + <listitem> + <para> + The expansion of the specified <replaceable class="parameter">dbpath</replaceable> + (see below how) failed. Check the path you entered or make sure that the + environment variable you are referencing does exist. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: createdb: May not be called in a transaction block.</computeroutput></term> + <listitem> + <para> + If you have an explicit transaction block in progress you cannot call + <command>CREATE DATABASE</command>. You must finish the transaction first. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: Unable to create database directory 'xxx'.</computeroutput></term> + <term><computeroutput>ERROR: Could not initialize database directory.</computeroutput></term> + <listitem> + <para> + These are most likely related to insufficient permissions on the data + directory, a full disk, or other file system problems. The user under + which the database server is running, must have access to the location. + </para> + </listitem> + </varlistentry> + </variablelist> </para> </refsect2> @@ -104,29 +150,40 @@ ERROR: Unable to create database directory <replaceable class="parameter">direc <refsect1 id="R1-SQL-CREATEDATABASE-1"> <refsect1info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect1info> <title> Description - CREATE DATABASE creates a new Postgres database. - The creator becomes the administrator of the new database. + CREATE DATABASE creates a new + PostgreSQL database. + The creator becomes the owner of the new database. - An alternate location can be specified as either an - environment variable known to the backend server - (e.g. 'PGDATA2') or, if the server is built to - allow it, as an absolute path name - (e.g. '/usr/local/pgsql/data'). - In either case, the location must be pre-configured - by initlocation. + An alternate location can be specified in order to, + for example, store the database on a different disk. + The path must have been prepared with the + command. + + + If the path contains a slash, the leading part is interpreted + as an environment variable, which must be known to the + server process. This way the database administrator can + exercise control over at which locations databases can be created. + (A customary choice is, e.g., 'PGDATA2'.) + If the server is compiled with ALLOW_ABSOLUTE_DBPATHS + (not so by default), absolute path names, as identified by + a leading slash + (e.g. '/usr/local/pgsql/data'), + are allowed as well. - 1998-04-15 + 1999-12-11 Notes @@ -136,7 +193,11 @@ ERROR: Unable to create database directory <replaceable class="parameter">direc language extension. </para> <para> - Use <command>DROP DATABASE</command> to remove a database. + Use <xref linkend="SQL-DROPDATABASE" endterm="SQL-DROPDATABASE-title"> to remove a database. + </para> + <para> + The program <xref linkend="APP-CREATEDB" endterm="APP-CREATEDB-title"> is a + shell script wrapper around this command, provided for convenience. </para> <para> @@ -183,16 +244,16 @@ comment from Olly; response from Thomas... <prompt>$</prompt> <userinput>initlocation ~/private_db</userinput> <computeroutput>Creating Postgres database system directory /home/olly/private_db/base</computeroutput> - <prompt>$</prompt> <userinput>psql olly</userinput> - <computeroutput>Welcome to psql, the PostgreSQL interactive terminal. +<prompt>$</prompt> <userinput>psql olly</userinput> +<computeroutput>Welcome to psql, the PostgreSQL interactive terminal. (Please type \copyright to see the distribution terms of PostgreSQL.) Type \h for help with SQL commands, \? for help on internal slash commands, \q to quit, \g or terminate with semicolon to execute query. - <prompt>olly=></prompt></computeroutput> <userinput>create database elsewhere with location = '/home/olly/private_db';</userinput> - <computeroutput>CREATE DATABASE</computeroutput> +<prompt>olly=></prompt></computeroutput> <userinput>CREATE DATABASE elsewhere WITH LOCATION = '/home/olly/private_db';</userinput> +<computeroutput>CREATE DATABASE</computeroutput> </programlisting> </para> </refsect1> diff --git a/doc/src/sgml/ref/drop_database.sgml b/doc/src/sgml/ref/drop_database.sgml index a6be13955c..363a4ab5cc 100644 --- a/doc/src/sgml/ref/drop_database.sgml +++ b/doc/src/sgml/ref/drop_database.sgml @@ -1,5 +1,5 @@ <!-- -$Header: /cvsroot/pgsql/doc/src/sgml/ref/drop_database.sgml,v 1.8 1999/12/04 04:53:15 momjian Exp $ +$Header: /cvsroot/pgsql/doc/src/sgml/ref/drop_database.sgml,v 1.9 1999/12/12 05:15:09 momjian Exp $ Postgres documentation --> @@ -15,12 +15,12 @@ Postgres documentation DROP DATABASE </refname> <refpurpose> - Destroys an existing database + Removes an existing database </refpurpose> </refnamediv> <refsynopsisdiv> <refsynopsisdivinfo> - <date>1999-07-20</date> + <date>1999-12-11</date> </refsynopsisdivinfo> <synopsis> DROP DATABASE <replaceable class="PARAMETER">name</replaceable> @@ -28,7 +28,7 @@ DROP DATABASE <replaceable class="PARAMETER">name</replaceable> <refsect2 id="R2-SQL-DROPDATABASE-1"> <refsect2info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect2info> <title> Inputs @@ -49,7 +49,7 @@ DROP DATABASE <replaceable class="PARAMETER">name</replaceable> <refsect2 id="R2-SQL-DROPDATABASE-2"> <refsect2info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect2info> <title> Outputs @@ -57,36 +57,86 @@ DROP DATABASE <replaceable class="PARAMETER">name</replaceable> <para> <variablelist> + <varlistentry> - <term><computeroutput> -DROP DATABASE - </computeroutput></term> + <term><computeroutput>DROP DATABASE</computeroutput></term> <listitem> <para> This message is returned if the command is successful. </para> </listitem> </varlistentry> + <varlistentry> - <term><computeroutput> -WARN: destroydb: database "<replaceable class="parameter">name</replaceable>" does not exist. - </computeroutput></term> + <term><computeroutput>ERROR: user '<replaceable class="parameter">username</replaceable>' is not allowed to create/drop databases</computeroutput></term> + <listitem> + <para> + You must have the special CREATEDB privilege to drop databases. + See <xref linkend="SQL-CREATEUSER" endterm="SQL-CREATEUSER-title">. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: dropdb: cannot be executed on the template database</computeroutput></term> + <listitem> + <para> + The <literal>template1</literal> database cannot be removed. It's not in + your interest. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: dropdb: cannot be executed on an open database</computeroutput></term> + <listitem> + <para> + You cannot be connected to the the database your are about to remove. + Instead, you could connect to <literal>template1</literal> or any other + database and run this command again. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: dropdb: database '<replaceable class="parameter">name</replaceable>' does not exist</computeroutput></term> <listitem> <para> This message occurs if the specified database does not exist. </para> </listitem> </varlistentry> + <varlistentry> - <term><computeroutput> - ERROR: destroydb cannot be executed on an open database - </computeroutput></term> + <term><computeroutput>ERROR: dropdb: database '<replaceable class="parameter">name</replaceable>' is not owned by you</computeroutput></term> <listitem> <para> - This message occurs if the specified database does not exist. + You must be the owner of the database. Being the owner usually means that + you created it as well. </para> </listitem> </varlistentry> + + <varlistentry> + <term><computeroutput>ERROR: dropdb: May not be called in a transaction block.</computeroutput></term> + <listitem> + <para> + You must finish the transaction in progress before you can call this command. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><computeroutput>NOTICE: The database directory 'xxx' could not be removed.</computeroutput></term> + <listitem> + <para> + The database was dropped (unless other error messages came up), but the + directory where the data is stored could not be removed. You must delete + it manually. + </para> + </listitem> + </varlistentry> + </variablelist> </para> </refsect2> @@ -94,7 +144,7 @@ WARN: destroydb: database "<replaceable class="parameter">name</replaceable>" do <refsect1 id="R1-SQL-DROPDATABASE-1"> <refsect1info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect1info> <title> Description @@ -102,23 +152,23 @@ WARN: destroydb: database "<replaceable class="parameter">name</replaceable>" do <para> <command>DROP DATABASE</command> removes the catalog entries for an existing database and deletes the directory containing the data. - It can only be executed by the database administrator - (See the <command>CREATE DATABASE</command> command for details). + It can only be executed by the database owner (usually the user that created + it). </para> <refsect2 id="R2-SQL-DROPDATABASE-3"> <refsect2info> - <date>1998-04-15</date> + <date>1999-12-11</date> </refsect2info> <title> Notes - This query cannot be executed while connected to the target - database. Thus, it might be more convenient to use - - from the shell instead. + This command cannot be executed while connected to the target + database. Thus, it might be more convenient to use the shell + script , + which is a wrapper around this command, instead. diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index d061b15b78..f78a90ec0c 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -7,7 +7,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.46 1999/12/10 03:55:49 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.47 1999/12/12 05:15:10 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -43,6 +43,12 @@ createdb(char *dbname, char *dbpath, int encoding, CommandDest dest) char *lp, loc[MAXPGPATH]; + /* no single quotes in dbname */ + if (strchr(dbname, '\'') != NULL) + elog(ERROR, "Single quotes are not allowed in database names."); + if (dbpath && strchr(dbpath, '\'') != NULL) + elog(ERROR, "Single quotes are not allowed in database paths."); + /* * If this call returns, the database does not exist and we're allowed * to create databases. @@ -52,7 +58,7 @@ createdb(char *dbname, char *dbpath, int encoding, CommandDest dest) /* close virtual file descriptors so we can do system() calls */ closeAllVfds(); - /* Now create directory for this new database */ + /* Make directory name for this new database */ if ((dbpath != NULL) && (strcmp(dbpath, dbname) != 0)) { if (*(dbpath + strlen(dbpath) - 1) == SEP_CHAR) @@ -65,25 +71,45 @@ createdb(char *dbname, char *dbpath, int encoding, CommandDest dest) lp = ExpandDatabasePath(loc); if (lp == NULL) - elog(ERROR, "Unable to locate path '%s'" - "\n\tThis may be due to a missing environment variable" - " in the server", loc); + elog(ERROR, "The path '%s' is invalid.\n" + "This may be due to a missing environment variable" + " on the server.", loc); - if (mkdir(lp, S_IRWXU) != 0) - elog(ERROR, "Unable to create database directory '%s'", lp); + /* no single quotes in expanded path */ + if (strchr(lp, '\'') != NULL) + elog(ERROR, "Single quotes are not allowed in database paths."); + + /* don't call this in a transaction block */ + if (IsTransactionBlock()) + elog(ERROR, "createdb: May not be called in a transaction block."); + else + BeginTransactionBlock(); + + snprintf(buf, sizeof(buf), + "INSERT INTO pg_database (datname, datdba, encoding, datpath)" + " VALUES ('%s', '%d', '%d', '%s')", dbname, user_id, encoding, loc); + + pg_exec_query_dest(buf, dest, false); + + if (mkdir(lp, S_IRWXU) != 0) { + UserAbortTransactionBlock(); + elog(ERROR, "Unable to create database directory '%s'.", lp); + } snprintf(buf, sizeof(buf), "%s %s%cbase%ctemplate1%c* '%s'", COPY_CMD, DataDir, SEP_CHAR, SEP_CHAR, SEP_CHAR, lp); - system(buf); + if (system(buf) != 0) { + rmdir(lp); + UserAbortTransactionBlock(); + elog(ERROR, "Could not initialize database directory."); + } - snprintf(buf, sizeof(buf), - "insert into pg_database (datname, datdba, encoding, datpath)" - " values ('%s', '%d', '%d', '%s');", dbname, user_id, encoding, - loc); - - pg_exec_query_dest(buf, dest, false); + if (IsTransactionBlock()) + EndTransactionBlock(); } + + void dropdb(char *dbname, CommandDest dest) { @@ -97,6 +123,10 @@ dropdb(char *dbname, CommandDest dest) ScanKeyData key; HeapTuple tup; + /* no single quotes in dbname */ + if (strchr(dbname, '\'') != NULL) + elog(ERROR, "Single quotes are not allowed in database names."); + /* * If this call returns, the database exists and we're allowed to * remove it. @@ -109,13 +139,19 @@ dropdb(char *dbname, CommandDest dest) path = ExpandDatabasePath(dbpath); if (path == NULL) - elog(ERROR, "Unable to locate path '%s'" - "\n\tThis may be due to a missing environment variable" - " in the server", dbpath); + elog(ERROR, "The path '%s' is invalid.\n" + "This may be due to a missing environment variable" + " on the server.", path); /* stop the vacuum daemon (dead code...) */ stop_vacuum(dbpath, dbname); + /* don't call this in a transaction block */ + if (IsTransactionBlock()) + elog(ERROR, "dropdb: May not be called in a transaction block."); + else + BeginTransactionBlock(); + /* * Obtain exclusive lock on pg_database. We need this to ensure * that no new backend starts up in the target database while we @@ -130,9 +166,12 @@ dropdb(char *dbname, CommandDest dest) /* * Check for active backends in the target database. */ - if (DatabaseHasActiveBackends(db_id)) - elog(ERROR, "Database '%s' has running backends, can't destroy it", + if (DatabaseHasActiveBackends(db_id)) { + heap_close(pgdbrel, AccessExclusiveLock); + UserAbortTransactionBlock(); + elog(ERROR, "Database '%s' has running backends, can't drop it.", dbname); + } /* * Find the database's tuple by OID (should be unique, we trust). @@ -146,6 +185,7 @@ dropdb(char *dbname, CommandDest dest) if (!HeapTupleIsValid(tup)) { heap_close(pgdbrel, AccessExclusiveLock); + UserAbortTransactionBlock(); elog(ERROR, "Database '%s', OID %u, not found in pg_database", dbname, db_id); } @@ -179,10 +219,16 @@ dropdb(char *dbname, CommandDest dest) /* * Remove the database's subdirectory and everything in it. */ - snprintf(buf, sizeof(buf), "rm -r '%s'", path); - system(buf); + snprintf(buf, sizeof(buf), "rm -rf '%s'", path); + if (system(buf)!=0) + elog(NOTICE, "The database directory '%s' could not be removed.", path); + + if (IsTransactionBlock()) + EndTransactionBlock(); } + + static HeapTuple get_pg_dbtup(char *command, char *dbname, Relation dbrel) { @@ -252,7 +298,7 @@ check_permissions(char *command, /* Check to make sure user has permission to use createdb */ if (!use_createdb) { - elog(ERROR, "user '%s' is not allowed to create/destroy databases", + elog(ERROR, "user '%s' is not allowed to create/drop databases", userName); }