1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
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"
49 /* Command line argument handling */
50 typedef enum PStoreStorage
{
52 PSTORE_STORAGE_EXTERNAL
,
53 PSTORE_STORAGE_JOURNAL
,
55 _PSTORE_STORAGE_INVALID
= -EINVAL
,
58 static const char* const pstore_storage_table
[_PSTORE_STORAGE_MAX
] = {
59 [PSTORE_STORAGE_NONE
] = "none",
60 [PSTORE_STORAGE_EXTERNAL
] = "external",
61 [PSTORE_STORAGE_JOURNAL
] = "journal",
64 DEFINE_PRIVATE_STRING_TABLE_LOOKUP(pstore_storage
, PStoreStorage
);
65 static DEFINE_CONFIG_PARSE_ENUM(config_parse_pstore_storage
, pstore_storage
, PStoreStorage
, "Failed to parse storage setting");
67 static PStoreStorage arg_storage
= PSTORE_STORAGE_EXTERNAL
;
69 static bool arg_unlink
= true;
70 static const char *arg_sourcedir
= "/sys/fs/pstore";
71 static const char *arg_archivedir
= "/var/lib/systemd/pstore";
73 static int parse_config(void) {
74 static const ConfigTableItem items
[] = {
75 { "PStore", "Unlink", config_parse_bool
, 0, &arg_unlink
},
76 { "PStore", "Storage", config_parse_pstore_storage
, 0, &arg_storage
},
80 return config_parse_many_nulstr(
81 PKGSYSCONFDIR
"/pstore.conf",
82 CONF_PATHS_NULSTR("systemd/pstore.conf.d"),
84 config_item_table_lookup
, items
,
90 /* File list handling - PStoreEntry is the struct and
91 * and PStoreEntry is the type that contains all info
92 * about a pstore entry. */
93 typedef struct PStoreEntry
{
101 typedef struct PStoreList
{
102 PStoreEntry
*entries
;
106 static void pstore_entries_reset(PStoreList
*list
) {
107 for (size_t i
= 0; i
< list
->n_entries
; i
++)
108 free(list
->entries
[i
].content
);
113 static int compare_pstore_entries(const PStoreEntry
*a
, const PStoreEntry
*b
) {
114 return strcmp(a
->dirent
.d_name
, b
->dirent
.d_name
);
117 static int move_file(PStoreEntry
*pe
, const char *subdir1
, const char *subdir2
) {
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
, subdir1
, subdir2
, 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 append_dmesg(PStoreEntry
*pe
, const char *subdir1
, const char *subdir2
) {
175 /* Append dmesg chunk to end, create if needed */
176 _cleanup_free_
char *ofd_path
= NULL
;
177 _cleanup_close_
int ofd
= -1;
182 if (pe
->content_size
== 0)
185 ofd_path
= path_join(arg_archivedir
, subdir1
, subdir2
, "dmesg.txt");
189 ofd
= open(ofd_path
, O_CREAT
|O_NOFOLLOW
|O_NOCTTY
|O_CLOEXEC
|O_APPEND
|O_WRONLY
, 0640);
191 return log_error_errno(ofd
, "Failed to open file %s: %m", ofd_path
);
192 wr
= write(ofd
, pe
->content
, pe
->content_size
);
194 return log_error_errno(errno
, "Failed to store dmesg to %s: %m", ofd_path
);
195 if ((size_t)wr
!= pe
->content_size
)
196 return log_error_errno(SYNTHETIC_ERRNO(EIO
), "Failed to store dmesg to %s. %zu bytes are lost.", ofd_path
, pe
->content_size
- wr
);
201 static int process_dmesg_files(PStoreList
*list
) {
202 /* Move files, reconstruct dmesg.txt */
203 _cleanup_free_
char *erst_subdir
= NULL
;
204 uint64_t last_record_id
= 0;
206 /* When dmesg is written into pstore, it is done so in small chunks, whatever the exchange buffer
207 * size is with the underlying pstore backend (ie. EFI may be ~2KiB), which means an example
208 * pstore with approximately 64KB of storage may have up to roughly 32 dmesg files, some likely
211 * Here we look at the dmesg filename and try to discern if files are part of a related group,
212 * meaning the same original dmesg.
214 * The dmesg- filename contains the backend-type and the Common Platform Error Record, CPER,
215 * record id, a 64-bit number.
217 * Files are processed in reverse lexigraphical order so as to properly reconstruct original dmesg.*/
219 for (size_t n
= list
->n_entries
; n
> 0; n
--) {
223 pe
= &list
->entries
[n
-1];
227 if (endswith(pe
->dirent
.d_name
, ".enc.z")) /* indicates a problem */
229 if (!startswith(pe
->dirent
.d_name
, "dmesg-"))
232 if ((p
= startswith(pe
->dirent
.d_name
, "dmesg-efi-"))) {
233 /* For the EFI backend, the 3 least significant digits of record id encodes a
234 * "count" number, the next 2 least significant digits for the dmesg part
235 * (chunk) number, and the remaining digits as the timestamp. See
236 * linux/drivers/firmware/efi/efi-pstore.c in efi_pstore_write(). */
237 _cleanup_free_
char *subdir1
= NULL
, *subdir2
= NULL
;
238 size_t plen
= strlen(p
);
243 /* Extract base record id */
244 subdir1
= strndup(p
, plen
- 5);
247 /* Extract "count" field */
248 subdir2
= strndup(p
+ plen
- 3, 3);
252 /* Now move file from pstore to archive storage */
253 (void) move_file(pe
, subdir1
, subdir2
);
255 /* Append to the dmesg */
256 (void) append_dmesg(pe
, subdir1
, subdir2
);
257 } else if ((p
= startswith(pe
->dirent
.d_name
, "dmesg-erst-"))) {
258 /* For the ERST backend, the record is a monotonically increasing number, seeded as
259 * a timestamp. See linux/drivers/acpi/apei/erst.c in erst_writer(). */
262 if (safe_atou64(p
, &record_id
) < 0)
264 if (last_record_id
- 1 != record_id
)
265 /* A discontinuity in the number has been detected, this current record id
266 * will become the directory name for all pieces of the dmesg in this
268 if (free_and_strdup(&erst_subdir
, p
) < 0)
271 /* Now move file from pstore to archive storage */
272 (void) move_file(pe
, erst_subdir
, NULL
);
274 /* Append to the dmesg */
275 (void) append_dmesg(pe
, erst_subdir
, NULL
);
277 /* Update, but keep erst_subdir for next file */
278 last_record_id
= record_id
;
280 log_debug("Unknown backend, ignoring \"%s\".", pe
->dirent
.d_name
);
285 static int list_files(PStoreList
*list
, const char *sourcepath
) {
286 _cleanup_(closedirp
) DIR *dirp
= NULL
;
289 dirp
= opendir(sourcepath
);
291 return log_error_errno(errno
, "Failed to opendir %s: %m", sourcepath
);
293 FOREACH_DIRENT(de
, dirp
, return log_error_errno(errno
, "Failed to iterate through %s: %m", sourcepath
)) {
294 _cleanup_free_
char *ifd_path
= NULL
;
296 ifd_path
= path_join(sourcepath
, de
->d_name
);
300 _cleanup_free_
char *buf
= NULL
;
303 /* Now read contents of pstore file */
304 r
= read_full_virtual_file(ifd_path
, &buf
, &buf_size
);
306 log_warning_errno(r
, "Failed to read file %s, skipping: %m", ifd_path
);
310 if (!GREEDY_REALLOC(list
->entries
, list
->n_entries
+ 1))
313 list
->entries
[list
->n_entries
++] = (PStoreEntry
) {
315 .content
= TAKE_PTR(buf
),
316 .content_size
= buf_size
,
325 static int run(int argc
, char *argv
[]) {
326 _cleanup_(pstore_entries_reset
) PStoreList list
= {};
332 arg_sourcedir
= argv
[1];
333 arg_archivedir
= argv
[2];
335 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
336 "This program takes zero or two arguments.");
338 /* Ignore all parse errors */
339 (void) parse_config();
341 log_debug("Selected storage: %s.", pstore_storage_to_string(arg_storage
));
342 log_debug("Selected unlink: %s.", yes_no(arg_unlink
));
344 if (arg_storage
== PSTORE_STORAGE_NONE
)
345 /* Do nothing, intentionally, leaving pstore untouched */
348 /* Obtain list of files in pstore */
349 r
= list_files(&list
, arg_sourcedir
);
353 /* Handle each pstore file */
354 /* Sort files lexigraphically ascending, generally needed by all */
355 typesafe_qsort(list
.entries
, list
.n_entries
, compare_pstore_entries
);
357 /* Process known file types */
358 (void) process_dmesg_files(&list
);
360 /* Move left over files out of pstore */
361 for (size_t n
= 0; n
< list
.n_entries
; n
++)
362 (void) move_file(&list
.entries
[n
], NULL
, NULL
);
367 DEFINE_MAIN_FUNCTION(run
);