/src/postgres/src/backend/backup/walsummary.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * walsummary.c |
4 | | * Functions for accessing and managing WAL summary data. |
5 | | * |
6 | | * Portions Copyright (c) 2010-2025, PostgreSQL Global Development Group |
7 | | * |
8 | | * src/backend/backup/walsummary.c |
9 | | * |
10 | | *------------------------------------------------------------------------- |
11 | | */ |
12 | | |
13 | | #include "postgres.h" |
14 | | |
15 | | #include <sys/stat.h> |
16 | | #include <unistd.h> |
17 | | |
18 | | #include "access/xlog_internal.h" |
19 | | #include "backup/walsummary.h" |
20 | | #include "common/int.h" |
21 | | #include "utils/wait_event.h" |
22 | | |
23 | | static bool IsWalSummaryFilename(char *filename); |
24 | | static int ListComparatorForWalSummaryFiles(const ListCell *a, |
25 | | const ListCell *b); |
26 | | |
27 | | /* |
28 | | * Get a list of WAL summaries. |
29 | | * |
30 | | * If tli != 0, only WAL summaries with the indicated TLI will be included. |
31 | | * |
32 | | * If start_lsn != InvalidXLogRecPtr, only summaries that end after the |
33 | | * indicated LSN will be included. |
34 | | * |
35 | | * If end_lsn != InvalidXLogRecPtr, only summaries that start before the |
36 | | * indicated LSN will be included. |
37 | | * |
38 | | * The intent is that you can call GetWalSummaries(tli, start_lsn, end_lsn) |
39 | | * to get all WAL summaries on the indicated timeline that overlap the |
40 | | * specified LSN range. |
41 | | */ |
42 | | List * |
43 | | GetWalSummaries(TimeLineID tli, XLogRecPtr start_lsn, XLogRecPtr end_lsn) |
44 | 0 | { |
45 | 0 | DIR *sdir; |
46 | 0 | struct dirent *dent; |
47 | 0 | List *result = NIL; |
48 | |
|
49 | 0 | sdir = AllocateDir(XLOGDIR "/summaries"); |
50 | 0 | while ((dent = ReadDir(sdir, XLOGDIR "/summaries")) != NULL) |
51 | 0 | { |
52 | 0 | WalSummaryFile *ws; |
53 | 0 | uint32 tmp[5]; |
54 | 0 | TimeLineID file_tli; |
55 | 0 | XLogRecPtr file_start_lsn; |
56 | 0 | XLogRecPtr file_end_lsn; |
57 | | |
58 | | /* Decode filename, or skip if it's not in the expected format. */ |
59 | 0 | if (!IsWalSummaryFilename(dent->d_name)) |
60 | 0 | continue; |
61 | 0 | sscanf(dent->d_name, "%08X%08X%08X%08X%08X", |
62 | 0 | &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4]); |
63 | 0 | file_tli = tmp[0]; |
64 | 0 | file_start_lsn = ((uint64) tmp[1]) << 32 | tmp[2]; |
65 | 0 | file_end_lsn = ((uint64) tmp[3]) << 32 | tmp[4]; |
66 | | |
67 | | /* Skip if it doesn't match the filter criteria. */ |
68 | 0 | if (tli != 0 && tli != file_tli) |
69 | 0 | continue; |
70 | 0 | if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn >= file_end_lsn) |
71 | 0 | continue; |
72 | 0 | if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn <= file_start_lsn) |
73 | 0 | continue; |
74 | | |
75 | | /* Add it to the list. */ |
76 | 0 | ws = palloc(sizeof(WalSummaryFile)); |
77 | 0 | ws->tli = file_tli; |
78 | 0 | ws->start_lsn = file_start_lsn; |
79 | 0 | ws->end_lsn = file_end_lsn; |
80 | 0 | result = lappend(result, ws); |
81 | 0 | } |
82 | 0 | FreeDir(sdir); |
83 | |
|
84 | 0 | return result; |
85 | 0 | } |
86 | | |
87 | | /* |
88 | | * Build a new list of WAL summaries based on an existing list, but filtering |
89 | | * out summaries that don't match the search parameters. |
90 | | * |
91 | | * If tli != 0, only WAL summaries with the indicated TLI will be included. |
92 | | * |
93 | | * If start_lsn != InvalidXLogRecPtr, only summaries that end after the |
94 | | * indicated LSN will be included. |
95 | | * |
96 | | * If end_lsn != InvalidXLogRecPtr, only summaries that start before the |
97 | | * indicated LSN will be included. |
98 | | */ |
99 | | List * |
100 | | FilterWalSummaries(List *wslist, TimeLineID tli, |
101 | | XLogRecPtr start_lsn, XLogRecPtr end_lsn) |
102 | 0 | { |
103 | 0 | List *result = NIL; |
104 | 0 | ListCell *lc; |
105 | | |
106 | | /* Loop over input. */ |
107 | 0 | foreach(lc, wslist) |
108 | 0 | { |
109 | 0 | WalSummaryFile *ws = lfirst(lc); |
110 | | |
111 | | /* Skip if it doesn't match the filter criteria. */ |
112 | 0 | if (tli != 0 && tli != ws->tli) |
113 | 0 | continue; |
114 | 0 | if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn > ws->end_lsn) |
115 | 0 | continue; |
116 | 0 | if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn < ws->start_lsn) |
117 | 0 | continue; |
118 | | |
119 | | /* Add it to the result list. */ |
120 | 0 | result = lappend(result, ws); |
121 | 0 | } |
122 | |
|
123 | 0 | return result; |
124 | 0 | } |
125 | | |
126 | | /* |
127 | | * Check whether the supplied list of WalSummaryFile objects covers the |
128 | | * whole range of LSNs from start_lsn to end_lsn. This function ignores |
129 | | * timelines, so the caller should probably filter using the appropriate |
130 | | * timeline before calling this. |
131 | | * |
132 | | * If the whole range of LSNs is covered, returns true, otherwise false. |
133 | | * If false is returned, *missing_lsn is set either to InvalidXLogRecPtr |
134 | | * if there are no WAL summary files in the input list, or to the first LSN |
135 | | * in the range that is not covered by a WAL summary file in the input list. |
136 | | */ |
137 | | bool |
138 | | WalSummariesAreComplete(List *wslist, XLogRecPtr start_lsn, |
139 | | XLogRecPtr end_lsn, XLogRecPtr *missing_lsn) |
140 | 0 | { |
141 | 0 | XLogRecPtr current_lsn = start_lsn; |
142 | 0 | ListCell *lc; |
143 | | |
144 | | /* Special case for empty list. */ |
145 | 0 | if (wslist == NIL) |
146 | 0 | { |
147 | 0 | *missing_lsn = InvalidXLogRecPtr; |
148 | 0 | return false; |
149 | 0 | } |
150 | | |
151 | | /* Make a private copy of the list and sort it by start LSN. */ |
152 | 0 | wslist = list_copy(wslist); |
153 | 0 | list_sort(wslist, ListComparatorForWalSummaryFiles); |
154 | | |
155 | | /* |
156 | | * Consider summary files in order of increasing start_lsn, advancing the |
157 | | * known-summarized range from start_lsn toward end_lsn. |
158 | | * |
159 | | * Normally, the summary files should cover non-overlapping WAL ranges, |
160 | | * but this algorithm is intended to be correct even in case of overlap. |
161 | | */ |
162 | 0 | foreach(lc, wslist) |
163 | 0 | { |
164 | 0 | WalSummaryFile *ws = lfirst(lc); |
165 | |
|
166 | 0 | if (ws->start_lsn > current_lsn) |
167 | 0 | { |
168 | | /* We found a gap. */ |
169 | 0 | break; |
170 | 0 | } |
171 | 0 | if (ws->end_lsn > current_lsn) |
172 | 0 | { |
173 | | /* |
174 | | * Next summary extends beyond end of previous summary, so extend |
175 | | * the end of the range known to be summarized. |
176 | | */ |
177 | 0 | current_lsn = ws->end_lsn; |
178 | | |
179 | | /* |
180 | | * If the range we know to be summarized has reached the required |
181 | | * end LSN, we have proved completeness. |
182 | | */ |
183 | 0 | if (current_lsn >= end_lsn) |
184 | 0 | return true; |
185 | 0 | } |
186 | 0 | } |
187 | | |
188 | | /* |
189 | | * We either ran out of summary files without reaching the end LSN, or we |
190 | | * hit a gap in the sequence that resulted in us bailing out of the loop |
191 | | * above. |
192 | | */ |
193 | 0 | *missing_lsn = current_lsn; |
194 | 0 | return false; |
195 | 0 | } |
196 | | |
197 | | /* |
198 | | * Open a WAL summary file. |
199 | | * |
200 | | * This will throw an error in case of trouble. As an exception, if |
201 | | * missing_ok = true and the trouble is specifically that the file does |
202 | | * not exist, it will not throw an error and will return a value less than 0. |
203 | | */ |
204 | | File |
205 | | OpenWalSummaryFile(WalSummaryFile *ws, bool missing_ok) |
206 | 0 | { |
207 | 0 | char path[MAXPGPATH]; |
208 | 0 | File file; |
209 | |
|
210 | 0 | snprintf(path, MAXPGPATH, |
211 | 0 | XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary", |
212 | 0 | ws->tli, |
213 | 0 | LSN_FORMAT_ARGS(ws->start_lsn), |
214 | 0 | LSN_FORMAT_ARGS(ws->end_lsn)); |
215 | |
|
216 | 0 | file = PathNameOpenFile(path, O_RDONLY); |
217 | 0 | if (file < 0 && (errno != EEXIST || !missing_ok)) |
218 | 0 | ereport(ERROR, |
219 | 0 | (errcode_for_file_access(), |
220 | 0 | errmsg("could not open file \"%s\": %m", path))); |
221 | | |
222 | 0 | return file; |
223 | 0 | } |
224 | | |
225 | | /* |
226 | | * Remove a WAL summary file if the last modification time precedes the |
227 | | * cutoff time. |
228 | | */ |
229 | | void |
230 | | RemoveWalSummaryIfOlderThan(WalSummaryFile *ws, time_t cutoff_time) |
231 | 0 | { |
232 | 0 | char path[MAXPGPATH]; |
233 | 0 | struct stat statbuf; |
234 | |
|
235 | 0 | snprintf(path, MAXPGPATH, |
236 | 0 | XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary", |
237 | 0 | ws->tli, |
238 | 0 | LSN_FORMAT_ARGS(ws->start_lsn), |
239 | 0 | LSN_FORMAT_ARGS(ws->end_lsn)); |
240 | |
|
241 | 0 | if (lstat(path, &statbuf) != 0) |
242 | 0 | { |
243 | 0 | if (errno == ENOENT) |
244 | 0 | return; |
245 | 0 | ereport(ERROR, |
246 | 0 | (errcode_for_file_access(), |
247 | 0 | errmsg("could not stat file \"%s\": %m", path))); |
248 | 0 | } |
249 | 0 | if (statbuf.st_mtime >= cutoff_time) |
250 | 0 | return; |
251 | 0 | if (unlink(path) != 0) |
252 | 0 | ereport(ERROR, |
253 | 0 | (errcode_for_file_access(), |
254 | 0 | errmsg("could not stat file \"%s\": %m", path))); |
255 | 0 | ereport(DEBUG2, |
256 | 0 | (errmsg_internal("removing file \"%s\"", path))); |
257 | 0 | } |
258 | | |
259 | | /* |
260 | | * Test whether a filename looks like a WAL summary file. |
261 | | */ |
262 | | static bool |
263 | | IsWalSummaryFilename(char *filename) |
264 | 0 | { |
265 | 0 | return strspn(filename, "0123456789ABCDEF") == 40 && |
266 | 0 | strcmp(filename + 40, ".summary") == 0; |
267 | 0 | } |
268 | | |
269 | | /* |
270 | | * Data read callback for use with CreateBlockRefTableReader. |
271 | | */ |
272 | | int |
273 | | ReadWalSummary(void *wal_summary_io, void *data, int length) |
274 | 0 | { |
275 | 0 | WalSummaryIO *io = wal_summary_io; |
276 | 0 | int nbytes; |
277 | |
|
278 | 0 | nbytes = FileRead(io->file, data, length, io->filepos, |
279 | 0 | WAIT_EVENT_WAL_SUMMARY_READ); |
280 | 0 | if (nbytes < 0) |
281 | 0 | ereport(ERROR, |
282 | 0 | (errcode_for_file_access(), |
283 | 0 | errmsg("could not read file \"%s\": %m", |
284 | 0 | FilePathName(io->file)))); |
285 | | |
286 | 0 | io->filepos += nbytes; |
287 | 0 | return nbytes; |
288 | 0 | } |
289 | | |
290 | | /* |
291 | | * Data write callback for use with WriteBlockRefTable. |
292 | | */ |
293 | | int |
294 | | WriteWalSummary(void *wal_summary_io, void *data, int length) |
295 | 0 | { |
296 | 0 | WalSummaryIO *io = wal_summary_io; |
297 | 0 | int nbytes; |
298 | |
|
299 | 0 | nbytes = FileWrite(io->file, data, length, io->filepos, |
300 | 0 | WAIT_EVENT_WAL_SUMMARY_WRITE); |
301 | 0 | if (nbytes < 0) |
302 | 0 | ereport(ERROR, |
303 | 0 | (errcode_for_file_access(), |
304 | 0 | errmsg("could not write file \"%s\": %m", |
305 | 0 | FilePathName(io->file)))); |
306 | 0 | if (nbytes != length) |
307 | 0 | ereport(ERROR, |
308 | 0 | (errcode_for_file_access(), |
309 | 0 | errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u", |
310 | 0 | FilePathName(io->file), nbytes, |
311 | 0 | length, (unsigned) io->filepos), |
312 | 0 | errhint("Check free disk space."))); |
313 | | |
314 | 0 | io->filepos += nbytes; |
315 | 0 | return nbytes; |
316 | 0 | } |
317 | | |
318 | | /* |
319 | | * Error-reporting callback for use with CreateBlockRefTableReader. |
320 | | */ |
321 | | void |
322 | | ReportWalSummaryError(void *callback_arg, char *fmt,...) |
323 | 0 | { |
324 | 0 | StringInfoData buf; |
325 | 0 | va_list ap; |
326 | 0 | int needed; |
327 | |
|
328 | 0 | initStringInfo(&buf); |
329 | 0 | for (;;) |
330 | 0 | { |
331 | 0 | va_start(ap, fmt); |
332 | 0 | needed = appendStringInfoVA(&buf, fmt, ap); |
333 | 0 | va_end(ap); |
334 | 0 | if (needed == 0) |
335 | 0 | break; |
336 | 0 | enlargeStringInfo(&buf, needed); |
337 | 0 | } |
338 | 0 | ereport(ERROR, |
339 | 0 | errcode(ERRCODE_DATA_CORRUPTED), |
340 | 0 | errmsg_internal("%s", buf.data)); |
341 | 0 | } |
342 | | |
343 | | /* |
344 | | * Comparator to sort a List of WalSummaryFile objects by start_lsn. |
345 | | */ |
346 | | static int |
347 | | ListComparatorForWalSummaryFiles(const ListCell *a, const ListCell *b) |
348 | 0 | { |
349 | 0 | WalSummaryFile *ws1 = lfirst(a); |
350 | 0 | WalSummaryFile *ws2 = lfirst(b); |
351 | |
|
352 | 0 | return pg_cmp_u64(ws1->start_lsn, ws2->start_lsn); |
353 | 0 | } |