summaryrefslogtreecommitdiff
path: root/src/grp-udev/udevadm
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-udev/udevadm')
l---------src/grp-udev/udevadm/GNUmakefile1
-rw-r--r--src/grp-udev/udevadm/Makefile46
-rw-r--r--src/grp-udev/udevadm/udevadm-control.c172
-rw-r--r--src/grp-udev/udevadm/udevadm-hwdb.c698
-rw-r--r--src/grp-udev/udevadm/udevadm-info.c481
-rw-r--r--src/grp-udev/udevadm/udevadm-monitor.c281
-rw-r--r--src/grp-udev/udevadm/udevadm-settle.c162
-rw-r--r--src/grp-udev/udevadm/udevadm-test-builtin.c112
-rw-r--r--src/grp-udev/udevadm/udevadm-test.c160
-rw-r--r--src/grp-udev/udevadm/udevadm-trigger.c287
-rw-r--r--src/grp-udev/udevadm/udevadm-util.c51
-rw-r--r--src/grp-udev/udevadm/udevadm-util.h24
-rw-r--r--src/grp-udev/udevadm/udevadm.c137
-rw-r--r--src/grp-udev/udevadm/udevadm.completion.bash97
-rw-r--r--src/grp-udev/udevadm/udevadm.completion.zsh141
-rw-r--r--src/grp-udev/udevadm/udevadm.xml576
16 files changed, 3426 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>.
+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 <kay@vrfy.org>
+ *
+ * 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 <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <KEY>=<value> 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 <kay@vrfy.org>
+
+ 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <ctype.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/time.h>
+#include <time.h>
+
+#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 <kay@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ * Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <poll.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <greg@kroah.com>
+ * Copyright (C) 2004-2008 Kay Sievers <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/signalfd.h>
+#include <unistd.h>
+
+#include "systemd-basic/string-util.h"
+#include "systemd-shared/udev-util.h"
+#include "udev.h"
+
+static void help(void) {
+
+ printf("%s test OPTIONS <syspath>\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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <zbyszek@in.waw.pl>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <kay@vrfy.org>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stddef.h>
+#include <stdio.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+__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 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<refentry id="udevadm">
+ <refentryinfo>
+ <title>udevadm</title>
+ <productname>systemd</productname>
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Kay</firstname>
+ <surname>Sievers</surname>
+ <email>kay@vrfy.org</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>udevadm</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>udevadm</refname><refpurpose>udev management tool</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>udevadm</command>
+ <arg><option>--debug</option></arg>
+ <arg><option>--version</option></arg>
+ <arg><option>--help</option></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm info <replaceable>options</replaceable></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm trigger <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm settle <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm control <replaceable>command</replaceable></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm monitor <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm test-builtin <optional>options</optional> <replaceable>command</replaceable> <replaceable>devpath</replaceable></command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1><title>Description</title>
+ <para><command>udevadm</command> expects a command and command
+ specific options. It controls the runtime behavior of
+ <command>systemd-udevd</command>, requests kernel events, manages
+ the event queue, and provides simple debugging mechanisms.</para>
+ </refsect1>
+
+ <refsect1><title>Options</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--debug</option></term>
+ <listitem>
+ <para>Print debug messages to standard error.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version number.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <refsect2><title>udevadm info
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg choice="opt"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg>
+ </title>
+
+ <para>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.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-q</option></term>
+ <term><option>--query=<replaceable>TYPE</replaceable></option></term>
+ <listitem>
+ <para>Query the database for the specified type of device
+ data. It needs the <option>--path</option> or
+ <option>--name</option> to identify the specified device.
+ Valid <replaceable>TYPE</replaceable>s are:
+ <constant>name</constant>, <constant>symlink</constant>,
+ <constant>path</constant>, <constant>property</constant>,
+ <constant>all</constant>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--path=<replaceable>DEVPATH</replaceable></option></term>
+ <listitem>
+ <para>The <filename>/sys</filename> path of the device to
+ query, e.g.
+ <filename><optional>/sys</optional>/class/block/sda</filename>.
+ Note that this option usually is not very useful, since
+ <command>udev</command> can guess the type of the
+ argument, so <command>udevadm
+ --devpath=/class/block/sda</command> is equivalent to
+ <command>udevadm /sys/class/block/sda</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--name=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>The name of the device node or a symlink to query,
+ e.g. <filename><optional>/dev</optional>/sda</filename>.
+ Note that this option usually is not very useful, since
+ <command>udev</command> can guess the type of the
+ argument, so <command>udevadm --name=sda</command> is
+ equivalent to <command>udevadm /dev/sda</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-r</option></term>
+ <term><option>--root</option></term>
+ <listitem>
+ <para>Print absolute paths in <command>name</command> or <command>symlink</command>
+ query.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--attribute-walk</option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-x</option></term>
+ <term><option>--export</option></term>
+ <listitem>
+ <para>Print output as key/value pairs. Values are enclosed in single quotes.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-P</option></term>
+ <term><option>--export-prefix=<replaceable>NAME</replaceable></option></term>
+ <listitem>
+ <para>Add a prefix to the key name of exported values.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-d</option></term>
+ <term><option>--device-id-of-file=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>Print major/minor numbers of the underlying device, where the file
+ lives on.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-e</option></term>
+ <term><option>--export-db</option></term>
+ <listitem>
+ <para>Export the content of the udev database.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--cleanup-db</option></term>
+ <listitem>
+ <para>Cleanup the udev database.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>In addition, an optional positional argument can be used
+ to specify a device name or a sys path. It must start with
+ <filename>/dev</filename> or <filename>/sys</filename>
+ respectively.</para>
+ </refsect2>
+
+ <refsect2><title>udevadm trigger
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg choice="opt" rep="repeat"><replaceable>devpath</replaceable>|<replaceable>file</replaceable></arg></title>
+ <para>Request device events from the kernel. Primarily used to replay events at system coldplug time.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-v</option></term>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>Print the list of devices which will be triggered.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--dry-run</option></term>
+ <listitem>
+ <para>Do not actually trigger the event.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--type=<replaceable>TYPE</replaceable></option></term>
+ <listitem>
+ <para>Trigger a specific type of devices. Valid types are:
+ <command>devices</command>, <command>subsystems</command>.
+ The default value is <command>devices</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-c</option></term>
+ <term><option>--action=<replaceable>ACTION</replaceable></option></term>
+ <listitem>
+ <para>Type of event to be triggered. The default value is
+ <command>change</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--subsystem-match=<replaceable>SUBSYSTEM</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices which belong to a
+ matching subsystem. This option can be specified multiple
+ times and supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--subsystem-nomatch=<replaceable>SUBSYSTEM</replaceable></option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--attr-match=<replaceable>ATTRIBUTE</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-A</option></term>
+ <term><option>--attr-nomatch=<replaceable>ATTRIBUTE</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property-match=<replaceable>PROPERTY</replaceable>=<replaceable>VALUE</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching property
+ value. This option can be specified multiple times and
+ supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-g</option></term>
+ <term><option>--tag-match=<replaceable>PROPERTY</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching tag. This
+ option can be specified multiple times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-y</option></term>
+ <term><option>--sysname-match=<replaceable>PATH</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching sys
+ device path. This option can be specified multiple times
+ and supports shell style pattern matching.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--name-match=<replaceable>NAME</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for devices with a matching
+ device path. This option can be specified multiple
+ times.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-b</option></term>
+ <term><option>--parent-match=<replaceable>SYSPATH</replaceable></option></term>
+ <listitem>
+ <para>Trigger events for all children of a given
+ device.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>In addition, optional positional arguments can be used
+ to specify device names or sys paths. They must start with
+ <filename>/dev</filename> or <filename>/sys</filename>
+ respectively.</para>
+ </refsect2>
+
+ <refsect2><title>udevadm settle
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ </title>
+ <para>Watches the udev event queue, and exits if all current events are handled.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--timeout=<replaceable>SECONDS</replaceable></option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-E</option></term>
+ <term><option>--exit-if-exists=<replaceable>FILE</replaceable></option></term>
+ <listitem>
+ <para>Stop waiting if file exists.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm control <replaceable>command</replaceable></title>
+ <para>Modify the internal state of the running udev daemon.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-e</option></term>
+ <term><option>--exit</option></term>
+ <listitem>
+ <para>Signal and wait for systemd-udevd to exit.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-l</option></term>
+ <term><option>--log-priority=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set the internal log level of
+ <filename>systemd-udevd</filename>. Valid values are the
+ numerical syslog priorities or their textual
+ representations: <option>emerg</option>,
+ <option>alert</option>, <option>crit</option>,
+ <option>err</option>, <option>warning</option>,
+ <option>notice</option>, <option>info</option>, and
+ <option>debug</option>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--stop-exec-queue</option></term>
+ <listitem>
+ <para>Signal systemd-udevd to stop executing new events. Incoming events
+ will be queued.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-S</option></term>
+ <term><option>--start-exec-queue</option></term>
+ <listitem>
+ <para>Signal systemd-udevd to enable the execution of events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-R</option></term>
+ <term><option>--reload</option></term>
+ <listitem>
+ <para>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.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property=<replaceable>KEY</replaceable>=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set a global property for all events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-m</option></term>
+ <term><option>--children-max=</option><replaceable>value</replaceable></term>
+ <listitem>
+ <para>Set the maximum number of events, systemd-udevd will handle at the
+ same time.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--timeout=</option><replaceable>seconds</replaceable></term>
+ <listitem>
+ <para>The maximum number of seconds to wait for a reply from systemd-udevd.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm monitor
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ </title>
+ <para>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.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-k</option></term>
+ <term><option>--kernel</option></term>
+ <listitem>
+ <para>Print the kernel uevents.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-u</option></term>
+ <term><option>--udev</option></term>
+ <listitem>
+ <para>Print the udev event after the rule processing.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-p</option></term>
+ <term><option>--property</option></term>
+ <listitem>
+ <para>Also print the properties of the event.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-s</option></term>
+ <term><option>--subsystem-match=<replaceable>string[/string]</replaceable></option></term>
+ <listitem>
+ <para>Filter events by subsystem[/devtype]. Only udev events with a matching subsystem value will pass.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-t</option></term>
+ <term><option>--tag-match=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>Filter events by property. Only udev events with a given tag attached will pass.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm test
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg><replaceable>devpath</replaceable></arg>
+ </title>
+ <para>Simulate a udev event run for the given device, and print debug output.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-a</option></term>
+ <term><option>--action=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>The action string.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-N</option></term>
+ <term><option>--resolve-names=<constant>early</constant>|<constant>late</constant>|<constant>never</constant></option></term>
+ <listitem>
+ <para>Specify when udevadm should resolve names of users
+ and groups. When set to <constant>early</constant> (the
+ default), names will be resolved when the rules are
+ parsed. When set to <constant>late</constant>, names will
+ be resolved for every event. When set to
+ <constant>never</constant>, names will never be resolved
+ and all devices will be owned by root.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm test-builtin
+ <arg choice="opt"><replaceable>options</replaceable></arg>
+ <arg><replaceable>command</replaceable></arg>
+ <arg><replaceable>devpath</replaceable></arg>
+ </title>
+ <para>Run a built-in command <replaceable>COMMAND</replaceable>
+ for device <replaceable>DEVPATH</replaceable>, and print debug
+ output.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-h</option></term>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para><citerefentry>
+ <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>systemd-udevd.service</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+</refentry>