diff --git a/zipl/src/Makefile b/zipl/src/Makefile index e1c4e3a6..786bb7f3 100644 --- a/zipl/src/Makefile +++ b/zipl/src/Makefile @@ -16,7 +16,7 @@ zipl_helpers = $(basename $(wildcard zipl_helper.*.c)) chreipl_helpers = $(subst zipl_,chreipl_, $(zipl_helpers)) zipl_stage3 = ../boot/stage3.bin -all: zipl $(chreipl_helpers) $(zipl_stage3) +all: zipl zipl-editenv $(chreipl_helpers) $(zipl_stage3) zipl: $(objects) $(libs) @@ -24,19 +24,23 @@ zipl_helper.device-mapper: $(rootdir)/libdasd/libdasd.a \ $(rootdir)/libvtoc/libvtoc.a \ $(libs) zipl_helper.device-mapper.o +zipl-editenv: $(libs) misc.o error.o fs-map.o bootmap_header.o \ + envblk.o zipl-editenv.o + chreipl_helper.%: zipl_helper.% ln -f -s $< $@ install: all $(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) $(INSTALL) -c zipl $(DESTDIR)$(BINDIR) + $(INSTALL) -c zipl-editenv $(DESTDIR)$(BINDIR) $(INSTALL) -m 755 $(zipl_helpers) $(chreipl_helpers) \ $(DESTDIR)$(TOOLS_LIBDIR) $(CP) --no-dereference $(chreipl_helpers) $(DESTDIR)$(TOOLS_LIBDIR) $(CP) --no-dereference $(zipl_stage3) $(DESTDIR)$(TOOLS_LIBDIR) clean: - rm -f *.o $(zipl_helpers) $(chreipl_helpers) zipl + rm -f *.o $(zipl_helpers) $(chreipl_helpers) zipl zipl-editenv .PHONY: all install clean diff --git a/zipl/src/zipl-editenv.c b/zipl/src/zipl-editenv.c new file mode 100644 index 00000000..91e0f3d2 --- /dev/null +++ b/zipl/src/zipl-editenv.c @@ -0,0 +1,611 @@ +/* + * zipl-editenv: Environment Editor for zSeries Initial Program Loader + * + * Copyright IBM Corp. 2020 + * + * 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 +#include +#include "disk.h" +#include "error.h" +#include "misc.h" +#include "bootmap.h" +#include "envblk.h" +#include "lib/util_prg.h" +#include "lib/util_opt.h" +#include "lib/util_part.h" +#include "lib/util_path.h" +#include "lib/util_proc.h" + +/* from linux/fs.h */ +#define FIBMAP _IO(0x00, 1) +#define FIGETBSZ _IO(0x00, 2) + +static const struct util_prg prg = { + .desc = "zipl-editenv: Environment Editor for zSeries Initial Program Loader", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2021, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +int verbose; +static char *bootmap_dir; + +enum op_id { + INVALID_OP_ID, + SET_OP_ID, + UNSET_OP_ID, + RESET_OP_ID, + LIST_OP_ID, + LAST_OP_ID +}; + +static char *op_desc[LAST_OP_ID] = {"invalid", "set", "unset", "reset", "list"}; + +struct zipl_envblk { + int fd; + char *buf; + off_t offset; + int size; + unsigned int need_update:1; +}; + +static char *opcode2desc(enum op_id opcode) +{ + return op_desc[opcode]; +} + +static void zipl_envblk_init(struct zipl_envblk *zeb) +{ + memset(zeb, 0, sizeof(*zeb)); + zeb->fd = -1; +} + +static void print_name_value(char *this) +{ + printf("%s\n", this); +} + +static int do_list(char *envblk, unsigned int envblk_size) +{ + return envblk_scan(envblk, envblk_size, print_name_value); +} + +static int do_set(char *envblk, unsigned int envblk_size, + const char *name, const char *new_val) +{ + unsigned int name_len, new_val_len; + char *fss; /* free space start */ + unsigned int lines_scanned = 0; + int name_found = 0; + char *s, *end; + + name_len = strlen(name); + new_val_len = strlen(new_val); + + s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1; + end = envblk + envblk_size; + + /* + * find the start of free space + */ + for (fss = end - 1; *fss == '\0'; fss--) + ; + if (*fss != '\n') { + error_reason("Found corrupted environment block - please run zipl"); + return -1; + } + fss++; + + while (fss - s > name_len) { + if (lines_scanned >= ENVBLK_MAX_LINES) { + error_reason("Found corrupted environment block - please run zipl"); + return -1; + } + if (memcmp(s, name, name_len) == 0 && s[name_len] == '=') { + unsigned int cur_val_len; + /* + * such name exists, replace its current value + */ + s += (name_len + 1); + + cur_val_len = 0; + while (s + cur_val_len < end && s[cur_val_len] != '\n') + cur_val_len++; + if (s + cur_val_len >= end) { + error_reason("Found corrupted environment block - please run zipl"); + return -1; + } + if (new_val_len > cur_val_len && + end - fss < new_val_len - cur_val_len) { + error_reason("Not enough space for new value"); + return -1; + } + /* + * make a precise-sized room for the new value + */ + if (new_val_len < cur_val_len) { + memmove(s + new_val_len, s + cur_val_len, + end - (s + cur_val_len)); + + memset(fss + cur_val_len - new_val_len, '\0', + cur_val_len - new_val_len); + } else + memmove(s + new_val_len, s + cur_val_len, + end - (s + new_val_len)); + name_found = 1; + break; + } + lines_scanned++; + s = envblk_next_line(s, end); + } + assert(lines_scanned <= ENVBLK_MAX_LINES); + + if (!name_found) { + if (lines_scanned == ENVBLK_MAX_LINES) { + error_reason("Maximum number of lines reached"); + return -1; + } + /* + * append a new variable + */ + if (end - fss < name_len + new_val_len + 2) { + error_reason("Not enough space in environment block"); + return -1; + } + memcpy(fss, name, name_len); + s = fss + name_len; + *s++ = '='; + } + /* + * copy the new value and terminate it with a new line symbol + */ + memcpy(s, new_val, new_val_len); + s[new_val_len] = '\n'; + return 0; +} + +static int do_unset(char *envblk, int envblk_len, const char *name) +{ + unsigned int name_len; + char *s, *end; + + name_len = strlen(name); + s = envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1; + end = envblk + envblk_len; + + while (end - s >= name_len + 2 /* minimal length of + * pattern "name=foo" + */) { + if (memcmp(s, name, name_len) == 0 && s[name_len] == '=') { + /* + * name was found. Locate the whole + * "named" line and cut it including + * the trailing "\n" + */ + unsigned int cut_len = name_len + 1; + + while (s + cut_len < end) { + if (s[cut_len] == '\n') + break; + cut_len++; + } + if (s + cut_len >= end) { + /* + * trailing "\n" not found + */ + error_reason("Found corrupted environment block - please run zipl"); + return -1; + } + cut_len++; + memmove(s, s + cut_len, end - (s + cut_len)); + memset(end - cut_len, '\0', cut_len); + return 0; + } + s = envblk_next_line(s, end); + } + error_reason("Name %s not found", name); + return -1; +} + +/** + * sync the content of the environment block with disk + */ +static int envblk_update(struct zipl_envblk *zeb) +{ + struct util_proc_part_entry part_entry; + struct stat info; + blocknum_t blknr; + char *dev_name; + int dev_fd; + + if (fstat(zeb->fd, &info)) + return -1; + + if (util_proc_part_get_entry(info.st_dev, &part_entry) != 0) + return -1; + + dev_name = misc_make_path("/dev", part_entry.name); + if (!dev_name) { + error_reason("Could not make path for %s", part_entry.name); + return -1; + } + dev_fd = open(dev_name, O_RDWR | O_DIRECT); + if (dev_fd < 0) { + error_reason("Could not open file %s", dev_name); + free(dev_name); + goto error; + } + free(dev_name); + /* + * det disk address of the environment block + */ + if (fs_map(zeb->fd, zeb->offset, &blknr, zeb->size) != 0) + goto error_close; + + if (lseek64(dev_fd, blknr * (uint64_t)zeb->size, SEEK_SET) < 0) { + error_reason(strerror(errno)); + goto error_close; + } + if (write(dev_fd, zeb->buf, zeb->size) != zeb->size) { + error_reason("Could not update physical block %ld on %s", + blknr, part_entry.name); + goto error_close; + } + if (verbose) + printf("Physical block %ld on %s got updated\n", + blknr, part_entry.name); + + util_proc_part_free_entry(&part_entry); + return 0; +error_close: + close(dev_fd); +error: + util_proc_part_free_entry(&part_entry); + return -1; +} + +static int envblk_close(struct zipl_envblk *zeb) +{ + if (zeb->buf != NULL) { + free(zeb->buf); + zeb->buf = NULL; + } + if (zeb->fd < 0) + return 0; + + if (close(zeb->fd) == 0) { + zeb->fd = -1; + return 0; + } + return -1; +} + +/** + * Open a regular file, which contains environment block, and + * read that block to a buffer. Fill in environment block info + * ZEB with valid values + */ +static int envblk_open(struct zipl_envblk *zeb) +{ + char *bootmap_file; + + if (zeb->fd >= 0) + /* it was opened before */ + return 0; + bootmap_file = + misc_make_path(bootmap_dir != NULL ? bootmap_dir : "/boot", + "bootmap"); + if (!bootmap_file) { + error_reason("Could not make path for bootmap file"); + return -1; + } + zeb->fd = open(bootmap_file, O_RDONLY); + if (zeb->fd < 0) { + error_reason("Could not open bootmap file %s: %s", + bootmap_file, strerror(errno)); + free(bootmap_file); + bootmap_file = NULL; + return -1; + } + if (verbose) + printf("Processing bootmap file at %s\n", bootmap_file); + + if (envblk_size_get(zeb->fd, &zeb->size)) { + error_reason("Could not get environment block size"); + goto error; + } + if (envblk_offset_get(zeb->fd, &zeb->offset)) { + error_reason("Could not get environment block location"); + goto error; + } + if (zeb->offset == 0) { + error_reason("Environment not installed - please run zipl"); + goto error; + } + /* reopen for direct operations */ + close(zeb->fd); + zeb->fd = open(bootmap_file, O_RDWR | O_DIRECT); + if (zeb->fd < 0) { + error_reason("Could not open environment block at %s", + bootmap_file); + goto error; + } + free(bootmap_file); + bootmap_file = NULL; + + zeb->buf = aligned_alloc(zeb->size, zeb->size); + if (!zeb->buf) { + error_reason("Could not allocate aligned memory region"); + goto error; + } + if (lseek(zeb->fd, zeb->offset, SEEK_SET) < 0) { + error_reason(strerror(errno)); + goto error; + } + if (read(zeb->fd, zeb->buf, zeb->size) != zeb->size) { + error_reason("Could not read environment block"); + goto error; + } + if (memcmp(zeb->buf, ZIPL_ENVBLK_SIGNATURE, + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1) != 0) { + error_reason("Found corrupted environment block - please run zipl"); + goto error; + } + return 0; +error: + if (bootmap_file) + free(bootmap_file); + envblk_close(zeb); + return -1; +} + +static int env_list(struct zipl_envblk *zeb) +{ + if (envblk_open(zeb)) + return -1; + return do_list(zeb->buf, strnlen(zeb->buf, zeb->size)); +} + +static int env_set(struct zipl_envblk *zeb, char *arg) +{ + char *name, *value; + + if (envblk_open(zeb)) + return -1; + /* + * parse the argument, locate name and value + */ + name = strdup(arg); + if (!name) + return -1; + + value = strchr(name, '='); + if (!value || value == name) { + /* name is not identified */ + error_reason("Invalid argument %s", arg); + goto error; + } + *value = '\0'; + value++; + + if (envblk_check_name(name, strlen(name))) { + error_reason("Unacceptable name '%s'", name); + goto error; + } + if (do_set(zeb->buf, zeb->size, name, value) == 0) { + free(name); + zeb->need_update = 1; + return 0; + } +error: + free(name); + return -1; +} + +static int env_unset(struct zipl_envblk *zeb, char *name) +{ + if (envblk_open(zeb)) + return -1; + /* + * check name for validness + */ + if (strchr(name, '=') != NULL) { + error_reason("Invalid argument %s", name); + return -1; + } + if (do_unset(zeb->buf, strnlen(zeb->buf, zeb->size), name) == 0) { + zeb->need_update = 1; + return 0; + } + return -1; +} + +static void env_blank(char *envblk, int envblk_len) +{ + memset(envblk + sizeof(ZIPL_ENVBLK_SIGNATURE) - 1, '\0', + envblk_len - sizeof(ZIPL_ENVBLK_SIGNATURE) + 1); +} + +static int env_reset(struct zipl_envblk *zeb) +{ + if (envblk_open(zeb)) + return -1; + + env_blank(zeb->buf, zeb->size); + zeb->need_update = 1; + return 0; +} + +static int save_op(enum op_id *dst, enum op_id src) +{ + if (*dst != INVALID_OP_ID) { + error_reason("Can't perform more than one operation (%s and %s) at a time\n", + opcode2desc(*dst), opcode2desc(src)); + return -EINVAL; + } + *dst = src; + return 0; +} + +static int save_op_arg(enum op_id *dst, enum op_id src, + char **dst_arg, char *src_arg) +{ + int ret; + + ret = save_op(dst, src); + if (ret) + return ret; + *dst_arg = strdup(src_arg); + if (*dst_arg == NULL) { + error_reason("Couldn't copy the argument\n"); + return -ENOMEM; + } + return 0; +} + +static struct util_opt opt_vec[] = { + UTIL_OPT_SECTION("OPTIONS WITHOUT ARGUMENTS"), + { + .option = { "list", no_argument, NULL, 'l'}, + .desc = "print list of zIPL environment variables with their values", + }, + { + .option = { "reset", no_argument, NULL, 'r'}, + .desc = "remove all variables from zIPL environment", + }, + { + .option = { "verbose", no_argument, NULL, 'V'}, + .desc = "provide more information", + }, + UTIL_OPT_SECTION("OPTIONS WITH ARGUMENTS"), + { + .option = { "target", required_argument, NULL, 't'}, + .argument = "DIR", + .desc = "specify directory, where bootmap file is located", + }, + { + .option = { "set", required_argument, NULL, 's'}, + .argument = "NAME=VALUE", + .desc = "assign value VALUE to variable NAME", + }, + { + .option = { "unset", required_argument, NULL, 'u'}, + .argument = "NAME", + .desc = "remove variable NAME from zIPL environment", + }, + UTIL_OPT_SECTION("STANDARD OPTIONS"), + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END, +}; + +int main(int argc, char *argv[]) +{ + enum op_id opcode = INVALID_OP_ID; + struct zipl_envblk zeb; + char *saved_arg = NULL; + int ret = 0; + int c; + + zipl_envblk_init(&zeb); + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + if (argc < 2) { + fprintf(stderr, + "%s: Missing command\nUse 'zipl-editenv --help' for more information\n", + argv[0]); + return EXIT_FAILURE; + } + while (1) { + c = util_opt_getopt_long(argc, argv); + if (c == -1) + break; + switch (c) { + case 'h': + util_prg_print_help(); + util_opt_print_help(); + goto out; + case 'v': + util_prg_print_version(); + goto out; + case 'V': + verbose = 1; + break; + case 't': + bootmap_dir = optarg; + break; + case 'r': + ret = save_op(&opcode, RESET_OP_ID); + break; + case 'l': + ret = save_op(&opcode, LIST_OP_ID); + break; + case 's': + ret = save_op_arg(&opcode, SET_OP_ID, + &saved_arg, optarg); + break; + case 'u': + ret = save_op_arg(&opcode, UNSET_OP_ID, + &saved_arg, optarg); + break; + default: + util_opt_print_parse_error(c, argv); + ret = -1; + } + if (ret) + goto out; + } + if (optind < argc) { + fprintf(stderr, "%s: Unknown command", argv[0]); + while (optind < argc) + printf(" %s", argv[optind++]); + fprintf(stderr, "\n"); + goto out; + } + switch (opcode) { + case SET_OP_ID: + ret = env_set(&zeb, saved_arg); + break; + case UNSET_OP_ID: + ret = env_unset(&zeb, saved_arg); + break; + case RESET_OP_ID: + ret = env_reset(&zeb); + break; + case LIST_OP_ID: + ret = env_list(&zeb); + break; + default: + break; + } +out: + if (zeb.need_update) + ret = envblk_update(&zeb); + + envblk_close(&zeb); + if (saved_arg != NULL) + free(saved_arg); + if (ret) { + error_print(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +}