From 7bd29679e6dd590bddfa18401749fad5c8f5d46b Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 10 May 2017 17:38:59 -0400 Subject: ./tools/notsd-move --- src/grp-udev/udevadm/GNUmakefile | 1 + src/grp-udev/udevadm/Makefile | 46 ++ src/grp-udev/udevadm/udevadm-control.c | 172 +++++++ src/grp-udev/udevadm/udevadm-hwdb.c | 698 +++++++++++++++++++++++++++ src/grp-udev/udevadm/udevadm-info.c | 481 ++++++++++++++++++ src/grp-udev/udevadm/udevadm-monitor.c | 281 +++++++++++ src/grp-udev/udevadm/udevadm-settle.c | 162 +++++++ src/grp-udev/udevadm/udevadm-test-builtin.c | 112 +++++ src/grp-udev/udevadm/udevadm-test.c | 160 ++++++ src/grp-udev/udevadm/udevadm-trigger.c | 287 +++++++++++ src/grp-udev/udevadm/udevadm-util.c | 51 ++ src/grp-udev/udevadm/udevadm-util.h | 24 + src/grp-udev/udevadm/udevadm.c | 137 ++++++ src/grp-udev/udevadm/udevadm.completion.bash | 97 ++++ src/grp-udev/udevadm/udevadm.completion.zsh | 141 ++++++ src/grp-udev/udevadm/udevadm.xml | 576 ++++++++++++++++++++++ 16 files changed, 3426 insertions(+) create mode 120000 src/grp-udev/udevadm/GNUmakefile create mode 100644 src/grp-udev/udevadm/Makefile create mode 100644 src/grp-udev/udevadm/udevadm-control.c create mode 100644 src/grp-udev/udevadm/udevadm-hwdb.c create mode 100644 src/grp-udev/udevadm/udevadm-info.c create mode 100644 src/grp-udev/udevadm/udevadm-monitor.c create mode 100644 src/grp-udev/udevadm/udevadm-settle.c create mode 100644 src/grp-udev/udevadm/udevadm-test-builtin.c create mode 100644 src/grp-udev/udevadm/udevadm-test.c create mode 100644 src/grp-udev/udevadm/udevadm-trigger.c create mode 100644 src/grp-udev/udevadm/udevadm-util.c create mode 100644 src/grp-udev/udevadm/udevadm-util.h create mode 100644 src/grp-udev/udevadm/udevadm.c create mode 100644 src/grp-udev/udevadm/udevadm.completion.bash create mode 100644 src/grp-udev/udevadm/udevadm.completion.zsh create mode 100644 src/grp-udev/udevadm/udevadm.xml (limited to 'src/grp-udev/udevadm') diff --git a/src/grp-udev/udevadm/GNUmakefile b/src/grp-udev/udevadm/GNUmakefile new file mode 120000 index 0000000000..95e5924740 --- /dev/null +++ b/src/grp-udev/udevadm/GNUmakefile @@ -0,0 +1 @@ +../../../GNUmakefile \ No newline at end of file diff --git a/src/grp-udev/udevadm/Makefile b/src/grp-udev/udevadm/Makefile new file mode 100644 index 0000000000..8f723e3a58 --- /dev/null +++ b/src/grp-udev/udevadm/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +rootbin_PROGRAMS += \ + udevadm + +udevadm_SOURCES = \ + src/udev/udevadm.c \ + src/udev/udevadm-info.c \ + src/udev/udevadm-control.c \ + src/udev/udevadm-monitor.c \ + src/udev/udevadm-hwdb.c \ + src/udev/udevadm-settle.c \ + src/udev/udevadm-trigger.c \ + src/udev/udevadm-test.c \ + src/udev/udevadm-test-builtin.c \ + src/udev/udevadm-util.c \ + src/udev/udevadm-util.h + +udevadm_LDADD = \ + libudev-core.la \ + libsystemd-basic.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/grp-udev/udevadm/udevadm-control.c b/src/grp-udev/udevadm/udevadm-control.c new file mode 100644 index 0000000000..415da614f4 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-control.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2005-2011 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/time-util.h" +#include "systemd-shared/udev-util.h" +#include "udev.h" + +static void print_help(void) { + printf("%s control COMMAND\n\n" + "Control the udev daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -e --exit Instruct the daemon to cleanup and exit\n" + " -l --log-priority=LEVEL Set the udev log level for the daemon\n" + " -s --stop-exec-queue Do not execute events, queue only\n" + " -S --start-exec-queue Execute events, flush queue\n" + " -R --reload Reload rules and databases\n" + " -p --property=KEY=VALUE Set a global property for all events\n" + " -m --children-max=N Maximum number of children\n" + " --timeout=SECONDS Maximum time to block for a reply\n" + , program_invocation_short_name); +} + +static int adm_control(struct udev *udev, int argc, char *argv[]) { + _cleanup_udev_ctrl_unref_ struct udev_ctrl *uctrl = NULL; + int timeout = 60; + int rc = 1, c; + + static const struct option options[] = { + { "exit", no_argument, NULL, 'e' }, + { "log-priority", required_argument, NULL, 'l' }, + { "stop-exec-queue", no_argument, NULL, 's' }, + { "start-exec-queue", no_argument, NULL, 'S' }, + { "reload", no_argument, NULL, 'R' }, + { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ + { "property", required_argument, NULL, 'p' }, + { "env", required_argument, NULL, 'p' }, /* alias for -p */ + { "children-max", required_argument, NULL, 'm' }, + { "timeout", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + if (getuid() != 0) { + log_error("root privileges required"); + return 1; + } + + uctrl = udev_ctrl_new(udev); + if (uctrl == NULL) + return 2; + + while ((c = getopt_long(argc, argv, "el:sSRp:m:h", options, NULL)) >= 0) + switch (c) { + case 'e': + if (udev_ctrl_send_exit(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'l': { + int i; + + i = util_log_priority(optarg); + if (i < 0) { + log_error("invalid number '%s'", optarg); + return rc; + } + if (udev_ctrl_send_set_log_level(uctrl, util_log_priority(optarg), timeout) < 0) + rc = 2; + else + rc = 0; + break; + } + case 's': + if (udev_ctrl_send_stop_exec_queue(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'S': + if (udev_ctrl_send_start_exec_queue(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'R': + if (udev_ctrl_send_reload(uctrl, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'p': + if (strchr(optarg, '=') == NULL) { + log_error("expect = instead of '%s'", optarg); + return rc; + } + if (udev_ctrl_send_set_env(uctrl, optarg, timeout) < 0) + rc = 2; + else + rc = 0; + break; + case 'm': { + char *endp; + int i; + + i = strtoul(optarg, &endp, 0); + if (endp[0] != '\0' || i < 1) { + log_error("invalid number '%s'", optarg); + return rc; + } + if (udev_ctrl_send_set_children_max(uctrl, i, timeout) < 0) + rc = 2; + else + rc = 0; + break; + } + case 't': { + usec_t s; + int seconds; + int r; + + r = parse_sec(optarg, &s); + if (r < 0) + return log_error_errno(r, "Failed to parse timeout value '%s'.", optarg); + + if (((s + USEC_PER_SEC - 1) / USEC_PER_SEC) > INT_MAX) + log_error("Timeout value is out of range."); + else { + seconds = s != USEC_INFINITY ? (int) ((s + USEC_PER_SEC - 1) / USEC_PER_SEC) : INT_MAX; + timeout = seconds; + rc = 0; + } + break; + } + case 'h': + print_help(); + rc = 0; + break; + } + + if (optind < argc) + log_error("Extraneous argument: %s", argv[optind]); + else if (optind == 1) + log_error("Option missing"); + return rc; +} + +const struct udevadm_cmd udevadm_control = { + .name = "control", + .cmd = adm_control, + .help = "Control the udev daemon", +}; diff --git a/src/grp-udev/udevadm/udevadm-hwdb.c b/src/grp-udev/udevadm/udevadm-hwdb.c new file mode 100644 index 0000000000..365f23fdff --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-hwdb.c @@ -0,0 +1,698 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include "sd-hwdb/hwdb-internal.h" +#include "sd-hwdb/hwdb-util.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/conf-files.h" +#include "systemd-basic/fileio.h" +#include "systemd-basic/fs-util.h" +#include "systemd-basic/label.h" +#include "systemd-basic/mkdir.h" +#include "systemd-basic/strbuf.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "udev.h" + +/* + * Generic udev properties, key/value database based on modalias strings. + * Uses a Patricia/radix trie to index all matches for efficient lookup. + */ + +static const char * const conf_file_dirs[] = { + "/etc/udev/hwdb.d", + UDEVLIBEXECDIR "/hwdb.d", + NULL +}; + +/* in-memory trie objects */ +struct trie { + struct trie_node *root; + struct strbuf *strings; + + size_t nodes_count; + size_t children_count; + size_t values_count; +}; + +struct trie_node { + /* prefix, common part for all children of this node */ + size_t prefix_off; + + /* sorted array of pointers to children nodes */ + struct trie_child_entry *children; + uint8_t children_count; + + /* sorted array of key/value pairs */ + struct trie_value_entry *values; + size_t values_count; +}; + +/* children array item with char (0-255) index */ +struct trie_child_entry { + uint8_t c; + struct trie_node *child; +}; + +/* value array item with key/value pairs */ +struct trie_value_entry { + size_t key_off; + size_t value_off; +}; + +static int trie_children_cmp(const void *v1, const void *v2) { + const struct trie_child_entry *n1 = v1; + const struct trie_child_entry *n2 = v2; + + return n1->c - n2->c; +} + +static int node_add_child(struct trie *trie, struct trie_node *node, struct trie_node *node_child, uint8_t c) { + struct trie_child_entry *child; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct trie_child_entry)); + if (!child) + return -ENOMEM; + + node->children = child; + trie->children_count++; + node->children[node->children_count].c = c; + node->children[node->children_count].child = node_child; + node->children_count++; + qsort(node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + trie->nodes_count++; + + return 0; +} + +static struct trie_node *node_lookup(const struct trie_node *node, uint8_t c) { + struct trie_child_entry *child; + struct trie_child_entry search; + + search.c = c; + child = bsearch(&search, node->children, node->children_count, sizeof(struct trie_child_entry), trie_children_cmp); + if (child) + return child->child; + return NULL; +} + +static void trie_node_cleanup(struct trie_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + trie_node_cleanup(node->children[i].child); + free(node->children); + free(node->values); + free(node); +} + +static int trie_values_cmp(const void *v1, const void *v2, void *arg) { + const struct trie_value_entry *val1 = v1; + const struct trie_value_entry *val2 = v2; + struct trie *trie = arg; + + return strcmp(trie->strings->buf + val1->key_off, + trie->strings->buf + val2->key_off); +} + +static int trie_node_add_value(struct trie *trie, struct trie_node *node, + const char *key, const char *value) { + ssize_t k, v; + struct trie_value_entry *val; + + k = strbuf_add_string(trie->strings, key, strlen(key)); + if (k < 0) + return k; + v = strbuf_add_string(trie->strings, value, strlen(value)); + if (v < 0) + return v; + + if (node->values_count) { + struct trie_value_entry search = { + .key_off = k, + .value_off = v, + }; + + val = xbsearch_r(&search, node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + if (val) { + /* replace existing earlier key with new value */ + val->value_off = v; + return 0; + } + } + + /* extend array, add new entry, sort for bisection */ + val = realloc(node->values, (node->values_count + 1) * sizeof(struct trie_value_entry)); + if (!val) + return -ENOMEM; + trie->values_count++; + node->values = val; + node->values[node->values_count].key_off = k; + node->values[node->values_count].value_off = v; + node->values_count++; + qsort_r(node->values, node->values_count, sizeof(struct trie_value_entry), trie_values_cmp, trie); + return 0; +} + +static int trie_insert(struct trie *trie, struct trie_node *node, const char *search, + const char *key, const char *value) { + size_t i = 0; + int err = 0; + + for (;;) { + size_t p; + uint8_t c; + struct trie_node *child; + + for (p = 0; (c = trie->strings->buf[node->prefix_off + p]); p++) { + _cleanup_free_ char *s = NULL; + ssize_t off; + _cleanup_free_ struct trie_node *new_child = NULL; + + if (c == search[i + p]) + continue; + + /* split node */ + new_child = new0(struct trie_node, 1); + if (!new_child) + return -ENOMEM; + + /* move values from parent to child */ + new_child->prefix_off = node->prefix_off + p+1; + new_child->children = node->children; + new_child->children_count = node->children_count; + new_child->values = node->values; + new_child->values_count = node->values_count; + + /* update parent; use strdup() because the source gets realloc()d */ + s = strndup(trie->strings->buf + node->prefix_off, p); + if (!s) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, s, p); + if (off < 0) + return off; + + node->prefix_off = off; + node->children = NULL; + node->children_count = 0; + node->values = NULL; + node->values_count = 0; + err = node_add_child(trie, node, new_child, c); + if (err) + return err; + + new_child = NULL; /* avoid cleanup */ + break; + } + i += p; + + c = search[i]; + if (c == '\0') + return trie_node_add_value(trie, node, key, value); + + child = node_lookup(node, c); + if (!child) { + ssize_t off; + + /* new child */ + child = new0(struct trie_node, 1); + if (!child) + return -ENOMEM; + + off = strbuf_add_string(trie->strings, search + i+1, strlen(search + i+1)); + if (off < 0) { + free(child); + return off; + } + + child->prefix_off = off; + err = node_add_child(trie, node, child, c); + if (err) { + free(child); + return err; + } + + return trie_node_add_value(trie, child, key, value); + } + + node = child; + i++; + } +} + +struct trie_f { + FILE *f; + struct trie *trie; + uint64_t strings_off; + + uint64_t nodes_count; + uint64_t children_count; + uint64_t values_count; +}; + +/* calculate the storage space for the nodes, children arrays, value arrays */ +static void trie_store_nodes_size(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + + for (i = 0; i < node->children_count; i++) + trie_store_nodes_size(trie, node->children[i].child); + + trie->strings_off += sizeof(struct trie_node_f); + for (i = 0; i < node->children_count; i++) + trie->strings_off += sizeof(struct trie_child_entry_f); + for (i = 0; i < node->values_count; i++) + trie->strings_off += sizeof(struct trie_value_entry_f); +} + +static int64_t trie_store_nodes(struct trie_f *trie, struct trie_node *node) { + uint64_t i; + struct trie_node_f n = { + .prefix_off = htole64(trie->strings_off + node->prefix_off), + .children_count = node->children_count, + .values_count = htole64(node->values_count), + }; + struct trie_child_entry_f *children = NULL; + int64_t node_off; + + if (node->children_count) { + children = new0(struct trie_child_entry_f, node->children_count); + if (!children) + return -ENOMEM; + } + + /* post-order recursion */ + for (i = 0; i < node->children_count; i++) { + int64_t child_off; + + child_off = trie_store_nodes(trie, node->children[i].child); + if (child_off < 0) { + free(children); + return child_off; + } + children[i].c = node->children[i].c; + children[i].child_off = htole64(child_off); + } + + /* write node */ + node_off = ftello(trie->f); + fwrite(&n, sizeof(struct trie_node_f), 1, trie->f); + trie->nodes_count++; + + /* append children array */ + if (node->children_count) { + fwrite(children, sizeof(struct trie_child_entry_f), node->children_count, trie->f); + trie->children_count += node->children_count; + free(children); + } + + /* append values array */ + for (i = 0; i < node->values_count; i++) { + struct trie_value_entry_f v = { + .key_off = htole64(trie->strings_off + node->values[i].key_off), + .value_off = htole64(trie->strings_off + node->values[i].value_off), + }; + + fwrite(&v, sizeof(struct trie_value_entry_f), 1, trie->f); + trie->values_count++; + } + + return node_off; +} + +static int trie_store(struct trie *trie, const char *filename) { + struct trie_f t = { + .trie = trie, + }; + _cleanup_free_ char *filename_tmp = NULL; + int64_t pos; + int64_t root_off; + int64_t size; + struct trie_header_f h = { + .signature = HWDB_SIG, + .tool_version = htole64(atoi(VERSION)), + .header_size = htole64(sizeof(struct trie_header_f)), + .node_size = htole64(sizeof(struct trie_node_f)), + .child_entry_size = htole64(sizeof(struct trie_child_entry_f)), + .value_entry_size = htole64(sizeof(struct trie_value_entry_f)), + }; + int err; + + /* calculate size of header, nodes, children entries, value entries */ + t.strings_off = sizeof(struct trie_header_f); + trie_store_nodes_size(&t, trie->root); + + err = fopen_temporary(filename , &t.f, &filename_tmp); + if (err < 0) + return err; + fchmod(fileno(t.f), 0444); + + /* write nodes */ + err = fseeko(t.f, sizeof(struct trie_header_f), SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + root_off = trie_store_nodes(&t, trie->root); + h.nodes_root_off = htole64(root_off); + pos = ftello(t.f); + h.nodes_len = htole64(pos - sizeof(struct trie_header_f)); + + /* write string buffer */ + fwrite(trie->strings->buf, trie->strings->len, 1, t.f); + h.strings_len = htole64(trie->strings->len); + + /* write header */ + size = ftello(t.f); + h.file_size = htole64(size); + err = fseeko(t.f, 0, SEEK_SET); + if (err < 0) { + fclose(t.f); + unlink_noerrno(filename_tmp); + return -errno; + } + fwrite(&h, sizeof(struct trie_header_f), 1, t.f); + err = ferror(t.f); + if (err) + err = -errno; + fclose(t.f); + if (err < 0 || rename(filename_tmp, filename) < 0) { + unlink_noerrno(filename_tmp); + return err < 0 ? err : -errno; + } + + log_debug("=== trie on-disk ==="); + log_debug("size: %8"PRIi64" bytes", size); + log_debug("header: %8zu bytes", sizeof(struct trie_header_f)); + log_debug("nodes: %8"PRIu64" bytes (%8"PRIu64")", + t.nodes_count * sizeof(struct trie_node_f), t.nodes_count); + log_debug("child pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.children_count * sizeof(struct trie_child_entry_f), t.children_count); + log_debug("value pointers: %8"PRIu64" bytes (%8"PRIu64")", + t.values_count * sizeof(struct trie_value_entry_f), t.values_count); + log_debug("string store: %8zu bytes", trie->strings->len); + log_debug("strings start: %8"PRIu64, t.strings_off); + + return 0; +} + +static int insert_data(struct trie *trie, struct udev_list *match_list, + char *line, const char *filename) { + char *value; + struct udev_list_entry *entry; + + value = strchr(line, '='); + if (!value) { + log_error("Error, key/value pair expected but got '%s' in '%s':", line, filename); + return -EINVAL; + } + + value[0] = '\0'; + value++; + + /* libudev requires properties to start with a space */ + while (isblank(line[0]) && isblank(line[1])) + line++; + + if (line[0] == '\0' || value[0] == '\0') { + log_error("Error, empty key or value '%s' in '%s':", line, filename); + return -EINVAL; + } + + udev_list_entry_foreach(entry, udev_list_get_entry(match_list)) + trie_insert(trie, trie->root, udev_list_entry_get_name(entry), line, value); + + return 0; +} + +static int import_file(struct udev *udev, struct trie *trie, const char *filename) { + enum { + HW_MATCH, + HW_DATA, + HW_NONE, + } state = HW_NONE; + FILE *f; + char line[LINE_MAX]; + struct udev_list match_list; + + udev_list_init(udev, &match_list, false); + + f = fopen(filename, "re"); + if (f == NULL) + return -errno; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *pos; + + /* comment line */ + if (line[0] == '#') + continue; + + /* strip trailing comment */ + pos = strchr(line, '#'); + if (pos) + pos[0] = '\0'; + + /* strip trailing whitespace */ + len = strlen(line); + while (len > 0 && isspace(line[len-1])) + len--; + line[len] = '\0'; + + switch (state) { + case HW_NONE: + if (len == 0) + break; + + if (line[0] == ' ') { + log_error("Error, MATCH expected but got '%s' in '%s':", line, filename); + break; + } + + /* start of record, first match */ + state = HW_MATCH; + udev_list_entry_add(&match_list, line, NULL); + break; + + case HW_MATCH: + if (len == 0) { + log_error("Error, DATA expected but got empty line in '%s':", filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + /* another match */ + if (line[0] != ' ') { + udev_list_entry_add(&match_list, line, NULL); + break; + } + + /* first data */ + state = HW_DATA; + insert_data(trie, &match_list, line, filename); + break; + + case HW_DATA: + /* end of record */ + if (len == 0) { + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + if (line[0] != ' ') { + log_error("Error, DATA expected but got '%s' in '%s':", line, filename); + state = HW_NONE; + udev_list_cleanup(&match_list); + break; + } + + insert_data(trie, &match_list, line, filename); + break; + }; + } + + fclose(f); + udev_list_cleanup(&match_list); + return 0; +} + +static void help(void) { + printf("Usage: udevadm hwdb OPTIONS\n" + " -u,--update update the hardware database\n" + " --usr generate in " UDEVLIBEXECDIR " instead of /etc/udev\n" + " -t,--test=MODALIAS query database and print result\n" + " -r,--root=PATH alternative root path in the filesystem\n" + " -h,--help\n\n"); +} + +static int adm_hwdb(struct udev *udev, int argc, char *argv[]) { + enum { + ARG_USR = 0x100, + }; + + static const struct option options[] = { + { "update", no_argument, NULL, 'u' }, + { "usr", no_argument, NULL, ARG_USR }, + { "test", required_argument, NULL, 't' }, + { "root", required_argument, NULL, 'r' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + const char *test = NULL; + const char *root = ""; + const char *hwdb_bin_dir = "/etc/udev"; + bool update = false; + struct trie *trie = NULL; + int err, c; + int rc = EXIT_SUCCESS; + + while ((c = getopt_long(argc, argv, "ut:r:h", options, NULL)) >= 0) + switch(c) { + case 'u': + update = true; + break; + case ARG_USR: + hwdb_bin_dir = UDEVLIBEXECDIR; + break; + case 't': + test = optarg; + break; + case 'r': + root = optarg; + break; + case 'h': + help(); + return EXIT_SUCCESS; + case '?': + return EXIT_FAILURE; + default: + assert_not_reached("Unknown option"); + } + + if (!update && !test) { + log_error("Either --update or --test must be used"); + return EXIT_FAILURE; + } + + if (update) { + char **files, **f; + _cleanup_free_ char *hwdb_bin = NULL; + + trie = new0(struct trie, 1); + if (!trie) { + rc = EXIT_FAILURE; + goto out; + } + + /* string store */ + trie->strings = strbuf_new(); + if (!trie->strings) { + rc = EXIT_FAILURE; + goto out; + } + + /* index */ + trie->root = new0(struct trie_node, 1); + if (!trie->root) { + rc = EXIT_FAILURE; + goto out; + } + trie->nodes_count++; + + err = conf_files_list_strv(&files, ".hwdb", root, conf_file_dirs); + if (err < 0) { + log_error_errno(err, "failed to enumerate hwdb files: %m"); + rc = EXIT_FAILURE; + goto out; + } + STRV_FOREACH(f, files) { + log_debug("reading file '%s'", *f); + import_file(udev, trie, *f); + } + strv_free(files); + + strbuf_complete(trie->strings); + + log_debug("=== trie in-memory ==="); + log_debug("nodes: %8zu bytes (%8zu)", + trie->nodes_count * sizeof(struct trie_node), trie->nodes_count); + log_debug("children arrays: %8zu bytes (%8zu)", + trie->children_count * sizeof(struct trie_child_entry), trie->children_count); + log_debug("values arrays: %8zu bytes (%8zu)", + trie->values_count * sizeof(struct trie_value_entry), trie->values_count); + log_debug("strings: %8zu bytes", + trie->strings->len); + log_debug("strings incoming: %8zu bytes (%8zu)", + trie->strings->in_len, trie->strings->in_count); + log_debug("strings dedup'ed: %8zu bytes (%8zu)", + trie->strings->dedup_len, trie->strings->dedup_count); + + hwdb_bin = strjoin(root, "/", hwdb_bin_dir, "/hwdb.bin", NULL); + if (!hwdb_bin) { + rc = EXIT_FAILURE; + goto out; + } + + mkdir_parents_label(hwdb_bin, 0755); + + err = trie_store(trie, hwdb_bin); + if (err < 0) { + log_error_errno(err, "Failure writing database %s: %m", hwdb_bin); + rc = EXIT_FAILURE; + } + + label_fix(hwdb_bin, false, false); + } + + if (test) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + int r; + + r = sd_hwdb_new(&hwdb); + if (r >= 0) { + const char *key, *value; + + SD_HWDB_FOREACH_PROPERTY(hwdb, test, key, value) + printf("%s=%s\n", key, value); + } + } +out: + if (trie) { + if (trie->root) + trie_node_cleanup(trie->root); + strbuf_cleanup(trie->strings); + free(trie); + } + return rc; +} + +const struct udevadm_cmd udevadm_hwdb = { + .name = "hwdb", + .cmd = adm_hwdb, +}; diff --git a/src/grp-udev/udevadm/udevadm-info.c b/src/grp-udev/udevadm/udevadm-info.c new file mode 100644 index 0000000000..38233de223 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-info.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2004-2009 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-shared/udev-util.h" +#include "udev.h" + +#include "udevadm-util.h" + +static bool skip_attribute(const char *name) { + static const char* const skip[] = { + "uevent", + "dev", + "modalias", + "resource", + "driver", + "subsystem", + "module", + }; + unsigned int i; + + for (i = 0; i < ELEMENTSOF(skip); i++) + if (streq(name, skip[i])) + return true; + return false; +} + +static void print_all_attributes(struct udev_device *device, const char *key) { + struct udev_list_entry *sysattr; + + udev_list_entry_foreach(sysattr, udev_device_get_sysattr_list_entry(device)) { + const char *name; + const char *value; + size_t len; + + name = udev_list_entry_get_name(sysattr); + if (skip_attribute(name)) + continue; + + value = udev_device_get_sysattr_value(device, name); + if (value == NULL) + continue; + + /* skip any values that look like a path */ + if (value[0] == '/') + continue; + + /* skip nonprintable attributes */ + len = strlen(value); + while (len > 0 && isprint(value[len-1])) + len--; + if (len > 0) + continue; + + printf(" %s{%s}==\"%s\"\n", key, name, value); + } + printf("\n"); +} + +static int print_device_chain(struct udev_device *device) { + struct udev_device *device_parent; + const char *str; + + printf("\n" + "Udevadm info starts with the device specified by the devpath and then\n" + "walks up the chain of parent devices. It prints for every device\n" + "found, all possible attributes in the udev rules key format.\n" + "A rule to match, can be composed by the attributes of the device\n" + "and the attributes from one single parent device.\n" + "\n"); + + printf(" looking at device '%s':\n", udev_device_get_devpath(device)); + printf(" KERNEL==\"%s\"\n", udev_device_get_sysname(device)); + str = udev_device_get_subsystem(device); + if (str == NULL) + str = ""; + printf(" SUBSYSTEM==\"%s\"\n", str); + str = udev_device_get_driver(device); + if (str == NULL) + str = ""; + printf(" DRIVER==\"%s\"\n", str); + print_all_attributes(device, "ATTR"); + + device_parent = device; + do { + device_parent = udev_device_get_parent(device_parent); + if (device_parent == NULL) + break; + printf(" looking at parent device '%s':\n", udev_device_get_devpath(device_parent)); + printf(" KERNELS==\"%s\"\n", udev_device_get_sysname(device_parent)); + str = udev_device_get_subsystem(device_parent); + if (str == NULL) + str = ""; + printf(" SUBSYSTEMS==\"%s\"\n", str); + str = udev_device_get_driver(device_parent); + if (str == NULL) + str = ""; + printf(" DRIVERS==\"%s\"\n", str); + print_all_attributes(device_parent, "ATTRS"); + } while (device_parent != NULL); + + return 0; +} + +static void print_record(struct udev_device *device) { + const char *str; + int i; + struct udev_list_entry *list_entry; + + printf("P: %s\n", udev_device_get_devpath(device)); + + str = udev_device_get_devnode(device); + if (str != NULL) + printf("N: %s\n", str + strlen("/dev/")); + + i = udev_device_get_devlink_priority(device); + if (i != 0) + printf("L: %i\n", i); + + udev_list_entry_foreach(list_entry, udev_device_get_devlinks_list_entry(device)) + printf("S: %s\n", udev_list_entry_get_name(list_entry) + strlen("/dev/")); + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) + printf("E: %s=%s\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + printf("\n"); +} + +static int stat_device(const char *name, bool export, const char *prefix) { + struct stat statbuf; + + if (stat(name, &statbuf) != 0) + return -errno; + + if (export) { + if (prefix == NULL) + prefix = "INFO_"; + printf("%sMAJOR=%u\n" + "%sMINOR=%u\n", + prefix, major(statbuf.st_dev), + prefix, minor(statbuf.st_dev)); + } else + printf("%u:%u\n", major(statbuf.st_dev), minor(statbuf.st_dev)); + return 0; +} + +static int export_devices(struct udev *udev) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *udev_enumerate; + struct udev_list_entry *list_entry; + + udev_enumerate = udev_enumerate_new(udev); + if (udev_enumerate == NULL) + return -ENOMEM; + + udev_enumerate_scan_devices(udev_enumerate); + udev_list_entry_foreach(list_entry, udev_enumerate_get_list_entry(udev_enumerate)) { + _cleanup_udev_device_unref_ struct udev_device *device; + + device = udev_device_new_from_syspath(udev, udev_list_entry_get_name(list_entry)); + if (device != NULL) + print_record(device); + } + + return 0; +} + +static void cleanup_dir(DIR *dir, mode_t mask, int depth) { + struct dirent *dent; + + if (depth <= 0) + return; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + struct stat stats; + + if (dent->d_name[0] == '.') + continue; + if (fstatat(dirfd(dir), dent->d_name, &stats, AT_SYMLINK_NOFOLLOW) != 0) + continue; + if ((stats.st_mode & mask) != 0) + continue; + if (S_ISDIR(stats.st_mode)) { + _cleanup_closedir_ DIR *dir2; + + dir2 = fdopendir(openat(dirfd(dir), dent->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC)); + if (dir2 != NULL) + cleanup_dir(dir2, mask, depth-1); + + (void) unlinkat(dirfd(dir), dent->d_name, AT_REMOVEDIR); + } else + (void) unlinkat(dirfd(dir), dent->d_name, 0); + } +} + +static void cleanup_db(struct udev *udev) { + _cleanup_closedir_ DIR *dir1 = NULL, *dir2 = NULL, *dir3 = NULL, *dir4 = NULL, *dir5 = NULL; + + (void) unlink("/run/udev/queue.bin"); + + dir1 = opendir("/run/udev/data"); + if (dir1 != NULL) + cleanup_dir(dir1, S_ISVTX, 1); + + dir2 = opendir("/run/udev/links"); + if (dir2 != NULL) + cleanup_dir(dir2, 0, 2); + + dir3 = opendir("/run/udev/tags"); + if (dir3 != NULL) + cleanup_dir(dir3, 0, 2); + + dir4 = opendir("/run/udev/static_node-tags"); + if (dir4 != NULL) + cleanup_dir(dir4, 0, 2); + + dir5 = opendir("/run/udev/watch"); + if (dir5 != NULL) + cleanup_dir(dir5, 0, 1); +} + +static void help(void) { + + printf("%s info [OPTIONS] [DEVPATH|FILE]\n\n" + "Query sysfs or the udev database.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n" + " -q --query=TYPE Query device information:\n" + " name Name of device node\n" + " symlink Pointing to node\n" + " path sysfs device path\n" + " property The device properties\n" + " all All values\n" + " -p --path=SYSPATH sysfs device path used for query or attribute walk\n" + " -n --name=NAME Node or symlink name used for query or attribute walk\n" + " -r --root Prepend dev directory to path names\n" + " -a --attribute-walk Print all key matches walking along the chain\n" + " of parent devices\n" + " -d --device-id-of-file=FILE Print major:minor of device containing this file\n" + " -x --export Export key/value pairs\n" + " -P --export-prefix Export the key name with a prefix\n" + " -e --export-db Export the content of the udev database\n" + " -c --cleanup-db Clean up the udev database\n" + , program_invocation_short_name); +} + +static int uinfo(struct udev *udev, int argc, char *argv[]) { + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + bool root = 0; + bool export = 0; + const char *export_prefix = NULL; + char name[UTIL_PATH_SIZE]; + struct udev_list_entry *list_entry; + int c; + + static const struct option options[] = { + { "name", required_argument, NULL, 'n' }, + { "path", required_argument, NULL, 'p' }, + { "query", required_argument, NULL, 'q' }, + { "attribute-walk", no_argument, NULL, 'a' }, + { "cleanup-db", no_argument, NULL, 'c' }, + { "export-db", no_argument, NULL, 'e' }, + { "root", no_argument, NULL, 'r' }, + { "device-id-of-file", required_argument, NULL, 'd' }, + { "export", no_argument, NULL, 'x' }, + { "export-prefix", required_argument, NULL, 'P' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + enum action_type { + ACTION_QUERY, + ACTION_ATTRIBUTE_WALK, + ACTION_DEVICE_ID_FILE, + } action = ACTION_QUERY; + + enum query_type { + QUERY_NAME, + QUERY_PATH, + QUERY_SYMLINK, + QUERY_PROPERTY, + QUERY_ALL, + } query = QUERY_ALL; + + while ((c = getopt_long(argc, argv, "aced:n:p:q:rxP:RVh", options, NULL)) >= 0) + switch (c) { + case 'n': { + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + return 2; + } + + device = find_device(udev, optarg, "/dev/"); + if (device == NULL) { + fprintf(stderr, "device node not found\n"); + return 2; + } + break; + } + case 'p': + if (device != NULL) { + fprintf(stderr, "device already specified\n"); + return 2; + } + + device = find_device(udev, optarg, "/sys"); + if (device == NULL) { + fprintf(stderr, "syspath not found\n"); + return 2; + } + break; + case 'q': + action = ACTION_QUERY; + if (streq(optarg, "property") || streq(optarg, "env")) + query = QUERY_PROPERTY; + else if (streq(optarg, "name")) + query = QUERY_NAME; + else if (streq(optarg, "symlink")) + query = QUERY_SYMLINK; + else if (streq(optarg, "path")) + query = QUERY_PATH; + else if (streq(optarg, "all")) + query = QUERY_ALL; + else { + fprintf(stderr, "unknown query type\n"); + return 3; + } + break; + case 'r': + root = true; + break; + case 'd': + action = ACTION_DEVICE_ID_FILE; + strscpy(name, sizeof(name), optarg); + break; + case 'a': + action = ACTION_ATTRIBUTE_WALK; + break; + case 'e': + if (export_devices(udev) < 0) + return 1; + return 0; + case 'c': + cleanup_db(udev); + return 0; + case 'x': + export = true; + break; + case 'P': + export_prefix = optarg; + break; + case 'V': + printf("%s\n", VERSION); + return 0; + case 'h': + help(); + return 0; + default: + return 1; + } + + switch (action) { + case ACTION_QUERY: + if (!device) { + if (!argv[optind]) { + help(); + return 2; + } + device = find_device(udev, argv[optind], NULL); + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + return 4; + } + } + + switch(query) { + case QUERY_NAME: { + const char *node = udev_device_get_devnode(device); + + if (node == NULL) { + fprintf(stderr, "no device node found\n"); + return 5; + } + + if (root) + printf("%s\n", udev_device_get_devnode(device)); + else + printf("%s\n", udev_device_get_devnode(device) + strlen("/dev/")); + break; + } + case QUERY_SYMLINK: + list_entry = udev_device_get_devlinks_list_entry(device); + while (list_entry != NULL) { + if (root) + printf("%s", udev_list_entry_get_name(list_entry)); + else + printf("%s", udev_list_entry_get_name(list_entry) + strlen("/dev/")); + list_entry = udev_list_entry_get_next(list_entry); + if (list_entry != NULL) + printf(" "); + } + printf("\n"); + break; + case QUERY_PATH: + printf("%s\n", udev_device_get_devpath(device)); + return 0; + case QUERY_PROPERTY: + list_entry = udev_device_get_properties_list_entry(device); + while (list_entry != NULL) { + if (export) + printf("%s%s='%s'\n", strempty(export_prefix), + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + else + printf("%s=%s\n", udev_list_entry_get_name(list_entry), udev_list_entry_get_value(list_entry)); + + list_entry = udev_list_entry_get_next(list_entry); + } + break; + case QUERY_ALL: + print_record(device); + break; + default: + assert_not_reached("unknown query type"); + } + break; + case ACTION_ATTRIBUTE_WALK: + if (!device && argv[optind]) { + device = find_device(udev, argv[optind], NULL); + if (!device) { + fprintf(stderr, "Unknown device, absolute path in /dev/ or /sys expected.\n"); + return 4; + } + } + if (!device) { + fprintf(stderr, "Unknown device, --name=, --path=, or absolute path in /dev/ or /sys expected.\n"); + return 4; + } + print_device_chain(device); + break; + case ACTION_DEVICE_ID_FILE: + if (stat_device(name, export, export_prefix) != 0) + return 1; + break; + } + + return 0; +} + +const struct udevadm_cmd udevadm_info = { + .name = "info", + .cmd = uinfo, + .help = "Query sysfs or the udev database", +}; diff --git a/src/grp-udev/udevadm/udevadm-monitor.c b/src/grp-udev/udevadm/udevadm-monitor.c new file mode 100644 index 0000000000..3503b19a21 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-monitor.c @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2004-2010 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/formats-util.h" +#include "systemd-shared/udev-util.h" +#include "udev.h" + +static bool udev_exit; + +static void sig_handler(int signum) { + if (signum == SIGINT || signum == SIGTERM) + udev_exit = true; +} + +static void print_device(struct udev_device *device, const char *source, int prop) { + struct timespec ts; + + assert_se(clock_gettime(CLOCK_MONOTONIC, &ts) == 0); + printf("%-6s[%"PRI_TIME".%06ld] %-8s %s (%s)\n", + source, + ts.tv_sec, ts.tv_nsec/1000, + udev_device_get_action(device), + udev_device_get_devpath(device), + udev_device_get_subsystem(device)); + if (prop) { + struct udev_list_entry *list_entry; + + udev_list_entry_foreach(list_entry, udev_device_get_properties_list_entry(device)) + printf("%s=%s\n", + udev_list_entry_get_name(list_entry), + udev_list_entry_get_value(list_entry)); + printf("\n"); + } +} + +static void help(void) { + printf("%s monitor [--property] [--kernel] [--udev] [--help]\n\n" + "Listen to kernel and udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --property Print the event properties\n" + " -k --kernel Print kernel uevents\n" + " -u --udev Print udev events\n" + " -s --subsystem-match=SUBSYSTEM[/DEVTYPE] Filter events by subsystem\n" + " -t --tag-match=TAG Filter events by tag\n" + , program_invocation_short_name); +} + +static int adm_monitor(struct udev *udev, int argc, char *argv[]) { + struct sigaction act = {}; + sigset_t mask; + bool prop = false; + bool print_kernel = false; + bool print_udev = false; + _cleanup_udev_list_cleanup_ struct udev_list subsystem_match_list; + _cleanup_udev_list_cleanup_ struct udev_list tag_match_list; + _cleanup_udev_monitor_unref_ struct udev_monitor *udev_monitor = NULL; + _cleanup_udev_monitor_unref_ struct udev_monitor *kernel_monitor = NULL; + _cleanup_close_ int fd_ep = -1; + int fd_kernel = -1, fd_udev = -1; + struct epoll_event ep_kernel, ep_udev; + int c; + + static const struct option options[] = { + { "property", no_argument, NULL, 'p' }, + { "environment", no_argument, NULL, 'e' }, /* alias for -p */ + { "kernel", no_argument, NULL, 'k' }, + { "udev", no_argument, NULL, 'u' }, + { "subsystem-match", required_argument, NULL, 's' }, + { "tag-match", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + udev_list_init(udev, &subsystem_match_list, true); + udev_list_init(udev, &tag_match_list, true); + + while ((c = getopt_long(argc, argv, "pekus:t:h", options, NULL)) >= 0) + switch (c) { + case 'p': + case 'e': + prop = true; + break; + case 'k': + print_kernel = true; + break; + case 'u': + print_udev = true; + break; + case 's': + { + char subsys[UTIL_NAME_SIZE]; + char *devtype; + + strscpy(subsys, sizeof(subsys), optarg); + devtype = strchr(subsys, '/'); + if (devtype != NULL) { + devtype[0] = '\0'; + devtype++; + } + udev_list_entry_add(&subsystem_match_list, subsys, devtype); + break; + } + case 't': + udev_list_entry_add(&tag_match_list, optarg, NULL); + break; + case 'h': + help(); + return 0; + default: + return 1; + } + + if (!print_kernel && !print_udev) { + print_kernel = true; + print_udev = true; + } + + /* set signal handlers */ + act.sa_handler = sig_handler; + act.sa_flags = SA_RESTART; + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigprocmask(SIG_UNBLOCK, &mask, NULL); + + /* Callers are expecting to see events as they happen: Line buffering */ + setlinebuf(stdout); + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + log_error_errno(errno, "error creating epoll fd: %m"); + return 1; + } + + printf("monitor will print the received events for:\n"); + if (print_udev) { + struct udev_list_entry *entry; + + udev_monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (udev_monitor == NULL) { + fprintf(stderr, "error: unable to create netlink socket\n"); + return 1; + } + udev_monitor_set_receive_buffer_size(udev_monitor, 128*1024*1024); + fd_udev = udev_monitor_get_fd(udev_monitor); + + udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { + const char *subsys = udev_list_entry_get_name(entry); + const char *devtype = udev_list_entry_get_value(entry); + + if (udev_monitor_filter_add_match_subsystem_devtype(udev_monitor, subsys, devtype) < 0) + fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys); + } + + udev_list_entry_foreach(entry, udev_list_get_entry(&tag_match_list)) { + const char *tag = udev_list_entry_get_name(entry); + + if (udev_monitor_filter_add_match_tag(udev_monitor, tag) < 0) + fprintf(stderr, "error: unable to apply tag filter '%s'\n", tag); + } + + if (udev_monitor_enable_receiving(udev_monitor) < 0) { + fprintf(stderr, "error: unable to subscribe to udev events\n"); + return 2; + } + + memzero(&ep_udev, sizeof(struct epoll_event)); + ep_udev.events = EPOLLIN; + ep_udev.data.fd = fd_udev; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_udev, &ep_udev) < 0) { + log_error_errno(errno, "fail to add fd to epoll: %m"); + return 2; + } + + printf("UDEV - the event which udev sends out after rule processing\n"); + } + + if (print_kernel) { + struct udev_list_entry *entry; + + kernel_monitor = udev_monitor_new_from_netlink(udev, "kernel"); + if (kernel_monitor == NULL) { + fprintf(stderr, "error: unable to create netlink socket\n"); + return 3; + } + udev_monitor_set_receive_buffer_size(kernel_monitor, 128*1024*1024); + fd_kernel = udev_monitor_get_fd(kernel_monitor); + + udev_list_entry_foreach(entry, udev_list_get_entry(&subsystem_match_list)) { + const char *subsys = udev_list_entry_get_name(entry); + + if (udev_monitor_filter_add_match_subsystem_devtype(kernel_monitor, subsys, NULL) < 0) + fprintf(stderr, "error: unable to apply subsystem filter '%s'\n", subsys); + } + + if (udev_monitor_enable_receiving(kernel_monitor) < 0) { + fprintf(stderr, "error: unable to subscribe to kernel events\n"); + return 4; + } + + memzero(&ep_kernel, sizeof(struct epoll_event)); + ep_kernel.events = EPOLLIN; + ep_kernel.data.fd = fd_kernel; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd_kernel, &ep_kernel) < 0) { + log_error_errno(errno, "fail to add fd to epoll: %m"); + return 5; + } + + printf("KERNEL - the kernel uevent\n"); + } + printf("\n"); + + while (!udev_exit) { + int fdcount; + struct epoll_event ev[4]; + int i; + + fdcount = epoll_wait(fd_ep, ev, ELEMENTSOF(ev), -1); + if (fdcount < 0) { + if (errno != EINTR) + fprintf(stderr, "error receiving uevent message: %m\n"); + continue; + } + + for (i = 0; i < fdcount; i++) { + if (ev[i].data.fd == fd_kernel && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(kernel_monitor); + if (device == NULL) + continue; + print_device(device, "KERNEL", prop); + udev_device_unref(device); + } else if (ev[i].data.fd == fd_udev && ev[i].events & EPOLLIN) { + struct udev_device *device; + + device = udev_monitor_receive_device(udev_monitor); + if (device == NULL) + continue; + print_device(device, "UDEV", prop); + udev_device_unref(device); + } + } + } + + return 0; +} + +const struct udevadm_cmd udevadm_monitor = { + .name = "monitor", + .cmd = adm_monitor, + .help = "Listen to kernel and udev events", +}; diff --git a/src/grp-udev/udevadm/udevadm-settle.c b/src/grp-udev/udevadm/udevadm-settle.c new file mode 100644 index 0000000000..1d2ee5ba26 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-settle.c @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2006-2009 Kay Sievers + * Copyright (C) 2009 Canonical Ltd. + * Copyright (C) 2009 Scott James Remnant + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/parse-util.h" +#include "systemd-basic/util.h" +#include "udev.h" + +static void help(void) { + printf("%s settle OPTIONS\n\n" + "Wait for pending udev events.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -t --timeout=SECONDS Maximum time to wait for events\n" + " -E --exit-if-exists=FILE Stop waiting if file exists\n" + , program_invocation_short_name); +} + +static int adm_settle(struct udev *udev, int argc, char *argv[]) { + static const struct option options[] = { + { "timeout", required_argument, NULL, 't' }, + { "exit-if-exists", required_argument, NULL, 'E' }, + { "help", no_argument, NULL, 'h' }, + { "seq-start", required_argument, NULL, 's' }, /* removed */ + { "seq-end", required_argument, NULL, 'e' }, /* removed */ + { "quiet", no_argument, NULL, 'q' }, /* removed */ + {} + }; + usec_t deadline; + const char *exists = NULL; + unsigned int timeout = 120; + struct pollfd pfd[1] = { {.fd = -1}, }; + int c; + struct udev_queue *queue; + int rc = EXIT_FAILURE; + + while ((c = getopt_long(argc, argv, "t:E:hs:e:q", options, NULL)) >= 0) { + switch (c) { + + case 't': { + int r; + + r = safe_atou(optarg, &timeout); + if (r < 0) { + log_error_errno(r, "Invalid timeout value '%s': %m", optarg); + return EXIT_FAILURE; + } + break; + } + + case 'E': + exists = optarg; + break; + + case 'h': + help(); + return EXIT_SUCCESS; + + case 's': + case 'e': + case 'q': + log_info("Option -%c no longer supported.", c); + return EXIT_FAILURE; + + case '?': + return EXIT_FAILURE; + + default: + assert_not_reached("Unknown argument"); + } + } + + if (optind < argc) { + fprintf(stderr, "Extraneous argument: '%s'\n", argv[optind]); + return EXIT_FAILURE; + } + + deadline = now(CLOCK_MONOTONIC) + timeout * USEC_PER_SEC; + + /* guarantee that the udev daemon isn't pre-processing */ + if (getuid() == 0) { + struct udev_ctrl *uctrl; + + uctrl = udev_ctrl_new(udev); + if (uctrl != NULL) { + if (udev_ctrl_send_ping(uctrl, MAX(5U, timeout)) < 0) { + log_debug("no connection to daemon"); + udev_ctrl_unref(uctrl); + return EXIT_SUCCESS; + } + udev_ctrl_unref(uctrl); + } + } + + queue = udev_queue_new(udev); + if (!queue) { + log_error("unable to get udev queue"); + return EXIT_FAILURE; + } + + pfd[0].events = POLLIN; + pfd[0].fd = udev_queue_get_fd(queue); + if (pfd[0].fd < 0) { + log_debug("queue is empty, nothing to watch"); + rc = EXIT_SUCCESS; + goto out; + } + + for (;;) { + if (exists && access(exists, F_OK) >= 0) { + rc = EXIT_SUCCESS; + break; + } + + /* exit if queue is empty */ + if (udev_queue_get_queue_is_empty(queue)) { + rc = EXIT_SUCCESS; + break; + } + + if (now(CLOCK_MONOTONIC) >= deadline) + break; + + /* wake up when queue is empty */ + if (poll(pfd, 1, MSEC_PER_SEC) > 0 && pfd[0].revents & POLLIN) + udev_queue_flush(queue); + } + +out: + udev_queue_unref(queue); + return rc; +} + +const struct udevadm_cmd udevadm_settle = { + .name = "settle", + .cmd = adm_settle, + .help = "Wait for pending udev events", +}; diff --git a/src/grp-udev/udevadm/udevadm-test-builtin.c b/src/grp-udev/udevadm/udevadm-test-builtin.c new file mode 100644 index 0000000000..1f65b3ed8b --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-test-builtin.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "systemd-basic/string-util.h" +#include "udev.h" + +static void help(struct udev *udev) { + printf("%s builtin [--help] COMMAND SYSPATH\n\n" + "Test a built-in command.\n\n" + " -h --help Print this message\n" + " --version Print version of the program\n\n" + "Commands:\n" + , program_invocation_short_name); + + udev_builtin_list(udev); +} + +static int adm_builtin(struct udev *udev, int argc, char *argv[]) { + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + {} + }; + char *command = NULL; + char *syspath = NULL; + char filename[UTIL_PATH_SIZE]; + struct udev_device *dev = NULL; + enum udev_builtin_cmd cmd; + int rc = EXIT_SUCCESS, c; + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch (c) { + case 'h': + help(udev); + goto out; + } + + command = argv[optind++]; + if (command == NULL) { + fprintf(stderr, "command missing\n"); + help(udev); + rc = 2; + goto out; + } + + syspath = argv[optind++]; + if (syspath == NULL) { + fprintf(stderr, "syspath missing\n"); + rc = 3; + goto out; + } + + udev_builtin_init(udev); + + cmd = udev_builtin_lookup(command); + if (cmd >= UDEV_BUILTIN_MAX) { + fprintf(stderr, "unknown command '%s'\n", command); + help(udev); + rc = 5; + goto out; + } + + /* add /sys if needed */ + if (!startswith(syspath, "/sys")) + strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_syspath(udev, filename); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n\n", filename); + rc = 4; + goto out; + } + + rc = udev_builtin_run(dev, cmd, command, true); + if (rc < 0) { + fprintf(stderr, "error executing '%s', exit code %i\n\n", command, rc); + rc = 6; + } +out: + udev_device_unref(dev); + udev_builtin_exit(udev); + return rc; +} + +const struct udevadm_cmd udevadm_test_builtin = { + .name = "test-builtin", + .cmd = adm_builtin, + .help = "Test a built-in command", + .debug = true, +}; diff --git a/src/grp-udev/udevadm/udevadm-test.c b/src/grp-udev/udevadm/udevadm-test.c new file mode 100644 index 0000000000..00b326d2cb --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-test.c @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2003-2004 Greg Kroah-Hartman + * Copyright (C) 2004-2008 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/string-util.h" +#include "systemd-shared/udev-util.h" +#include "udev.h" + +static void help(void) { + + printf("%s test OPTIONS \n\n" + "Test an event run.\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -a --action=ACTION Set action string\n" + " -N --resolve-names=early|late|never When to resolve names\n" + , program_invocation_short_name); +} + +static int adm_test(struct udev *udev, int argc, char *argv[]) { + int resolve_names = 1; + char filename[UTIL_PATH_SIZE]; + const char *action = "add"; + const char *syspath = NULL; + struct udev_list_entry *entry; + _cleanup_udev_rules_unref_ struct udev_rules *rules = NULL; + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + _cleanup_udev_event_unref_ struct udev_event *event = NULL; + sigset_t mask, sigmask_orig; + int rc = 0, c; + + static const struct option options[] = { + { "action", required_argument, NULL, 'a' }, + { "resolve-names", required_argument, NULL, 'N' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + + log_debug("version %s", VERSION); + + while ((c = getopt_long(argc, argv, "a:N:h", options, NULL)) >= 0) + switch (c) { + case 'a': + action = optarg; + break; + case 'N': + if (streq (optarg, "early")) { + resolve_names = 1; + } else if (streq (optarg, "late")) { + resolve_names = 0; + } else if (streq (optarg, "never")) { + resolve_names = -1; + } else { + fprintf(stderr, "resolve-names must be early, late or never\n"); + log_error("resolve-names must be early, late or never"); + exit(EXIT_FAILURE); + } + break; + case 'h': + help(); + exit(EXIT_SUCCESS); + case '?': + exit(EXIT_FAILURE); + default: + assert_not_reached("Unknown option"); + } + + syspath = argv[optind]; + if (syspath == NULL) { + fprintf(stderr, "syspath parameter missing\n"); + rc = 2; + goto out; + } + + printf("This program is for debugging only, it does not run any program\n" + "specified by a RUN key. It may show incorrect results, because\n" + "some values may be different, or not available at a simulation run.\n" + "\n"); + + sigprocmask(SIG_SETMASK, NULL, &sigmask_orig); + + udev_builtin_init(udev); + + rules = udev_rules_new(udev, resolve_names); + if (rules == NULL) { + fprintf(stderr, "error reading rules\n"); + rc = 3; + goto out; + } + + /* add /sys if needed */ + if (!startswith(syspath, "/sys")) + strscpyl(filename, sizeof(filename), "/sys", syspath, NULL); + else + strscpy(filename, sizeof(filename), syspath); + util_remove_trailing_chars(filename, '/'); + + dev = udev_device_new_from_synthetic_event(udev, filename, action); + if (dev == NULL) { + fprintf(stderr, "unable to open device '%s'\n", filename); + rc = 4; + goto out; + } + + /* don't read info from the db */ + udev_device_set_info_loaded(dev); + + event = udev_event_new(dev); + + sigfillset(&mask); + sigprocmask(SIG_SETMASK, &mask, &sigmask_orig); + + udev_event_execute_rules(event, + 60 * USEC_PER_SEC, 20 * USEC_PER_SEC, + NULL, + rules); + + udev_list_entry_foreach(entry, udev_device_get_properties_list_entry(dev)) + printf("%s=%s\n", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); + + udev_list_entry_foreach(entry, udev_list_get_entry(&event->run_list)) { + char program[UTIL_PATH_SIZE]; + + udev_event_apply_format(event, udev_list_entry_get_name(entry), program, sizeof(program)); + printf("run: '%s'\n", program); + } +out: + udev_builtin_exit(udev); + return rc; +} + +const struct udevadm_cmd udevadm_test = { + .name = "test", + .cmd = adm_test, + .help = "Test an event run", + .debug = true, +}; diff --git a/src/grp-udev/udevadm/udevadm-trigger.c b/src/grp-udev/udevadm/udevadm-trigger.c new file mode 100644 index 0000000000..150b1e5a9a --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-trigger.c @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2008-2009 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-shared/udev-util.h" +#include "udev.h" + +#include "udevadm-util.h" + +static int verbose; +static int dry_run; + +static void exec_list(struct udev_enumerate *udev_enumerate, const char *action) { + struct udev_list_entry *entry; + + udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(udev_enumerate)) { + char filename[UTIL_PATH_SIZE]; + int fd; + + if (verbose) + printf("%s\n", udev_list_entry_get_name(entry)); + if (dry_run) + continue; + strscpyl(filename, sizeof(filename), udev_list_entry_get_name(entry), "/uevent", NULL); + fd = open(filename, O_WRONLY|O_CLOEXEC); + if (fd < 0) + continue; + if (write(fd, action, strlen(action)) < 0) + log_debug_errno(errno, "error writing '%s' to '%s': %m", action, filename); + close(fd); + } +} + +static const char *keyval(const char *str, const char **val, char *buf, size_t size) { + char *pos; + + strscpy(buf, size,str); + pos = strchr(buf, '='); + if (pos != NULL) { + pos[0] = 0; + pos++; + } + *val = pos; + return buf; +} + +static void help(void) { + printf("%s trigger OPTIONS\n\n" + "Request events from the kernel.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -v --verbose Print the list of devices while running\n" + " -n --dry-run Do not actually trigger the events\n" + " -t --type= Type of events to trigger\n" + " devices sysfs devices (default)\n" + " subsystems sysfs subsystems and drivers\n" + " -c --action=ACTION Event action value, default is \"change\"\n" + " -s --subsystem-match=SUBSYSTEM Trigger devices from a matching subsystem\n" + " -S --subsystem-nomatch=SUBSYSTEM Exclude devices from a matching subsystem\n" + " -a --attr-match=FILE[=VALUE] Trigger devices with a matching attribute\n" + " -A --attr-nomatch=FILE[=VALUE] Exclude devices with a matching attribute\n" + " -p --property-match=KEY=VALUE Trigger devices with a matching property\n" + " -g --tag-match=KEY=VALUE Trigger devices with a matching property\n" + " -y --sysname-match=NAME Trigger devices with this /sys path\n" + " --name-match=NAME Trigger devices with this /dev name\n" + " -b --parent-match=NAME Trigger devices with that parent device\n" + , program_invocation_short_name); +} + +static int adm_trigger(struct udev *udev, int argc, char *argv[]) { + enum { + ARG_NAME = 0x100, + }; + + static const struct option options[] = { + { "verbose", no_argument, NULL, 'v' }, + { "dry-run", no_argument, NULL, 'n' }, + { "type", required_argument, NULL, 't' }, + { "action", required_argument, NULL, 'c' }, + { "subsystem-match", required_argument, NULL, 's' }, + { "subsystem-nomatch", required_argument, NULL, 'S' }, + { "attr-match", required_argument, NULL, 'a' }, + { "attr-nomatch", required_argument, NULL, 'A' }, + { "property-match", required_argument, NULL, 'p' }, + { "tag-match", required_argument, NULL, 'g' }, + { "sysname-match", required_argument, NULL, 'y' }, + { "name-match", required_argument, NULL, ARG_NAME }, + { "parent-match", required_argument, NULL, 'b' }, + { "help", no_argument, NULL, 'h' }, + {} + }; + enum { + TYPE_DEVICES, + TYPE_SUBSYSTEMS, + } device_type = TYPE_DEVICES; + const char *action = "change"; + _cleanup_udev_enumerate_unref_ struct udev_enumerate *udev_enumerate = NULL; + int c, r; + + udev_enumerate = udev_enumerate_new(udev); + if (udev_enumerate == NULL) + return 1; + + while ((c = getopt_long(argc, argv, "vno:t:c:s:S:a:A:p:g:y:b:h", options, NULL)) >= 0) { + const char *key; + const char *val; + char buf[UTIL_PATH_SIZE]; + + switch (c) { + case 'v': + verbose = 1; + break; + case 'n': + dry_run = 1; + break; + case 't': + if (streq(optarg, "devices")) + device_type = TYPE_DEVICES; + else if (streq(optarg, "subsystems")) + device_type = TYPE_SUBSYSTEMS; + else { + log_error("unknown type --type=%s", optarg); + return 2; + } + break; + case 'c': + if (!nulstr_contains("add\0" "remove\0" "change\0", optarg)) { + log_error("unknown action '%s'", optarg); + return 2; + } else + action = optarg; + + break; + case 's': + r = udev_enumerate_add_match_subsystem(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add subsystem match '%s': %m", optarg); + return 2; + } + break; + case 'S': + r = udev_enumerate_add_nomatch_subsystem(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add negative subsystem match '%s': %m", optarg); + return 2; + } + break; + case 'a': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_match_sysattr(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add sysattr match '%s=%s': %m", key, val); + return 2; + } + break; + case 'A': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_nomatch_sysattr(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add negative sysattr match '%s=%s': %m", key, val); + return 2; + } + break; + case 'p': + key = keyval(optarg, &val, buf, sizeof(buf)); + r = udev_enumerate_add_match_property(udev_enumerate, key, val); + if (r < 0) { + log_error_errno(r, "could not add property match '%s=%s': %m", key, val); + return 2; + } + break; + case 'g': + r = udev_enumerate_add_match_tag(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add tag match '%s': %m", optarg); + return 2; + } + break; + case 'y': + r = udev_enumerate_add_match_sysname(udev_enumerate, optarg); + if (r < 0) { + log_error_errno(r, "could not add sysname match '%s': %m", optarg); + return 2; + } + break; + case 'b': { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/sys"); + if (dev == NULL) { + log_error("unable to open the device '%s'", optarg); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add parent match '%s': %m", optarg); + return 2; + } + break; + } + + case ARG_NAME: { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, optarg, "/dev/"); + if (dev == NULL) { + log_error("unable to open the device '%s'", optarg); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add parent match '%s': %m", optarg); + return 2; + } + break; + } + + case 'h': + help(); + return 0; + case '?': + return 1; + default: + assert_not_reached("Unknown option"); + } + } + + for (; optind < argc; optind++) { + _cleanup_udev_device_unref_ struct udev_device *dev; + + dev = find_device(udev, argv[optind], NULL); + if (dev == NULL) { + log_error("unable to open the device '%s'", argv[optind]); + return 2; + } + + r = udev_enumerate_add_match_parent(udev_enumerate, dev); + if (r < 0) { + log_error_errno(r, "could not add tag match '%s': %m", optarg); + return 2; + } + } + + switch (device_type) { + case TYPE_SUBSYSTEMS: + udev_enumerate_scan_subsystems(udev_enumerate); + exec_list(udev_enumerate, action); + return 0; + case TYPE_DEVICES: + udev_enumerate_scan_devices(udev_enumerate); + exec_list(udev_enumerate, action); + return 0; + default: + assert_not_reached("device_type"); + } +} + +const struct udevadm_cmd udevadm_trigger = { + .name = "trigger", + .cmd = adm_trigger, + .help = "Request events from the kernel", +}; diff --git a/src/grp-udev/udevadm/udevadm-util.c b/src/grp-udev/udevadm/udevadm-util.c new file mode 100644 index 0000000000..c972959888 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-util.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2008-2009 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "systemd-basic/string-util.h" + +#include "udevadm-util.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix) { + + assert(udev); + assert(id); + + if (prefix && !startswith(id, prefix)) + id = strjoina(prefix, id); + + if (startswith(id, "/dev/")) { + struct stat statbuf; + char type; + + if (stat(id, &statbuf) < 0) + return NULL; + + if (S_ISBLK(statbuf.st_mode)) + type = 'b'; + else if (S_ISCHR(statbuf.st_mode)) + type = 'c'; + else + return NULL; + + return udev_device_new_from_devnum(udev, type, statbuf.st_rdev); + } else if (startswith(id, "/sys/")) + return udev_device_new_from_syspath(udev, id); + else + return NULL; +} diff --git a/src/grp-udev/udevadm/udevadm-util.h b/src/grp-udev/udevadm/udevadm-util.h new file mode 100644 index 0000000000..dc712b0d93 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm-util.h @@ -0,0 +1,24 @@ +#pragma once + +/* + * Copyright (C) 2014 Zbigniew Jędrzejewski-Szmek + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "udev.h" + +struct udev_device *find_device(struct udev *udev, + const char *id, + const char *prefix); diff --git a/src/grp-udev/udevadm/udevadm.c b/src/grp-udev/udevadm/udevadm.c new file mode 100644 index 0000000000..4f61722836 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2007-2012 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include + +#include "systemd-basic/selinux-util.h" +#include "systemd-basic/string-util.h" +#include "udev.h" + +static int adm_version(struct udev *udev, int argc, char *argv[]) { + printf("%s\n", VERSION); + return 0; +} + +static const struct udevadm_cmd udevadm_version = { + .name = "version", + .cmd = adm_version, +}; + +static int adm_help(struct udev *udev, int argc, char *argv[]); + +static const struct udevadm_cmd udevadm_help = { + .name = "help", + .cmd = adm_help, +}; + +static const struct udevadm_cmd *udevadm_cmds[] = { + &udevadm_info, + &udevadm_trigger, + &udevadm_settle, + &udevadm_control, + &udevadm_monitor, + &udevadm_hwdb, + &udevadm_test, + &udevadm_test_builtin, + &udevadm_version, + &udevadm_help, +}; + +static int adm_help(struct udev *udev, int argc, char *argv[]) { + unsigned int i; + + printf("%s [--help] [--version] [--debug] COMMAND [COMMAND OPTIONS]\n\n" + "Send control commands or test the device manager.\n\n" + "Commands:\n" + , program_invocation_short_name); + + for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++) + if (udevadm_cmds[i]->help != NULL) + printf(" %-12s %s\n", udevadm_cmds[i]->name, udevadm_cmds[i]->help); + return 0; +} + +static int run_command(struct udev *udev, const struct udevadm_cmd *cmd, int argc, char *argv[]) { + if (cmd->debug) + log_set_max_level(LOG_DEBUG); + log_debug("calling: %s", cmd->name); + return cmd->cmd(udev, argc, argv); +} + +int main(int argc, char *argv[]) { + struct udev *udev; + static const struct option options[] = { + { "debug", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + {} + }; + const char *command; + unsigned int i; + int rc = 1, c; + + udev = udev_new(); + if (udev == NULL) + goto out; + + log_parse_environment(); + log_open(); + mac_selinux_init(); + + while ((c = getopt_long(argc, argv, "+dhV", options, NULL)) >= 0) + switch (c) { + + case 'd': + log_set_max_level(LOG_DEBUG); + break; + + case 'h': + rc = adm_help(udev, argc, argv); + goto out; + + case 'V': + rc = adm_version(udev, argc, argv); + goto out; + + default: + goto out; + } + + command = argv[optind]; + + if (command != NULL) + for (i = 0; i < ELEMENTSOF(udevadm_cmds); i++) + if (streq(udevadm_cmds[i]->name, command)) { + argc -= optind; + argv += optind; + /* we need '0' here to reset the internal state */ + optind = 0; + rc = run_command(udev, udevadm_cmds[i], argc, argv); + goto out; + } + + fprintf(stderr, "%s: missing or unknown command\n", program_invocation_short_name); + rc = 2; +out: + mac_selinux_finish(); + udev_unref(udev); + log_close(); + return rc; +} diff --git a/src/grp-udev/udevadm/udevadm.completion.bash b/src/grp-udev/udevadm/udevadm.completion.bash new file mode 100644 index 0000000000..b828b8dd7c --- /dev/null +++ b/src/grp-udev/udevadm/udevadm.completion.bash @@ -0,0 +1,97 @@ +# udevadm(8) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2010 Ran Benita +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see . + +__contains_word () { + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done +} + +__get_all_sysdevs() { + local -a devs=(/sys/bus/*/devices/*/ /sys/class/*/*/) + printf '%s\n' "${devs[@]%/}" +} + +_udevadm() { + local i verb comps + local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} + local OPTS='-h --help --version --debug' + + local verbs=(info trigger settle control monitor hwdb test-builtin test) + + for ((i=0; i < COMP_CWORD; i++)); do + if __contains_word "${COMP_WORDS[i]}" "${verbs[@]}" && + ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then + verb=${COMP_WORDS[i]} + break + fi + done + + if [[ -z $verb ]]; then + COMPREPLY=( $(compgen -W '${OPTS[*]} ${verbs[*]}' -- "$cur") ) + return 0 + fi + + case $verb in + 'info') + if [[ $cur = -* ]]; then + comps='--help --query= --path= --name= --root --attribute-walk --export-db --cleanup-db' + else + comps=$( __get_all_sysdevs ) + fi + ;; + 'trigger') + comps='--help --verbose --dry-run --type= --action= --subsystem-match= + --subsystem-nomatch= --attr-match= --attr-nomatch= --property-match= + --tag-match= --sysname-match= --parent-match=' + ;; + 'settle') + comps='--help --timeout= --seq-start= --seq-end= --exit-if-exists= --quiet' + ;; + 'control') + comps='--help --exit --log-priority= --stop-exec-queue --start-exec-queue + --reload --property= --children-max= --timeout=' + ;; + 'monitor') + comps='--help --kernel --udev --property --subsystem-match= --tag-match=' + ;; + 'hwdb') + comps='--help --update --test=' + ;; + 'test') + if [[ $cur = -* ]]; then + comps='--help --action=' + else + comps=$( __get_all_sysdevs ) + fi + ;; + 'test-builtin') + comps='blkid btrfs hwdb input_id keyboard kmod net_id net_setup_link path_id usb_id uaccess' + ;; + *) + comps=${VERBS[*]} + ;; + esac + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _udevadm udevadm diff --git a/src/grp-udev/udevadm/udevadm.completion.zsh b/src/grp-udev/udevadm/udevadm.completion.zsh new file mode 100644 index 0000000000..bb23e64d24 --- /dev/null +++ b/src/grp-udev/udevadm/udevadm.completion.zsh @@ -0,0 +1,141 @@ +#compdef udevadm + +_udevadm_info(){ + _arguments \ + '--query=[Query the database for specified type of device data. It needs the --path or --name to identify the specified device.]:type:(name symlink path property all)' \ + '--path=[The devpath of the device to query.]:sys files:_files -P /sys/ -W /sys' \ + '--name=[The name of the device node or a symlink to query]:device files:_files -P /dev/ -W /dev' \ + '--root[Print absolute paths in name or symlink query.]' \ + '--attribute-walk[Print all sysfs properties of the specified device that can be used in udev rules to match the specified device]' \ + '--export[Print output as key/value pairs.]' \ + '--export-prefix=[Add a prefix to the key name of exported values.]:prefix' \ + '--device-id-of-file=[Print major/minor numbers of the underlying device, where the file lives on.]:files:_udevadm_mounts' \ + '--export-db[Export the content of the udev database.]' \ + '--cleanup-db[Cleanup the udev database.]' +} + +_udevadm_trigger(){ + _arguments \ + '--verbose[Print the list of devices which will be triggered.]' \ + '--dry-run[Do not actually trigger the event.]' \ + '--type=[Trigger a specific type of devices.]:types:(devices subsystems failed)' \ + '--action=[Type of event to be triggered.]:actions:(add change remove)' \ + '--subsystem-match=[Trigger events for devices which belong to a matching subsystem.]' \ + '--subsystem-nomatch=[Do not trigger events for devices which belong to a matching subsystem.]' \ + '--attr-match=attribute=[Trigger events for devices with a matching sysfs attribute.]' \ + '--attr-nomatch=attribute=[Do not trigger events for devices with a matching sysfs attribute.]' \ + '--property-match=[Trigger events for devices with a matching property value.]' \ + '--tag-match=property[Trigger events for devices with a matching tag.]' \ + '--sysname-match=[Trigger events for devices with a matching sys device name.]' \ + '--parent-match=[Trigger events for all children of a given device.]' +} + +_udevadm_settle(){ + _arguments \ + '--timeout=[Maximum number of seconds to wait for the event queue to become empty.]' \ + '--seq-start=[Wait only for events after the given sequence number.]' \ + '--seq-end=[Wait only for events before the given sequence number.]' \ + '--exit-if-exists=[Stop waiting if file exists.]:files:_files' \ + '--quiet[Do not print any output, like the remaining queue entries when reaching the timeout.]' \ + '--help[Print help text.]' +} + +_udevadm_control(){ + _arguments \ + '--exit[Signal and wait for systemd-udevd to exit.]' \ + '--log-priority=[Set the internal log level of systemd-udevd.]:priorities:(err info debug)' \ + '--stop-exec-queue[Signal systemd-udevd to stop executing new events. Incoming events will be queued.]' \ + '--start-exec-queue[Signal systemd-udevd to enable the execution of events.]' \ + '--reload[Signal systemd-udevd to reload the rules files and other databases like the kernel module index.]' \ + '--property=[Set a global property for all events.]' \ + '--children-max=[Set the maximum number of events.]' \ + '--timeout=[The maximum number of seconds to wait for a reply from systemd-udevd.]' \ + '--help[Print help text.]' +} + +_udevadm_monitor(){ + _arguments \ + '--kernel[Print the kernel uevents.]' \ + '--udev[Print the udev event after the rule processing.]' \ + '--property[Also print the properties of the event.]' \ + '--subsystem-match=[Filter events by subsystem/\[devtype\].]' \ + '--tag-match=[Filter events by property.]' \ + '--help[Print help text.]' +} + +_udevadm_test(){ + _arguments \ + '--action=[The action string.]:actions:(add change remove)' \ + '--subsystem=[The subsystem string.]' \ + '--help[Print help text.]' \ + '*::devpath:_files -P /sys/ -W /sys' +} + +_udevadm_test-builtin(){ + if (( CURRENT == 2 )); then + _arguments \ + '--help[Print help text]' \ + '*::builtins:(blkid btrfs hwdb input_id net_id net_setup_link kmod path_id usb_id uaccess)' + elif (( CURRENT == 3 )); then + _arguments \ + '--help[Print help text]' \ + '*::syspath:_files -P /sys -W /sys' + else + _arguments \ + '--help[Print help text]' + fi +} + +_udevadm_mounts(){ + local dev_tmp dpath_tmp mp_tmp mline + + tmp=( "${(@f)$(< /proc/self/mounts)}" ) + dev_tmp=( "${(@)${(@)tmp%% *}:#none}" ) + mp_tmp=( "${(@)${(@)tmp#* }%% *}" ) + + local MATCH + mp_tmp=("${(@q)mp_tmp//(#m)\\[0-7](#c3)/${(#)$(( 8#${MATCH[2,-1]} ))}}") + dpath_tmp=( "${(@Mq)dev_tmp:#/*}" ) + dev_tmp=( "${(@q)dev_tmp:#/*}" ) + + _alternative \ + 'device-paths: device path:compadd -a dpath_tmp' \ + 'directories:mount point:compadd -a mp_tmp' +} + + +_udevadm_command(){ + local -a _udevadm_cmds + _udevadm_cmds=( + 'info:query sysfs or the udev database' + 'trigger:request events from the kernel' + 'settle:wait for the event queue to finish' + 'control:control the udev daemon' + 'monitor:listen to kernel and udev events' + 'test:test an event run' + 'test-builtin:test a built-in command' + ) + + if ((CURRENT == 1)); then + _describe -t commands 'udevadm commands' _udevadm_cmds + else + local curcontext="$curcontext" + cmd="${${_udevadm_cmds[(r)$words[1]:*]%%:*}}" + if (($#cmd)); then + if (( $+functions[_udevadm_$cmd] )); then + _udevadm_$cmd + else + _message "no options for $cmd" + fi + else + _message "no more options" + fi + fi +} + + +_arguments \ + '--debug[Print debug messages to stderr]' \ + '--version[Print version number]' \ + '--help[Print help text]' \ + '*::udevadm commands:_udevadm_command' diff --git a/src/grp-udev/udevadm/udevadm.xml b/src/grp-udev/udevadm/udevadm.xml new file mode 100644 index 0000000000..1c7921f5bd --- /dev/null +++ b/src/grp-udev/udevadm/udevadm.xml @@ -0,0 +1,576 @@ + + + + + + udevadm + systemd + + + Developer + Kay + Sievers + kay@vrfy.org + + + + + + udevadm + 8 + + + + udevadmudev management tool + + + + + udevadm + + + + + + udevadm info options + + + udevadm trigger options + + + udevadm settle options + + + udevadm control command + + + udevadm monitor options + + + udevadm test options devpath + + + udevadm test-builtin options command devpath + + + + Description + udevadm expects a command and command + specific options. It controls the runtime behavior of + systemd-udevd, requests kernel events, manages + the event queue, and provides simple debugging mechanisms. + + + Options + + + + + Print debug messages to standard error. + + + + + + Print version number. + + + + + + + Print help text. + + + + + udevadm info + <arg choice="opt"><replaceable>options</replaceable></arg> + <arg choice="opt"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg> + + + Queries the udev database for device information + stored in the udev database. It can also query the properties + of a device from its sysfs representation to help creating udev + rules that match this device. + + + + + + Query the database for the specified type of device + data. It needs the or + to identify the specified device. + Valid TYPEs are: + name, symlink, + path, property, + all. + + + + + + + The /sys path of the device to + query, e.g. + /sys/class/block/sda. + Note that this option usually is not very useful, since + udev can guess the type of the + argument, so udevadm + --devpath=/class/block/sda is equivalent to + udevadm /sys/class/block/sda. + + + + + + + The name of the device node or a symlink to query, + e.g. /dev/sda. + Note that this option usually is not very useful, since + udev can guess the type of the + argument, so udevadm --name=sda is + equivalent to udevadm /dev/sda. + + + + + + + Print absolute paths in name or symlink + query. + + + + + + + Print all sysfs properties of the specified device that can be used + in udev rules to match the specified device. It prints all devices + along the chain, up to the root of sysfs that can be used in udev rules. + + + + + + + Print output as key/value pairs. Values are enclosed in single quotes. + + + + + + + Add a prefix to the key name of exported values. + + + + + + + Print major/minor numbers of the underlying device, where the file + lives on. + + + + + + + Export the content of the udev database. + + + + + + + Cleanup the udev database. + + + + + + Print version. + + + + + + + Print help text. + + + + + In addition, an optional positional argument can be used + to specify a device name or a sys path. It must start with + /dev or /sys + respectively. + + + udevadm trigger + <arg choice="opt"><replaceable>options</replaceable></arg> + <arg choice="opt" rep="repeat"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg> + Request device events from the kernel. Primarily used to replay events at system coldplug time. + + + + + + Print the list of devices which will be triggered. + + + + + + + Do not actually trigger the event. + + + + + + + Trigger a specific type of devices. Valid types are: + devices, subsystems. + The default value is devices. + + + + + + + Type of event to be triggered. The default value is + change. + + + + + + + Trigger events for devices which belong to a + matching subsystem. This option can be specified multiple + times and supports shell style pattern matching. + + + + + + + Do not trigger events for devices which belong to a matching subsystem. This option + can be specified multiple times and supports shell style pattern matching. + + + + + + + Trigger events for devices with a matching sysfs + attribute. If a value is specified along with the + attribute name, the content of the attribute is matched + against the given value using shell style pattern + matching. If no value is specified, the existence of the + sysfs attribute is checked. This option can be specified + multiple times. + + + + + + + Do not trigger events for devices with a matching + sysfs attribute. If a value is specified along with the + attribute name, the content of the attribute is matched + against the given value using shell style pattern + matching. If no value is specified, the existence of the + sysfs attribute is checked. This option can be specified + multiple times. + + + + + + + Trigger events for devices with a matching property + value. This option can be specified multiple times and + supports shell style pattern matching. + + + + + + + Trigger events for devices with a matching tag. This + option can be specified multiple times. + + + + + + + Trigger events for devices with a matching sys + device path. This option can be specified multiple times + and supports shell style pattern matching. + + + + + + Trigger events for devices with a matching + device path. This option can be specified multiple + times. + + + + + + + Trigger events for all children of a given + device. + + + + + + + Print help text. + + + + + In addition, optional positional arguments can be used + to specify device names or sys paths. They must start with + /dev or /sys + respectively. + + + udevadm settle + <arg choice="opt"><replaceable>options</replaceable></arg> + + Watches the udev event queue, and exits if all current events are handled. + + + + + + Maximum number of seconds to wait for the event + queue to become empty. The default value is 120 seconds. A + value of 0 will check if the queue is empty and always + return immediately. + + + + + + + Stop waiting if file exists. + + + + + + + Print help text. + + + + + + udevadm control <replaceable>command</replaceable> + Modify the internal state of the running udev daemon. + + + + + + Signal and wait for systemd-udevd to exit. + + + + + + + Set the internal log level of + systemd-udevd. Valid values are the + numerical syslog priorities or their textual + representations: , + , , + , , + , , and + . + + + + + + + Signal systemd-udevd to stop executing new events. Incoming events + will be queued. + + + + + + + Signal systemd-udevd to enable the execution of events. + + + + + + + Signal systemd-udevd to reload the rules files and other databases like the kernel + module index. Reloading rules and databases does not apply any changes to already + existing devices; the new configuration will only be applied to new events. + + + + + + + Set a global property for all events. + + + + + value + + Set the maximum number of events, systemd-udevd will handle at the + same time. + + + + seconds + + The maximum number of seconds to wait for a reply from systemd-udevd. + + + + + + + Print help text. + + + + + + udevadm monitor + <arg choice="opt"><replaceable>options</replaceable></arg> + + Listens to the kernel uevents and events sent out by a udev rule + and prints the devpath of the event to the console. It can be used to analyze the + event timing, by comparing the timestamps of the kernel uevent and the udev event. + + + + + + + Print the kernel uevents. + + + + + + + Print the udev event after the rule processing. + + + + + + + Also print the properties of the event. + + + + + + + Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass. + + + + + + + Filter events by property. Only udev events with a given tag attached will pass. + + + + + + + Print help text. + + + + + + udevadm test + <arg choice="opt"><replaceable>options</replaceable></arg> + <arg><replaceable>devpath</replaceable></arg> + + Simulate a udev event run for the given device, and print debug output. + + + + + + The action string. + + + + + + + Specify when udevadm should resolve names of users + and groups. When set to early (the + default), names will be resolved when the rules are + parsed. When set to late, names will + be resolved for every event. When set to + never, names will never be resolved + and all devices will be owned by root. + + + + + + + Print help text. + + + + + + udevadm test-builtin + <arg choice="opt"><replaceable>options</replaceable></arg> + <arg><replaceable>command</replaceable></arg> + <arg><replaceable>devpath</replaceable></arg> + + Run a built-in command COMMAND + for device DEVPATH, and print debug + output. + + + + + + Print help text. + + + + + + + + See Also + + udev7 + , + + systemd-udevd.service8 + + + -- cgit v1.2.3-54-g00ecf