From 8c9cc6e12ac8035c48fdb76c7eaef824aae030f5 Mon Sep 17 00:00:00 2001 From: Alexander Egorenkov Date: Thu, 8 Apr 2021 13:06:33 +0200 Subject: [PATCH] s390-tools: add hsavmcore utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsavmcore is designed to make the dump process with kdump more efficient. With hsavmcore, the HSA memory that contains a part of the production kernel's memory can be released early in the process. Depending on the size of the production kernel's memory, writing the dump to persistent storage can be time consuming and prevent the HSA memory from being reused by other LPARs. Signed-off-by: Alexander Egorenkov Reviewed-by: Philipp Rudo Acked-by: Jan Höppner Signed-off-by: Jan Höppner --- .gitignore | 1 + Makefile | 2 +- README.md | 19 ++- hsavmcore/Makefile | 91 ++++++++++++++ hsavmcore/cmdline_options.c | 219 ++++++++++++++++++++++++++++++++ hsavmcore/cmdline_options.h | 19 +++ hsavmcore/common.h | 27 ++++ hsavmcore/config.c | 242 ++++++++++++++++++++++++++++++++++++ hsavmcore/config.h | 59 +++++++++ hsavmcore/hsa.c | 172 +++++++++++++++++++++++++ hsavmcore/hsa.h | 75 +++++++++++ hsavmcore/hsa_file.c | 236 +++++++++++++++++++++++++++++++++++ hsavmcore/hsa_file.h | 27 ++++ hsavmcore/hsa_mem.c | 151 ++++++++++++++++++++++ hsavmcore/hsa_mem.h | 24 ++++ hsavmcore/main.c | 238 +++++++++++++++++++++++++++++++++++ hsavmcore/mount.c | 63 ++++++++++ hsavmcore/mount.h | 17 +++ hsavmcore/overlay.c | 209 +++++++++++++++++++++++++++++++ hsavmcore/overlay.h | 33 +++++ hsavmcore/proxy.c | 198 +++++++++++++++++++++++++++++ hsavmcore/proxy.h | 35 ++++++ hsavmcore/swap.c | 51 ++++++++ hsavmcore/swap.h | 15 +++ 24 files changed, 2221 insertions(+), 2 deletions(-) create mode 100644 hsavmcore/Makefile create mode 100644 hsavmcore/cmdline_options.c create mode 100644 hsavmcore/cmdline_options.h create mode 100644 hsavmcore/common.h create mode 100644 hsavmcore/config.c create mode 100644 hsavmcore/config.h create mode 100644 hsavmcore/hsa.c create mode 100644 hsavmcore/hsa.h create mode 100644 hsavmcore/hsa_file.c create mode 100644 hsavmcore/hsa_file.h create mode 100644 hsavmcore/hsa_mem.c create mode 100644 hsavmcore/hsa_mem.h create mode 100644 hsavmcore/main.c create mode 100644 hsavmcore/mount.c create mode 100644 hsavmcore/mount.h create mode 100644 hsavmcore/overlay.c create mode 100644 hsavmcore/overlay.h create mode 100644 hsavmcore/proxy.c create mode 100644 hsavmcore/proxy.h create mode 100644 hsavmcore/swap.c create mode 100644 hsavmcore/swap.h diff --git a/.gitignore b/.gitignore index e223dbd2..0b7da11d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ dasdview/dasdview dump2tar/src/dump2tar fdasd/fdasd hmcdrvfs/hmcdrvfs +hsavmcore/hsavmcore hyptop/hyptop ip_watcher/xcec-bridge ipl_tools/chreipl diff --git a/Makefile b/Makefile index cfbbd950..a62ece72 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \ vmconvert vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \ ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \ systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \ - genprotimg lsstp hsci + genprotimg lsstp hsci hsavmcore SUB_DIRS = $(LIB_DIRS) $(TOOL_DIRS) diff --git a/README.md b/README.md index 44a90d72..32fe4d5e 100644 --- a/README.md +++ b/README.md @@ -252,6 +252,14 @@ Package contents * hsci: Manage HiperSockets Converged Interfaces (HSCI). + * hsavmcore: + hsavmcore is designed to make the dump process with kdump more efficient. + With hsavmcore, the HSA memory that contains a part of the production + kernel's memory can be released early in the process. Depending on the size + of the production kernel's memory, writing the dump to persistent storage + can be time consuming and prevent the HSA memory from being reused + by other LPARs. + For more information refer to the following publications: * "Device Drivers, Features, and Commands" chapter "Useful Linux commands" @@ -270,7 +278,8 @@ build options: | __LIBRARY__ | __BUILD OPTION__ | __TOOLS__ | |----------------|:------------------:|:-------------------------------------:| -| fuse | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump | +| fuse | `HAVE_FUSE` | cmsfs-fuse, zdsfs, hmcdrvfs, zgetdump,| +| | | hsavmcore | | zlib | `HAVE_ZLIB` | zgetdump, dump2tar | | ncurses | `HAVE_NCURSES` | hyptop | | pfm | `HAVE_PFM` | cpacfstats | @@ -281,6 +290,7 @@ build options: | json-c | `HAVE_JSONC` | zkey-cryptsetup, libekmfweb | | glib2 | `HAVE_GLIB2` | genprotimg | | libcurl | `HAVE_LIBCURL` | genprotimg, libekmfweb | +| systemd | `HAVE_SYSTEMD` | hsavmcore | This table lists additional build or install options: @@ -427,3 +437,10 @@ the different tools are provided: (libcurl-devel.rpm). Tip: you may skip the libekmfweb build by adding `HAVE_OPENSSL=0`, `HAVE_JSONC=0`, or `HAVE_LIBCURL=0` to the make invocation. + +* hsavmcore: + For building the hsavmcore tool you need fuse version 2.6 and optionally + systemd which is enabled by default, to disable systemd support, + add `HAVE_SYSTEMD=0` to the make invocation. + Tip: you may skip the hsavmcore build by adding `HAVE_FUSE=0` + to the make invocation. diff --git a/hsavmcore/Makefile b/hsavmcore/Makefile new file mode 100644 index 00000000..1ace3c32 --- /dev/null +++ b/hsavmcore/Makefile @@ -0,0 +1,91 @@ +# +# Copyright IBM Corp. 2021 +# +# s390-tools is free software; you can redistribute it and/or modify +# it under the terms of the MIT license. See LICENSE for details. +# + +include ../common.mak + +ifeq (${HAVE_FUSE},0) + +all: + $(SKIP) HAVE_FUSE=0 + +install: + $(SKIP) HAVE_FUSE=0 + +else # HAVE_FUSE + +# +# FUSE +# +ifneq ($(shell sh -c 'command -v pkg-config'),) +FUSE_CFLAGS = $(shell pkg-config --silence-errors --cflags fuse) +FUSE_LDLIBS = $(shell pkg-config --silence-errors --libs fuse) +else +FUSE_CFLAGS = -I/usr/include/fuse +FUSE_LDLIBS = -lfuse +endif + +# +# systemd +# +ifneq (${HAVE_SYSTEMD},0) +ifneq ($(shell sh -c 'command -v pkg-config'),) +SYSTEMD_CFLAGS = $(shell pkg-config --silence-errors --cflags libsystemd) +SYSTEMD_LDLIBS = $(shell pkg-config --silence-errors --libs libsystemd) +else +SYSTEMD_CFLAGS = +SYSTEMD_LDLIBS = -lsystemd +endif +ALL_CPPFLAGS += -DHAVE_SYSTEMD +endif # HAVE_SYSTEMD + +ALL_CPPFLAGS += -D_FILE_OFFSET_BITS=64 +ALL_CFLAGS += $(FUSE_CFLAGS) $(SYSTEMD_CFLAGS) +LDLIBS += $(FUSE_LDLIBS) $(SYSTEMD_LDLIBS) -lpthread + +OBJECTS = \ + main.o \ + cmdline_options.o \ + config.o \ + mount.o \ + swap.o \ + hsa.o \ + hsa_file.o \ + hsa_mem.o \ + proxy.o \ + overlay.o + +libs = $(rootdir)/libutil/libutil.a + +all: check_dep hsavmcore + +check_dep: + $(call check_dep, \ + "hsavmcore", \ + "fuse.h", \ + "fuse-devel or libfuse-dev", \ + "HAVE_FUSE=0") +ifneq (${HAVE_SYSTEMD},0) + $(call check_dep, \ + "hsavmcore", \ + "systemd/sd-daemon.h", \ + "systemd-devel or libsystemd-dev", \ + "HAVE_SYSTEMD=0") +endif + +hsavmcore: $(OBJECTS) $(libs) + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ + +install: all + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 hsavmcore \ + $(DESTDIR)$(USRSBINDIR) + +endif # HAVE_FUSE + +clean: + rm -f hsavmcore $(OBJECTS) + +.PHONY: all install clean diff --git a/hsavmcore/cmdline_options.c b/hsavmcore/cmdline_options.c new file mode 100644 index 00000000..b3918636 --- /dev/null +++ b/hsavmcore/cmdline_options.c @@ -0,0 +1,219 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "lib/util_opt.h" +#include "lib/util_prg.h" +#include "lib/util_log.h" + +#include "cmdline_options.h" + +static const struct util_prg prg = { + .desc = "hsavmcore is designed to make the dump process with kdump more " + "efficient. The HSA memory contains a part of the production " + "kernel's memory. Use hsavmcore to cache this information and " + "release HSA memory early in the process.", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2021, + .pub_last = 2021, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +static struct util_opt opt_vec[] = { + UTIL_OPT_SECTION("CONFIGURATION"), + { + .option = { "config", required_argument, NULL, 'c' }, + .argument = "CONFIGFILE", + .desc = "Path to the configuration file.\n" + "Default: no configuration file is used", + }, + { + .option = { "vmcore", required_argument, NULL, 'C' }, + .argument = "VMCOREFILE", + .desc = "Path to the vmcore file.\n" + "Default: " PROC_VMCORE, + }, + { + .option = { "hsa", required_argument, NULL, 'H' }, + .argument = "ZCOREHSAFILE", + .desc = "Path to the zcore HSA file.\n" + "Default: " ZCORE_HSA, + }, + { + .option = { "workdir", required_argument, NULL, 'W' }, + .argument = "WORKDIR", + .desc = "Path to the work directory where temporary files can be " + "stored.\nDefault: " WORKDIR, + }, + { + .option = { "bmvmcore", required_argument, NULL, 'B' }, + .argument = "VMCOREFILE", + .desc = "Path to the target of the bind mount for the vmcore " + "replacement.\nDefault: " PROC_VMCORE, + }, + { + .option = { "swap", required_argument, NULL, 'S' }, + .argument = "PATH", + .desc = "Path to a swap device or file. The specified swap " + "device or file must exist and have the proper swap " + "format.\nDefault: no swap device or file is activated", + }, + { + .option = { "hsasize", required_argument, NULL, 'T' }, + .argument = "HSASIZE", + .desc = "HSA size in bytes.\n" + "Default: -1 (read from the zcore HSA file)", + }, + { + .option = { "dbgfsmnt", no_argument, NULL, 'D' }, + .desc = "Mount the debug file system.\n" + "Default: the debug file system is not mounted", + }, + { + .option = { "hsamem", no_argument, NULL, 'F' }, + .desc = "Cache the HSA memory in regular memory.\n" + "Default: the HSA memory is cached as a file within " + "WORKDIR", + }, + { + .option = { "norelhsa", no_argument, NULL, 'R' }, + .desc = "Do NOT release the HSA memory after caching.\n" + "Default: the HSA memory is released", + }, + { + .option = { "nobindmnt", no_argument, NULL, 'N' }, + .desc = "Do NOT replace the system's vmcore.\n" + "Default: the system's vmcore is replaced", + }, + UTIL_OPT_SECTION("LOGGING"), + { + .option = { "verbose", no_argument, NULL, 'V' }, + .desc = "Print verbose messages to stdout. Repeat this option " + "for increased verbosity from just error messages to " + "also include warning, information, debug, and trace " + "messages. This option is intended for debugging", + }, + { + .option = { "fusedbg", no_argument, NULL, 'G' }, + .desc = "Enable FUSE debugging.\n" + "Default: FUSE debugging is disabled", + }, + UTIL_OPT_SECTION("GENERAL OPTIONS"), + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +void parse_cmdline_options(int argc, char *argv[], struct config *config) +{ + int opt, ret; + + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + /* Parse given command-line config */ + while (1) { + opt = util_opt_getopt_long(argc, argv); + if (opt == -1) + break; + + switch (opt) { + case 'h': + util_prg_print_help(); + util_opt_print_help(); + exit(EXIT_SUCCESS); + case 'v': + util_prg_print_version(); + exit(EXIT_SUCCESS); + case 'V': + config->verbose++; + util_log_set_level(config->verbose); + break; + case 'c': + ret = update_config_from_file(optarg, config); + if (ret < 0) + exit(EXIT_FAILURE); + util_log_set_level(config->verbose); + break; + case 'C': + strncpy(config->vmcore_path, optarg, + sizeof(config->vmcore_path) - 1); + /* Ensure null termination */ + config->vmcore_path[sizeof(config->vmcore_path) - 1] = + '\0'; + break; + case 'H': + strncpy(config->zcore_hsa_path, optarg, + sizeof(config->zcore_hsa_path) - 1); + /* Ensure null termination */ + config->zcore_hsa_path[sizeof(config->zcore_hsa_path) - + 1] = '\0'; + break; + case 'W': + strncpy(config->workdir_path, optarg, + sizeof(config->workdir_path) - 1); + /* Ensure null termination */ + config->workdir_path[sizeof(config->workdir_path) - 1] = + '\0'; + break; + case 'B': + strncpy(config->bind_mount_vmcore_path, optarg, + sizeof(config->bind_mount_vmcore_path) - 1); + /* Ensure null termination */ + config->bind_mount_vmcore_path + [sizeof(config->bind_mount_vmcore_path) - 1] = + '\0'; + break; + case 'S': + strncpy(config->swap, optarg, sizeof(config->swap) - 1); + /* Ensure null termination */ + config->swap[sizeof(config->swap) - 1] = '\0'; + break; + case 'T': { + char *endptr; + long hsa_size = strtol(optarg, &endptr, 0); + + if (*endptr != '\0' || hsa_size < -1 || + hsa_size > INT_MAX) { + fprintf(stderr, + "The given HSA size is invalid.\n"); + exit(EXIT_FAILURE); + } + config->hsa_size = hsa_size; + break; + } + case 'D': + config->mount_debugfs = true; + break; + case 'F': + config->use_hsa_mem = true; + break; + case 'R': + config->release_hsa = false; + break; + case 'N': + config->bind_mount_vmcore = false; + break; + case 'G': + config->fuse_debug = true; + break; + case '?': + default: + util_opt_print_parse_error(opt, argv); + exit(EXIT_FAILURE); + } + } +} diff --git a/hsavmcore/cmdline_options.h b/hsavmcore/cmdline_options.h new file mode 100644 index 00000000..9d40085a --- /dev/null +++ b/hsavmcore/cmdline_options.h @@ -0,0 +1,19 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_CMDLINE_OPTIONS_H +#define _HSAVMCORE_CMDLINE_OPTIONS_H + +#include "config.h" + +/* + * Parses the given command-line options and adjusts the application's + * configuration accordingly. + */ +void parse_cmdline_options(int argc, char *argv[], struct config *config); + +#endif diff --git a/hsavmcore/common.h b/hsavmcore/common.h new file mode 100644 index 00000000..3d127ebb --- /dev/null +++ b/hsavmcore/common.h @@ -0,0 +1,27 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_COMMON_H +#define _HSAVMCORE_COMMON_H + +#define NAME "hsavmcore" + +#define DEBUGFS_MOUNT_POINT "/sys/kernel/debug" + +#define ZCORE_HSA DEBUGFS_MOUNT_POINT "/zcore/hsa" + +#define VMCORE_FILE "vmcore" + +#define PROC_VMCORE "/proc/" VMCORE_FILE + +#define WORKDIR "/var/crash" + +#define HSA_CACHE_FILE NAME "-hsa-cache.bin" + +#define OVERLAY_MOUNT_POINT "/tmp/" NAME "-overlay/" + +#endif diff --git a/hsavmcore/config.c b/hsavmcore/config.c new file mode 100644 index 00000000..8bd005fa --- /dev/null +++ b/hsavmcore/config.c @@ -0,0 +1,242 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include + +#include "lib/util_libc.h" +#include "lib/util_log.h" + +#include "config.h" + +#define CONFIG_LINE_MAX_SIZE 1024 + +/* + * Supported configuration parameters + */ +#define CONFIG_VERBOSE "verbose" +#define CONFIG_WORKDIR "workdir" +#define CONFIG_HSA_SIZE "hsa_size" +#define CONFIG_MOUNT_DEBUGFS "mount_debugfs" +#define CONFIG_USE_HSA_MEM "use_hsa_mem" +#define CONFIG_RELEASE_HSA "release_hsa" +#define CONFIG_BIND_MOUNT_VMCORE "bind_mount_vmcore" +#define CONFIG_FUSE_DEBUG "fuse_debug" +#define CONFIG_SWAP "swap" + +static char *get_value_str(char *line) +{ + char *ptr = strchr(line, '='); + + if (!ptr) + return NULL; + + return util_strstrip(ptr + 1); +} + +static int parse_bool(char *line, int linenum, const char *name, bool *value) +{ + const char *value_str; + unsigned long value_num; + char *endptr; + + value_str = get_value_str(line); + if (!value_str) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: value expected for %s\n", + linenum, name); + return -1; + } + + util_log_print(UTIL_LOG_DEBUG, "config parse bool: %s\n", value_str); + + value_num = strtoul(value_str, &endptr, 0); + if (*endptr != '\0' || (value_num != 0 && value_num != 1)) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: invalid value for %s\n", + linenum, name); + return -1; + } + + *value = value_num; + + return 0; +} + +static int parse_int(char *line, int linenum, const char *name, int min_value, + int max_value, int *value) +{ + const char *value_str; + long value_num; + char *endptr; + + value_str = get_value_str(line); + if (!value_str) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: value expected for %s\n", + linenum, name); + return -1; + } + + util_log_print(UTIL_LOG_DEBUG, "config parse int: %s\n", value_str); + + value_num = strtol(value_str, &endptr, 0); + if (*endptr != '\0' || value_num < min_value || value_num > max_value) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: invalid value for %s\n", + linenum, name); + return -1; + } + + *value = value_num; + + return 0; +} + +static int parse_str(char *line, int linenum, const char *name, int min_size, + int max_size, char *value) +{ + const char *value_str; + int size; + + value_str = get_value_str(line); + if (!value_str) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: value expected for %s\n", + linenum, name); + return -1; + } + + util_log_print(UTIL_LOG_DEBUG, "config parse string: %s\n", value_str); + + size = strlen(value_str); + if ((min_size >= 0 && size < min_size) || size > max_size) { + util_log_print(UTIL_LOG_ERROR, + "config line %d: invalid value for %s\n", + linenum, name); + return -1; + } + + strncpy(value, value_str, max_size); + /* Ensure null termination */ + value[max_size] = '\0'; + + return 0; +} + +static int parse_line(char *line, int linenum, struct config *config) +{ + if (strncmp(line, CONFIG_VERBOSE, strlen(CONFIG_VERBOSE)) == 0) { + if (parse_int(line, linenum, CONFIG_VERBOSE, UTIL_LOG_ERROR, + UTIL_LOG_TRACE, &config->verbose)) + return -1; + } else if (strncmp(line, CONFIG_WORKDIR, strlen(CONFIG_WORKDIR)) == 0) { + if (parse_str(line, linenum, CONFIG_WORKDIR, 0, + sizeof(config->workdir_path) - 1, + config->workdir_path)) + return -1; + } else if (strncmp(line, CONFIG_HSA_SIZE, strlen(CONFIG_HSA_SIZE)) == + 0) { + if (parse_int(line, linenum, CONFIG_HSA_SIZE, -1, INT_MAX, + &config->hsa_size)) + return -1; + } else if (strncmp(line, CONFIG_MOUNT_DEBUGFS, + strlen(CONFIG_MOUNT_DEBUGFS)) == 0) { + if (parse_bool(line, linenum, CONFIG_MOUNT_DEBUGFS, + &config->mount_debugfs)) + return -1; + } else if (strncmp(line, CONFIG_USE_HSA_MEM, + strlen(CONFIG_USE_HSA_MEM)) == 0) { + if (parse_bool(line, linenum, CONFIG_USE_HSA_MEM, + &config->use_hsa_mem)) + return -1; + } else if (strncmp(line, CONFIG_RELEASE_HSA, + strlen(CONFIG_RELEASE_HSA)) == 0) { + if (parse_bool(line, linenum, CONFIG_RELEASE_HSA, + &config->release_hsa)) + return -1; + } else if (strncmp(line, CONFIG_BIND_MOUNT_VMCORE, + strlen(CONFIG_BIND_MOUNT_VMCORE)) == 0) { + if (parse_bool(line, linenum, CONFIG_BIND_MOUNT_VMCORE, + &config->bind_mount_vmcore)) + return -1; + } else if (strncmp(line, CONFIG_FUSE_DEBUG, + strlen(CONFIG_FUSE_DEBUG)) == 0) { + if (parse_bool(line, linenum, CONFIG_FUSE_DEBUG, + &config->fuse_debug)) + return -1; + } else if (strncmp(line, CONFIG_SWAP, strlen(CONFIG_SWAP)) == 0) { + if (parse_str(line, linenum, CONFIG_SWAP, 0, + sizeof(config->swap) - 1, config->swap)) + return -1; + } else { + util_log_print(UTIL_LOG_ERROR, + "config line %d: unknown configuration '%s'\n", + linenum, line); + return -1; + } + + return 0; +} + +void init_config(struct config *config) +{ + memset(config, 0, sizeof(struct config)); + strncpy(config->vmcore_path, PROC_VMCORE, + sizeof(config->vmcore_path) - 1); + strncpy(config->zcore_hsa_path, ZCORE_HSA, + sizeof(config->zcore_hsa_path) - 1); + strncpy(config->workdir_path, WORKDIR, + sizeof(config->workdir_path) - 1); + strncpy(config->bind_mount_vmcore_path, PROC_VMCORE, + sizeof(config->bind_mount_vmcore_path) - 1); + config->hsa_size = -1; + config->mount_debugfs = false; + config->use_hsa_mem = false; + config->release_hsa = true; + config->bind_mount_vmcore = true; + config->fuse_debug = false; +} + +int update_config_from_file(const char *config_path, struct config *config) +{ + char line[CONFIG_LINE_MAX_SIZE], *ptr; + int ret = 0, linenum; + FILE *fp; + + fp = fopen(config_path, "r"); + if (!fp) { + util_log_print(UTIL_LOG_ERROR, "Couldn't open config file %s\n", + config_path); + return -1; + } + + /* Read the given configuration file linewise and parse parameters */ + linenum = 0; + while (fgets(line, sizeof(line), fp)) { + linenum++; + + ptr = util_strstrip(line); + /* Skip empty or comment lines */ + if (ptr[0] == '\0' || ptr[0] == '#') + continue; + + util_log_print(UTIL_LOG_DEBUG, "config line %d: %s\n", linenum, + ptr); + + ret = parse_line(ptr, linenum, config); + if (ret < 0) + break; + } + + fclose(fp); + + return ret; +} diff --git a/hsavmcore/config.h b/hsavmcore/config.h new file mode 100644 index 00000000..40809927 --- /dev/null +++ b/hsavmcore/config.h @@ -0,0 +1,59 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_CONFIG_H +#define _HSAVMCORE_CONFIG_H + +#include +#include + +#include "common.h" + +/* + * This represents the application's configuration. + */ +struct config { + /* Log message level */ + int verbose; + /* Path to the system's vmcore file */ + char vmcore_path[PATH_MAX]; + /* Path to the system's zcore hsa file */ + char zcore_hsa_path[PATH_MAX]; + /* + * Path to a directory where the application could create temporary + * files. + */ + char workdir_path[PATH_MAX]; + /* Path to a bind-mount target for vmcore Overlay */ + char bind_mount_vmcore_path[PATH_MAX]; + /* Path to a swap device/file */ + char swap[PATH_MAX]; + /* HSA memory size */ + int hsa_size; + /* Indicates whether the debugfs shall be mounted */ + bool mount_debugfs; + /* Indicates whether the HSA memory file reader shall be used */ + bool use_hsa_mem; + /* Indicates whether the HSA memory shall be released after caching */ + bool release_hsa; + /* Indicates whether a bind-mount of vmcore Proxy shall be enabled */ + bool bind_mount_vmcore; + /* Indicates whether the FUSE debug messages shall be enabled */ + bool fuse_debug; +}; + +/* + * Initializes the application's configuration to its default values. + */ +void init_config(struct config *config); + +/* + * Updates the application's configuration from the given configuration file. + */ +int update_config_from_file(const char *config_path, struct config *config); + +#endif diff --git a/hsavmcore/hsa.c b/hsavmcore/hsa.c new file mode 100644 index 00000000..b79263f6 --- /dev/null +++ b/hsavmcore/hsa.c @@ -0,0 +1,172 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_file.h" +#include "lib/util_log.h" + +#include "hsa.h" + +long get_hsa_size(const char *zcore_hsa_path) +{ + long size; + int ret; + + util_log_print(UTIL_LOG_DEBUG, "Reading HSA memory size from %s\n", + zcore_hsa_path); + + /* Read HSA size */ + ret = util_file_read_l(&size, 16, zcore_hsa_path); + if (ret < 0) { + util_log_print(UTIL_LOG_ERROR, "File read failed (%s)\n", + strerror(errno)); + return -1; + } + + return size; +} + +long get_hsa_vmcore_offset(const char *vmcore_path) +{ + Elf64_Ehdr elf_hdr; + int fd = -1, n, i; + long offset = -1; + + util_log_print(UTIL_LOG_DEBUG, + "Reading HSA memory offset from vmcore %s\n", + vmcore_path); + + /* Open vmcore file */ + fd = open(vmcore_path, O_RDONLY); + if (fd < 0) { + util_log_print(UTIL_LOG_ERROR, "open syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + util_log_print(UTIL_LOG_DEBUG, "Reading vmcore ELF header\n"); + + /* Read ELF header */ + n = read(fd, &elf_hdr, sizeof(Elf64_Ehdr)); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "read syscall failed (%s)\n", + strerror(errno)); + goto fail; + } else if (n != sizeof(Elf64_Ehdr)) { + util_log_print(UTIL_LOG_ERROR, + "read syscall read less data than expected (%s)\n", + strerror(errno)); + goto fail; + } + + /* Verify ELF header */ + if ((memcmp(elf_hdr.e_ident, ELFMAG, SELFMAG) != 0) || + elf_hdr.e_type != ET_CORE || elf_hdr.e_machine != EM_S390 || + elf_hdr.e_ident[EI_CLASS] != ELFCLASS64) { + util_log_print(UTIL_LOG_ERROR, "Invalid vmcore ELF header\n"); + goto fail; + } + + util_log_print(UTIL_LOG_DEBUG, + "Reading vmcore ELF program header(s)\n"); + + /* Read ELF program header(s) */ + n = lseek(fd, elf_hdr.e_phoff, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + /* + * Go through all ELF program headers and find one + * that starts at physical/virtual address 0x0. + */ + for (i = 0; i < elf_hdr.e_phnum; i++) { + Elf64_Phdr elf_phdr; + + util_log_print(UTIL_LOG_DEBUG, + "Reading vmcore ELF program header #%d\n", i); + + n = read(fd, &elf_phdr, sizeof(Elf64_Phdr)); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall failed (%s)\n", + strerror(errno)); + goto fail; + } else if (n != sizeof(Elf64_Phdr)) { + util_log_print(UTIL_LOG_ERROR, + "read syscall read less data than expected (%s)\n", + strerror(errno)); + goto fail; + } + + util_log_print(UTIL_LOG_DEBUG, + "vmcore ELF program segment #%d: type=%lx vaddr=%lx paddr=%lx offset=%lx\n", + i, elf_phdr.p_type, elf_phdr.p_vaddr, + elf_phdr.p_paddr, elf_phdr.p_offset); + + /* HSA memory starts at physical/virtual address 0x0 */ + if (elf_phdr.p_type == PT_LOAD && elf_phdr.p_vaddr == 0 && + elf_phdr.p_paddr == 0) { + offset = elf_phdr.p_offset; + break; + } + } + + if (offset < 0) { + util_log_print(UTIL_LOG_ERROR, + "Couldn't find HSA memory offset in vmcore\n"); + goto fail; + } + + util_log_print(UTIL_LOG_DEBUG, "HSA memory vmcore offset %lx\n", + offset); + + close(fd); + + return offset; + +fail: + if (fd >= 0) + close(fd); + return -1; +} + +int release_hsa(const char *zcore_hsa_path) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Release HSA memory via %s\n", + zcore_hsa_path); + + /* Release HSA memory */ + ret = util_file_write_s("0", zcore_hsa_path); + if (ret < 0) { + util_log_print(UTIL_LOG_ERROR, "File write failed (%s)\n", + strerror(errno)); + return -1; + } + + /* Verify that HSA memory has been released */ + if (get_hsa_size(zcore_hsa_path) > 0) { + util_log_print(UTIL_LOG_ERROR, "HSA memory release failed\n"); + return -1; + } + + util_log_print(UTIL_LOG_INFO, "HSA memory successfully released\n"); + + return 0; +} diff --git a/hsavmcore/hsa.h b/hsavmcore/hsa.h new file mode 100644 index 00000000..3091731b --- /dev/null +++ b/hsavmcore/hsa.h @@ -0,0 +1,75 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_HSA_H +#define _HSAVMCORE_HSA_H + +#include + +/* + * The interface to a HSA memory reader. + * This interface must be implemented by a concrete HSA memory reader. + */ +struct hsa_reader { + /* Total HSA memory size */ + long hsa_size; + /* Offset of HSA memory in /proc/vmcore */ + long hsa_vmcore_offset; + /* Destroys a concrete HSA memory reader */ + void (*destroy)(struct hsa_reader *self); + /* Reads a HSA memory block given by offset and size */ + int (*read_at)(struct hsa_reader *self, long offset, void *buf, + int size); +}; + +static inline long hsa_get_size(struct hsa_reader *self) +{ + return self->hsa_size; +} + +static inline long hsa_get_vmcore_offset(struct hsa_reader *self) +{ + return self->hsa_vmcore_offset; +} + +static inline void destroy_hsa_reader(struct hsa_reader *self) +{ + self->destroy(self); +} + +static inline int read_hsa_at(struct hsa_reader *self, long offset, void *buf, + int size) +{ + return self->read_at(self, offset, buf, size); +} + +/* + * Reads total HSA memory size from /sys/kernel/debug/zcore/hsa. + */ +long get_hsa_size(const char *zcore_hsa_path); + +/* + * Returns the offset of HSA memory in /proc/vmcore. + */ +long get_hsa_vmcore_offset(const char *vmcore_path); + +/* + * Releases HSA memory. + */ +int release_hsa(const char *zcore_hsa_path); + +/* + * Returns a pointer to the enclosing struct which contains + * the variable pointed to by the given pointer as a member. + */ +#define container_of(ptr, type, member) \ + ({ \ + const typeof(((type *)NULL)->member) *mptr = (ptr); \ + (type *)((char *)mptr - offsetof(type, member)); \ + }) + +#endif diff --git a/hsavmcore/hsa_file.c b/hsavmcore/hsa_file.c new file mode 100644 index 00000000..fb9a61b1 --- /dev/null +++ b/hsavmcore/hsa_file.c @@ -0,0 +1,236 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "lib/util_log.h" + +#include "common.h" +#include "hsa.h" +#include "hsa_file.h" + +struct hsa_file_reader { + struct hsa_reader super; + /* Temporary file containing a copy of the HSA memory */ + int fd; +}; + +static void destroy(struct hsa_reader *super) +{ + struct hsa_file_reader *self = + container_of(super, struct hsa_file_reader, super); + + close(self->fd); + free(self); +} + +static int read_at(struct hsa_reader *super, long offset, void *buf, int size) +{ + struct hsa_file_reader *self = + container_of(super, struct hsa_file_reader, super); + long n, nread = 0; + + util_log_print(UTIL_LOG_DEBUG, "HSA file read: offset=%lx size=%x\n", + offset, size); + + /* Validate given offset */ + if (offset >= super->hsa_size) + return 0; + + /* Validate given size */ + size = MIN(super->hsa_size - offset, size); + + n = lseek(self->fd, offset, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + return -1; + } + + while (size) { + n = read(self->fd, buf + nread, size); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall failed (%s)\n", + strerror(errno)); + return -1; + } else if (n == 0) { + break; + } + + nread += n; + size -= n; + } + + return nread; +} + +static int copy_hsa_to_file(const char *vmcore_path, const char *workdir_path, + long size, long offset) +{ + int fd_in = -1, fd_out = -1; + char cache_file_path[PATH_MAX]; + long n; + + snprintf(cache_file_path, sizeof(cache_file_path), "%s/%s", + workdir_path, HSA_CACHE_FILE); + + util_log_print(UTIL_LOG_DEBUG, + "Copy HSA memory from vmcore %s to cache file %s\n", + vmcore_path, cache_file_path); + + /* Open vmcore file */ + fd_in = open(vmcore_path, O_RDONLY); + if (fd_in < 0) { + util_log_print(UTIL_LOG_ERROR, "open syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + /* Open cache file */ + fd_out = open(cache_file_path, O_RDWR | O_CREAT | O_TRUNC, 0644); + if (fd_out < 0) { + util_log_print(UTIL_LOG_ERROR, "open syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + /* Unlink cache file to auto-delete it on close */ + n = unlink(cache_file_path); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "unlink syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + /* Copy HSA memory to cache file */ + n = lseek(fd_in, offset, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + /* Copy HSA memory chunkwise to the temporary file */ + while (size) { + char buf[1024]; + long nread, nwrite; + + /* Read a chunk from vmcore */ + nread = MIN((long)sizeof(buf), size); + + n = read(fd_in, buf, nread); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall failed (%s)\n", + strerror(errno)); + goto fail; + } else if (n == 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall read less data than expected\n"); + goto fail; + } + + /* Write a chunk to cache file */ + nwrite = n; + n = write(fd_out, buf, nwrite); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "write syscall failed (%s)\n", + strerror(errno)); + goto fail; + } else if (n != nwrite) { + util_log_print(UTIL_LOG_ERROR, + "write syscall wrote less data than expected\n"); + goto fail; + } + + size -= n; + } + + /* Reset cache file position */ + n = lseek(fd_out, 0, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + close(fd_in); + + return fd_out; + +fail: + if (fd_in >= 0) + close(fd_in); + if (fd_out >= 0) + close(fd_out); + return -1; +} + +struct hsa_reader *make_hsa_file_reader(const char *zcore_hsa_path, + const char *vmcore_path, + const char *workdir_path, long hsa_size, + bool release_hsa_flag) +{ + struct hsa_file_reader *self; + long hsa_vmcore_offset; + int fd; + + /* Calculate HSA size if not given by user */ + if (hsa_size < 0) { + hsa_size = get_hsa_size(zcore_hsa_path); + if (hsa_size <= 0) + return NULL; + } + hsa_vmcore_offset = get_hsa_vmcore_offset(vmcore_path); + if (hsa_vmcore_offset < 0) + return NULL; + + util_log_print(UTIL_LOG_INFO, "HSA: size=%lx vmcore offset=%lx\n", + hsa_size, hsa_vmcore_offset); + + /* + * Store the whole HSA memory from /proc/vmcore to a temporary file + * before releasing HSA. + */ + fd = copy_hsa_to_file(vmcore_path, workdir_path, hsa_size, + hsa_vmcore_offset); + if (fd < 0) + return NULL; + + if (release_hsa_flag) { + if (release_hsa(zcore_hsa_path)) { + close(fd); + return NULL; + } + } + + self = malloc(sizeof(struct hsa_file_reader)); + if (!self) { + util_log_print(UTIL_LOG_ERROR, "malloc failed\n"); + close(fd); + return NULL; + } + + self->super.hsa_size = hsa_size; + self->super.hsa_vmcore_offset = hsa_vmcore_offset; + self->super.destroy = destroy; + self->super.read_at = read_at; + self->fd = fd; + + return &self->super; +} diff --git a/hsavmcore/hsa_file.h b/hsavmcore/hsa_file.h new file mode 100644 index 00000000..872d32dd --- /dev/null +++ b/hsavmcore/hsa_file.h @@ -0,0 +1,27 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_HSA_FILE_H +#define _HSAVMCORE_HSA_FILE_H + +#include + +#include "hsa.h" + +/* + * This concrete HSA memory reader copies the whole HSA memory from /proc/vmcore + * to a temporary file. + * In order for it to work, the system must provide enough file storage. + * The advantage of this reader is that it doesn't require extra memory for + * caching. + */ +struct hsa_reader *make_hsa_file_reader(const char *zcore_hsa_path, + const char *vmcore_path, + const char *workdir_path, long hsa_size, + bool release_hsa_flag); + +#endif diff --git a/hsavmcore/hsa_mem.c b/hsavmcore/hsa_mem.c new file mode 100644 index 00000000..0e7d1240 --- /dev/null +++ b/hsavmcore/hsa_mem.c @@ -0,0 +1,151 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "lib/util_log.h" + +#include "hsa.h" +#include "hsa_mem.h" + +struct hsa_mem_reader { + struct hsa_reader super; + unsigned char cache[]; +}; + +static void destroy(struct hsa_reader *super) +{ + struct hsa_mem_reader *self = + container_of(super, struct hsa_mem_reader, super); + + free(self); +} + +static int read_at(struct hsa_reader *super, long offset, void *buf, int size) +{ + struct hsa_mem_reader *self = + container_of(super, struct hsa_mem_reader, super); + + util_log_print(UTIL_LOG_DEBUG, "HSA file read: offset=%lx size=%x\n", + offset, size); + + /* Validate given offset */ + if (offset >= super->hsa_size) + return 0; + + /* Validate given size */ + size = MIN(super->hsa_size - offset, size); + + memcpy(buf, self->cache + offset, size); + + return size; +} + +static int read_hsa(const char *vmcore_path, long offset, void *buf, int size) +{ + long n, nread = 0; + int fd = -1; + + util_log_print(UTIL_LOG_DEBUG, "Read HSA memory from vmcore %s\n", + vmcore_path); + + /* Open vmcore file */ + fd = open(vmcore_path, O_RDONLY); + if (fd < 0) { + util_log_print(UTIL_LOG_ERROR, "open syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + n = lseek(fd, offset, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + goto fail; + } + + /* Read HSA memory */ + while (size) { + n = read(fd, buf + nread, size); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall failed (%s)\n", + strerror(errno)); + goto fail; + } else if (n == 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall read less data than expected\n"); + goto fail; + } + + nread += n; + size -= n; + } + + close(fd); + + return 0; + +fail: + if (fd >= 0) + close(fd); + return -1; +} + +struct hsa_reader *make_hsa_mem_reader(const char *zcore_hsa_path, + const char *vmcore_path, long hsa_size, + bool release_hsa_flag) +{ + struct hsa_mem_reader *self; + long hsa_vmcore_offset; + + /* Calculate HSA size if not given by user */ + if (hsa_size < 0) { + hsa_size = get_hsa_size(zcore_hsa_path); + if (hsa_size <= 0) + return NULL; + } + hsa_vmcore_offset = get_hsa_vmcore_offset(vmcore_path); + if (hsa_vmcore_offset < 0) + return NULL; + + util_log_print(UTIL_LOG_INFO, "HSA: size=%lx vmcore offset=%lx\n", + hsa_size, hsa_vmcore_offset); + + self = malloc(sizeof(struct hsa_mem_reader) + hsa_size); + if (!self) { + util_log_print(UTIL_LOG_ERROR, "malloc failed\n"); + return NULL; + } + + /* Cache the whole HSA memory from /proc/vmcore before releasing HSA */ + if (read_hsa(vmcore_path, hsa_vmcore_offset, self->cache, hsa_size)) { + free(self); + return NULL; + } + + if (release_hsa_flag) { + if (release_hsa(zcore_hsa_path)) { + free(self); + return NULL; + } + } + + self->super.hsa_size = hsa_size; + self->super.hsa_vmcore_offset = hsa_vmcore_offset; + self->super.destroy = destroy; + self->super.read_at = read_at; + + return &self->super; +} diff --git a/hsavmcore/hsa_mem.h b/hsavmcore/hsa_mem.h new file mode 100644 index 00000000..1e33a1aa --- /dev/null +++ b/hsavmcore/hsa_mem.h @@ -0,0 +1,24 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_HSA_MEM_H +#define _HSAVMCORE_HSA_MEM_H + +#include + +#include "hsa.h" + +/* + * This concrete HSA memory reader reads the whole HSA memory from /proc/vmcore + * and caches it all in an internal memory buffer. + * In order for it to work, the system must provide enough memory or swap space. + */ +struct hsa_reader *make_hsa_mem_reader(const char *zcore_hsa_path, + const char *vmcore_path, long hsa_size, + bool release_hsa_flag); + +#endif diff --git a/hsavmcore/main.c b/hsavmcore/main.c new file mode 100644 index 00000000..e7f67bae --- /dev/null +++ b/hsavmcore/main.c @@ -0,0 +1,238 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYSTEMD +#include +#endif + +#include "lib/util_log.h" + +#include "common.h" +#include "config.h" +#include "cmdline_options.h" +#include "mount.h" +#include "swap.h" +#include "hsa.h" +#include "hsa_mem.h" +#include "hsa_file.h" +#include "proxy.h" +#include "overlay.h" + +#define MAX_WAIT_VMCORE_OVERLAY_SECS 5 + +static int bind_mount_vmcore(const char *src, const char *target, + int max_wait_secs) +{ + struct stat st; + int ret; + + util_log_print(UTIL_LOG_INFO, "Wait %d secs for %s to appear\n", + max_wait_secs, src); + + while (max_wait_secs--) { + if (!stat(src, &st)) + break; + sleep(1); + } + + if (stat(src, &st)) { + util_log_print(UTIL_LOG_ERROR, "Timeout for appearance of %s\n", + src); + return -1; + } + + ret = bind_mount(src, target); + if (ret < 0) + return -1; + + return 0; +} + +static void block_all_signals(void) +{ + sigset_t signal_set; + + sigfillset(&signal_set); + pthread_sigmask(SIG_BLOCK, &signal_set, NULL); +} + +static void unblock_all_signals(void) +{ + sigset_t signal_set; + + sigfillset(&signal_set); + pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL); +} + +static void *vmcore_overlay_server(void *arg) +{ + struct vmcore_overlay *vmcore_overlay = (struct vmcore_overlay *)arg; + int ret; + + util_log_print(UTIL_LOG_DEBUG, "vmcore overlay thread: start\n"); + + /* Unblock all signals because vmcore overlay handles them */ + unblock_all_signals(); + + /* Blocks until a signal has been received or an error occurred */ + ret = serve_vmcore_overlay(vmcore_overlay); + + util_log_print(UTIL_LOG_DEBUG, "vmcore overlay thread: end (%d)\n", + ret); + + return (void *)(long)ret; +} + +static void terminate_vmcore_overlay(pthread_t tid) +{ + pthread_kill(tid, SIGINT); + pthread_join(tid, NULL); +} + +static int wait_for_vmcore_overlay(pthread_t tid) +{ + int ret; + + pthread_join(tid, (void **)&ret); + + return ret; +} + +int main(int argc, char *argv[]) +{ + struct vmcore_overlay *vmcore_overlay; + struct vmcore_proxy *vmcore_proxy; + int exit_code = EXIT_SUCCESS, ret; + struct hsa_reader *hsa_reader; + pthread_t vmcore_overlay_tid; + struct config config; + + init_config(&config); + + parse_cmdline_options(argc, argv, &config); + + if (strlen(config.swap)) { + ret = swap_on(config.swap); + if (ret < 0) { + exit_code = EXIT_FAILURE; + goto done; + } + } + + if (config.mount_debugfs) { + ret = mount_debugfs(DEBUGFS_MOUNT_POINT); + if (ret < 0) { + exit_code = EXIT_FAILURE; + goto swap_off; + } + } + + if (config.use_hsa_mem) + hsa_reader = + make_hsa_mem_reader(config.zcore_hsa_path, + config.vmcore_path, config.hsa_size, + config.release_hsa); + else + hsa_reader = make_hsa_file_reader(config.zcore_hsa_path, + config.vmcore_path, + config.workdir_path, + config.hsa_size, + config.release_hsa); + if (!hsa_reader) { + exit_code = EXIT_FAILURE; + goto unmount_debugfs; + } + + vmcore_proxy = make_vmcore_proxy(config.vmcore_path, hsa_reader); + if (!vmcore_proxy) { + exit_code = EXIT_FAILURE; + goto destroy_hsa_reader; + } + + vmcore_overlay = make_vmcore_overlay(vmcore_proxy, OVERLAY_MOUNT_POINT, + config.fuse_debug); + if (!vmcore_overlay) { + exit_code = EXIT_FAILURE; + goto destroy_vmcore_proxy; + } + + /* vmcore overlay thread handles all signals */ + block_all_signals(); + + /* Start vmcore overlay thread which handles file system calls */ + ret = pthread_create(&vmcore_overlay_tid, NULL, vmcore_overlay_server, + vmcore_overlay); + if (ret < 0) { + exit_code = EXIT_FAILURE; + goto destroy_vmcore_overlay; + } + + /* Bind mount /proc/vmcore */ + if (config.bind_mount_vmcore) { + ret = bind_mount_vmcore(OVERLAY_MOUNT_POINT "/" VMCORE_FILE, + config.bind_mount_vmcore_path, + MAX_WAIT_VMCORE_OVERLAY_SECS); + if (ret < 0) { + terminate_vmcore_overlay(vmcore_overlay_tid); + exit_code = EXIT_FAILURE; + goto destroy_vmcore_overlay; + } + } + +#ifdef HAVE_SYSTEMD + /* Tell systemd that service is ready now */ + ret = sd_notify(0, "READY=1"); + if (ret <= 0) + util_log_print(UTIL_LOG_WARN, "Failed to notify systemd (%d)\n", + ret); +#endif + + ret = wait_for_vmcore_overlay(vmcore_overlay_tid); + if (ret < 0) + exit_code = EXIT_FAILURE; + +#ifdef HAVE_SYSTEMD + /* Tell systemd that service is stopping now */ + ret = sd_notify(0, "STOPPING=1"); + if (ret <= 0) + util_log_print(UTIL_LOG_WARN, "Failed to notify systemd (%d)\n", + ret); +#endif + + unblock_all_signals(); + + if (config.bind_mount_vmcore) + unmount_detach(config.bind_mount_vmcore_path); + +destroy_vmcore_overlay: + destroy_vmcore_overlay(vmcore_overlay); + +destroy_vmcore_proxy: + destroy_vmcore_proxy(vmcore_proxy); + +destroy_hsa_reader: + destroy_hsa_reader(hsa_reader); + +unmount_debugfs: + if (config.mount_debugfs) + unmount_detach(DEBUGFS_MOUNT_POINT); + +swap_off: + if (strlen(config.swap)) + swap_off(config.swap); + +done: + return exit_code; +} diff --git a/hsavmcore/mount.c b/hsavmcore/mount.c new file mode 100644 index 00000000..0473b871 --- /dev/null +++ b/hsavmcore/mount.c @@ -0,0 +1,63 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include + +#include "lib/util_log.h" + +#include "mount.h" + +int mount_debugfs(const char *target) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Mount debugfs on %s\n", target); + + ret = mount("none", target, "debugfs", 0, NULL); + if (ret) { + util_log_print(UTIL_LOG_ERROR, "mount syscall failed (%s)\n", + strerror(errno)); + return -1; + } + + return 0; +} + +int bind_mount(const char *src, const char *target) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Bind mount %s on %s\n", src, target); + + ret = mount(src, target, "", MS_BIND, NULL); + if (ret) { + util_log_print(UTIL_LOG_ERROR, "mount syscall failed (%s)\n", + strerror(errno)); + return -1; + } + + return 0; +} + +int unmount_detach(const char *target) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Unmount detach %s\n", target); + + ret = umount2(target, MNT_DETACH); + if (ret) { + util_log_print(UTIL_LOG_ERROR, "umount2 syscall failed (%s)\n", + strerror(errno)); + return -1; + } + + return 0; +} diff --git a/hsavmcore/mount.h b/hsavmcore/mount.h new file mode 100644 index 00000000..a74acc2e --- /dev/null +++ b/hsavmcore/mount.h @@ -0,0 +1,17 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_MOUNT_H +#define _HSAVMCORE_MOUNT_H + +int mount_debugfs(const char *target); + +int bind_mount(const char *src, const char *target); + +int unmount_detach(const char *target); + +#endif diff --git a/hsavmcore/overlay.c b/hsavmcore/overlay.c new file mode 100644 index 00000000..8efd7529 --- /dev/null +++ b/hsavmcore/overlay.c @@ -0,0 +1,209 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUSE_USE_VERSION 26 +#include + +#include "lib/util_log.h" + +#include "common.h" +#include "overlay.h" + +#define ROOT_DIR "/" + +struct vmcore_overlay { + struct vmcore_proxy *vmcore_proxy; + char mount_point[PATH_MAX]; + bool fuse_debug; +}; + +static int vmcore_fuse_getattr(const char *path, struct stat *stbuf) +{ + struct vmcore_overlay *overlay = fuse_get_context()->private_data; + int ret = 0; + + memset(stbuf, 0, sizeof(struct stat)); + + if (strcmp(path, ROOT_DIR) == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } else if (strcmp(path + 1, VMCORE_FILE) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + stbuf->st_size = vmcore_proxy_size(overlay->vmcore_proxy); + } else { + ret = -ENOENT; + } + + return ret; +} + +static int vmcore_fuse_readdir(const char *path, void *buf, + fuse_fill_dir_t filler, off_t offset, + struct fuse_file_info *fi) +{ + (void)offset; + (void)fi; + + if (strcmp(path, ROOT_DIR) != 0) + return -ENOENT; + + /* We have only one file */ + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + filler(buf, VMCORE_FILE, NULL, 0); + + return 0; +} + +static int vmcore_fuse_open(const char *path, struct fuse_file_info *fi) +{ + if (strcmp(path + 1, VMCORE_FILE) != 0) + return -ENOENT; + + if ((fi->flags & O_ACCMODE) != O_RDONLY) + return -EACCES; + + return 0; +} + +static int vmcore_fuse_read(const char *path, char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + (void)fi; + + if (strcmp(path + 1, VMCORE_FILE) != 0) + return -ENOENT; + + struct vmcore_overlay *overlay = fuse_get_context()->private_data; + + return read_vmcore_proxy_at(overlay->vmcore_proxy, offset, buf, size); +} + +static int setup_fuse_args(struct fuse_args *args, const char *mount_point, + bool debug) +{ + int ret; + + ret = fuse_opt_add_arg(args, NAME); + if (ret) + goto done; + + /* Single-threaded */ + ret = fuse_opt_add_arg(args, "-s"); + if (ret) + goto done; + + /* Foreground */ + ret = fuse_opt_add_arg(args, "-f"); + if (ret) + goto done; + + /* Debugging */ + if (debug) { + ret = fuse_opt_add_arg(args, "-d"); + if (ret) + goto done; + } + + ret = fuse_opt_add_arg(args, mount_point); + if (ret) + goto done; + +done: + if (ret) + return -1; + else + return 0; +} + +struct vmcore_overlay *make_vmcore_overlay(struct vmcore_proxy *vmcore_proxy, + const char *mount_point, + bool fuse_debug) +{ + struct vmcore_overlay *overlay; + + util_log_print(UTIL_LOG_INFO, "vmcore overlay: mountpoint=%s\n", + mount_point); + + overlay = malloc(sizeof(struct vmcore_overlay)); + if (!overlay) { + util_log_print(UTIL_LOG_ERROR, "malloc failed\n"); + return NULL; + } + + overlay->vmcore_proxy = vmcore_proxy; + strncpy(overlay->mount_point, mount_point, + sizeof(overlay->mount_point) - 1); + /* Ensure null termination */ + overlay->mount_point[sizeof(overlay->mount_point) - 1] = '\0'; + overlay->fuse_debug = fuse_debug; + + return overlay; +} + +void destroy_vmcore_overlay(struct vmcore_overlay *overlay) +{ + free(overlay); +} + +/* + * FUSE file system operations + */ +static struct fuse_operations vmcore_fuse_ops = { + .getattr = vmcore_fuse_getattr, + .readdir = vmcore_fuse_readdir, + .open = vmcore_fuse_open, + .read = vmcore_fuse_read, +}; + +int serve_vmcore_overlay(struct vmcore_overlay *overlay) +{ + struct fuse_args args = FUSE_ARGS_INIT(0, NULL); + int ret; + + util_log_print(UTIL_LOG_DEBUG, "vmcore overlay: FUSE main\n"); + + ret = setup_fuse_args(&args, overlay->mount_point, overlay->fuse_debug); + if (ret < 0) + goto free_args; + + /* Create mount point */ + ret = mkdir(overlay->mount_point, 0755); + if (ret < 0) { + util_log_print(UTIL_LOG_ERROR, "mkdir syscall failed (%s)\n", + strerror(errno)); + goto free_args; + } + + /* + * Run file system, blocks until a signal has been received or an error + * occurred. + */ + fuse_main(args.argc, args.argv, &vmcore_fuse_ops, overlay); + + /* Remove mount point */ + rmdir(overlay->mount_point); + + ret = 0; + +free_args: + + fuse_opt_free_args(&args); + + return ret; +} diff --git a/hsavmcore/overlay.h b/hsavmcore/overlay.h new file mode 100644 index 00000000..d8fb57ee --- /dev/null +++ b/hsavmcore/overlay.h @@ -0,0 +1,33 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_OVERLAY_H +#define _HSAVMCORE_OVERLAY_H + +#include + +#include "proxy.h" + +/* + * A vmcore Overlay exports a vmcore Proxy as a normal read-only file + * that could be used, for instance, by *makedumpfile*. + */ + +struct vmcore_overlay; + +struct vmcore_overlay *make_vmcore_overlay(struct vmcore_proxy *vmcore_proxy, + const char *mount_point, + bool fuse_debug); + +void destroy_vmcore_overlay(struct vmcore_overlay *overlay); + +/* + * This method handles all file system calls and blocks until a signal arrives. + */ +int serve_vmcore_overlay(struct vmcore_overlay *overlay); + +#endif diff --git a/hsavmcore/proxy.c b/hsavmcore/proxy.c new file mode 100644 index 00000000..b8b64b2e --- /dev/null +++ b/hsavmcore/proxy.c @@ -0,0 +1,198 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lib/zt_common.h" +#include "lib/util_log.h" + +#include "proxy.h" + +struct vmcore_proxy { + int vmcore_fd; + long vmcore_size; + struct hsa_reader *hsa_reader; +}; + +static int read_file_at(int fd, long offset, void *buf, int size) +{ + long n, nread = 0; + + util_log_print(UTIL_LOG_DEBUG, + "vmcore proxy vmcore read: offset=%lx size=%x\n", offset, + size); + + n = lseek(fd, offset, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + return -1; + } + + while (size) { + n = read(fd, buf + nread, size); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, + "read syscall failed (%s)\n", + strerror(errno)); + return -1; + } else if (n == 0) { + break; + } + + nread += n; + size -= n; + } + + return nread; +} + +static long get_vmcore_size(int fd) +{ + long n, size; + + /* Get vmcore file size */ + n = lseek(fd, 0, SEEK_END); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + return 0; + } + + size = n; + + /* Reset vmcore file position */ + n = lseek(fd, 0, SEEK_SET); + if (n < 0) { + util_log_print(UTIL_LOG_ERROR, "lseek syscall failed (%s)\n", + strerror(errno)); + return 0; + } + + return size; +} + +struct vmcore_proxy *make_vmcore_proxy(const char *vmcore_path, + struct hsa_reader *hsa_reader) +{ + struct vmcore_proxy *proxy; + int vmcore_fd; + long vmcore_size; + + util_log_print(UTIL_LOG_INFO, "vmcore proxy: vmcore path=%s\n", + vmcore_path); + + /* Open vmcore file */ + vmcore_fd = open(vmcore_path, O_RDONLY); + if (vmcore_fd < 0) { + util_log_print(UTIL_LOG_ERROR, "open syscall failed (%s)\n", + strerror(errno)); + return NULL; + } + + vmcore_size = get_vmcore_size(vmcore_fd); + if (!vmcore_size) { + close(vmcore_fd); + return NULL; + } + + util_log_print(UTIL_LOG_INFO, "vmcore proxy: vmcore size=%lx\n", + vmcore_size); + + proxy = malloc(sizeof(struct vmcore_proxy)); + if (!proxy) { + util_log_print(UTIL_LOG_ERROR, "malloc failed\n"); + close(vmcore_fd); + return NULL; + } + + proxy->vmcore_fd = vmcore_fd; + proxy->vmcore_size = vmcore_size; + proxy->hsa_reader = hsa_reader; + + return proxy; +} + +void destroy_vmcore_proxy(struct vmcore_proxy *proxy) +{ + close(proxy->vmcore_fd); + free(proxy); +} + +long vmcore_proxy_size(struct vmcore_proxy *proxy) +{ + return proxy->vmcore_size; +} + +int read_vmcore_proxy_at(struct vmcore_proxy *proxy, long offset, void *buf, + int size) +{ + const long hsa_size = hsa_get_size(proxy->hsa_reader); + const long hsa_vmcore_offset = hsa_get_vmcore_offset(proxy->hsa_reader); + long nread = 0; + + util_log_print(UTIL_LOG_DEBUG, + "vmcore proxy read: offset=%lx size=%x\n", offset, size); + + /* + * The caller might try to read beyond the maximum length of vmcore. + * This guarantees the termination of the loop below in that case. + */ + size = MIN(proxy->vmcore_size - offset, size); + + /* + * 0 HSA offset HSA offset + vmcore size + * HSA size + * + * +---------------------+---------------------+-----------------------+ + * | | | | + * | vmcore 1st part | HSA memory region | vmcore 2nd part | + * | | | | + * +---------------------+---------------------+-----------------------+ + */ + + while (size) { + long n, nbyte; + + if (offset < hsa_vmcore_offset) { + /* vmcore 1st part */ + nbyte = MIN(hsa_vmcore_offset - offset, size); + n = read_file_at(proxy->vmcore_fd, offset, buf + nread, + nbyte); + } else if (offset >= hsa_vmcore_offset && + offset < (hsa_vmcore_offset + hsa_size)) { + /* HSA memory region */ + nbyte = MIN(hsa_vmcore_offset + hsa_size - offset, + size); + n = read_hsa_at(proxy->hsa_reader, + offset - hsa_vmcore_offset, + buf + nread, nbyte); + } else { + /* vmcore 2nd part */ + nbyte = MIN(proxy->vmcore_size - offset, size); + n = read_file_at(proxy->vmcore_fd, offset, buf + nread, + nbyte); + } + + if (n != nbyte) + return -1; + + nread += n; + size -= n; + offset += n; + } + + return nread; +} diff --git a/hsavmcore/proxy.h b/hsavmcore/proxy.h new file mode 100644 index 00000000..2a6b725b --- /dev/null +++ b/hsavmcore/proxy.h @@ -0,0 +1,35 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_PROXY_H +#define _HSAVMCORE_PROXY_H + +#include "hsa.h" + +/* + * A vmcore Proxy combines the original /proc/vmcore file with a HSA memory + * reader into a new interface which can be used to read vmcore data w/o being + * aware that the HSA memory region is NOT contained in the file /proc/vmcore. + * + * After releasing the HSA memory, the original /proc/vmcore will contain + * a *hole* where the HSA memory was located. The vmcore proxy hides this + * inconvenience from the user of this interface. + */ + +struct vmcore_proxy; + +struct vmcore_proxy *make_vmcore_proxy(const char *vmcore_path, + struct hsa_reader *hsa_reader); + +void destroy_vmcore_proxy(struct vmcore_proxy *proxy); + +long vmcore_proxy_size(struct vmcore_proxy *proxy); + +int read_vmcore_proxy_at(struct vmcore_proxy *proxy, long offset, void *buf, + int size); + +#endif diff --git a/hsavmcore/swap.c b/hsavmcore/swap.c new file mode 100644 index 00000000..d904928b --- /dev/null +++ b/hsavmcore/swap.c @@ -0,0 +1,51 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lib/util_log.h" + +#include "common.h" +#include "swap.h" + +int swap_on(const char *path) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Swap on %s\n", path); + + ret = swapon(path, 0); + if (ret) { + util_log_print(UTIL_LOG_ERROR, "swapon syscall failed (%s)\n", + strerror(errno)); + return ret; + } + + return 0; +} + +int swap_off(const char *path) +{ + int ret; + + util_log_print(UTIL_LOG_INFO, "Swap off %s\n", path); + + ret = swapoff(path); + if (ret) { + util_log_print(UTIL_LOG_ERROR, "swapoff syscall failed (%s)\n", + strerror(errno)); + return ret; + } + + return 0; +} diff --git a/hsavmcore/swap.h b/hsavmcore/swap.h new file mode 100644 index 00000000..ccfde1fb --- /dev/null +++ b/hsavmcore/swap.h @@ -0,0 +1,15 @@ +/* + * Copyright IBM Corp. 2021 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#ifndef _HSAVMCORE_SWAP_H +#define _HSAVMCORE_SWAP_H + +int swap_on(const char *path); + +int swap_off(const char *path); + +#endif