Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * reinit.c
4 : : * Reinitialization of unlogged relations
5 : : *
6 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : * IDENTIFICATION
10 : : * src/backend/storage/file/reinit.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : #include "postgres.h"
16 : :
17 : : #include <unistd.h>
18 : :
19 : : #include "common/relpath.h"
20 : : #include "postmaster/startup.h"
21 : : #include "storage/copydir.h"
22 : : #include "storage/fd.h"
23 : : #include "storage/reinit.h"
24 : : #include "utils/hsearch.h"
25 : : #include "utils/memutils.h"
26 : :
27 : : static void ResetUnloggedRelationsInTablespaceDir(const char *tsdirname,
28 : : int op);
29 : : static void ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname,
30 : : int op);
31 : :
32 : : typedef struct
33 : : {
34 : : RelFileNumber relnumber; /* hash key */
35 : : } unlogged_relation_entry;
36 : :
37 : : /*
38 : : * Reset unlogged relations from before the last restart.
39 : : *
40 : : * If op includes UNLOGGED_RELATION_CLEANUP, we remove all forks of any
41 : : * relation with an "init" fork, except for the "init" fork itself.
42 : : *
43 : : * If op includes UNLOGGED_RELATION_INIT, we copy the "init" fork to the main
44 : : * fork.
45 : : */
46 : : void
4855 rhaas@postgresql.org 47 :CBC 390 : ResetUnloggedRelations(int op)
48 : : {
49 : : char temp_path[MAXPGPATH + 10 + sizeof(TABLESPACE_VERSION_DIRECTORY)];
50 : : DIR *spc_dir;
51 : : struct dirent *spc_de;
52 : : MemoryContext tmpctx,
53 : : oldctx;
54 : :
55 : : /* Log it. */
4653 peter_e@gmx.net 56 [ + + ]: 390 : elog(DEBUG1, "resetting unlogged relations: cleanup %d init %d",
57 : : (op & UNLOGGED_RELATION_CLEANUP) != 0,
58 : : (op & UNLOGGED_RELATION_INIT) != 0);
59 : :
60 : : /*
61 : : * Just to be sure we don't leak any memory, let's create a temporary
62 : : * memory context for this operation.
63 : : */
4855 rhaas@postgresql.org 64 : 390 : tmpctx = AllocSetContextCreate(CurrentMemoryContext,
65 : : "ResetUnloggedRelations",
66 : : ALLOCSET_DEFAULT_SIZES);
67 : 390 : oldctx = MemoryContextSwitchTo(tmpctx);
68 : :
69 : : /* Prepare to report progress resetting unlogged relations. */
902 70 : 390 : begin_startup_progress_phase();
71 : :
72 : : /*
73 : : * First process unlogged files in pg_default ($PGDATA/base)
74 : : */
4855 75 : 390 : ResetUnloggedRelationsInTablespaceDir("base", op);
76 : :
77 : : /*
78 : : * Cycle through directories for all non-default tablespaces.
79 : : */
80 : 390 : spc_dir = AllocateDir("pg_tblspc");
81 : :
82 [ + + ]: 1271 : while ((spc_de = ReadDir(spc_dir, "pg_tblspc")) != NULL)
83 : : {
84 [ + + ]: 881 : if (strcmp(spc_de->d_name, ".") == 0 ||
85 [ + + ]: 491 : strcmp(spc_de->d_name, "..") == 0)
86 : 780 : continue;
87 : :
88 : 101 : snprintf(temp_path, sizeof(temp_path), "pg_tblspc/%s/%s",
4753 bruce@momjian.us 89 : 101 : spc_de->d_name, TABLESPACE_VERSION_DIRECTORY);
4855 rhaas@postgresql.org 90 : 101 : ResetUnloggedRelationsInTablespaceDir(temp_path, op);
91 : : }
92 : :
93 : 390 : FreeDir(spc_dir);
94 : :
95 : : /*
96 : : * Restore memory context.
97 : : */
98 : 390 : MemoryContextSwitchTo(oldctx);
99 : 390 : MemoryContextDelete(tmpctx);
100 : 390 : }
101 : :
102 : : /*
103 : : * Process one tablespace directory for ResetUnloggedRelations
104 : : */
105 : : static void
106 : 491 : ResetUnloggedRelationsInTablespaceDir(const char *tsdirname, int op)
107 : : {
108 : : DIR *ts_dir;
109 : : struct dirent *de;
110 : : char dbspace_path[MAXPGPATH * 2];
111 : :
112 : 491 : ts_dir = AllocateDir(tsdirname);
113 : :
114 : : /*
115 : : * If we get ENOENT on a tablespace directory, log it and return. This
116 : : * can happen if a previous DROP TABLESPACE crashed between removing the
117 : : * tablespace directory and removing the symlink in pg_tblspc. We don't
118 : : * really want to prevent database startup in that scenario, so let it
119 : : * pass instead. Any other type of error will be reported by ReadDir
120 : : * (causing a startup failure).
121 : : */
2323 tgl@sss.pgh.pa.us 122 [ - + - - ]: 491 : if (ts_dir == NULL && errno == ENOENT)
123 : : {
2323 tgl@sss.pgh.pa.us 124 [ # # ]:UBC 0 : ereport(LOG,
125 : : (errcode_for_file_access(),
126 : : errmsg("could not open directory \"%s\": %m",
127 : : tsdirname)));
4855 rhaas@postgresql.org 128 : 0 : return;
129 : : }
130 : :
4855 rhaas@postgresql.org 131 [ + + ]:CBC 2878 : while ((de = ReadDir(ts_dir, tsdirname)) != NULL)
132 : : {
133 : : /*
134 : : * We're only interested in the per-database directories, which have
135 : : * numeric names. Note that this code will also (properly) ignore "."
136 : : * and "..".
137 : : */
2323 tgl@sss.pgh.pa.us 138 [ + + ]: 2387 : if (strspn(de->d_name, "0123456789") != strlen(de->d_name))
4855 rhaas@postgresql.org 139 : 1045 : continue;
140 : :
141 : 1342 : snprintf(dbspace_path, sizeof(dbspace_path), "%s/%s",
142 : 1342 : tsdirname, de->d_name);
143 : :
902 144 [ + + ]: 1342 : if (op & UNLOGGED_RELATION_INIT)
145 [ - + - - ]: 507 : ereport_startup_progress("resetting unlogged relations (init), elapsed time: %ld.%02d s, current path: %s",
146 : : dbspace_path);
147 [ + - ]: 835 : else if (op & UNLOGGED_RELATION_CLEANUP)
148 [ - + - - ]: 835 : ereport_startup_progress("resetting unlogged relations (cleanup), elapsed time: %ld.%02d s, current path: %s",
149 : : dbspace_path);
150 : :
4855 151 : 1342 : ResetUnloggedRelationsInDbspaceDir(dbspace_path, op);
152 : : }
153 : :
154 : 491 : FreeDir(ts_dir);
155 : : }
156 : :
157 : : /*
158 : : * Process one per-dbspace directory for ResetUnloggedRelations
159 : : */
160 : : static void
161 : 1342 : ResetUnloggedRelationsInDbspaceDir(const char *dbspacedirname, int op)
162 : : {
163 : : DIR *dbspace_dir;
164 : : struct dirent *de;
165 : : char rm_path[MAXPGPATH * 2];
166 : :
167 : : /* Caller must specify at least one operation. */
168 [ - + ]: 1342 : Assert((op & (UNLOGGED_RELATION_CLEANUP | UNLOGGED_RELATION_INIT)) != 0);
169 : :
170 : : /*
171 : : * Cleanup is a two-pass operation. First, we go through and identify all
172 : : * the files with init forks. Then, we go through again and nuke
173 : : * everything with the same OID except the init fork.
174 : : */
175 [ + + ]: 1342 : if ((op & UNLOGGED_RELATION_CLEANUP) != 0)
176 : : {
177 : : HTAB *hash;
178 : : HASHCTL ctl;
179 : :
180 : : /*
181 : : * It's possible that someone could create a ton of unlogged relations
182 : : * in the same database & tablespace, so we'd better use a hash table
183 : : * rather than an array or linked list to keep track of which files
184 : : * need to be reset. Otherwise, this cleanup operation would be
185 : : * O(n^2).
186 : : */
564 187 : 835 : ctl.keysize = sizeof(Oid);
4855 188 : 835 : ctl.entrysize = sizeof(unlogged_relation_entry);
1216 tgl@sss.pgh.pa.us 189 : 835 : ctl.hcxt = CurrentMemoryContext;
564 rhaas@postgresql.org 190 : 835 : hash = hash_create("unlogged relation OIDs", 32, &ctl,
191 : : HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
192 : :
193 : : /* Scan the directory. */
2323 tgl@sss.pgh.pa.us 194 : 835 : dbspace_dir = AllocateDir(dbspacedirname);
4855 rhaas@postgresql.org 195 [ + + ]: 239298 : while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL)
196 : : {
197 : : ForkNumber forkNum;
198 : : unsigned segno;
199 : : unlogged_relation_entry ent;
200 : :
201 : : /* Skip anything that doesn't look like a relation data file. */
174 rhaas@postgresql.org 202 [ + + ]:GNC 238463 : if (!parse_filename_for_nontemp_relation(de->d_name,
203 : : &ent.relnumber,
204 : : &forkNum, &segno))
4855 rhaas@postgresql.org 205 :CBC 238456 : continue;
206 : :
207 : : /* Also skip it unless this is the init fork. */
208 [ + + ]: 235215 : if (forkNum != INIT_FORKNUM)
209 : 235208 : continue;
210 : :
211 : : /*
212 : : * Put the RelFileNumber into the hash table, if it isn't already.
213 : : */
1216 tgl@sss.pgh.pa.us 214 : 7 : (void) hash_search(hash, &ent, HASH_ENTER, NULL);
215 : : }
216 : :
217 : : /* Done with the first pass. */
4855 rhaas@postgresql.org 218 : 835 : FreeDir(dbspace_dir);
219 : :
220 : : /*
221 : : * If we didn't find any init forks, there's no point in continuing;
222 : : * we can bail out now.
223 : : */
224 [ + + ]: 835 : if (hash_get_num_entries(hash) == 0)
225 : : {
226 : 832 : hash_destroy(hash);
227 : 832 : return;
228 : : }
229 : :
230 : : /*
231 : : * Now, make a second pass and remove anything that matches.
232 : : */
233 : 3 : dbspace_dir = AllocateDir(dbspacedirname);
234 [ + + ]: 622 : while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL)
235 : : {
236 : : ForkNumber forkNum;
237 : : unsigned segno;
238 : : unlogged_relation_entry ent;
239 : :
240 : : /* Skip anything that doesn't look like a relation data file. */
174 rhaas@postgresql.org 241 [ + + ]:GNC 619 : if (!parse_filename_for_nontemp_relation(de->d_name,
242 : : &ent.relnumber,
243 : : &forkNum, &segno))
4855 rhaas@postgresql.org 244 :CBC 17 : continue;
245 : :
246 : : /* We never remove the init fork. */
247 [ + + ]: 609 : if (forkNum == INIT_FORKNUM)
248 : 7 : continue;
249 : :
250 : : /*
251 : : * See whether the OID portion of the name shows up in the hash
252 : : * table. If so, nuke it!
253 : : */
1216 tgl@sss.pgh.pa.us 254 [ + + ]: 602 : if (hash_search(hash, &ent, HASH_FIND, NULL))
255 : : {
4855 rhaas@postgresql.org 256 : 7 : snprintf(rm_path, sizeof(rm_path), "%s/%s",
4753 bruce@momjian.us 257 : 7 : dbspacedirname, de->d_name);
2323 tgl@sss.pgh.pa.us 258 [ - + ]: 7 : if (unlink(rm_path) < 0)
2323 tgl@sss.pgh.pa.us 259 [ # # ]:UBC 0 : ereport(ERROR,
260 : : (errcode_for_file_access(),
261 : : errmsg("could not remove file \"%s\": %m",
262 : : rm_path)));
263 : : else
4855 rhaas@postgresql.org 264 [ - + ]:CBC 7 : elog(DEBUG2, "unlinked file \"%s\"", rm_path);
265 : : }
266 : : }
267 : :
268 : : /* Cleanup is complete. */
269 : 3 : FreeDir(dbspace_dir);
270 : 3 : hash_destroy(hash);
271 : : }
272 : :
273 : : /*
274 : : * Initialization happens after cleanup is complete: we copy each init
275 : : * fork file to the corresponding main fork file. Note that if we are
276 : : * asked to do both cleanup and init, we may never get here: if the
277 : : * cleanup code determines that there are no init forks in this dbspace,
278 : : * it will return before we get to this point.
279 : : */
280 [ + + ]: 510 : if ((op & UNLOGGED_RELATION_INIT) != 0)
281 : : {
282 : : /* Scan the directory. */
2323 tgl@sss.pgh.pa.us 283 : 507 : dbspace_dir = AllocateDir(dbspacedirname);
4855 rhaas@postgresql.org 284 [ + + ]: 141802 : while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL)
285 : : {
286 : : ForkNumber forkNum;
287 : : RelFileNumber relNumber;
288 : : unsigned segno;
289 : : char srcpath[MAXPGPATH * 2];
290 : : char dstpath[MAXPGPATH];
291 : :
292 : : /* Skip anything that doesn't look like a relation data file. */
174 rhaas@postgresql.org 293 [ + + ]:GNC 141295 : if (!parse_filename_for_nontemp_relation(de->d_name, &relNumber,
294 : : &forkNum, &segno))
4855 rhaas@postgresql.org 295 :CBC 141288 : continue;
296 : :
297 : : /* Also skip it unless this is the init fork. */
298 [ + + ]: 139328 : if (forkNum != INIT_FORKNUM)
299 : 139321 : continue;
300 : :
301 : : /* Construct source pathname. */
302 : 7 : snprintf(srcpath, sizeof(srcpath), "%s/%s",
303 : 7 : dbspacedirname, de->d_name);
304 : :
305 : : /* Construct destination pathname. */
174 rhaas@postgresql.org 306 [ + - ]:GNC 7 : if (segno == 0)
307 : 7 : snprintf(dstpath, sizeof(dstpath), "%s/%u",
308 : : dbspacedirname, relNumber);
309 : : else
174 rhaas@postgresql.org 310 :UNC 0 : snprintf(dstpath, sizeof(dstpath), "%s/%u.%u",
311 : : dbspacedirname, relNumber, segno);
312 : :
313 : : /* OK, we're ready to perform the actual copy. */
4855 rhaas@postgresql.org 314 [ - + ]:CBC 7 : elog(DEBUG2, "copying %s to %s", srcpath, dstpath);
315 : 7 : copy_file(srcpath, dstpath);
316 : : }
317 : :
318 : 507 : FreeDir(dbspace_dir);
319 : :
320 : : /*
321 : : * copy_file() above has already called pg_flush_data() on the files
322 : : * it created. Now we need to fsync those files, because a checkpoint
323 : : * won't do it for us while we're in recovery. We do this in a
324 : : * separate pass to allow the kernel to perform all the flushes
325 : : * (especially the metadata ones) at once.
326 : : */
3439 andres@anarazel.de 327 : 507 : dbspace_dir = AllocateDir(dbspacedirname);
328 [ + + ]: 141809 : while ((de = ReadDir(dbspace_dir, dbspacedirname)) != NULL)
329 : : {
330 : : RelFileNumber relNumber;
331 : : ForkNumber forkNum;
332 : : unsigned segno;
333 : : char mainpath[MAXPGPATH];
334 : :
335 : : /* Skip anything that doesn't look like a relation data file. */
174 rhaas@postgresql.org 336 [ + + ]:GNC 141302 : if (!parse_filename_for_nontemp_relation(de->d_name, &relNumber,
337 : : &forkNum, &segno))
3439 andres@anarazel.de 338 :CBC 141295 : continue;
339 : :
340 : : /* Also skip it unless this is the init fork. */
341 [ + + ]: 139335 : if (forkNum != INIT_FORKNUM)
342 : 139328 : continue;
343 : :
344 : : /* Construct main fork pathname. */
174 rhaas@postgresql.org 345 [ + - ]:GNC 7 : if (segno == 0)
346 : 7 : snprintf(mainpath, sizeof(mainpath), "%s/%u",
347 : : dbspacedirname, relNumber);
348 : : else
174 rhaas@postgresql.org 349 :UNC 0 : snprintf(mainpath, sizeof(mainpath), "%s/%u.%u",
350 : : dbspacedirname, relNumber, segno);
351 : :
3439 andres@anarazel.de 352 :CBC 7 : fsync_fname(mainpath, false);
353 : : }
354 : :
355 : 507 : FreeDir(dbspace_dir);
356 : :
357 : : /*
358 : : * Lastly, fsync the database directory itself, ensuring the
359 : : * filesystem remembers the file creations and deletions we've done.
360 : : * We don't bother with this during a call that does only
361 : : * UNLOGGED_RELATION_CLEANUP, because if recovery crashes before we
362 : : * get to doing UNLOGGED_RELATION_INIT, we'll redo the cleanup step
363 : : * too at the next startup attempt.
364 : : */
2958 365 : 507 : fsync_fname(dbspacedirname, true);
366 : : }
367 : : }
368 : :
369 : : /*
370 : : * Basic parsing of putative relation filenames.
371 : : *
372 : : * This function returns true if the file appears to be in the correct format
373 : : * for a non-temporary relation and false otherwise.
374 : : *
375 : : * If it returns true, it sets *relnumber, *fork, and *segno to the values
376 : : * extracted from the filename. If it returns false, these values are set to
377 : : * InvalidRelFileNumber, InvalidForkNumber, and 0, respectively.
378 : : */
379 : : bool
174 rhaas@postgresql.org 380 :GNC 811998 : parse_filename_for_nontemp_relation(const char *name, RelFileNumber *relnumber,
381 : : ForkNumber *fork, unsigned *segno)
382 : : {
383 : : unsigned long n,
384 : : s;
385 : : ForkNumber f;
386 : : char *endp;
387 : :
388 : 811998 : *relnumber = InvalidRelFileNumber;
389 : 811998 : *fork = InvalidForkNumber;
390 : 811998 : *segno = 0;
391 : :
392 : : /*
393 : : * Relation filenames should begin with a digit that is not a zero. By
394 : : * rejecting cases involving leading zeroes, the caller can assume that
395 : : * there's only one possible string of characters that could have produced
396 : : * any given value for *relnumber.
397 : : *
398 : : * (To be clear, we don't expect files with names like 0017.3 to exist at
399 : : * all -- but if 0017.3 does exist, it's a non-relation file, not part of
400 : : * the main fork for relfilenode 17.)
401 : : */
402 [ + + + + ]: 811998 : if (name[0] < '1' || name[0] > '9')
174 rhaas@postgresql.org 403 :CBC 9646 : return false;
404 : :
405 : : /*
406 : : * Parse the leading digit string. If the value is out of range, we
407 : : * conclude that this isn't a relation file at all.
408 : : */
174 rhaas@postgresql.org 409 :GNC 802352 : errno = 0;
410 : 802352 : n = strtoul(name, &endp, 10);
411 [ + - + - : 802352 : if (errno || name == endp || n <= 0 || n > PG_UINT32_MAX)
+ - - + ]
4855 rhaas@postgresql.org 412 :UNC 0 : return false;
174 rhaas@postgresql.org 413 :GNC 802352 : name = endp;
414 : :
415 : : /* Check for a fork name. */
416 [ + + ]: 802352 : if (*name != '_')
417 : 603851 : f = MAIN_FORKNUM;
418 : : else
419 : : {
420 : : int forkchar;
421 : :
422 : 198501 : forkchar = forkname_chars(name + 1, &f);
4855 rhaas@postgresql.org 423 [ - + ]:CBC 198501 : if (forkchar <= 0)
4855 rhaas@postgresql.org 424 :UBC 0 : return false;
174 rhaas@postgresql.org 425 :GNC 198501 : name += forkchar + 1;
426 : : }
427 : :
428 : : /* Check for a segment number. */
429 [ + - ]: 802352 : if (*name != '.')
430 : 802352 : s = 0;
431 : : else
432 : : {
433 : : /* Reject leading zeroes, just like we do for RelFileNumber. */
117 rhaas@postgresql.org 434 [ # # # # ]:UNC 0 : if (name[1] < '1' || name[1] > '9')
174 rhaas@postgresql.org 435 :UBC 0 : return false;
436 : :
174 rhaas@postgresql.org 437 :UNC 0 : errno = 0;
438 : 0 : s = strtoul(name + 1, &endp, 10);
439 [ # # # # : 0 : if (errno || name + 1 == endp || s <= 0 || s > PG_UINT32_MAX)
# # # # ]
4855 440 : 0 : return false;
174 441 : 0 : name = endp;
442 : : }
443 : :
444 : : /* Now we should be at the end. */
174 rhaas@postgresql.org 445 [ - + ]:GNC 802352 : if (*name != '\0')
4855 rhaas@postgresql.org 446 :UBC 0 : return false;
447 : :
448 : : /* Set out parameters and return. */
174 rhaas@postgresql.org 449 :GNC 802352 : *relnumber = (RelFileNumber) n;
450 : 802352 : *fork = f;
451 : 802352 : *segno = (unsigned) s;
4855 rhaas@postgresql.org 452 :CBC 802352 : return true;
453 : : }
|