1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 /* Copyright © 2019 Oracle and/or its affiliates. */
5 /* Generally speaking, the pstore contains a small number of files
6 * that in turn contain a small amount of data. */
10 #include <sys/prctl.h>
11 #include <sys/xattr.h>
14 #include "sd-daemon.h"
15 #include "sd-journal.h"
17 #include "sd-messages.h"
20 #include "alloc-util.h"
21 #include "capability-util.h"
22 #include "cgroup-util.h"
24 #include "conf-parser.h"
26 #include "dirent-util.h"
32 #include "journal-importer.h"
35 #include "main-func.h"
37 #include "parse-util.h"
38 #include "process-util.h"
39 #include "signal-util.h"
40 #include "socket-util.h"
42 #include "sort-util.h"
43 #include "string-table.h"
44 #include "string-util.h"
46 #include "tmpfile-util.h"
47 #include "user-util.h"
50 /* Command line argument handling */
51 typedef enum PStoreStorage
{
53 PSTORE_STORAGE_EXTERNAL
,
54 PSTORE_STORAGE_JOURNAL
,
56 _PSTORE_STORAGE_INVALID
= -1
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",
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");
68 static PStoreStorage arg_storage
= PSTORE_STORAGE_EXTERNAL
;
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";
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
},
81 return config_parse_many_nulstr(PKGSYSCONFDIR
"/pstore.conf",
82 CONF_PATHS_NULSTR("systemd/pstore.conf.d"),
84 config_item_table_lookup
, items
,
85 CONFIG_PARSE_WARN
, NULL
);
88 /* File list handling - PStoreEntry is the struct and
89 * and PStoreEntry is the type that contains all info
90 * about a pstore entry. */
91 typedef struct PStoreEntry
{
99 typedef struct PStoreList
{
100 PStoreEntry
*entries
;
102 size_t n_entries_allocated
;
105 static void pstore_entries_reset(PStoreList
*list
) {
106 for (size_t i
= 0; i
< list
->n_entries
; i
++)
107 free(list
->entries
[i
].content
);
112 static int compare_pstore_entries(const void *_a
, const void *_b
) {
113 PStoreEntry
*a
= (PStoreEntry
*)_a
, *b
= (PStoreEntry
*)_b
;
114 return strcmp(a
->dirent
.d_name
, b
->dirent
.d_name
);
117 static int move_file(PStoreEntry
*pe
, const char *subdir
) {
118 _cleanup_free_
char *ifd_path
= NULL
, *ofd_path
= NULL
;
119 _cleanup_free_
void *field
= NULL
;
120 const char *suffix
, *message
;
121 struct iovec iovec
[2];
127 ifd_path
= path_join(arg_sourcedir
, pe
->dirent
.d_name
);
131 ofd_path
= path_join(arg_archivedir
, subdir
, pe
->dirent
.d_name
);
135 /* Always log to the journal */
136 suffix
= arg_storage
== PSTORE_STORAGE_EXTERNAL
? strjoina(" moved to ", ofd_path
) : (char *)".";
137 message
= strjoina("MESSAGE=PStore ", pe
->dirent
.d_name
, suffix
);
138 iovec
[n_iovec
++] = IOVEC_MAKE_STRING(message
);
140 if (pe
->content_size
> 0) {
143 field_size
= strlen("FILE=") + pe
->content_size
;
144 field
= malloc(field_size
);
147 memcpy(stpcpy(field
, "FILE="), pe
->content
, pe
->content_size
);
148 iovec
[n_iovec
++] = IOVEC_MAKE(field
, field_size
);
151 r
= sd_journal_sendv(iovec
, n_iovec
);
153 return log_error_errno(r
, "Failed to log pstore entry: %m");
155 if (arg_storage
== PSTORE_STORAGE_EXTERNAL
) {
156 /* Move file from pstore to external storage */
157 r
= mkdir_parents(ofd_path
, 0755);
159 return log_error_errno(r
, "Failed to create directory %s: %m", ofd_path
);
160 r
= copy_file_atomic(ifd_path
, ofd_path
, 0600, 0, 0, COPY_REPLACE
);
162 return log_error_errno(r
, "Failed to copy_file_atomic: %s to %s", ifd_path
, ofd_path
);
165 /* If file copied properly, remove it from pstore */
167 (void) unlink(ifd_path
);
174 static int write_dmesg(const char *dmesg
, size_t size
, const char *id
) {
175 _cleanup_(unlink_and_freep
) char *tmp_path
= NULL
;
176 _cleanup_free_
char *ofd_path
= NULL
;
177 _cleanup_close_
int ofd
= -1;
186 ofd_path
= path_join(arg_archivedir
, id
, "dmesg.txt");
190 ofd
= open_tmpfile_linkable(ofd_path
, O_CLOEXEC
|O_CREAT
|O_TRUNC
|O_WRONLY
, &tmp_path
);
192 return log_error_errno(ofd
, "Failed to open temporary file %s: %m", ofd_path
);
193 wr
= write(ofd
, dmesg
, size
);
195 return log_error_errno(errno
, "Failed to store dmesg to %s: %m", ofd_path
);
196 if (wr
!= (ssize_t
)size
)
197 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to store dmesg to %s. %zu bytes are lost.", ofd_path
, size
- wr
);
198 r
= link_tmpfile(ofd
, tmp_path
, ofd_path
);
200 return log_error_errno(r
, "Failed to write temporary file %s: %m", ofd_path
);
201 tmp_path
= mfree(tmp_path
);
206 static void process_dmesg_files(PStoreList
*list
) {
207 /* Move files, reconstruct dmesg.txt */
208 _cleanup_free_
char *dmesg
= NULL
, *dmesg_id
= NULL
;
209 size_t dmesg_size
= 0, dmesg_allocated
= 0;
210 bool dmesg_bad
= false;
213 /* Handle each dmesg file: files processed in reverse
214 * order so as to properly reconstruct original dmesg */
215 for (size_t n
= list
->n_entries
; n
> 0; n
--) {
216 bool move_file_and_continue
= false;
217 _cleanup_free_
char *pe_id
= NULL
;
221 pe
= &list
->entries
[n
-1];
225 if (!startswith(pe
->dirent
.d_name
, "dmesg-"))
228 if (endswith(pe
->dirent
.d_name
, ".enc.z")) /* indicates a problem */
229 move_file_and_continue
= true;
230 p
= strrchr(pe
->dirent
.d_name
, '-');
232 move_file_and_continue
= true;
234 if (move_file_and_continue
) {
235 /* A dmesg file on which we do NO additional processing */
236 (void) move_file(pe
, NULL
);
240 /* See if this file is one of a related group of files
241 * in order to reconstruct dmesg */
243 /* When dmesg is written into pstore, it is done so in
244 * small chunks, whatever the exchange buffer size is
245 * with the underlying pstore backend (ie. EFI may be
246 * ~2KiB), which means an example pstore with approximately
247 * 64KB of storage may have up to roughly 32 dmesg files
248 * that could be related, depending upon the size of the
251 * Here we look at the dmesg filename and try to discern
252 * if files are part of a related group, meaning the same
255 * The two known pstore backends are EFI and ERST. These
256 * backends store data in the Common Platform Error
257 * Record, CPER, format. The dmesg- filename contains the
258 * CPER record id, a 64bit number (in decimal notation).
259 * In Linux, the record id is encoded with two digits for
260 * the dmesg part (chunk) number and 3 digits for the
261 * count number. So allowing an additional digit to
262 * compensate for advancing time, this code ignores the
263 * last six digits of the filename in determining the
266 * For the EFI backend, the record id encodes an id in the
267 * upper 32 bits, and a timestamp in the lower 32-bits.
268 * So ignoring the least significant 6 digits has proven
269 * to generally identify related dmesg entries. */
270 #define PSTORE_FILENAME_IGNORE 6
272 /* determine common portion of record id */
273 ++p
; /* move beyond dmesg- */
275 if (plen
> PSTORE_FILENAME_IGNORE
) {
276 pe_id
= memdup_suffix0(p
, plen
- PSTORE_FILENAME_IGNORE
);
282 pe_id
= mfree(pe_id
);
284 /* Now move file from pstore to archive storage */
285 move_file(pe
, pe_id
);
290 /* If the current record id is NOT the same as the
291 * previous record id, then start a new dmesg.txt file */
292 if (!streq_ptr(pe_id
, dmesg_id
)) {
293 /* Encountered a new dmesg group, close out old one, open new one */
294 (void) write_dmesg(dmesg
, dmesg_size
, dmesg_id
);
297 /* now point dmesg_id to storage of pe_id */
298 free_and_replace(dmesg_id
, pe_id
);
301 /* Reconstruction of dmesg is done as a useful courtesy: do not fail, but don't write garbled
303 size_t needed
= strlen(pe
->dirent
.d_name
) + strlen(":\n") + pe
->content_size
+ 1;
304 if (!GREEDY_REALLOC(dmesg
, dmesg_allocated
, dmesg_size
+ needed
)) {
305 log_warning_errno(ENOMEM
, "Failed to write dmesg file: %m");
310 dmesg_size
+= sprintf(dmesg
+ dmesg_size
, "%s:\n", pe
->dirent
.d_name
);
312 memcpy(dmesg
+ dmesg_size
, pe
->content
, pe
->content_size
);
313 dmesg_size
+= pe
->content_size
;
316 pe_id
= mfree(pe_id
);
320 (void) write_dmesg(dmesg
, dmesg_size
, dmesg_id
);
323 static int list_files(PStoreList
*list
, const char *sourcepath
) {
324 _cleanup_(closedirp
) DIR *dirp
= NULL
;
328 dirp
= opendir(sourcepath
);
330 return log_error_errno(errno
, "Failed to opendir %s: %m", sourcepath
);
332 FOREACH_DIRENT(de
, dirp
, return log_error_errno(errno
, "Failed to iterate through %s: %m", sourcepath
)) {
333 _cleanup_free_
char *ifd_path
= NULL
;
335 ifd_path
= path_join(sourcepath
, de
->d_name
);
339 _cleanup_free_
char *buf
= NULL
;
342 /* Now read contents of pstore file */
343 r
= read_full_file(ifd_path
, &buf
, &buf_size
);
345 log_warning_errno(r
, "Failed to read file %s, skipping: %m", ifd_path
);
349 if (!GREEDY_REALLOC(list
->entries
, list
->n_entries_allocated
, list
->n_entries
+ 1))
352 list
->entries
[list
->n_entries
++] = (PStoreEntry
) {
354 .content
= TAKE_PTR(buf
),
355 .content_size
= buf_size
,
364 static int run(int argc
, char *argv
[]) {
365 _cleanup_(pstore_entries_reset
) PStoreList list
= {};
371 arg_sourcedir
= argv
[1];
372 arg_archivedir
= argv
[2];
374 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
375 "This program takes zero or two arguments.");
377 /* Ignore all parse errors */
378 (void) parse_config();
380 log_debug("Selected storage: %s.", pstore_storage_to_string(arg_storage
));
381 log_debug("Selected unlink: %s.", yes_no(arg_unlink
));
383 if (arg_storage
== PSTORE_STORAGE_NONE
)
384 /* Do nothing, intentionally, leaving pstore untouched */
387 /* Obtain list of files in pstore */
388 r
= list_files(&list
, arg_sourcedir
);
392 /* Handle each pstore file */
393 /* Sort files lexigraphically ascending, generally needed by all */
394 qsort_safe(list
.entries
, list
.n_entries
, sizeof(PStoreEntry
), compare_pstore_entries
);
396 /* Process known file types */
397 process_dmesg_files(&list
);
399 /* Move left over files out of pstore */
400 for (size_t n
= 0; n
< list
.n_entries
; n
++)
401 move_file(&list
.entries
[n
], NULL
);
406 DEFINE_MAIN_FUNCTION(run
);