Branch data Line data Source code
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 */
52 : : typedef 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 : :
60 : : static 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 : :
66 [ # # # # ]: 0 : DEFINE_PRIVATE_STRING_TABLE_LOOKUP(pstore_storage, PStoreStorage);
67 [ # # # # : 0 : static DEFINE_CONFIG_PARSE_ENUM(config_parse_pstore_storage, pstore_storage, PStoreStorage, "Failed to parse storage setting");
# # # # #
# # # ]
68 : :
69 : : static PStoreStorage arg_storage = PSTORE_STORAGE_EXTERNAL;
70 : :
71 : : static bool arg_unlink = true;
72 : : static const char *arg_sourcedir = "/sys/fs/pstore";
73 : : static const char *arg_archivedir = "/var/lib/systemd/pstore";
74 : :
75 : 0 : static 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 : 0 : 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. */
92 : : typedef struct PStoreEntry {
93 : : struct dirent dirent;
94 : : bool is_binary;
95 : : bool handled;
96 : : char *content;
97 : : size_t content_size;
98 : : } PStoreEntry;
99 : :
100 : : typedef struct PStoreList {
101 : : PStoreEntry *entries;
102 : : size_t n_entries;
103 : : size_t n_entries_allocated;
104 : : } PStoreList;
105 : :
106 : 0 : static void pstore_entries_reset(PStoreList *list) {
107 [ # # ]: 0 : for (size_t i = 0; i < list->n_entries; i++)
108 : 0 : free(list->entries[i].content);
109 : 0 : free(list->entries);
110 : 0 : list->n_entries = 0;
111 : 0 : }
112 : :
113 : 0 : static int compare_pstore_entries(const void *_a, const void *_b) {
114 : 0 : PStoreEntry *a = (PStoreEntry *)_a, *b = (PStoreEntry *)_b;
115 : 0 : return strcmp(a->dirent.d_name, b->dirent.d_name);
116 : : }
117 : :
118 : 0 : static int move_file(PStoreEntry *pe, const char *subdir) {
119 : 0 : _cleanup_free_ char *ifd_path = NULL, *ofd_path = NULL;
120 : : const char *suffix, *message;
121 : : struct iovec iovec[2];
122 : 0 : int n_iovec = 0, r;
123 : :
124 [ # # ]: 0 : if (pe->handled)
125 : 0 : return 0;
126 : :
127 : 0 : ifd_path = path_join(arg_sourcedir, pe->dirent.d_name);
128 [ # # ]: 0 : if (!ifd_path)
129 : 0 : return log_oom();
130 : :
131 : 0 : ofd_path = path_join(arg_archivedir, subdir, pe->dirent.d_name);
132 [ # # ]: 0 : if (!ofd_path)
133 : 0 : return log_oom();
134 : :
135 : : /* Always log to the journal */
136 [ # # # # : 0 : suffix = arg_storage == PSTORE_STORAGE_EXTERNAL ? strjoina(" moved to ", ofd_path) : (char *)".";
# # # # #
# # # #
# ]
137 [ # # # # : 0 : message = strjoina("MESSAGE=PStore ", pe->dirent.d_name, suffix);
# # # # #
# # # ]
138 : 0 : iovec[n_iovec++] = IOVEC_MAKE_STRING(message);
139 : :
140 [ # # ]: 0 : if (pe->content_size > 0) {
141 [ # # ]: 0 : _cleanup_free_ void *field = NULL;
142 : : size_t field_size;
143 : :
144 : 0 : field_size = strlen("FILE=") + pe->content_size;
145 : 0 : field = malloc(field_size);
146 [ # # ]: 0 : if (!field)
147 : 0 : return log_oom();
148 : 0 : memcpy(stpcpy(field, "FILE="), pe->content, pe->content_size);
149 : 0 : iovec[n_iovec++] = IOVEC_MAKE(field, field_size);
150 : : }
151 : :
152 : 0 : r = sd_journal_sendv(iovec, n_iovec);
153 [ # # ]: 0 : if (r < 0)
154 [ # # ]: 0 : return log_error_errno(r, "Failed to log pstore entry: %m");
155 : :
156 [ # # ]: 0 : if (arg_storage == PSTORE_STORAGE_EXTERNAL) {
157 : : /* Move file from pstore to external storage */
158 : 0 : r = mkdir_parents(ofd_path, 0755);
159 [ # # ]: 0 : if (r < 0)
160 [ # # ]: 0 : return log_error_errno(r, "Failed to create directoy %s: %m", ofd_path);
161 : 0 : r = copy_file_atomic(ifd_path, ofd_path, 0600, 0, 0, COPY_REPLACE);
162 [ # # ]: 0 : if (r < 0)
163 [ # # ]: 0 : 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 [ # # ]: 0 : if (arg_unlink)
168 : 0 : (void) unlink(ifd_path);
169 : :
170 : 0 : pe->handled = true;
171 : :
172 : 0 : return 0;
173 : : }
174 : :
175 : 0 : static int write_dmesg(const char *dmesg, size_t size, const char *id) {
176 : 0 : _cleanup_(unlink_and_freep) char *tmp_path = NULL;
177 : 0 : _cleanup_free_ char *ofd_path = NULL;
178 : 0 : _cleanup_close_ int ofd = -1;
179 : : ssize_t wr;
180 : : int r;
181 : :
182 [ # # # # ]: 0 : if (isempty(dmesg) || size == 0)
183 : 0 : return 0;
184 : :
185 : 0 : ofd_path = path_join(arg_archivedir, id, "dmesg.txt");
186 [ # # ]: 0 : if (!ofd_path)
187 : 0 : return log_oom();
188 : :
189 : 0 : ofd = open_tmpfile_linkable(ofd_path, O_CLOEXEC|O_CREAT|O_TRUNC|O_WRONLY, &tmp_path);
190 [ # # ]: 0 : if (ofd < 0)
191 [ # # ]: 0 : return log_error_errno(ofd, "Failed to open temporary file %s: %m", ofd_path);
192 : 0 : wr = write(ofd, dmesg, size);
193 [ # # ]: 0 : if (wr < 0)
194 [ # # ]: 0 : return log_error_errno(errno, "Failed to store dmesg to %s: %m", ofd_path);
195 [ # # ]: 0 : if (wr != (ssize_t)size)
196 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to store dmesg to %s. %zu bytes are lost.", ofd_path, size - wr);
197 : 0 : r = link_tmpfile(ofd, tmp_path, ofd_path);
198 [ # # ]: 0 : if (r < 0)
199 [ # # ]: 0 : return log_error_errno(r, "Failed to write temporary file %s: %m", ofd_path);
200 : 0 : tmp_path = mfree(tmp_path);
201 : :
202 : 0 : return 0;
203 : : }
204 : :
205 : 0 : static void process_dmesg_files(PStoreList *list) {
206 : : /* Move files, reconstruct dmesg.txt */
207 [ # # # # ]: 0 : _cleanup_free_ char *dmesg = NULL, *dmesg_id = NULL;
208 : 0 : size_t dmesg_size = 0;
209 : : PStoreEntry *pe;
210 : :
211 : : /* Handle each dmesg file: files processed in reverse
212 : : * order so as to properly reconstruct original dmesg */
213 [ # # ]: 0 : for (size_t n = list->n_entries; n > 0; n--) {
214 : 0 : bool move_file_and_continue = false;
215 [ # # # ]: 0 : _cleanup_free_ char *pe_id = NULL;
216 : : char *p;
217 : : size_t plen;
218 : :
219 : 0 : pe = &list->entries[n-1];
220 : :
221 [ # # ]: 0 : if (pe->handled)
222 : 0 : continue;
223 [ # # ]: 0 : if (!startswith(pe->dirent.d_name, "dmesg-"))
224 : 0 : continue;
225 : :
226 [ # # ]: 0 : if (endswith(pe->dirent.d_name, ".enc.z")) /* indicates a problem */
227 : 0 : move_file_and_continue = true;
228 : 0 : p = strrchr(pe->dirent.d_name, '-');
229 [ # # ]: 0 : if (!p)
230 : 0 : move_file_and_continue = true;
231 : :
232 [ # # ]: 0 : if (move_file_and_continue) {
233 : : /* A dmesg file on which we do NO additional processing */
234 : 0 : (void) move_file(pe, NULL);
235 : 0 : 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 : 0 : ++p; /* move beyond dmesg- */
272 : 0 : plen = strlen(p);
273 [ # # ]: 0 : if (plen > PSTORE_FILENAME_IGNORE) {
274 : 0 : pe_id = memdup_suffix0(p, plen - PSTORE_FILENAME_IGNORE);
275 [ # # ]: 0 : if (!pe_id) {
276 : 0 : log_oom();
277 : 0 : return;
278 : : }
279 : : } else
280 : 0 : pe_id = mfree(pe_id);
281 : :
282 : : /* Now move file from pstore to archive storage */
283 : 0 : 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 [ # # # # : 0 : if (!pe_id || !dmesg_id || !streq(pe_id, dmesg_id)) {
# # ]
288 : : /* Encountered a new dmesg group, close out old one, open new one */
289 [ # # ]: 0 : if (dmesg) {
290 : 0 : (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
291 : 0 : dmesg = mfree(dmesg);
292 : 0 : dmesg_size = 0;
293 : : }
294 : :
295 : : /* now point dmesg_id to storage of pe_id */
296 : 0 : free_and_replace(dmesg_id, pe_id);
297 : : }
298 : :
299 : : /* Reconstruction of dmesg is done as a useful courtesy, do not log errors */
300 : 0 : dmesg = realloc(dmesg, dmesg_size + strlen(pe->dirent.d_name) + strlen(":\n") + pe->content_size + 1);
301 [ # # ]: 0 : if (dmesg) {
302 : 0 : dmesg_size += sprintf(&dmesg[dmesg_size], "%s:\n", pe->dirent.d_name);
303 [ # # ]: 0 : if (pe->content) {
304 : 0 : memcpy(&dmesg[dmesg_size], pe->content, pe->content_size);
305 : 0 : dmesg_size += pe->content_size;
306 : : }
307 : : }
308 : :
309 : 0 : pe_id = mfree(pe_id);
310 : : }
311 [ # # ]: 0 : if (dmesg)
312 : 0 : (void) write_dmesg(dmesg, dmesg_size, dmesg_id);
313 : : }
314 : :
315 : 0 : static int list_files(PStoreList *list, const char *sourcepath) {
316 : 0 : _cleanup_(closedirp) DIR *dirp = NULL;
317 : : struct dirent *de;
318 : : int r;
319 : :
320 : 0 : dirp = opendir(sourcepath);
321 [ # # ]: 0 : if (!dirp)
322 [ # # ]: 0 : return log_error_errno(errno, "Failed to opendir %s: %m", sourcepath);
323 : :
324 [ # # # # : 0 : FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", sourcepath)) {
# # # # ]
325 [ # # # ]: 0 : _cleanup_free_ char *ifd_path = NULL;
326 : :
327 : 0 : ifd_path = path_join(sourcepath, de->d_name);
328 [ # # ]: 0 : if (!ifd_path)
329 : 0 : return log_oom();
330 : :
331 [ # # # ]: 0 : _cleanup_free_ char *buf = NULL;
332 : : size_t buf_size;
333 : :
334 : : /* Now read contents of pstore file */
335 : 0 : r = read_full_file(ifd_path, &buf, &buf_size);
336 [ # # ]: 0 : if (r < 0) {
337 [ # # ]: 0 : log_warning_errno(r, "Failed to read file %s, skipping: %m", ifd_path);
338 : 0 : continue;
339 : : }
340 : :
341 [ # # ]: 0 : if (!GREEDY_REALLOC(list->entries, list->n_entries_allocated, list->n_entries + 1))
342 : 0 : return log_oom();
343 : :
344 : 0 : list->entries[list->n_entries++] = (PStoreEntry) {
345 : 0 : .dirent = *de,
346 : 0 : .content = TAKE_PTR(buf),
347 : : .content_size = buf_size,
348 : : .is_binary = true,
349 : : .handled = false,
350 : : };
351 : : }
352 : :
353 : 0 : return 0;
354 : : }
355 : :
356 : 0 : static int run(int argc, char *argv[]) {
357 : 0 : _cleanup_(pstore_entries_reset) PStoreList list = {};
358 : : int r;
359 : :
360 : 0 : log_setup_service();
361 : :
362 [ # # ]: 0 : if (argc > 1)
363 [ # # ]: 0 : return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
364 : : "This program takes no arguments.");
365 : :
366 : : /* Ignore all parse errors */
367 : 0 : (void) parse_config();
368 : :
369 [ # # ]: 0 : log_debug("Selected storage '%s'.", pstore_storage_to_string(arg_storage));
370 [ # # ]: 0 : log_debug("Selected Unlink '%d'.", arg_unlink);
371 : :
372 [ # # ]: 0 : if (arg_storage == PSTORE_STORAGE_NONE)
373 : : /* Do nothing, intentionally, leaving pstore untouched */
374 : 0 : return 0;
375 : :
376 : : /* Obtain list of files in pstore */
377 : 0 : r = list_files(&list, arg_sourcedir);
378 [ # # ]: 0 : if (r < 0)
379 : 0 : return r;
380 : :
381 : : /* Handle each pstore file */
382 : : /* Sort files lexigraphically ascending, generally needed by all */
383 : 0 : qsort_safe(list.entries, list.n_entries, sizeof(PStoreEntry), compare_pstore_entries);
384 : :
385 : : /* Process known file types */
386 : 0 : process_dmesg_files(&list);
387 : :
388 : : /* Move left over files out of pstore */
389 [ # # ]: 0 : for (size_t n = 0; n < list.n_entries; n++)
390 : 0 : move_file(&list.entries[n], NULL);
391 : :
392 : 0 : return 0;
393 : : }
394 : :
395 : 0 : DEFINE_MAIN_FUNCTION(run);
|