/* * ap-check - Validate vfio-ap mediated device configuration changes * * This tool in intended to be driven via the callout API of the mdevctl * utility (https://github.com/mdevctl/mdevctl/) * * Copyright IBM Corp. 2022 * * s390-tools is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See LICENSE for details. */ #include #include #include #include #include #include #include #include #include #include "lib/ap.h" #include "lib/util_base.h" #include "lib/util_libc.h" #include "lib/util_opt.h" #include "lib/util_path.h" #include "ap-check.h" /* The supported mdevctl callout version */ #define MDEVCTL_CAP_VERSION 2 static const struct mdevctl_action mdevctl_action_table[NUM_MDEVCTL_ACTIONS] = { {MDEVCTL_ACTION_DEFINE, "define"}, {MDEVCTL_ACTION_MODIFY, "modify"}, {MDEVCTL_ACTION_START, "start"}, {MDEVCTL_ACTION_STOP, "stop"}, {MDEVCTL_ACTION_UNDEFINE, "undefine"}, {MDEVCTL_ACTION_ATTRIBUTES, "attributes"}, {MDEVCTL_ACTION_CAPABILITIES, "capabilities"} /* * Note: the following actions are known to exist but currently ignored: * {MDEVCTL_ACTION_LIST, "list"}, * {MDEVCTL_ACTION_TYPES, "types"} */ }; static const struct mdevctl_event mdevctl_event_table[NUM_MDEVCTL_EVENTS] = { {MDEVCTL_EVENT_PRE, "pre"}, {MDEVCTL_EVENT_POST, "post"}, {MDEVCTL_EVENT_GET, "get"}, {MDEVCTL_EVENT_LIVE, "live"} /* * Note: the following events are known to exist but currently ignored: * {MDEVCTL_EVENT_NOTIFY, "notify"}, */ }; /* * Convert mdevctl action string to an enumerated value */ static enum mdevctl_action_id validate_action(char *action) { int i; for (i = 0; i < NUM_MDEVCTL_ACTIONS; i++) { if (strcmp(action, mdevctl_action_table[i].action) == 0) return mdevctl_action_table[i].id; } return MDEVCTL_ACTION_UNKNOWN; } /* * Convert mdevctl event string to an enumerated value */ static enum mdevctl_event_id validate_event(char *event) { int i; for (i = 0; i < NUM_MDEVCTL_EVENTS; i++) { if (strcmp(event, mdevctl_event_table[i].event) == 0) return mdevctl_event_table[i].id; } return MDEVCTL_EVENT_UNKNOWN; } static struct util_opt opt_vec[] = { UTIL_OPT_SECTION("DEVICE"), { .option = { "e", required_argument, NULL, 'e' }, .argument = "EVENT", .desc = "The type of callout being issued", }, { .option = { "a", required_argument, NULL, 'a' }, .argument = "ACTION", .desc = "The action being performed on the specified device", }, { .option = { "s", required_argument, NULL, 's' }, .argument = "STATE", .desc = "The state of the associated mdevctl command", }, { .option = { "u", required_argument, NULL, 'u' }, .argument = "UUID", .desc = "Universally Unique ID for the mediated device", }, { .option = { "p", required_argument, NULL, 'p' }, .argument = "PDEV", .desc = "Parent device name, e.g. matrix", }, { .option = { "t", required_argument, NULL, 't' }, .argument = "TYPE", .desc = "Mediated device type, e.g. vfio_ap-passthrough", }, UTIL_OPT_END }; /* * Initialize the ap_check anchor struct. */ static void ap_check_init(struct ap_check_anchor *anc) { anc->uuid = anc->parent = anc->type = NULL; anc->dev = vfio_ap_device_new(); anc->cleanup_lock = false; } /* * Free memory of ap_check anchor struct. */ static void ap_check_cleanup(struct ap_check_anchor *anc) { if (anc->uuid) free(anc->uuid); if (anc->parent) free(anc->parent); if (anc->type) free(anc->type); if (anc->dev) vfio_ap_device_free(anc->dev); if (anc->cleanup_lock) ap_release_lock_callout(); } /* * Exit ap_check */ static void __noreturn ap_check_exit(struct ap_check_anchor *anc, int rc) { ap_check_cleanup(anc); exit(rc); } /* * parses the command line */ static void ap_check_parse(struct ap_check_anchor *anc, int argc, char *argv[]) { bool action = false, event = false, state = false, bad_opts = false; int opt; util_opt_init(opt_vec, NULL); while (1) { opt = util_opt_getopt_long(argc, argv); if (opt == -1) break; switch (opt) { case 'e': if (event) { bad_opts = true; } else { anc->event = validate_event(optarg); event = true; } break; case 'a': if (action) { bad_opts = true; } else { anc->action = validate_action(optarg); action = true; } break; case 's': if (state) { bad_opts = true; } else { /* Ignore the state */ state = true; } break; case 'u': if (anc->uuid) bad_opts = true; else anc->uuid = util_strdup(optarg); break; case 'p': if (anc->parent) bad_opts = true; else anc->parent = util_strdup(optarg); break; case 't': if (anc->type) bad_opts = true; else anc->type = util_strdup(optarg); break; default: fprintf(stderr, "Unknown operand\n"); ap_check_exit(anc, EXIT_FAILURE); } } /* Make sure we got all expected input values */ if (!(action && event && state && anc->uuid && anc->parent && anc->type) || bad_opts) { fprintf(stderr, "Duplicate or missing operand\n"); ap_check_exit(anc, EXIT_FAILURE); } /* Check for invalid UUID */ if (!is_valid_uuid(anc->uuid)) { fprintf(stderr, "Invalid UUID specified\n"); ap_check_exit(anc, EXIT_FAILURE); } anc->dev->uuid = util_strdup(anc->uuid); /* Check for valid type */ if (strcmp(anc->type, VFIO_AP_TYPE) != 0) ap_check_exit(anc, APC_EXIT_UNKNOWN_TYPE); /* Check for invalid parent - currently only 'matrix' supported */ if (strcmp(anc->parent, "matrix") != 0) { fprintf(stderr, "Invalid parent specified\n"); ap_check_exit(anc, EXIT_FAILURE); } } /* * Call a function for each entry in a directory: * int callback(const char *abs_path, const char *rel_path, void *data) * Continues for all entries in the directory regardless of callback return * code. Will return 0 or, if one or more callbacks failed, the first nonzero * rc received. */ static int path_for_each(const char *path, int (*callback)(const char *, const char *, void *), void *data) { struct dirent *de; int rc = 0; int r = 0; DIR *dir; char *p; dir = opendir(path); if (!dir) return -1; while ((de = readdir(dir))) { if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; util_asprintf(&p, "%s/%s", path, de->d_name); r = callback(p, de->d_name, data); /* Save first nonzero return code for caller */ if (rc == 0 && r != 0) rc = r; free(p); } closedir(dir); return rc; } /* * Report an error message when the specified configuration will conflict * with an existing device */ static void conflict_error(const char *uuid, unsigned int a, unsigned int d, bool persistent) { if (uuid) { if (persistent) { fprintf(stderr, "APQN %u.%u is defined for autostart by %s\n", a, d, uuid); } else { fprintf(stderr, "APQN %u.%u already in use by %s\n", a, d, uuid); } } else { if (persistent) { fprintf(stderr, "APQN %u.%u is not defined for " "vfio_ap-passthrough use by the persistent " "ap bus mask settings\n", a, d); } else { fprintf(stderr, "APQN %u.%u is not allowed for " "vfio_ap-passthrough use by the active ap " "bus mask settings\n", a, d); } } } /* * Compare the list of adapters and domains for two devices, reporting error * messages for any conflicts that occur. A conflict occurs when both devices * have the same adapter + domain pair. * The function below takes advantage of the fact that the lists are known to * be sorted in numeric order; therefore we can use this information to run * the lists in parallel rather than always starting from the beginning. */ static int find_apqn_conflicts(const char *uuid, struct util_list *adapters, struct util_list *domains, struct util_list *adapters2, struct util_list *domains2, bool persistent) { struct vfio_ap_node *a, *a2, *d, *d2; int rc = 0; /* Checks for conflicts with the device */ a = util_list_start(adapters); a2 = util_list_start(adapters2); while ((a != NULL) && (a2 != NULL)) { if (a->id == a2->id) { d = util_list_start(domains); d2 = util_list_start(domains2); while ((d != NULL) && (d2 != NULL)) { if (d->id == d2->id) { /* Report error, look for more */ conflict_error(uuid, a->id, d->id, persistent); rc = -1; d = util_list_next(domains, d); d2 = util_list_next(domains2, d2); } else if (d->id > d2->id) { d2 = util_list_next(domains2, d2); } else { d = util_list_next(domains, d); } } a = util_list_next(adapters, a); a2 = util_list_next(adapters2, a2); } else if (a->id > a2->id) { a2 = util_list_next(adapters2, a2); } else { a = util_list_next(adapters, a); } } return rc; } /* * If the provided path maps to a valid vfio-ap device configuration, * determine if its current configuration will conflict with the proposed * changes. */ static int check_other_mdev_cfg_cb(const char *path, const char *filename, void *data) { struct other_mdev_cb_data *cbdata = data; struct vfio_ap_device *dev = cbdata->dev; struct vfio_ap_device *dev2 = NULL; int rc = 0; /* Skip anything that isn't an mdev config */ if (!is_valid_uuid(filename)) goto out; /* Skip if this is the input device */ if (strcasecmp(cbdata->uuid, filename) == 0) goto out; /* Read the device config */ dev2 = vfio_ap_device_new(); if (vfio_ap_read_device_config(path, dev2) != 0) goto out; /* If wrong device type, skip */ if (strcmp(dev2->type, VFIO_AP_TYPE) != 0) goto out; /* If not AUTO device, skip */ if (dev2->manual) goto out; /* Perform mdev-to-mdev apqn conflict analysis */ rc = find_apqn_conflicts(filename, dev->adapters, dev->domains, dev2->adapters, dev2->domains, true); out: if (dev2 != NULL) vfio_ap_device_free(dev2); return rc; } /* * Perform conflict analysis against all other vfio-ap persistent * configurations. */ int check_other_mdevs_cfg(struct ap_check_anchor *anc) { struct other_mdev_cb_data cb_data; if (!util_path_is_dir(VFIO_AP_CONFIG_PATH)) return 0; cb_data.uuid = anc->uuid; cb_data.dev = anc->dev; return path_for_each(VFIO_AP_CONFIG_PATH, check_other_mdev_cfg_cb, &cb_data); } /* * If the provided path maps to a valid device, determine if its current * configuration will conflict with the proposed changes. */ static int check_other_mdev_sysfs_cb(const char *path, const char *filename, void *data) { struct other_mdev_cb_data *cbdata = data; struct vfio_ap_device *dev = cbdata->dev; struct vfio_ap_device *dev2; char *matrix_path; char buf[80]; int rc = 0; FILE *f; if (!is_valid_uuid(filename) || path == NULL || strcasecmp(filename, cbdata->uuid) == 0) return 0; /* * Read the 'matrix' attribute to get the list of queues for the active * device. If the sysfs attribute is unreadable, assume the device is * being destroyed and skip it. */ matrix_path = path_get_vfio_ap_attr(filename, "matrix"); f = fopen(matrix_path, "r"); free(matrix_path); if (!f) return 0; dev2 = vfio_ap_device_new(); while (fgets(buf, sizeof(buf), f)) vfio_ap_parse_matrix(dev2, buf); vfio_ap_sort_matrix_results(dev2); fclose(f); /* Look for conflicts between target device and this device */ rc = find_apqn_conflicts(filename, dev->adapters, dev->domains, dev2->adapters, dev2->domains, false); vfio_ap_device_free(dev2); return rc; } /* Run conflict analysis against all other active vfio-ap devices */ static int check_other_mdevs_sysfs(struct ap_check_anchor *anc) { struct other_mdev_cb_data cb_data; char *root; int rc = 0; cb_data.uuid = anc->uuid; cb_data.dev = anc->dev; root = path_get_vfio_ap_mdev(""); if (util_path_is_dir(root)) rc = path_for_each(root, check_other_mdev_sysfs_cb, &cb_data); free(root); return rc; } /* * Determine if there are any conflicts between the specified device and * the active apmask/aqmask settings. This is done by treating the masks * as a temporary vfio_ap_device with all of the associated APQNs owned by * the system. */ static int check_sysfs_mask_conflicts(struct ap_check_anchor *anc) { struct vfio_ap_device *sysdev = vfio_ap_device_new(); char *apmask = util_zalloc(AP_MASK_SIZE); char *aqmask = util_zalloc(AP_MASK_SIZE); int rc = 0; if (ap_read_sysfs_masks(apmask, aqmask, AP_MASK_SIZE) != 0) { fprintf(stderr, "Error reading system AP settings\n"); rc = -1; goto out; } /* Convert the masks to a device with the associated APQNs */ ap_mask_to_list(apmask, sysdev->adapters); ap_mask_to_list(aqmask, sysdev->domains); /* Perform conflict analysis */ rc = find_apqn_conflicts(NULL, anc->dev->adapters, anc->dev->domains, sysdev->adapters, sysdev->domains, false); out: free(apmask); free(aqmask); vfio_ap_device_free(sysdev); return rc; } /* * Determine if there are any conflicts between the specified device and * the apmask/aqmask settings stored in udev. This is done by treating * the masks as a temporary vfio_ap_device with all of the associated * APQNs owned by the system. */ static int check_cfg_mask_conflicts(struct ap_check_anchor *anc) { struct vfio_ap_device *sysdev = vfio_ap_device_new(); char *apmask = util_zalloc(AP_MASK_SIZE); char *aqmask = util_zalloc(AP_MASK_SIZE); bool read_ap = false, read_aq = false; char *path; int rc = 0; path = path_get_ap_udev(); if (!ap_read_udev_masks(path, apmask, aqmask, &read_ap, &read_aq)) { fprintf(stderr, "Error reading system AP settings\n"); rc = -1; goto out; } /* Convert the masks to a device with the associated APQNs */ ap_mask_to_list(apmask, sysdev->adapters); ap_mask_to_list(aqmask, sysdev->domains); /* Perform conflict analysis */ rc = find_apqn_conflicts(NULL, anc->dev->adapters, anc->dev->domains, sysdev->adapters, sysdev->domains, true); out: free(apmask); free(aqmask); free(path); vfio_ap_device_free(sysdev); return rc; } /* Subroutine to handle checking shared between DEFINE and MODIFY actions. */ static int ap_check_changes(struct ap_check_anchor *anc) { int rc = 0, rc2; rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); rc = -1; goto out; } anc->cleanup_lock = true; if (vfio_ap_read_device_config(NULL, anc->dev) != 0) { fprintf(stderr, "Failed to read device config\n"); rc = -1; goto out; } if (strcmp(anc->dev->type, anc->type) != 0) { fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); rc = -1; goto out; } if (!anc->dev->manual) { /* Check against all other AUTO config files */ rc = check_other_mdevs_cfg(anc); /* Check against the system UDEV rule for apmask/aqmask */ rc2 = check_cfg_mask_conflicts(anc); /* If either hit an error, reflect this */ rc = rc != 0 ? rc : rc2; } /* If successful, lock must remain held until post callout */ if (rc == 0) anc->cleanup_lock = false; out: return rc; } static int ap_check_active(struct ap_check_anchor *anc) { int rc, rc2; /* Ensure device with control domains also has usage domains */ if (util_list_is_empty(anc->dev->domains) && !util_list_is_empty(anc->dev->controls)) { fprintf(stderr, "At least one usage domain must be specified\n"); return -1; } /* Check against all other active vfio-ap devices */ rc = check_other_mdevs_sysfs(anc); /* Check against the system sysfs values for apmask/aqmask */ rc2 = check_sysfs_mask_conflicts(anc); /* If either hit an error, reflect this */ rc = rc != 0 ? rc : rc2; return rc; } static int ap_do_dynamic_config(struct ap_check_anchor *anc) { char *adapters, *domains, *controls, *path, *attr; int asize, dsize, csize, size; int rc = 0; FILE *f; adapters = vfio_ap_device_get_adapter_mask(anc->dev, &asize); domains = vfio_ap_device_get_domain_mask(anc->dev, &dsize); controls = vfio_ap_device_get_control_mask(anc->dev, &csize); if (!adapters || !domains || !controls) { fprintf(stderr, "Failed to read device config\n"); rc = -1; goto out; } /* * The 'ap_config' command takes a comma-delimited list of the 3 masks * combined. Each mask size includes a terminating character, two of * which will be replaced by commas and the final replaced by a * newline, which ap_config seems to require at the end of the input. * Add one to ensure room for a null termination. */ size = asize + dsize + csize + 1; attr = util_zalloc(size); /* Use the 3 masks to generate a 'ap_config' command */ rc = snprintf(attr, size, "%s,%s,%s\n", adapters, domains, controls); if (rc < size - 1) { fprintf(stderr, "Error creating ap_config command\n"); rc = -1; goto out; } /* Apply the new configuration to the active device */ path = path_get_vfio_ap_attr(anc->uuid, "ap_config"); f = fopen(path, "w"); if (!f) { fprintf(stderr, "Error opening ap_config\n"); rc = -1; goto out; } rc = fputs(attr, f); fclose(f); if (rc == EOF) fprintf(stderr, "Error writing to ap_config\n"); else rc = 0; out: if (!adapters) free(adapters); if (!domains) free(domains); if (!controls) free(controls); if (!path) free(path); if (!attr) free(attr); return rc; } /* * Determine if defining the specified device is a valid operation. * mdevctl can reach us for a DEFINE under the following circumstances: * 1) the device does not exist * 2) the device is active but does not have a config file, so this action * would be to generate a config file based upon the active device. * DEFINE has no effect on an active device (if one exists) it only creates * the configuration file. The config file might be empty or may have various * attributes if being fed by --jsonfile or an active device. */ static int ap_check_handle_define(struct ap_check_anchor *anc) { char *path = path_get_vfio_ap_mdev_config(anc->uuid); if (util_path_is_readable(path)) { fprintf(stderr, "Config already exists\n"); free(path); return -1; } free(path); return ap_check_changes(anc); } /* * Determine if modifying the specified device is a valid operation. * mdevctl can reach us for a MODIFY under the following circumstances: * 1) Modifying a MANUAL device * 2) Modifying an AUTO device * In the case of MANUAL, we don't take any action because changes made via * MODIFY don't take affect on the active mdev until a STOP/START cycle. * In the case of AUTO, we must compare the contents of the proposed device * with the contents of stashed AUTO mdev configurations + the system. */ static int ap_check_handle_modify(struct ap_check_anchor *anc) { char *path = path_get_vfio_ap_mdev_config(anc->uuid); FILE *fd = fopen(path, "r"); /* Determine if a base config file already exists for UUID */ free(path); if (fd == NULL) { fprintf(stderr, "Config doesn't exist\n"); return -1; } fclose(fd); return ap_check_changes(anc); } /* * Determine if modifying the active device is a valid operation. * This is similar to STARTing a device, in that the requested modifications * cannot conflict with the active configuration. LIVE MODIFY can only be * handled if the ap_config attribute is available in the vfio-ap driver. */ static int ap_check_handle_live_modify(struct ap_check_anchor *anc) { int rc; rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); return -1; } anc->cleanup_lock = true; if (vfio_ap_read_device_config(NULL, anc->dev) != 0) { fprintf(stderr, "Failed to read device config\n"); return -1; } if (strcmp(anc->dev->type, anc->type) != 0) { fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); return -1; } if (!vfio_ap_need_dynamic_config(anc->dev)) { fprintf(stderr, "vfio-ap module does not support ap_config for live modification"); return -1; } /* Check if the new configuration would cause conflicts */ rc = ap_check_active(anc); if (rc) return rc; /* Attempt to perform the dynamic configuration */ rc = ap_do_dynamic_config(anc); return rc; } /* * Determine if starting the specified device is a valid operation. * mdevctl can reach us for a START under the following circumstances: * 1) STARTing a MANUAL device * 1a) Where the MANUAL device is defined (has a config file) * 1b) Where the MANUAL device is NOT defined (no config file). * For vfio-ap this case provides an mdev with no adapters/domains. * 1c) Where the MANUAL device is NOT defined but a full configuration is * provided via --jsonfile * 2) STARTing an AUTO device * 2a) Where the AUTO device is defined (has a config file) * 2b) Where the AUTO device is NOT defined but a full configuration is * provided via --jsonfile * In each case, we must compare the proposed device with the contents of * active mdevs + the system. */ static int ap_check_handle_start(struct ap_check_anchor *anc) { int rc = 0; /* Can only start a device if vfio_ap is built-in or loaded */ if (!util_path_is_dir(VFIO_AP_PATH)) { fprintf(stderr, "vfio_ap module is not loaded\n"); ap_check_exit(anc, EXIT_FAILURE); } rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); rc = -1; goto out; } anc->cleanup_lock = true; if (vfio_ap_read_device_config(NULL, anc->dev) != 0) { fprintf(stderr, "Failed to read device config\n"); rc = -1; goto out; } if (strcmp(anc->dev->type, anc->type) != 0) { fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); rc = -1; goto out; } rc = ap_check_active(anc); /* If successful, lock must remain held until post callout */ if (rc == 0) anc->cleanup_lock = false; out: return rc; } /* * Acquire the appropriate serialization so that the specified device can be * STOPped. */ static int ap_check_handle_stop(void) { int rc; rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); return -1; } /* The lock must remain held until post callout */ return 0; } /* * Determine if UNDEFINEing the specified device is a valid operation. * mdevctl can reach us for an UNDEFINE under the following circumstances: * 1) UNDEFINEing an active device * 2) UNDEFINEing an inactive device * UNDEFINE has no effect on the active device, it only removes the config * file. */ static int ap_check_handle_undefine(struct ap_check_anchor *anc) { char *path = path_get_vfio_ap_mdev_config(anc->uuid); int rc = 0; rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); rc = -1; goto out; } anc->cleanup_lock = true; if (vfio_ap_read_device_config(path, anc->dev) != 0) { fprintf(stderr, "Failed to read device config\n"); rc = -1; goto out; } if (strcmp(anc->dev->type, anc->type) != 0) { fprintf(stderr, "Invalid mdev_type: %s\n", anc->dev->type); rc = -1; goto out; } /* Success: lock must remain held until post callout */ anc->cleanup_lock = false; out: free(path); return rc; } /* * For callouts where the "pre" callout would have acquired the lock, it is * now safe to remove the lock as all changes have been committed. */ static int ap_check_handle_post(void) { return ap_release_lock_callout(); } /* For the specified device, print the attributes to stdout in JSON format */ static int ap_check_handle_get_attributes(struct ap_check_anchor *anc) { struct vfio_ap_device *dev = anc->dev; struct vfio_ap_node *node; bool has_attr = false; char buf[80]; char *path; FILE *f; int rc; /* * For the get-attributes callout, we are typically called without the * callout lock held. However, there is a particular scenario (define * of an active mdev) where we may or may not be called with the lock * already held on behalf of mdevctl, depending on the mdevctl version. * Let's test for lock ownership first and, if already owned by the * parent (mdevctl) proceed rather than waiting on the file lock. */ rc = ap_try_lock_callout(); switch (rc) { case 0: /* Lock acquired */ anc->cleanup_lock = true; break; case 1: /* Lock held by parent -- trust the lock will remain held */ break; default: /* Lock not acquired or held by parent -- do a normal obtain */ rc = ap_get_lock_callout(); if (rc) { fprintf(stderr, "Failed to acquire configuration lock %d\n", rc); return -1; } anc->cleanup_lock = true; } /* * Read the 'matrix' and 'control_domains' attributes to get the * current attributes of the active device. If either of these sysfs * attributes is unreadable, assume the device is being destroyed * and return nothing. */ path = path_get_vfio_ap_attr(anc->uuid, "matrix"); f = fopen(path, "r"); free(path); if (!f) return 0; while (fgets(buf, sizeof(buf), f)) vfio_ap_parse_matrix(dev, buf); vfio_ap_sort_matrix_results(dev); fclose(f); path = path_get_vfio_ap_attr(anc->uuid, "control_domains"); f = fopen(path, "r"); free(path); if (!f) return 0; while (fgets(buf, sizeof(buf), f)) vfio_ap_parse_control(dev, buf); fclose(f); printf("[{"); if (!util_list_is_empty(dev->adapters)) { util_list_iterate(dev->adapters, node) { if (has_attr) printf("},{"); printf("\"assign_adapter\": \"%u\"", node->id); has_attr = true; } } if (!util_list_is_empty(dev->domains)) { util_list_iterate(dev->domains, node) { if (has_attr) printf("},{"); printf("\"assign_domain\": \"%u\"", node->id); has_attr = true; } } if (!util_list_is_empty(dev->controls)) { util_list_iterate(dev->controls, node) { if (has_attr) printf("},{"); printf("\"assign_control_domain\": \"%u\"", node->id); has_attr = true; } } printf("}]\n"); return 0; } /* * If the target 'attr' is in the 's' array, add it to the 't' array. */ static void json_add_attr(json_object *t, json_object *s, const char *attr) { size_t vlen, alen = strlen(attr); const char *val; json_object *o; int i, num; num = json_object_array_length(s); for (i = 0; i < num; i++) { o = json_object_array_get_idx(s, i); val = json_object_get_string(o); vlen = strlen(val); if (alen == vlen && strncasecmp(attr, val, alen) == 0) { json_object_array_add(t, json_object_new_string(val)); return; } } } /* * Generate a JSON-formatted list of capability information that this script * supports and return it to the caller via stdout. An example of what the * output should look like (without the newlines): * { * "supports": { * "version": 2, * "actions": ["define", * "modify", * "start", * "stop", * "undefine", * "attributes", * "capabilities"], * "events": ["pre", * "post", * "get", * "live"] * } * } */ static int ap_check_handle_get_capabilities(void) { json_object *root, *csup, *cver, *cact, *cev, *cap, *caps, *o; int i, rc = 0; root = json_object_from_fd(STDIN_FILENO); if (!root) { fprintf(stderr, "No capabilities provided\n"); return -1; } if (!json_object_object_get_ex(root, "provides", &csup)) { fprintf(stderr, "No supported capabilities provided\n"); rc = -1; goto out; } if (!json_object_object_get_ex(csup, "version", &cver)) { fprintf(stderr, "No version provided in capabilities\n"); rc = -1; goto out; } if (!json_object_object_get_ex(csup, "actions", &cact)) { fprintf(stderr, "No actions provided in capabilities\n"); rc = -1; goto out; } if (!json_object_object_get_ex(csup, "events", &cev)) { fprintf(stderr, "No events provided in capabilities\n"); rc = -1; goto out; } /* * Advertise the subset of supported capabilities from the list * provided on stdin. */ cap = json_object_new_object(); caps = json_object_new_object(); json_object_object_add(cap, "supports", caps); /* * Currently we always advertise a fixed version, but we may need to * revisit this if we increase MDEVCTL_CAP_VERSION in the future (e.g. * how to handle ap-check having a greater supported version than * what mdevctl reports) */ o = json_object_new_int(MDEVCTL_CAP_VERSION); json_object_object_add(caps, "version", o); o = json_object_new_array(); for (i = 0; i < NUM_MDEVCTL_ACTIONS; i++) json_add_attr(o, cact, mdevctl_action_table[i].action); json_object_object_add(caps, "actions", o); o = json_object_new_array(); for (i = 0; i < NUM_MDEVCTL_EVENTS; i++) json_add_attr(o, cev, mdevctl_event_table[i].event); json_object_object_add(caps, "events", o); /* Return supported capabilities JSON on stdout */ printf("%s\n", json_object_to_json_string(cap)); json_object_put(cap); out: json_object_put(root); return rc; } /* * Determine which mdevctl action is being checked and handle accordingly. */ static int ap_check_handle_action(struct ap_check_anchor *anc) { int rc = 0; switch (anc->event) { case MDEVCTL_EVENT_PRE: switch (anc->action) { case MDEVCTL_ACTION_DEFINE: rc = ap_check_handle_define(anc); break; case MDEVCTL_ACTION_MODIFY: rc = ap_check_handle_modify(anc); break; case MDEVCTL_ACTION_START: rc = ap_check_handle_start(anc); break; case MDEVCTL_ACTION_STOP: rc = ap_check_handle_stop(); break; case MDEVCTL_ACTION_UNDEFINE: rc = ap_check_handle_undefine(anc); break; default: /* Ignore some actions including unknown ones */ break; } break; case MDEVCTL_EVENT_POST: switch (anc->action) { case MDEVCTL_ACTION_DEFINE: case MDEVCTL_ACTION_MODIFY: case MDEVCTL_ACTION_START: case MDEVCTL_ACTION_STOP: case MDEVCTL_ACTION_UNDEFINE: ap_check_handle_post(); break; default: /* Ignore other post events */ break; } break; case MDEVCTL_EVENT_GET: switch (anc->action) { case MDEVCTL_ACTION_ATTRIBUTES: rc = ap_check_handle_get_attributes(anc); break; case MDEVCTL_ACTION_CAPABILITIES: rc = ap_check_handle_get_capabilities(); break; default: /* Ignore some actions including unknown ones */ break; } break; case MDEVCTL_EVENT_LIVE: switch (anc->action) { case MDEVCTL_ACTION_MODIFY: rc = ap_check_handle_live_modify(anc); break; default: /* Ignore some actions including unknown ones */ break; } break; default: /* Ignore any unknown events */ break; } return rc; } /* * */ int main(int argc, char *argv[]) { struct ap_check_anchor anchor; int rc; ap_check_init(&anchor); ap_check_parse(&anchor, argc, argv); rc = ap_check_handle_action(&anchor); ap_check_exit(&anchor, rc); }