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