Files
s390-tools/ap_tools/ap-check.c
Matthew Rosato 5806c4e1f4 ap_tools/ap-check: implement get capabilities callout
Add a new action type 'capabilities' for the 'get' event.  This
will return a JSON list of events and actions that the callout
script recognizes and actually does something with.  It omits
events like 'notify' and actions like 'list' that ap-check
currently ignores.

Reviewed-by: Boris Fiuczynski <fiuczy@linux.ibm.com>
Signed-off-by: Matthew Rosato <mjrosato@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2024-08-01 14:26:06 +02:00

1228 lines
30 KiB
C

/*
* 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 <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <json-c/json.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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, "AQPN %u.%u is not defined for "
"vfio_ap-passthrough use by the persistent "
"ap bus mask settings\n", a, d);
} else {
fprintf(stderr, "AQPN %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
* AQPNs 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);
}