Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * Read and manipulate backup label files
4 : : *
5 : : * Portions Copyright (c) 1996-2024, 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
116 rhaas@postgresql.org 45 :GNC 31 : parse_backup_label(char *filename, StringInfo buf,
46 : : TimeLineID *start_tli, XLogRecPtr *start_lsn,
47 : : TimeLineID *previous_tli, XLogRecPtr *previous_lsn)
48 : : {
49 : 31 : int found = 0;
50 : :
51 : 31 : *start_tli = 0;
52 : 31 : *start_lsn = InvalidXLogRecPtr;
53 : 31 : *previous_tli = 0;
54 : 31 : *previous_lsn = InvalidXLogRecPtr;
55 : :
56 [ + + ]: 282 : while (buf->cursor < buf->len)
57 : : {
58 : 251 : char *s = &buf->data[buf->cursor];
59 : 251 : int eo = get_eol_offset(buf);
60 : 251 : char *e = &buf->data[eo];
61 : : char *c;
62 : :
63 [ + + ]: 251 : if (line_starts_with(s, e, "START WAL LOCATION: ", &s))
64 : : {
65 [ - + ]: 31 : if (!parse_lsn(s, e, start_lsn, &c))
116 rhaas@postgresql.org 66 :UNC 0 : pg_fatal("%s: could not parse %s",
67 : : filename, "START WAL LOCATION");
116 rhaas@postgresql.org 68 [ + - - + ]:GNC 31 : if (c >= e || *c != ' ')
116 rhaas@postgresql.org 69 :UNC 0 : pg_fatal("%s: improper terminator for %s",
70 : : filename, "START WAL LOCATION");
116 rhaas@postgresql.org 71 :GNC 31 : found |= 1;
72 : : }
73 [ + + ]: 220 : else if (line_starts_with(s, e, "START TIMELINE: ", &s))
74 : : {
75 [ - + ]: 31 : if (!parse_tli(s, e, start_tli))
116 rhaas@postgresql.org 76 :UNC 0 : pg_fatal("%s: could not parse TLI for %s",
77 : : filename, "START TIMELINE");
116 rhaas@postgresql.org 78 [ - + ]:GNC 31 : if (*start_tli == 0)
116 rhaas@postgresql.org 79 :UNC 0 : pg_fatal("%s: invalid TLI", filename);
116 rhaas@postgresql.org 80 :GNC 31 : found |= 2;
81 : : }
82 [ + + ]: 189 : else if (line_starts_with(s, e, "INCREMENTAL FROM LSN: ", &s))
83 : : {
84 [ - + ]: 17 : if (!parse_lsn(s, e, previous_lsn, &c))
116 rhaas@postgresql.org 85 :UNC 0 : pg_fatal("%s: could not parse %s",
86 : : filename, "INCREMENTAL FROM LSN");
116 rhaas@postgresql.org 87 [ + - - + ]:GNC 17 : if (c >= e || *c != '\n')
116 rhaas@postgresql.org 88 :UNC 0 : pg_fatal("%s: improper terminator for %s",
89 : : filename, "INCREMENTAL FROM LSN");
116 rhaas@postgresql.org 90 :GNC 17 : found |= 4;
91 : : }
92 [ + + ]: 172 : else if (line_starts_with(s, e, "INCREMENTAL FROM TLI: ", &s))
93 : : {
94 [ - + ]: 17 : if (!parse_tli(s, e, previous_tli))
116 rhaas@postgresql.org 95 :UNC 0 : pg_fatal("%s: could not parse %s",
96 : : filename, "INCREMENTAL FROM TLI");
116 rhaas@postgresql.org 97 [ - + ]:GNC 17 : if (*previous_tli == 0)
116 rhaas@postgresql.org 98 :UNC 0 : pg_fatal("%s: invalid TLI", filename);
116 rhaas@postgresql.org 99 :GNC 17 : found |= 8;
100 : : }
101 : :
102 : 251 : buf->cursor = eo;
103 : : }
104 : :
105 [ - + ]: 31 : if ((found & 1) == 0)
116 rhaas@postgresql.org 106 :UNC 0 : pg_fatal("%s: could not find %s", filename, "START WAL LOCATION");
116 rhaas@postgresql.org 107 [ - + ]:GNC 31 : if ((found & 2) == 0)
116 rhaas@postgresql.org 108 :UNC 0 : pg_fatal("%s: could not find %s", filename, "START TIMELINE");
116 rhaas@postgresql.org 109 [ + + - + ]:GNC 31 : if ((found & 4) != 0 && (found & 8) == 0)
116 rhaas@postgresql.org 110 :UNC 0 : pg_fatal("%s: %s requires %s", filename,
111 : : "INCREMENTAL FROM LSN", "INCREMENTAL FROM TLI");
116 rhaas@postgresql.org 112 [ + + - + ]:GNC 31 : if ((found & 8) != 0 && (found & 4) == 0)
116 rhaas@postgresql.org 113 :UNC 0 : pg_fatal("%s: %s requires %s", filename,
114 : : "INCREMENTAL FROM TLI", "INCREMENTAL FROM LSN");
116 rhaas@postgresql.org 115 :GNC 31 : }
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 : 10 : 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 : 10 : pg_checksum_init(&checksum_ctx, checksum_type);
137 : :
138 : 10 : snprintf(output_filename, MAXPGPATH, "%s/backup_label", output_directory);
139 : :
140 [ - + ]: 10 : if ((output_fd = open(output_filename,
141 : : O_WRONLY | O_CREAT | O_EXCL | PG_BINARY,
142 : : pg_file_create_mode)) < 0)
116 rhaas@postgresql.org 143 :UNC 0 : pg_fatal("could not open file \"%s\": %m", output_filename);
144 : :
116 rhaas@postgresql.org 145 [ + + ]:GNC 94 : while (buf->cursor < buf->len)
146 : : {
147 : 84 : char *s = &buf->data[buf->cursor];
148 : 84 : int eo = get_eol_offset(buf);
149 : 84 : char *e = &buf->data[eo];
150 : :
151 [ + + ]: 84 : if (!line_starts_with(s, e, "INCREMENTAL FROM LSN: ", NULL) &&
152 [ + + ]: 77 : !line_starts_with(s, e, "INCREMENTAL FROM TLI: ", NULL))
153 : : {
154 : : ssize_t wb;
155 : :
156 : 70 : wb = write(output_fd, s, e - s);
157 [ - + ]: 70 : if (wb != e - s)
158 : : {
116 rhaas@postgresql.org 159 [ # # ]:UNC 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 only %d of %d bytes",
163 : : output_filename, (int) wb, (int) (e - s));
164 : : }
116 rhaas@postgresql.org 165 [ - + ]:GNC 70 : if (pg_checksum_update(&checksum_ctx, (uint8 *) s, e - s) < 0)
116 rhaas@postgresql.org 166 :UNC 0 : pg_fatal("could not update checksum of file \"%s\"",
167 : : output_filename);
168 : : }
169 : :
116 rhaas@postgresql.org 170 :GNC 84 : buf->cursor = eo;
171 : : }
172 : :
173 [ - + ]: 10 : if (close(output_fd) != 0)
116 rhaas@postgresql.org 174 :UNC 0 : pg_fatal("could not close \"%s\": %m", output_filename);
175 : :
116 rhaas@postgresql.org 176 :GNC 10 : checksum_length = pg_checksum_final(&checksum_ctx, checksum_payload);
177 : :
178 [ + + ]: 10 : 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 [ - + ]: 9 : if (stat(output_filename, &sb) < 0)
116 rhaas@postgresql.org 187 :UNC 0 : pg_fatal("could not stat file \"%s\": %m", output_filename);
116 rhaas@postgresql.org 188 :GNC 9 : add_file_to_manifest(mwriter, "backup_label", sb.st_size,
189 : : sb.st_mtime, checksum_type,
190 : : checksum_length, checksum_payload);
191 : : }
192 : 10 : }
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 : 335 : get_eol_offset(StringInfo buf)
202 : : {
203 : 335 : int eo = buf->cursor;
204 : :
205 [ + - ]: 10569 : while (eo < buf->len)
206 : : {
207 [ + + ]: 10569 : if (buf->data[eo] == '\n')
208 : 335 : return eo + 1;
209 : 10234 : ++eo;
210 : : }
211 : :
116 rhaas@postgresql.org 212 :UNC 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
116 rhaas@postgresql.org 224 :GNC 993 : line_starts_with(char *s, char *e, char *match, char **sout)
225 : : {
226 [ + - + + : 4255 : while (s < e && *match != '\0' && *s == *match)
+ + ]
227 : 3262 : ++s, ++match;
228 : :
229 [ + + + + ]: 993 : if (*match == '\0' && sout != NULL)
230 : 96 : *sout = s;
231 : :
232 : 993 : 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 : 48 : parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c)
242 : : {
243 : 48 : char save = *e;
244 : : int nchars;
245 : : bool success;
246 : : unsigned hi;
247 : : unsigned lo;
248 : :
249 : 48 : *e = '\0';
250 : 48 : success = (sscanf(s, "%X/%X%n", &hi, &lo, &nchars) == 2);
251 : 48 : *e = save;
252 : :
253 [ + - ]: 48 : if (success)
254 : : {
255 : 48 : *lsn = ((XLogRecPtr) hi) << 32 | (XLogRecPtr) lo;
256 : 48 : *c = s + nchars;
257 : : }
258 : :
259 : 48 : 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 : 48 : parse_tli(char *s, char *e, TimeLineID *tli)
270 : : {
271 : 48 : char save = *e;
272 : : int nchars;
273 : : bool success;
274 : :
275 : 48 : *e = '\0';
276 : 48 : success = (sscanf(s, "%u%n", tli, &nchars) == 1);
277 : 48 : *e = save;
278 : :
279 [ + - - + ]: 48 : if (success && s[nchars] != '\n')
116 rhaas@postgresql.org 280 :UNC 0 : success = false;
281 : :
116 rhaas@postgresql.org 282 :GNC 48 : return success;
283 : : }
|