/* * ipl_tools - Linux for System z reipl and shutdown tools * * Command: chreipl * * Copyright IBM Corp. 2008, 2017 * * 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 "lib/util_libc.h" #include "lib/zt_common.h" #include "ipl_tools.h" #include "proc.h" #define BOOTPARMS_NSS_MAX 56 #define BOOTPARMS_CCW_MAX 64 #define BOOTPARMS_FCP_MAX 3452 enum target_type { TT_CCW, TT_FCP, TT_NSS, TT_NODE, }; enum reipl_type { REIPL_FCP, REIPL_CCW, REIPL_NSS }; static const char *const usage_chreipl = "Usage: %s [TARGET] [ARGS] [OPTIONS]\n" "\n" " chreipl [ccw] [-d] [OPTIONS]\n" " chreipl [fcp] [-d] [-w] [-l] [OPTIONS]\n" " chreipl [node] [OPTIONS]\n" " chreipl nss [-n] [OPTIONS]\n" " chreipl [-h] [-v]\n" "\n" "The following re-IPL targets are supported:\n" " ccw IPL from CCW device\n" " fcp IPL from FCP device\n" " nss IPL from NSS\n" " node IPL from device specified by device node or directory\n" "\n" "General options:\n" " -f, --force Allow targets that cannot be verified by the system\n" " -p, --bootparms Boot parameter specification\n" " -h, --help Print this help, then exit\n" " -v, --version Print version information, then exit\n" "\n" "Options for ccw target:\n" " -d, --device Device number of the CCW IPL device\n" " -L, --loadparm Loadparm specification\n" "\n" "Options for fcp target:\n" " -d, --device Device number of the adapter of the FCP IPL device\n" " -l --lun Logical unit number of the FCP IPL device\n" " -w --wwpn World Wide Port Name of the FCP IPL device\n" " -b, --bootprog Bootprog specification\n" " -L, --loadparm Loadparm specification\n" "\n" "Options for nss target:\n" " -n, --name Identifier of the NSS\n" "\n" "Options for node target:\n" " Depending on underlying target type (ccw or fcp)\n"; static struct locals { char loadparm[9]; /* Entry in the boot menu */ int loadparm_set; char bootprog[11]; /* bootprog number (32 bit)*/ int bootprog_set; char wwpn[20]; /* 18 character +0x" */ int wwpn_set; char lun[20]; /* 18 character +0x" */ int lun_set; char busid[10]; /* Bus ID e.g. 0.0.4711 */ int busid_set; char dev[15]; /* Device (e.g. dasda) */ int dev_set; char nss_name[9]; /* NSS name */ int nss_name_set; char bootparms[4096]; int bootparms_set; int force_set; enum target_type target_type; /* CCW, FCP, NSS or NODE */ int target_type_set; int target_type_auto_mode; enum reipl_type reipl_type; /* CCW, FCP, NSS */ } l; static void __noreturn print_usage_chreipl_exit(void) { printf(usage_chreipl, g.prog_name); exit(0); } static int busid_strtok(char *str, unsigned long max_len, unsigned long *val) { char *token, *end; token = strtok(str, "."); if (!token) return -1; if (strlen(token) > max_len) return -1; *val = strtoul(token, &end, 16); if (*end) return -1; return 0; } static int mk_busid(char busid_out[9], const char *busid_in) { unsigned long devno, cssid = 0, ssid = 0; char busid_tmp[10]; if (strlen(busid_in) > 9) return -1; strcpy(busid_tmp, busid_in); if (strstr(busid_in, ".")) { /* Check xx.x.xxxx full bus-ID format */ if (busid_strtok(busid_tmp, 2, &cssid)) return -1; if (busid_strtok(NULL, 1, &ssid)) return -1; if (busid_strtok(NULL, 4, &devno)) return -1; /* Ensure that there are no more additional fields */ if (strtok(NULL, ".")) return -1; } else { /* Check xxxx short bus-ID format */ if (busid_strtok(busid_tmp, 4, &devno)) return -1; } sprintf(busid_out, "%lx.%lx.%04lx", cssid, ssid, devno); return 0; } static void set_device(const char *busid) { if (mk_busid(l.busid, busid)) ERR_EXIT("Invalid device number \"%s\" specified", busid); l.busid_set = 1; } static void set_nss_name(const char *nss_name) { if (strlen(nss_name) > 8) ERR_EXIT("NSS name \"%s\" exceeds maximum of 8 characters", nss_name); strcpy(l.nss_name, nss_name); l.nss_name_set = 1; } static void set_loadparm(const char *loadparm) { if (strlen(loadparm) > 8) ERR_EXIT("Loadparm \"%s\" exceeds 8 characters", loadparm); strcpy(l.loadparm, loadparm); if (strcmp(l.loadparm, " ") == 0) l.loadparm[0] = '\0'; l.loadparm_set = 1; } static void set_bootprog(const char *bootprog) { long long bootprog_int; char *endptr; bootprog_int = strtoll(bootprog, &endptr, 10); if (*endptr) ERR_EXIT("Bootprog \"%s\" is not a decimal number", bootprog); if (bootprog_int > UINT_MAX) ERR_EXIT("Invalid bootprog specified"); util_strlcpy(l.bootprog, bootprog, sizeof(l.bootprog)); l.bootprog_set = 1; } static void set_bootparms(const char *bootparms) { unsigned int i; for (i = 0; i < strlen(bootparms); i++) { if (isascii(bootparms[i])) continue; ERR_EXIT("Non ASCII characters found in boot parameters"); } if (strlen(bootparms) + 1 > sizeof(l.bootparms)) ERR_EXIT("Boot parameter line is too long"); strcpy(l.bootparms, bootparms); l.bootparms_set = 1; } static void set_lun(const char *lun) { unsigned long long lun_tmp; char *endptr; lun_tmp = strtoull(lun, &endptr, 16); if (*endptr) ERR_EXIT("LUN \"%s\" is not a hexadecimal number", lun); snprintf(l.lun, sizeof(l.lun), "0x%016llx", lun_tmp); l.lun_set = 1; } static void set_wwpn(const char *wwpn) { unsigned long long wwpn_tmp; char *endptr; wwpn_tmp = strtoull(wwpn, &endptr, 16); if (*endptr) ERR_EXIT("WWPN \"%s\" is not a hexadecimal number", wwpn); snprintf(l.wwpn, sizeof(l.wwpn), "0x%016llx", wwpn_tmp); l.wwpn_set = 1; } static void parse_fcp_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl fcp 4711 0x12345... 0x12345... */ if (l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Use either options or positional parameters"); if (nargc > 3) ERR_EXIT("Too many arguments specified for \"fcp\" re-IPL " "type"); else if (nargc != 3) ERR_EXIT("The \"fcp\" re-IPL type requires device, WWPN, " "and LUN"); set_device(nargv[0]); set_wwpn(nargv[1]); set_lun(nargv[2]); } static void parse_ccw_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl ccw 4711 */ if (l.busid_set) ERR_EXIT("Use either options or positional parameters"); if (nargc == 0) ERR_EXIT("The \"ccw\" re-IPL type requires device"); else if (nargc > 1) ERR_EXIT("Too many arguments specified for \"ccw\" re-IPL " "type"); set_device(nargv[0]); } static void parse_nss_args(char *nargv[], int nargc) { /* * we might be called like this: * chreipl nss lnxnss */ if (l.nss_name_set) ERR_EXIT("Use either options or positional parameters"); if (nargc == 0) ERR_EXIT("A NSS name must be specified"); if (nargc > 1) ERR_EXIT("Too many arguments specified for \"nss\" re-IPL " "type"); set_nss_name(nargv[0]); } static void dev_from_part(char *dev_name) { int i; for (i = strlen(dev_name) - 1; isdigit(dev_name[i]); i--) dev_name[i] = 0; } static int set_reipl_type(const char *dev_name) { if (strncmp(dev_name, "dasd", strlen("dasd")) == 0 || strncmp(dev_name, "vd", strlen("vd")) == 0) l.reipl_type = REIPL_CCW; else if (strncmp(dev_name, "sd", strlen("sd")) == 0) l.reipl_type = REIPL_FCP; else return -1; util_strlcpy(l.dev, dev_name, sizeof(l.dev)); dev_from_part(l.dev); l.dev_set = 1; return 0; } static int get_chreipl_helper_cmd(dev_t dev, char cmd[PATH_MAX]) { char *chreipl_helper; struct proc_dev_entry pde; if (proc_dev_get_entry(dev, 1, &pde) != 0) return -1; util_asprintf(&chreipl_helper, "%s/%s.%s", TOOLS_LIBDIR, "chreipl_helper", pde.name); if (access(chreipl_helper, X_OK) != 0) { proc_dev_free_entry(&pde); free(chreipl_helper); return -1; } sprintf(cmd, "%s %d:%d", chreipl_helper, major(dev), minor(dev)); proc_dev_free_entry(&pde); free(chreipl_helper); return 0; } /* * Use chreipl_helper (E.g. for device mapper devices) */ static int set_reipl_type_helper(int maj, int min) { char helper_cmd[PATH_MAX], buf[4096]; struct proc_part_entry ppe; int rc = -1; dev_t dev; FILE *fh; if (get_chreipl_helper_cmd(makedev(maj, min), helper_cmd) != 0) return -1; fh = popen(helper_cmd, "r"); if (fh == NULL) ERR_EXIT_ERRNO("Could not start chreipl_helper"); if (fread(buf, 1, sizeof(buf), fh) == 0) ERR_EXIT_ERRNO("Could not read from chreipl_helper"); if (sscanf(buf, "%d:%d", &maj, &min) != 2) goto fail_pclose; dev = makedev(maj, min); if (proc_part_get_entry(dev, &ppe) != 0) goto fail_pclose; if (set_reipl_type(ppe.name)) goto fail_part_free; rc = 0; fail_part_free: proc_part_free_entry(&ppe); fail_pclose: pclose(fh); return rc; } static void get_dev_by_path(const char *path, dev_t *dev) { struct stat sb; if (stat(path, &sb) != 0) ERR_EXIT_ERRNO("Could not access device node \"%s\"", path); if (S_ISDIR(sb.st_mode)) *dev = sb.st_dev; else if (S_ISBLK(sb.st_mode)) *dev = sb.st_rdev; else ERR_EXIT("Only block device nodes or directories are valid for" " \"node\" target"); } static void parse_node_args(char *nargv[], int nargc) { struct proc_part_entry ppe; char *path = nargv[0]; dev_t dev; if (nargc == 0) ERR_EXIT("No device node specified"); if (l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Do not use device, WWPN, or LUN for \"node\" target"); get_dev_by_path(path, &dev); if (proc_part_get_entry(dev, &ppe) != 0) ERR_EXIT("Invalid device node \"%s\" specified", path); if (set_reipl_type(ppe.name) == 0) goto out; if (set_reipl_type_helper(major(dev), minor(dev)) == 0) goto out; ERR_EXIT("Unsupported device node \"%s\" specified", path); out: proc_part_free_entry(&ppe); } static void parse_pos_args(char *nargv[], int nargc) { switch (l.target_type) { case TT_FCP: parse_fcp_args(nargv, nargc); break; case TT_CCW: parse_ccw_args(nargv, nargc); break; case TT_NSS: parse_nss_args(nargv, nargc); break; case TT_NODE: parse_node_args(nargv, nargc); break; } } static void check_fcp_opts(void) { if (l.nss_name_set) ERR_EXIT("Invalid option for \"fcp\" target specified"); if (!(l.busid_set && l.wwpn_set && l.lun_set)) ERR_EXIT("The \"fcp\" target requires device, WWPN, " "and LUN"); } static void check_ccw_opts(void) { if (l.bootprog_set || l.lun_set || l.wwpn_set || l.nss_name_set) ERR_EXIT("Invalid option for \"ccw\" target specified"); if (!l.busid_set) ERR_EXIT("The \"ccw\" target requires device"); } static void check_nss_opts(void) { if (l.bootprog_set || l.loadparm_set || l.busid_set || l.wwpn_set || l.lun_set) ERR_EXIT("Invalid option for \"nss\" target specified"); if (!l.nss_name_set) ERR_EXIT("The \"nss\" target requires NSS name"); } static void set_target_type(enum target_type tt, int mode_auto) { l.target_type = tt; l.target_type_set = 1; l.target_type_auto_mode = mode_auto; } static void set_target_type_auto(const char *arg) { char busid[10]; if (access(arg, F_OK) == 0) { set_target_type(TT_NODE, 1); return; } if (mk_busid(busid, arg) == 0) { if (ccw_is_device(busid)) set_target_type(TT_CCW, 1); else if (fcp_is_device(busid)) set_target_type(TT_FCP, 1); } } static void parse_chreipl_options(int argc, char *argv[]) { int opt, idx; const struct option long_opts[] = { { "help", no_argument, NULL, 'h'}, { "bootprog", required_argument, NULL, 'b' }, { "device", required_argument, NULL, 'd' }, { "lun", required_argument, NULL, 'l' }, { "wwpn", required_argument, NULL, 'w' }, { "loadparm", required_argument, NULL, 'L' }, { "name", required_argument, NULL, 'n' }, { "bootparms", required_argument, NULL, 'p' }, { "force", no_argument, NULL, 'f' }, { "version", no_argument, NULL, 'v' }, { NULL, 0, NULL, 0 } }; static const char optstr[] = "hcd:vw:l:fL:b:n:p:"; /* dont run without any argument */ if (argc == 1) print_usage_chreipl_exit(); if (strcmp(argv[1], "fcp") == 0) set_target_type(TT_FCP, 0); else if (strcmp(argv[1], "ccw") == 0) set_target_type(TT_CCW, 0); else if (strcmp(argv[1], "nss") == 0) set_target_type(TT_NSS, 0); else if (strcmp(argv[1], "node") == 0) set_target_type(TT_NODE, 0); else set_target_type_auto(argv[1]); while ((opt = getopt_long(argc, argv, optstr, long_opts, &idx)) != -1) { switch (opt) { case 'h': print_usage_chreipl_exit(); case 'd': set_device(optarg); break; case 'l': set_lun(optarg); break; case 'w': set_wwpn(optarg); break; case 'L': set_loadparm(optarg); break; case 'b': set_bootprog(optarg); break; case 'n': set_nss_name(optarg); break; case 'p': set_bootparms(optarg); break; case 'f': l.force_set = 1; break; case 'v': print_version_exit(); default: print_help_hint_exit(); } } if (!is_root()) ERR_EXIT("You must be root to perform this operation"); if (!l.target_type_set) ERR_EXIT("No valid target specified"); /* * optind is a index which points to the first unrecognized * command line argument. In case of no auto action we have to * skip the action argument. */ if (!l.target_type_auto_mode) optind += 1; if (argc - optind > 0) parse_pos_args(&argv[optind], argc - optind); } static void check_exists(const char *path, const char *attr) { char fpath[PATH_MAX]; snprintf(fpath, sizeof(fpath), "/sys/firmware/%s", path); if (access(fpath, F_OK) != 0) ERR_EXIT("System does not allow to set %s", attr); } static void write_str_optional(char *string, char *file, int exit_on_fail, const char *attr) { if (write_str_errno(string, file) && exit_on_fail) ERR_EXIT("System does not allow to set %s", attr); } /* * Check if device is on the cio_ignore blacklist * * IMPLEMENTATION: * * "cio_ignore --is-ignored " returns 0 if the device is ignored, * 1 for internal errrors, and 2 if the device is not ignored. * * We get the "cio_ignore" exit status by the return code of the system() * function via WEXITSTATUS(). * * If no shell is available or the "cio_ignore" tool is not available * we get system() rc != 0 and WEXITSTATUS() = 127. */ static int is_ignored(const char *busid) { const char *fmt = "cio_ignore --is-ignored %s > /dev/null 2>&1"; char cmd[256]; int rc; snprintf(cmd, sizeof(cmd), fmt, busid); rc = system(cmd); if ((rc != -1) && (WEXITSTATUS(rc) == 0)) return 1; return 0; } static void chreipl_ccw(void) { check_ccw_opts(); if (!ccw_is_device(l.busid) && !l.force_set) { if (is_ignored(l.busid)) ERR_EXIT("Device is on cio_ignore list, try \"cio_ignore -r %s\"?", l.busid); ERR_EXIT("Could not find DASD CCW device \"%s\"", l.busid); } if (l.bootparms_set && strlen(l.bootparms) > BOOTPARMS_CCW_MAX) { ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", strlen(l.bootparms), BOOTPARMS_CCW_MAX); } /* * On old systems that use CCW reipl loadparm cannot be set */ write_str_optional(l.loadparm, "reipl/ccw/loadparm", l.loadparm_set, "loadparm"); write_str_optional(l.bootparms, "reipl/ccw/parm", l.bootparms_set, "boot parameters"); write_str(l.busid, "reipl/ccw/device"); write_str("ccw", "reipl/reipl_type"); print_ccw(0); } static void chreipl_fcp(void) { check_fcp_opts(); if (!fcp_is_device(l.busid) && !l.force_set) { if (is_ignored(l.busid)) ERR_EXIT("Device is on cio_ignore list, try \"cio_ignore -r %s\"?", l.busid); ERR_EXIT("Could not find FCP device \"%s\"", l.busid); } check_exists("reipl/fcp/device", "\"fcp\" re-IPL target"); if (l.bootparms_set && strlen(l.bootparms) > BOOTPARMS_FCP_MAX) { ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", strlen(l.bootparms), BOOTPARMS_FCP_MAX); } /* * On old systems the FCP reipl loadparm cannot be set */ write_str_optional(l.loadparm, "reipl/fcp/loadparm", l.loadparm_set, "loadparm"); write_str_optional(l.bootparms, "reipl/fcp/scp_data", l.bootparms_set, "boot parameters"); write_str(l.busid, "reipl/fcp/device"); write_str(l.wwpn, "reipl/fcp/wwpn"); write_str(l.lun, "reipl/fcp/lun"); /* * set the boot record logical block address. Master boot * record. It is always 0 for Linux */ write_str("0", "reipl/fcp/br_lba"); if (!l.bootprog_set) sprintf(l.bootprog, "0"); write_str(l.bootprog, "reipl/fcp/bootprog"); write_str("fcp", "reipl/reipl_type"); print_fcp(0, 0); } static void chreipl_nss(void) { check_nss_opts(); check_exists("reipl/nss/name", "\"nss\" re-IPL target"); if (l.bootparms_set && strlen(l.bootparms) > BOOTPARMS_NSS_MAX) { ERR_EXIT("Maximum boot parameter length exceeded (%zu/%u)", strlen(l.bootparms), BOOTPARMS_NSS_MAX); } write_str_optional(l.bootparms, "reipl/nss/parm", l.bootparms_set, "boot parameters"); write_str(l.nss_name, "reipl/nss/name"); write_str("nss", "reipl/reipl_type"); print_nss(0); } static void chreipl_node(void) { char path[PATH_MAX]; if (!l.dev_set) ERR_EXIT("No device node specified"); snprintf(path, sizeof(path), "/sys/block/%s/device", l.dev); if (chdir(path) != 0) ERR_EXIT("Could not find device \"%s\"", l.dev); switch (l.reipl_type) { case REIPL_CCW: ccw_busid_get(l.dev, l.busid); l.busid_set = 1; chreipl_ccw(); break; case REIPL_FCP: fcp_wwpn_get(l.dev, l.wwpn); l.wwpn_set = 1; fcp_lun_get(l.dev, l.lun); l.lun_set = 1; fcp_busid_get(l.dev, l.busid); l.busid_set = 1; chreipl_fcp(); break; default: ERR_EXIT("Internal error: chreipl_node"); } } void cmd_chreipl(int argc, char *argv[]) { parse_chreipl_options(argc, argv); switch (l.target_type) { case TT_CCW: chreipl_ccw(); break; case TT_FCP: chreipl_fcp(); break; case TT_NSS: chreipl_nss(); break; case TT_NODE: chreipl_node(); break; } }