]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/pstore/pstore.c
conf-parser: return mtime in config_parse() and friends
[thirdparty/systemd.git] / src / pstore / pstore.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 /* Copyright © 2019 Oracle and/or its affiliates. */
4
5 /* Generally speaking, the pstore contains a small number of files
6 * that in turn contain a small amount of data. */
7 #include <errno.h>
8 #include <stdio.h>
9 #include <stdio_ext.h>
10 #include <sys/prctl.h>
11 #include <sys/xattr.h>
12 #include <unistd.h>
13
14 #include "sd-daemon.h"
15 #include "sd-journal.h"
16 #include "sd-login.h"
17 #include "sd-messages.h"
18
19 #include "acl-util.h"
20 #include "alloc-util.h"
21 #include "capability-util.h"
22 #include "cgroup-util.h"
23 #include "compress.h"
24 #include "conf-parser.h"
25 #include "copy.h"
26 #include "dirent-util.h"
27 #include "escape.h"
28 #include "fd-util.h"
29 #include "fileio.h"
30 #include "fs-util.h"
31 #include "io-util.h"
32 #include "journal-importer.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "main-func.h"
36 #include "mkdir.h"
37 #include "parse-util.h"
38 #include "process-util.h"
39 #include "signal-util.h"
40 #include "socket-util.h"
41 #include "special.h"
42 #include "sort-util.h"
43 #include "string-table.h"
44 #include "string-util.h"
45 #include "strv.h"
46 #include "tmpfile-util.h"
47 #include "user-util.h"
48 #include "util.h"
49
50 /* Command line argument handling */
51 typedef enum PStoreStorage {
52 PSTORE_STORAGE_NONE,
53 PSTORE_STORAGE_EXTERNAL,
54 PSTORE_STORAGE_JOURNAL,
55 _PSTORE_STORAGE_MAX,
56 _PSTORE_STORAGE_INVALID = -1
57 } PStoreStorage;
58
59 static const char* const pstore_storage_table[_PSTORE_STORAGE_MAX] = {
60 [PSTORE_STORAGE_NONE] = "none",
61 [PSTORE_STORAGE_EXTERNAL] = "external",
62 [PSTORE_STORAGE_JOURNAL] = "journal",
63 };
64
65 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(pstore_storage, PStoreStorage);
66 static DEFINE_CONFIG_PARSE_ENUM(config_parse_pstore_storage, pstore_storage, PStoreStorage, "Failed to parse storage setting");
67
68 static PStoreStorage arg_storage = PSTORE_STORAGE_EXTERNAL;
69
70 static bool arg_unlink = true;
71 static const char *arg_sourcedir = "/sys/fs/pstore";
72 static const char *arg_archivedir = "/var/lib/systemd/pstore";
73
74 static int parse_config(void) {
75 static const ConfigTableItem items[] = {
76 { "PStore", "Unlink", config_parse_bool, 0, &arg_unlink },
77 { "PStore", "Storage", config_parse_pstore_storage, 0, &arg_storage },
78 {}
79 };
80
81 return config_parse_many_nulstr(
82 PKGSYSCONFDIR "/pstore.conf",
83 CONF_PATHS_NULSTR("systemd/pstore.conf.d"),
84 "PStore\0",
85 config_item_table_lookup, items,
86 CONFIG_PARSE_WARN,
87 NULL,
88 NULL);
89 }
90
91 /* File list handling - PStoreEntry is the struct and
92 * and PStoreEntry is the type that contains all info
93 * about a pstore entry. */
94 typedef struct PStoreEntry {
95 struct dirent dirent;
96 bool is_binary;
97 bool handled;
98 char *content;
99 size_t content_size;
100 } PStoreEntry;
101
102 typedef struct PStoreList {
103 PStoreEntry *entries;
104 size_t n_entries;
105 size_t n_entries_allocated;
106 } PStoreList;
107
108 static void pstore_entries_reset(PStoreList *list) {
109 for (size_t i = 0; i < list->n_entries; i++)
110 free(list->entries[i].content);
111 free(list->entries);
112 list->n_entries = 0;
113 }
114
115 static int compare_pstore_entries(const void *_a, const void *_b) {
116 PStoreEntry *a = (PStoreEntry *)_a, *b = (PStoreEntry *)_b;
117 return strcmp(a->dirent.d_name, b->dirent.d_name);
118 }
119
120 static int move_file(PStoreEntry *pe, const char *subdir) {
121 _cleanup_free_ char *ifd_path = NULL, *ofd_path = NULL;
122 _cleanup_free_ void *field = NULL;
123 const char *suffix, *message;
124 struct iovec iovec[2];
125 int n_iovec = 0, r;
126
127 if (pe->handled)
128 return 0;
129
130 ifd_path = path_join(arg_sourcedir, pe->dirent.d_name);
131 if (!ifd_path)
132 return log_oom();
133
134 ofd_path = path_join(arg_archivedir, subdir, pe->dirent.d_name);
135 if (!ofd_path)
136 return log_oom();
137
138 /* Always log to the journal */
139 suffix = arg_storage == PSTORE_STORAGE_EXTERNAL ? strjoina(" moved to ", ofd_path) : (char *)".";
140 message = strjoina("MESSAGE=PStore ", pe->dirent.d_name, suffix);
141 iovec[n_iovec++] = IOVEC_MAKE_STRING(message);
142
143 if (pe->content_size > 0) {
144 size_t field_size;
145
146 field_size = strlen("FILE=") + pe->content_size;
147 field = malloc(field_size);
148 if (!field)
149 return log_oom();
150 memcpy(stpcpy(field, "FILE="), pe->content, pe->content_size);
151 iovec[n_iovec++] = IOVEC_MAKE(field, field_size);
152 }
153
154 r = sd_journal_sendv(iovec, n_iovec);
155 if (r < 0)
156 return log_error_errno(r, "Failed to log pstore entry: %m");
157
158 if (arg_storage == PSTORE_STORAGE_EXTERNAL) {
159 /* Move file from pstore to external storage */
160 r = mkdir_parents(ofd_path, 0755);
161 if (r < 0)
162 return log_error_errno(r, "Failed to create directory %s: %m", ofd_path);
163 r = copy_file_atomic(ifd_path, ofd_path, 0600, 0, 0, COPY_REPLACE);
164 if (r < 0)
165 return log_error_errno(r, "Failed to copy_file_atomic: %s to %s", ifd_path, ofd_path);
166 }
167
168 /* If file copied properly, remove it from pstore */
169 if (arg_unlink)
170 (void) unlink(ifd_path);
171
172 pe->handled = true;
173
174 return 0;
175 }
176
177 static int write_dmesg(const char *dmesg, size_t size, const char *id) {
178 _cleanup_(unlink_and_freep) char *tmp_path = NULL;
179 _cleanup_free_ char *ofd_path = NULL;
180 _cleanup_close_ int ofd = -1;
181 ssize_t wr;
182 int r;
183
184 if (size == 0)
185 return 0;
186
187 assert(dmesg);
188
189 ofd_path = path_join(arg_archivedir, id, "dmesg.txt");
190 if (!ofd_path)
191 return log_oom();
192
193 ofd = open_tmpfile_linkable(ofd_path, O_CLOEXEC|O_CREAT|O_TRUNC|O_WRONLY, &tmp_path);
194 if (ofd < 0)
195 return log_error_errno(ofd, "Failed to open temporary file %s: %m", ofd_path);
196 wr = write(ofd, dmesg, size);
197 if (wr < 0)
198 return log_error_errno(errno, "Failed to store dmesg to %s: %m", ofd_path);
199 if (wr != (ssize_t)size)
200 return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to store dmesg to %s. %zu bytes are lost.", ofd_path, size - wr);
201 r = link_tmpfile(ofd, tmp_path, ofd_path);
202 if (r < 0)
203 return log_error_errno(r, "Failed to write temporary file %s: %m", ofd_path);
204 tmp_path = mfree(tmp_path);
205
206 return 0;
207 }
208
209 static void process_dmesg_files(PStoreList *list) {
210 /* Move files, reconstruct dmesg.txt */
211 _cleanup_free_ char *dmesg = NULL, *dmesg_id = NULL;
212 size_t dmesg_size = 0, dmesg_allocated = 0;
213 bool dmesg_bad = false;
214 PStoreEntry *pe;
215
216 /* Handle each dmesg file: files processed in reverse
217 * order so as to properly reconstruct original dmesg */
218 for (size_t n = list->n_entries; n > 0; n--) {
219 bool move_file_and_continue = false;
220 _cleanup_free_ char *pe_id = NULL;
221 char *p;
222 size_t plen;
223
224 pe = &list->entries[n-1];
225
226 if (pe->handled)
227 continue;
228 if (!startswith(pe->dirent.d_name, "dmesg-"))
229 continue;
230
231 if (endswith(pe->dirent.d_name, ".enc.z")) /* indicates a problem */
232 move_file_and_continue = true;
233 p = strrchr(pe->dirent.d_name, '-');
234 if (!p)
235 move_file_and_continue = true;
236
237 if (move_file_and_continue) {
238 /* A dmesg file on which we do NO additional processing */
239 (void) move_file(pe, NULL);
240 continue;
241 }
242
243 /* See if this file is one of a related group of files
244 * in order to reconstruct dmesg */
245
246 /* When dmesg is written into pstore, it is done so in
247 * small chunks, whatever the exchange buffer size is
248 * with the underlying pstore backend (ie. EFI may be
249 * ~2KiB), which means an example pstore with approximately
250 * 64KB of storage may have up to roughly 32 dmesg files
251 * that could be related, depending upon the size of the
252 * original dmesg.
253 *
254 * Here we look at the dmesg filename and try to discern
255 * if files are part of a related group, meaning the same
256 * original dmesg.
257 *
258 * The two known pstore backends are EFI and ERST. These
259 * backends store data in the Common Platform Error
260 * Record, CPER, format. The dmesg- filename contains the
261 * CPER record id, a 64bit number (in decimal notation).
262 * In Linux, the record id is encoded with two digits for
263 * the dmesg part (chunk) number and 3 digits for the
264 * count number. So allowing an additional digit to
265 * compensate for advancing time, this code ignores the
266 * last six digits of the filename in determining the
267 * record id.
268 *
269 * For the EFI backend, the record id encodes an id in the
270 * upper 32 bits, and a timestamp in the lower 32-bits.
271 * So ignoring the least significant 6 digits has proven
272 * to generally identify related dmesg entries. */
273 #define PSTORE_FILENAME_IGNORE 6
274
275 /* determine common portion of record id */
276 ++p; /* move beyond dmesg- */
277 plen = strlen(p);
278 if (plen > PSTORE_FILENAME_IGNORE) {
279 pe_id = memdup_suffix0(p, plen - PSTORE_FILENAME_IGNORE);
280 if (!pe_id) {
281 log_oom();
282 return;
283 }
284 } else
285 pe_id = mfree(pe_id);
286
287 /* Now move file from pstore to archive storage */
288 move_file(pe, pe_id);
289
290 if (dmesg_bad)
291 continue;
292
293 /* If the current record id is NOT the same as the
294 * previous record id, then start a new dmesg.txt file */
295 if (!streq_ptr(pe_id, dmesg_id)) {
296 /* Encountered a new dmesg group, close out old one, open new one */
297 (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
298 dmesg_size = 0;
299
300 /* now point dmesg_id to storage of pe_id */
301 free_and_replace(dmesg_id, pe_id);
302 }
303
304 /* Reconstruction of dmesg is done as a useful courtesy: do not fail, but don't write garbled
305 * output either. */
306 size_t needed = strlen(pe->dirent.d_name) + strlen(":\n") + pe->content_size + 1;
307 if (!GREEDY_REALLOC(dmesg, dmesg_allocated, dmesg_size + needed)) {
308 log_warning_errno(ENOMEM, "Failed to write dmesg file: %m");
309 dmesg_bad = true;
310 continue;
311 }
312
313 dmesg_size += sprintf(dmesg + dmesg_size, "%s:\n", pe->dirent.d_name);
314 if (pe->content) {
315 memcpy(dmesg + dmesg_size, pe->content, pe->content_size);
316 dmesg_size += pe->content_size;
317 }
318
319 pe_id = mfree(pe_id);
320 }
321
322 if (!dmesg_bad)
323 (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
324 }
325
326 static int list_files(PStoreList *list, const char *sourcepath) {
327 _cleanup_(closedirp) DIR *dirp = NULL;
328 struct dirent *de;
329 int r;
330
331 dirp = opendir(sourcepath);
332 if (!dirp)
333 return log_error_errno(errno, "Failed to opendir %s: %m", sourcepath);
334
335 FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", sourcepath)) {
336 _cleanup_free_ char *ifd_path = NULL;
337
338 ifd_path = path_join(sourcepath, de->d_name);
339 if (!ifd_path)
340 return log_oom();
341
342 _cleanup_free_ char *buf = NULL;
343 size_t buf_size;
344
345 /* Now read contents of pstore file */
346 r = read_full_file(ifd_path, &buf, &buf_size);
347 if (r < 0) {
348 log_warning_errno(r, "Failed to read file %s, skipping: %m", ifd_path);
349 continue;
350 }
351
352 if (!GREEDY_REALLOC(list->entries, list->n_entries_allocated, list->n_entries + 1))
353 return log_oom();
354
355 list->entries[list->n_entries++] = (PStoreEntry) {
356 .dirent = *de,
357 .content = TAKE_PTR(buf),
358 .content_size = buf_size,
359 .is_binary = true,
360 .handled = false,
361 };
362 }
363
364 return 0;
365 }
366
367 static int run(int argc, char *argv[]) {
368 _cleanup_(pstore_entries_reset) PStoreList list = {};
369 int r;
370
371 log_setup_service();
372
373 if (argc == 3) {
374 arg_sourcedir = argv[1];
375 arg_archivedir = argv[2];
376 } else if (argc > 1)
377 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
378 "This program takes zero or two arguments.");
379
380 /* Ignore all parse errors */
381 (void) parse_config();
382
383 log_debug("Selected storage: %s.", pstore_storage_to_string(arg_storage));
384 log_debug("Selected unlink: %s.", yes_no(arg_unlink));
385
386 if (arg_storage == PSTORE_STORAGE_NONE)
387 /* Do nothing, intentionally, leaving pstore untouched */
388 return 0;
389
390 /* Obtain list of files in pstore */
391 r = list_files(&list, arg_sourcedir);
392 if (r < 0)
393 return r;
394
395 /* Handle each pstore file */
396 /* Sort files lexigraphically ascending, generally needed by all */
397 qsort_safe(list.entries, list.n_entries, sizeof(PStoreEntry), compare_pstore_entries);
398
399 /* Process known file types */
400 process_dmesg_files(&list);
401
402 /* Move left over files out of pstore */
403 for (size_t n = 0; n < list.n_entries; n++)
404 move_file(&list.entries[n], NULL);
405
406 return 0;
407 }
408
409 DEFINE_MAIN_FUNCTION(run);