checkfiles.c 5.3 KB
Newer Older
1 2 3
/*-------------------------------------------------------------------------
 *
 *	checkfiles.c
4
 *	  check for stale relation files during crash recovery
5 6 7 8 9 10 11 12 13 14 15 16
 *
 *	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.
 *
 *	This adds a 17% increase in startup cost for 100 empty databases.  bjm
 *	One optimization would be to create a 'dirty' file on a postmaster recovery
 *	and remove the dirty flag only when a clean startup detects no unreferenced
 *	files, and use the 'dirty' flag to determine if we should run this on
 *	a clean startup.
 *
17
 * $PostgreSQL: pgsql/src/backend/utils/init/checkfiles.c,v 1.2 2005/05/05 22:18:27 tgl Exp $
18 19 20 21 22
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

23 24 25 26 27 28
#include "access/heapam.h"
#include "access/relscan.h"
#include "access/skey.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "miscadmin.h"
29 30 31 32 33
#include "storage/fd.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/resowner.h"

34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
static void CheckStaleRelFilesFrom(Oid tablespaceoid, Oid dboid);
static void CheckStaleRelFilesFromTablespace(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.
 */
void
CheckStaleRelFiles(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')
			CheckStaleRelFilesFromTablespace(tablespaceoid);
	}
	FreeDir(dirdesc);
	pfree(path);

	CheckStaleRelFilesFromTablespace(DEFAULTTABLESPACE_OID);
}

/* Scan a specific tablespace for stale relations */
static void
CheckStaleRelFilesFromTablespace(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')
			CheckStaleRelFilesFrom(tablespaceoid, dboid);
	}
	FreeDir(dirdesc);
	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
CheckStaleRelFilesFrom(Oid tablespaceoid, Oid dboid)
{
	DIR		   *dirdesc;
	struct dirent *de;
	HASHCTL		hashctl;
	HTAB	   *relfilenodeHash;
	RelFileNode rnode;
	char	   *path;

	/*
	 * 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);
131 132
	hashctl.entrysize = sizeof(Oid);
	hashctl.hash = tag_hash;
133
	relfilenodeHash = hash_create("relfilenodeHash", 100, &hashctl,
134
								  HASH_FUNCTION | HASH_ELEM);
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

	/* Read all relfilenodes from pg_class into the hash table */
	{
		ResourceOwner owner,
					oldowner;
		Relation	rel;
		HeapScanDesc scan;
		HeapTuple	tuple;

		/* Need a resowner to keep the heapam and buffer code happy */
		owner = ResourceOwnerCreate(NULL, "CheckStaleRelFiles");
		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(LOG,
						(errcode_for_file_access(),
196
						 errmsg("table or index file \"%s\" is stale and can safely be removed",
197 198 199 200 201 202 203 204 205
								filepath)));
				pfree(filepath);
			}
		}
	}
	FreeDir(dirdesc);
	pfree(path);
	hash_destroy(relfilenodeHash);
}