/* * 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 #include #include #include #include #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; }