Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * parsexlog.c
4 : : * Functions for reading Write-Ahead-Log
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *-------------------------------------------------------------------------
10 : : */
11 : :
12 : : #include "postgres_fe.h"
13 : :
14 : : #include <unistd.h>
15 : :
16 : : #include "access/rmgr.h"
17 : : #include "access/xact.h"
18 : : #include "access/xlog_internal.h"
19 : : #include "access/xlogreader.h"
20 : : #include "catalog/pg_control.h"
21 : : #include "catalog/storage_xlog.h"
22 : : #include "commands/dbcommands_xlog.h"
23 : : #include "fe_utils/archive.h"
24 : : #include "filemap.h"
25 : : #include "pg_rewind.h"
26 : :
27 : : /*
28 : : * RmgrNames is an array of the built-in resource manager names, to make error
29 : : * messages a bit nicer.
30 : : */
31 : : #define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup,mask,decode) \
32 : : name,
33 : :
34 : : static const char *const RmgrNames[RM_MAX_ID + 1] = {
35 : : #include "access/rmgrlist.h"
36 : : };
37 : :
38 : : #define RmgrName(rmid) (((rmid) <= RM_MAX_BUILTIN_ID) ? \
39 : : RmgrNames[rmid] : "custom")
40 : :
41 : : static void extractPageInfo(XLogReaderState *record);
42 : :
43 : : static int xlogreadfd = -1;
44 : : static XLogSegNo xlogreadsegno = 0;
45 : : static char xlogfpath[MAXPGPATH];
46 : :
47 : : typedef struct XLogPageReadPrivate
48 : : {
49 : : const char *restoreCommand;
50 : : int tliIndex;
51 : : } XLogPageReadPrivate;
52 : :
53 : : static int SimpleXLogPageRead(XLogReaderState *xlogreader,
54 : : XLogRecPtr targetPagePtr,
55 : : int reqLen, XLogRecPtr targetRecPtr, char *readBuf);
56 : :
57 : : /*
58 : : * Read WAL from the datadir/pg_wal, starting from 'startpoint' on timeline
59 : : * index 'tliIndex' in target timeline history, until 'endpoint'. Make note of
60 : : * the data blocks touched by the WAL records, and return them in a page map.
61 : : *
62 : : * 'endpoint' is the end of the last record to read. The record starting at
63 : : * 'endpoint' is the first one that is not read.
64 : : */
65 : : void
66 : 17 : extractPageMap(const char *datadir, XLogRecPtr startpoint, int tliIndex,
67 : : XLogRecPtr endpoint, const char *restoreCommand)
68 : : {
69 : : XLogRecord *record;
70 : : XLogReaderState *xlogreader;
71 : : char *errormsg;
72 : : XLogPageReadPrivate private;
73 : :
74 : 17 : private.tliIndex = tliIndex;
75 : 17 : private.restoreCommand = restoreCommand;
76 : 17 : xlogreader = XLogReaderAllocate(WalSegSz, datadir,
77 : 17 : XL_ROUTINE(.page_read = &SimpleXLogPageRead),
78 : : &private);
79 [ - + ]: 17 : if (xlogreader == NULL)
80 : 0 : pg_fatal("out of memory while allocating a WAL reading processor");
81 : :
82 : 17 : XLogBeginRead(xlogreader, startpoint);
83 : : do
84 : : {
85 : 86139 : record = XLogReadRecord(xlogreader, &errormsg);
86 : :
87 [ - + ]: 86139 : if (record == NULL)
88 : : {
89 : 0 : XLogRecPtr errptr = xlogreader->EndRecPtr;
90 : :
91 [ # # ]: 0 : if (errormsg)
92 : 0 : pg_fatal("could not read WAL record at %X/%08X: %s",
93 : : LSN_FORMAT_ARGS(errptr),
94 : : errormsg);
95 : : else
96 : 0 : pg_fatal("could not read WAL record at %X/%08X",
97 : : LSN_FORMAT_ARGS(errptr));
98 : : }
99 : :
100 : 86139 : extractPageInfo(xlogreader);
101 [ + + ]: 86139 : } while (xlogreader->EndRecPtr < endpoint);
102 : :
103 : : /*
104 : : * If 'endpoint' didn't point exactly at a record boundary, the caller
105 : : * messed up.
106 : : */
107 [ - + ]: 17 : if (xlogreader->EndRecPtr != endpoint)
108 : 0 : pg_fatal("end pointer %X/%08X is not a valid end point; expected %X/%08X",
109 : : LSN_FORMAT_ARGS(endpoint), LSN_FORMAT_ARGS(xlogreader->EndRecPtr));
110 : :
111 : 17 : XLogReaderFree(xlogreader);
112 [ + - ]: 17 : if (xlogreadfd != -1)
113 : : {
114 : 17 : close(xlogreadfd);
115 : 17 : xlogreadfd = -1;
116 : : }
117 : 17 : }
118 : :
119 : : /*
120 : : * Reads one WAL record. Returns the end position of the record, without
121 : : * doing anything with the record itself.
122 : : */
123 : : XLogRecPtr
124 : 17 : readOneRecord(const char *datadir, XLogRecPtr ptr, int tliIndex,
125 : : const char *restoreCommand)
126 : : {
127 : : XLogRecord *record;
128 : : XLogReaderState *xlogreader;
129 : : char *errormsg;
130 : : XLogPageReadPrivate private;
131 : : XLogRecPtr endptr;
132 : :
133 : 17 : private.tliIndex = tliIndex;
134 : 17 : private.restoreCommand = restoreCommand;
135 : 17 : xlogreader = XLogReaderAllocate(WalSegSz, datadir,
136 : 17 : XL_ROUTINE(.page_read = &SimpleXLogPageRead),
137 : : &private);
138 [ - + ]: 17 : if (xlogreader == NULL)
139 : 0 : pg_fatal("out of memory while allocating a WAL reading processor");
140 : :
141 : 17 : XLogBeginRead(xlogreader, ptr);
142 : 17 : record = XLogReadRecord(xlogreader, &errormsg);
143 [ - + ]: 17 : if (record == NULL)
144 : : {
145 [ # # ]: 0 : if (errormsg)
146 : 0 : pg_fatal("could not read WAL record at %X/%08X: %s",
147 : : LSN_FORMAT_ARGS(ptr), errormsg);
148 : : else
149 : 0 : pg_fatal("could not read WAL record at %X/%08X",
150 : : LSN_FORMAT_ARGS(ptr));
151 : : }
152 : 17 : endptr = xlogreader->EndRecPtr;
153 : :
154 : 17 : XLogReaderFree(xlogreader);
155 [ + - ]: 17 : if (xlogreadfd != -1)
156 : : {
157 : 17 : close(xlogreadfd);
158 : 17 : xlogreadfd = -1;
159 : : }
160 : :
161 : 17 : return endptr;
162 : : }
163 : :
164 : : /*
165 : : * Find the previous checkpoint preceding given WAL location.
166 : : */
167 : : void
168 : 17 : findLastCheckpoint(const char *datadir, XLogRecPtr forkptr, int tliIndex,
169 : : XLogRecPtr *lastchkptrec, TimeLineID *lastchkpttli,
170 : : XLogRecPtr *lastchkptredo, uint32 *lastchkptdatachecksums,
171 : : const char *restoreCommand)
172 : : {
173 : : /* Walk backwards, starting from the given record */
174 : : XLogRecord *record;
175 : : XLogRecPtr searchptr;
176 : : XLogReaderState *xlogreader;
177 : : char *errormsg;
178 : : XLogPageReadPrivate private;
179 : 17 : XLogSegNo current_segno = 0;
180 : 17 : TimeLineID current_tli = 0;
181 : :
182 : : /*
183 : : * The given fork pointer points to the end of the last common record,
184 : : * which is not necessarily the beginning of the next record, if the
185 : : * previous record happens to end at a page boundary. Skip over the page
186 : : * header in that case to find the next record.
187 : : */
188 [ - + ]: 17 : if (forkptr % XLOG_BLCKSZ == 0)
189 : : {
190 [ # # ]: 0 : if (XLogSegmentOffset(forkptr, WalSegSz) == 0)
191 : 0 : forkptr += SizeOfXLogLongPHD;
192 : : else
193 : 0 : forkptr += SizeOfXLogShortPHD;
194 : : }
195 : :
196 : 17 : private.tliIndex = tliIndex;
197 : 17 : private.restoreCommand = restoreCommand;
198 : 17 : xlogreader = XLogReaderAllocate(WalSegSz, datadir,
199 : 17 : XL_ROUTINE(.page_read = &SimpleXLogPageRead),
200 : : &private);
201 [ - + ]: 17 : if (xlogreader == NULL)
202 : 0 : pg_fatal("out of memory while allocating a WAL reading processor");
203 : :
204 : 17 : searchptr = forkptr;
205 : : for (;;)
206 : 2639 : {
207 : : uint8 info;
208 : :
209 : 2656 : XLogBeginRead(xlogreader, searchptr);
210 : 2656 : record = XLogReadRecord(xlogreader, &errormsg);
211 : :
212 [ - + ]: 2656 : if (record == NULL)
213 : : {
214 [ # # ]: 0 : if (errormsg)
215 : 0 : pg_fatal("could not find previous WAL record at %X/%08X: %s",
216 : : LSN_FORMAT_ARGS(searchptr),
217 : : errormsg);
218 : : else
219 : 0 : pg_fatal("could not find previous WAL record at %X/%08X",
220 : : LSN_FORMAT_ARGS(searchptr));
221 : : }
222 : :
223 : : /* Detect if a new WAL file has been opened */
224 [ + + ]: 2656 : if (xlogreader->seg.ws_tli != current_tli ||
225 [ + + ]: 2639 : xlogreader->seg.ws_segno != current_segno)
226 : : {
227 : : char xlogfname[MAXFNAMELEN];
228 : :
229 : 22 : snprintf(xlogfname, MAXFNAMELEN, XLOGDIR "/");
230 : :
231 : : /* update current values */
232 : 22 : current_tli = xlogreader->seg.ws_tli;
233 : 22 : current_segno = xlogreader->seg.ws_segno;
234 : :
235 : 22 : XLogFileName(xlogfname + sizeof(XLOGDIR),
236 : : current_tli, current_segno, WalSegSz);
237 : :
238 : : /* Track this filename as one to not remove */
239 : 22 : keepwal_add_entry(xlogfname);
240 : : }
241 : :
242 : : /*
243 : : * Check if it is a checkpoint record. This checkpoint record needs to
244 : : * be the latest checkpoint before WAL forked and not the checkpoint
245 : : * where the primary has been stopped to be rewound.
246 : : */
247 : 2656 : info = XLogRecGetInfo(xlogreader) & ~XLR_INFO_MASK;
248 [ + + ]: 2656 : if (searchptr < forkptr &&
249 [ + + + + ]: 2639 : XLogRecGetRmid(xlogreader) == RM_XLOG_ID &&
250 [ + + ]: 1909 : (info == XLOG_CHECKPOINT_SHUTDOWN ||
251 : : info == XLOG_CHECKPOINT_ONLINE))
252 : : {
253 : : CheckPoint checkPoint;
254 : :
255 : 17 : memcpy(&checkPoint, XLogRecGetData(xlogreader), sizeof(CheckPoint));
256 : 17 : *lastchkptrec = searchptr;
257 : 17 : *lastchkpttli = checkPoint.ThisTimeLineID;
258 : 17 : *lastchkptredo = checkPoint.redo;
259 : 17 : *lastchkptdatachecksums = checkPoint.dataChecksumState;
260 : 17 : break;
261 : : }
262 : :
263 : : /* Walk backwards to previous record. */
264 : 2639 : searchptr = record->xl_prev;
265 : : }
266 : :
267 : 17 : XLogReaderFree(xlogreader);
268 [ + - ]: 17 : if (xlogreadfd != -1)
269 : : {
270 : 17 : close(xlogreadfd);
271 : 17 : xlogreadfd = -1;
272 : : }
273 : 17 : }
274 : :
275 : : /* XLogReader callback function, to read a WAL page */
276 : : static int
277 : 5891 : SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
278 : : int reqLen, XLogRecPtr targetRecPtr, char *readBuf)
279 : : {
280 : 5891 : XLogPageReadPrivate *private = (XLogPageReadPrivate *) xlogreader->private_data;
281 : : uint32 targetPageOff;
282 : : XLogRecPtr targetSegEnd;
283 : : XLogSegNo targetSegNo;
284 : : ssize_t r;
285 : :
286 : 5891 : XLByteToSeg(targetPagePtr, targetSegNo, WalSegSz);
287 : 5891 : XLogSegNoOffsetToRecPtr(targetSegNo + 1, 0, WalSegSz, targetSegEnd);
288 : 5891 : targetPageOff = XLogSegmentOffset(targetPagePtr, WalSegSz);
289 : :
290 : : /*
291 : : * See if we need to switch to a new segment because the requested record
292 : : * is not in the currently open one.
293 : : */
294 [ + + ]: 5891 : if (xlogreadfd >= 0 &&
295 [ + + ]: 5840 : !XLByteInSeg(targetPagePtr, xlogreadsegno, WalSegSz))
296 : : {
297 : 11 : close(xlogreadfd);
298 : 11 : xlogreadfd = -1;
299 : : }
300 : :
301 : 5891 : XLByteToSeg(targetPagePtr, xlogreadsegno, WalSegSz);
302 : :
303 [ + + ]: 5891 : if (xlogreadfd < 0)
304 : : {
305 : : char xlogfname[MAXFNAMELEN];
306 : :
307 : : /*
308 : : * Since incomplete segments are copied into next timelines, switch to
309 : : * the timeline holding the required segment. Assuming this scan can
310 : : * be done both forward and backward, consider also switching timeline
311 : : * accordingly.
312 : : */
313 [ + + ]: 64 : while (private->tliIndex < targetNentries - 1 &&
314 [ + - ]: 2 : targetHistory[private->tliIndex].end < targetSegEnd)
315 : 2 : private->tliIndex++;
316 [ + + ]: 62 : while (private->tliIndex > 0 &&
317 [ - + ]: 6 : targetHistory[private->tliIndex].begin >= targetSegEnd)
318 : 0 : private->tliIndex--;
319 : :
320 : 62 : XLogFileName(xlogfname, targetHistory[private->tliIndex].tli,
321 : : xlogreadsegno, WalSegSz);
322 : :
323 : 62 : snprintf(xlogfpath, MAXPGPATH, "%s/" XLOGDIR "/%s",
324 : 62 : xlogreader->segcxt.ws_dir, xlogfname);
325 : :
326 : 62 : xlogreadfd = open(xlogfpath, O_RDONLY | PG_BINARY, 0);
327 : :
328 [ + + ]: 62 : if (xlogreadfd < 0)
329 : : {
330 : : /*
331 : : * If we have no restore_command to execute, then exit.
332 : : */
333 [ - + ]: 1 : if (private->restoreCommand == NULL)
334 : : {
335 : 0 : pg_log_error("could not open file \"%s\": %m", xlogfpath);
336 : 0 : return -1;
337 : : }
338 : :
339 : : /*
340 : : * Since we have restore_command, then try to retrieve missing WAL
341 : : * file from the archive.
342 : : */
343 : 1 : xlogreadfd = RestoreArchivedFile(xlogreader->segcxt.ws_dir,
344 : : xlogfname,
345 : : WalSegSz,
346 : : private->restoreCommand);
347 : :
348 [ - + ]: 1 : if (xlogreadfd < 0)
349 : 0 : return -1;
350 : : else
351 [ + - ]: 1 : pg_log_debug("using file \"%s\" restored from archive",
352 : : xlogfpath);
353 : : }
354 : : }
355 : :
356 : : /*
357 : : * At this point, we have the right segment open.
358 : : */
359 : : Assert(xlogreadfd != -1);
360 : :
361 : : /* Read the requested page */
362 [ - + ]: 5891 : if (lseek(xlogreadfd, (off_t) targetPageOff, SEEK_SET) < 0)
363 : : {
364 : 0 : pg_log_error("could not seek in file \"%s\": %m", xlogfpath);
365 : 0 : return -1;
366 : : }
367 : :
368 : :
369 : 5891 : r = read(xlogreadfd, readBuf, XLOG_BLCKSZ);
370 [ - + ]: 5891 : if (r != XLOG_BLCKSZ)
371 : : {
372 [ # # ]: 0 : if (r < 0)
373 : 0 : pg_log_error("could not read file \"%s\": %m", xlogfpath);
374 : : else
375 : 0 : pg_log_error("could not read file \"%s\": read %zd of %zu",
376 : : xlogfpath, r, (Size) XLOG_BLCKSZ);
377 : 0 : return -1;
378 : : }
379 : :
380 : : Assert(targetSegNo == xlogreadsegno);
381 : :
382 : 5891 : xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
383 : 5891 : return XLOG_BLCKSZ;
384 : : }
385 : :
386 : : /*
387 : : * Extract information on which blocks the current record modifies.
388 : : */
389 : : static void
390 : 86139 : extractPageInfo(XLogReaderState *record)
391 : : {
392 : : int block_id;
393 : 86139 : RmgrId rmid = XLogRecGetRmid(record);
394 : 86139 : uint8 info = XLogRecGetInfo(record);
395 : 86139 : uint8 rminfo = info & ~XLR_INFO_MASK;
396 : :
397 : : /* Is this a special record type that I recognize? */
398 : :
399 [ + + - + ]: 86139 : if (rmid == RM_DBASE_ID && rminfo == XLOG_DBASE_CREATE_FILE_COPY)
400 : : {
401 : : /*
402 : : * New databases can be safely ignored. It won't be present in the
403 : : * source system, so it will be deleted. There's one corner-case,
404 : : * though: if a new, different, database is also created in the source
405 : : * system, we'll see that the files already exist and not copy them.
406 : : * That's OK, though; WAL replay of creating the new database, from
407 : : * the source systems's WAL, will re-copy the new database,
408 : : * overwriting the database created in the target system.
409 : : */
410 : : }
411 [ + + + - ]: 86139 : else if (rmid == RM_DBASE_ID && rminfo == XLOG_DBASE_CREATE_WAL_LOG)
412 : : {
413 : : /*
414 : : * New databases can be safely ignored. It won't be present in the
415 : : * source system, so it will be deleted.
416 : : */
417 : : }
418 [ - + - - ]: 86135 : else if (rmid == RM_DBASE_ID && rminfo == XLOG_DBASE_DROP)
419 : : {
420 : : /*
421 : : * An existing database was dropped. We'll see that the files don't
422 : : * exist in the target data dir, and copy them in toto from the source
423 : : * system. No need to do anything special here.
424 : : */
425 : : }
426 [ + + + + ]: 86135 : else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_CREATE)
427 : : {
428 : : /*
429 : : * We can safely ignore these. The file will be removed from the
430 : : * target, if it doesn't exist in source system. If a file with same
431 : : * name is created in source system, too, there will be WAL records
432 : : * for all the blocks in it.
433 : : */
434 : : }
435 [ + + + - ]: 84938 : else if (rmid == RM_SMGR_ID && rminfo == XLOG_SMGR_TRUNCATE)
436 : : {
437 : : /*
438 : : * We can safely ignore these. When we compare the sizes later on,
439 : : * we'll notice that they differ, and copy the missing tail from
440 : : * source system.
441 : : */
442 : : }
443 [ + + ]: 84934 : else if (rmid == RM_XACT_ID &&
444 [ - + ]: 45 : ((rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_COMMIT ||
445 [ # # ]: 0 : (rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_COMMIT_PREPARED ||
446 [ # # ]: 0 : (rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_ABORT ||
447 [ # # ]: 0 : (rminfo & XLOG_XACT_OPMASK) == XLOG_XACT_ABORT_PREPARED))
448 : : {
449 : : /*
450 : : * These records can include "dropped rels". We can safely ignore
451 : : * them, we will see that they are missing and copy them from the
452 : : * source.
453 : : */
454 : : }
455 [ - + ]: 84889 : else if (info & XLR_SPECIAL_REL_UPDATE)
456 : : {
457 : : /*
458 : : * This record type modifies a relation file in some special way, but
459 : : * we don't recognize the type. That's bad - we don't know how to
460 : : * track that change.
461 : : */
462 [ # # ]: 0 : pg_fatal("WAL record modifies a relation, but record type is not recognized:\n"
463 : : "lsn: %X/%08X, rmid: %d, rmgr: %s, info: %02X",
464 : : LSN_FORMAT_ARGS(record->ReadRecPtr),
465 : : rmid, RmgrName(rmid), info);
466 : : }
467 : :
468 [ + + ]: 171299 : for (block_id = 0; block_id <= XLogRecMaxBlockId(record); block_id++)
469 : : {
470 : : RelFileLocator rlocator;
471 : : ForkNumber forknum;
472 : : BlockNumber blkno;
473 : :
474 [ - + ]: 85160 : if (!XLogRecGetBlockTagExtended(record, block_id,
475 : : &rlocator, &forknum, &blkno, NULL))
476 : 968 : continue;
477 : :
478 : : /* We only care about the main fork; others are copied in toto */
479 [ + + ]: 85160 : if (forknum != MAIN_FORKNUM)
480 : 968 : continue;
481 : :
482 : 84192 : process_target_wal_block_change(forknum, rlocator, blkno);
483 : : }
484 : 86139 : }
|