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