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