mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Add basic checking when reading udev rules and print a warning when an
invalid udev rule file is found. This addresses the current inconsistent
behavior:
- CCW devices: invalid udev rules are considered valid
- CCW group devices: invalid udev rules are silently ignored
- zFCP LUNs: invalid rules cause chzdev/lszdev to silently terminate
with non-zero exit code
Checks include a test for empty or truncated rule files that might be
the result of an interrupted chzdev operation, or a file system or I/O
error.
Note: The recommended way to correct invalid udev rules is to either
remove the offending rules file, or to repeat the associated persistent
configuration step.
Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Reported-by: Steffen Maier <maier@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
459 lines
8.8 KiB
C
459 lines
8.8 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 "misc.h"
|
|
#include "path.h"
|
|
#include "setting.h"
|
|
#include "udev.h"
|
|
|
|
int udev_need_settle = 0;
|
|
int udev_no_settle;
|
|
|
|
/* Create a newly allocated udev entry. */
|
|
static struct udev_entry_node *udev_entry_node_new(const char *key,
|
|
const char *op,
|
|
const char *value)
|
|
{
|
|
struct udev_entry_node *entry;
|
|
|
|
entry = misc_malloc(sizeof(struct udev_entry_node));
|
|
entry->key = misc_strdup(key);
|
|
entry->op = misc_strdup(op);
|
|
entry->value = misc_strdup(value);
|
|
|
|
return entry;
|
|
}
|
|
|
|
/* Release resources associated with udev entry. */
|
|
static void udev_entry_node_free(struct udev_entry_node *entry)
|
|
{
|
|
if (!entry)
|
|
return;
|
|
free(entry->key);
|
|
free(entry->op);
|
|
free(entry->value);
|
|
free(entry);
|
|
}
|
|
|
|
/* Create a newly allocated udev line. */
|
|
static struct udev_line_node *udev_line_node_new(void)
|
|
{
|
|
struct udev_line_node *line;
|
|
|
|
line = misc_malloc(sizeof(struct udev_line_node));
|
|
util_list_init(&line->entries, struct udev_entry_node, node);
|
|
|
|
return line;
|
|
}
|
|
|
|
/* Release resources associated with udev line. */
|
|
static void udev_line_node_free(struct udev_line_node *line)
|
|
{
|
|
struct udev_entry_node *e, *n;
|
|
|
|
if (!line)
|
|
return;
|
|
util_list_iterate_safe(&line->entries, e, n) {
|
|
util_list_remove(&line->entries, e);
|
|
udev_entry_node_free(e);
|
|
}
|
|
free(line->line);
|
|
free(line);
|
|
}
|
|
|
|
/* Create a newly allocated udev file. */
|
|
static struct udev_file *udev_file_new(void)
|
|
{
|
|
struct udev_file *file;
|
|
|
|
file = misc_malloc(sizeof(struct udev_file));
|
|
util_list_init(&file->lines, struct udev_line_node, node);
|
|
|
|
return file;
|
|
}
|
|
|
|
/* Release resources associated with udev file. */
|
|
void udev_free_file(struct udev_file *file)
|
|
{
|
|
struct udev_line_node *l, *n;
|
|
|
|
if (!file)
|
|
return;
|
|
util_list_iterate_safe(&file->lines, l, n) {
|
|
util_list_remove(&file->lines, l);
|
|
udev_line_node_free(l);
|
|
}
|
|
free(file);
|
|
}
|
|
|
|
/* Used for debugging. */
|
|
void udev_file_print(struct udev_file *file)
|
|
{
|
|
struct udev_line_node *l;
|
|
struct udev_entry_node *e;
|
|
|
|
printf("udev_file at %p\n", (void *) file);
|
|
if (!file)
|
|
return;
|
|
util_list_iterate(&file->lines, l) {
|
|
printf(" udev_line_node at %p\n", (void *) l);
|
|
printf(" line='%s'\n", l->line);
|
|
util_list_iterate(&l->entries, e) {
|
|
printf(" udev_entry at %p\n", (void *) e);
|
|
printf(" '%s' '%s' '%s'\n", e->key, e->op,
|
|
e->value);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void skip_whitespace(const char **s_ptr)
|
|
{
|
|
const char *s = *s_ptr;
|
|
|
|
while (*s && isspace(*s))
|
|
s++;
|
|
|
|
*s_ptr = s;
|
|
}
|
|
|
|
static char *parse_key(const char **s_ptr)
|
|
{
|
|
const char *s, *e;
|
|
char *key;
|
|
|
|
s = *s_ptr;
|
|
/* Parse \w+(\{[^\}]*\})? */
|
|
e = s;
|
|
while (*e && (isalnum(*e) || *e == '_'))
|
|
e++;
|
|
if (*e == '{') {
|
|
while (*e && *e != '}')
|
|
e++;
|
|
if (*e == '}')
|
|
e++;
|
|
}
|
|
|
|
if (e == s)
|
|
return NULL;
|
|
|
|
/* s points to key start, e to character after key end. */
|
|
key = misc_malloc(e - s + 1);
|
|
memcpy(key, s, e - s);
|
|
|
|
*s_ptr = e;
|
|
|
|
return key;
|
|
}
|
|
|
|
static char *parse_op(const char **s_ptr)
|
|
{
|
|
const char *ops[] = { "==", "!=", "=", "+=", ":=", NULL };
|
|
const char *entry;
|
|
size_t len;
|
|
int i;
|
|
|
|
entry = *s_ptr;
|
|
for (i = 0; ops[i]; i++) {
|
|
len = strlen(ops[i]);
|
|
if (strncmp(entry, ops[i], len) == 0) {
|
|
*s_ptr += len;
|
|
return misc_strdup(ops[i]);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *parse_value(const char **s_ptr)
|
|
{
|
|
const char *s, *e;
|
|
char *value;
|
|
|
|
/* Parse: ^\s*(.*)\s*$ */
|
|
s = *s_ptr;
|
|
skip_whitespace(&s);
|
|
e = s;
|
|
while (*e)
|
|
e++;
|
|
e--;
|
|
while (e > s && isspace(*e))
|
|
e--;
|
|
e++;
|
|
|
|
*s_ptr = e;
|
|
|
|
/* Remove quotes. */
|
|
if ((*s == '"' && *(e - 1) == '"') ||
|
|
(*s == '\'' && *(e - 1) == '\'')) {
|
|
s++;
|
|
e--;
|
|
}
|
|
|
|
/* s points to value start, e to character after value end. */
|
|
value = misc_malloc(e - s + 1);
|
|
memcpy(value, s, e - s);
|
|
|
|
return value;
|
|
}
|
|
|
|
static bool parse_udev_entry(struct udev_line_node *line, const char *entry)
|
|
{
|
|
char *key = NULL, *op = NULL, *value = NULL;
|
|
struct udev_entry_node *e;
|
|
bool rc = false;
|
|
|
|
/* Parse: ^\s*(\w+)\s*(==|!=|=|\+=|:=)\s*"?([^"]*)"\s*$ */
|
|
|
|
/* Parse key. */
|
|
skip_whitespace(&entry);
|
|
key = parse_key(&entry);
|
|
if (!key)
|
|
goto out;
|
|
|
|
/* Parse operator. */
|
|
skip_whitespace(&entry);
|
|
op = parse_op(&entry);
|
|
if (!op)
|
|
goto out;
|
|
|
|
/* Parse value. */
|
|
skip_whitespace(&entry);
|
|
value = parse_value(&entry);
|
|
if (!value)
|
|
goto out;
|
|
skip_whitespace(&entry);
|
|
|
|
/* Check for unrecognized characters at end of entry. */
|
|
if (*entry != 0)
|
|
goto out;
|
|
|
|
/* Add entry to list. */
|
|
e = udev_entry_node_new(key, op, value);
|
|
util_list_add_tail(&line->entries, e);
|
|
rc = true;
|
|
|
|
out:
|
|
free(key);
|
|
free(op);
|
|
free(value);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void replace_unquoted(char *s, char from, char to)
|
|
{
|
|
char quoted = 0;
|
|
|
|
for (; *s; s++) {
|
|
if (quoted) {
|
|
/* Skip until quote end is found. */
|
|
if (*s == quoted)
|
|
quoted = 0;
|
|
continue;
|
|
}
|
|
if (*s == '"' || *s == '\'') {
|
|
quoted = *s;
|
|
continue;
|
|
}
|
|
if (*s == from)
|
|
*s = to;
|
|
}
|
|
}
|
|
|
|
static bool parse_udev_line(struct udev_file *file, const char *line)
|
|
{
|
|
char *copy, *curr, *next;
|
|
struct udev_line_node *l;
|
|
int i;
|
|
bool result = true;
|
|
|
|
l = udev_line_node_new();
|
|
l->line = misc_strdup(line);
|
|
|
|
/* Check for empty lines and comment lines. */
|
|
for (i = 0; line[i] && isspace(line[i]); i++);
|
|
if (line[i] == 0 || line[i] == '#')
|
|
goto ok;
|
|
|
|
/* Parse each comma-separated entry. */
|
|
copy = misc_strdup(line);
|
|
|
|
/* A hack to differentiate between quoted and unquoted commas. */
|
|
replace_unquoted(copy, ',', 1);
|
|
|
|
next = copy;
|
|
while ((curr = strsep(&next, "\1"))) {
|
|
if (!parse_udev_entry(l, curr)) {
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
free(copy);
|
|
|
|
ok:
|
|
if (result)
|
|
util_list_add_tail(&file->lines, l);
|
|
else
|
|
udev_line_node_free(l);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Read the contents of a udev rule file. */
|
|
exit_code_t udev_read_file(const char *path, struct udev_file **file_ptr)
|
|
{
|
|
char *text, *curr, *next;
|
|
struct udev_file *file;
|
|
int once = 0;
|
|
|
|
text = misc_read_text_file(path, 0, err_print);
|
|
if (!text)
|
|
return EXIT_RUNTIME_ERROR;
|
|
file = udev_file_new();
|
|
|
|
/* Iterate over each line. */
|
|
next = text;
|
|
while ((curr = strsep(&next, "\n"))) {
|
|
if (parse_udev_line(file, curr))
|
|
continue;
|
|
if (!once) {
|
|
once = 1;
|
|
verb("Unrecognized udev rule format in %s:\n", path);
|
|
}
|
|
verb("%s\n", curr);
|
|
}
|
|
|
|
free(text);
|
|
*file_ptr = file;
|
|
|
|
return EXIT_OK;
|
|
}
|
|
|
|
/* Check if a udev file does not contain any statements. */
|
|
bool udev_file_is_empty(struct udev_file *file)
|
|
{
|
|
struct udev_line_node *l;
|
|
|
|
if (!file)
|
|
return true;
|
|
util_list_iterate(&file->lines, l) {
|
|
if (l->line[0])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool get_ids_cb(const char *filename, void *data)
|
|
{
|
|
char *prefix = data;
|
|
|
|
if (strncmp(filename, prefix, strlen(prefix)) != 0)
|
|
return false;
|
|
if (!ends_with(filename, UDEV_SUFFIX))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Add the IDs for all devices of the specified subtype name for which a
|
|
* udev rule exists to strlist LIST. */
|
|
void udev_get_device_ids(const char *type, struct util_list *list,
|
|
bool autoconf)
|
|
{
|
|
char *path, *prefix;
|
|
struct util_list *files;
|
|
struct strlist_node *s;
|
|
size_t plen, len;
|
|
|
|
prefix = misc_asprintf("%s-%s-", UDEV_PREFIX, type);
|
|
plen = strlen(prefix);
|
|
path = path_get_udev_rules(autoconf);
|
|
files = strlist_new();
|
|
if (!misc_read_dir(path, files, get_ids_cb, prefix))
|
|
goto out;
|
|
|
|
util_list_iterate(files, s) {
|
|
/* 41-dasd-eckd-0.0.1234.rules */
|
|
len = strlen(s->str);
|
|
s->str[len - sizeof(UDEV_SUFFIX) + 1] = 0;
|
|
strlist_add(list, &s->str[plen]);
|
|
}
|
|
|
|
out:
|
|
strlist_free(files);
|
|
free(path);
|
|
free(prefix);
|
|
}
|
|
|
|
/* Remove UDEV rule for device. */
|
|
exit_code_t udev_remove_rule(const char *type, const char *id, bool autoconf)
|
|
{
|
|
char *path;
|
|
exit_code_t rc = EXIT_OK;
|
|
|
|
path = path_get_udev_rule(type, id, autoconf);
|
|
if (util_path_is_reg_file(path))
|
|
rc = remove_file(path);
|
|
free(path);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Wait for all current udev events to finish. */
|
|
void udev_settle(void)
|
|
{
|
|
if (udev_no_settle)
|
|
return;
|
|
misc_system(err_ignore, "%s settle", PATH_UDEVADM);
|
|
}
|
|
|
|
/* Extract internal attribute settings from @entry and add to @list.
|
|
* Associate corresponding attribute if found in @attribs. */
|
|
void udev_add_internal_from_entry(struct setting_list *list,
|
|
struct udev_entry_node *entry,
|
|
struct attrib **attribs)
|
|
{
|
|
char *copy, *name, *end, *u;
|
|
struct attrib *a;
|
|
|
|
/* ENV{zdev_var}="1" */
|
|
copy = misc_strdup(entry->key);
|
|
|
|
/* Find attribute name start. */
|
|
name = strchr(copy, '{');
|
|
end = strrchr(copy, '}');
|
|
if (!name || !end)
|
|
goto out;
|
|
*end = 0;
|
|
name++;
|
|
|
|
/* zdev_ => zdev: */
|
|
u = strchr(name, '_');
|
|
if (u)
|
|
*u = ':';
|
|
|
|
a = attrib_find(attribs, name);
|
|
setting_list_apply_actual(list, a, name, entry->value);
|
|
|
|
out:
|
|
free(copy);
|
|
}
|