Files
s390-tools/zdev/src/udev_ccw.c
Dimitri John Ledkov 07b11982e3 zdev: Trigger generic_ccw devices on any kernel module loads.
Generic CCW device can use any driver, and the value of the driver is
not known ahead of time. To avoid the race between loading and binding
a kernel module, and devices added - retrigger generic-ccw devices on
any kernel module load.

Fixes: https://github.com/ibm-s390-tools/s390-tools/issues/37
Link: https://bugs.launchpad.net/ubuntu-z-systems/+bug/1794308
Closes: https://github.com/ibm-s390-tools/s390-tools/pull/45

Signed-off-by: Dimitri John Ledkov <xnox@ubuntu.com>
Acked-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
2018-10-19 15:32:40 +02:00

311 lines
7.1 KiB
C

/*
* zdev - Modify and display the persistent configuration of devices
*
* Copyright IBM Corp. 2016, 2017
*
* s390-tools is free software; you can redistribute it and/or modify
* it under the terms of the MIT license. See LICENSE for details.
*/
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lib/util_path.h"
#include "attrib.h"
#include "ccw.h"
#include "device.h"
#include "internal.h"
#include "misc.h"
#include "path.h"
#include "setting.h"
#include "udev.h"
#include "udev_ccw.h"
/* Check if a udev rule for the specified ccw device exists. */
bool udev_ccw_exists(const char *type, const char *id, bool autoconf)
{
char *path, *normid;
bool rc;
normid = ccw_normalize_id(id);
if (!normid)
return false;
path = path_get_udev_rule(type, normid, autoconf);
rc = util_path_is_reg_file(path);
free(path);
free(normid);
return rc;
}
static void add_setting_from_entry(struct setting_list *list,
struct udev_entry_node *entry,
struct attrib **attribs)
{
char *copy, *name, *end;
struct attrib *a;
/* ENV{zdev_var}="1" */
if (starts_with(entry->key, "ENV{zdev_") &&
strcmp(entry->op, "=") == 0) {
udev_add_internal_from_entry(list, entry, attribs);
return;
}
/* ATTR{[ccw/0.0.37bf]online}=1 */
if (strncmp(entry->key, "ATTR{[ccw/", 10) != 0 ||
strcmp(entry->op, "=") != 0)
return;
copy = misc_strdup(entry->key);
name = copy;
/* Find attribute name start. */
name = strchr(entry->key, ']');
end = strrchr(entry->key, '}');
if (!name || !end)
goto out;
*end = 0;
name++;
a = attrib_find(attribs, name);
setting_list_apply_actual(list, a, name, entry->value);
out:
free(copy);
}
/* Extract CCW device settings from a CCW device udev rule file. */
static void udev_file_get_settings(struct udev_file *file,
struct attrib **attribs,
struct setting_list *list)
{
struct udev_line_node *line;
struct udev_entry_node *entry;
util_list_iterate(&file->lines, line) {
entry = util_list_start(&line->entries);
if (!entry)
continue;
add_setting_from_entry(list, entry, attribs);
}
}
/* Read the persistent configuration of a CCW device from a udev rule. */
exit_code_t udev_ccw_read_device(struct device *dev, bool autoconf)
{
struct subtype *st = dev->subtype;
struct device_state *state = autoconf ? &dev->autoconf :
&dev->persistent;
struct udev_file *file = NULL;
exit_code_t rc;
char *path;
path = path_get_udev_rule(st->name, dev->id, autoconf);
rc = udev_read_file(path, &file);
if (rc)
goto out;
udev_file_get_settings(file, st->dev_attribs, state->settings);
state->exists = 1;
udev_free_file(file);
out:
free(path);
return rc;
}
/* Return an ID suitable for use as udev label. */
static char *get_label_id(const char *prefix, const char *type,
const char *dev_id)
{
char *id;
int i;
id = misc_asprintf("%s_%s_%s", prefix, type, dev_id);
for (i = 0; id[i]; i++) {
if (isalnum(id[i]) || id[i] == '_' || id[i] == '.')
continue;
id[i] = '_';
}
return id;
}
/* Write the persistent configuration of a CCW device to a udev rule. */
exit_code_t udev_ccw_write_device(struct device *dev, bool autoconf)
{
struct subtype *st = dev->subtype;
struct ccw_subtype_data *data = st->data;
const char *type = st->name, *drv = data->any_driver ? "*" : data->ccwdrv, *id = dev->id;
struct device_state *state = autoconf ? &dev->autoconf :
&dev->persistent;
char *path, *cfg_label = NULL, *end_label = NULL;
struct util_list *list;
struct ptrlist_node *p;
struct setting *s;
exit_code_t rc = EXIT_OK;
FILE *fd;
if (!state->exists)
return udev_remove_rule(type, id, autoconf);
cfg_label = get_label_id("cfg", type, id);
end_label = get_label_id("end", type, id);
/* Apply attributes in correct order. */
list = setting_list_get_sorted(state->settings);
path = path_get_udev_rule(type, id, autoconf);
debug("Writing %s udev rule file %s\n", type, path);
if (!util_path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
/* Write udev rule prolog. */
fprintf(fd, "# Generated by chzdev\n");
if (drv) {
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccw\", "
"KERNEL==\"%s\", DRIVER==\"%s\", GOTO=\"%s\"\n", id,
drv, cfg_label);
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"drivers\", "
"KERNEL==\"%s\", TEST==\"[ccw/%s]\", "
"GOTO=\"%s\"\n", drv, id, cfg_label);
} else {
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"ccw\", "
"KERNEL==\"%s\", GOTO=\"%s\"\n", id, cfg_label);
}
fprintf(fd, "GOTO=\"%s\"\n", end_label);
fprintf(fd, "\n");
fprintf(fd, "LABEL=\"%s\"\n", cfg_label);
/* Write settings. */
util_list_iterate(list, p) {
s = p->ptr;
if (s->removed)
continue;
if ((s->attrib && s->attrib->internal) ||
internal_by_name(s->name)) {
fprintf(fd, "ENV{zdev_%s}=\"%s\"\n",
internal_get_name(s->name), s->value);
} else {
fprintf(fd, "ATTR{[ccw/%s]%s}=\"%s\"\n", id, s->name,
s->value);
}
}
/* Write udev rule epilog. */
fprintf(fd, "\n");
fprintf(fd, "LABEL=\"%s\"\n", end_label);
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
ptrlist_free(list, 0);
free(end_label);
free(cfg_label);
free(path);
return rc;
}
#define MARKER "echo free "
static char *read_cio_ignore(const char *path)
{
char *text, *start, *end, *result = NULL;
text = misc_read_text_file(path, 0, err_ignore);
if (!text)
goto out;
start = strstr(text, MARKER);
if (!start)
goto out;
start += strlen(MARKER);
end = strchr(start, ' ');
if (!end)
goto out;
*end = 0;
result = misc_strdup(start);
out:
free(text);
return result;
}
/* Write a udev rule to free devices from the cio-ignore blacklist. */
exit_code_t udev_ccw_write_cio_ignore(const char *id_list, bool autoconf)
{
char *path, *prefix, *curr = NULL;
FILE *fd;
exit_code_t rc = EXIT_OK;
/* Ensure that autoconf version of cio-ignore is not masked
* by normal one. */
prefix = autoconf ? "cio-ignore-autoconf" : "cio-ignore";
/* Create file. */
path = path_get_udev_rule(prefix, NULL, autoconf);
if (!*id_list) {
/* Empty id_list string - remove file. */
if (!util_path_is_reg_file(path)) {
/* Already removed. */
goto out;
}
rc = remove_file(path);
goto out;
}
curr = read_cio_ignore(path);
if (curr && strcmp(curr, id_list) == 0)
goto out;
debug("Writing cio-ignore udev rule file %s\n", path);
if (!util_path_exists(path)) {
rc = path_create(path);
if (rc)
goto out;
}
fd = misc_fopen(path, "w");
if (!fd) {
error("Could not write to file %s: %s\n", path,
strerror(errno));
rc = EXIT_RUNTIME_ERROR;
goto out;
}
/* Write udev rule. */
fprintf(fd, "# Generated by chzdev\n");
fprintf(fd, "ACTION==\"add\", SUBSYSTEM==\"subsystem\", "
"KERNEL==\"ccw\", RUN{program}+=\"/bin/sh -c "
"'echo free %s > /proc/cio_ignore'\"\n", id_list);
/* Close file. */
if (misc_fclose(fd))
warn("Could not close file %s: %s\n", path, strerror(errno));
out:
free(curr);
free(path);
return rc;
}