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