Index: doc/src/sgml/maintenance.sgml =================================================================== RCS file: /projects/cvsroot/pgsql/doc/src/sgml/maintenance.sgml,v retrieving revision 1.41 diff -c -r1.41 maintenance.sgml *** doc/src/sgml/maintenance.sgml 20 Feb 2005 02:21:26 -0000 1.41 --- doc/src/sgml/maintenance.sgml 28 Apr 2005 17:32:41 -0000 *************** *** 474,479 **** --- 474,496 ---- + + Cleanup after crash + + + stale file + + + + PostgreSQL recovers automatically after crash + using the write-ahead log (see ) and no manual + operations are normally needed. However, if there was a transaction running + when the crash occured that created or dropped a relation, the + transaction might have left a stale file in the data directory. If this + happens, you will get a notice in the log file stating which files can be + deleted. + + Log File Maintenance Index: src/backend/access/transam/xlog.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/access/transam/xlog.c,v retrieving revision 1.188 diff -c -r1.188 xlog.c *** src/backend/access/transam/xlog.c 23 Apr 2005 18:49:54 -0000 1.188 --- src/backend/access/transam/xlog.c 28 Apr 2005 17:32:41 -0000 *************** *** 42,47 **** --- 42,48 ---- #include "utils/builtins.h" #include "utils/guc.h" #include "utils/relcache.h" + #include "utils/cleanup.h" /* *************** *** 4520,4525 **** --- 4521,4528 ---- CreateCheckPoint(true, true); + CleanupStaleRelFiles(); + /* * Close down recovery environment */ Index: src/backend/catalog/catalog.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/catalog/catalog.c,v retrieving revision 1.59 diff -c -r1.59 catalog.c *** src/backend/catalog/catalog.c 14 Apr 2005 20:03:23 -0000 1.59 --- src/backend/catalog/catalog.c 28 Apr 2005 17:32:41 -0000 *************** *** 106,111 **** --- 106,144 ---- return path; } + /* + * GetTablespacePath - construct path to a tablespace symbolic link + * + * Result is a palloc'd string. + * + * XXX this must agree with relpath and GetDatabasePath! + */ + char * + GetTablespacePath(Oid spcNode) + { + int pathlen; + char *path; + + Assert(spcNode != GLOBALTABLESPACE_OID); + + if (spcNode == DEFAULTTABLESPACE_OID) + { + /* The default tablespace is {datadir}/base */ + pathlen = strlen(DataDir) + 5 + 1; + path = (char *) palloc(pathlen); + snprintf(path, pathlen, "%s/base", + DataDir); + } + else + { + /* All other tablespaces have symlinks in pg_tblspc */ + pathlen = strlen(DataDir) + 11 + OIDCHARS + 1; + path = (char *) palloc(pathlen); + snprintf(path, pathlen, "%s/pg_tblspc/%u", + DataDir, spcNode); + } + return path; + } /* * IsSystemRelation Index: src/backend/commands/tablespace.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/commands/tablespace.c,v retrieving revision 1.17 diff -c -r1.17 tablespace.c *** src/backend/commands/tablespace.c 14 Apr 2005 20:03:24 -0000 1.17 --- src/backend/commands/tablespace.c 28 Apr 2005 17:32:41 -0000 *************** *** 341,348 **** /* * All seems well, create the symlink */ ! linkloc = (char *) palloc(strlen(DataDir) + 11 + 10 + 1); ! sprintf(linkloc, "%s/pg_tblspc/%u", DataDir, tablespaceoid); if (symlink(location, linkloc) < 0) ereport(ERROR, --- 341,347 ---- /* * All seems well, create the symlink */ ! linkloc = GetTablespacePath(tablespaceoid); if (symlink(location, linkloc) < 0) ereport(ERROR, *************** *** 495,502 **** char *subfile; struct stat st; ! location = (char *) palloc(strlen(DataDir) + 11 + 10 + 1); ! sprintf(location, "%s/pg_tblspc/%u", DataDir, tablespaceoid); /* * Check if the tablespace still contains any files. We try to rmdir --- 494,500 ---- char *subfile; struct stat st; ! location = GetTablespacePath(tablespaceoid); /* * Check if the tablespace still contains any files. We try to rmdir *************** *** 1036,1043 **** set_short_version(location); /* Create the symlink if not already present */ ! linkloc = (char *) palloc(strlen(DataDir) + 11 + 10 + 1); ! sprintf(linkloc, "%s/pg_tblspc/%u", DataDir, xlrec->ts_id); if (symlink(location, linkloc) < 0) { --- 1034,1040 ---- set_short_version(location); /* Create the symlink if not already present */ ! linkloc = GetTablespacePath(xlrec->ts_id); if (symlink(location, linkloc) < 0) { Index: src/backend/utils/adt/misc.c =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/adt/misc.c,v retrieving revision 1.40 diff -c -r1.40 misc.c *** src/backend/utils/adt/misc.c 31 Dec 2004 22:01:22 -0000 1.40 --- src/backend/utils/adt/misc.c 28 Apr 2005 17:32:42 -0000 *************** *** 26,31 **** --- 26,32 ---- #include "funcapi.h" #include "catalog/pg_type.h" #include "catalog/pg_tablespace.h" + #include "catalog/catalog.h" #define atooid(x) ((Oid) strtoul((x), NULL, 10)) *************** *** 144,154 **** fctx = palloc(sizeof(ts_db_fctx)); - /* - * size = path length + tablespace dirname length + 2 dir sep - * chars + oid + terminator - */ - fctx->location = (char *) palloc(strlen(DataDir) + 11 + 10 + 1); if (tablespaceOid == GLOBALTABLESPACE_OID) { fctx->dirdesc = NULL; --- 145,150 ---- *************** *** 157,168 **** } else { ! if (tablespaceOid == DEFAULTTABLESPACE_OID) ! sprintf(fctx->location, "%s/base", DataDir); ! else ! sprintf(fctx->location, "%s/pg_tblspc/%u", DataDir, ! tablespaceOid); ! fctx->dirdesc = AllocateDir(fctx->location); if (!fctx->dirdesc) --- 153,159 ---- } else { ! fctx->location = GetTablespacePath(tablespaceOid); fctx->dirdesc = AllocateDir(fctx->location); if (!fctx->dirdesc) Index: src/backend/utils/init/Makefile =================================================================== RCS file: /projects/cvsroot/pgsql/src/backend/utils/init/Makefile,v retrieving revision 1.18 diff -c -r1.18 Makefile *** src/backend/utils/init/Makefile 20 Feb 2005 02:22:00 -0000 1.18 --- src/backend/utils/init/Makefile 28 Apr 2005 17:32:42 -0000 *************** *** 12,18 **** top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = flatfiles.o globals.o miscinit.o postinit.o all: SUBSYS.o --- 12,18 ---- top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = flatfiles.o globals.o miscinit.o postinit.o cleanup.o all: SUBSYS.o Index: src/backend/utils/init/cleanup.c =================================================================== RCS file: src/backend/utils/init/cleanup.c diff -N src/backend/utils/init/cleanup.c *** /dev/null 1 Jan 1970 00:00:00 -0000 --- src/backend/utils/init/cleanup.c 28 Apr 2005 17:32:42 -0000 *************** *** 0 **** --- 1,209 ---- + /*------------------------------------------------------------------------- + * + * cleanup.c + * support to clean up stale relation files on crash recovery + * + * If a backend crashes while in a transaction that has created or + * deleted a relfilenode, a stale file can be left over in the data + * directory. This file contains routines to clean up those stale + * files on recovery. + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "storage/fd.h" + + #include "utils/cleanup.h" + #include "miscadmin.h" + #include "catalog/pg_tablespace.h" + #include "catalog/catalog.h" + #include "access/skey.h" + #include "utils/fmgroids.h" + #include "access/relscan.h" + #include "access/heapam.h" + #include "utils/resowner.h" + + static void CleanupStaleRelFilesFrom(Oid tablespaceoid, Oid dboid); + static void CleanupStaleRelFilesFromTablespace(Oid tablespaceoid); + + /* Like AllocateDir, but ereports on failure */ + static DIR * + AllocateDirChecked(char *path) + { + DIR *dirdesc = AllocateDir(path); + if (dirdesc == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + path))); + return dirdesc; + } + + /* + * Scan through all tablespaces for relations left over + * by aborted transactions. + * + * For example, if a transaction issues + * BEGIN; CREATE TABLE foobar (); + * and then the backend crashes, the file is left in the + * tablespace until CleanupStaleRelFiles deletes it. + */ + void + CleanupStaleRelFiles(void) + { + DIR *dirdesc; + struct dirent *de; + char *path; + int pathlen; + + pathlen = strlen(DataDir) + 11 + 1; + path = (char *) palloc(pathlen); + snprintf(path, pathlen, "%s/pg_tblspc/", DataDir); + dirdesc = AllocateDirChecked(path); + while ((de = readdir(dirdesc)) != NULL) + { + char *invalid; + Oid tablespaceoid; + + /* Check that the directory name looks like valid tablespace link. */ + tablespaceoid = (Oid) strtol(de->d_name, &invalid, 10); + if(invalid[0] == '\0') + CleanupStaleRelFilesFromTablespace(tablespaceoid); + } + pfree(path); + + CleanupStaleRelFilesFromTablespace(DEFAULTTABLESPACE_OID); + } + + /* Scan a specific tablespace for stale relations */ + static void + CleanupStaleRelFilesFromTablespace(Oid tablespaceoid) + { + DIR *dirdesc; + struct dirent *de; + char *path; + + path = GetTablespacePath(tablespaceoid); + + dirdesc = AllocateDirChecked(path); + while ((de = readdir(dirdesc)) != NULL) + { + char *invalid; + Oid dboid; + + dboid = (Oid) strtol(de->d_name, &invalid, 10); + if(invalid[0] == '\0') + CleanupStaleRelFilesFrom(tablespaceoid, dboid); + } + pfree(path); + } + + /* Scan a specific database in a specific tablespace for stale relations. + * + * First, pg_class for the database is opened, and the relfilenodes of all + * relations mentioned there are stored in a hash table. + * + * Then the directory is scanned. Every file in the directory that's not + * found in pg_class (the hash table) is logged. + */ + static void + CleanupStaleRelFilesFrom(Oid tablespaceoid, Oid dboid) + { + DIR *dirdesc; + struct dirent *de; + HASHCTL hashctl; + HTAB *relfilenodeHash; + MemoryContext mcxt; + RelFileNode rnode; + char *path; + + /* We create a private memory context so that we can easily deallocate + * the hash table and its contents + */ + mcxt = AllocSetContextCreate(TopMemoryContext, "CleanupStaleRelFiles", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + hashctl.hash = tag_hash; + /* The entry contents is not used for anything, we just check + * if an oid is in the hash table or not. */ + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = 1; + hashctl.hcxt = mcxt; + relfilenodeHash = hash_create("relfilenodeHash", 100, &hashctl, + HASH_FUNCTION + | HASH_ELEM | HASH_CONTEXT); + + /* Read all relfilenodes from pg_class into the hash table */ + { + ResourceOwner owner; + ResourceOwner oldowner; + Relation rel; + HeapScanDesc scan; + HeapTuple tuple; + + /* Need a resowner to keep the heapam and buffer code happy */ + owner = ResourceOwnerCreate(NULL, "CleanupStaleRelFiles"); + oldowner = CurrentResourceOwner; + CurrentResourceOwner = owner; + + rnode.spcNode = tablespaceoid; + rnode.dbNode = dboid; + rnode.relNode = RelationRelationId; + rel = XLogOpenRelation(true, 0 , rnode); + + scan = heap_beginscan(rel, SnapshotNow, 0, NULL); + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Form_pg_class classform = (Form_pg_class) GETSTRUCT(tuple); + + hash_search(relfilenodeHash, &classform->relfilenode, + HASH_ENTER, NULL); + } + heap_endscan(scan); + + XLogCloseRelation(rnode); + CurrentResourceOwner = oldowner; + ResourceOwnerDelete(owner); + } + + /* Scan the directory */ + path = GetDatabasePath(dboid, tablespaceoid); + + dirdesc = AllocateDirChecked(path); + while ((de = readdir(dirdesc)) != NULL) + { + char *invalid; + Oid relfilenode; + + relfilenode = strtol(de->d_name, &invalid, 10); + if(invalid[0] == '\0') + { + /* Filename was a valid number, check if pg_class knows + about it */ + if(hash_search(relfilenodeHash, &relfilenode, + HASH_FIND, NULL) == NULL) + { + char *filepath; + rnode.spcNode = tablespaceoid; + rnode.dbNode = dboid; + rnode.relNode = relfilenode; + + filepath = relpath(rnode); + + ereport(NOTICE, + (errcode_for_file_access(), + errmsg("The relation file \"%s\" is stale and can be safely deleted", + filepath))); + pfree(filepath); + } + } + } + pfree(path); + hash_destroy(relfilenodeHash); + MemoryContextDelete(mcxt); + } Index: src/include/catalog/catalog.h =================================================================== RCS file: /projects/cvsroot/pgsql/src/include/catalog/catalog.h,v retrieving revision 1.30 diff -c -r1.30 catalog.h *** src/include/catalog/catalog.h 31 Dec 2004 22:03:24 -0000 1.30 --- src/include/catalog/catalog.h 28 Apr 2005 17:32:43 -0000 *************** *** 19,24 **** --- 19,25 ---- extern char *relpath(RelFileNode rnode); extern char *GetDatabasePath(Oid dbNode, Oid spcNode); + extern char *GetTablespacePath(Oid spcNode); extern bool IsSystemRelation(Relation relation); extern bool IsToastRelation(Relation relation); Index: src/include/utils/cleanup.h =================================================================== RCS file: src/include/utils/cleanup.h diff -N src/include/utils/cleanup.h *** /dev/null 1 Jan 1970 00:00:00 -0000 --- src/include/utils/cleanup.h 28 Apr 2005 17:32:44 -0000 *************** *** 0 **** --- 1,15 ---- + /*------------------------------------------------------------------------- + * + * cleanup.h + * Routines for fsck-style cleanup on crash recovery + * + * $PostgreSQL$ + * + *------------------------------------------------------------------------- + */ + #ifndef CLEANUP_H + #define CLEANUP_H + + extern void CleanupStaleRelFiles(void); + + #endif /* CLEANUP_H */