Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * walsummary.c
4 : : * Functions for accessing and managing WAL summary data.
5 : : *
6 : : * Portions Copyright (c) 2010-2024, 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 *
116 rhaas@postgresql.org 43 :GNC 56 : GetWalSummaries(TimeLineID tli, XLogRecPtr start_lsn, XLogRecPtr end_lsn)
44 : : {
45 : : DIR *sdir;
46 : : struct dirent *dent;
47 : 56 : List *result = NIL;
48 : :
49 : 56 : sdir = AllocateDir(XLOGDIR "/summaries");
50 [ + + ]: 415 : while ((dent = ReadDir(sdir, XLOGDIR "/summaries")) != NULL)
51 : : {
52 : : WalSummaryFile *ws;
53 : : uint32 tmp[5];
54 : : TimeLineID file_tli;
55 : : XLogRecPtr file_start_lsn;
56 : : XLogRecPtr file_end_lsn;
57 : :
58 : : /* Decode filename, or skip if it's not in the expected format. */
59 [ + + ]: 359 : if (!IsWalSummaryFilename(dent->d_name))
60 : 157 : continue;
61 : 247 : sscanf(dent->d_name, "%08X%08X%08X%08X%08X",
62 : : &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4]);
63 : 247 : file_tli = tmp[0];
64 : 247 : file_start_lsn = ((uint64) tmp[1]) << 32 | tmp[2];
65 : 247 : file_end_lsn = ((uint64) tmp[3]) << 32 | tmp[4];
66 : :
67 : : /* Skip if it doesn't match the filter criteria. */
68 [ - + - - ]: 247 : if (tli != 0 && tli != file_tli)
116 rhaas@postgresql.org 69 :UNC 0 : continue;
116 rhaas@postgresql.org 70 [ + + + + ]:GNC 247 : if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn >= file_end_lsn)
71 : 45 : continue;
72 [ + + - + ]: 202 : if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn <= file_start_lsn)
116 rhaas@postgresql.org 73 :UNC 0 : continue;
74 : :
75 : : /* Add it to the list. */
116 rhaas@postgresql.org 76 :GNC 202 : ws = palloc(sizeof(WalSummaryFile));
77 : 202 : ws->tli = file_tli;
78 : 202 : ws->start_lsn = file_start_lsn;
79 : 202 : ws->end_lsn = file_end_lsn;
80 : 202 : result = lappend(result, ws);
81 : : }
82 : 56 : FreeDir(sdir);
83 : :
84 : 56 : return result;
85 : : }
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 : 7 : FilterWalSummaries(List *wslist, TimeLineID tli,
101 : : XLogRecPtr start_lsn, XLogRecPtr end_lsn)
102 : : {
103 : 7 : List *result = NIL;
104 : : ListCell *lc;
105 : :
106 : : /* Loop over input. */
107 [ + - + + : 19 : foreach(lc, wslist)
+ + ]
108 : : {
109 : 12 : WalSummaryFile *ws = lfirst(lc);
110 : :
111 : : /* Skip if it doesn't match the filter criteria. */
112 [ + - - + ]: 12 : if (tli != 0 && tli != ws->tli)
116 rhaas@postgresql.org 113 :UNC 0 : continue;
116 rhaas@postgresql.org 114 [ + - - + ]:GNC 12 : if (!XLogRecPtrIsInvalid(start_lsn) && start_lsn > ws->end_lsn)
116 rhaas@postgresql.org 115 :UNC 0 : continue;
116 rhaas@postgresql.org 116 [ + - - + ]:GNC 12 : if (!XLogRecPtrIsInvalid(end_lsn) && end_lsn < ws->start_lsn)
116 rhaas@postgresql.org 117 :UNC 0 : continue;
118 : :
119 : : /* Add it to the result list. */
116 rhaas@postgresql.org 120 :GNC 12 : result = lappend(result, ws);
121 : : }
122 : :
123 : 7 : return result;
124 : : }
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 : 7 : WalSummariesAreComplete(List *wslist, XLogRecPtr start_lsn,
139 : : XLogRecPtr end_lsn, XLogRecPtr *missing_lsn)
140 : : {
141 : 7 : XLogRecPtr current_lsn = start_lsn;
142 : : ListCell *lc;
143 : :
144 : : /* Special case for empty list. */
145 [ - + ]: 7 : if (wslist == NIL)
146 : : {
116 rhaas@postgresql.org 147 :UNC 0 : *missing_lsn = InvalidXLogRecPtr;
148 : 0 : return false;
149 : : }
150 : :
151 : : /* Make a private copy of the list and sort it by start LSN. */
116 rhaas@postgresql.org 152 :GNC 7 : wslist = list_copy(wslist);
153 : 7 : 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 [ + - + - : 12 : foreach(lc, wslist)
+ - ]
163 : : {
164 : 12 : WalSummaryFile *ws = lfirst(lc);
165 : :
166 [ - + ]: 12 : if (ws->start_lsn > current_lsn)
167 : : {
168 : : /* We found a gap. */
116 rhaas@postgresql.org 169 :UNC 0 : break;
170 : : }
116 rhaas@postgresql.org 171 [ + - ]:GNC 12 : if (ws->end_lsn > current_lsn)
172 : : {
173 : : /*
174 : : * Next summary extends beyond end of previous summary, so extend
175 : : * the end of the range known to be summarized.
176 : : */
177 : 12 : 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 [ + + ]: 12 : if (current_lsn >= end_lsn)
184 : 7 : return true;
185 : : }
186 : : }
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 : : */
116 rhaas@postgresql.org 193 :UNC 0 : *missing_lsn = current_lsn;
194 : 0 : return false;
195 : : }
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
116 rhaas@postgresql.org 205 :GNC 12 : OpenWalSummaryFile(WalSummaryFile *ws, bool missing_ok)
206 : : {
207 : : char path[MAXPGPATH];
208 : : File file;
209 : :
210 : 12 : snprintf(path, MAXPGPATH,
211 : : XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
212 : : ws->tli,
213 : 12 : LSN_FORMAT_ARGS(ws->start_lsn),
214 : 12 : LSN_FORMAT_ARGS(ws->end_lsn));
215 : :
216 : 12 : file = PathNameOpenFile(path, O_RDONLY);
217 [ - + - - : 12 : if (file < 0 && (errno != EEXIST || !missing_ok))
- - ]
116 rhaas@postgresql.org 218 [ # # ]:UNC 0 : ereport(ERROR,
219 : : (errcode_for_file_access(),
220 : : errmsg("could not open file \"%s\": %m", path)));
221 : :
116 rhaas@postgresql.org 222 :GNC 12 : return file;
223 : : }
224 : :
225 : : /*
226 : : * Remove a WAL summary file if the last modification time precedes the
227 : : * cutoff time.
228 : : */
229 : : void
230 : 67 : RemoveWalSummaryIfOlderThan(WalSummaryFile *ws, time_t cutoff_time)
231 : : {
232 : : char path[MAXPGPATH];
233 : : struct stat statbuf;
234 : :
235 : 67 : snprintf(path, MAXPGPATH,
236 : : XLOGDIR "/summaries/%08X%08X%08X%08X%08X.summary",
237 : : ws->tli,
238 : 67 : LSN_FORMAT_ARGS(ws->start_lsn),
239 : 67 : LSN_FORMAT_ARGS(ws->end_lsn));
240 : :
241 [ - + ]: 67 : if (lstat(path, &statbuf) != 0)
242 : : {
116 rhaas@postgresql.org 243 [ # # ]:UNC 0 : if (errno == ENOENT)
244 : 0 : return;
245 [ # # ]: 0 : ereport(ERROR,
246 : : (errcode_for_file_access(),
247 : : errmsg("could not stat file \"%s\": %m", path)));
248 : : }
116 rhaas@postgresql.org 249 [ + - ]:GNC 67 : if (statbuf.st_mtime >= cutoff_time)
250 : 67 : return;
116 rhaas@postgresql.org 251 [ # # ]:UNC 0 : if (unlink(path) != 0)
252 [ # # ]: 0 : ereport(ERROR,
253 : : (errcode_for_file_access(),
254 : : errmsg("could not stat file \"%s\": %m", path)));
255 [ # # ]: 0 : ereport(DEBUG2,
256 : : (errmsg_internal("removing file \"%s\"", path)));
257 : : }
258 : :
259 : : /*
260 : : * Test whether a filename looks like a WAL summary file.
261 : : */
262 : : static bool
116 rhaas@postgresql.org 263 :GNC 359 : IsWalSummaryFilename(char *filename)
264 : : {
265 [ + + ]: 606 : return strspn(filename, "0123456789ABCDEF") == 40 &&
266 [ + - ]: 247 : strcmp(filename + 40, ".summary") == 0;
267 : : }
268 : :
269 : : /*
270 : : * Data read callback for use with CreateBlockRefTableReader.
271 : : */
272 : : int
273 : 12 : ReadWalSummary(void *wal_summary_io, void *data, int length)
274 : : {
275 : 12 : WalSummaryIO *io = wal_summary_io;
276 : : int nbytes;
277 : :
278 : 12 : nbytes = FileRead(io->file, data, length, io->filepos,
279 : : WAIT_EVENT_WAL_SUMMARY_READ);
280 [ - + ]: 12 : if (nbytes < 0)
116 rhaas@postgresql.org 281 [ # # ]:UNC 0 : ereport(ERROR,
282 : : (errcode_for_file_access(),
283 : : errmsg("could not read file \"%s\": %m",
284 : : FilePathName(io->file))));
285 : :
116 rhaas@postgresql.org 286 :GNC 12 : io->filepos += nbytes;
287 : 12 : return nbytes;
288 : : }
289 : :
290 : : /*
291 : : * Data write callback for use with WriteBlockRefTable.
292 : : */
293 : : int
294 : 69 : WriteWalSummary(void *wal_summary_io, void *data, int length)
295 : : {
296 : 69 : WalSummaryIO *io = wal_summary_io;
297 : : int nbytes;
298 : :
299 : 69 : nbytes = FileWrite(io->file, data, length, io->filepos,
300 : : WAIT_EVENT_WAL_SUMMARY_WRITE);
301 [ - + ]: 69 : if (nbytes < 0)
116 rhaas@postgresql.org 302 [ # # ]:UNC 0 : ereport(ERROR,
303 : : (errcode_for_file_access(),
304 : : errmsg("could not write file \"%s\": %m",
305 : : FilePathName(io->file))));
116 rhaas@postgresql.org 306 [ - + ]:GNC 69 : if (nbytes != length)
116 rhaas@postgresql.org 307 [ # # ]:UNC 0 : ereport(ERROR,
308 : : (errcode_for_file_access(),
309 : : errmsg("could not write file \"%s\": wrote only %d of %d bytes at offset %u",
310 : : FilePathName(io->file), nbytes,
311 : : length, (unsigned) io->filepos),
312 : : errhint("Check free disk space.")));
313 : :
116 rhaas@postgresql.org 314 :GNC 69 : io->filepos += nbytes;
315 : 69 : return nbytes;
316 : : }
317 : :
318 : : /*
319 : : * Error-reporting callback for use with CreateBlockRefTableReader.
320 : : */
321 : : void
116 rhaas@postgresql.org 322 :UNC 0 : ReportWalSummaryError(void *callback_arg, char *fmt,...)
323 : : {
324 : : StringInfoData buf;
325 : : va_list ap;
326 : : int needed;
327 : :
328 : 0 : initStringInfo(&buf);
329 : : for (;;)
330 : : {
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 : : }
338 [ # # ]: 0 : ereport(ERROR,
339 : : errcode(ERRCODE_DATA_CORRUPTED),
340 : : errmsg_internal("%s", buf.data));
341 : : }
342 : :
343 : : /*
344 : : * Comparator to sort a List of WalSummaryFile objects by start_lsn.
345 : : */
346 : : static int
116 rhaas@postgresql.org 347 :GNC 5 : ListComparatorForWalSummaryFiles(const ListCell *a, const ListCell *b)
348 : : {
349 : 5 : WalSummaryFile *ws1 = lfirst(a);
350 : 5 : WalSummaryFile *ws2 = lfirst(b);
351 : :
58 nathan@postgresql.or 352 : 5 : return pg_cmp_u64(ws1->start_lsn, ws2->start_lsn);
353 : : }
|