Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Read and manipulate backup label files
4 : : *
5 : : * Portions Copyright (c) 1996-2026, 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
948 rhaas@postgresql.org 45 :CBC 41 : parse_backup_label(char *filename, StringInfo buf,
46 : : TimeLineID *start_tli, XLogRecPtr *start_lsn,
47 : : TimeLineID *previous_tli, XLogRecPtr *previous_lsn)
48 : : {
49 : 41 : int found = 0;
50 : :
51 : 41 : *start_tli = 0;
52 : 41 : *start_lsn = InvalidXLogRecPtr;
53 : 41 : *previous_tli = 0;
54 : 41 : *previous_lsn = InvalidXLogRecPtr;
55 : :
56 [ + + ]: 372 : while (buf->cursor < buf->len)
57 : : {
58 : 331 : char *s = &buf->data[buf->cursor];
59 : 331 : int eo = get_eol_offset(buf);
60 : 331 : char *e = &buf->data[eo];
61 : : char *c;
62 : :
63 [ + + ]: 331 : if (line_starts_with(s, e, "START WAL LOCATION: ", &s))
64 : : {
65 [ - + ]: 41 : if (!parse_lsn(s, e, start_lsn, &c))
948 rhaas@postgresql.org 66 :UBC 0 : pg_fatal("%s: could not parse %s",
67 : : filename, "START WAL LOCATION");
948 rhaas@postgresql.org 68 [ + - - + ]:CBC 41 : if (c >= e || *c != ' ')
948 rhaas@postgresql.org 69 :UBC 0 : pg_fatal("%s: improper terminator for %s",
70 : : filename, "START WAL LOCATION");
948 rhaas@postgresql.org 71 :CBC 41 : found |= 1;
72 : : }
73 [ + + ]: 290 : else if (line_starts_with(s, e, "START TIMELINE: ", &s))
74 : : {
75 [ - + ]: 41 : if (!parse_tli(s, e, start_tli))
948 rhaas@postgresql.org 76 :UBC 0 : pg_fatal("%s: could not parse TLI for %s",
77 : : filename, "START TIMELINE");
948 rhaas@postgresql.org 78 [ - + ]:CBC 41 : if (*start_tli == 0)
948 rhaas@postgresql.org 79 :UBC 0 : pg_fatal("%s: invalid TLI", filename);
948 rhaas@postgresql.org 80 :CBC 41 : found |= 2;
81 : : }
82 [ + + ]: 249 : else if (line_starts_with(s, e, "INCREMENTAL FROM LSN: ", &s))
83 : : {
84 [ - + ]: 22 : if (!parse_lsn(s, e, previous_lsn, &c))
948 rhaas@postgresql.org 85 :UBC 0 : pg_fatal("%s: could not parse %s",
86 : : filename, "INCREMENTAL FROM LSN");
948 rhaas@postgresql.org 87 [ + - - + ]:CBC 22 : if (c >= e || *c != '\n')
948 rhaas@postgresql.org 88 :UBC 0 : pg_fatal("%s: improper terminator for %s",
89 : : filename, "INCREMENTAL FROM LSN");
948 rhaas@postgresql.org 90 :CBC 22 : found |= 4;
91 : : }
92 [ + + ]: 227 : else if (line_starts_with(s, e, "INCREMENTAL FROM TLI: ", &s))
93 : : {
94 [ - + ]: 22 : if (!parse_tli(s, e, previous_tli))
948 rhaas@postgresql.org 95 :UBC 0 : pg_fatal("%s: could not parse %s",
96 : : filename, "INCREMENTAL FROM TLI");
948 rhaas@postgresql.org 97 [ - + ]:CBC 22 : if (*previous_tli == 0)
948 rhaas@postgresql.org 98 :UBC 0 : pg_fatal("%s: invalid TLI", filename);
948 rhaas@postgresql.org 99 :CBC 22 : found |= 8;
100 : : }
101 : :
102 : 331 : buf->cursor = eo;
103 : : }
104 : :
105 [ - + ]: 41 : if ((found & 1) == 0)
948 rhaas@postgresql.org 106 :UBC 0 : pg_fatal("%s: could not find %s", filename, "START WAL LOCATION");
948 rhaas@postgresql.org 107 [ - + ]:CBC 41 : if ((found & 2) == 0)
948 rhaas@postgresql.org 108 :UBC 0 : pg_fatal("%s: could not find %s", filename, "START TIMELINE");
948 rhaas@postgresql.org 109 [ + + - + ]:CBC 41 : if ((found & 4) != 0 && (found & 8) == 0)
948 rhaas@postgresql.org 110 :UBC 0 : pg_fatal("%s: %s requires %s", filename,
111 : : "INCREMENTAL FROM LSN", "INCREMENTAL FROM TLI");
948 rhaas@postgresql.org 112 [ + + - + ]:CBC 41 : if ((found & 8) != 0 && (found & 4) == 0)
948 rhaas@postgresql.org 113 :UBC 0 : pg_fatal("%s: %s requires %s", filename,
114 : : "INCREMENTAL FROM TLI", "INCREMENTAL FROM LSN");
948 rhaas@postgresql.org 115 :CBC 41 : }
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 : 15 : 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 : 15 : pg_checksum_init(&checksum_ctx, checksum_type);
137 : :
138 : 15 : snprintf(output_filename, MAXPGPATH, "%s/backup_label", output_directory);
139 : :
140 [ - + ]: 15 : if ((output_fd = open(output_filename,
141 : : O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
142 : : pg_file_create_mode)) < 0)
948 rhaas@postgresql.org 143 :UBC 0 : pg_fatal("could not open file \"%s\": %m", output_filename);
144 : :
948 rhaas@postgresql.org 145 [ + + ]:CBC 144 : while (buf->cursor < buf->len)
146 : : {
147 : 129 : char *s = &buf->data[buf->cursor];
148 : 129 : int eo = get_eol_offset(buf);
149 : 129 : char *e = &buf->data[eo];
150 : :
151 [ + + ]: 129 : if (!line_starts_with(s, e, "INCREMENTAL FROM LSN: ", NULL) &&
152 [ + + ]: 117 : !line_starts_with(s, e, "INCREMENTAL FROM TLI: ", NULL))
153 : : {
10 peter@eisentraut.org 154 :GNC 105 : const size_t bytes_left = e - s;
155 : : ssize_t wb;
156 : :
157 : 105 : wb = write(output_fd, s, bytes_left);
158 [ - + ]: 105 : if (wb != bytes_left)
159 : : {
948 rhaas@postgresql.org 160 [ # # ]:UBC 0 : if (wb < 0)
161 : 0 : pg_fatal("could not write file \"%s\": %m", output_filename);
162 : : else
10 peter@eisentraut.org 163 :UNC 0 : pg_fatal("could not write file \"%s\": wrote %zd of %zu",
164 : : output_filename, wb, bytes_left);
165 : : }
10 peter@eisentraut.org 166 [ - + ]:GNC 105 : if (pg_checksum_update(&checksum_ctx, (uint8 *) s, bytes_left) < 0)
948 rhaas@postgresql.org 167 :UBC 0 : pg_fatal("could not update checksum of file \"%s\"",
168 : : output_filename);
169 : : }
170 : :
948 rhaas@postgresql.org 171 :CBC 129 : buf->cursor = eo;
172 : : }
173 : :
174 [ - + ]: 15 : if (close(output_fd) != 0)
764 peter@eisentraut.org 175 :UBC 0 : pg_fatal("could not close file \"%s\": %m", output_filename);
176 : :
948 rhaas@postgresql.org 177 :CBC 15 : checksum_length = pg_checksum_final(&checksum_ctx, checksum_payload);
178 : :
179 [ + + ]: 15 : if (mwriter != NULL)
180 : : {
181 : : struct stat sb;
182 : :
183 : : /*
184 : : * We could track the length ourselves, but must stat() to get the
185 : : * mtime.
186 : : */
187 [ - + ]: 14 : if (stat(output_filename, &sb) < 0)
948 rhaas@postgresql.org 188 :UBC 0 : pg_fatal("could not stat file \"%s\": %m", output_filename);
948 rhaas@postgresql.org 189 :CBC 14 : add_file_to_manifest(mwriter, "backup_label", sb.st_size,
190 : : sb.st_mtime, checksum_type,
191 : : checksum_length, checksum_payload);
192 : : }
193 : 15 : }
194 : :
195 : : /*
196 : : * Return the offset at which the next line in the buffer starts, or there
197 : : * is none, the offset at which the buffer ends.
198 : : *
199 : : * The search begins at buf->cursor.
200 : : */
201 : : static int
202 : 460 : get_eol_offset(StringInfo buf)
203 : : {
204 : 460 : int eo = buf->cursor;
205 : :
206 [ + - ]: 14650 : while (eo < buf->len)
207 : : {
208 [ + + ]: 14650 : if (buf->data[eo] == '\n')
209 : 460 : return eo + 1;
210 : 14190 : ++eo;
211 : : }
212 : :
948 rhaas@postgresql.org 213 :UBC 0 : return eo;
214 : : }
215 : :
216 : : /*
217 : : * Test whether the line that runs from s to e (inclusive of *s, but not
218 : : * inclusive of *e) starts with the match string provided, and return true
219 : : * or false according to whether or not this is the case.
220 : : *
221 : : * If the function returns true and if *sout != NULL, stores a pointer to the
222 : : * byte following the match into *sout.
223 : : */
224 : : static bool
948 rhaas@postgresql.org 225 :CBC 1343 : line_starts_with(char *s, char *e, char *match, char **sout)
226 : : {
227 [ + - + + : 5795 : while (s < e && *match != '\0' && *s == *match)
+ + ]
228 : 4452 : ++s, ++match;
229 : :
230 [ + + + + ]: 1343 : if (*match == '\0' && sout != NULL)
231 : 126 : *sout = s;
232 : :
233 : 1343 : return (*match == '\0');
234 : : }
235 : :
236 : : /*
237 : : * Parse an LSN starting at s and not stopping at or before e. The return value
238 : : * is true on success and otherwise false. On success, stores the result into
239 : : * *lsn and sets *c to the first character that is not part of the LSN.
240 : : */
241 : : static bool
242 : 63 : parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c)
243 : : {
244 : 63 : char save = *e;
245 : : int nchars;
246 : : bool success;
247 : : unsigned hi;
248 : : unsigned lo;
249 : :
250 : 63 : *e = '\0';
383 alvherre@kurilemu.de 251 : 63 : success = (sscanf(s, "%X/%08X%n", &hi, &lo, &nchars) == 2);
948 rhaas@postgresql.org 252 : 63 : *e = save;
253 : :
254 [ + - ]: 63 : if (success)
255 : : {
256 : 63 : *lsn = ((XLogRecPtr) hi) << 32 | (XLogRecPtr) lo;
257 : 63 : *c = s + nchars;
258 : : }
259 : :
260 : 63 : return success;
261 : : }
262 : :
263 : : /*
264 : : * Parse a TLI starting at s and stopping at or before e. The return value is
265 : : * true on success and otherwise false. On success, stores the result into
266 : : * *tli. If the first character that is not part of the TLI is anything other
267 : : * than a newline, that is deemed a failure.
268 : : */
269 : : static bool
270 : 63 : parse_tli(char *s, char *e, TimeLineID *tli)
271 : : {
272 : 63 : char save = *e;
273 : : int nchars;
274 : : bool success;
275 : :
276 : 63 : *e = '\0';
277 : 63 : success = (sscanf(s, "%u%n", tli, &nchars) == 1);
278 : 63 : *e = save;
279 : :
280 [ + - - + ]: 63 : if (success && s[nchars] != '\n')
948 rhaas@postgresql.org 281 :UBC 0 : success = false;
282 : :
948 rhaas@postgresql.org 283 :CBC 63 : return success;
284 : : }
|