From 4bcce9664ab339519a0e285c41b11651b8570250 Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Wed, 3 Apr 2019 11:46:47 +0200 Subject: [PATCH] dump_systemstate reads extra tasks from config Change-Id: I0cf2f0411218e0ce6d0b7c0e940ea5e84f48fdf2 Signed-off-by: Michal Bloch --- include/defs.h.in | 1 + packaging/crash-worker.spec | 3 + src/crash-manager/crash-manager.c | 2 +- src/dump_systemstate/CMakeLists.txt | 13 +- src/dump_systemstate/dump_systemstate.c | 31 ++-- src/dump_systemstate/dump_systemstate.h | 28 ++++ src/dump_systemstate/extras.c | 221 +++++++++++++++++++++++++++++ src/dump_systemstate/extras.h | 35 +++++ src/dump_systemstate/files.conf.example | 8 ++ src/dump_systemstate/programs.conf.example | 10 ++ src/log_dump/log_dump.c | 2 +- 11 files changed, 338 insertions(+), 16 deletions(-) create mode 100644 src/dump_systemstate/dump_systemstate.h create mode 100644 src/dump_systemstate/extras.c create mode 100644 src/dump_systemstate/extras.h create mode 100644 src/dump_systemstate/files.conf.example create mode 100644 src/dump_systemstate/programs.conf.example diff --git a/include/defs.h.in b/include/defs.h.in index 25e465e..587172d 100644 --- a/include/defs.h.in +++ b/include/defs.h.in @@ -7,6 +7,7 @@ #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@" diff --git a/packaging/crash-worker.spec b/packaging/crash-worker.spec index efbb324..d6e7e7a 100644 --- a/packaging/crash-worker.spec +++ b/packaging/crash-worker.spec @@ -104,6 +104,7 @@ export CFLAGS+=" -Werror" -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} \ @@ -181,6 +182,8 @@ sed -i "/${pattern}/D" %{_sysconfdir}/ld.so.preload %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 diff --git a/src/crash-manager/crash-manager.c b/src/crash-manager/crash-manager.c index 321dd4b..ea5b10a 100644 --- a/src/crash-manager/crash-manager.c +++ b/src/crash-manager/crash-manager.c @@ -461,7 +461,7 @@ static void launch_crash_popup(struct crash_info *cinfo) 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); } diff --git a/src/dump_systemstate/CMakeLists.txt b/src/dump_systemstate/CMakeLists.txt index 60737cb..7c3fa21 100755 --- a/src/dump_systemstate/CMakeLists.txt +++ b/src/dump_systemstate/CMakeLists.txt @@ -4,12 +4,17 @@ PROJECT(dump_systemstate C) 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}") @@ -26,3 +31,9 @@ TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${dump_systemstate_pkgs_LDFLAGS} -pie) 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) diff --git a/src/dump_systemstate/dump_systemstate.c b/src/dump_systemstate/dump_systemstate.c index 0d79cca..b853080 100644 --- a/src/dump_systemstate/dump_systemstate.c +++ b/src/dump_systemstate/dump_systemstate.c @@ -1,7 +1,7 @@ /* * 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. @@ -33,22 +33,14 @@ #include #include +#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; @@ -67,12 +59,15 @@ static struct dump_item { 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" ); } @@ -99,6 +94,7 @@ int main(int argc, char *argv[]) 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]; @@ -106,7 +102,7 @@ int main(int argc, char *argv[]) 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; @@ -114,6 +110,9 @@ int main(int argc, char *argv[]) case 'k': arg_dmesg = true; break; + case 'e': + arg_extras = true; + break; case 'j': arg_journal = true; break; @@ -165,6 +164,9 @@ int main(int argc, char *argv[]) } 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; \ @@ -236,6 +238,9 @@ int main(int argc, char *argv[]) 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) diff --git a/src/dump_systemstate/dump_systemstate.h b/src/dump_systemstate/dump_systemstate.h new file mode 100644 index 0000000..3ad833e --- /dev/null +++ b/src/dump_systemstate/dump_systemstate.h @@ -0,0 +1,28 @@ +/* + * 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 +}; + diff --git a/src/dump_systemstate/extras.c b/src/dump_systemstate/extras.c new file mode 100644 index 0000000..6033485 --- /dev/null +++ b/src/dump_systemstate/extras.c @@ -0,0 +1,221 @@ +/* + * 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 + +// POSIX +#include +#include + +// C +#include +#include + +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; +} + diff --git a/src/dump_systemstate/extras.h b/src/dump_systemstate/extras.h new file mode 100644 index 0000000..c6d78de --- /dev/null +++ b/src/dump_systemstate/extras.h @@ -0,0 +1,35 @@ +/* + * 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); + diff --git a/src/dump_systemstate/files.conf.example b/src/dump_systemstate/files.conf.example new file mode 100644 index 0000000..92d5f22 --- /dev/null +++ b/src/dump_systemstate/files.conf.example @@ -0,0 +1,8 @@ +[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 + diff --git a/src/dump_systemstate/programs.conf.example b/src/dump_systemstate/programs.conf.example new file mode 100644 index 0000000..8e26083 --- /dev/null +++ b/src/dump_systemstate/programs.conf.example @@ -0,0 +1,10 @@ +[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 diff --git a/src/log_dump/log_dump.c b/src/log_dump/log_dump.c index 868e163..c20149f 100644 --- a/src/log_dump/log_dump.c +++ b/src/log_dump/log_dump.c @@ -204,7 +204,7 @@ static bool dump_systemstate(const char *const destdir, const char *const timest 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); -- 2.7.4