Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * Read and manipulate backup label files
4 : *
5 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
6 : * Portions Copyright (c) 1994, Regents of the University of California
7 : *
8 : * src/bin/pg_combinebackup/backup_label.c
9 : *
10 : *-------------------------------------------------------------------------
11 : */
12 : #include "postgres_fe.h"
13 :
14 : #include <unistd.h>
15 :
16 : #include "access/xlogdefs.h"
17 : #include "backup_label.h"
18 : #include "common/file_perm.h"
19 : #include "common/logging.h"
20 : #include "write_manifest.h"
21 :
22 : static int get_eol_offset(StringInfo buf);
23 : static bool line_starts_with(char *s, char *e, char *match, char **sout);
24 : static bool parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c);
25 : static bool parse_tli(char *s, char *e, TimeLineID *tli);
26 :
27 : /*
28 : * Parse a backup label file, starting at buf->cursor.
29 : *
30 : * We expect to find a START WAL LOCATION line, followed by a LSN, followed
31 : * by a space; the resulting LSN is stored into *start_lsn.
32 : *
33 : * We expect to find a START TIMELINE line, followed by a TLI, followed by
34 : * a newline; the resulting TLI is stored into *start_tli.
35 : *
36 : * We expect to find either both INCREMENTAL FROM LSN and INCREMENTAL FROM TLI
37 : * or neither. If these are found, they should be followed by an LSN or TLI
38 : * respectively and then by a newline, and the values will be stored into
39 : * *previous_lsn and *previous_tli, respectively.
40 : *
41 : * Other lines in the provided backup_label data are ignored. filename is used
42 : * for error reporting; errors are fatal.
43 : */
44 : void
45 74 : parse_backup_label(char *filename, StringInfo buf,
46 : TimeLineID *start_tli, XLogRecPtr *start_lsn,
47 : TimeLineID *previous_tli, XLogRecPtr *previous_lsn)
48 : {
49 74 : int found = 0;
50 :
51 74 : *start_tli = 0;
52 74 : *start_lsn = InvalidXLogRecPtr;
53 74 : *previous_tli = 0;
54 74 : *previous_lsn = InvalidXLogRecPtr;
55 :
56 672 : while (buf->cursor < buf->len)
57 : {
58 598 : char *s = &buf->data[buf->cursor];
59 598 : int eo = get_eol_offset(buf);
60 598 : char *e = &buf->data[eo];
61 : char *c;
62 :
63 598 : if (line_starts_with(s, e, "START WAL LOCATION: ", &s))
64 : {
65 74 : if (!parse_lsn(s, e, start_lsn, &c))
66 0 : pg_fatal("%s: could not parse %s",
67 : filename, "START WAL LOCATION");
68 74 : if (c >= e || *c != ' ')
69 0 : pg_fatal("%s: improper terminator for %s",
70 : filename, "START WAL LOCATION");
71 74 : found |= 1;
72 : }
73 524 : else if (line_starts_with(s, e, "START TIMELINE: ", &s))
74 : {
75 74 : if (!parse_tli(s, e, start_tli))
76 0 : pg_fatal("%s: could not parse TLI for %s",
77 : filename, "START TIMELINE");
78 74 : if (*start_tli == 0)
79 0 : pg_fatal("%s: invalid TLI", filename);
80 74 : found |= 2;
81 : }
82 450 : else if (line_starts_with(s, e, "INCREMENTAL FROM LSN: ", &s))
83 : {
84 40 : if (!parse_lsn(s, e, previous_lsn, &c))
85 0 : pg_fatal("%s: could not parse %s",
86 : filename, "INCREMENTAL FROM LSN");
87 40 : if (c >= e || *c != '\n')
88 0 : pg_fatal("%s: improper terminator for %s",
89 : filename, "INCREMENTAL FROM LSN");
90 40 : found |= 4;
91 : }
92 410 : else if (line_starts_with(s, e, "INCREMENTAL FROM TLI: ", &s))
93 : {
94 40 : if (!parse_tli(s, e, previous_tli))
95 0 : pg_fatal("%s: could not parse %s",
96 : filename, "INCREMENTAL FROM TLI");
97 40 : if (*previous_tli == 0)
98 0 : pg_fatal("%s: invalid TLI", filename);
99 40 : found |= 8;
100 : }
101 :
102 598 : buf->cursor = eo;
103 : }
104 :
105 74 : if ((found & 1) == 0)
106 0 : pg_fatal("%s: could not find %s", filename, "START WAL LOCATION");
107 74 : if ((found & 2) == 0)
108 0 : pg_fatal("%s: could not find %s", filename, "START TIMELINE");
109 74 : if ((found & 4) != 0 && (found & 8) == 0)
110 0 : pg_fatal("%s: %s requires %s", filename,
111 : "INCREMENTAL FROM LSN", "INCREMENTAL FROM TLI");
112 74 : if ((found & 8) != 0 && (found & 4) == 0)
113 0 : pg_fatal("%s: %s requires %s", filename,
114 : "INCREMENTAL FROM TLI", "INCREMENTAL FROM LSN");
115 74 : }
116 :
117 : /*
118 : * Write a backup label file to the output directory.
119 : *
120 : * This will be identical to the provided backup_label file, except that the
121 : * INCREMENTAL FROM LSN and INCREMENTAL FROM TLI lines will be omitted.
122 : *
123 : * The new file will be checksummed using the specified algorithm. If
124 : * mwriter != NULL, it will be added to the manifest.
125 : */
126 : void
127 26 : write_backup_label(char *output_directory, StringInfo buf,
128 : pg_checksum_type checksum_type, manifest_writer *mwriter)
129 : {
130 : char output_filename[MAXPGPATH];
131 : int output_fd;
132 : pg_checksum_context checksum_ctx;
133 : uint8 checksum_payload[PG_CHECKSUM_MAX_LENGTH];
134 : int checksum_length;
135 :
136 26 : pg_checksum_init(&checksum_ctx, checksum_type);
137 :
138 26 : snprintf(output_filename, MAXPGPATH, "%s/backup_label", output_directory);
139 :
140 26 : if ((output_fd = open(output_filename,
141 : O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
142 : pg_file_create_mode)) < 0)
143 0 : pg_fatal("could not open file \"%s\": %m", output_filename);
144 :
145 248 : while (buf->cursor < buf->len)
146 : {
147 222 : char *s = &buf->data[buf->cursor];
148 222 : int eo = get_eol_offset(buf);
149 222 : char *e = &buf->data[eo];
150 :
151 222 : if (!line_starts_with(s, e, "INCREMENTAL FROM LSN: ", NULL) &&
152 202 : !line_starts_with(s, e, "INCREMENTAL FROM TLI: ", NULL))
153 : {
154 : ssize_t wb;
155 :
156 182 : wb = write(output_fd, s, e - s);
157 182 : if (wb != e - s)
158 : {
159 0 : if (wb < 0)
160 0 : pg_fatal("could not write file \"%s\": %m", output_filename);
161 : else
162 0 : pg_fatal("could not write file \"%s\": wrote %d of %d",
163 : output_filename, (int) wb, (int) (e - s));
164 : }
165 182 : if (pg_checksum_update(&checksum_ctx, (uint8 *) s, e - s) < 0)
166 0 : pg_fatal("could not update checksum of file \"%s\"",
167 : output_filename);
168 : }
169 :
170 222 : buf->cursor = eo;
171 : }
172 :
173 26 : if (close(output_fd) != 0)
174 0 : pg_fatal("could not close file \"%s\": %m", output_filename);
175 :
176 26 : checksum_length = pg_checksum_final(&checksum_ctx, checksum_payload);
177 :
178 26 : if (mwriter != NULL)
179 : {
180 : struct stat sb;
181 :
182 : /*
183 : * We could track the length ourselves, but must stat() to get the
184 : * mtime.
185 : */
186 24 : if (stat(output_filename, &sb) < 0)
187 0 : pg_fatal("could not stat file \"%s\": %m", output_filename);
188 24 : add_file_to_manifest(mwriter, "backup_label", sb.st_size,
189 : sb.st_mtime, checksum_type,
190 : checksum_length, checksum_payload);
191 : }
192 26 : }
193 :
194 : /*
195 : * Return the offset at which the next line in the buffer starts, or there
196 : * is none, the offset at which the buffer ends.
197 : *
198 : * The search begins at buf->cursor.
199 : */
200 : static int
201 820 : get_eol_offset(StringInfo buf)
202 : {
203 820 : int eo = buf->cursor;
204 :
205 25860 : while (eo < buf->len)
206 : {
207 25860 : if (buf->data[eo] == '\n')
208 820 : return eo + 1;
209 25040 : ++eo;
210 : }
211 :
212 0 : return eo;
213 : }
214 :
215 : /*
216 : * Test whether the line that runs from s to e (inclusive of *s, but not
217 : * inclusive of *e) starts with the match string provided, and return true
218 : * or false according to whether or not this is the case.
219 : *
220 : * If the function returns true and if *sout != NULL, stores a pointer to the
221 : * byte following the match into *sout.
222 : */
223 : static bool
224 2406 : line_starts_with(char *s, char *e, char *match, char **sout)
225 : {
226 10358 : while (s < e && *match != '\0' && *s == *match)
227 7952 : ++s, ++match;
228 :
229 2406 : if (*match == '\0' && sout != NULL)
230 228 : *sout = s;
231 :
232 2406 : return (*match == '\0');
233 : }
234 :
235 : /*
236 : * Parse an LSN starting at s and not stopping at or before e. The return value
237 : * is true on success and otherwise false. On success, stores the result into
238 : * *lsn and sets *c to the first character that is not part of the LSN.
239 : */
240 : static bool
241 114 : parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c)
242 : {
243 114 : char save = *e;
244 : int nchars;
245 : bool success;
246 : unsigned hi;
247 : unsigned lo;
248 :
249 114 : *e = '\0';
250 114 : success = (sscanf(s, "%X/%X%n", &hi, &lo, &nchars) == 2);
251 114 : *e = save;
252 :
253 114 : if (success)
254 : {
255 114 : *lsn = ((XLogRecPtr) hi) << 32 | (XLogRecPtr) lo;
256 114 : *c = s + nchars;
257 : }
258 :
259 114 : return success;
260 : }
261 :
262 : /*
263 : * Parse a TLI starting at s and stopping at or before e. The return value is
264 : * true on success and otherwise false. On success, stores the result into
265 : * *tli. If the first character that is not part of the TLI is anything other
266 : * than a newline, that is deemed a failure.
267 : */
268 : static bool
269 114 : parse_tli(char *s, char *e, TimeLineID *tli)
270 : {
271 114 : char save = *e;
272 : int nchars;
273 : bool success;
274 :
275 114 : *e = '\0';
276 114 : success = (sscanf(s, "%u%n", tli, &nchars) == 1);
277 114 : *e = save;
278 :
279 114 : if (success && s[nchars] != '\n')
280 0 : success = false;
281 :
282 114 : return success;
283 : }
|