aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/init/checkfiles.c
blob: 8ed5110d73c6da2bfb908053340ecd44023f4a0d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
131
132
133
134
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
196
197
198
199
200
201
202
203
204
205
/*-------------------------------------------------------------------------
 *
 *	checkfiles.c
 *	  check for stale relation files during 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.
 *
 *	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.
 *
 * $PostgreSQL: pgsql/src/backend/utils/init/checkfiles.c,v 1.2 2005/05/05 22:18:27 tgl Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/heapam.h"
#include "access/relscan.h"
#include "access/skey.h"
#include "catalog/catalog.h"
#include "catalog/pg_tablespace.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/flatfiles.h"
#include "utils/fmgroids.h"
#include "utils/resowner.h"


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);
	hashctl.entrysize = sizeof(Oid);
	hashctl.hash = tag_hash;
	relfilenodeHash = hash_create("relfilenodeHash", 100, &hashctl,
								  HASH_FUNCTION | HASH_ELEM);

	/* 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(),
						 errmsg("table or index file \"%s\" is stale and can safely be removed",
								filepath)));
				pfree(filepath);
			}
		}
	}
	FreeDir(dirdesc);
	pfree(path);
	hash_destroy(relfilenodeHash);
}