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
45 62 : parse_backup_label(char *filename, StringInfo buf,
46 : TimeLineID *start_tli, XLogRecPtr *start_lsn,
47 : TimeLineID *previous_tli, XLogRecPtr *previous_lsn)
48 : {
49 62 : int found = 0;
50 :
51 62 : *start_tli = 0;
52 62 : *start_lsn = InvalidXLogRecPtr;
53 62 : *previous_tli = 0;
54 62 : *previous_lsn = InvalidXLogRecPtr;
55 :
56 564 : while (buf->cursor < buf->len)
57 : {
58 502 : char *s = &buf->data[buf->cursor];
59 502 : int eo = get_eol_offset(buf);
60 502 : char *e = &buf->data[eo];
61 : char *c;
62 :
63 502 : if (line_starts_with(s, e, "START WAL LOCATION: ", &s))
64 : {
65 62 : if (!parse_lsn(s, e, start_lsn, &c))
66 0 : pg_fatal("%s: could not parse %s",
67 : filename, "START WAL LOCATION");
68 62 : if (c >= e || *c != ' ')
69 0 : pg_fatal("%s: improper terminator for %s",
70 : filename, "START WAL LOCATION");
71 62 : found |= 1;
72 : }
73 440 : else if (line_starts_with(s, e, "START TIMELINE: ", &s))
74 : {
75 62 : if (!parse_tli(s, e, start_tli))
76 0 : pg_fatal("%s: could not parse TLI for %s",
77 : filename, "START TIMELINE");
78 62 : if (*start_tli == 0)
79 0 : pg_fatal("%s: invalid TLI", filename);
80 62 : found |= 2;
81 : }
82 378 : else if (line_starts_with(s, e, "INCREMENTAL FROM LSN: ", &s))
83 : {
84 34 : if (!parse_lsn(s, e, previous_lsn, &c))
85 0 : pg_fatal("%s: could not parse %s",
86 : filename, "INCREMENTAL FROM LSN");
87 34 : if (c >= e || *c != '\n')
88 0 : pg_fatal("%s: improper terminator for %s",
89 : filename, "INCREMENTAL FROM LSN");
90 34 : found |= 4;
91 : }
92 344 : else if (line_starts_with(s, e, "INCREMENTAL FROM TLI: ", &s))
93 : {
94 34 : if (!parse_tli(s, e, previous_tli))
95 0 : pg_fatal("%s: could not parse %s",
96 : filename, "INCREMENTAL FROM TLI");
97 34 : if (*previous_tli == 0)
98 0 : pg_fatal("%s: invalid TLI", filename);
99 34 : found |= 8;
100 : }
101 :
102 502 : buf->cursor = eo;
103 : }
104 :
105 62 : if ((found & 1) == 0)
106 0 : pg_fatal("%s: could not find %s", filename, "START WAL LOCATION");
107 62 : if ((found & 2) == 0)
108 0 : pg_fatal("%s: could not find %s", filename, "START TIMELINE");
109 62 : if ((found & 4) != 0 && (found & 8) == 0)
110 0 : pg_fatal("%s: %s requires %s", filename,
111 : "INCREMENTAL FROM LSN", "INCREMENTAL FROM TLI");
112 62 : if ((found & 8) != 0 && (found & 4) == 0)
113 0 : pg_fatal("%s: %s requires %s", filename,
114 : "INCREMENTAL FROM TLI", "INCREMENTAL FROM LSN");
115 62 : }
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 20 : 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 20 : pg_checksum_init(&checksum_ctx, checksum_type);
137 :
138 20 : snprintf(output_filename, MAXPGPATH, "%s/backup_label", output_directory);
139 :
140 20 : 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 188 : while (buf->cursor < buf->len)
146 : {
147 168 : char *s = &buf->data[buf->cursor];
148 168 : int eo = get_eol_offset(buf);
149 168 : char *e = &buf->data[eo];
150 :
151 168 : if (!line_starts_with(s, e, "INCREMENTAL FROM LSN: ", NULL) &&
152 154 : !line_starts_with(s, e, "INCREMENTAL FROM TLI: ", NULL))
153 : {
154 : ssize_t wb;
155 :
156 140 : wb = write(output_fd, s, e - s);
157 140 : 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 only %d of %d bytes",
163 : output_filename, (int) wb, (int) (e - s));
164 : }
165 140 : 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 168 : buf->cursor = eo;
171 : }
172 :
173 20 : if (close(output_fd) != 0)
174 0 : pg_fatal("could not close \"%s\": %m", output_filename);
175 :
176 20 : checksum_length = pg_checksum_final(&checksum_ctx, checksum_payload);
177 :
178 20 : 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 18 : if (stat(output_filename, &sb) < 0)
187 0 : pg_fatal("could not stat file \"%s\": %m", output_filename);
188 18 : add_file_to_manifest(mwriter, "backup_label", sb.st_size,
189 : sb.st_mtime, checksum_type,
190 : checksum_length, checksum_payload);
191 : }
192 20 : }
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 670 : get_eol_offset(StringInfo buf)
202 : {
203 670 : int eo = buf->cursor;
204 :
205 21138 : while (eo < buf->len)
206 : {
207 21138 : if (buf->data[eo] == '\n')
208 670 : return eo + 1;
209 20468 : ++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 1986 : line_starts_with(char *s, char *e, char *match, char **sout)
225 : {
226 8510 : while (s < e && *match != '\0' && *s == *match)
227 6524 : ++s, ++match;
228 :
229 1986 : if (*match == '\0' && sout != NULL)
230 192 : *sout = s;
231 :
232 1986 : 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 96 : parse_lsn(char *s, char *e, XLogRecPtr *lsn, char **c)
242 : {
243 96 : char save = *e;
244 : int nchars;
245 : bool success;
246 : unsigned hi;
247 : unsigned lo;
248 :
249 96 : *e = '\0';
250 96 : success = (sscanf(s, "%X/%X%n", &hi, &lo, &nchars) == 2);
251 96 : *e = save;
252 :
253 96 : if (success)
254 : {
255 96 : *lsn = ((XLogRecPtr) hi) << 32 | (XLogRecPtr) lo;
256 96 : *c = s + nchars;
257 : }
258 :
259 96 : 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 96 : parse_tli(char *s, char *e, TimeLineID *tli)
270 : {
271 96 : char save = *e;
272 : int nchars;
273 : bool success;
274 :
275 96 : *e = '\0';
276 96 : success = (sscanf(s, "%u%n", tli, &nchars) == 1);
277 96 : *e = save;
278 :
279 96 : if (success && s[nchars] != '\n')
280 0 : success = false;
281 :
282 96 : return success;
283 : }
|