#define SYS_ASSERT "@SYS_ASSERT@"
#define CRASH_STACK_PATH "@CRASH_STACK_PATH@"
#define CRASH_MANAGER_CONFIG_PATH "@CRASH_MANAGER_CONFIG_PATH@"
+#define DUMP_SYSTEMSTATE_CONFIG_DIR_PATH "@DUMP_SYSTEMSTATE_CONFIG_DIR_PATH@"
#define MINICOREDUMPER_BIN_PATH "@MINICOREDUMPER_BIN_PATH@"
#define MINICOREDUMPER_CONFIG_PATH "@MINICOREDUMPER_CONFIG_PATH@"
#define DEBUGMODE_PATH "@DEBUGMODE_PATH@"
-DTMP_FILES_DIR=%{_sysconfdir}/tmpfiles.d \
-DARCH=%{ARCH} \
-DARCH_BIT=%{ARCH_BIT} \
+ -DDUMP_SYSTEMSTATE_CONFIG_DIR_PATH=%{_sysconfdir}/dump_systemstate.conf.d \
-DCRASH_MANAGER_CONFIG_PATH=%{_sysconfdir}/crash-manager.conf \
-DCRASH_ROOT_PATH=%{crash_root_path} \
-DCRASH_PATH=%{crash_path} \
%attr(-,root,root) %{_prefix}/lib/sysctl.d/99-crash-manager.conf
%attr(0750,system_fw,system_fw) %{_bindir}/crash-manager
%attr(0750,system_fw,system_fw) %{_bindir}/dump_systemstate
+%{_sysconfdir}/dump_systemstate.conf.d/files/files.conf.example
+%{_sysconfdir}/dump_systemstate.conf.d/programs/programs.conf.example
%{_libexecdir}/crash-stack
%{_libexecdir}/crash-popup-launch
%{_libexecdir}/crash-notify-send
static bool dump_system_state(const struct crash_info *cinfo, pid_t *pid)
{
- char *av[] = {"/usr/bin/dump_systemstate", "-d", "-k", "-j", "-p", "-f", cinfo->log_path, NULL};
+ char *av[] = {"/usr/bin/dump_systemstate", "-d", "-k", "-j", "-p", "-e", "-f", cinfo->log_path, NULL};
return spawn(av, NULL, NULL, NULL, pid, NULL);
}
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src)
SET(SRCS
dump_systemstate.c
+ extras.c
${CMAKE_SOURCE_DIR}/src/shared/util.c
${CMAKE_SOURCE_DIR}/src/shared/spawn.c
)
INCLUDE(FindPkgConfig)
-pkg_check_modules(dump_systemstate_pkgs REQUIRED dlog libunwind)
+pkg_check_modules(dump_systemstate_pkgs REQUIRED
+ dlog
+ iniparser
+ libunwind
+)
FOREACH(flag ${dump_systemstate_pkgs_CFLAGS})
SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
INSTALL(TARGETS ${PROJECT_NAME} DESTINATION bin
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
+INSTALL(FILES ${CMAKE_SOURCE_DIR}/src/${PROJECT_NAME}/files.conf.example
+ DESTINATION ${DUMP_SYSTEMSTATE_CONFIG_DIR_PATH}/files
+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
+INSTALL(FILES ${CMAKE_SOURCE_DIR}/src/${PROJECT_NAME}/programs.conf.example
+ DESTINATION ${DUMP_SYSTEMSTATE_CONFIG_DIR_PATH}/programs
+ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
/*
* dump_systemstate
*
- * Copyright (c) 2012 - 2013 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2012 - 2019 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
#include <sys/stat.h>
#include <sys/vfs.h>
+#include "dump_systemstate.h"
+#include "extras.h"
#include "shared/util.h"
#include "shared/log.h"
#include "shared/spawn.h"
#define FILE_PERM (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH)
-#define TIMEOUT_DEFAULT_MS (60*1000) /* 60sec */
-
-
-enum {
- EXIT_OK = 0, // all ok
- EXIT_ERR = (1 << 0), // setup error
- EXIT_FILEERR = (1 << 1), // at least one file failed to be copied
- EXIT_CMDERR = (1 << 2), // at least one command failed
-};
-
static struct dump_item {
const char *title;
const char *path;
static void usage()
{
- fprintf(stderr, "usage: dump_systemstate [-k] [-d] [-j] [-p] [-f file]\n"
+ fprintf(stderr, "usage: dump_systemstate [-k] [-d] [-j] [-p] [-e] [-f file]\n"
" -f: write to file (instead of stdout)\n"
" -k: dump kernel messages (only root)\n"
" -d: dump dlog messages\n"
" -j: dump journal log messages\n"
" -p: dump list of installed packages\n"
+ " -e: dump extras defined in the config\n"
+ " at " DUMP_SYSTEMSTATE_CONFIG_DIR_PROGRAMS_PATH "\n"
+ " and " DUMP_SYSTEMSTATE_CONFIG_DIR_FILES_PATH "\n"
);
}
int out_fd = -1;
bool arg_dlog = false;
bool arg_dmesg = false;
+ bool arg_extras = false;
bool arg_journal = false;
bool arg_pkgs = false;
char timestr[80];
struct tm gm_tm;
struct tm loc_tm;
- while ((c = getopt(argc, argv, "hf:kdjp")) != -1) {
+ while ((c = getopt(argc, argv, "hf:kdjep")) != -1) {
switch (c) {
case 'd':
arg_dlog = true;
case 'k':
arg_dmesg = true;
break;
+ case 'e':
+ arg_extras = true;
+ break;
case 'j':
arg_journal = true;
break;
}
fprintf_fd(out_fd, "\n");
+ if (arg_extras)
+ exit_code |= handle_extra_dir(out_fd, DUMP_SYSTEMSTATE_CONFIG_DIR_FILES_PATH, handle_extra_file);
+
#define spawn_wait_checked(av, env) \
do { \
int err; \
spawn_wait_checked(journalctl_args, NULL);
}
+ if (arg_extras)
+ exit_code |= handle_extra_dir(out_fd, DUMP_SYSTEMSTATE_CONFIG_DIR_PROGRAMS_PATH, handle_extra_program);
+
#undef spawn_wait_checked
if (arg_file)
--- /dev/null
+/*
+ * This file is a part of dump_systemstate from the crash-worker project.
+ *
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+enum {
+ EXIT_OK = 0, // all ok
+ EXIT_ERR = (1 << 0), // setup error
+ EXIT_FILEERR = (1 << 1), // at least one file failed to be copied
+ EXIT_CMDERR = (1 << 2), // at least one command failed
+ EXIT_CONFERR = (1 << 3), // at least one config entry was invalid
+};
+
--- /dev/null
+/*
+ * This file is a part of dump_systemstate from the crash-worker project.
+ *
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// dump_systemstate
+#include "dump_systemstate.h"
+#include "extras.h"
+
+// crash-worker
+#include "shared/spawn.h"
+#include "shared/util.h"
+
+// external projects
+#include <iniparser.h>
+
+// POSIX
+#include <dirent.h>
+#include <fcntl.h>
+
+// C
+#include <assert.h>
+#include <stdbool.h>
+
+static inline void cleanup_dictionary(dictionary **ini)
+{
+ assert(ini);
+ if (!*ini)
+ return;
+
+ iniparser_freedict(*ini);
+}
+
+enum ini_fields {
+ INI_FIELD_TITLE = 0,
+ INI_FIELD_PATH,
+ INI_FIELD_ARGS,
+ INI_FIELD_ENV,
+ COUNT_INI_FIELDS,
+};
+
+static const char *const INI_KEYS[COUNT_INI_FIELDS] = {
+ [INI_FIELD_TITLE] = "title",
+ [INI_FIELD_PATH] = "path",
+ [INI_FIELD_ARGS] = "args",
+ [INI_FIELD_ENV] = "env",
+};
+static const size_t MAX_INI_KEY_LEN = 5;
+
+struct extra_dump_item {
+ // not separate named fields, for convenient iteration
+ char *fields[COUNT_INI_FIELDS];
+};
+
+int handle_extra_program(int out_fd, struct extra_dump_item *item)
+{
+ assert(out_fd >= 0);
+ assert(item);
+
+ char *const title = item->fields[INI_FIELD_TITLE];
+ char *const path = item->fields[INI_FIELD_PATH];
+ char *const args = item->fields[INI_FIELD_ARGS] ?: "";
+ char *const env = item->fields[INI_FIELD_ENV] ?: "";
+
+ if (!title || !path) {
+ fprintf_fd(out_fd, "\nNo title or path in extra program config");
+ return EXIT_CONFERR;
+ }
+
+ char *command_line;
+ int printed = asprintf(&command_line, "%s %s %s", env, path, args);
+ if (printed < 0) {
+ fprintf_fd(out_fd, "\nError, out of memory");
+ return EXIT_ERR;
+ }
+
+ fprintf_fd(out_fd, "\n==== %s (%s)\n", title, command_line);
+
+ /* Both `args` and `env` are single strings (because `iniparser` does not
+ * offer much when it comes to hierarchies and arrays) but `execve` expects
+ * an array of char pointers. Splitting isn't trivial (consider a brutal set
+ * of arguments using " or `) and I don't want to reinvent the wheel so I'm
+ * delegating the splitting to the shell. */
+ char *argv[] = {"/bin/sh", "-c", command_line, NULL};
+
+ int err;
+ spawn_param_u param = { .int_val = out_fd };
+ bool failed = !spawn_wait(argv, NULL, spawn_setstdout, ¶m, DEFAULT_COMMAND_TIMEOUT_MS, &err) || err != 0;
+
+ free(command_line);
+ return failed ? EXIT_CMDERR : 0;
+}
+
+int handle_extra_file(int out_fd, struct extra_dump_item *item)
+{
+ assert(out_fd >= 0);
+ assert(item);
+
+ char *const title = item->fields[INI_FIELD_TITLE];
+ char *const path = item->fields[INI_FIELD_PATH];
+ if (!title || !path) {
+ fprintf_fd(out_fd, "\nNo title or path in extra file config");
+ return EXIT_CONFERR;
+ }
+
+ fprintf_fd(out_fd, "\n==== %s (%s)\n", title, path);
+ int ret = dump_file_write_fd(out_fd, (char *)path);
+ if (ret < 0) {
+ fprintf_fd(out_fd, "Unable to copy file.\n");
+ return EXIT_FILEERR;
+ }
+ return 0;
+}
+
+typedef int (*handle_ini_section_t)(int out_fd, struct extra_dump_item *);
+
+static int handle_ini_Nth_section(int out_fd, dictionary *ini, int n, handle_ini_section_t handle_ini_section)
+{
+ assert(out_fd >= 0);
+ assert(ini);
+ assert(n >= 0);
+ assert(n < iniparser_getnsec(ini));
+ assert(handle_ini_section);
+
+ char *const secname = iniparser_getsecname(ini, n);
+ assert(secname); // can only be NULL if `ini` is NULL or `n` is outta bounds
+
+ const size_t secname_len = strlen(secname);
+ char key_buf[secname_len + sizeof ':' + MAX_INI_KEY_LEN + sizeof '\0'];
+ memcpy(key_buf, secname, secname_len);
+ key_buf[secname_len] = ':';
+
+ char *const key_suffix_ptr = key_buf + secname_len + 1;
+ struct extra_dump_item item;
+ for (size_t i = 0; i < ARRAY_SIZE(item.fields); ++i) {
+ strcpy(key_suffix_ptr, INI_KEYS[i]);
+ item.fields[i] = iniparser_getstring(ini, key_buf, NULL);
+ }
+
+ return handle_ini_section(out_fd, &item);
+}
+
+static int handle_extra_ini(int out_fd, const char *ini_path, handle_ini_section_t handle_ini_section)
+{
+ assert(out_fd >= 0);
+ assert(ini_path);
+ assert(handle_ini_section);
+
+ __attribute__((cleanup(cleanup_dictionary))) dictionary *ini = iniparser_load(ini_path);
+ if (!ini) {
+ fprintf_fd(out_fd, "\nCouldn't parse ini file %s", ini_path);
+ return EXIT_CONFERR;
+ }
+
+ const int nsec = iniparser_getnsec(ini);
+ assert(nsec >= 0); // can only be -1 when ini is NULL
+
+ int ret = 0;
+ for (int i = 0; i < nsec; ++i)
+ ret |= handle_ini_Nth_section(out_fd, ini, i, handle_ini_section);
+ return ret;
+}
+
+static int config_entry_filter(const struct dirent *de)
+{
+ assert(de);
+ return de->d_type == DT_REG && string_ends_with(de->d_name, ".conf");
+}
+
+int handle_extra_dir(int out_fd, char *dir_path, handle_ini_section_t handle_ini_section)
+{
+ assert(out_fd >= 0);
+ assert(dir_path);
+ assert(handle_ini_section);
+
+ const int dir_fd = open(dir_path, O_DIRECTORY | O_RDONLY);
+ if (dir_fd < 0) {
+ fprintf_fd(out_fd, "\nCouldn't open extras dir: %s %m", dir_path);
+ return EXIT_ERR;
+ }
+
+ struct dirent **entries;
+ int entry_count = scandirat(dir_fd, ".", &entries, config_entry_filter, alphasort);
+ if (entry_count < 0) {
+ fprintf_fd(out_fd, "\nCouldn't process directory %s: %m", dir_path);
+ close(dir_fd);
+ return EXIT_ERR;
+ }
+
+ int ret = 0;
+ for (int i = 0; i < entry_count; ++i) {
+ struct dirent *const de = entries[i];
+ char ini_path[PATH_MAX];
+
+ /* In theory this introduces a race condition (somebody could replace
+ * folders between `scandirat` and the file being read) but `iniparser`
+ * does not offer any other way to open a file. Not letting the system
+ * get so brutally pwned is not our responsibility, at any rate. */
+ snprintf(ini_path, sizeof ini_path, "%s/%s", dir_path, de->d_name);
+ free(de);
+
+ ret |= handle_extra_ini(out_fd, ini_path, handle_ini_section);
+ }
+ free(entries);
+ close(dir_fd);
+ return ret;
+}
+
--- /dev/null
+/*
+ * This file is a part of dump_systemstate from the crash-worker project.
+ *
+ * Copyright (c) 2019 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "defs.h"
+
+#define DUMP_SYSTEMSTATE_CONFIG_DIR_PROGRAMS_PATH \
+ DUMP_SYSTEMSTATE_CONFIG_DIR_PATH "/programs"
+#define DUMP_SYSTEMSTATE_CONFIG_DIR_FILES_PATH \
+ DUMP_SYSTEMSTATE_CONFIG_DIR_PATH "/files"
+
+struct extra_dump_item;
+
+typedef int (*handle_ini_section_t)(int out_fd, struct extra_dump_item *);
+
+int handle_extra_dir(int out_fd, char *dir_path, handle_ini_section_t handle_ini_section);
+int handle_extra_file(int out_fd, struct extra_dump_item *item);
+int handle_extra_program(int out_fd, struct extra_dump_item *item);
+
--- /dev/null
+[UNIQUE_ID_KEY]
+title=header line that gets printed (path gets appended too)
+path=/path/to/the/file
+
+[DLOG_CONF]
+title=dlog configuration file
+path=/opt/etc/dlog.conf
+
--- /dev/null
+[UNIQUE_ID_KEY]
+title=header line describing the program (will be printed alongside env, path and args)
+path=/path/to/the/program/executable
+args=-x foo --verbose
+env=POSIXLY_CORRECT=1
+
+[DLOG_DUMP]
+title=dump dlog contents
+path=/usr/bin/dlogutil
+args=-d
return false;
}
- char *av[] = {"/usr/bin/dump_systemstate", "-k", "-d", "-j", "-f", dump_path, NULL};
+ char *av[] = {"/usr/bin/dump_systemstate", "-k", "-d", "-j", "-e", "-f", dump_path, NULL};
bool is_ok = spawn_wait(av, NULL, NULL, NULL, 0, exit_code);
free(dump_path);