/* * zdev - Modify and display the persistent configuration of devices * * chzdev: Configure z Systems specific devices * * Copyright IBM Corp. 2016, 2019 * * 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 "lib/util_path.h" #include "lib/zt_common.h" #include "attrib.h" #include "blkinfo.h" #include "ccw.h" #include "ctc.h" #include "device.h" #include "devnode.h" #include "devtype.h" #include "export.h" #include "firmware.h" #include "inuse.h" #include "misc.h" #include "module.h" #include "namespace.h" #include "opts.h" #include "path.h" #include "root.h" #include "scsi.h" #include "select.h" #include "setting.h" #include "subtype.h" #include "table_attribs.h" #include "table_types.h" #include "udev.h" #include "zfcp_lun.h" /* Main program action. */ typedef enum { ACT_CONFIGURE, ACT_DECONFIGURE, ACT_LIST_ATTRIBS, ACT_LIST_TYPES, ACT_HELP_ATTRIBS, ACT_EXPORT, ACT_IMPORT, ACT_APPLY, ACT_HELP, ACT_VERSION, } action_t; /* Representation of command line options. */ struct options { /* Unparsed positional parameters. */ struct util_list *positional; /* List of struct strlist_node */ /* Selection. */ struct select_opts *select; unsigned int type:1; /* Settings */ struct util_list *settings; /* List of struct strlist_node */ /* Actions */ unsigned int enable:1; unsigned int deconfigure:1; unsigned int list_attribs:1; unsigned int list_types:1; unsigned int help_attribs:1; char *export; char *import; unsigned int apply:1; unsigned int help:1; unsigned int version:1; /* Options */ config_t config; unsigned int active:1; unsigned int persistent:1; unsigned int auto_conf:1; struct util_list *remove; /* List of struct strlist_node */ unsigned int remove_all:1; unsigned int force:1; unsigned int yes:1; unsigned int no_root_check:1; unsigned int dryrun:1; struct util_list *base; /* List of struct strlist_node */ unsigned int verbose:1; unsigned int quiet:1; unsigned int no_settle:1; }; /* Makefile converts chzdev_usage.txt into C file which we include here. */ static const char *usage_text = #include "chzdev_usage.c" ; #define OPT_ANONYMOUS_BASE 0x80 enum { OPT_ALL = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_CONFIGURED = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_EXISTING = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_ONLINE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_OFFLINE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_FAILED = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_PATH = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_NODE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_INTERFACE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BY_ATTRIB = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_TYPE = 't', OPT_ENABLE = 'e', OPT_DECONFIGURE = 'd', OPT_LIST_ATTRIBS = 'l', OPT_HELP_ATTRIBS = 'H', OPT_LIST_TYPES = 'L', OPT_EXPORT = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_IMPORT = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_APPLY = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_ACTIVE = 'a', OPT_PERSISTENT = 'p', OPT_REMOVE = 'r', OPT_REMOVE_ALL = 'R', OPT_FORCE = 'f', OPT_YES = 'y', OPT_NO_ROOT_UPDATE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_DRY_RUN = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_BASE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_HELP = 'h', OPT_VERSION = 'v', OPT_VERBOSE = 'V', OPT_QUIET = 'q', OPT_NO_SETTLE = (OPT_ANONYMOUS_BASE+__COUNTER__), OPT_AUTO_CONF = (OPT_ANONYMOUS_BASE+__COUNTER__), }; static struct opts_conflict conflict_list[] = { OPTS_CONFLICT(OPT_ENABLE, OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_LIST_TYPES, OPT_EXPORT, OPT_IMPORT, OPT_APPLY, OPT_TYPE, 0), OPTS_CONFLICT(OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_HELP_ATTRIBS, OPT_LIST_TYPES, OPT_EXPORT, OPT_IMPORT, OPT_APPLY, OPT_REMOVE, OPT_REMOVE_ALL, OPT_TYPE, 0), OPTS_CONFLICT(OPT_LIST_ATTRIBS, OPT_DECONFIGURE, OPT_HELP_ATTRIBS, OPT_LIST_TYPES, OPT_EXPORT, OPT_IMPORT, OPT_APPLY, OPT_REMOVE, OPT_REMOVE_ALL, OPT_CONFIGURED, OPT_EXISTING, OPT_ONLINE, OPT_OFFLINE, OPT_BY_PATH, OPT_BY_NODE, OPT_BY_INTERFACE, OPT_BY_ATTRIB, OPT_ACTIVE, OPT_PERSISTENT, OPT_FAILED, 0), OPTS_CONFLICT(OPT_LIST_TYPES, OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_HELP_ATTRIBS, OPT_EXPORT, OPT_IMPORT, OPT_APPLY, OPT_REMOVE, OPT_REMOVE_ALL, OPT_CONFIGURED, OPT_EXISTING, OPT_ONLINE, OPT_OFFLINE, OPT_BY_PATH, OPT_BY_NODE, OPT_BY_INTERFACE, OPT_BY_ATTRIB, OPT_ACTIVE, OPT_PERSISTENT, OPT_FAILED, 0), OPTS_CONFLICT(OPT_EXPORT, OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_HELP_ATTRIBS, OPT_LIST_TYPES, OPT_IMPORT, OPT_APPLY, OPT_REMOVE, OPT_REMOVE_ALL, 0), OPTS_CONFLICT(OPT_IMPORT, OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_HELP_ATTRIBS, OPT_LIST_TYPES, OPT_EXPORT, OPT_APPLY, OPT_REMOVE, OPT_REMOVE_ALL, OPT_CONFIGURED, OPT_EXISTING, OPT_BY_PATH, OPT_BY_ATTRIB, OPT_BY_NODE, OPT_BY_INTERFACE, 0), OPTS_CONFLICT(OPT_APPLY, OPT_DECONFIGURE, OPT_LIST_ATTRIBS, OPT_HELP_ATTRIBS, OPT_LIST_TYPES, OPT_EXPORT, OPT_IMPORT, OPT_REMOVE, OPT_REMOVE_ALL, OPT_ACTIVE, 0), OPTS_CONFLICT(OPT_ONLINE, OPT_OFFLINE), OPTS_CONFLICT(OPT_QUIET, OPT_VERBOSE), OPTS_CONFLICT(OPT_AUTO_CONF, OPT_TYPE, OPT_LIST_ATTRIBS, OPT_LIST_TYPES), OPTS_CONFLICT(0, 0), }; /* Command line options. */ static const struct option opt_list[] = { /* Selection. */ { "all", no_argument, NULL, OPT_ALL }, { "configured", no_argument, NULL, OPT_CONFIGURED }, { "existing", no_argument, NULL, OPT_EXISTING }, { "online", no_argument, NULL, OPT_ONLINE }, { "offline", no_argument, NULL, OPT_OFFLINE }, { "failed", no_argument, NULL, OPT_FAILED }, { "by-path", required_argument, NULL, OPT_BY_PATH }, { "by-node", required_argument, NULL, OPT_BY_NODE }, { "by-interface", required_argument, NULL, OPT_BY_INTERFACE }, { "by-attrib", required_argument, NULL, OPT_BY_ATTRIB }, { "type", no_argument, NULL, OPT_TYPE }, /* Actions. */ { "enable", no_argument, NULL, OPT_ENABLE }, { "disable", no_argument, NULL, OPT_DECONFIGURE }, { "list-attributes", no_argument, NULL, OPT_LIST_ATTRIBS }, { "help-attribute", no_argument, NULL, OPT_HELP_ATTRIBS }, { "list-types", no_argument, NULL, OPT_LIST_TYPES }, { "export", required_argument, NULL, OPT_EXPORT }, { "import", required_argument, NULL, OPT_IMPORT }, { "apply", no_argument, NULL, OPT_APPLY }, { "help", no_argument, NULL, OPT_HELP }, { "version", no_argument, NULL, OPT_VERSION }, /* Options. */ { "active", no_argument, NULL, OPT_ACTIVE }, { "persistent", no_argument, NULL, OPT_PERSISTENT }, { "auto-conf", no_argument, NULL, OPT_AUTO_CONF }, { "remove", required_argument, NULL, OPT_REMOVE }, { "remove-all", no_argument, NULL, OPT_REMOVE_ALL }, { "force", no_argument, NULL, OPT_FORCE }, { "yes", no_argument, NULL, OPT_YES }, { "no-root-update", no_argument, NULL, OPT_NO_ROOT_UPDATE }, { "dry-run", no_argument, NULL, OPT_DRY_RUN }, { "base", required_argument, NULL, OPT_BASE }, { "verbose", no_argument, NULL, OPT_VERBOSE }, { "quiet", no_argument, NULL, OPT_QUIET }, { "no-settle", no_argument, NULL, OPT_NO_SETTLE }, { NULL, no_argument, NULL, 0 }, }; /* Command line abbreviations. */ static const char opt_str[] = ":edlHLapr:RfyhvVqt"; /* Count of persistently modified devices. */ static int pers_mod_devs; /* Count of persistently modified device types. */ static int pers_mod_devtypes; /* Initialize options data structure. */ static void init_options(struct options *opts) { memset(opts, 0, sizeof(struct options)); opts->select = select_opts_new(); opts->positional = strlist_new(); opts->settings = strlist_new(); opts->remove = strlist_new(); opts->base = strlist_new(); } /* Release memory used in options data structure. */ static void free_options(struct options *opts) { if (!opts) return; free(opts->export); free(opts->import); select_opts_free(opts->select); strlist_free(opts->positional); strlist_free(opts->settings); strlist_free(opts->remove); strlist_free(opts->base); } /* Print usage information. */ static void print_usage(void) { printf("%s", usage_text); } /* Print version information. */ static void print_version(void) { printf("%s version %s\n", toolname, RELEASE_STRING); } /* Determine main program action from parse command line options. */ static action_t get_action(struct options *opts) { if (opts->help) return ACT_HELP; if (opts->version) return ACT_VERSION; if (opts->enable) return ACT_CONFIGURE; if (opts->deconfigure) return ACT_DECONFIGURE; if (opts->list_attribs) return ACT_LIST_ATTRIBS; if (opts->help_attribs) return ACT_HELP_ATTRIBS; if (opts->list_types) return ACT_LIST_TYPES; if (opts->export) return ACT_EXPORT; if (opts->import) return ACT_IMPORT; if (opts->apply) return ACT_APPLY; return ACT_CONFIGURE; } /* Return option corresponding to action. */ static const char *get_action_option(action_t action) { switch (action) { case ACT_DECONFIGURE: return "--disable"; case ACT_LIST_ATTRIBS: return "--list-attributes"; case ACT_HELP_ATTRIBS: return "--help-attributes"; case ACT_LIST_TYPES: return "--list-types"; case ACT_EXPORT: return "--export"; case ACT_IMPORT: return "--import"; case ACT_APPLY: return "--apply"; case ACT_HELP: return "--help"; case ACT_VERSION: return "--version"; default: return ""; } } /* Check options data structure for syntax errors. */ static exit_code_t check_options(struct options *opts, int specified[OPTS_MAX + 1], int op) { if (opts_check_conflict(op, specified, conflict_list, opt_list)) return EXIT_USAGE_ERROR; return EXIT_OK; } /* Determine if STR is a valid device specification. */ static bool is_devspec(struct devtype *dt, struct subtype *st, char *str) { char *copy, *curr, *next; bool rc; /* ID,ID-ID,... */ copy = misc_strdup(str); next = copy; /* Separate by comma. */ rc = true; if (st) { /* Is this a valid ID or range for this subtype? */ while ((curr = strsep(&next, ","))) { if (!ns_is_id_valid(st->namespace, curr) && !ns_is_id_range_valid(st->namespace, curr)) { rc = false; break; } } } else if (dt) { /* Is this a valid ID or range for any subtype of this * devtype? */ while ((curr = strsep(&next, ","))) { if (!devtype_is_id_valid(dt, curr) && !devtype_is_id_range_valid(dt, curr)) { rc = false; break; } } } else { /* Is this a valid ID or range for any subtype? */ while ((curr = strsep(&next, ","))) { if (!namespaces_is_id_valid(curr) && !namespaces_is_id_range_valid(curr)) { rc = false; break; } } } free(copy); return rc; } static exit_code_t check_devid(struct devtype *only_dt, struct subtype *only_st, const char *id) { int i, j; struct devtype *dt; struct subtype *st; struct namespace *ns; for (i = 0; (dt = devtypes[i]); i++) { if (only_dt && dt != only_dt) continue; for (j = 0; (st = dt->subtypes[j]); j++) { if (only_st && st != only_st) continue; if (ns_is_id_valid(st->namespace, id)) return EXIT_OK; } } /* ID not valid - generate corresponding error message and exit code. */ ns = NULL; if (only_st) ns = only_st->namespace; else if (only_dt && devtype_count_namespaces(only_dt) == 1) ns = only_dt->subtypes[0]->namespace; else ns = devtype_most_similar_namespace(only_dt, NULL, id); if (ns) return ns->is_id_valid(id, err_print); if (only_dt) { error("Unrecognized %s ID format: %s\n", only_dt->devname, id); return EXIT_INVALID_ID; } syntax("Unknown device type or device ID format: %s\n", id); return EXIT_USAGE_ERROR; } static exit_code_t check_range(struct devtype *only_dt, struct subtype *only_st, const char *range) { int i, j; struct devtype *dt; struct subtype *st; struct namespace *ns; for (i = 0; (dt = devtypes[i]); i++) { if (only_dt && dt != only_dt) continue; for (j = 0; (st = dt->subtypes[j]); j++) { if (only_st && st != only_st) continue; if (ns_is_id_range_valid(st->namespace, range)) return EXIT_OK; } } /* Range not valid - generate corresponding error message and exit * code. */ ns = NULL; if (only_st) ns = only_st->namespace; else if (only_dt && devtype_count_namespaces(only_dt) == 1) ns = only_dt->subtypes[0]->namespace; if (ns) return ns->is_id_range_valid(range, err_print); if (only_dt) { error("Unrecognized %s ID range format: %s\n", only_dt->devname, range); return EXIT_INVALID_ID; } syntax("Unknown device type or device ID range format: %s\n", range); return EXIT_USAGE_ERROR; } /* Get corresponding exit code and error message for invalid devspec. */ static exit_code_t check_devspec(struct devtype *dt, struct subtype *st, const char *devspec) { char *copy, *curr, *next; exit_code_t rc = EXIT_OK; /* ID,ID-ID,... */ copy = misc_strdup(devspec); next = copy; /* Separate by comma. */ while ((curr = strsep(&next, ","))) { if (strchr(curr, '-')) rc = check_range(dt, st, curr); else rc = check_devid(dt, st, curr); if (rc) break; } free(copy); return rc; } static exit_code_t check_devspecs(struct options *opts) { struct strlist_node *str; exit_code_t rc = EXIT_OK; util_list_iterate(&opts->select->devids, str) { rc = check_devspec(opts->select->devtype, opts->select->subtype, str->str); if (rc) break; } return rc; } /* Parse positional parameters. */ static exit_code_t parse_positional(struct options *opts, int argc, char *argv[], int start) { action_t action = get_action(opts); struct subtype *st; struct devtype *dt; int i; bool got_devspec; /* Collect parameters. */ for (i = start; i < argc; i++) { dt = devtype_find(argv[i]); st = subtype_find(argv[i]); switch (action) { case ACT_CONFIGURE: if (opts->type) { /* DEVTYPE: Required * DEVICE: Invalid * SETTING: Required */ if (st && !opts->select->devtype) { opts->select->subtype = st; opts->select->devtype = st->devtype; } else if (dt && !opts->select->devtype) { opts->select->devtype = dt; } else if (strchr(argv[i], '=')) { strlist_add(opts->settings, "%s", argv[i]); } else goto err_extra; } else { /* DEVTYPE: Optional * DEVICE: Optional * SETTING: Optional */ if (st && !opts->select->devtype) { opts->select->subtype = st; opts->select->devtype = st->devtype; } else if (dt && !opts->select->devtype) opts->select->devtype = dt; else if (strchr(argv[i], '=')) { strlist_add(opts->settings, "%s", argv[i]); } else if (is_devspec(opts->select->devtype, opts->select->subtype, argv[i])) { strlist_add_multi(&opts->select->devids, argv[i], ",", 0); } else goto err_inv_dev; } break; case ACT_DECONFIGURE: case ACT_EXPORT: case ACT_IMPORT: case ACT_APPLY: /* DEVTYPE: Optional * DEVICE: Required/optional */ if (st && !opts->select->devtype) { opts->select->subtype = st; opts->select->devtype = st->devtype; } else if (dt && !opts->select->devtype) opts->select->devtype = dt; else if (strchr(argv[i], '=')) goto err_inv_setting; else if (is_devspec(opts->select->devtype, opts->select->subtype, argv[i])) { strlist_add_multi(&opts->select->devids, argv[i], ",", 0); } else goto err_inv_dev; break; case ACT_LIST_ATTRIBS: case ACT_HELP_ATTRIBS: /* DEVTYPE: Mandatory * OTHER: Optional */ if (st && !opts->select->devtype) { opts->select->subtype = st; opts->select->devtype = st->devtype; } else if (dt && !opts->select->devtype) opts->select->devtype = dt; else if (!st && !opts->select->devtype) goto err_unknown_devtype; else strlist_add(opts->positional, "%s", argv[i]); break; case ACT_LIST_TYPES: /* DEVTYPE: Optional */ if (st || dt) { if (opts->select->devtype) goto err_extra; if (dt) opts->select->devtype = dt; else { opts->select->subtype = st; opts->select->devtype = st->devtype; } } else { if (!opts->select->devtype) goto err_unknown_devtype; else goto err_extra; } break; default: /* No positional parameters expected. */ goto err_extra; }; } got_devspec = select_opts_dev_specified(opts->select); /* Check for required parameters. */ switch (action) { case ACT_CONFIGURE: if (opts->type) { /* DEVTYPE: Required * DEVICE: Invalid * SETTING: Required */ if (got_devspec) goto err_inv_type_dev; if (!opts->select->devtype) goto err_no_devtype; if (util_list_is_empty(opts->settings) && util_list_is_empty(opts->remove) && !opts->remove_all) goto err_no_devtype_setting; } else { if (!opts->enable && util_list_is_empty(opts->settings) && util_list_is_empty(opts->remove) && !opts->remove_all) goto err_no_enable; if (!got_devspec) { if (opts->enable) goto err_no_dev_conf; if (!opts->select->devtype) goto err_no_dev_or_type; if (util_list_is_empty(opts->settings) && util_list_is_empty(opts->remove) && !opts->remove_all) goto err_no_devtype_or_setting; goto err_no_dev_or_type_opt; } goto check_devspec; } break; case ACT_DECONFIGURE: if (!got_devspec) goto err_no_dev_deconf; goto check_devspec; case ACT_LIST_ATTRIBS: case ACT_HELP_ATTRIBS: if (!opts->select->devtype) goto err_no_devtype; break; case ACT_EXPORT: if (!got_devspec && !opts->type) goto err_no_dev_or_type_opt; goto check_devspec; case ACT_APPLY: if (!got_devspec && !opts->type) goto err_no_dev_or_type_opt; goto check_devspec; default: /* All ok. */ break; }; return EXIT_OK; check_devspec: return check_devspecs(opts); err_unknown_devtype: error("Unknown device type: %s\n", argv[i]); return EXIT_UNKNOWN_DEVTYPE; err_extra: syntax("Extra parameter found: %s\n", argv[i]); return EXIT_USAGE_ERROR; err_inv_dev: return check_devspec(opts->select->devtype, opts->select->subtype, argv[i]); err_inv_setting: syntax("Cannot specify '%s' together with setting '%s'\n", get_action_option(action), argv[i]); return EXIT_USAGE_ERROR; err_no_dev_or_type: syntax("Please specify a device or device type to configure\n"); return EXIT_USAGE_ERROR; err_no_dev_conf: syntax("Please specify a device to configure\n"); return EXIT_USAGE_ERROR; err_no_dev_deconf: syntax("Please specify a device to deconfigure\n"); return EXIT_USAGE_ERROR; err_no_devtype: error("Please specify a device type\n" "Use '%s --list-types' to get a list of device types\n", toolname); return EXIT_USAGE_ERROR; err_no_devtype_setting: if (opts->select->devtype->type_attribs[0]) { error("Please specify a device type setting to configure\n" "Use '%s %s --type --list-attributes' to get a list of " "attributes\n", toolname, opts->select->devtype->name); return EXIT_USAGE_ERROR; } error("Device type %s does not provide type attributes\n", opts->select->devtype->name); return EXIT_ATTRIB_NOT_FOUND; err_no_devtype_or_setting: syntax("Please specify a device or setting to configure\n"); return EXIT_USAGE_ERROR; err_inv_type_dev: error("Cannot specify '--type' and select a device when configuring\n"); return EXIT_USAGE_ERROR; err_no_dev_or_type_opt: syntax("Please specify a device or --type to select device type\n"); return EXIT_USAGE_ERROR; err_no_enable: syntax("Please specify an action\n"); return EXIT_USAGE_ERROR; } /* Parse command line options. Filter out invalid combinations. */ static exit_code_t parse_options(struct options *opts, int argc, char *argv[]) { exit_code_t rc; int opt; int specified[OPTS_MAX + 1]; /* Suppress getopt error messages. */ memset(specified, 0, sizeof(specified)); rc = EXIT_OK; opterr = 0; while ((opt = getopt_long(argc, argv, opt_str, opt_list, NULL)) != -1) { switch (opt) { case OPT_ALL: /* --all */ opts->select->all = 1; break; case OPT_CONFIGURED: /* --configured */ opts->select->configured = 1; break; case OPT_EXISTING: /* --existing */ opts->select->existing = 1; break; case OPT_ONLINE: /* --online */ opts->select->online = 1; break; case OPT_OFFLINE: /* --offline */ opts->select->offline = 1; break; case OPT_FAILED: /* --failed */ opts->select->failed = 1; break; case OPT_BY_PATH: /* --by-path MOUNTPOINT */ strlist_add(&opts->select->by_path, "%s", optarg); break; case OPT_BY_NODE: /* --by-node NODE */ strlist_add(&opts->select->by_node, "%s", optarg); break; case OPT_BY_INTERFACE: /* --by-interface NAME */ strlist_add(&opts->select->by_if, "%s", optarg); break; case OPT_BY_ATTRIB: /* --by-attrib NAME */ if (!strchr(optarg, '=')) { syntax("--by-attrib requires argument in " "ATTRIB=VALUE or ATTRIB!=VALUE " "format\n"); return EXIT_USAGE_ERROR; } strlist_add(&opts->select->by_attr, "%s", optarg); break; case OPT_TYPE: /* --type */ opts->type = 1; break; case OPT_ENABLE: /* --enable */ opts->enable = 1; break; case OPT_DECONFIGURE: /* --disable */ opts->deconfigure = 1; break; case OPT_LIST_ATTRIBS: /* --list-attributes */ opts->list_attribs = 1; break; case OPT_LIST_TYPES: /* --list-types */ opts->list_types = 1; break; case OPT_HELP_ATTRIBS: /* --help-attributes */ opts->help_attribs = 1; break; case OPT_EXPORT: /* --export */ if (opts->export) { error("Cannot specify '--export' multiple " "times\n"); return EXIT_USAGE_ERROR; } opts->export = misc_strdup(optarg); break; case OPT_IMPORT: /* --import */ if (opts->import) { error("Cannot specify '--import' multiple " "times\n"); return EXIT_USAGE_ERROR; } opts->import = misc_strdup(optarg); break; case OPT_APPLY: /* --apply */ opts->apply = 1; break; case OPT_ACTIVE: /* --active */ opts->active = 1; break; case OPT_PERSISTENT: /* --persistent */ opts->persistent = 1; break; case OPT_AUTO_CONF: /* --auto-conf */ opts->auto_conf = 1; break; case OPT_REMOVE: /* --remove ATTRIB */ strlist_add(opts->remove, "%s", optarg); break; case OPT_REMOVE_ALL: /* --remove-all */ opts->remove_all = 1; break; case OPT_FORCE: /* --force */ opts->force = 1; break; case OPT_YES: /* --yes */ opts->yes = 1; break; case OPT_NO_ROOT_UPDATE: /* --no-root-update */ opts->no_root_check = 1; break; case OPT_DRY_RUN: /* --dry-run */ opts->dryrun = 1; break; case OPT_BASE: /* --base PATH */ strlist_add(opts->base, "%s", optarg); break; case OPT_HELP: /* --help */ opts->help = 1; /* --help has precedence - exit early */ return EXIT_OK; case OPT_VERSION: /* --version */ opts->version = 1; /* --version has precedence - exit early */ return EXIT_OK; case OPT_VERBOSE: /* --verbose */ opts->verbose = 1; break; case OPT_QUIET: /* --quiet */ opts->quiet = 1; break; case OPT_NO_SETTLE: /* --no-settle */ opts->no_settle = 1; break; case ':': /* Missing option argument. */ syntax("Option '%s' requires an argument\n", argv[optind - 1]); return EXIT_USAGE_ERROR; case '?': /* Unknown option character. */ if (optopt) syntax("Unrecognized option '-%c'\n", optopt); else { syntax("Unrecognized option '%s'\n", argv[optind - 1]); } return EXIT_USAGE_ERROR; default: break; } if (opt >= 0 && opt <= OPTS_MAX) specified[opt] = 1; /* Check after each option to report errors in order of * specification. */ rc = check_options(opts, specified, opt); if (rc) break; } if (rc) goto out; /* Determine configuration set. */ if (!opts->active && !opts->persistent && !opts->auto_conf) { /* Default configuration targets are active + persistent */ opts->config = config_active | config_persistent; } else { opts->config = get_config(opts->active, opts->persistent, opts->auto_conf); } /* Handle positional parameters. */ rc = parse_positional(opts, argc, argv, optind); /* Set implicit settings. */ if (!select_opts_dev_specified(opts->select)) { switch (get_action(opts)) { case ACT_IMPORT: /* --import without --type or device spec * selects both type and all devices. */ if (!opts->type) { opts->select->all = 1; opts->type = 1; } break; default: break; } } out: return rc; } static bool setting_is_removable(struct setting *s, int active) { if (active) { if (!s->attrib || !s->attrib->activerem) return false; } if (s->derived) return false; if (s->attrib && s->attrib->mandatory) return false; return true; } static int count_removable(struct setting_list *list, int active) { struct setting *s; int removable = 0; util_list_iterate(&list->list, s) { if (setting_is_removable(s, active)) removable++; } return removable; } /* Remove all settings. */ static void remove_all_settings(struct setting_list *settings, int active) { struct setting *s; int modified = 0; util_list_iterate(&settings->list, s) { if (!setting_is_removable(s, active)) continue; s->removed = 1; s->modified = 1; modified = 1; } if (modified) settings->modified = 1; } /* Perform --remove-all operation for specified config on device. */ static exit_code_t device_remove_all(struct device *dev, config_t config) { int active, persistent, autoconf; active = SCOPE_ACTIVE(config) ? count_removable(dev->active.settings, 1) : 0; persistent = SCOPE_PERSISTENT(config) ? count_removable(dev->persistent.settings, 0) : 0; autoconf = SCOPE_AUTOCONF(config) ? count_removable(dev->autoconf.settings, 0) : 0; if (active == 0 && persistent == 0 && autoconf == 0) { delayed_err("No removable settings found\n"); return EXIT_SETTING_NOT_FOUND; } if (active) remove_all_settings(dev->active.settings, 1); if (persistent) remove_all_settings(dev->persistent.settings, 0); if (autoconf) remove_all_settings(dev->autoconf.settings, 0); return EXIT_OK; } /* Remove settings specified by NAMES from a list of SETTINGS. */ static exit_code_t remove_settings(struct setting_list *settings, struct util_list *names, struct util_list *found, struct util_list *notfound, int check_activerem) { struct strlist_node *s; struct setting *set; util_list_iterate(names, s) { set = setting_list_find(settings, s->str); if (set && !set->derived) { if (set->attrib && set->attrib->mandatory) goto err_mandatory; if (check_activerem && set->attrib && !set->attrib->activerem) goto err_activerem; strlist_add_unique(found, "%s", s->str); set->removed = 1; set->modified = 1; continue; } strlist_add_unique(notfound, "%s", s->str); } return EXIT_OK; err_mandatory: delayed_err("Cannot remove setting for mandatory attribute '%s'\n", s->str); return EXIT_USAGE_ERROR; err_activerem: delayed_err("Cannot remove setting '%s' from the active " "configuration\n", s->str); return EXIT_USAGE_ERROR; } /* Perform --remove operation on specified device. */ static exit_code_t device_remove_settings(struct device *dev, struct util_list *names, config_t config) { struct util_list *found, *notfound; char *flat; exit_code_t rc = EXIT_OK; found = strlist_new(); notfound = strlist_new(); if (SCOPE_ACTIVE(config)) { rc = remove_settings(dev->active.settings, names, found, notfound, 1); if (rc) goto out; } if (SCOPE_PERSISTENT(config)) { rc = remove_settings(dev->persistent.settings, names, found, notfound, 0); if (rc) goto out; } if (SCOPE_AUTOCONF(config)) { rc = remove_settings(dev->autoconf.settings, names, found, notfound, 0); if (rc) goto out; } if (!util_list_is_empty(notfound)) { flat = strlist_flatten(notfound, " "); delayed_err("Setting not found: %s\n", flat); free(flat); rc = EXIT_SETTING_NOT_FOUND; } out: strlist_free(found); strlist_free(notfound); return rc; } static void ensure_online(struct device *dev, config_t config) { struct subtype *st = dev->subtype; if (subtype_online_specified(st, dev, config)) return; if (subtype_online_get(st, dev, config) == 1) return; subtype_online_set(st, dev, 1, config); } static void print_devs(struct util_list *list, devnode_t type) { struct ptrlist_node *p; struct devnode *d; int first; first = 1; util_list_iterate(list, p) { d = p->ptr; if (d->type != type) continue; if (first) { switch (type) { case BLOCKDEV: info(" Block devices: "); break; case CHARDEV: info(" Character devices: "); break; case NETDEV: info(" Network interface: "); break; default: break; } first = 0; } else info(" "); info("%s%s", type != NETDEV ? "/dev/" : "", d->name); } if (!first) info("\n"); } static void print_dev_config_info(struct device *dev, config_t config) { struct subtype *st = dev->subtype; char *changes; struct util_list *devnodes; changes = setting_get_changes( SCOPE_ACTIVE(config) ? dev->active.settings : NULL, SCOPE_PERSISTENT(config) ? dev->persistent.settings : NULL, SCOPE_AUTOCONF(config) ? dev->autoconf.settings : NULL); if (changes) info(" Changes: %s\n", changes); free(changes); /* Wait for potential renaming udev rules to finish. */ udev_settle(); devnodes = subtype_get_devnodes(st, dev->id); if (devnodes) { print_devs(devnodes, BLOCKDEV); print_devs(devnodes, CHARDEV); print_devs(devnodes, NETDEV); ptrlist_free(devnodes, 1); } } static exit_code_t cfg_read(struct subtype *st, const char *id, config_t config, read_scope_t scope, int reread, struct device **dev_ptr) { exit_code_t rc; struct device *dev; struct namespace *ns = st->namespace; /* Handle blacklist. */ if (ns->is_id_blacklisted && ns->unblacklist_id && ns->is_id_blacklisted(id)) ns->unblacklist_id(id); /* Read configuration. */ if (reread) rc = subtype_reread_device(st, id, config, scope, &dev); else rc = subtype_read_device(st, id, config, scope, &dev); if (rc == EXIT_OK && dev_ptr) *dev_ptr = dev; return rc; } static exit_code_t cfg_mod_existence(struct device *dev, config_t config) { /* Create active config if necessary. */ if (SCOPE_ACTIVE(config) && !dev->active.exists) { if (!dev->active.definable) return EXIT_DEVICE_NOT_FOUND; dev->active.exists = 1; dev->active.modified = 1; } /* Create persistent config if necessary. */ if (SCOPE_PERSISTENT(config) && !dev->persistent.exists) { dev->persistent.exists = 1; dev->persistent.modified = 1; } /* Create autoconf config if necessary. */ if (SCOPE_AUTOCONF(config) && !dev->autoconf.exists) { dev->autoconf.exists = 1; dev->autoconf.modified = 1; } return EXIT_OK; } static exit_code_t cfg_mod_settings(struct device *dev, struct options *opts, int prereq) { exit_code_t rc; if (prereq) goto online; /* Remove all settings. */ if (opts->remove_all) { rc = device_remove_all(dev, opts->config); if (rc) return rc; } /* Remove a list of settings. */ if (!util_list_is_empty(opts->remove)) { rc = device_remove_settings(dev, opts->remove, opts->config); if (rc) return rc; } /* Apply settings. */ if (!util_list_is_empty(opts->settings)) { rc = device_apply_strlist(dev, opts->config, opts->settings); if (rc) return rc; } if (!opts->enable) goto mand; online: /* Make sure there's an online attribute. */ ensure_online(dev, config_active); ensure_online(dev, config_persistent); ensure_online(dev, config_autoconf); mand: /* Ensure default values for mandatory attributes. */ setting_list_apply_defaults(dev->persistent.settings, dev->subtype->dev_attribs, true); setting_list_apply_defaults(dev->autoconf.settings, dev->subtype->dev_attribs, true); return EXIT_OK; } static exit_code_t handle_nonexistent(struct device *dev) { struct subtype *st = dev->subtype, *other_st; struct namespace *ns = st->namespace; if (namespaces_device_exists(ns, dev->id, config_active, &other_st) && dev->subtype != other_st) { if (force) { delayed_warn("Configuring %s %s as %s\n", other_st->devname, dev->id, st->devname); return EXIT_OK; } delayed_forceable("Trying to configure %s %s as %s\n", other_st->devname, dev->id, st->devname); return EXIT_INVALID_DEVTYPE; } delayed_warn("%s %s does not exist in active configuration\n", st->devname, dev->id); return EXIT_OK; } static exit_code_t cfg_write(struct device *dev, int prereq, config_t config, int check_active) { struct subtype *st = dev->subtype; exit_code_t rc = EXIT_OK; if (!device_needs_writing(dev, config) && !force) goto out; if (check_active && !SCOPE_ACTIVE(config) && (SCOPE_PERSISTENT(config) || SCOPE_AUTOCONF(config)) && (!dev->active.exists && !dev->active.definable)) { rc = handle_nonexistent(dev); if (rc) return rc; } /* Pre-write check. */ rc = subtype_check_pre_configure(st, dev, prereq, config); if (rc && !force) goto out; /* Write configuration. */ if (SCOPE_PERSISTENT(config)) pers_mod_devs++; rc = subtype_write_device(st, dev, config); if (rc) goto out; /* Post-write check. */ rc = subtype_check_post_configure(st, dev, prereq, config); if (rc && !force) goto out; out: return rc; } /* Apply changes in @opts to the device defined by @st and @id. */ static exit_code_t cfg_configure(struct subtype *st, const char *id, struct options *opts, int try, int prereq, struct device **dev_ptr, int *proc_ptr) { config_t config = opts->config; exit_code_t rc; struct device *dev; /* Reset processed flag. */ if (proc_ptr) *proc_ptr = 0; /* Read current device data. Note that we read data from all * configurations to enable QETH autodetection and subtype * detection (e.g. dasd -> dasd_fba).*/ dev = NULL; rc = cfg_read(st, id, config_all, scope_known, 0, &dev); if (dev_ptr) *dev_ptr = dev; if (rc) return rc; /* Skip if already processed. */ if (dev->processed) return EXIT_OK; /* Mark device as processed. */ if (proc_ptr) *proc_ptr = 1; dev->processed = 1; /* Exit here if we're only trying and device cannot be configured. */ if (try && !(dev->active.exists || dev->active.definable || dev->persistent.exists || dev->autoconf.exists)) { return EXIT_DEVICE_NOT_FOUND; } /* Create device if needed by action. */ if (opts->enable || !util_list_is_empty(opts->settings)) { rc = cfg_mod_existence(dev, config); if (rc) return rc; } /* Apply changes to device settings. */ rc = cfg_mod_settings(dev, opts, prereq); if (rc) return rc; /* Write resulting device data. */ return cfg_write(dev, prereq, config, 1); } /* Apply persistent configuration to active configuration. */ static exit_code_t cfg_apply(struct subtype *st, const char *id, int prereq, struct device **dev_ptr, int *proc_ptr, bool autoconf) { exit_code_t rc; struct device *dev; struct device_state *state; /* Reset processed flag. */ if (proc_ptr) *proc_ptr = 0; /* Read current device data. */ dev = NULL; rc = cfg_read(st, id, config_all, scope_known, 1, &dev); if (dev_ptr) *dev_ptr = dev; if (rc) return rc; /* Skip if already processed. */ if (dev->processed) return EXIT_OK; /* Mark device as processed. */ if (proc_ptr) *proc_ptr = 1; dev->processed = 1; state = autoconf ? &dev->autoconf : &dev->persistent; /* Exit here if there is no persistent configuration. */ if (!state->exists) return EXIT_NO_DATA; /* Apply changes to device existence. */ rc = cfg_mod_existence(dev, config_active); if (rc) return rc; /* Copy persistent settings to active configuration. */ rc = device_apply_settings(dev, config_active, &state->settings->list); if (rc) return rc; /* Write resulting device data. */ return cfg_write(dev, prereq, config_active, 1); } /* Apply imported configuration. */ static exit_code_t cfg_import(struct subtype *st, const char *id, config_t config, int prereq, struct device **dev_ptr, int *proc_ptr) { exit_code_t rc; struct setting_list *active = NULL, *persistent = NULL, *autoconf = NULL; struct device *dev; /* Reset processed flag. */ if (proc_ptr) *proc_ptr = 0; /* Get imported data. */ dev = device_list_find(st->devices, id, NULL); if (dev_ptr) *dev_ptr = dev; if (dev) { /* Only import each device once. */ if (dev->processed) return EXIT_OK; active = setting_list_copy(dev->active.settings); persistent = setting_list_copy(dev->persistent.settings); autoconf = setting_list_copy(dev->autoconf.settings); } else if (!prereq) { /* Should not happen. */ return EXIT_OK; } /* Read current device data. */ rc = cfg_read(st, id, config, scope_known, 1, &dev); if (rc) goto out; /* Mark device as processed. */ if (proc_ptr) *proc_ptr = 1; dev->processed = 1; /* Apply changes to device existence. */ rc = cfg_mod_existence(dev, config); if (rc) goto out; if (!prereq) { /* Copy target settings to device configuration. */ rc = device_apply_settings(dev, config_active, &active->list); if (rc) goto out; rc = device_apply_settings(dev, config_persistent, &persistent->list); if (rc) goto out; rc = device_apply_settings(dev, config_autoconf, &autoconf->list); if (rc) goto out; } ensure_online(dev, config_active); ensure_online(dev, config_persistent); ensure_online(dev, config_autoconf); /* Write resulting device data. */ rc = cfg_write(dev, prereq, config, 0); out: setting_list_free(autoconf); setting_list_free(persistent); setting_list_free(active); return rc; } static exit_code_t print_generic_err(struct selected_dev_node *sel, struct device *dev, exit_code_t rc) { struct subtype *other_st; struct subtype *st; int print = 1; exit_code_t rc2; if (rc != EXIT_DEVICE_NOT_FOUND || !sel) goto out; st = sel->st; /* Check if there is a subtype mismatch. */ if (st && dev && !(dev->active.exists || dev->active.definable) && namespaces_device_exists(st->namespace, sel->id, config_active, &other_st) && st != other_st) { delayed_err("Trying to configure %s %s as %s\n", other_st->devname, sel->id, st->devname); rc = EXIT_INVALID_DEVTYPE; print = 0; goto out; } if (!sel->dt || devtype_count_namespaces(sel->dt) != 1) goto out; if (namespaces_device_exists(sel->dt->subtypes[0]->namespace, sel->id, config_active, &other_st) && other_st->devtype != sel->dt) { delayed_err("Trying to configure %s %s as %s\n", other_st->devname, sel->id, sel->dt->devname); rc = EXIT_INVALID_DEVTYPE; print = 0; goto out; } /* Try to get more information in case of a problem with definable * devices. */ if (st && st->support_definable && delayed_errors == 0) { rc2 = subtype_device_is_definable(st, sel->id, err_delayed_print); if (rc2) { rc = rc2; print = 0; } } out: if (print) delayed_err("%s\n", exit_code_to_str(rc)); return rc; } static exit_code_t print_config_result(struct selected_dev_node *sel, struct device *dev, struct options *opts, config_t config, exit_code_t rc, int prereq, int proc) { const char *devname, *devid, *op, *verb; int already; /* Exit here if a message for this device has already been printed. */ if (!proc) return rc; already = 0; if (opts->deconfigure) { op = "deconfigure"; verb = "deconfigured"; if (dev) { already = !dev->active.modified && !dev->persistent.modified && !dev->autoconf.modified; } else if (rc == EXIT_DEVICE_NOT_FOUND && (!SCOPE_ACTIVE(config) && (SCOPE_PERSISTENT(config) || SCOPE_AUTOCONF(config)))) { rc = EXIT_OK; already = 1; } } else { op = "configure"; verb = "configured"; if (dev) already = device_needs_writing(dev, config) ? 0 : 1; } /* Re-do actions if run with --force */ if (force) already = 0; if (dev) { devname = dev->subtype->devname; devid = dev->id; } else if (sel && sel->st) { devname = sel->st->devname; devid = sel->id ? sel->id : sel->param; } else if (sel && sel->dt) { devname = sel->dt->devname; devid = sel->id ? sel->id : sel->param; } else { devname = "Device"; if (sel) devid = sel->id ? sel->id : sel->param; else devid = ""; } if (rc) { /* Error message. */ warn("%s %s %s failed%s\n", devname, devid, op, prereq ? " (prerequisite)" : ""); /* Add the overall error message in case no other error message * has been queued. */ if (delayed_errors == 0) rc = print_generic_err(sel, dev, rc); } else if (already) { if (!prereq || delayed_messages_available()) { /* Already done message. */ printf("%s %s already %s\n", devname, devid, verb); } } else { /* Success message. */ printf("%s %s %s%s\n", devname, devid, verb, prereq ? " (prerequisite)" : ""); if (verbose && dev) print_dev_config_info(dev, config); } /* Show all delayed messages. */ delayed_print(DELAY_INDENT); return rc; } static exit_code_t cfg_prereqs(struct subtype *st, const char *id, struct options *opts, config_t config, int try) { struct util_list *prereqs; struct selected_dev_node *sel; exit_code_t rc = EXIT_OK; struct device *dev; int proc; prereqs = selected_dev_list_new(); subtype_add_prereqs(st, id, prereqs); util_list_iterate(prereqs, sel) { if (opts->apply) { rc = cfg_apply(sel->st, sel->id, 1, &dev, &proc, opts->auto_conf); } else if (opts->import) { rc = cfg_import(sel->st, sel->id, config, 1, &dev, &proc); } else { rc = cfg_configure(sel->st, sel->id, opts, try, 1, &dev, &proc); } rc = print_config_result(sel, dev, opts, config, rc, 1, proc); if (rc) { if (st == &zfcp_lun_subtype && rc == EXIT_DEVICE_NOT_FOUND) { /* Use special exit code for FCP. */ rc = EXIT_ZFCP_FCP_NOT_FOUND; } if (try) break; warn("%s %s configure failed\n", st->devname, id); delayed_err("Could not configure prerequisite %s %s\n", sel->st->devname, sel->id); delayed_print(DELAY_INDENT); break; } } selected_dev_list_free(prereqs); return rc; } static void handle_blacklist_range(struct namespace *ns, const char *range) { if (!ns->is_id_range_blacklisted || !ns->unblacklist_id_range) return; if (!ns_is_id_range_valid(ns, range)) return; if (ns->is_id_range_blacklisted(range)) ns->unblacklist_id_range(range); } /* Try to perform unblacklisting in ranges while minimizing calls to * handle_blacklist_range (that is, only call when namespace or param * changed). */ static void unblacklist_ranges(struct selected_dev_node *sel, struct namespace **ns_ptr, const char **param_ptr) { struct namespace *ns = *ns_ptr; const char *param = *param_ptr; if (ns == sel->st->namespace) return; if (!sel->param) return; if (param && strcmp(param, sel->param) == 0) return; ns = sel->st->namespace; param = sel->param; handle_blacklist_range(ns, param); *ns_ptr = ns; *param_ptr = param; } /* Handle device configuration. */ static exit_code_t configure_devices(struct options *opts, int specified, int *found_ptr) { struct util_list *selected; struct selected_dev_node *sel; exit_code_t rc, drc = EXIT_OK; int existing, proc; struct namespace *ns; const char *param; struct device *dev; config_t config = opts->config; /* Determine list of selected devices. */ if ((!SCOPE_ACTIVE(config) && (SCOPE_PERSISTENT(config) || SCOPE_AUTOCONF(config))) || (opts->select->subtype && opts->select->subtype->support_definable)) existing = 0; else existing = 1; selected = selected_dev_list_new(); rc = select_devices(opts->select, selected, existing, 1, 0, opts->config, scope_known, err_print); if (rc) goto out; if (util_list_is_empty(selected)) { if (!specified) { rc = EXIT_OK; goto out; } error("No device was selected!\n"); rc = EXIT_EMPTY_SELECTION; goto out; } /* Work on selected devices. */ ns = NULL; param = NULL; util_list_iterate(selected, sel) { dev = NULL; proc = 0; rc = sel->rc; if (rc) { proc = 1; /* If select_devices didn't find the device with any * type in the active config, it does not exist. */ if (rc == EXIT_INCOMPLETE_TYPE && SCOPE_ACTIVE(opts->config)) rc = EXIT_DEVICE_NOT_FOUND; goto next; } /* Attempt to perform efficient unblacklisting in ranges. */ unblacklist_ranges(sel, &ns, ¶m); /* Configure potential prerequisite devices. */ rc = cfg_prereqs(sel->st, sel->id, opts, opts->config, 0); if (rc) goto next; /* Configure actual target device. */ if (opts->apply) { rc = cfg_apply(sel->st, sel->id, 0, &dev, &proc, opts->auto_conf); } else { rc = cfg_configure(sel->st, sel->id, opts, 0, 0, &dev, &proc); } next: /* Print results. */ rc = print_config_result(sel, dev, opts, opts->config, rc, 0, proc); /* Remember first non-zero exit code. */ if (rc && !drc) drc = rc; /* Skip device IDs which are combined in this one. */ if (rc == EXIT_OK && dev) { if (found_ptr) (*found_ptr)++; /* Note: selected is modified but since we're not * using util_list_iterate_safe, the next * element will be correctly taken from the * modified list. */ subtype_rem_combined(sel->st, dev, sel, selected); } } out: selected_dev_list_free(selected); return drc ? drc : rc; } static exit_code_t check_in_use(struct device *dev) { struct util_list *res; struct strlist_node *s; if (force) return EXIT_OK; res = inuse_get_resources(dev); if (!res) return EXIT_OK; warn("Warning: %s %s is in use!\n", dev->subtype->devname, dev->id); warn(" The following resources may be affected:\n"); util_list_iterate(res, s) warn(" - %s\n", s->str); if (!confirm("Continue with operation?")) return EXIT_ABORTED; return EXIT_OK; } /* Deconfigure specified device. */ static exit_code_t deconfigure_one_device(struct subtype *st, const char *id, struct options *opts, int try, struct device **dev_ptr, int *proc_ptr) { exit_code_t rc; struct device *dev; config_t config, read_config; /* Reset processed flag. */ if (proc_ptr) *proc_ptr = 0; config = opts->config; /* For trial configuration runs, we need information about the active * configuration. */ if (try && !SCOPE_ACTIVE(config)) read_config = config_all; else read_config = opts->config; /* Read configuration. */ dev = NULL; rc = subtype_read_device(st, id, read_config, scope_mandatory, &dev); if (dev_ptr) *dev_ptr = dev; if (rc) return rc; /* Selection can queue a device ID multiple times - filter out * double selections here. */ if (dev->processed) return EXIT_OK; /* Mark device as processed. */ if (proc_ptr) *proc_ptr = 1; dev->processed = 1; if (try && !(dev->active.exists || dev->active.definable || dev->persistent.exists || dev->autoconf.exists)) { /* Attempt to deconfigure this device will fail. */ return EXIT_DEVICE_NOT_FOUND; } /* Check if device exists in active config - we allow deconfigure if * device doesn't exist in active when there may be a persistent * dev. */ if (SCOPE_ACTIVE(config) && !(dev->active.exists || dev->active.definable)) { if (!((SCOPE_PERSISTENT(config) && dev->persistent.exists) || (SCOPE_AUTOCONF(config) && dev->autoconf.exists))) return EXIT_DEVICE_NOT_FOUND; } /* Sanity check before deconfiguring devices that are in use. */ rc = check_in_use(dev); if (rc) return rc; /* Deconfigure. */ if (SCOPE_ACTIVE(config)) { dev->active.deconfigured = 1; if (st->support_definable) { /* Deconfigure for configurable devices means * undefine. */ if (dev->active.exists) dev->active.modified = 1; } else if (subtype_online_get(st, dev, config_active) == 1) dev->active.modified = 1; } if (SCOPE_PERSISTENT(config) && dev->persistent.exists) { dev->persistent.deconfigured = 1; dev->persistent.modified = 1; } if (SCOPE_AUTOCONF(config) && dev->autoconf.exists) { dev->autoconf.deconfigured = 1; dev->autoconf.modified = 1; } if (!dev->active.modified && !dev->persistent.modified && !dev->autoconf.modified) return EXIT_OK; /* Pre-write check. */ rc = subtype_check_pre_configure(st, dev, 0, config); if (rc && !force) return rc; /* Write configuration. */ if (SCOPE_PERSISTENT(config)) pers_mod_devs++; rc = subtype_write_device(st, dev, config); if (rc) return rc; /* Post-write check. */ rc = subtype_check_post_configure(st, dev, 0, config); if (rc && !force) return rc; return EXIT_OK; } /* Handle device deconfiguration. */ static exit_code_t deconfigure_devices(struct options *opts) { struct util_list *selected; struct selected_dev_node *sel; exit_code_t rc, drc = EXIT_OK; int proc; struct device *dev; /* Determine list of selected devices. */ selected = selected_dev_list_new(); rc = select_devices(opts->select, selected, 1, 0, 0, opts->config, scope_mandatory, err_print); if (rc) goto out; if (util_list_is_empty(selected)) { error("No device was selected!\n"); rc = EXIT_EMPTY_SELECTION; goto out; } /* Work on selected devices. */ util_list_iterate(selected, sel) { dev = NULL; proc = 0; if (sel->rc) { rc = EXIT_DEVICE_NOT_FOUND; proc = 1; goto next; } /* Deconfigure device. */ rc = deconfigure_one_device(sel->st, sel->id, opts, 0, &dev, &proc); next: /* Print results. */ rc = print_config_result(sel, dev, opts, opts->config, rc, 0, proc); if (rc && !drc) drc = rc; /* Skip devices which are combined in this one. */ if (rc == EXIT_OK && dev) { /* Note: selected is modified but since we're not * using util_list_iterate_safe, the next * element will be correctly taken from the * modified list. */ subtype_rem_combined(sel->st, dev, sel, selected); } } out: selected_dev_list_free(selected); return drc ? drc : rc; } /* Perform --remove operation for specified config on devtype. */ static exit_code_t devtype_remove_settings(struct devtype *dt, config_t config, struct util_list *names) { struct util_list *found, *notfound; char *flat; exit_code_t rc = EXIT_OK; found = strlist_new(); notfound = strlist_new(); if (SCOPE_ACTIVE(config)) remove_settings(dt->active_settings, names, found, notfound, 1); if (SCOPE_PERSISTENT(config)) { remove_settings(dt->persistent_settings, names, found, notfound, 0); } if (!util_list_is_empty(notfound)) { flat = strlist_flatten(notfound, " "); delayed_err("Setting not found: %s\n", flat); free(flat); rc = EXIT_SETTING_NOT_FOUND; } strlist_free(found); strlist_free(notfound); return rc; } /* Perform --remove-all operation for specified config on devtype. */ static exit_code_t devtype_remove_all(struct devtype *dt, config_t config) { int active, persistent; active = SCOPE_ACTIVE(config) ? count_removable(dt->active_settings, 1) : 0; persistent = SCOPE_PERSISTENT(config) ? count_removable(dt->persistent_settings, 0) : 0; if (active == 0 && persistent == 0) { delayed_err("No removable settings found\n"); return EXIT_SETTING_NOT_FOUND; } if (active) remove_all_settings(dt->active_settings, 1); if (persistent) remove_all_settings(dt->persistent_settings, 0); return EXIT_OK; } static void print_type_config_info(struct devtype *dt, const char *title, config_t config) { char *changes; changes = setting_get_changes( SCOPE_ACTIVE(config) ? dt->active_settings : NULL, SCOPE_PERSISTENT(config) ? dt->persistent_settings : NULL, NULL); if (changes) info(" %s: %s\n", title, changes); free(changes); } static void print_devtype_config_result(struct devtype *dt, config_t config, exit_code_t rc) { if (rc) { /* Error message. */ warn("%s device type configure failed\n", dt->name); /* Add the overall error message in case no other error message * has been queued. */ if (delayed_errors == 0) delayed_err("%s\n", exit_code_to_str(rc)); } else if (!devtype_needs_writing(dt, config)) /* Already done message. */ printf("%s device type already configured\n", dt->name); else { /* Success message. */ printf("%s device type configured\n", dt->name); if (verbose) print_type_config_info(dt, "Changes", config); } delayed_print(DELAY_INDENT); } /* Handle device type settings. */ static exit_code_t configure_devtype(struct options *opts) { struct devtype *dt = opts->select->devtype; exit_code_t rc; /* Read settings. */ rc = dt->read_settings(dt, opts->config); if (rc) goto out; /* Remove all settings. */ if (opts->remove_all) { rc = devtype_remove_all(dt, opts->config); if (rc) goto out; } /* Remove a list of settings. */ if (!util_list_is_empty(opts->remove)) { rc = devtype_remove_settings(dt, opts->config, opts->remove); if (rc) goto out; } /* Apply new settings. */ rc = devtype_apply_strlist(dt, opts->config, opts->settings); if (rc) goto out; if (!devtype_needs_writing(dt, opts->config)) goto out; /* Write settings. */ if (SCOPE_PERSISTENT(opts->config)) pers_mod_devtypes++; rc = dt->write_settings(dt, opts->config); out: print_devtype_config_result(dt, opts->config, rc); return rc; } /* Apply persistent device type settings to active configuration. */ static exit_code_t apply_devtype(struct devtype *dt, int specified, int *found_ptr) { exit_code_t rc; /* Read settings. */ rc = dt->read_settings(dt, config_all); if (rc) return rc; if (!dt->persistent_settings || setting_list_count_set(dt->persistent_settings) == 0) { /* No persistent configuration found. */ if (!specified) return EXIT_OK; rc = EXIT_NO_DATA; goto out; } (*found_ptr)++; /* Apply new settings. */ rc = devtype_apply_settings(dt, config_active, &dt->persistent_settings->list); if (rc) goto out; if (!devtype_needs_writing(dt, config_active)) goto out; /* Write settings. */ rc = dt->write_settings(dt, config_active); out: print_devtype_config_result(dt, config_active, rc); return rc; } /* Return a ptrlist of newly allocated struct table_attribs for all device type * attributes of a devtype. */ static struct util_list *get_type_attribs(struct devtype *dt, struct util_list *names) { int i; struct util_list *attribs; struct attrib *a; struct strlist_node *s; attribs = ptrlist_new(); if (names) { util_list_iterate(names, s) { a = attrib_find(dt->type_attribs, s->str); if (!a) goto notfound; ptrlist_add(attribs, table_attrib_new(NULL, a)); } } else { for (i = 0; (a = dt->type_attribs[i]); i++) ptrlist_add(attribs, table_attrib_new(NULL, a)); } return attribs; notfound: ptrlist_free(attribs, 1); error("%s type attribute '%s' not found!\n", dt->name, s->str); return NULL; } /* Return a ptrlist of all device attributes of a devtype without duplicates. */ static struct util_list *get_dev_attribs(struct devtype *dt, struct subtype *st_only, struct util_list *names) { struct subtype *st; int i, j; struct util_list *attribs; struct attrib *a; struct strlist_node *s; bool found; attribs = ptrlist_new(); if (names) { util_list_iterate(names, s) { a = NULL; found = false; for (i = 0; (st = dt->subtypes[i]); i++) { if (st_only && st != st_only) continue; a = attrib_find(st->dev_attribs, s->str); if (a) { ptrlist_add(attribs, table_attrib_new(st, a)); found = true; } } if (!found) goto notfound; } } else { for (i = 0; (st = dt->subtypes[i]); i++) { if (st_only && st != st_only) continue; for (j = 0; (a = st->dev_attribs[j]); j++) ptrlist_add(attribs, table_attrib_new(st, a)); } } return attribs; notfound: ptrlist_free(attribs, 0); if (st_only) { error("%s attribute '%s' not found!\n", st_only->devname, s->str); } else { error("%s attribute '%s' not found!\n", dt->devname, s->str); } return NULL; } /* Remove duplicate struct type_attrib entries in ptrlist @list. A duplicate * entry is an entry that occurs @num_subtypes times in @list. */ static void remove_duplicate_attribs(struct util_list *list, int num_subtypes) { struct ptrlist_node *curr, *check, *next; struct table_attrib *curr_t, *check_t; int num; if (!list) return; util_list_iterate(list, curr) { num = 1; curr_t = curr->ptr; for (check = util_list_next(list, curr); check; check = util_list_next(list, check)) { check_t = check->ptr; if (curr_t->attrib == check_t->attrib) num++; } if (num != num_subtypes) continue; /* Remove duplicates. */ curr_t->st = NULL; check = util_list_next(list, curr); while (check) { next = util_list_next(list, check); check_t = check->ptr; if (curr_t->attrib == check_t->attrib) { util_list_remove(list, check); free(check->ptr); free(check); } check = next; } } } static exit_code_t do_list_attribs(struct options *opts) { struct devtype *dt = opts->select->devtype; struct subtype *st = opts->select->subtype; struct util_list *names; struct util_list *attribs; exit_code_t rc = EXIT_OK; names = opts->positional; if (names && util_list_is_empty(names)) names = NULL; if (opts->type) { if (!dt->type_attribs[0]) { error("Device type %s does not provide type " "attributes\n", dt->name); return EXIT_ATTRIB_NOT_FOUND; } attribs = get_type_attribs(dt, names); } else attribs = get_dev_attribs(dt, st, names); remove_duplicate_attribs(attribs, devtype_count_subtypes(dt)); if (attribs) { table_attribs_show(attribs, 1, 0, dt); ptrlist_free(attribs, 1); } else rc = EXIT_ATTRIB_NOT_FOUND; return rc; } static exit_code_t do_help_attribs(struct options *opts) { struct devtype *dt = opts->select->devtype; struct subtype *st = opts->select->subtype; struct util_list *names; struct util_list *attribs; exit_code_t rc = EXIT_OK; names = opts->positional; if (names && util_list_is_empty(names)) names = NULL; if (opts->type) attribs = get_type_attribs(dt, names); else attribs = get_dev_attribs(dt, st, names); remove_duplicate_attribs(attribs, devtype_count_subtypes(dt)); if (attribs) { table_attribs_show_details(attribs, dt); ptrlist_free(attribs, 1); } else rc = EXIT_ATTRIB_NOT_FOUND; return rc; } /* List known device types. */ static exit_code_t do_list_types(struct options *opts) { return table_types_show(NULL, 1, 0); } static exit_code_t export_single_devtype(FILE *fd, struct devtype *dt, struct options *opts, int *first_ptr) { exit_code_t rc; /* Get settings. */ rc = dt->read_settings(dt, opts->config); if (rc || dt->processed) return rc; dt->processed = 1; return export_write_devtype(fd, dt, opts->config, first_ptr); } static exit_code_t export_devtypes(FILE *fd, struct options *opts, int *first_ptr) { struct devtype *dt; int i; exit_code_t rc; rc = EXIT_OK; for (i = 0; (dt = devtypes[i]); i++) { if (opts->select->devtype && dt != opts->select->devtype) continue; rc = export_single_devtype(fd, dt, opts, first_ptr); if (rc) break; } return rc; } static void print_export_error(struct selected_dev_node *sel) { const char *devname; if (sel->st) devname = sel->st->devname; else if (sel->dt) devname = sel->dt->devname; else devname = "Device"; warn("%s %s export failed\n", devname, sel->id); warn(" Error: %s\n", exit_code_to_str(sel->rc)); } static exit_code_t export_devices_and_devtypes(FILE *fd, struct options *opts, int *first_ptr) { struct util_list *selected; exit_code_t rc, drc = EXIT_OK; struct selected_dev_node *sel; struct device *dev; config_t config = opts->config; /* Determine list of selected devices. */ selected = selected_dev_list_new(); rc = select_devices(opts->select, selected, 1, 0, 0, opts->config, scope_known, err_print); if (rc) goto out; if (util_list_is_empty(selected)) goto out; /* Work on selected devices. */ util_list_iterate(selected, sel) { if (sel->rc) { print_export_error(sel); rc = sel->rc; goto next; } rc = subtype_read_device(sel->st, sel->id, config, scope_known, &dev); if (rc || dev->processed) goto next; dev->processed = 1; /* Write out device type configuration. */ if (opts->type) { rc = export_single_devtype(fd, dev->subtype->devtype, opts, first_ptr); if (rc) goto next; } /* Write out device configuration. */ rc = export_write_device(fd, dev, opts->config, first_ptr); next: if (rc && !drc) drc = rc; } out: selected_dev_list_free(selected); return drc ? drc : rc; } static void action_note(const char *msg, config_t config) { if (config == config_active) info("%s the active configuration only\n", msg); else if (config == config_persistent) info("%s the persistent configuration only\n", msg); else if (config == config_autoconf) info("%s the auto-configuration only\n", msg); } /* Export configuration of selected devices and/or device type to file. */ static exit_code_t do_export(struct options *opts) { FILE *fd; exit_code_t rc = EXIT_OK; int devtype, devices, first; devtype = opts->type; devices = select_opts_dev_specified(opts->select); /* Open output stream. */ if (strcmp(opts->export, "-") == 0) { fd = stdout; info("Exporting data to standard output\n"); } else { info("Exporting data to %s\n", opts->export); if (!util_path_exists("%s", opts->export)) { rc = path_create(opts->export); if (rc) return rc; } fd = fopen(opts->export, "w"); } if (!fd) { error("Could not write to file %s: %s\n", opts->export, strerror(errno)); return EXIT_RUNTIME_ERROR; } first = 1; /* Handle export of device type configuration if no device was * specified. */ if (devtype && !devices) { rc = export_devtypes(fd, opts, &first); if (rc) goto out; } /* Handle export of selected devices and associated device types. */ if (devices) rc = export_devices_and_devtypes(fd, opts, &first); out: if (rc == EXIT_OK) { if (first) { error("No settings found to export\n"); rc = EXIT_EMPTY_SELECTION; } } /* Close stream. */ if (fd != stdout) fclose(fd); return rc; } static exit_code_t import_devtype(struct devtype *dt, config_t config) { exit_code_t rc; struct setting_list *active = NULL, *persistent = NULL; if (dt->processed) return EXIT_OK; /* Save settings. */ if (dt->active_settings) { active = setting_list_copy(dt->active_settings); setting_list_free(dt->active_settings); dt->active_settings = NULL; } if (dt->persistent_settings) { persistent = setting_list_copy(dt->persistent_settings); setting_list_free(dt->persistent_settings); dt->persistent_settings = NULL; } /* Read settings. */ rc = dt->read_settings(dt, config); if (rc) goto out; dt->processed = 1; /* Copy target settings to device configuration. */ if (active) { rc = devtype_apply_settings(dt, config_active, &active->list); if (rc) goto out; } if (persistent) { rc = devtype_apply_settings(dt, config_persistent, &persistent->list); if (rc) goto out; } if (!devtype_needs_writing(dt, config)) goto out; /* Write settings. */ if (SCOPE_PERSISTENT(config)) pers_mod_devtypes++; rc = dt->write_settings(dt, config); out: setting_list_free(persistent); setting_list_free(active); print_devtype_config_result(dt, config, rc); return rc; } static exit_code_t import_device(struct device *dev, struct options *opts) { struct subtype *st = dev->subtype; exit_code_t rc; config_t config; int proc = 0, active, persistent, autoconf; if (SCOPE_ACTIVE(opts->config)) active = (dev->active.exists || dev->active.definable); else active = 0; if (SCOPE_PERSISTENT(opts->config)) persistent = dev->persistent.exists; else persistent = 0; if (SCOPE_AUTOCONF(opts->config)) autoconf = dev->autoconf.exists; else autoconf = 0; if (!active && !persistent && !autoconf) { /* Nothing to import */ return EXIT_OK; } config = get_config(active, persistent, autoconf); /* First check for prerequisite devices that need to be configured. */ rc = cfg_prereqs(st, dev->id, opts, config, 0); if (rc) goto out; rc = cfg_import(st, dev->id, config, 0, NULL, &proc); out: rc = print_config_result(NULL, dev, opts, config, rc, 0, proc); return rc; } static bool import_device_selected(struct device *dev, struct options *opts) { struct select_opts *select = opts->select; struct subtype *st = dev->subtype; struct devtype *dt = st->devtype; struct namespace *ns = st->namespace; struct strlist_node *str; /* --type only */ if (!select_opts_dev_specified(select)) return false; /* Target configuration */ if ((device_get_config(dev) & opts->config) == 0) return false; /* Devtype */ if (select->devtype && dt != select->devtype) return false; /* Subtype */ if (select->subtype && st != select->subtype) return false; /* Device ID */ if (!util_list_is_empty(&select->devids)) { str = NULL; util_list_iterate(&select->devids, str) { if (ns->cmp_ids(str->str, dev->id) == 0) break; } if (str) return true; else return false; } /* State */ if (select_match_state(dev, select)) return true; return false; } static bool import_devtype_selected(struct devtype *dt, struct options *opts) { if (!opts->type) return false; if (opts->config == config_active) { if (!dt->active_settings || util_list_is_empty(&dt->active_settings->list)) return false; } if (opts->config == config_persistent) { if (!dt->persistent_settings || util_list_is_empty(&dt->persistent_settings->list)) return false; } return true; } /* Remove export objects from @objects which are not matched by selection * options. */ static void apply_selection_to_import(struct util_list *objects, struct options *opts) { struct ptrlist_node *p, *n; struct export_object *obj; util_list_iterate_safe(objects, p, n) { obj = p->ptr; if (obj->type == export_device) { if (import_device_selected(obj->ptr.dev, opts)) continue; } else if (obj->type == export_devtype) { if (import_devtype_selected(obj->ptr.dt, opts)) continue; } util_list_remove(objects, p); free(p->ptr); free(p); } } /* Import configuration data. */ static exit_code_t do_import(struct options *opts) { FILE *fd; exit_code_t rc; struct util_list *objects; struct ptrlist_node *p; struct export_object *obj; exit_code_t drc = EXIT_OK; const char *filename; int found; bool is_firmware; /* Open input stream. */ if (strcmp(opts->import, "-") == 0) { fd = stdin; filename = "Standard input"; } else { fd = fopen(opts->import, "r"); filename = opts->import; } if (!fd) { error("Could not open file %s: %s\n", opts->import, strerror(errno)); return EXIT_RUNTIME_ERROR; } is_firmware = firmware_detect(fd); info("Importing data from %s%s\n", filename, is_firmware ? " (firmware format)" : ""); /* Read data. */ objects = ptrlist_new(); if (is_firmware) rc = firmware_read(fd, filename, -1, opts->config, objects); else rc = export_read(fd, filename, objects); if (rc) goto out; found = !util_list_is_empty(objects); apply_selection_to_import(objects, opts); if (util_list_is_empty(objects)) { if (found) { error("%s: Imported configuration data did not match " "selection\n", filename); rc = EXIT_EMPTY_SELECTION; } else { info("%s: No settings found to import\n", filename); rc = EXIT_OK; } goto out; } util_list_iterate(objects, p) { obj = p->ptr; if (obj->type == export_devtype) rc = import_devtype(obj->ptr.dt, opts->config); else rc = import_device(obj->ptr.dev, opts); if (rc && !drc) drc = rc; } out: ptrlist_free(objects, 1); /* Close stream. */ if (fd != stdin) fclose(fd); return drc ? drc : rc; } static bool opts_stdout_data(struct options *opts) { if (opts->export && strcmp(opts->export, "-") == 0) return true; return false; } static exit_code_t do_configure(struct options *opts) { if (opts->type) { action_note("Configuring device type in", opts->config); return configure_devtype(opts); } action_note("Configuring devices in", opts->config); return configure_devices(opts, 1, NULL); } static exit_code_t do_deconfigure(struct options *opts) { action_note("Deconfiguring devices in", opts->config); return deconfigure_devices(opts); } static exit_code_t apply_devtypes(struct options *opts, int *found_ptr, int *specified_ptr) { int i; struct devtype *dt; exit_code_t rc, drc = EXIT_OK; if (opts->select->devtype) { (*specified_ptr)++; return apply_devtype(opts->select->devtype, 1, found_ptr); } for (i = 0; (dt = devtypes[i]); i++) { rc = apply_devtype(dt, 0, found_ptr); if (rc && drc == EXIT_OK) drc = rc; } return drc; } static exit_code_t do_apply(struct options *opts) { exit_code_t rc, drc = EXIT_OK; int found, specified; found = 0; specified = 0; /* Apply devtype configuration. */ if (opts->type) { rc = apply_devtypes(opts, &found, &specified); if (rc && drc == EXIT_OK) drc = rc; } /* Apply device configuration. */ if (select_opts_dev_specified(opts->select)) { specified++; rc = configure_devices(opts, 1, &found); if (rc && drc == EXIT_OK) drc = rc; } if (!found && !specified) { error("No configuration data found\n"); if (drc == EXIT_OK) drc = EXIT_NO_DATA; } return drc; } int main(int argc, char *argv[]) { exit_code_t rc, drc = EXIT_OK; struct options opts; debug_init(argc, argv); /* Handle command line. */ toolname = argv[0]; init_options(&opts); devtypes_init(); rc = parse_options(&opts, argc, argv); if (rc) { if (!drc) drc = rc; goto out; } /* Set globals. */ if (opts_stdout_data(&opts)) set_stdout_data(); verbose = opts.verbose; quiet = opts.quiet; force = opts.force; yes = opts.yes; dryrun = opts.dryrun; udev_no_settle = opts.no_settle; path_set_base(opts.base); if (dryrun) info("Starting dry-run, configuration will not be changed\n"); /* Perform main action. */ switch (get_action(&opts)) { case ACT_CONFIGURE: rc = do_configure(&opts); break; case ACT_DECONFIGURE: rc = do_deconfigure(&opts); break; case ACT_LIST_ATTRIBS: rc = do_list_attribs(&opts); break; case ACT_HELP_ATTRIBS: rc = do_help_attribs(&opts); break; case ACT_LIST_TYPES: rc = do_list_types(&opts); break; case ACT_EXPORT: rc = do_export(&opts); break; case ACT_IMPORT: rc = do_import(&opts); break; case ACT_APPLY: rc = do_apply(&opts); break; case ACT_HELP: print_usage(); break; case ACT_VERSION: print_version(); break; } if (rc) { if (!drc) drc = rc; goto out; } if (udev_need_settle) udev_settle(); if ((pers_mod_devs || pers_mod_devtypes) && !opts.no_root_check && !dryrun) { /* If the root device/device type or early devices have been * modified, additional work might be necessary. */ rc = root_check(); if (rc && !drc) drc = rc; } out: /* Write out any remaining messages. */ delayed_print(0); /* Clean-up. */ free_options(&opts); blkinfo_exit(); ccw_exit(); ctc_exit(); devtypes_exit(); inuse_exit(); misc_exit(); module_exit(); rc = namespace_exit(); if (rc && !drc) drc = rc; path_exit(); scsi_exit(); if (found_forceable && !force) { info("Note: You can use --force to override safety checks " "(*)\n"); } return drc ? drc : rc; }