summaryrefslogtreecommitdiff
path: root/udev
diff options
context:
space:
mode:
Diffstat (limited to 'udev')
-rw-r--r--udev/list.h289
-rw-r--r--udev/logging.h73
-rw-r--r--udev/test-udev.c175
-rw-r--r--udev/udev.h184
-rw-r--r--udev/udev.xml639
-rw-r--r--udev/udev_config.c200
-rw-r--r--udev/udev_db.c331
-rw-r--r--udev/udev_device.c305
-rw-r--r--udev/udev_node.c448
-rw-r--r--udev/udev_rules.c1618
-rw-r--r--udev/udev_rules.h131
-rw-r--r--udev/udev_rules_parse.c804
-rw-r--r--udev/udev_selinux.c173
-rw-r--r--udev/udev_selinux.h38
-rw-r--r--udev/udev_sysdeps.c73
-rw-r--r--udev/udev_sysdeps.h172
-rw-r--r--udev/udev_sysfs.c509
-rw-r--r--udev/udev_utils.c222
-rw-r--r--udev/udev_utils_file.c178
-rw-r--r--udev/udev_utils_string.c270
-rw-r--r--udev/udevadm.c168
-rw-r--r--udev/udevadm.xml379
-rw-r--r--udev/udevcontrol.c165
-rw-r--r--udev/udevd.c1304
-rw-r--r--udev/udevd.h74
-rw-r--r--udev/udevd.xml108
-rw-r--r--udev/udevinfo.c500
-rw-r--r--udev/udevmonitor.c289
-rw-r--r--udev/udevsettle.c178
-rw-r--r--udev/udevtest.c207
-rw-r--r--udev/udevtrigger.c712
31 files changed, 10916 insertions, 0 deletions
diff --git a/udev/list.h b/udev/list.h
new file mode 100644
index 0000000000..8626630f6b
--- /dev/null
+++ b/udev/list.h
@@ -0,0 +1,289 @@
+/*
+ * Copied from the Linux kernel source tree, version 2.6.0-test1.
+ *
+ * Licensed under the GPL v2 as per the whole kernel source tree.
+ *
+ */
+
+#ifndef _LIST_H
+#define _LIST_H
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+#define INIT_LIST_HEAD(ptr) do { \
+ (ptr)->next = (ptr); (ptr)->prev = (ptr); \
+} while (0)
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ INIT_LIST_HEAD(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(struct list_head *head)
+{
+ return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+ struct list_head *head)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head);
+ INIT_LIST_HEAD(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); \
+ pos = pos->next)
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev; pos != (head); pos = pos->prev)
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, typeof(*pos), member))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop counter.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, typeof(*pos), member), \
+ n = list_entry(pos->member.next, typeof(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, typeof(*n), member))
+
+#endif /* _LIST_H */
diff --git a/udev/logging.h b/udev/logging.h
new file mode 100644
index 0000000000..6e1d5a20ef
--- /dev/null
+++ b/udev/logging.h
@@ -0,0 +1,73 @@
+/*
+ * simple logging functions that can be expanded into nothing
+ *
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef LOGGING_H
+#define LOGGING_H
+
+#define err(format, arg...) do { } while (0)
+#define info(format, arg...) do { } while (0)
+#define dbg(format, arg...) do { } while (0)
+#define logging_init(foo) do { } while (0)
+#define logging_close(foo) do { } while (0)
+
+#ifdef USE_LOG
+#include <stdarg.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#undef err
+#define err(format, arg...) \
+ do { \
+ log_message(LOG_ERR ,"%s: " format ,__FUNCTION__ ,## arg); \
+ } while (0)
+
+#undef info
+#define info(format, arg...) \
+ do { \
+ log_message(LOG_INFO ,"%s: " format ,__FUNCTION__ ,## arg); \
+ } while (0)
+
+#ifdef DEBUG
+#undef dbg
+#define dbg(format, arg...) \
+ do { \
+ log_message(LOG_DEBUG ,"%s: " format ,__FUNCTION__ ,## arg); \
+ } while (0)
+#endif
+
+extern void log_message(int priority, const char *format, ...)
+ __attribute__ ((format (printf, 2, 3)));
+
+#undef logging_init
+static inline void logging_init(const char *program_name)
+{
+ openlog(program_name, LOG_PID | LOG_CONS, LOG_DAEMON);
+}
+
+#undef logging_close
+static inline void logging_close(void)
+{
+ closelog();
+}
+
+#endif /* USE_LOG */
+
+#endif
diff --git a/udev/test-udev.c b/udev/test-udev.c
new file mode 100644
index 0000000000..4ac2d5a889
--- /dev/null
+++ b/udev/test-udev.c
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+#include "udev_selinux.h"
+
+#ifdef USE_LOG
+void log_message(int priority, const char *format, ...)
+{
+ va_list args;
+
+ if (priority > udev_log_priority)
+ return;
+
+ va_start(args, format);
+ vsyslog(priority, format, args);
+ va_end(args);
+}
+#endif
+
+static void asmlinkage sig_handler(int signum)
+{
+ switch (signum) {
+ case SIGALRM:
+ exit(1);
+ case SIGINT:
+ case SIGTERM:
+ exit(20 + signum);
+ }
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ struct sysfs_device *dev;
+ struct udevice *udev;
+ const char *maj, *min;
+ struct udev_rules rules;
+ const char *action;
+ const char *devpath;
+ const char *subsystem;
+ struct sigaction act;
+ int devnull;
+ int retval = -EINVAL;
+
+ if (argc == 2 && strcmp(argv[1], "-V") == 0) {
+ printf("%s\n", UDEV_VERSION);
+ exit(0);
+ }
+
+ /* set std fd's to /dev/null, /sbin/hotplug forks us, we don't have them at all */
+ devnull = open("/dev/null", O_RDWR);
+ if (devnull >= 0) {
+ if (devnull != STDIN_FILENO)
+ dup2(devnull, STDIN_FILENO);
+ if (devnull != STDOUT_FILENO)
+ dup2(devnull, STDOUT_FILENO);
+ if (devnull != STDERR_FILENO)
+ dup2(devnull, STDERR_FILENO);
+ if (devnull > STDERR_FILENO)
+ close(devnull);
+ }
+
+ logging_init("udev");
+ if (devnull < 0)
+ err("open /dev/null failed: %s\n", strerror(errno));
+ udev_config_init();
+ selinux_init();
+ dbg("version %s\n", UDEV_VERSION);
+
+ /* set signal handlers */
+ memset(&act, 0x00, sizeof(act));
+ act.sa_handler = (void (*)(int)) sig_handler;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGALRM, &act, NULL);
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+
+ /* trigger timeout to prevent hanging processes */
+ alarm(UDEV_EVENT_TIMEOUT);
+
+ action = getenv("ACTION");
+ devpath = getenv("DEVPATH");
+ subsystem = getenv("SUBSYSTEM");
+ /* older kernels passed the SUBSYSTEM only as argument */
+ if (subsystem == NULL && argc == 2)
+ subsystem = argv[1];
+
+ if (action == NULL || subsystem == NULL || devpath == NULL) {
+ err("action, subsystem or devpath missing\n");
+ goto exit;
+ }
+
+ /* export log_priority , as called programs may want to do the same as udev */
+ if (udev_log_priority) {
+ char priority[32];
+
+ sprintf(priority, "%i", udev_log_priority);
+ setenv("UDEV_LOG", priority, 1);
+ }
+
+ sysfs_init();
+ udev_rules_init(&rules, 0);
+
+ dev = sysfs_device_get(devpath);
+ if (dev == NULL) {
+ info("unable to open '%s'\n", devpath);
+ goto fail;
+ }
+
+ udev = udev_device_init(NULL);
+ if (udev == NULL)
+ goto fail;
+
+ /* override built-in sysfs device */
+ udev->dev = dev;
+ strlcpy(udev->action, action, sizeof(udev->action));
+
+ /* get dev_t from environment, which is needed for "remove" to work, "add" works also from sysfs */
+ maj = getenv("MAJOR");
+ min = getenv("MINOR");
+ if (maj != NULL && min != NULL)
+ udev->devt = makedev(atoi(maj), atoi(min));
+ else
+ udev->devt = udev_device_get_devt(udev);
+
+ retval = udev_device_event(&rules, udev);
+
+ /* rules may change/disable the timeout */
+ if (udev->event_timeout >= 0)
+ alarm(udev->event_timeout);
+
+ if (retval == 0 && !udev->ignore_device && udev_run)
+ udev_rules_run(udev);
+
+ udev_device_cleanup(udev);
+fail:
+ udev_rules_cleanup(&rules);
+ sysfs_cleanup();
+ selinux_exit();
+
+exit:
+ logging_close();
+ if (retval != 0)
+ return 1;
+ return 0;
+}
diff --git a/udev/udev.h b/udev/udev.h
new file mode 100644
index 0000000000..5ecef4abbd
--- /dev/null
+++ b/udev/udev.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2003-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _UDEV_H_
+#define _UDEV_H_
+
+#include <sys/types.h>
+#include <sys/param.h>
+
+#include "list.h"
+#include "logging.h"
+#include "udev_sysdeps.h"
+#include "udev_version.h"
+
+#define COMMENT_CHARACTER '#'
+#define LINE_SIZE 512
+#define PATH_SIZE 512
+#define NAME_SIZE 256
+#define VALUE_SIZE 128
+
+#define ALLOWED_CHARS "#+-.:=@_"
+#define ALLOWED_CHARS_FILE ALLOWED_CHARS "/"
+#define ALLOWED_CHARS_INPUT ALLOWED_CHARS_FILE " $%?,"
+
+#define DEFAULT_PARTITIONS_COUNT 15
+#define UDEV_EVENT_TIMEOUT 180
+
+#define UDEV_MAX(a,b) ((a) > (b) ? (a) : (b))
+
+/* pipes */
+#define READ_END 0
+#define WRITE_END 1
+
+#define UDEV_ROOT "/dev"
+#define DB_DIR ".udev/db"
+#define DB_NAME_INDEX_DIR ".udev/names"
+#define RULES_LIB_DIR "/lib/udev/rules.d"
+#define RULES_DYN_DIR ".udev/rules.d"
+#define RULES_ETC_DIR "/etc/udev/rules.d"
+
+struct udev_rules;
+
+struct sysfs_device {
+ struct list_head node; /* for device cache */
+ struct sysfs_device *parent; /* already cached parent*/
+ char devpath[PATH_SIZE];
+ char subsystem[NAME_SIZE]; /* $class, $bus, drivers, module */
+ char kernel[NAME_SIZE]; /* device instance name */
+ char kernel_number[NAME_SIZE];
+ char driver[NAME_SIZE]; /* device driver name */
+};
+
+struct udevice {
+ /* device event */
+ struct sysfs_device *dev; /* points to dev_local by default */
+ struct sysfs_device dev_local;
+ struct sysfs_device *dev_parent; /* current parent device used for matching */
+ char action[NAME_SIZE];
+ char *devpath_old;
+
+ /* node */
+ char name[PATH_SIZE];
+ struct list_head symlink_list;
+ int symlink_final;
+ char owner[NAME_SIZE];
+ int owner_final;
+ char group[NAME_SIZE];
+ int group_final;
+ mode_t mode;
+ int mode_final;
+ dev_t devt;
+
+ /* event processing */
+ struct list_head run_list;
+ int run_final;
+ struct list_head env_list;
+ char tmp_node[PATH_SIZE];
+ int partitions;
+ int ignore_device;
+ int ignore_remove;
+ char program_result[PATH_SIZE];
+ int link_priority;
+ int event_timeout;
+ int test_run;
+};
+
+/* udev_config.c */
+extern char udev_root[PATH_SIZE];
+extern char udev_config_filename[PATH_SIZE];
+extern char udev_rules_dir[PATH_SIZE];
+extern int udev_log_priority;
+extern int udev_run;
+extern void udev_config_init(void);
+
+/* udev_device.c */
+extern struct udevice *udev_device_init(struct udevice *udev);
+extern void udev_device_cleanup(struct udevice *udev);
+extern int udev_device_event(struct udev_rules *rules, struct udevice *udev);
+extern dev_t udev_device_get_devt(struct udevice *udev);
+
+/* udev_sysfs.c */
+extern char sysfs_path[PATH_SIZE];
+extern int sysfs_init(void);
+extern void sysfs_cleanup(void);
+extern void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath,
+ const char *subsystem, const char *driver);
+extern struct sysfs_device *sysfs_device_get(const char *devpath);
+extern struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev);
+extern struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem);
+extern char *sysfs_attr_get_value(const char *devpath, const char *attr_name);
+extern int sysfs_resolve_link(char *path, size_t size);
+extern int sysfs_lookup_devpath_by_subsys_id(char *devpath, size_t len, const char *subsystem, const char *id);
+
+/* udev_node.c */
+extern int udev_node_mknod(struct udevice *udev, const char *file, dev_t devt, mode_t mode, uid_t uid, gid_t gid);
+extern void udev_node_update_symlinks(struct udevice *udev, struct udevice *udev_old);
+extern int udev_node_add(struct udevice *udev);
+extern int udev_node_remove(struct udevice *udev);
+
+/* udev_db.c */
+extern int udev_db_add_device(struct udevice *dev);
+extern int udev_db_delete_device(struct udevice *dev);
+extern int udev_db_rename(const char *devpath_old, const char *devpath);
+extern int udev_db_get_device(struct udevice *udev, const char *devpath);
+extern int udev_db_get_devices_by_name(const char *name, struct list_head *name_list);
+extern int udev_db_get_all_entries(struct list_head *name_list);
+
+/* udev_utils.c */
+struct name_entry {
+ struct list_head node;
+ char name[PATH_SIZE];
+ unsigned int ignore_error:1;
+};
+
+extern int log_priority(const char *priority);
+extern struct name_entry *name_list_add(struct list_head *name_list, const char *name, int sort);
+extern struct name_entry *name_list_key_add(struct list_head *name_list, const char *key, const char *value);
+extern int name_list_key_remove(struct list_head *name_list, const char *key);
+extern void name_list_cleanup(struct list_head *name_list);
+extern int add_matching_files(struct list_head *name_list, const char *dirname, const char *suffix);
+extern uid_t lookup_user(const char *user);
+extern gid_t lookup_group(const char *group);
+
+/* udev_utils_string.c */
+extern int string_is_true(const char *str);
+extern void remove_trailing_chars(char *path, char c);
+extern size_t path_encode(char *s, size_t len);
+extern size_t path_decode(char *s);
+extern int utf8_encoded_valid_unichar(const char *str);
+extern int replace_chars(char *str, const char *white);
+
+/* udev_utils_file.c */
+extern int create_path(const char *path);
+extern int delete_path(const char *path);
+extern int file_map(const char *filename, char **buf, size_t *bufsize);
+extern void file_unmap(void *buf, size_t bufsize);
+extern int unlink_secure(const char *filename);
+extern size_t buf_get_line(const char *buf, size_t buflen, size_t cur);
+
+/* udev commands */
+extern int udevmonitor(int argc, char *argv[], char *envp[]);
+extern int udevinfo(int argc, char *argv[], char *envp[]);
+extern int udevcontrol(int argc, char *argv[], char *envp[]);
+extern int udevtrigger(int argc, char *argv[], char *envp[]);
+extern int udevsettle(int argc, char *argv[], char *envp[]);
+extern int udevtest(int argc, char *argv[], char *envp[]);
+
+#endif
diff --git a/udev/udev.xml b/udev/udev.xml
new file mode 100644
index 0000000000..c740c0d807
--- /dev/null
+++ b/udev/udev.xml
@@ -0,0 +1,639 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<article>
+ <section>
+ <title>udev</title>
+ <refentry>
+ <refentryinfo>
+ <title>udev</title>
+ <date>August 2005</date>
+ <productname>udev</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>udev</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo class="version"></refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>udev</refname>
+ <refpurpose>dynamic device management</refpurpose>
+ </refnamediv>
+
+ <refsect1><title>DESCRIPTION</title>
+ <para>udev provides a dynamic device directory containing only the files for
+ actually present devices. It creates or removes device node files in the
+ <filename>/dev</filename> directory, or it renames network interfaces.</para>
+
+ <para>Usually udev runs as <citerefentry><refentrytitle>udevd</refentrytitle>
+ <manvolnum>8</manvolnum></citerefentry> and receives uevents directly from the
+ kernel if a device is added or removed from the system.</para>
+
+ <para>If udev receives a device event, it matches its configured rules
+ against the available device attributes provided in sysfs to identify the device.
+ Rules that match may provide additional device information or specify a device
+ node name and multiple symlink names and instruct udev to run additional programs
+ as part of the device event handling.</para>
+ </refsect1>
+
+ <refsect1><title>CONFIGURATION</title>
+ <para>udev configuration files are placed in <filename>/etc/udev/</filename>
+ and <filename>/lib/udev/</filename>. All empty lines, or lines beginning with
+ '#' will be ignored.</para>
+
+ <refsect2><title>Configuration file</title>
+ <para>udev expects its main configuration file at <filename>/etc/udev/udev.conf</filename>.
+ It consists of a set of variables allowing the user to override default udev values.
+ The following variables can be set:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>udev_root</option></term>
+ <listitem>
+ <para>Specifies where to place the device nodes in the filesystem.
+ The default value is <filename>/dev</filename>.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>udev_log</option></term>
+ <listitem>
+ <para>The logging priority. Valid values are the numerical syslog priorities
+ or their textual representations: <option>err</option>, <option>info</option>
+ and <option>debug</option>.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>Rules files</title>
+ <para>The udev rules are read from the files located in the
+ default rules directory <filename>/lib/udev/rules.d/</filename>,
+ the custom rules directory <filename>/etc/udev/rules.d/</filename>
+ and the temporary rules directory <filename>/dev/.udev/rules.d/</filename>.
+ All rule files are sorted and processed in lexical order, regardless
+ in which of these directories they live. Every line in the rules file contains at least
+ one key value pair. There are two kind of keys, match and assignment keys.
+ If all match keys are matching against its value, the rule gets applied and the
+ assign keys get the specified value assigned.</para>
+
+ <para>A matching rule may specify the name of the device node, add a symlink
+ pointing to the node, or run a specified program as part of the event handling.
+ If no matching rule is found, the default device node name is used.</para>
+
+ <para>A rule may consist of a list of one or more key value pairs separated by
+ a comma. Each key has a distinct operation, depending on the used operator. Valid
+ operators are:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>==</option></term>
+ <listitem>
+ <para>Compare for equality.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>!=</option></term>
+ <listitem>
+ <para>Compare for non-equality.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>=</option></term>
+ <listitem>
+ <para>Assign a value to a key. Keys that represent a list, are reset
+ and only this single value is assigned.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>+=</option></term>
+ <listitem>
+ <para>Add the value to a key that holds a list of entries.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>:=</option></term>
+ <listitem>
+ <para>Assign a value to a key finally; disallow any later changes,
+ which may be used to prevent changes by any later rules.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>The following key names can be used to match against device properties.
+ Some of the keys also match against properties of the parent devices in sysfs,
+ not only the device that has generated the event. If multiple keys that match
+ a parent device are specified in a single rule, all these keys must match at
+ one and the same parent device.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>ACTION</option></term>
+ <listitem>
+ <para>Match the name of the event action.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>DEVPATH</option></term>
+ <listitem>
+ <para>Match the devpath of the event device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>KERNEL</option></term>
+ <listitem>
+ <para>Match the name of the event device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>NAME</option></term>
+ <listitem>
+ <para>Match the name of the node or network interface. It can
+ be used once the NAME key has been set in one of the preceding
+ rules.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>SUBSYSTEM</option></term>
+ <listitem>
+ <para>Match the subsystem of the event device.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>DRIVER</option></term>
+ <listitem>
+ <para>Match the driver name of the event device. Only set for devices
+ which are bound to a driver at the time the event is generated.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>ATTR{<replaceable>filename</replaceable>}</option></term>
+ <listitem>
+ <para>Match sysfs attribute values of the event device. Up to five
+ <option>ATTR</option> keys can be specified per rule. Trailing
+ whitespace in the attribute values is ignored, if the specified match
+ value does not contain trailing whitespace itself. Depending on the type
+ of operator, this key is also used to set the value of a sysfs attribute.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>KERNELS</option></term>
+ <listitem>
+ <para>Search the devpath upwards for a matching device name.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>SUBSYSTEMS</option></term>
+ <listitem>
+ <para>Search the devpath upwards for a matching device subsystem name.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>DRIVERS</option></term>
+ <listitem>
+ <para>Search the devpath upwards for a matching device driver name.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>ATTRS{<replaceable>filename</replaceable>}</option></term>
+ <listitem>
+ <para>Search the devpath upwards for a device with matching sysfs attribute values.
+ Up to five <option>ATTRS</option> keys can be specified per rule, but all of them
+ must match on the same device. Trailing whitespace in the attribute values is ignored,
+ if the specified match value does not contain trailing whitespace itself.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+ <listitem>
+ <para>Match against the value of an environment variable. Up to five <option>ENV</option>
+ keys can be specified per rule. Depending on the type of operator, this key is also used
+ to export a variable to the environment.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>TEST{<replaceable>octal mode mask</replaceable>}</option></term>
+ <listitem>
+ <para>Test the existence of a file. An octal mode mask can be specified
+ if needed.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>PROGRAM</option></term>
+ <listitem>
+ <para>Execute external program. The key is true, if the program returns
+ with exit code zero. The whole event environment is available to the
+ executed program. The program's output printed to stdout, is available in
+ the RESULT key.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>RESULT</option></term>
+ <listitem>
+ <para>Match the returned string of the last PROGRAM call. This key can
+ be used in the same or in any later rule after a PROGRAM call.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>Most of the fields support a shell style pattern matching. The following
+ pattern characters are supported:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>*</option></term>
+ <listitem>
+ <para>Matches zero, or any number of characters.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>?</option></term>
+ <listitem>
+ <para>Matches any single character.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>[]</option></term>
+ <listitem>
+ <para>Matches any single character specified within the brackets. For
+ example, the pattern string 'tty[SR]' would match either 'ttyS' or 'ttyR'.
+ Ranges are also supported within this match with the '-' character.
+ For example, to match on the range of all digits, the pattern [0-9] would
+ be used. If the first character following the '[' is a '!', any characters
+ not enclosed are matched.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>The following keys can get values assigned:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>NAME</option></term>
+ <listitem>
+ <para>The name of the node to be created, or the name the network interface
+ should be renamed to. Only one rule can set the node name, all later rules with
+ a NAME key will be ignored.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>SYMLINK</option></term>
+ <listitem>
+ <para>The name of a symlink targeting the node. Every matching rule can add
+ this value to the list of symlinks to be created along with the device node.
+ Multiple symlinks may be specified by separating the names by the space
+ character.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>OWNER, GROUP, MODE</option></term>
+ <listitem>
+ <para>The permissions for the device node. Every specified value overwrites
+ the compiled-in default value.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>ATTR{<replaceable>key</replaceable>}</option></term>
+ <listitem>
+ <para>The value that should be written to a sysfs attribute of the
+ event device. Depending on the type of operator, this key is also
+ used to match against the value of a sysfs attribute.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>ENV{<replaceable>key</replaceable>}</option></term>
+ <listitem>
+ <para>Export a variable to the environment. Depending on the type of operator,
+ this key is also to match against an environment variable.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>RUN</option></term>
+ <listitem>
+ <para>Add a program to the list of programs to be executed for a specific
+ device. This can only be used for very short running tasks. Running an
+ event process for a long period of time may block all further events for
+ this or a dependent device. Long running tasks need to be immediately
+ detached from the event process itself.</para>
+ <para>If the specifiefd string starts with
+ <option>socket:<replaceable>path</replaceable></option>, all current event
+ values will be passed to the specified socket, as a message in the same
+ format the kernel sends an uevent. If the first character of the specified path
+ is an @ character, an abstract namespace socket is used, instead of an existing
+ socket file.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>LABEL</option></term>
+ <listitem>
+ <para>Named label where a GOTO can jump to.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>GOTO</option></term>
+ <listitem>
+ <para>Jumps to the next LABEL with a matching name</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>IMPORT{<replaceable>type</replaceable>}</option></term>
+ <listitem>
+ <para>Import a set of variables into the event environment,
+ depending on <replaceable>type</replaceable>:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>program</option></term>
+ <listitem>
+ <para>Execute an external program specified as the assigned value and
+ import its output, which must be in environment key format.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>file</option></term>
+ <listitem>
+ <para>Import a text file specified as the assigned value, which must be in
+ environment key format.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>parent</option></term>
+ <listitem>
+ <para>Import the stored keys from the parent device by reading
+ the database entry of the parent device. The value assigned to
+ <option>IMPORT{parent}</option> is used as a filter of key names
+ to import (with the same shell-style pattern matching used for
+ comparisons).</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>If no option is given, udev will choose between <option>program</option>
+ and <option>file</option> based on the executable bit of the file
+ permissions.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>WAIT_FOR</option></term>
+ <listitem>
+ <para>Wait for a file to become available.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>OPTIONS</option></term>
+ <listitem>
+ <para>Rule and device options:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>last_rule</option></term>
+ <listitem>
+ <para>Stops further rules application. No later rules will have
+ any effect.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>ignore_device</option></term>
+ <listitem>
+ <para>Ignore this event completely.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>ignore_remove</option></term>
+ <listitem>
+ <para>Do not remove the device node when the device goes away. This may be
+ useful as a workaround for broken device drivers.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>link_priority=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Specify the priority of the created symlinks. Devices with higher
+ priorities overwrite existing symlinks of other devices. The default is 0.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>all_partitions</option></term>
+ <listitem>
+ <para>Create the device nodes for all available partitions of a block device.
+ This may be useful for removable media devices where media changes are not
+ detected.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>event_timeout=</option></term>
+ <listitem>
+ <para>Number of seconds an event will wait for operations to finish, before it
+ will terminate itself.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>string_escape=<replaceable>none|replace</replaceable></option></term>
+ <listitem>
+ <para>Usually control and other possibly unsafe characters are replaced
+ in strings used for device naming. The mode of replacement can be specified
+ with this option.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>The <option>NAME</option>, <option>SYMLINK</option>, <option>PROGRAM</option>,
+ <option>OWNER</option>, <option>GROUP</option>, <option>MODE</option> and <option>RUN</option>
+ fields support simple printf-like string substitutions. The <option>RUN</option>
+ format chars gets applied after all rules have been processed, right before the program
+ is executed. It allows the use of the complete environment set by earlier matching
+ rules. For all other fields, substitutions are applied while the individual rule is
+ being processed. The available substitutions are:</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>$kernel</option>, <option>%k</option></term>
+ <listitem>
+ <para>The kernel name for this device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$number</option>, <option>%n</option></term>
+ <listitem>
+ <para>The kernel number for this device. For example, 'sda3' has
+ kernel number of '3'</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$devpath</option>, <option>%p</option></term>
+ <listitem>
+ <para>The devpath of the device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$id</option>, <option>%b</option></term>
+ <listitem>
+ <para>The name of the device matched while searching the devpath upwards for
+ <option>SUBSYSTEMS</option>, <option>KERNELS</option>, <option>DRIVERS</option> and <option>ATTRS</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$driver</option></term>
+ <listitem>
+ <para>The driver name of the device matched while searching the devpath upwards for
+ <option>SUBSYSTEMS</option>, <option>KERNELS</option>, <option>DRIVERS</option> and <option>ATTRS</option>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$attr{<replaceable>file</replaceable>}</option>, <option>%s{<replaceable>file</replaceable>}</option></term>
+ <listitem>
+ <para>The value of a sysfs attribute found at the device, where
+ all keys of the rule have matched. If the matching device does not have
+ such an attribute, follow the chain of parent devices and use the value
+ of the first attribute that matches.
+ If the attribute is a symlink, the last element of the symlink target is
+ returned as the value.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$env{<replaceable>key</replaceable>}</option>, <option>%E{<replaceable>key</replaceable>}</option></term>
+ <listitem>
+ <para>The value of an environment variable.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$major</option>, <option>%M</option></term>
+ <listitem>
+ <para>The kernel major number for the device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$minor</option>, <option>%m</option></term>
+ <listitem>
+ <para>The kernel minor number for the device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$result</option>, <option>%c</option></term>
+ <listitem>
+ <para>The string returned by the external program requested with PROGRAM.
+ A single part of the string, separated by a space character may be selected
+ by specifying the part number as an attribute: <option>%c{N}</option>.
+ If the number is followed by the '+' char this part plus all remaining parts
+ of the result string are substituted: <option>%c{N+}</option></para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$parent</option>, <option>%P</option></term>
+ <listitem>
+ <para>The node name of the parent device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$name</option></term>
+ <listitem>
+ <para>The current name of the device node. If not changed by a rule, it is the
+ name of the kernel device.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$links</option></term>
+ <listitem>
+ <para>The current list of symlinks, separated by a space character. The value is
+ only set if an earlier rule assigned a value, or during a remove events.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$root</option>, <option>%r</option></term>
+ <listitem>
+ <para>The udev_root value.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$sys</option>, <option>%S</option></term>
+ <listitem>
+ <para>The sysfs mount point.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$tempnode</option>, <option>%N</option></term>
+ <listitem>
+ <para>The name of a created temporary device node to provide access to
+ the device from a external program before the real node is created.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>%%</option></term>
+ <listitem>
+ <para>The '%' character itself.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>$$</option></term>
+ <listitem>
+ <para>The '$' character itself.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>The count of characters to be substituted may be limited by specifying
+ the format length value. For example, '%3s{file}' will only
+ insert the first three characters of the sysfs attribute</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1><title>AUTHOR</title>
+ <para>Written by Greg Kroah-Hartman <email>greg@kroah.com</email> and
+ Kay Sievers <email>kay.sievers@vrfy.org</email>. With much help from
+ Dan Stekloff and many others.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para><citerefentry>
+ <refentrytitle>udevd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/udev/udev_config.c b/udev/udev_config.c
new file mode 100644
index 0000000000..55f0361dd2
--- /dev/null
+++ b/udev/udev_config.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <syslog.h>
+
+#include "udev.h"
+
+/* global variables */
+char udev_root[PATH_SIZE];
+char udev_config_filename[PATH_SIZE];
+char udev_rules_dir[PATH_SIZE];
+int udev_log_priority;
+int udev_run;
+
+static int get_key(char **line, char **key, char **value)
+{
+ char *linepos;
+ char *temp;
+
+ linepos = *line;
+ if (!linepos)
+ return -1;
+
+ /* skip whitespace */
+ while (isspace(linepos[0]))
+ linepos++;
+
+ /* get the key */
+ *key = linepos;
+ while (1) {
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+ if (isspace(linepos[0]))
+ break;
+ if (linepos[0] == '=')
+ break;
+ }
+
+ /* terminate key */
+ linepos[0] = '\0';
+ linepos++;
+
+ /* skip whitespace */
+ while (isspace(linepos[0]))
+ linepos++;
+
+ /* get the value*/
+ if (linepos[0] == '"')
+ linepos++;
+ else
+ return -1;
+ *value = linepos;
+
+ temp = strchr(linepos, '"');
+ if (!temp)
+ return -1;
+ temp[0] = '\0';
+
+ return 0;
+}
+
+static int parse_config_file(void)
+{
+ char line[LINE_SIZE];
+ char *bufline;
+ char *linepos;
+ char *variable;
+ char *value;
+ char *buf;
+ size_t bufsize;
+ size_t cur;
+ size_t count;
+ int lineno;
+ int retval = 0;
+
+ if (file_map(udev_config_filename, &buf, &bufsize) != 0) {
+ err("can't open '%s' as config file: %s\n", udev_config_filename, strerror(errno));
+ return -ENODEV;
+ }
+
+ /* loop through the whole file */
+ lineno = 0;
+ cur = 0;
+ while (cur < bufsize) {
+ count = buf_get_line(buf, bufsize, cur);
+ bufline = &buf[cur];
+ cur += count+1;
+ lineno++;
+
+ /* eat the whitespace */
+ while ((count > 0) && isspace(bufline[0])) {
+ bufline++;
+ count--;
+ }
+ if (count == 0)
+ continue;
+
+ /* see if this is a comment */
+ if (bufline[0] == COMMENT_CHARACTER)
+ continue;
+
+ if (count >= sizeof(line)) {
+ err("line too long, conf line skipped %s, line %d\n", udev_config_filename, lineno);
+ continue;
+ }
+
+ memcpy(line, bufline, count);
+ line[count] = '\0';
+
+ linepos = line;
+ retval = get_key(&linepos, &variable, &value);
+ if (retval != 0) {
+ err("error parsing %s, line %d:%d\n", udev_config_filename, lineno, (int)(linepos-line));
+ continue;
+ }
+
+ if (strcasecmp(variable, "udev_root") == 0) {
+ strlcpy(udev_root, value, sizeof(udev_root));
+ remove_trailing_chars(udev_root, '/');
+ continue;
+ }
+
+ if (strcasecmp(variable, "udev_rules") == 0) {
+ strlcpy(udev_rules_dir, value, sizeof(udev_rules_dir));
+ remove_trailing_chars(udev_rules_dir, '/');
+ continue;
+ }
+
+ if (strcasecmp(variable, "udev_log") == 0) {
+ udev_log_priority = log_priority(value);
+ continue;
+ }
+ }
+
+ file_unmap(buf, bufsize);
+ return retval;
+}
+
+void udev_config_init(void)
+{
+ const char *env;
+
+ strcpy(udev_config_filename, UDEV_CONFIG_FILE);
+ strcpy(udev_root, UDEV_ROOT);
+ udev_rules_dir[0] = '\0';
+ udev_log_priority = LOG_ERR;
+ udev_run = 1;
+
+ /* disable RUN key execution */
+ env = getenv("UDEV_RUN");
+ if (env && !string_is_true(env))
+ udev_run = 0;
+
+ env = getenv("UDEV_CONFIG_FILE");
+ if (env) {
+ strlcpy(udev_config_filename, env, sizeof(udev_config_filename));
+ remove_trailing_chars(udev_config_filename, '/');
+ }
+
+ parse_config_file();
+
+ env = getenv("UDEV_ROOT");
+ if (env) {
+ strlcpy(udev_root, env, sizeof(udev_root));
+ remove_trailing_chars(udev_root, '/');
+ }
+
+ env = getenv("UDEV_LOG");
+ if (env)
+ udev_log_priority = log_priority(env);
+
+ dbg("UDEV_CONFIG_FILE='%s'\n", udev_config_filename);
+ dbg("udev_root='%s'\n", udev_root);
+ dbg("udev_rules_dir='%s'\n", udev_rules_dir);
+ dbg("udev_log=%d\n", udev_log_priority);
+}
diff --git a/udev/udev_db.c b/udev/udev_db.c
new file mode 100644
index 0000000000..3348c9a02c
--- /dev/null
+++ b/udev/udev_db.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+#include "udev_selinux.h"
+
+
+static size_t devpath_to_db_path(const char *devpath, char *filename, size_t len)
+{
+ size_t start;
+
+ /* translate to location of db file */
+ strlcpy(filename, udev_root, len);
+ start = strlcat(filename, "/"DB_DIR"/", len);
+ strlcat(filename, devpath, len);
+ return path_encode(&filename[start], len - start);
+}
+
+/* reverse mapping from the device file name to the devpath */
+static int name_index(const char *devpath, const char *name, int add)
+{
+ char device[PATH_SIZE];
+ char filename[PATH_SIZE * 2];
+ size_t start;
+ int fd;
+
+ /* directory with device name */
+ strlcpy(filename, udev_root, sizeof(filename));
+ start = strlcat(filename, "/"DB_NAME_INDEX_DIR"/", sizeof(filename));
+ strlcat(filename, name, sizeof(filename));
+ path_encode(&filename[start], sizeof(filename) - start);
+ /* entry with the devpath */
+ strlcpy(device, devpath, sizeof(device));
+ path_encode(device, sizeof(device));
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, device, sizeof(filename));
+
+ if (add) {
+ info("creating index: '%s'\n", filename);
+ create_path(filename);
+ fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0644);
+ if (fd > 0)
+ close(fd);
+ } else {
+ info("removing index: '%s'\n", filename);
+ unlink(filename);
+ delete_path(filename);
+ }
+ return 0;
+}
+
+int udev_db_get_devices_by_name(const char *name, struct list_head *name_list)
+{
+ char dirname[PATH_MAX];
+ size_t start;
+ DIR *dir;
+ int rc = 0;
+
+ strlcpy(dirname, udev_root, sizeof(dirname));
+ start = strlcat(dirname, "/"DB_NAME_INDEX_DIR"/", sizeof(dirname));
+ strlcat(dirname, name, sizeof(dirname));
+ path_encode(&dirname[start], sizeof(dirname) - start);
+
+ dir = opendir(dirname);
+ if (dir == NULL) {
+ info("no index directory '%s': %s\n", dirname, strerror(errno));
+ rc = -1;
+ goto out;
+ }
+
+ info("found index directory '%s'\n", dirname);
+ while (1) {
+ struct dirent *ent;
+ char device[PATH_SIZE];
+
+ ent = readdir(dir);
+ if (ent == NULL || ent->d_name[0] == '\0')
+ break;
+ if (ent->d_name[0] == '.')
+ continue;
+
+ strlcpy(device, ent->d_name, sizeof(device));
+ path_decode(device);
+ name_list_add(name_list, device, 0);
+ rc++;
+ }
+ closedir(dir);
+out:
+ return rc;
+}
+
+int udev_db_rename(const char *devpath_old, const char *devpath)
+{
+ char filename[PATH_SIZE];
+ char filename_old[PATH_SIZE];
+
+ devpath_to_db_path(devpath_old, filename_old, sizeof(filename_old));
+ devpath_to_db_path(devpath, filename, sizeof(filename));
+ return rename(filename_old, filename);
+}
+
+int udev_db_add_device(struct udevice *udev)
+{
+ char filename[PATH_SIZE];
+
+ if (udev->test_run)
+ return 0;
+
+ devpath_to_db_path(udev->dev->devpath, filename, sizeof(filename));
+ create_path(filename);
+ unlink(filename);
+
+ /*
+ * don't waste tmpfs memory pages, if we don't have any data to store
+ * create fake db-file; store the node-name in a symlink target
+ */
+ if (list_empty(&udev->symlink_list) && list_empty(&udev->env_list) &&
+ !udev->partitions && !udev->ignore_remove) {
+ int ret;
+ dbg("nothing interesting to store, create symlink\n");
+ selinux_setfscreatecon(filename, NULL, S_IFLNK);
+ ret = symlink(udev->name, filename);
+ selinux_resetfscreatecon();
+ if (ret != 0) {
+ err("unable to create db link '%s': %s\n", filename, strerror(errno));
+ return -1;
+ }
+ } else {
+ FILE *f;
+ struct name_entry *name_loop;
+
+ f = fopen(filename, "w");
+ if (f == NULL) {
+ err("unable to create db file '%s': %s\n", filename, strerror(errno));
+ return -1;
+ }
+ dbg("storing data for device '%s' in '%s'\n", udev->dev->devpath, filename);
+
+ fprintf(f, "N:%s\n", udev->name);
+ list_for_each_entry(name_loop, &udev->symlink_list, node) {
+ fprintf(f, "S:%s\n", name_loop->name);
+ /* add symlink-name to index */
+ name_index(udev->dev->devpath, name_loop->name, 1);
+ }
+ fprintf(f, "M:%u:%u\n", major(udev->devt), minor(udev->devt));
+ if (udev->link_priority != 0)
+ fprintf(f, "L:%u\n", udev->link_priority);
+ if (udev->event_timeout >= 0)
+ fprintf(f, "T:%u\n", udev->event_timeout);
+ if (udev->partitions != 0)
+ fprintf(f, "A:%u\n", udev->partitions);
+ if (udev->ignore_remove)
+ fprintf(f, "R:%u\n", udev->ignore_remove);
+ list_for_each_entry(name_loop, &udev->env_list, node)
+ fprintf(f, "E:%s\n", name_loop->name);
+ fclose(f);
+ }
+
+ /* add name to index */
+ name_index(udev->dev->devpath, udev->name, 1);
+
+ return 0;
+}
+
+int udev_db_get_device(struct udevice *udev, const char *devpath)
+{
+ struct stat stats;
+ char filename[PATH_SIZE];
+ char line[PATH_SIZE];
+ unsigned int maj, min;
+ char *bufline;
+ char *buf;
+ size_t bufsize;
+ size_t cur;
+ size_t count;
+
+ sysfs_device_set_values(udev->dev, devpath, NULL, NULL);
+ devpath_to_db_path(devpath, filename, sizeof(filename));
+
+ if (lstat(filename, &stats) != 0) {
+ info("no db file to read %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+ if ((stats.st_mode & S_IFMT) == S_IFLNK) {
+ char target[NAME_SIZE];
+ int target_len;
+
+ info("found a symlink as db file\n");
+ target_len = readlink(filename, target, sizeof(target));
+ if (target_len > 0)
+ target[target_len] = '\0';
+ else {
+ info("error reading db link %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+ dbg("db link points to '%s'\n", target);
+ strlcpy(udev->name, target, sizeof(udev->name));
+ return 0;
+ }
+
+ if (file_map(filename, &buf, &bufsize) != 0) {
+ info("error reading db file %s: %s\n", filename, strerror(errno));
+ return -1;
+ }
+
+ cur = 0;
+ while (cur < bufsize) {
+ count = buf_get_line(buf, bufsize, cur);
+ bufline = &buf[cur];
+ cur += count+1;
+
+ if (count > sizeof(line))
+ count = sizeof(line);
+ memcpy(line, &bufline[2], count-2);
+ line[count-2] = '\0';
+
+ switch(bufline[0]) {
+ case 'N':
+ strlcpy(udev->name, line, sizeof(udev->name));
+ break;
+ case 'M':
+ sscanf(line, "%u:%u", &maj, &min);
+ udev->devt = makedev(maj, min);
+ break;
+ case 'S':
+ name_list_add(&udev->symlink_list, line, 0);
+ break;
+ case 'L':
+ udev->link_priority = atoi(line);
+ break;
+ case 'T':
+ udev->event_timeout = atoi(line);
+ break;
+ case 'A':
+ udev->partitions = atoi(line);
+ break;
+ case 'R':
+ udev->ignore_remove = atoi(line);
+ break;
+ case 'E':
+ name_list_add(&udev->env_list, line, 0);
+ break;
+ }
+ }
+ file_unmap(buf, bufsize);
+
+ if (udev->name[0] == '\0')
+ return -1;
+
+ return 0;
+}
+
+int udev_db_delete_device(struct udevice *udev)
+{
+ char filename[PATH_SIZE];
+ struct name_entry *name_loop;
+
+ if (udev->test_run)
+ return 0;
+
+ devpath_to_db_path(udev->dev->devpath, filename, sizeof(filename));
+ unlink(filename);
+
+ name_index(udev->dev->devpath, udev->name, 0);
+ list_for_each_entry(name_loop, &udev->symlink_list, node)
+ name_index(udev->dev->devpath, name_loop->name, 0);
+
+ return 0;
+}
+
+int udev_db_get_all_entries(struct list_head *name_list)
+{
+ char dbpath[PATH_MAX];
+ DIR *dir;
+
+ strlcpy(dbpath, udev_root, sizeof(dbpath));
+ strlcat(dbpath, "/"DB_DIR, sizeof(dbpath));
+ dir = opendir(dbpath);
+ if (dir == NULL) {
+ info("no udev_db available '%s': %s\n", dbpath, strerror(errno));
+ return -1;
+ }
+
+ while (1) {
+ struct dirent *ent;
+ char device[PATH_SIZE];
+
+ ent = readdir(dir);
+ if (ent == NULL || ent->d_name[0] == '\0')
+ break;
+ if (ent->d_name[0] == '.')
+ continue;
+
+ strlcpy(device, ent->d_name, sizeof(device));
+ path_decode(device);
+ name_list_add(name_list, device, 1);
+ dbg("added '%s'\n", device);
+ }
+
+ closedir(dir);
+ return 0;
+}
diff --git a/udev/udev_device.c b/udev/udev_device.c
new file mode 100644
index 0000000000..cf21191ca0
--- /dev/null
+++ b/udev/udev_device.c
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <linux/sockios.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+
+
+struct udevice *udev_device_init(struct udevice *udev)
+{
+ if (udev == NULL)
+ udev = malloc(sizeof(struct udevice));
+ if (udev == NULL)
+ return NULL;
+ memset(udev, 0x00, sizeof(struct udevice));
+
+ INIT_LIST_HEAD(&udev->symlink_list);
+ INIT_LIST_HEAD(&udev->run_list);
+ INIT_LIST_HEAD(&udev->env_list);
+
+ /* set sysfs device to local storage, can be overridden if needed */
+ udev->dev = &udev->dev_local;
+
+ /* default node permissions */
+ udev->mode = 0660;
+ strcpy(udev->owner, "root");
+ strcpy(udev->group, "root");
+
+ udev->event_timeout = -1;
+
+ return udev;
+}
+
+void udev_device_cleanup(struct udevice *udev)
+{
+ name_list_cleanup(&udev->symlink_list);
+ name_list_cleanup(&udev->run_list);
+ name_list_cleanup(&udev->env_list);
+ free(udev);
+}
+
+dev_t udev_device_get_devt(struct udevice *udev)
+{
+ const char *attr;
+ unsigned int maj, min;
+
+ /* read it from sysfs */
+ attr = sysfs_attr_get_value(udev->dev->devpath, "dev");
+ if (attr != NULL) {
+ if (sscanf(attr, "%u:%u", &maj, &min) == 2)
+ return makedev(maj, min);
+ }
+ return makedev(0, 0);
+}
+
+static void kernel_log(struct ifreq ifr)
+{
+ int klog;
+ FILE *f;
+
+ klog = open("/dev/kmsg", O_WRONLY);
+ if (klog < 0)
+ return;
+
+ f = fdopen(klog, "w");
+ if (f == NULL) {
+ close(klog);
+ return;
+ }
+
+ fprintf(f, "<6>udev: renamed network interface %s to %s\n",
+ ifr.ifr_name, ifr.ifr_newname);
+ fclose(f);
+}
+
+static int rename_netif(struct udevice *udev)
+{
+ int sk;
+ struct ifreq ifr;
+ int retval;
+
+ info("changing net interface name from '%s' to '%s'\n", udev->dev->kernel, udev->name);
+ if (udev->test_run)
+ return 0;
+
+ sk = socket(PF_INET, SOCK_DGRAM, 0);
+ if (sk < 0) {
+ err("error opening socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ memset(&ifr, 0x00, sizeof(struct ifreq));
+ strlcpy(ifr.ifr_name, udev->dev->kernel, IFNAMSIZ);
+ strlcpy(ifr.ifr_newname, udev->name, IFNAMSIZ);
+ retval = ioctl(sk, SIOCSIFNAME, &ifr);
+ if (retval == 0)
+ kernel_log(ifr);
+ else {
+ int loop;
+
+ /* see if the destination interface name already exists */
+ if (errno != EEXIST) {
+ err("error changing netif name %s to %s: %s\n", ifr.ifr_name, ifr.ifr_newname, strerror(errno));
+ goto exit;
+ }
+
+ /* free our own name, another process may wait for us */
+ strlcpy(ifr.ifr_newname, udev->dev->kernel, IFNAMSIZ);
+ strlcat(ifr.ifr_newname, "_rename", IFNAMSIZ);
+ retval = ioctl(sk, SIOCSIFNAME, &ifr);
+ if (retval != 0) {
+ err("error changing netif name %s to %s: %s\n", ifr.ifr_name, ifr.ifr_newname, strerror(errno));
+ goto exit;
+ }
+
+ /* wait 30 seconds for our target to become available */
+ strlcpy(ifr.ifr_name, ifr.ifr_newname, IFNAMSIZ);
+ strlcpy(ifr.ifr_newname, udev->name, IFNAMSIZ);
+ loop = 30 * 20;
+ while (loop--) {
+ retval = ioctl(sk, SIOCSIFNAME, &ifr);
+ if (retval == 0) {
+ kernel_log(ifr);
+ break;
+ }
+
+ if (errno != EEXIST) {
+ err("error changing net interface name %s to %s: %s\n",
+ ifr.ifr_name, ifr.ifr_newname, strerror(errno));
+ break;
+ }
+ dbg("wait for netif '%s' to become free, loop=%i\n", udev->name, (30 * 20) - loop);
+ usleep(1000 * 1000 / 20);
+ }
+ }
+
+exit:
+ close(sk);
+ return retval;
+}
+
+int udev_device_event(struct udev_rules *rules, struct udevice *udev)
+{
+ int retval = 0;
+
+ if (udev->devpath_old != NULL)
+ if (udev_db_rename(udev->devpath_old, udev->dev->devpath) == 0)
+ info("moved database from '%s' to '%s'\n", udev->devpath_old, udev->dev->devpath);
+
+ /* add device node */
+ if (major(udev->devt) != 0 &&
+ (strcmp(udev->action, "add") == 0 || strcmp(udev->action, "change") == 0)) {
+ struct udevice *udev_old;
+
+ dbg("device node add '%s'\n", udev->dev->devpath);
+
+ udev_rules_get_name(rules, udev);
+ if (udev->ignore_device) {
+ info("device event will be ignored\n");
+ goto exit;
+ }
+ if (udev->name[0] == '\0') {
+ info("device node creation supressed\n");
+ goto exit;
+ }
+
+ /* read current database entry; cleanup, if it is known device */
+ udev_old = udev_device_init(NULL);
+ if (udev_old != NULL) {
+ udev_old->test_run = udev->test_run;
+ if (udev_db_get_device(udev_old, udev->dev->devpath) == 0) {
+ info("device '%s' already in database, cleanup\n", udev->dev->devpath);
+ udev_db_delete_device(udev_old);
+ } else {
+ udev_device_cleanup(udev_old);
+ udev_old = NULL;
+ }
+ }
+
+ /* create node */
+ retval = udev_node_add(udev);
+ if (retval != 0)
+ goto exit;
+
+ /* store in database */
+ udev_db_add_device(udev);
+
+ /* create, replace, delete symlinks according to priority */
+ udev_node_update_symlinks(udev, udev_old);
+
+ if (udev_old != NULL)
+ udev_device_cleanup(udev_old);
+ goto exit;
+ }
+
+ /* add netif */
+ if (strcmp(udev->dev->subsystem, "net") == 0 && strcmp(udev->action, "add") == 0) {
+ dbg("netif add '%s'\n", udev->dev->devpath);
+ udev_rules_get_name(rules, udev);
+ if (udev->ignore_device) {
+ info("device event will be ignored\n");
+ goto exit;
+ }
+ if (udev->name[0] == '\0') {
+ info("device renaming supressed\n");
+ goto exit;
+ }
+
+ /* look if we want to change the name of the netif */
+ if (strcmp(udev->name, udev->dev->kernel) != 0) {
+ char devpath[PATH_MAX];
+ char *pos;
+
+ retval = rename_netif(udev);
+ if (retval != 0)
+ goto exit;
+ info("renamed netif to '%s'\n", udev->name);
+
+ /* export old name */
+ setenv("INTERFACE_OLD", udev->dev->kernel, 1);
+
+ /* now change the devpath, because the kernel device name has changed */
+ strlcpy(devpath, udev->dev->devpath, sizeof(devpath));
+ pos = strrchr(devpath, '/');
+ if (pos != NULL) {
+ pos[1] = '\0';
+ strlcat(devpath, udev->name, sizeof(devpath));
+ sysfs_device_set_values(udev->dev, devpath, NULL, NULL);
+ setenv("DEVPATH", udev->dev->devpath, 1);
+ setenv("INTERFACE", udev->name, 1);
+ info("changed devpath to '%s'\n", udev->dev->devpath);
+ }
+ }
+ goto exit;
+ }
+
+ /* remove device node */
+ if (major(udev->devt) != 0 && strcmp(udev->action, "remove") == 0) {
+ struct name_entry *name_loop;
+
+ /* import database entry, and delete it */
+ if (udev_db_get_device(udev, udev->dev->devpath) == 0) {
+ udev_db_delete_device(udev);
+ /* restore stored persistent data */
+ list_for_each_entry(name_loop, &udev->env_list, node)
+ putenv(name_loop->name);
+ } else {
+ dbg("'%s' not found in database, using kernel name '%s'\n",
+ udev->dev->devpath, udev->dev->kernel);
+ strlcpy(udev->name, udev->dev->kernel, sizeof(udev->name));
+ }
+
+ udev_rules_get_run(rules, udev);
+ if (udev->ignore_device) {
+ info("device event will be ignored\n");
+ goto exit;
+ }
+
+ if (udev->ignore_remove) {
+ info("ignore_remove for '%s'\n", udev->name);
+ goto exit;
+ }
+ /* remove the node */
+ retval = udev_node_remove(udev);
+
+ /* delete or restore symlinks according to priority */
+ udev_node_update_symlinks(udev, NULL);
+ goto exit;
+ }
+
+ /* default devices */
+ udev_rules_get_run(rules, udev);
+ if (udev->ignore_device)
+ info("device event will be ignored\n");
+
+exit:
+ return retval;
+}
diff --git a/udev/udev_node.c b/udev/udev_node.c
new file mode 100644
index 0000000000..78b6747043
--- /dev/null
+++ b/udev/udev_node.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+#include "udev_selinux.h"
+
+#define TMP_FILE_EXT ".udev-tmp"
+
+int udev_node_mknod(struct udevice *udev, const char *file, dev_t devt, mode_t mode, uid_t uid, gid_t gid)
+{
+ char file_tmp[PATH_SIZE + sizeof(TMP_FILE_EXT)];
+ struct stat stats;
+ int preserve = 0;
+ int err = 0;
+
+ if (major(devt) != 0 && strcmp(udev->dev->subsystem, "block") == 0)
+ mode |= S_IFBLK;
+ else
+ mode |= S_IFCHR;
+
+ if (lstat(file, &stats) == 0) {
+ if (((stats.st_mode & S_IFMT) == (mode & S_IFMT)) && (stats.st_rdev == devt)) {
+ info("preserve file '%s', because it has correct dev_t\n", file);
+ preserve = 1;
+ selinux_setfilecon(file, udev->dev->kernel, mode);
+ } else {
+ info("atomically replace existing file '%s'\n", file);
+ strlcpy(file_tmp, file, sizeof(file_tmp));
+ strlcat(file_tmp, TMP_FILE_EXT, sizeof(file_tmp));
+ unlink(file_tmp);
+ selinux_setfscreatecon(file_tmp, udev->dev->kernel, mode);
+ err = mknod(file_tmp, mode, devt);
+ selinux_resetfscreatecon();
+ if (err != 0) {
+ err("mknod(%s, %#o, %u, %u) failed: %s\n",
+ file_tmp, mode, major(devt), minor(devt), strerror(errno));
+ goto exit;
+ }
+ err = rename(file_tmp, file);
+ if (err != 0) {
+ err("rename(%s, %s) failed: %s\n",
+ file_tmp, file, strerror(errno));
+ unlink(file_tmp);
+ }
+ }
+ } else {
+ info("mknod(%s, %#o, (%u,%u))\n", file, mode, major(devt), minor(devt));
+ selinux_setfscreatecon(file, udev->dev->kernel, mode);
+ err = mknod(file, mode, devt);
+ selinux_resetfscreatecon();
+ if (err != 0) {
+ err("mknod(%s, %#o, (%u,%u) failed: %s\n",
+ file, mode, major(devt), minor(devt), strerror(errno));
+ goto exit;
+ }
+ }
+
+ if (!preserve || stats.st_mode != mode) {
+ info("chmod(%s, %#o)\n", file, mode);
+ err = chmod(file, mode);
+ if (err != 0) {
+ err("chmod(%s, %#o) failed: %s\n", file, mode, strerror(errno));
+ goto exit;
+ }
+ }
+
+ if (!preserve || stats.st_uid != uid || stats.st_gid != gid) {
+ info("chown(%s, %u, %u)\n", file, uid, gid);
+ err = chown(file, uid, gid);
+ if (err != 0) {
+ err("chown(%s, %u, %u) failed: %s\n", file, uid, gid, strerror(errno));
+ goto exit;
+ }
+ }
+exit:
+ return err;
+}
+
+static int node_symlink(const char *node, const char *slink)
+{
+ struct stat stats;
+ char target[PATH_SIZE] = "";
+ char slink_tmp[PATH_SIZE + sizeof(TMP_FILE_EXT)];
+ int i = 0;
+ int tail = 0;
+ int len;
+ int retval = 0;
+
+ /* use relative link */
+ while (node[i] && (node[i] == slink[i])) {
+ if (node[i] == '/')
+ tail = i+1;
+ i++;
+ }
+ while (slink[i] != '\0') {
+ if (slink[i] == '/')
+ strlcat(target, "../", sizeof(target));
+ i++;
+ }
+ strlcat(target, &node[tail], sizeof(target));
+
+ /* preserve link with correct target, do not replace node of other device */
+ if (lstat(slink, &stats) == 0) {
+ if (S_ISBLK(stats.st_mode) || S_ISCHR(stats.st_mode)) {
+ struct stat stats2;
+
+ info("found existing node instead of symlink '%s'\n", slink);
+ if (lstat(node, &stats2) == 0) {
+ if ((stats.st_mode & S_IFMT) == (stats2.st_mode & S_IFMT) &&
+ stats.st_rdev == stats2.st_rdev) {
+ info("replace device node '%s' with symlink to our node '%s'\n", slink, node);
+ } else {
+ err("device node '%s' already exists, link to '%s' will not overwrite it\n", slink, node);
+ goto exit;
+ }
+ }
+ } else if (S_ISLNK(stats.st_mode)) {
+ char buf[PATH_SIZE];
+
+ info("found existing symlink '%s'\n", slink);
+ len = readlink(slink, buf, sizeof(buf));
+ if (len > 0) {
+ buf[len] = '\0';
+ if (strcmp(target, buf) == 0) {
+ info("preserve already existing symlink '%s' to '%s'\n", slink, target);
+ selinux_setfilecon(slink, NULL, S_IFLNK);
+ goto exit;
+ }
+ }
+ }
+ } else {
+ info("creating symlink '%s' to '%s'\n", slink, target);
+ selinux_setfscreatecon(slink, NULL, S_IFLNK);
+ retval = symlink(target, slink);
+ selinux_resetfscreatecon();
+ if (retval == 0)
+ goto exit;
+ }
+
+ info("atomically replace '%s'\n", slink);
+ strlcpy(slink_tmp, slink, sizeof(slink_tmp));
+ strlcat(slink_tmp, TMP_FILE_EXT, sizeof(slink_tmp));
+ unlink(slink_tmp);
+ selinux_setfscreatecon(slink, NULL, S_IFLNK);
+ retval = symlink(target, slink_tmp);
+ selinux_resetfscreatecon();
+ if (retval != 0) {
+ err("symlink(%s, %s) failed: %s\n", target, slink_tmp, strerror(errno));
+ goto exit;
+ }
+ retval = rename(slink_tmp, slink);
+ if (retval != 0) {
+ err("rename(%s, %s) failed: %s\n", slink_tmp, slink, strerror(errno));
+ unlink(slink_tmp);
+ goto exit;
+ }
+exit:
+ return retval;
+}
+
+static int update_link(struct udevice *udev, const char *name)
+{
+ LIST_HEAD(name_list);
+ char slink[PATH_SIZE];
+ char node[PATH_SIZE];
+ struct udevice *udev_db;
+ struct name_entry *device;
+ char target[PATH_MAX] = "";
+ int count;
+ int priority = 0;
+ int rc = 0;
+
+ strlcpy(slink, udev_root, sizeof(slink));
+ strlcat(slink, "/", sizeof(slink));
+ strlcat(slink, name, sizeof(slink));
+
+ count = udev_db_get_devices_by_name(name, &name_list);
+ info("found %i devices with name '%s'\n", count, name);
+
+ /* if we don't have a reference, delete it */
+ if (count <= 0) {
+ info("no reference left, remove '%s'\n", name);
+ if (!udev->test_run) {
+ unlink(slink);
+ delete_path(slink);
+ }
+ goto out;
+ }
+
+ /* find the device with the highest priority */
+ list_for_each_entry(device, &name_list, node) {
+ info("found '%s' for '%s'\n", device->name, name);
+
+ /* did we find ourself? we win, if we have the same priority */
+ if (strcmp(udev->dev->devpath, device->name) == 0) {
+ info("compare (our own) priority of '%s' %i >= %i\n",
+ udev->dev->devpath, udev->link_priority, priority);
+ if (strcmp(udev->name, name) == 0) {
+ info("'%s' is our device node, database inconsistent, skip link update\n", udev->name);
+ } else if (target[0] == '\0' || udev->link_priority >= priority) {
+ priority = udev->link_priority;
+ strlcpy(target, udev->name, sizeof(target));
+ }
+ continue;
+ }
+
+ /* another device, read priority from database */
+ udev_db = udev_device_init(NULL);
+ if (udev_db == NULL)
+ continue;
+ if (udev_db_get_device(udev_db, device->name) == 0) {
+ if (strcmp(udev_db->name, name) == 0) {
+ info("'%s' is a device node of '%s', skip link update\n", udev_db->name, device->name);
+ } else {
+ info("compare priority of '%s' %i > %i\n",
+ udev_db->dev->devpath, udev_db->link_priority, priority);
+ if (target[0] == '\0' || udev_db->link_priority > priority) {
+ priority = udev_db->link_priority;
+ strlcpy(target, udev_db->name, sizeof(target));
+ }
+ }
+ }
+ udev_device_cleanup(udev_db);
+ }
+ name_list_cleanup(&name_list);
+
+ if (target[0] == '\0') {
+ info("no current target for '%s' found\n", name);
+ rc = 1;
+ goto out;
+ }
+
+ /* create symlink to the target with the highest priority */
+ strlcpy(node, udev_root, sizeof(node));
+ strlcat(node, "/", sizeof(node));
+ strlcat(node, target, sizeof(node));
+ info("'%s' with target '%s' has the highest priority %i, create it\n", name, target, priority);
+ if (!udev->test_run) {
+ create_path(slink);
+ node_symlink(node, slink);
+ }
+out:
+ return rc;
+}
+
+void udev_node_update_symlinks(struct udevice *udev, struct udevice *udev_old)
+{
+ struct name_entry *name_loop;
+ char symlinks[PATH_SIZE] = "";
+
+ list_for_each_entry(name_loop, &udev->symlink_list, node) {
+ info("update symlink '%s' of '%s'\n", name_loop->name, udev->dev->devpath);
+ update_link(udev, name_loop->name);
+ strlcat(symlinks, udev_root, sizeof(symlinks));
+ strlcat(symlinks, "/", sizeof(symlinks));
+ strlcat(symlinks, name_loop->name, sizeof(symlinks));
+ strlcat(symlinks, " ", sizeof(symlinks));
+ }
+
+ /* export symlinks to environment */
+ remove_trailing_chars(symlinks, ' ');
+ if (symlinks[0] != '\0')
+ setenv("DEVLINKS", symlinks, 1);
+
+ /* update possible left-over symlinks (device metadata changed) */
+ if (udev_old != NULL) {
+ struct name_entry *link_loop;
+ struct name_entry *link_old_loop;
+ int found;
+
+ /* remove current symlinks from old list */
+ list_for_each_entry(link_old_loop, &udev_old->symlink_list, node) {
+ found = 0;
+ list_for_each_entry(link_loop, &udev->symlink_list, node) {
+ if (strcmp(link_old_loop->name, link_loop->name) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ /* link does no longer belong to this device */
+ info("update old symlink '%s' no longer belonging to '%s'\n",
+ link_old_loop->name, udev->dev->devpath);
+ update_link(udev, link_old_loop->name);
+ }
+ }
+
+ /*
+ * if the node name has changed, delete the node,
+ * or possibly restore a symlink of another device
+ */
+ if (strcmp(udev->name, udev_old->name) != 0)
+ update_link(udev, udev_old->name);
+ }
+}
+
+int udev_node_add(struct udevice *udev)
+{
+ char filename[PATH_SIZE];
+ uid_t uid;
+ gid_t gid;
+ int i;
+ int retval = 0;
+
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, udev->name, sizeof(filename));
+ create_path(filename);
+
+ if (strcmp(udev->owner, "root") == 0)
+ uid = 0;
+ else {
+ char *endptr;
+ unsigned long id;
+
+ id = strtoul(udev->owner, &endptr, 10);
+ if (endptr[0] == '\0')
+ uid = (uid_t) id;
+ else
+ uid = lookup_user(udev->owner);
+ }
+
+ if (strcmp(udev->group, "root") == 0)
+ gid = 0;
+ else {
+ char *endptr;
+ unsigned long id;
+
+ id = strtoul(udev->group, &endptr, 10);
+ if (endptr[0] == '\0')
+ gid = (gid_t) id;
+ else
+ gid = lookup_group(udev->group);
+ }
+
+ info("creating device node '%s', major=%d, minor=%d, mode=%#o, uid=%d, gid=%d\n",
+ filename, major(udev->devt), minor(udev->devt), udev->mode, uid, gid);
+
+ if (!udev->test_run)
+ if (udev_node_mknod(udev, filename, udev->devt, udev->mode, uid, gid) != 0) {
+ retval = -1;
+ goto exit;
+ }
+
+ setenv("DEVNAME", filename, 1);
+
+ /* create all_partitions if requested */
+ if (udev->partitions) {
+ char partitionname[PATH_SIZE];
+ char *attr;
+ int range;
+
+ /* take the maximum registered minor range */
+ attr = sysfs_attr_get_value(udev->dev->devpath, "range");
+ if (attr != NULL) {
+ range = atoi(attr);
+ if (range > 1)
+ udev->partitions = range-1;
+ }
+ info("creating device partition nodes '%s[1-%i]'\n", filename, udev->partitions);
+ if (!udev->test_run) {
+ for (i = 1; i <= udev->partitions; i++) {
+ dev_t part_devt;
+
+ snprintf(partitionname, sizeof(partitionname), "%s%d", filename, i);
+ partitionname[sizeof(partitionname)-1] = '\0';
+ part_devt = makedev(major(udev->devt), minor(udev->devt) + i);
+ udev_node_mknod(udev, partitionname, part_devt, udev->mode, uid, gid);
+ }
+ }
+ }
+exit:
+ return retval;
+}
+
+int udev_node_remove(struct udevice *udev)
+{
+ char filename[PATH_SIZE];
+ char partitionname[PATH_SIZE];
+ struct stat stats;
+ int retval = 0;
+ int num;
+
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, udev->name, sizeof(filename));
+ if (stat(filename, &stats) != 0) {
+ info("device node '%s' not found\n", filename);
+ return 0;
+ }
+ if (udev->devt && stats.st_rdev != udev->devt) {
+ info("device node '%s' points to a different device, skip removal\n", filename);
+ return -1;
+ }
+
+ info("removing device node '%s'\n", filename);
+ if (!udev->test_run)
+ retval = unlink_secure(filename);
+ if (retval)
+ return retval;
+
+ setenv("DEVNAME", filename, 1);
+ num = udev->partitions;
+ if (num > 0) {
+ int i;
+
+ info("removing all_partitions '%s[1-%i]'\n", filename, num);
+ if (num > 255)
+ return -1;
+ for (i = 1; i <= num; i++) {
+ snprintf(partitionname, sizeof(partitionname), "%s%d", filename, i);
+ partitionname[sizeof(partitionname)-1] = '\0';
+ if (!udev->test_run)
+ unlink_secure(partitionname);
+ }
+ }
+ delete_path(filename);
+ return retval;
+}
diff --git a/udev/udev_rules.c b/udev/udev_rules.c
new file mode 100644
index 0000000000..ea850a84c3
--- /dev/null
+++ b/udev/udev_rules.c
@@ -0,0 +1,1618 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2003-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <syslog.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+#include "udev_selinux.h"
+
+extern char **environ;
+
+/* extract possible {attr} and move str behind it */
+static char *get_format_attribute(char **str)
+{
+ char *pos;
+ char *attr = NULL;
+
+ if (*str[0] == '{') {
+ pos = strchr(*str, '}');
+ if (pos == NULL) {
+ err("missing closing brace for format\n");
+ return NULL;
+ }
+ pos[0] = '\0';
+ attr = *str+1;
+ *str = pos+1;
+ dbg("attribute='%s', str='%s'\n", attr, *str);
+ }
+ return attr;
+}
+
+/* extract possible format length and move str behind it*/
+static int get_format_len(char **str)
+{
+ int num;
+ char *tail;
+
+ if (isdigit(*str[0])) {
+ num = (int) strtoul(*str, &tail, 10);
+ if (num > 0) {
+ *str = tail;
+ dbg("format length=%i\n", num);
+ return num;
+ } else {
+ err("format parsing error '%s'\n", *str);
+ }
+ }
+ return -1;
+}
+
+static int get_key(char **line, char **key, char **value)
+{
+ char *linepos;
+ char *temp;
+
+ linepos = *line;
+ if (linepos == NULL)
+ return -1;
+
+ /* skip whitespace */
+ while (isspace(linepos[0]))
+ linepos++;
+
+ /* get the key */
+ temp = strchr(linepos, '=');
+ if (temp == NULL || temp == linepos)
+ return -1;
+ temp[0] = '\0';
+ *key = linepos;
+ linepos = &temp[1];
+
+ /* get a quoted value */
+ if (linepos[0] == '"' || linepos[0] == '\'') {
+ temp = strchr(&linepos[1], linepos[0]);
+ if (temp != NULL) {
+ temp[0] = '\0';
+ *value = &linepos[1];
+ goto out;
+ }
+ }
+
+ /* get the value*/
+ temp = strchr(linepos, '\n');
+ if (temp != NULL)
+ temp[0] = '\0';
+ *value = linepos;
+out:
+ return 0;
+}
+
+static int run_program(const char *command, const char *subsystem,
+ char *result, size_t ressize, size_t *reslen)
+{
+ int status;
+ int outpipe[2] = {-1, -1};
+ int errpipe[2] = {-1, -1};
+ pid_t pid;
+ char arg[PATH_SIZE];
+ char program[PATH_SIZE];
+ char *argv[(sizeof(arg) / 2) + 1];
+ int devnull;
+ int i;
+ int retval = 0;
+
+ /* build argv from comand */
+ strlcpy(arg, command, sizeof(arg));
+ i = 0;
+ if (strchr(arg, ' ') != NULL) {
+ char *pos = arg;
+
+ while (pos != NULL) {
+ if (pos[0] == '\'') {
+ /* don't separate if in apostrophes */
+ pos++;
+ argv[i] = strsep(&pos, "\'");
+ while (pos != NULL && pos[0] == ' ')
+ pos++;
+ } else {
+ argv[i] = strsep(&pos, " ");
+ }
+ dbg("arg[%i] '%s'\n", i, argv[i]);
+ i++;
+ }
+ argv[i] = NULL;
+ } else {
+ argv[0] = arg;
+ argv[1] = NULL;
+ }
+ info("'%s'\n", command);
+
+ /* prepare pipes from child to parent */
+ if (result != NULL || udev_log_priority >= LOG_INFO) {
+ if (pipe(outpipe) != 0) {
+ err("pipe failed: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+ if (udev_log_priority >= LOG_INFO) {
+ if (pipe(errpipe) != 0) {
+ err("pipe failed: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* allow programs in /lib/udev called without the path */
+ if (strchr(argv[0], '/') == NULL) {
+ strlcpy(program, "/lib/udev/", sizeof(program));
+ strlcat(program, argv[0], sizeof(program));
+ argv[0] = program;
+ }
+
+ pid = fork();
+ switch(pid) {
+ case 0:
+ /* child closes parent ends of pipes */
+ if (outpipe[READ_END] > 0)
+ close(outpipe[READ_END]);
+ if (errpipe[READ_END] > 0)
+ close(errpipe[READ_END]);
+
+ /* discard child output or connect to pipe */
+ devnull = open("/dev/null", O_RDWR);
+ if (devnull > 0) {
+ dup2(devnull, STDIN_FILENO);
+ if (outpipe[WRITE_END] < 0)
+ dup2(devnull, STDOUT_FILENO);
+ if (errpipe[WRITE_END] < 0)
+ dup2(devnull, STDERR_FILENO);
+ close(devnull);
+ } else
+ err("open /dev/null failed: %s\n", strerror(errno));
+ if (outpipe[WRITE_END] > 0) {
+ dup2(outpipe[WRITE_END], STDOUT_FILENO);
+ close(outpipe[WRITE_END]);
+ }
+ if (errpipe[WRITE_END] > 0) {
+ dup2(errpipe[WRITE_END], STDERR_FILENO);
+ close(errpipe[WRITE_END]);
+ }
+ execv(argv[0], argv);
+ if (errno == ENOENT || errno == ENOTDIR) {
+ /* may be on a filesytem which is not mounted right now */
+ info("program '%s' not found\n", argv[0]);
+ } else {
+ /* other problems */
+ err("exec of program '%s' failed\n", argv[0]);
+ }
+ _exit(1);
+ case -1:
+ err("fork of '%s' failed: %s\n", argv[0], strerror(errno));
+ return -1;
+ default:
+ /* read from child if requested */
+ if (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+ ssize_t count;
+ size_t respos = 0;
+
+ /* parent closes child ends of pipes */
+ if (outpipe[WRITE_END] > 0)
+ close(outpipe[WRITE_END]);
+ if (errpipe[WRITE_END] > 0)
+ close(errpipe[WRITE_END]);
+
+ /* read child output */
+ while (outpipe[READ_END] > 0 || errpipe[READ_END] > 0) {
+ int fdcount;
+ fd_set readfds;
+
+ FD_ZERO(&readfds);
+ if (outpipe[READ_END] > 0)
+ FD_SET(outpipe[READ_END], &readfds);
+ if (errpipe[READ_END] > 0)
+ FD_SET(errpipe[READ_END], &readfds);
+ fdcount = select(UDEV_MAX(outpipe[READ_END], errpipe[READ_END])+1, &readfds, NULL, NULL, NULL);
+ if (fdcount < 0) {
+ if (errno == EINTR)
+ continue;
+ retval = -1;
+ break;
+ }
+
+ /* get stdout */
+ if (outpipe[READ_END] > 0 && FD_ISSET(outpipe[READ_END], &readfds)) {
+ char inbuf[1024];
+ char *pos;
+ char *line;
+
+ count = read(outpipe[READ_END], inbuf, sizeof(inbuf)-1);
+ if (count <= 0) {
+ close(outpipe[READ_END]);
+ outpipe[READ_END] = -1;
+ if (count < 0) {
+ err("stdin read failed: %s\n", strerror(errno));
+ retval = -1;
+ }
+ continue;
+ }
+ inbuf[count] = '\0';
+
+ /* store result for rule processing */
+ if (result) {
+ if (respos + count < ressize) {
+ memcpy(&result[respos], inbuf, count);
+ respos += count;
+ } else {
+ err("ressize %ld too short\n", (long)ressize);
+ retval = -1;
+ }
+ }
+ pos = inbuf;
+ while ((line = strsep(&pos, "\n")))
+ if (pos || line[0] != '\0')
+ info("'%s' (stdout) '%s'\n", argv[0], line);
+ }
+
+ /* get stderr */
+ if (errpipe[READ_END] > 0 && FD_ISSET(errpipe[READ_END], &readfds)) {
+ char errbuf[1024];
+ char *pos;
+ char *line;
+
+ count = read(errpipe[READ_END], errbuf, sizeof(errbuf)-1);
+ if (count <= 0) {
+ close(errpipe[READ_END]);
+ errpipe[READ_END] = -1;
+ if (count < 0)
+ err("stderr read failed: %s\n", strerror(errno));
+ continue;
+ }
+ errbuf[count] = '\0';
+ pos = errbuf;
+ while ((line = strsep(&pos, "\n")))
+ if (pos || line[0] != '\0')
+ info("'%s' (stderr) '%s'\n", argv[0], line);
+ }
+ }
+ if (outpipe[READ_END] > 0)
+ close(outpipe[READ_END]);
+ if (errpipe[READ_END] > 0)
+ close(errpipe[READ_END]);
+
+ /* return the childs stdout string */
+ if (result) {
+ result[respos] = '\0';
+ dbg("result='%s'\n", result);
+ if (reslen)
+ *reslen = respos;
+ }
+ }
+ waitpid(pid, &status, 0);
+ if (WIFEXITED(status)) {
+ info("'%s' returned with status %i\n", argv[0], WEXITSTATUS(status));
+ if (WEXITSTATUS(status) != 0)
+ retval = -1;
+ } else {
+ err("'%s' abnormal exit\n", argv[0]);
+ retval = -1;
+ }
+ }
+
+ return retval;
+}
+
+static int import_keys_into_env(struct udevice *udev, const char *buf, size_t bufsize)
+{
+ char line[LINE_SIZE];
+ const char *bufline;
+ char *linepos;
+ char *variable;
+ char *value;
+ size_t cur;
+ size_t count;
+ int lineno;
+
+ /* loop through the whole buffer */
+ lineno = 0;
+ cur = 0;
+ while (cur < bufsize) {
+ count = buf_get_line(buf, bufsize, cur);
+ bufline = &buf[cur];
+ cur += count+1;
+ lineno++;
+
+ /* eat the whitespace */
+ while ((count > 0) && isspace(bufline[0])) {
+ bufline++;
+ count--;
+ }
+ if (count == 0)
+ continue;
+
+ /* see if this is a comment */
+ if (bufline[0] == COMMENT_CHARACTER)
+ continue;
+
+ if (count >= sizeof(line)) {
+ err("line too long, conf line skipped %s, line %d\n", udev_config_filename, lineno);
+ continue;
+ }
+
+ memcpy(line, bufline, count);
+ line[count] = '\0';
+
+ linepos = line;
+ if (get_key(&linepos, &variable, &value) == 0) {
+ dbg("import '%s=%s'\n", variable, value);
+
+ /* handle device, renamed by external tool, returning new path */
+ if (strcmp(variable, "DEVPATH") == 0) {
+ info("updating devpath from '%s' to '%s'\n", udev->dev->devpath, value);
+ sysfs_device_set_values(udev->dev, value, NULL, NULL);
+ } else
+ name_list_key_add(&udev->env_list, variable, value);
+ setenv(variable, value, 1);
+ }
+ }
+
+ return 0;
+}
+
+static int import_file_into_env(struct udevice *udev, const char *filename)
+{
+ char *buf;
+ size_t bufsize;
+
+ if (file_map(filename, &buf, &bufsize) != 0) {
+ err("can't open '%s': %s\n", filename, strerror(errno));
+ return -1;
+ }
+ import_keys_into_env(udev, buf, bufsize);
+ file_unmap(buf, bufsize);
+
+ return 0;
+}
+
+static int import_program_into_env(struct udevice *udev, const char *program)
+{
+ char result[2048];
+ size_t reslen;
+
+ if (run_program(program, udev->dev->subsystem, result, sizeof(result), &reslen) != 0)
+ return -1;
+ return import_keys_into_env(udev, result, reslen);
+}
+
+static int import_parent_into_env(struct udevice *udev, const char *filter)
+{
+ struct sysfs_device *dev_parent;
+ int rc = -1;
+
+ dev_parent = sysfs_device_get_parent(udev->dev);
+ if (dev_parent != NULL) {
+ struct udevice *udev_parent;
+ struct name_entry *name_loop;
+
+ dbg("found parent '%s', get the node name\n", dev_parent->devpath);
+ udev_parent = udev_device_init(NULL);
+ if (udev_parent == NULL)
+ return -1;
+ /* import the udev_db of the parent */
+ if (udev_db_get_device(udev_parent, dev_parent->devpath) == 0) {
+ dbg("import stored parent env '%s'\n", udev_parent->name);
+ list_for_each_entry(name_loop, &udev_parent->env_list, node) {
+ char name[NAME_SIZE];
+ char *pos;
+
+ strlcpy(name, name_loop->name, sizeof(name));
+ pos = strchr(name, '=');
+ if (pos) {
+ pos[0] = '\0';
+ pos++;
+ if (fnmatch(filter, name, 0) == 0) {
+ dbg("import key '%s'\n", name_loop->name);
+ name_list_add(&udev->env_list, name_loop->name, 0);
+ setenv(name, pos, 1);
+ } else
+ dbg("skip key '%s'\n", name_loop->name);
+ }
+ }
+ rc = 0;
+ } else
+ dbg("parent not found in database\n");
+ udev_device_cleanup(udev_parent);
+ }
+
+ return rc;
+}
+
+static int pass_env_to_socket(const char *sockpath, const char *devpath, const char *action)
+{
+ int sock;
+ struct sockaddr_un saddr;
+ socklen_t saddrlen;
+ struct stat stats;
+ char buf[2048];
+ size_t bufpos = 0;
+ int i;
+ ssize_t count;
+ int retval = 0;
+
+ dbg("pass environment to socket '%s'\n", sockpath);
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ memset(&saddr, 0x00, sizeof(struct sockaddr_un));
+ saddr.sun_family = AF_LOCAL;
+ if (sockpath[0] == '@') {
+ /* abstract namespace socket requested */
+ strlcpy(&saddr.sun_path[1], &sockpath[1], sizeof(saddr.sun_path)-1);
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+ } else if (stat(sockpath, &stats) == 0 && S_ISSOCK(stats.st_mode)) {
+ /* existing socket file */
+ strlcpy(saddr.sun_path, sockpath, sizeof(saddr.sun_path));
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path);
+ } else {
+ /* no socket file, assume abstract namespace socket */
+ strlcpy(&saddr.sun_path[1], sockpath, sizeof(saddr.sun_path)-1);
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+ }
+
+ bufpos = snprintf(buf, sizeof(buf)-1, "%s@%s", action, devpath);
+ bufpos++;
+ for (i = 0; environ[i] != NULL && bufpos < (sizeof(buf)-1); i++) {
+ bufpos += strlcpy(&buf[bufpos], environ[i], sizeof(buf) - bufpos-1);
+ bufpos++;
+ }
+ if (bufpos > sizeof(buf))
+ bufpos = sizeof(buf);
+
+ count = sendto(sock, &buf, bufpos, 0, (struct sockaddr *)&saddr, saddrlen);
+ if (count < 0)
+ retval = -1;
+ info("passed %zi bytes to socket '%s', \n", count, sockpath);
+
+ close(sock);
+ return retval;
+}
+
+int udev_rules_run(struct udevice *udev)
+{
+ struct name_entry *name_loop;
+ int retval = 0;
+
+ dbg("executing run list\n");
+ list_for_each_entry(name_loop, &udev->run_list, node) {
+ if (strncmp(name_loop->name, "socket:", strlen("socket:")) == 0) {
+ pass_env_to_socket(&name_loop->name[strlen("socket:")], udev->dev->devpath, udev->action);
+ } else {
+ char program[PATH_SIZE];
+
+ strlcpy(program, name_loop->name, sizeof(program));
+ udev_rules_apply_format(udev, program, sizeof(program));
+ if (run_program(program, udev->dev->subsystem, NULL, 0, NULL) != 0)
+ if (!name_loop->ignore_error)
+ retval = -1;
+ }
+ }
+
+ return retval;
+}
+
+#define WAIT_LOOP_PER_SECOND 50
+static int wait_for_file(struct udevice *udev, const char *file, int timeout)
+{
+ char filepath[PATH_SIZE];
+ char devicepath[PATH_SIZE] = "";
+ struct stat stats;
+ int loop = timeout * WAIT_LOOP_PER_SECOND;
+
+ /* a relative path is a device attribute */
+ if (file[0] != '/') {
+ strlcpy(devicepath, sysfs_path, sizeof(devicepath));
+ strlcat(devicepath, udev->dev->devpath, sizeof(devicepath));
+
+ strlcpy(filepath, devicepath, sizeof(filepath));
+ strlcat(filepath, "/", sizeof(filepath));
+ strlcat(filepath, file, sizeof(filepath));
+ file = filepath;
+ }
+
+ dbg("will wait %i sec for '%s'\n", timeout, file);
+ while (--loop) {
+ /* lookup file */
+ if (stat(file, &stats) == 0) {
+ info("file '%s' appeared after %i loops\n", file, (timeout * WAIT_LOOP_PER_SECOND) - loop-1);
+ return 0;
+ }
+ /* make sure, the device did not disappear in the meantime */
+ if (devicepath[0] != '\0' && stat(devicepath, &stats) != 0) {
+ info("device disappeared while waiting for '%s'\n", file);
+ return -2;
+ }
+ info("wait for '%s' for %i mseconds\n", file, 1000 / WAIT_LOOP_PER_SECOND);
+ usleep(1000 * 1000 / WAIT_LOOP_PER_SECOND);
+ }
+ info("waiting for '%s' failed\n", file);
+ return -1;
+}
+
+/* handle "[$SUBSYSTEM/$KERNEL]<attribute>" lookup */
+static int attr_get_by_subsys_id(const char *attrstr, char *devpath, size_t len, char **attr)
+{
+ char subsys[NAME_SIZE];
+ char *attrib;
+ char *id;
+ int found = 0;
+
+ if (attrstr[0] != '[')
+ goto out;
+
+ strlcpy(subsys, &attrstr[1], sizeof(subsys));
+
+ attrib = strchr(subsys, ']');
+ if (attrib == NULL)
+ goto out;
+ attrib[0] = '\0';
+ attrib = &attrib[1];
+
+ id = strchr(subsys, '/');
+ if (id == NULL)
+ goto out;
+ id[0] = '\0';
+ id = &id[1];
+
+ if (sysfs_lookup_devpath_by_subsys_id(devpath, len, subsys, id)) {
+ if (attr != NULL) {
+ if (attrib[0] != '\0')
+ *attr = attrib;
+ else
+ *attr = NULL;
+ }
+ found = 1;
+ }
+out:
+ return found;
+}
+
+static int attr_subst_subdir(char *attr, size_t len)
+{
+ char *pos;
+ int found = 0;
+
+ pos = strstr(attr, "/*/");
+ if (pos != NULL) {
+ char str[PATH_SIZE];
+ DIR *dir;
+
+ pos[1] = '\0';
+ strlcpy(str, &pos[2], sizeof(str));
+ dir = opendir(attr);
+ if (dir != NULL) {
+ struct dirent *dent;
+
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ struct stat stats;
+
+ if (dent->d_name[0] == '.')
+ continue;
+ strlcat(attr, dent->d_name, len);
+ strlcat(attr, str, len);
+ if (stat(attr, &stats) == 0) {
+ found = 1;
+ break;
+ }
+ pos[1] = '\0';
+ }
+ closedir(dir);
+ }
+ if (!found)
+ strlcat(attr, str, len);
+ }
+
+ return found;
+}
+
+void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize)
+{
+ char temp[PATH_SIZE];
+ char temp2[PATH_SIZE];
+ char *head, *tail, *pos, *cpos, *attr, *rest;
+ int len;
+ int i;
+ int count;
+ enum subst_type {
+ SUBST_UNKNOWN,
+ SUBST_DEVPATH,
+ SUBST_KERNEL,
+ SUBST_KERNEL_NUMBER,
+ SUBST_ID,
+ SUBST_DRIVER,
+ SUBST_MAJOR,
+ SUBST_MINOR,
+ SUBST_RESULT,
+ SUBST_ATTR,
+ SUBST_PARENT,
+ SUBST_TEMP_NODE,
+ SUBST_NAME,
+ SUBST_LINKS,
+ SUBST_ROOT,
+ SUBST_SYS,
+ SUBST_ENV,
+ };
+ static const struct subst_map {
+ char *name;
+ char fmt;
+ enum subst_type type;
+ } map[] = {
+ { .name = "devpath", .fmt = 'p', .type = SUBST_DEVPATH },
+ { .name = "number", .fmt = 'n', .type = SUBST_KERNEL_NUMBER },
+ { .name = "kernel", .fmt = 'k', .type = SUBST_KERNEL },
+ { .name = "id", .fmt = 'b', .type = SUBST_ID },
+ { .name = "driver", .fmt = 'd', .type = SUBST_DRIVER },
+ { .name = "major", .fmt = 'M', .type = SUBST_MAJOR },
+ { .name = "minor", .fmt = 'm', .type = SUBST_MINOR },
+ { .name = "result", .fmt = 'c', .type = SUBST_RESULT },
+ { .name = "attr", .fmt = 's', .type = SUBST_ATTR },
+ { .name = "sysfs", .fmt = 's', .type = SUBST_ATTR },
+ { .name = "parent", .fmt = 'P', .type = SUBST_PARENT },
+ { .name = "tempnode", .fmt = 'N', .type = SUBST_TEMP_NODE },
+ { .name = "name", .fmt = 'D', .type = SUBST_NAME },
+ { .name = "links", .fmt = 'L', .type = SUBST_LINKS },
+ { .name = "root", .fmt = 'r', .type = SUBST_ROOT },
+ { .name = "sys", .fmt = 'S', .type = SUBST_SYS },
+ { .name = "env", .fmt = 'E', .type = SUBST_ENV },
+ { NULL, '\0', 0 }
+ };
+ enum subst_type type;
+ const struct subst_map *subst;
+
+ head = string;
+ while (1) {
+ len = -1;
+ while (head[0] != '\0') {
+ if (head[0] == '$') {
+ /* substitute named variable */
+ if (head[1] == '\0')
+ break;
+ if (head[1] == '$') {
+ strlcpy(temp, head+2, sizeof(temp));
+ strlcpy(head+1, temp, maxsize);
+ head++;
+ continue;
+ }
+ head[0] = '\0';
+ for (subst = map; subst->name; subst++) {
+ if (strncasecmp(&head[1], subst->name, strlen(subst->name)) == 0) {
+ type = subst->type;
+ tail = head + strlen(subst->name)+1;
+ dbg("will substitute format name '%s'\n", subst->name);
+ goto found;
+ }
+ }
+ head[0] = '$';
+ err("unknown format variable '%s'\n", head);
+ } else if (head[0] == '%') {
+ /* substitute format char */
+ if (head[1] == '\0')
+ break;
+ if (head[1] == '%') {
+ strlcpy(temp, head+2, sizeof(temp));
+ strlcpy(head+1, temp, maxsize);
+ head++;
+ continue;
+ }
+ head[0] = '\0';
+ tail = head+1;
+ len = get_format_len(&tail);
+ for (subst = map; subst->name; subst++) {
+ if (tail[0] == subst->fmt) {
+ type = subst->type;
+ tail++;
+ dbg("will substitute format char '%c'\n", subst->fmt);
+ goto found;
+ }
+ }
+ head[0] = '%';
+ err("unknown format char '%c'\n", tail[0]);
+ }
+ head++;
+ }
+ break;
+found:
+ attr = get_format_attribute(&tail);
+ strlcpy(temp, tail, sizeof(temp));
+ dbg("format=%i, string='%s', tail='%s'\n", type ,string, tail);
+
+ switch (type) {
+ case SUBST_DEVPATH:
+ strlcat(string, udev->dev->devpath, maxsize);
+ dbg("substitute devpath '%s'\n", udev->dev->devpath);
+ break;
+ case SUBST_KERNEL:
+ strlcat(string, udev->dev->kernel, maxsize);
+ dbg("substitute kernel name '%s'\n", udev->dev->kernel);
+ break;
+ case SUBST_KERNEL_NUMBER:
+ strlcat(string, udev->dev->kernel_number, maxsize);
+ dbg("substitute kernel number '%s'\n", udev->dev->kernel_number);
+ break;
+ case SUBST_ID:
+ if (udev->dev_parent != NULL) {
+ strlcat(string, udev->dev_parent->kernel, maxsize);
+ dbg("substitute id '%s'\n", udev->dev_parent->kernel);
+ }
+ break;
+ case SUBST_DRIVER:
+ if (udev->dev_parent != NULL) {
+ strlcat(string, udev->dev_parent->driver, maxsize);
+ dbg("substitute driver '%s'\n", udev->dev_parent->driver);
+ }
+ break;
+ case SUBST_MAJOR:
+ sprintf(temp2, "%d", major(udev->devt));
+ strlcat(string, temp2, maxsize);
+ dbg("substitute major number '%s'\n", temp2);
+ break;
+ case SUBST_MINOR:
+ sprintf(temp2, "%d", minor(udev->devt));
+ strlcat(string, temp2, maxsize);
+ dbg("substitute minor number '%s'\n", temp2);
+ break;
+ case SUBST_RESULT:
+ if (udev->program_result[0] == '\0')
+ break;
+ /* get part part of the result string */
+ i = 0;
+ if (attr != NULL)
+ i = strtoul(attr, &rest, 10);
+ if (i > 0) {
+ dbg("request part #%d of result string\n", i);
+ cpos = udev->program_result;
+ while (--i) {
+ while (cpos[0] != '\0' && !isspace(cpos[0]))
+ cpos++;
+ while (isspace(cpos[0]))
+ cpos++;
+ }
+ if (i > 0) {
+ err("requested part of result string not found\n");
+ break;
+ }
+ strlcpy(temp2, cpos, sizeof(temp2));
+ /* %{2+}c copies the whole string from the second part on */
+ if (rest[0] != '+') {
+ cpos = strchr(temp2, ' ');
+ if (cpos)
+ cpos[0] = '\0';
+ }
+ strlcat(string, temp2, maxsize);
+ dbg("substitute part of result string '%s'\n", temp2);
+ } else {
+ strlcat(string, udev->program_result, maxsize);
+ dbg("substitute result string '%s'\n", udev->program_result);
+ }
+ break;
+ case SUBST_ATTR:
+ if (attr == NULL)
+ err("missing file parameter for attr\n");
+ else {
+ char devpath[PATH_SIZE];
+ char *attrib;
+ const char *value = NULL;
+ size_t size;
+
+ if (attr_get_by_subsys_id(attr, devpath, sizeof(devpath), &attrib)) {
+ if (attrib != NULL)
+ value = sysfs_attr_get_value(devpath, attrib);
+ else
+ break;
+ }
+
+ /* try the current device, other matches may have selected */
+ if (value == NULL && udev->dev_parent != NULL && udev->dev_parent != udev->dev)
+ value = sysfs_attr_get_value(udev->dev_parent->devpath, attr);
+
+ /* look at all devices along the chain of parents */
+ if (value == NULL) {
+ struct sysfs_device *dev_parent = udev->dev;
+
+ do {
+ dbg("looking at '%s'\n", dev_parent->devpath);
+ value = sysfs_attr_get_value(dev_parent->devpath, attr);
+ if (value != NULL) {
+ strlcpy(temp2, value, sizeof(temp2));
+ break;
+ }
+ dev_parent = sysfs_device_get_parent(dev_parent);
+ } while (dev_parent != NULL);
+ }
+
+ if (value == NULL)
+ break;
+
+ /* strip trailing whitespace, and replace unwanted characters */
+ size = strlcpy(temp2, value, sizeof(temp2));
+ if (size >= sizeof(temp2))
+ size = sizeof(temp2)-1;
+ while (size > 0 && isspace(temp2[size-1]))
+ temp2[--size] = '\0';
+ count = replace_chars(temp2, ALLOWED_CHARS_INPUT);
+ if (count > 0)
+ info("%i character(s) replaced\n" , count);
+ strlcat(string, temp2, maxsize);
+ dbg("substitute sysfs value '%s'\n", temp2);
+ }
+ break;
+ case SUBST_PARENT:
+ {
+ struct sysfs_device *dev_parent;
+
+ dev_parent = sysfs_device_get_parent(udev->dev);
+ if (dev_parent != NULL) {
+ struct udevice *udev_parent;
+
+ dbg("found parent '%s', get the node name\n", dev_parent->devpath);
+ udev_parent = udev_device_init(NULL);
+ if (udev_parent != NULL) {
+ /* lookup the name in the udev_db with the DEVPATH of the parent */
+ if (udev_db_get_device(udev_parent, dev_parent->devpath) == 0) {
+ strlcat(string, udev_parent->name, maxsize);
+ dbg("substitute parent node name'%s'\n", udev_parent->name);
+ } else
+ dbg("parent not found in database\n");
+ udev_device_cleanup(udev_parent);
+ }
+ }
+ }
+ break;
+ case SUBST_TEMP_NODE:
+ if (udev->tmp_node[0] == '\0' && major(udev->devt) > 0) {
+ dbg("create temporary device node for callout\n");
+ snprintf(udev->tmp_node, sizeof(udev->tmp_node), "%s/.tmp-%u-%u",
+ udev_root, major(udev->devt), minor(udev->devt));
+ udev->tmp_node[sizeof(udev->tmp_node)-1] = '\0';
+ udev_node_mknod(udev, udev->tmp_node, udev->devt, 0600, 0, 0);
+ }
+ strlcat(string, udev->tmp_node, maxsize);
+ dbg("substitute temporary device node name '%s'\n", udev->tmp_node);
+ break;
+ case SUBST_NAME:
+ if (udev->name[0] == '\0') {
+ strlcat(string, udev->dev->kernel, maxsize);
+ dbg("substitute udev->kernel '%s'\n", udev->name);
+ } else {
+ strlcat(string, udev->name, maxsize);
+ dbg("substitute udev->name '%s'\n", udev->name);
+ }
+ break;
+ case SUBST_LINKS:
+ if (!list_empty(&udev->symlink_list)) {
+ struct name_entry *name_loop;
+ char symlinks[PATH_SIZE] = "";
+
+ list_for_each_entry(name_loop, &udev->symlink_list, node) {
+ strlcat(symlinks, name_loop->name, sizeof(symlinks));
+ strlcat(symlinks, " ", sizeof(symlinks));
+ }
+ remove_trailing_chars(symlinks, ' ');
+ strlcat(string, symlinks, maxsize);
+ }
+ break;
+ case SUBST_ROOT:
+ strlcat(string, udev_root, maxsize);
+ dbg("substitute udev_root '%s'\n", udev_root);
+ break;
+ case SUBST_SYS:
+ strlcat(string, sysfs_path, maxsize);
+ dbg("substitute sysfs_path '%s'\n", sysfs_path);
+ break;
+ case SUBST_ENV:
+ if (attr == NULL) {
+ dbg("missing attribute\n");
+ break;
+ }
+ pos = getenv(attr);
+ if (pos == NULL) {
+ dbg("env '%s' not available\n", attr);
+ break;
+ }
+ dbg("substitute env '%s=%s'\n", attr, pos);
+ strlcat(string, pos, maxsize);
+ break;
+ default:
+ err("unknown substitution type=%i\n", type);
+ break;
+ }
+ /* possibly truncate to format-char specified length */
+ if (len >= 0 && len < (int)strlen(head)) {
+ head[len] = '\0';
+ dbg("truncate to %i chars, subtitution string becomes '%s'\n", len, head);
+ }
+ strlcat(string, temp, maxsize);
+ }
+}
+
+static char *key_val(struct udev_rule *rule, struct key *key)
+{
+ return rule->buf + key->val_off;
+}
+
+static char *key_pair_name(struct udev_rule *rule, struct key_pair *pair)
+{
+ return rule->buf + pair->key_name_off;
+}
+
+static int match_key(const char *key_name, struct udev_rule *rule, struct key *key, const char *val)
+{
+ char value[PATH_SIZE];
+ char *key_value;
+ char *pos;
+ int match = 0;
+
+ if (key->operation != KEY_OP_MATCH &&
+ key->operation != KEY_OP_NOMATCH)
+ return 0;
+
+ /* look for a matching string, parts are separated by '|' */
+ strlcpy(value, rule->buf + key->val_off, sizeof(value));
+ key_value = value;
+ dbg("key %s value='%s'\n", key_name, key_value);
+ while (key_value) {
+ pos = strchr(key_value, '|');
+ if (pos) {
+ pos[0] = '\0';
+ pos++;
+ }
+
+ dbg("match %s '%s' <-> '%s'\n", key_name, key_value, val);
+ match = (fnmatch(key_value, val, 0) == 0);
+ if (match)
+ break;
+
+ key_value = pos;
+ }
+
+ if (match && (key->operation == KEY_OP_MATCH)) {
+ dbg("%s is true (matching value)\n", key_name);
+ return 0;
+ }
+ if (!match && (key->operation == KEY_OP_NOMATCH)) {
+ dbg("%s is true (non-matching value)\n", key_name);
+ return 0;
+ }
+ return -1;
+}
+
+/* match a single rule against a given device and possibly its parent devices */
+static int match_rule(struct udevice *udev, struct udev_rule *rule)
+{
+ int i;
+
+ if (match_key("ACTION", rule, &rule->action, udev->action))
+ goto nomatch;
+
+ if (match_key("KERNEL", rule, &rule->kernel, udev->dev->kernel))
+ goto nomatch;
+
+ if (match_key("SUBSYSTEM", rule, &rule->subsystem, udev->dev->subsystem))
+ goto nomatch;
+
+ if (match_key("DEVPATH", rule, &rule->devpath, udev->dev->devpath))
+ goto nomatch;
+
+ if (match_key("DRIVER", rule, &rule->driver, udev->dev->driver))
+ goto nomatch;
+
+ /* match NAME against a value assigned by an earlier rule */
+ if (match_key("NAME", rule, &rule->name, udev->name))
+ goto nomatch;
+
+ /* match against current list of symlinks */
+ if (rule->symlink_match.operation == KEY_OP_MATCH ||
+ rule->symlink_match.operation == KEY_OP_NOMATCH) {
+ struct name_entry *name_loop;
+ int match = 0;
+
+ list_for_each_entry(name_loop, &udev->symlink_list, node) {
+ if (match_key("SYMLINK", rule, &rule->symlink_match, name_loop->name) == 0) {
+ match = 1;
+ break;
+ }
+ }
+ if (!match)
+ goto nomatch;
+ }
+
+ for (i = 0; i < rule->env.count; i++) {
+ struct key_pair *pair = &rule->env.keys[i];
+
+ /* we only check for matches, assignments will be handled later */
+ if (pair->key.operation == KEY_OP_MATCH ||
+ pair->key.operation == KEY_OP_NOMATCH) {
+ const char *key_name = key_pair_name(rule, pair);
+ const char *value = getenv(key_name);
+
+ if (!value) {
+ dbg("ENV{'%s'} is not set, treat as empty\n", key_name);
+ value = "";
+ }
+ if (match_key("ENV", rule, &pair->key, value))
+ goto nomatch;
+ }
+ }
+
+ if (rule->test.operation == KEY_OP_MATCH ||
+ rule->test.operation == KEY_OP_NOMATCH) {
+ char filename[PATH_SIZE];
+ char devpath[PATH_SIZE];
+ char *attr;
+ struct stat statbuf;
+ int match;
+
+ strlcpy(filename, key_val(rule, &rule->test), sizeof(filename));
+ udev_rules_apply_format(udev, filename, sizeof(filename));
+
+ if (attr_get_by_subsys_id(filename, devpath, sizeof(devpath), &attr)) {
+ strlcpy(filename, sysfs_path, sizeof(filename));
+ strlcat(filename, devpath, sizeof(filename));
+ if (attr != NULL) {
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, attr, sizeof(filename));
+ }
+ } else if (filename[0] != '/') {
+ char tmp[PATH_SIZE];
+
+ strlcpy(tmp, sysfs_path, sizeof(tmp));
+ strlcat(tmp, udev->dev->devpath, sizeof(tmp));
+ strlcat(tmp, "/", sizeof(tmp));
+ strlcat(tmp, filename, sizeof(tmp));
+ strlcpy(filename, tmp, sizeof(filename));
+ }
+
+ attr_subst_subdir(filename, sizeof(filename));
+
+ match = (stat(filename, &statbuf) == 0);
+ info("'%s' %s", filename, match ? "exists\n" : "does not exist\n");
+ if (match && rule->test_mode_mask > 0) {
+ match = ((statbuf.st_mode & rule->test_mode_mask) > 0);
+ info("'%s' has mode=%#o and %s %#o\n", filename, statbuf.st_mode,
+ match ? "matches" : "does not match",
+ rule->test_mode_mask);
+ }
+ if (match && rule->test.operation == KEY_OP_NOMATCH)
+ goto nomatch;
+ if (!match && rule->test.operation == KEY_OP_MATCH)
+ goto nomatch;
+ dbg("TEST key is true\n");
+ }
+
+ if (rule->wait_for.operation != KEY_OP_UNSET) {
+ char filename[PATH_SIZE];
+ int found;
+
+ strlcpy(filename, key_val(rule, &rule->wait_for), sizeof(filename));
+ udev_rules_apply_format(udev, filename, sizeof(filename));
+ found = (wait_for_file(udev, filename, 10) == 0);
+ if (!found && (rule->wait_for.operation != KEY_OP_NOMATCH))
+ goto nomatch;
+ }
+
+ /* check for matching sysfs attribute pairs */
+ for (i = 0; i < rule->attr.count; i++) {
+ struct key_pair *pair = &rule->attr.keys[i];
+
+ if (pair->key.operation == KEY_OP_MATCH ||
+ pair->key.operation == KEY_OP_NOMATCH) {
+ const char *key_name = key_pair_name(rule, pair);
+ const char *key_value = key_val(rule, &pair->key);
+ char devpath[PATH_SIZE];
+ char *attrib;
+ const char *value = NULL;
+ char val[VALUE_SIZE];
+ size_t len;
+
+ if (attr_get_by_subsys_id(key_name, devpath, sizeof(devpath), &attrib)) {
+ if (attrib != NULL)
+ value = sysfs_attr_get_value(devpath, attrib);
+ else
+ goto nomatch;
+ }
+ if (value == NULL)
+ value = sysfs_attr_get_value(udev->dev->devpath, key_name);
+ if (value == NULL)
+ goto nomatch;
+ strlcpy(val, value, sizeof(val));
+
+ /* strip trailing whitespace of value, if not asked to match for it */
+ len = strlen(key_value);
+ if (len > 0 && !isspace(key_value[len-1])) {
+ len = strlen(val);
+ while (len > 0 && isspace(val[len-1]))
+ val[--len] = '\0';
+ dbg("removed %zi trailing whitespace chars from '%s'\n", strlen(val)-len, val);
+ }
+
+ if (match_key("ATTR", rule, &pair->key, val))
+ goto nomatch;
+ }
+ }
+
+ /* walk up the chain of parent devices and find a match */
+ udev->dev_parent = udev->dev;
+ while (1) {
+ /* check for matching kernel device name */
+ if (match_key("KERNELS", rule, &rule->kernels, udev->dev_parent->kernel))
+ goto try_parent;
+
+ /* check for matching subsystem value */
+ if (match_key("SUBSYSTEMS", rule, &rule->subsystems, udev->dev_parent->subsystem))
+ goto try_parent;
+
+ /* check for matching driver */
+ if (match_key("DRIVERS", rule, &rule->drivers, udev->dev_parent->driver))
+ goto try_parent;
+
+ /* check for matching sysfs attribute pairs */
+ for (i = 0; i < rule->attrs.count; i++) {
+ struct key_pair *pair = &rule->attrs.keys[i];
+
+ if (pair->key.operation == KEY_OP_MATCH ||
+ pair->key.operation == KEY_OP_NOMATCH) {
+ const char *key_name = key_pair_name(rule, pair);
+ const char *key_value = key_val(rule, &pair->key);
+ const char *value;
+ char val[VALUE_SIZE];
+ size_t len;
+
+ value = sysfs_attr_get_value(udev->dev_parent->devpath, key_name);
+ if (value == NULL)
+ value = sysfs_attr_get_value(udev->dev->devpath, key_name);
+ if (value == NULL)
+ goto try_parent;
+ strlcpy(val, value, sizeof(val));
+
+ /* strip trailing whitespace of value, if not asked to match for it */
+ len = strlen(key_value);
+ if (len > 0 && !isspace(key_value[len-1])) {
+ len = strlen(val);
+ while (len > 0 && isspace(val[len-1]))
+ val[--len] = '\0';
+ dbg("removed %zi trailing whitespace chars from '%s'\n", strlen(val)-len, val);
+ }
+
+ if (match_key("ATTRS", rule, &pair->key, val))
+ goto try_parent;
+ }
+ }
+
+ /* found matching device */
+ break;
+try_parent:
+ /* move to parent device */
+ dbg("try parent sysfs device\n");
+ udev->dev_parent = sysfs_device_get_parent(udev->dev_parent);
+ if (udev->dev_parent == NULL)
+ goto nomatch;
+ dbg("looking at dev_parent->devpath='%s'\n", udev->dev_parent->devpath);
+ dbg("looking at dev_parent->kernel='%s'\n", udev->dev_parent->kernel);
+ }
+
+ /* execute external program */
+ if (rule->program.operation != KEY_OP_UNSET) {
+ char program[PATH_SIZE];
+ char result[PATH_SIZE];
+
+ strlcpy(program, key_val(rule, &rule->program), sizeof(program));
+ udev_rules_apply_format(udev, program, sizeof(program));
+ if (run_program(program, udev->dev->subsystem, result, sizeof(result), NULL) != 0) {
+ dbg("PROGRAM is false\n");
+ udev->program_result[0] = '\0';
+ if (rule->program.operation != KEY_OP_NOMATCH)
+ goto nomatch;
+ } else {
+ int count;
+
+ dbg("PROGRAM matches\n");
+ remove_trailing_chars(result, '\n');
+ if (rule->string_escape == ESCAPE_UNSET ||
+ rule->string_escape == ESCAPE_REPLACE) {
+ count = replace_chars(result, ALLOWED_CHARS_INPUT);
+ if (count > 0)
+ info("%i character(s) replaced\n" , count);
+ }
+ dbg("result is '%s'\n", result);
+ strlcpy(udev->program_result, result, sizeof(udev->program_result));
+ dbg("PROGRAM returned successful\n");
+ if (rule->program.operation == KEY_OP_NOMATCH)
+ goto nomatch;
+ }
+ dbg("PROGRAM key is true\n");
+ }
+
+ /* check for matching result of external program */
+ if (match_key("RESULT", rule, &rule->result, udev->program_result))
+ goto nomatch;
+
+ /* import variables returned from program or or file into environment */
+ if (rule->import.operation != KEY_OP_UNSET) {
+ char import[PATH_SIZE];
+ int rc = -1;
+
+ strlcpy(import, key_val(rule, &rule->import), sizeof(import));
+ udev_rules_apply_format(udev, import, sizeof(import));
+ dbg("check for IMPORT import='%s'\n", import);
+ if (rule->import_type == IMPORT_PROGRAM) {
+ rc = import_program_into_env(udev, import);
+ } else if (rule->import_type == IMPORT_FILE) {
+ dbg("import file import='%s'\n", import);
+ rc = import_file_into_env(udev, import);
+ } else if (rule->import_type == IMPORT_PARENT) {
+ dbg("import parent import='%s'\n", import);
+ rc = import_parent_into_env(udev, import);
+ }
+ if (rc != 0) {
+ dbg("IMPORT failed\n");
+ if (rule->import.operation != KEY_OP_NOMATCH)
+ goto nomatch;
+ } else
+ dbg("IMPORT '%s' imported\n", key_val(rule, &rule->import));
+ dbg("IMPORT key is true\n");
+ }
+
+ /* rule matches, if we have ENV assignments export it */
+ for (i = 0; i < rule->env.count; i++) {
+ struct key_pair *pair = &rule->env.keys[i];
+
+ if (pair->key.operation == KEY_OP_ASSIGN) {
+ char temp_value[NAME_SIZE];
+ const char *key_name = key_pair_name(rule, pair);
+ const char *value = key_val(rule, &pair->key);
+
+ /* make sure we don't write to the same string we possibly read from */
+ strlcpy(temp_value, value, sizeof(temp_value));
+ udev_rules_apply_format(udev, temp_value, NAME_SIZE);
+
+ if (temp_value[0] == '\0') {
+ name_list_key_remove(&udev->env_list, key_name);
+ unsetenv(key_name);
+ info("unset ENV '%s'\n", key_name);
+ } else {
+ struct name_entry *entry;
+
+ entry = name_list_key_add(&udev->env_list, key_name, temp_value);
+ if (entry == NULL)
+ break;
+ putenv(entry->name);
+ info("set ENV '%s'\n", entry->name);
+ }
+ }
+ }
+
+ /* if we have ATTR assignments, write value to sysfs file */
+ for (i = 0; i < rule->attr.count; i++) {
+ struct key_pair *pair = &rule->attr.keys[i];
+
+ if (pair->key.operation == KEY_OP_ASSIGN) {
+ const char *key_name = key_pair_name(rule, pair);
+ char devpath[PATH_SIZE];
+ char *attrib;
+ char attr[PATH_SIZE] = "";
+ char value[NAME_SIZE];
+ FILE *f;
+
+ if (attr_get_by_subsys_id(key_name, devpath, sizeof(devpath), &attrib)) {
+ if (attrib != NULL) {
+ strlcpy(attr, sysfs_path, sizeof(attr));
+ strlcat(attr, devpath, sizeof(attr));
+ strlcat(attr, "/", sizeof(attr));
+ strlcat(attr, attrib, sizeof(attr));
+ }
+ }
+
+ if (attr[0] == '\0') {
+ strlcpy(attr, sysfs_path, sizeof(attr));
+ strlcat(attr, udev->dev->devpath, sizeof(attr));
+ strlcat(attr, "/", sizeof(attr));
+ strlcat(attr, key_name, sizeof(attr));
+ }
+
+ attr_subst_subdir(attr, sizeof(attr));
+
+ strlcpy(value, key_val(rule, &pair->key), sizeof(value));
+ udev_rules_apply_format(udev, value, sizeof(value));
+ info("writing '%s' to sysfs file '%s'\n", value, attr);
+ f = fopen(attr, "w");
+ if (f != NULL) {
+ if (!udev->test_run)
+ if (fprintf(f, "%s", value) <= 0)
+ err("error writing ATTR{%s}: %s\n", attr, strerror(errno));
+ fclose(f);
+ } else
+ err("error opening ATTR{%s} for writing: %s\n", attr, strerror(errno));
+ }
+ }
+ return 0;
+
+nomatch:
+ return -1;
+}
+
+int udev_rules_get_name(struct udev_rules *rules, struct udevice *udev)
+{
+ struct udev_rule *rule;
+ int name_set = 0;
+
+ dbg("udev->dev->devpath='%s'\n", udev->dev->devpath);
+ dbg("udev->dev->kernel='%s'\n", udev->dev->kernel);
+
+ /* look for a matching rule to apply */
+ udev_rules_iter_init(rules);
+ while (1) {
+ rule = udev_rules_iter_next(rules);
+ if (rule == NULL)
+ break;
+
+ if (name_set &&
+ (rule->name.operation == KEY_OP_ASSIGN ||
+ rule->name.operation == KEY_OP_ASSIGN_FINAL ||
+ rule->name.operation == KEY_OP_ADD)) {
+ dbg("node name already set, rule ignored\n");
+ continue;
+ }
+
+ dbg("process rule\n");
+ if (match_rule(udev, rule) == 0) {
+ /* apply options */
+ if (rule->ignore_device) {
+ info("rule applied, '%s' is ignored\n", udev->dev->kernel);
+ udev->ignore_device = 1;
+ return 0;
+ }
+ if (rule->ignore_remove) {
+ udev->ignore_remove = 1;
+ dbg("remove event should be ignored\n");
+ }
+ if (rule->link_priority != 0) {
+ udev->link_priority = rule->link_priority;
+ info("link_priority=%i\n", udev->link_priority);
+ }
+ if (rule->event_timeout >= 0) {
+ udev->event_timeout = rule->event_timeout;
+ info("event_timeout=%i\n", udev->event_timeout);
+ }
+ /* apply all_partitions option only at a main block device */
+ if (rule->partitions &&
+ strcmp(udev->dev->subsystem, "block") == 0 && udev->dev->kernel_number[0] == '\0') {
+ udev->partitions = rule->partitions;
+ dbg("creation of partition nodes requested\n");
+ }
+
+ /* apply permissions */
+ if (!udev->mode_final && rule->mode.operation != KEY_OP_UNSET) {
+ if (rule->mode.operation == KEY_OP_ASSIGN_FINAL)
+ udev->mode_final = 1;
+ char buf[20];
+ strlcpy(buf, key_val(rule, &rule->mode), sizeof(buf));
+ udev_rules_apply_format(udev, buf, sizeof(buf));
+ udev->mode = strtol(buf, NULL, 8);
+ dbg("applied mode=%#o to '%s'\n", udev->mode, udev->dev->kernel);
+ }
+ if (!udev->owner_final && rule->owner.operation != KEY_OP_UNSET) {
+ if (rule->owner.operation == KEY_OP_ASSIGN_FINAL)
+ udev->owner_final = 1;
+ strlcpy(udev->owner, key_val(rule, &rule->owner), sizeof(udev->owner));
+ udev_rules_apply_format(udev, udev->owner, sizeof(udev->owner));
+ dbg("applied owner='%s' to '%s'\n", udev->owner, udev->dev->kernel);
+ }
+ if (!udev->group_final && rule->group.operation != KEY_OP_UNSET) {
+ if (rule->group.operation == KEY_OP_ASSIGN_FINAL)
+ udev->group_final = 1;
+ strlcpy(udev->group, key_val(rule, &rule->group), sizeof(udev->group));
+ udev_rules_apply_format(udev, udev->group, sizeof(udev->group));
+ dbg("applied group='%s' to '%s'\n", udev->group, udev->dev->kernel);
+ }
+
+ /* collect symlinks */
+ if (!udev->symlink_final &&
+ (rule->symlink.operation == KEY_OP_ASSIGN ||
+ rule->symlink.operation == KEY_OP_ASSIGN_FINAL ||
+ rule->symlink.operation == KEY_OP_ADD)) {
+ char temp[PATH_SIZE];
+ char *pos, *next;
+ int count;
+
+ if (rule->symlink.operation == KEY_OP_ASSIGN_FINAL)
+ udev->symlink_final = 1;
+ if (rule->symlink.operation == KEY_OP_ASSIGN ||
+ rule->symlink.operation == KEY_OP_ASSIGN_FINAL) {
+ info("reset symlink list\n");
+ name_list_cleanup(&udev->symlink_list);
+ }
+ /* allow multiple symlinks separated by spaces */
+ strlcpy(temp, key_val(rule, &rule->symlink), sizeof(temp));
+ udev_rules_apply_format(udev, temp, sizeof(temp));
+ if (rule->string_escape == ESCAPE_UNSET ||
+ rule->string_escape == ESCAPE_REPLACE) {
+ count = replace_chars(temp, ALLOWED_CHARS_FILE " ");
+ if (count > 0)
+ info("%i character(s) replaced\n" , count);
+ }
+ dbg("rule applied, added symlink(s) '%s'\n", temp);
+ pos = temp;
+ while (isspace(pos[0]))
+ pos++;
+ next = strchr(pos, ' ');
+ while (next) {
+ next[0] = '\0';
+ info("add symlink '%s'\n", pos);
+ name_list_add(&udev->symlink_list, pos, 0);
+ while (isspace(next[1]))
+ next++;
+ pos = &next[1];
+ next = strchr(pos, ' ');
+ }
+ if (pos[0] != '\0') {
+ info("add symlink '%s'\n", pos);
+ name_list_add(&udev->symlink_list, pos, 0);
+ }
+ }
+
+ /* set name, later rules with name set will be ignored */
+ if (rule->name.operation == KEY_OP_ASSIGN ||
+ rule->name.operation == KEY_OP_ASSIGN_FINAL ||
+ rule->name.operation == KEY_OP_ADD) {
+ int count;
+
+ name_set = 1;
+ strlcpy(udev->name, key_val(rule, &rule->name), sizeof(udev->name));
+ udev_rules_apply_format(udev, udev->name, sizeof(udev->name));
+ if (rule->string_escape == ESCAPE_UNSET ||
+ rule->string_escape == ESCAPE_REPLACE) {
+ count = replace_chars(udev->name, ALLOWED_CHARS_FILE);
+ if (count > 0)
+ info("%i character(s) replaced\n", count);
+ }
+
+ info("rule applied, '%s' becomes '%s'\n", udev->dev->kernel, udev->name);
+ if (strcmp(udev->dev->subsystem, "net") != 0)
+ dbg("name, '%s' is going to have owner='%s', group='%s', mode=%#o partitions=%i\n",
+ udev->name, udev->owner, udev->group, udev->mode, udev->partitions);
+ }
+
+ if (!udev->run_final && rule->run.operation != KEY_OP_UNSET) {
+ struct name_entry *entry;
+
+ if (rule->run.operation == KEY_OP_ASSIGN_FINAL)
+ udev->run_final = 1;
+ if (rule->run.operation == KEY_OP_ASSIGN || rule->run.operation == KEY_OP_ASSIGN_FINAL) {
+ info("reset run list\n");
+ name_list_cleanup(&udev->run_list);
+ }
+ dbg("add run '%s'\n", key_val(rule, &rule->run));
+ entry = name_list_add(&udev->run_list, key_val(rule, &rule->run), 0);
+ if (rule->run_ignore_error)
+ entry->ignore_error = 1;
+ }
+
+ if (rule->last_rule) {
+ dbg("last rule to be applied\n");
+ break;
+ }
+
+ if (rule->goto_label.operation != KEY_OP_UNSET) {
+ dbg("moving forward to label '%s'\n", key_val(rule, &rule->goto_label));
+ udev_rules_iter_label(rules, key_val(rule, &rule->goto_label));
+ }
+ }
+ }
+
+ if (!name_set) {
+ info("no node name set, will use kernel name '%s'\n", udev->dev->kernel);
+ strlcpy(udev->name, udev->dev->kernel, sizeof(udev->name));
+ }
+
+ if (udev->tmp_node[0] != '\0') {
+ dbg("removing temporary device node\n");
+ unlink_secure(udev->tmp_node);
+ udev->tmp_node[0] = '\0';
+ }
+
+ return 0;
+}
+
+int udev_rules_get_run(struct udev_rules *rules, struct udevice *udev)
+{
+ struct udev_rule *rule;
+
+ dbg("udev->kernel='%s'\n", udev->dev->kernel);
+
+ /* look for a matching rule to apply */
+ udev_rules_iter_init(rules);
+ while (1) {
+ rule = udev_rules_iter_next(rules);
+ if (rule == NULL)
+ break;
+
+ dbg("process rule\n");
+ if (rule->name.operation == KEY_OP_ASSIGN ||
+ rule->name.operation == KEY_OP_ASSIGN_FINAL ||
+ rule->name.operation == KEY_OP_ADD ||
+ rule->symlink.operation == KEY_OP_ASSIGN ||
+ rule->symlink.operation == KEY_OP_ASSIGN_FINAL ||
+ rule->symlink.operation == KEY_OP_ADD ||
+ rule->mode.operation != KEY_OP_UNSET ||
+ rule->owner.operation != KEY_OP_UNSET || rule->group.operation != KEY_OP_UNSET) {
+ dbg("skip rule that names a device\n");
+ continue;
+ }
+
+ if (match_rule(udev, rule) == 0) {
+ if (rule->ignore_device) {
+ info("rule applied, '%s' is ignored\n", udev->dev->kernel);
+ udev->ignore_device = 1;
+ return 0;
+ }
+ if (rule->ignore_remove) {
+ udev->ignore_remove = 1;
+ dbg("remove event should be ignored\n");
+ }
+
+ if (!udev->run_final && rule->run.operation != KEY_OP_UNSET) {
+ struct name_entry *entry;
+
+ if (rule->run.operation == KEY_OP_ASSIGN ||
+ rule->run.operation == KEY_OP_ASSIGN_FINAL) {
+ info("reset run list\n");
+ name_list_cleanup(&udev->run_list);
+ }
+ dbg("add run '%s'\n", key_val(rule, &rule->run));
+ entry = name_list_add(&udev->run_list, key_val(rule, &rule->run), 0);
+ if (rule->run_ignore_error)
+ entry->ignore_error = 1;
+ if (rule->run.operation == KEY_OP_ASSIGN_FINAL)
+ break;
+ }
+
+ if (rule->last_rule) {
+ dbg("last rule to be applied\n");
+ break;
+ }
+
+ if (rule->goto_label.operation != KEY_OP_UNSET) {
+ dbg("moving forward to label '%s'\n", key_val(rule, &rule->goto_label));
+ udev_rules_iter_label(rules, key_val(rule, &rule->goto_label));
+ }
+ }
+ }
+
+ return 0;
+}
diff --git a/udev/udev_rules.h b/udev/udev_rules.h
new file mode 100644
index 0000000000..fe0f9dfbb5
--- /dev/null
+++ b/udev/udev_rules.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef UDEV_RULES_H
+#define UDEV_RULES_H
+
+#include "udev.h"
+#include "list.h"
+
+#define PAIRS_MAX 5
+#define RULESFILE_SUFFIX ".rules"
+
+enum key_operation {
+ KEY_OP_UNSET,
+ KEY_OP_MATCH,
+ KEY_OP_NOMATCH,
+ KEY_OP_ADD,
+ KEY_OP_ASSIGN,
+ KEY_OP_ASSIGN_FINAL,
+};
+
+struct key {
+ enum key_operation operation;
+ size_t val_off;
+};
+
+struct key_pair {
+ struct key key;
+ size_t key_name_off;
+};
+
+struct key_pairs {
+ int count;
+ struct key_pair keys[PAIRS_MAX];
+};
+
+enum import_type {
+ IMPORT_UNSET,
+ IMPORT_PROGRAM,
+ IMPORT_FILE,
+ IMPORT_PARENT,
+};
+
+enum escape_type {
+ ESCAPE_UNSET,
+ ESCAPE_NONE,
+ ESCAPE_REPLACE,
+};
+
+struct udev_rule {
+ struct key action;
+ struct key devpath;
+ struct key kernel;
+ struct key subsystem;
+ struct key driver;
+ struct key_pairs attr;
+
+ struct key kernels;
+ struct key subsystems;
+ struct key drivers;
+ struct key_pairs attrs;
+
+ struct key_pairs env;
+ struct key program;
+ struct key result;
+ struct key import;
+ enum import_type import_type;
+ struct key test;
+ mode_t test_mode_mask;
+ struct key run;
+ struct key wait_for;
+ struct key label;
+ struct key goto_label;
+
+ struct key name;
+ struct key symlink;
+ struct key symlink_match;
+ struct key owner;
+ struct key group;
+ struct key mode;
+ enum escape_type string_escape;
+
+ unsigned int link_priority;
+ int event_timeout;
+ unsigned int partitions;
+ unsigned int last_rule:1,
+ run_ignore_error:1,
+ ignore_device:1,
+ ignore_remove:1;
+
+ size_t bufsize;
+ char buf[];
+};
+
+struct udev_rules {
+ char *buf;
+ size_t bufsize;
+ size_t current;
+ int resolve_names;
+};
+
+extern int udev_rules_init(struct udev_rules *rules, int resolve_names);
+extern void udev_rules_cleanup(struct udev_rules *rules);
+
+extern void udev_rules_iter_init(struct udev_rules *rules);
+extern struct udev_rule *udev_rules_iter_next(struct udev_rules *rules);
+extern struct udev_rule *udev_rules_iter_label(struct udev_rules *rules, const char *label);
+
+extern int udev_rules_get_name(struct udev_rules *rules, struct udevice *udev);
+extern int udev_rules_get_run(struct udev_rules *rules, struct udevice *udev);
+extern int udev_rules_run(struct udevice *udev);
+
+extern void udev_rules_apply_format(struct udevice *udev, char *string, size_t maxsize);
+
+#endif
diff --git a/udev/udev_rules_parse.c b/udev/udev_rules_parse.c
new file mode 100644
index 0000000000..3ada8b1c7d
--- /dev/null
+++ b/udev/udev_rules_parse.c
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2003,2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2003-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+#include "udev_selinux.h"
+
+
+void udev_rules_iter_init(struct udev_rules *rules)
+{
+ dbg("bufsize=%zi\n", rules->bufsize);
+ rules->current = 0;
+}
+
+struct udev_rule *udev_rules_iter_next(struct udev_rules *rules)
+{
+ static struct udev_rule *rule;
+
+ if (!rules)
+ return NULL;
+
+ dbg("current=%zi\n", rules->current);
+ if (rules->current >= rules->bufsize) {
+ dbg("no more rules\n");
+ return NULL;
+ }
+
+ /* get next rule */
+ rule = (struct udev_rule *) (rules->buf + rules->current);
+ rules->current += sizeof(struct udev_rule) + rule->bufsize;
+
+ return rule;
+}
+
+struct udev_rule *udev_rules_iter_label(struct udev_rules *rules, const char *label)
+{
+ static struct udev_rule *rule;
+ size_t start = rules->current;
+
+next:
+ dbg("current=%zi\n", rules->current);
+ if (rules->current >= rules->bufsize) {
+ err("LABEL='%s' not found, GOTO will be ignored\n", label);
+ rules->current = start;
+ return NULL;
+ }
+ rule = (struct udev_rule *) (rules->buf + rules->current);
+
+ if (strcmp(&rule->buf[rule->label.val_off], label) != 0) {
+ dbg("moving forward, looking for label '%s'\n", label);
+ rules->current += sizeof(struct udev_rule) + rule->bufsize;
+ goto next;
+ }
+
+ dbg("found label '%s'\n", label);
+ return rule;
+}
+
+static int get_key(char **line, char **key, enum key_operation *operation, char **value)
+{
+ char *linepos;
+ char *temp;
+
+ linepos = *line;
+ if (linepos == NULL && linepos[0] == '\0')
+ return -1;
+
+ /* skip whitespace */
+ while (isspace(linepos[0]) || linepos[0] == ',')
+ linepos++;
+
+ /* get the key */
+ if (linepos[0] == '\0')
+ return -1;
+ *key = linepos;
+
+ while (1) {
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+ if (isspace(linepos[0]))
+ break;
+ if (linepos[0] == '=')
+ break;
+ if ((linepos[0] == '+') || (linepos[0] == '!') || (linepos[0] == ':'))
+ if (linepos[1] == '=')
+ break;
+ }
+
+ /* remember end of key */
+ temp = linepos;
+
+ /* skip whitespace after key */
+ while (isspace(linepos[0]))
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+
+ /* get operation type */
+ if (linepos[0] == '=' && linepos[1] == '=') {
+ *operation = KEY_OP_MATCH;
+ linepos += 2;
+ dbg("operator=match\n");
+ } else if (linepos[0] == '!' && linepos[1] == '=') {
+ *operation = KEY_OP_NOMATCH;
+ linepos += 2;
+ dbg("operator=nomatch\n");
+ } else if (linepos[0] == '+' && linepos[1] == '=') {
+ *operation = KEY_OP_ADD;
+ linepos += 2;
+ dbg("operator=add\n");
+ } else if (linepos[0] == '=') {
+ *operation = KEY_OP_ASSIGN;
+ linepos++;
+ dbg("operator=assign\n");
+ } else if (linepos[0] == ':' && linepos[1] == '=') {
+ *operation = KEY_OP_ASSIGN_FINAL;
+ linepos += 2;
+ dbg("operator=assign_final\n");
+ } else
+ return -1;
+
+ /* terminate key */
+ temp[0] = '\0';
+ dbg("key='%s'\n", *key);
+
+ /* skip whitespace after operator */
+ while (isspace(linepos[0]))
+ linepos++;
+ if (linepos[0] == '\0')
+ return -1;
+
+ /* get the value*/
+ if (linepos[0] == '"')
+ linepos++;
+ else
+ return -1;
+ *value = linepos;
+
+ temp = strchr(linepos, '"');
+ if (!temp)
+ return -1;
+ temp[0] = '\0';
+ temp++;
+ dbg("value='%s'\n", *value);
+
+ /* move line to next key */
+ *line = temp;
+
+ return 0;
+}
+
+/* extract possible KEY{attr} */
+static char *get_key_attribute(char *str)
+{
+ char *pos;
+ char *attr;
+
+ attr = strchr(str, '{');
+ if (attr != NULL) {
+ attr++;
+ pos = strchr(attr, '}');
+ if (pos == NULL) {
+ err("missing closing brace for format\n");
+ return NULL;
+ }
+ pos[0] = '\0';
+ dbg("attribute='%s'\n", attr);
+ return attr;
+ }
+
+ return NULL;
+}
+
+static int add_rule_key(struct udev_rule *rule, struct key *key,
+ enum key_operation operation, const char *value)
+{
+ size_t val_len = strnlen(value, PATH_SIZE);
+
+ key->operation = operation;
+
+ key->val_off = rule->bufsize;
+ strlcpy(rule->buf + rule->bufsize, value, val_len+1);
+ rule->bufsize += val_len+1;
+
+ return 0;
+}
+
+static int add_rule_key_pair(struct udev_rule *rule, struct key_pairs *pairs,
+ enum key_operation operation, const char *key, const char *value)
+{
+ size_t key_len = strnlen(key, PATH_SIZE);
+
+ if (pairs->count >= PAIRS_MAX) {
+ err("skip, too many keys of the same type in a single rule\n");
+ return -1;
+ }
+
+ add_rule_key(rule, &pairs->keys[pairs->count].key, operation, value);
+
+ /* add the key-name of the pair */
+ pairs->keys[pairs->count].key_name_off = rule->bufsize;
+ strlcpy(rule->buf + rule->bufsize, key, key_len+1);
+ rule->bufsize += key_len+1;
+
+ pairs->count++;
+
+ return 0;
+}
+
+static int add_to_rules(struct udev_rules *rules, char *line, const char *filename, unsigned int lineno)
+{
+ char buf[sizeof(struct udev_rule) + LINE_SIZE];
+ struct udev_rule *rule;
+ size_t rule_size;
+ int valid;
+ char *linepos;
+ char *attr;
+ size_t padding;
+ int physdev = 0;
+ int retval;
+
+ memset(buf, 0x00, sizeof(buf));
+ rule = (struct udev_rule *) buf;
+ rule->event_timeout = -1;
+ linepos = line;
+ valid = 0;
+
+ /* get all the keys */
+ while (1) {
+ char *key;
+ char *value;
+ enum key_operation operation = KEY_OP_UNSET;
+
+ retval = get_key(&linepos, &key, &operation, &value);
+ if (retval)
+ break;
+
+ if (strcasecmp(key, "ACTION") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid ACTION operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->action, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "DEVPATH") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid DEVPATH operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->devpath, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "KERNEL") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid KERNEL operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->kernel, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "SUBSYSTEM") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid SUBSYSTEM operation\n");
+ goto invalid;
+ }
+ /* bus, class, subsystem events should all be the same */
+ if (strcmp(value, "subsystem") == 0 ||
+ strcmp(value, "bus") == 0 ||
+ strcmp(value, "class") == 0) {
+ if (strcmp(value, "bus") == 0 || strcmp(value, "class") == 0)
+ err("'%s' must be specified as 'subsystem' \n"
+ "please fix it in %s:%u", value, filename, lineno);
+ add_rule_key(rule, &rule->subsystem, operation, "subsystem|class|bus");
+ } else
+ add_rule_key(rule, &rule->subsystem, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "DRIVER") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid DRIVER operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->driver, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "ATTR{", sizeof("ATTR{")-1) == 0) {
+ attr = get_key_attribute(key + sizeof("ATTR")-1);
+ if (attr == NULL) {
+ err("error parsing ATTR attribute\n");
+ goto invalid;
+ }
+ if (add_rule_key_pair(rule, &rule->attr, operation, attr, value) != 0)
+ goto invalid;
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "KERNELS") == 0 ||
+ strcasecmp(key, "ID") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid KERNELS operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->kernels, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "SUBSYSTEMS") == 0 ||
+ strcasecmp(key, "BUS") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid SUBSYSTEMS operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->subsystems, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "DRIVERS") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid DRIVERS operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->drivers, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "ATTRS{", sizeof("ATTRS{")-1) == 0 ||
+ strncasecmp(key, "SYSFS{", sizeof("SYSFS{")-1) == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid ATTRS operation\n");
+ goto invalid;
+ }
+ attr = get_key_attribute(key + sizeof("ATTRS")-1);
+ if (attr == NULL) {
+ err("error parsing ATTRS attribute\n");
+ goto invalid;
+ }
+ if (strncmp(attr, "device/", 7) == 0)
+ err("the 'device' link is deprecated and will be removed from a future kernel, \n"
+ "please fix it in %s:%u", filename, lineno);
+ else if (strstr(attr, "../") != NULL)
+ err("do not reference parent sysfs directories directly, that may break with a future kernel, \n"
+ "please fix it in %s:%u", filename, lineno);
+ if (add_rule_key_pair(rule, &rule->attrs, operation, attr, value) != 0)
+ goto invalid;
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "ENV{", sizeof("ENV{")-1) == 0) {
+ attr = get_key_attribute(key + sizeof("ENV")-1);
+ if (attr == NULL) {
+ err("error parsing ENV attribute\n");
+ goto invalid;
+ }
+ if (strncmp(attr, "PHYSDEV", 7) == 0)
+ physdev = 1;
+ if (add_rule_key_pair(rule, &rule->env, operation, attr, value) != 0)
+ goto invalid;
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "PROGRAM") == 0) {
+ add_rule_key(rule, &rule->program, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "RESULT") == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid RESULT operation\n");
+ goto invalid;
+ }
+ add_rule_key(rule, &rule->result, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "IMPORT", sizeof("IMPORT")-1) == 0) {
+ attr = get_key_attribute(key + sizeof("IMPORT")-1);
+ if (attr != NULL && strstr(attr, "program")) {
+ dbg("IMPORT will be executed\n");
+ rule->import_type = IMPORT_PROGRAM;
+ } else if (attr != NULL && strstr(attr, "file")) {
+ dbg("IMPORT will be included as file\n");
+ rule->import_type = IMPORT_FILE;
+ } else if (attr != NULL && strstr(attr, "parent")) {
+ dbg("IMPORT will include the parent values\n");
+ rule->import_type = IMPORT_PARENT;
+ } else {
+ /* figure it out if it is executable */
+ char file[PATH_SIZE];
+ char *pos;
+ struct stat statbuf;
+
+ strlcpy(file, value, sizeof(file));
+ pos = strchr(file, ' ');
+ if (pos)
+ pos[0] = '\0';
+
+ /* allow programs in /lib/udev called without the path */
+ if (strchr(file, '/') == NULL) {
+ strlcpy(file, "/lib/udev/", sizeof(file));
+ strlcat(file, value, sizeof(file));
+ pos = strchr(file, ' ');
+ if (pos)
+ pos[0] = '\0';
+ }
+
+ dbg("IMPORT auto mode for '%s'\n", file);
+ if (!lstat(file, &statbuf) && (statbuf.st_mode & S_IXUSR)) {
+ dbg("IMPORT is executable, will be executed (autotype)\n");
+ rule->import_type = IMPORT_PROGRAM;
+ } else {
+ dbg("IMPORT is not executable, will be included as file (autotype)\n");
+ rule->import_type = IMPORT_FILE;
+ }
+ }
+ add_rule_key(rule, &rule->import, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "TEST", sizeof("TEST")-1) == 0) {
+ if (operation != KEY_OP_MATCH &&
+ operation != KEY_OP_NOMATCH) {
+ err("invalid TEST operation\n");
+ goto invalid;
+ }
+ attr = get_key_attribute(key + sizeof("TEST")-1);
+ if (attr != NULL)
+ rule->test_mode_mask = strtol(attr, NULL, 8);
+ add_rule_key(rule, &rule->test, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "RUN", sizeof("RUN")-1) == 0) {
+ attr = get_key_attribute(key + sizeof("RUN")-1);
+ if (attr != NULL) {
+ if (strstr(attr, "ignore_error"))
+ rule->run_ignore_error = 1;
+ }
+ add_rule_key(rule, &rule->run, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "WAIT_FOR") == 0 || strcasecmp(key, "WAIT_FOR_SYSFS") == 0) {
+ add_rule_key(rule, &rule->wait_for, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "LABEL") == 0) {
+ add_rule_key(rule, &rule->label, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "GOTO") == 0) {
+ add_rule_key(rule, &rule->goto_label, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strncasecmp(key, "NAME", sizeof("NAME")-1) == 0) {
+ attr = get_key_attribute(key + sizeof("NAME")-1);
+ if (attr != NULL) {
+ if (strstr(attr, "all_partitions") != NULL) {
+ dbg("creation of partition nodes requested\n");
+ rule->partitions = DEFAULT_PARTITIONS_COUNT;
+ }
+ if (strstr(attr, "ignore_remove") != NULL) {
+ dbg("remove event should be ignored\n");
+ rule->ignore_remove = 1;
+ }
+ }
+ if (value[0] == '\0')
+ dbg("name empty, node creation supressed\n");
+ add_rule_key(rule, &rule->name, operation, value);
+ continue;
+ }
+
+ if (strcasecmp(key, "SYMLINK") == 0) {
+ if (operation == KEY_OP_MATCH ||
+ operation == KEY_OP_NOMATCH)
+ add_rule_key(rule, &rule->symlink_match, operation, value);
+ else
+ add_rule_key(rule, &rule->symlink, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "OWNER") == 0) {
+ valid = 1;
+ if (rules->resolve_names && (!strchr(value, '$') && !strchr(value, '%'))) {
+ char *endptr;
+ strtoul(value, &endptr, 10);
+ if (endptr[0] != '\0') {
+ char owner[32];
+ uid_t uid = lookup_user(value);
+ dbg("replacing username='%s' by id=%i\n", value, uid);
+ sprintf(owner, "%u", (unsigned int) uid);
+ add_rule_key(rule, &rule->owner, operation, owner);
+ continue;
+ }
+ }
+
+ add_rule_key(rule, &rule->owner, operation, value);
+ continue;
+ }
+
+ if (strcasecmp(key, "GROUP") == 0) {
+ valid = 1;
+ if (rules->resolve_names && (!strchr(value, '$') && !strchr(value, '%'))) {
+ char *endptr;
+ strtoul(value, &endptr, 10);
+ if (endptr[0] != '\0') {
+ char group[32];
+ gid_t gid = lookup_group(value);
+ dbg("replacing groupname='%s' by id=%i\n", value, gid);
+ sprintf(group, "%u", (unsigned int) gid);
+ add_rule_key(rule, &rule->group, operation, group);
+ continue;
+ }
+ }
+
+ add_rule_key(rule, &rule->group, operation, value);
+ continue;
+ }
+
+ if (strcasecmp(key, "MODE") == 0) {
+ add_rule_key(rule, &rule->mode, operation, value);
+ valid = 1;
+ continue;
+ }
+
+ if (strcasecmp(key, "OPTIONS") == 0) {
+ const char *pos;
+
+ if (strstr(value, "last_rule") != NULL) {
+ dbg("last rule to be applied\n");
+ rule->last_rule = 1;
+ }
+ if (strstr(value, "ignore_device") != NULL) {
+ dbg("device should be ignored\n");
+ rule->ignore_device = 1;
+ }
+ if (strstr(value, "ignore_remove") != NULL) {
+ dbg("remove event should be ignored\n");
+ rule->ignore_remove = 1;
+ }
+ pos = strstr(value, "link_priority=");
+ if (pos != NULL) {
+ rule->link_priority = atoi(&pos[strlen("link_priority=")]);
+ dbg("link priority=%i\n", rule->link_priority);
+ }
+ pos = strstr(value, "event_timeout=");
+ if (pos != NULL) {
+ rule->event_timeout = atoi(&pos[strlen("event_timeout=")]);
+ dbg("event timout=%i\n", rule->event_timeout);
+ }
+ pos = strstr(value, "string_escape=");
+ if (pos != NULL) {
+ pos = &pos[strlen("string_escape=")];
+ if (strncmp(pos, "none", strlen("none")) == 0)
+ rule->string_escape = ESCAPE_NONE;
+ else if (strncmp(pos, "replace", strlen("replace")) == 0)
+ rule->string_escape = ESCAPE_REPLACE;
+ }
+ if (strstr(value, "all_partitions") != NULL) {
+ dbg("creation of partition nodes requested\n");
+ rule->partitions = DEFAULT_PARTITIONS_COUNT;
+ }
+ valid = 1;
+ continue;
+ }
+
+ err("unknown key '%s' in %s:%u\n", key, filename, lineno);
+ }
+
+ if (physdev && rule->wait_for.operation == KEY_OP_UNSET)
+ err("PHYSDEV* values are deprecated and will be removed from a future kernel, \n"
+ "please fix it in %s:%u", filename, lineno);
+
+ /* skip line if not any valid key was found */
+ if (!valid)
+ goto invalid;
+
+ /* grow buffer and add rule */
+ rule_size = sizeof(struct udev_rule) + rule->bufsize;
+ padding = (sizeof(size_t) - rule_size % sizeof(size_t)) % sizeof(size_t);
+ dbg("add %zi padding bytes\n", padding);
+ rule_size += padding;
+ rule->bufsize += padding;
+
+ rules->buf = realloc(rules->buf, rules->bufsize + rule_size);
+ if (!rules->buf) {
+ err("realloc failed\n");
+ goto exit;
+ }
+ dbg("adding rule to offset %zi\n", rules->bufsize);
+ memcpy(rules->buf + rules->bufsize, rule, rule_size);
+ rules->bufsize += rule_size;
+exit:
+ return 0;
+
+invalid:
+ err("invalid rule '%s:%u'\n", filename, lineno);
+ return -1;
+}
+
+static int parse_file(struct udev_rules *rules, const char *filename)
+{
+ char line[LINE_SIZE];
+ char *bufline;
+ unsigned int lineno;
+ char *buf;
+ size_t bufsize;
+ size_t cur;
+ size_t count;
+ int retval = 0;
+
+ if (file_map(filename, &buf, &bufsize) != 0) {
+ err("can't open '%s' as rules file: %s\n", filename, strerror(errno));
+ return -1;
+ }
+ info("reading '%s' as rules file\n", filename);
+
+ /* loop through the whole file */
+ cur = 0;
+ lineno = 0;
+ while (cur < bufsize) {
+ unsigned int i, j;
+
+ count = buf_get_line(buf, bufsize, cur);
+ bufline = &buf[cur];
+ cur += count+1;
+ lineno++;
+
+ /* eat the whitespace */
+ while ((count > 0) && isspace(bufline[0])) {
+ bufline++;
+ count--;
+ }
+ if (count == 0)
+ continue;
+
+ /* see if this is a comment */
+ if (bufline[0] == COMMENT_CHARACTER)
+ continue;
+
+ if (count >= sizeof(line)) {
+ err("line too long, rule skipped '%s:%u'\n", filename, lineno);
+ continue;
+ }
+
+ /* skip backslash and newline from multiline rules */
+ for (i = j = 0; i < count; i++) {
+ if (bufline[i] == '\\' && bufline[i+1] == '\n')
+ continue;
+
+ line[j++] = bufline[i];
+ }
+ line[j] = '\0';
+
+ dbg("read '%s'\n", line);
+ add_to_rules(rules, line, filename, lineno);
+ }
+
+ file_unmap(buf, bufsize);
+ return retval;
+}
+
+int udev_rules_init(struct udev_rules *rules, int resolve_names)
+{
+ struct stat statbuf;
+ char filename[PATH_MAX];
+ LIST_HEAD(name_list);
+ LIST_HEAD(sort_list);
+ struct name_entry *name_loop, *name_tmp;
+ struct name_entry *sort_loop, *sort_tmp;
+ int retval = 0;
+
+ memset(rules, 0x00, sizeof(struct udev_rules));
+ rules->resolve_names = resolve_names;
+
+ if (udev_rules_dir[0] != '\0') {
+ /* custom rules location for testing */
+ add_matching_files(&name_list, udev_rules_dir, RULESFILE_SUFFIX);
+ } else {
+ /* read default rules */
+ add_matching_files(&name_list, RULES_LIB_DIR, RULESFILE_SUFFIX);
+
+ /* read user/custom rules */
+ add_matching_files(&sort_list, RULES_ETC_DIR, RULESFILE_SUFFIX);
+
+ /* read dynamic/temporary rules */
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/"RULES_DYN_DIR, sizeof(filename));
+ if (stat(filename, &statbuf) != 0) {
+ create_path(filename);
+ selinux_setfscreatecon(filename, NULL, S_IFDIR|0755);
+ mkdir(filename, 0755);
+ selinux_resetfscreatecon();
+ }
+ add_matching_files(&sort_list, filename, RULESFILE_SUFFIX);
+
+ /* sort all rules files by basename into list of files */
+ list_for_each_entry_safe(sort_loop, sort_tmp, &sort_list, node) {
+ const char *sort_base = strrchr(sort_loop->name, '/');
+
+ if (sort_base == NULL)
+ continue;
+
+ list_for_each_entry_safe(name_loop, name_tmp, &name_list, node) {
+ const char *name_base = strrchr(name_loop->name, '/');
+
+ if (name_base == NULL)
+ continue;
+
+ if (strcmp(name_base, sort_base) > 0)
+ break;
+ }
+ list_move_tail(&sort_loop->node, &name_loop->node);
+ }
+ }
+
+ /* parse list of files */
+ list_for_each_entry_safe(name_loop, name_tmp, &name_list, node) {
+ if (stat(name_loop->name, &statbuf) == 0) {
+ if (statbuf.st_size)
+ parse_file(rules, name_loop->name);
+ else
+ dbg("empty rules file '%s'\n", name_loop->name);
+ } else
+ err("could not read '%s': %s\n", name_loop->name, strerror(errno));
+ list_del(&name_loop->node);
+ free(name_loop);
+ }
+
+ return retval;
+}
+
+void udev_rules_cleanup(struct udev_rules *rules)
+{
+ if (rules->buf) {
+ free(rules->buf);
+ rules->buf = NULL;
+ }
+}
+
diff --git a/udev/udev_selinux.c b/udev/udev_selinux.c
new file mode 100644
index 0000000000..eec950194c
--- /dev/null
+++ b/udev/udev_selinux.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2004 Daniel Walsh
+ *
+ * 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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <selinux/selinux.h>
+
+#include "udev.h"
+#include "udev_selinux.h"
+
+static security_context_t prev_scontext = NULL;
+
+static int is_selinux_running(void)
+{
+ static int selinux_enabled = -1;
+
+ if (selinux_enabled == -1)
+ selinux_enabled = (is_selinux_enabled() > 0);
+
+ dbg("selinux=%i\n", selinux_enabled);
+ return selinux_enabled;
+}
+
+static char *get_media(const char *devname, int mode)
+{
+ FILE *fp;
+ char procfile[PATH_MAX];
+ char mediabuf[256];
+ int size;
+ char *media = NULL;
+
+ if (!(mode & S_IFBLK))
+ return NULL;
+
+ snprintf(procfile, PATH_MAX, "/proc/ide/%s/media", devname);
+ procfile[PATH_MAX-1] = '\0';
+
+ fp = fopen(procfile, "r");
+ if (!fp)
+ goto out;
+
+ if (fgets(mediabuf, sizeof(mediabuf), fp) == NULL)
+ goto close_out;
+
+ size = strlen(mediabuf);
+ while (size-- > 0) {
+ if (isspace(mediabuf[size])) {
+ mediabuf[size] = '\0';
+ } else {
+ break;
+ }
+ }
+
+ media = strdup(mediabuf);
+ info("selinux_get_media(%s)='%s'\n", devname, media);
+
+close_out:
+ fclose(fp);
+out:
+ return media;
+}
+
+void selinux_setfilecon(const char *file, const char *devname, unsigned int mode)
+{
+ if (is_selinux_running()) {
+ security_context_t scontext = NULL;
+ char *media;
+ int ret = -1;
+
+ if (devname) {
+ media = get_media(devname, mode);
+ if (media) {
+ ret = matchmediacon(media, &scontext);
+ free(media);
+ }
+ }
+
+ if (ret < 0)
+ if (matchpathcon(file, mode, &scontext) < 0) {
+ err("matchpathcon(%s) failed\n", file);
+ return;
+ }
+
+ if (lsetfilecon(file, scontext) < 0)
+ err("setfilecon %s failed: %s\n", file, strerror(errno));
+
+ freecon(scontext);
+ }
+}
+
+void selinux_setfscreatecon(const char *file, const char *devname, unsigned int mode)
+{
+ if (is_selinux_running()) {
+ security_context_t scontext = NULL;
+ char *media;
+ int ret = -1;
+
+ if (devname) {
+ media = get_media(devname, mode);
+ if (media) {
+ ret = matchmediacon(media, &scontext);
+ free(media);
+ }
+ }
+
+ if (ret < 0)
+ if (matchpathcon(file, mode, &scontext) < 0) {
+ err("matchpathcon(%s) failed\n", file);
+ return;
+ }
+
+ if (setfscreatecon(scontext) < 0)
+ err("setfscreatecon %s failed: %s\n", file, strerror(errno));
+
+ freecon(scontext);
+ }
+}
+
+void selinux_resetfscreatecon(void)
+{
+ if (is_selinux_running()) {
+ if (setfscreatecon(prev_scontext) < 0)
+ err("setfscreatecon failed: %s\n", strerror(errno));
+ }
+}
+
+void selinux_init(void)
+{
+ /*
+ * record the present security context, for file-creation
+ * restoration creation purposes.
+ */
+ if (is_selinux_running()) {
+ if (!udev_root[0])
+ err("selinux_init: udev_root not set\n");
+ matchpathcon_init_prefix(NULL, udev_root);
+ if (getfscreatecon(&prev_scontext) < 0) {
+ err("getfscreatecon failed\n");
+ prev_scontext = NULL;
+ }
+ }
+}
+
+void selinux_exit(void)
+{
+ if (is_selinux_running() && prev_scontext) {
+ freecon(prev_scontext);
+ prev_scontext = NULL;
+ }
+}
diff --git a/udev/udev_selinux.h b/udev/udev_selinux.h
new file mode 100644
index 0000000000..73567d6cfe
--- /dev/null
+++ b/udev/udev_selinux.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004 Daniel Walsh
+ *
+ * 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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+#ifndef _UDEV_SELINUX_H
+#define _UDEV_SELINUX_H
+
+#ifdef USE_SELINUX
+
+extern void selinux_setfilecon(const char *file, const char *devname, unsigned int mode);
+extern void selinux_setfscreatecon(const char *file, const char *devname, unsigned int mode);
+extern void selinux_resetfscreatecon(void);
+extern void selinux_init(void);
+extern void selinux_exit(void);
+
+#else
+
+static inline void selinux_setfilecon(const char *file, const char *devname, unsigned int mode) {}
+static inline void selinux_setfscreatecon(const char *file, const char *devname, unsigned int mode) {}
+static inline void selinux_resetfscreatecon(void) {}
+static inline void selinux_init(void) {}
+static inline void selinux_exit(void) {}
+
+#endif /* USE_SELINUX */
+#endif /* _UDEV_USE_SELINUX */
diff --git a/udev/udev_sysdeps.c b/udev/udev_sysdeps.c
new file mode 100644
index 0000000000..9447cca936
--- /dev/null
+++ b/udev/udev_sysdeps.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2003 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+#ifdef __GLIBC__
+size_t strlcpy(char *dst, const char *src, size_t size)
+{
+ size_t bytes = 0;
+ char *q = dst;
+ const char *p = src;
+ char ch;
+
+ while ((ch = *p++)) {
+ if (bytes+1 < size)
+ *q++ = ch;
+ bytes++;
+ }
+
+ /* If size == 0 there is no space for a final null... */
+ if (size)
+ *q = '\0';
+ return bytes;
+}
+
+size_t strlcat(char *dst, const char *src, size_t size)
+{
+ size_t bytes = 0;
+ char *q = dst;
+ const char *p = src;
+ char ch;
+
+ while (bytes < size && *q) {
+ q++;
+ bytes++;
+ }
+ if (bytes == size)
+ return (bytes + strlen(src));
+
+ while ((ch = *p++)) {
+ if (bytes+1 < size)
+ *q++ = ch;
+ bytes++;
+ }
+
+ *q = '\0';
+ return bytes;
+}
+#endif /* __GLIBC__ */
diff --git a/udev/udev_sysdeps.h b/udev/udev_sysdeps.h
new file mode 100644
index 0000000000..d4f03686af
--- /dev/null
+++ b/udev/udev_sysdeps.h
@@ -0,0 +1,172 @@
+/*
+ * wrapping of libc features and kernel interfaces
+ *
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef _UDEV_SYSDEPS_H_
+#define _UDEV_SYSDEPS_H_
+
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+
+/* needed until Inotify! syscalls reach glibc */
+#include <sys/syscall.h>
+#ifndef __NR_inotify_init
+#if defined(__i386__)
+# define __NR_inotify_init 291
+# define __NR_inotify_add_watch 292
+# define __NR_inotify_rm_watch 293
+#elif defined(__x86_64__)
+# define __NR_inotify_init 253
+# define __NR_inotify_add_watch 254
+# define __NR_inotify_rm_watch 255
+#elif defined(__powerpc__) || defined(__powerpc64__)
+# define __NR_inotify_init 275
+# define __NR_inotify_add_watch 276
+# define __NR_inotify_rm_watch 277
+#elif defined (__ia64__)
+# define __NR_inotify_init 1277
+# define __NR_inotify_add_watch 1278
+# define __NR_inotify_rm_watch 1279
+#elif defined (__s390__)
+# define __NR_inotify_init 284
+# define __NR_inotify_add_watch 285
+# define __NR_inotify_rm_watch 286
+#elif defined (__alpha__)
+# define __NR_inotify_init 444
+# define __NR_inotify_add_watch 445
+# define __NR_inotify_rm_watch 446
+#elif defined (__sparc__) || defined (__sparc64__)
+# define __NR_inotify_init 151
+# define __NR_inotify_add_watch 152
+# define __NR_inotify_rm_watch 156
+#elif defined (__arm__)
+# define __NR_inotify_init __NR_SYSCALL_BASE+316
+# define __NR_inotify_add_watch __NR_SYSCALL_BASE+317
+# define __NR_inotify_rm_watch __NR_SYSCALL_BASE+318
+#elif defined (__sh__)
+# define __NR_inotify_init 290
+# define __NR_inotify_add_watch 291
+# define __NR_inotify_rm_watch 292
+#elif defined (__m32r__)
+# define __NR_inotify_init 290
+# define __NR_inotify_add_watch 291
+# define __NR_inotify_rm_watch 292
+#elif defined (__hppa__)
+# define __NR_inotify_init 269
+# define __NR_inotify_add_watch 270
+# define __NR_inotify_rm_watch 271
+#elif defined (__mips__)
+# include <sgidefs.h>
+# if _MIPS_SIM == _MIPS_SIM_ABI32
+# define __NR_Linux 4000
+# define __NR_inotify_init (__NR_Linux + 284)
+# define __NR_inotify_add_watch (__NR_Linux + 285)
+# define __NR_inotify_rm_watch (__NR_Linux + 286)
+# elif _MIPS_SIM == _MIPS_SIM_ABI64
+# define __NR_Linux 5000
+# define __NR_inotify_init (__NR_Linux + 243)
+# define __NR_inotify_add_watch (__NR_Linux + 244)
+# define __NR_inotify_rm_watch (__NR_Linux + 245)
+# elif _MIPS_SIM == _MIPS_SIM_NABI32
+# define __NR_Linux 6000
+# define __NR_inotify_init (__NR_Linux + 247)
+# define __NR_inotify_add_watch (__NR_Linux + 248)
+# define __NR_inotify_rm_watch (__NR_Linux + 249)
+# endif
+#else
+#warning "inotify unsupported on this architecture!"
+#endif
+#endif /* __NR_inotify_init */
+
+/* dummy if we don't have the syscalls defined */
+#ifndef __NR_inotify_init
+static inline int inotify_init(void)
+{
+ return -1;
+}
+
+static inline int inotify_add_watch(int fd, const char *name, uint32_t mask)
+{
+ return -1;
+}
+#else
+/* needed until /usr/include/sys/inotify.h is working */
+#ifndef __GLIBC__
+#include <sys/inotify.h>
+#else
+static inline int inotify_init(void)
+{
+ return syscall(__NR_inotify_init);
+}
+
+static inline int inotify_add_watch(int fd, const char *name, uint32_t mask)
+{
+ return syscall(__NR_inotify_add_watch, fd, name, mask);
+}
+#endif /* __GLIBC__ */
+#endif /* __NR_inotify_init */
+
+#ifndef IN_CREATE
+#define IN_CREATE 0x00000100 /* Subfile was created */
+#define IN_MOVED_FROM 0x00000040 /* File was moved from X */
+#define IN_MOVED_TO 0x00000080 /* File was moved to Y */
+#define IN_DELETE 0x00000200 /* Subfile was deleted */
+#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */
+#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* moves */
+#endif /* IN_CREATE */
+
+/* needed for our signal handlers to work */
+#undef asmlinkage
+#ifdef __i386__
+#define asmlinkage __attribute__((regparm(0)))
+#else
+#define asmlinkage
+#endif /* __i386__ */
+
+/* headers are broken on some architectures */
+#ifndef __FD_SET
+#define __FD_SET(d, set) ((set)->fds_bits[__FDELT(d)] |= __FDMASK(d))
+#endif
+#ifndef __FD_CLR
+#define __FD_CLR(d, set) ((set)->fds_bits[__FDELT(d)] &= ~__FDMASK(d))
+#endif
+#ifndef __FD_ISSET
+#define __FD_ISSET(d, set) (((set)->fds_bits[__FDELT(d)] & __FDMASK(d)) != 0)
+#endif
+#ifndef __FD_ZERO
+#define __FD_ZERO(set) ((void) memset ((void*) (set), 0, sizeof (fd_set)))
+#endif
+
+#ifndef NETLINK_KOBJECT_UEVENT
+#define NETLINK_KOBJECT_UEVENT 15
+#endif
+
+#ifndef SO_RCVBUFFORCE
+#if defined(__alpha__) || defined(__hppa__) || defined(__sparc__) || defined(__sparc_v9__)
+#define SO_RCVBUFFORCE 0x100b
+#else
+#define SO_RCVBUFFORCE 33
+#endif
+#endif
+
+extern size_t strlcpy(char *dst, const char *src, size_t size);
+extern size_t strlcat(char *dst, const char *src, size_t size);
+
+#endif
diff --git a/udev/udev_sysfs.c b/udev/udev_sysfs.c
new file mode 100644
index 0000000000..c4cd4ab75d
--- /dev/null
+++ b/udev/udev_sysfs.c
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "udev.h"
+
+char sysfs_path[PATH_SIZE];
+
+/* device cache */
+static LIST_HEAD(dev_list);
+
+/* attribute value cache */
+static LIST_HEAD(attr_list);
+struct sysfs_attr {
+ struct list_head node;
+ char path[PATH_SIZE];
+ char *value; /* points to value_local if value is cached */
+ char value_local[NAME_SIZE];
+};
+
+int sysfs_init(void)
+{
+ const char *env;
+
+ env = getenv("SYSFS_PATH");
+ if (env) {
+ strlcpy(sysfs_path, env, sizeof(sysfs_path));
+ remove_trailing_chars(sysfs_path, '/');
+ } else
+ strlcpy(sysfs_path, "/sys", sizeof(sysfs_path));
+ dbg("sysfs_path='%s'\n", sysfs_path);
+
+ INIT_LIST_HEAD(&dev_list);
+ INIT_LIST_HEAD(&attr_list);
+ return 0;
+}
+
+void sysfs_cleanup(void)
+{
+ struct sysfs_attr *attr_loop;
+ struct sysfs_attr *attr_temp;
+ struct sysfs_device *dev_loop;
+ struct sysfs_device *dev_temp;
+
+ list_for_each_entry_safe(attr_loop, attr_temp, &attr_list, node) {
+ list_del(&attr_loop->node);
+ free(attr_loop);
+ }
+
+ list_for_each_entry_safe(dev_loop, dev_temp, &dev_list, node) {
+ list_del(&dev_loop->node);
+ free(dev_loop);
+ }
+}
+
+void sysfs_device_set_values(struct sysfs_device *dev, const char *devpath,
+ const char *subsystem, const char *driver)
+{
+ char *pos;
+
+ strlcpy(dev->devpath, devpath, sizeof(dev->devpath));
+ if (subsystem != NULL)
+ strlcpy(dev->subsystem, subsystem, sizeof(dev->subsystem));
+ if (driver != NULL)
+ strlcpy(dev->driver, driver, sizeof(dev->driver));
+
+ /* set kernel name */
+ pos = strrchr(dev->devpath, '/');
+ if (pos == NULL)
+ return;
+ strlcpy(dev->kernel, &pos[1], sizeof(dev->kernel));
+ dbg("kernel='%s'\n", dev->kernel);
+
+ /* some devices have '!' in their name, change that to '/' */
+ pos = dev->kernel;
+ while (pos[0] != '\0') {
+ if (pos[0] == '!')
+ pos[0] = '/';
+ pos++;
+ }
+
+ /* get kernel number */
+ pos = &dev->kernel[strlen(dev->kernel)];
+ while (isdigit(pos[-1]))
+ pos--;
+ strlcpy(dev->kernel_number, pos, sizeof(dev->kernel_number));
+ dbg("kernel_number='%s'\n", dev->kernel_number);
+}
+
+int sysfs_resolve_link(char *devpath, size_t size)
+{
+ char link_path[PATH_SIZE];
+ char link_target[PATH_SIZE];
+ int len;
+ int i;
+ int back;
+
+ strlcpy(link_path, sysfs_path, sizeof(link_path));
+ strlcat(link_path, devpath, sizeof(link_path));
+ len = readlink(link_path, link_target, sizeof(link_target));
+ if (len <= 0)
+ return -1;
+ link_target[len] = '\0';
+ dbg("path link '%s' points to '%s'\n", devpath, link_target);
+
+ for (back = 0; strncmp(&link_target[back * 3], "../", 3) == 0; back++)
+ ;
+ dbg("base '%s', tail '%s', back %i\n", devpath, &link_target[back * 3], back);
+ for (i = 0; i <= back; i++) {
+ char *pos = strrchr(devpath, '/');
+
+ if (pos == NULL)
+ return -1;
+ pos[0] = '\0';
+ }
+ dbg("after moving back '%s'\n", devpath);
+ strlcat(devpath, "/", size);
+ strlcat(devpath, &link_target[back * 3], size);
+ return 0;
+}
+
+struct sysfs_device *sysfs_device_get(const char *devpath)
+{
+ char path[PATH_SIZE];
+ char devpath_real[PATH_SIZE];
+ struct sysfs_device *dev;
+ struct sysfs_device *dev_loop;
+ struct stat statbuf;
+ char link_path[PATH_SIZE];
+ char link_target[PATH_SIZE];
+ int len;
+ char *pos;
+
+ /* we handle only these devpathes */
+ if (devpath != NULL &&
+ strncmp(devpath, "/devices/", 9) != 0 &&
+ strncmp(devpath, "/subsystem/", 11) != 0 &&
+ strncmp(devpath, "/module/", 8) != 0 &&
+ strncmp(devpath, "/bus/", 5) != 0 &&
+ strncmp(devpath, "/class/", 7) != 0 &&
+ strncmp(devpath, "/block/", 7) != 0)
+ return NULL;
+
+ dbg("open '%s'\n", devpath);
+ strlcpy(devpath_real, devpath, sizeof(devpath_real));
+ remove_trailing_chars(devpath_real, '/');
+ if (devpath[0] == '\0' )
+ return NULL;
+
+ /* look for device already in cache (we never put an untranslated path in the cache) */
+ list_for_each_entry(dev_loop, &dev_list, node) {
+ if (strcmp(dev_loop->devpath, devpath_real) == 0) {
+ dbg("found in cache '%s'\n", dev_loop->devpath);
+ return dev_loop;
+ }
+ }
+
+ /* if we got a link, resolve it to the real device */
+ strlcpy(path, sysfs_path, sizeof(path));
+ strlcat(path, devpath_real, sizeof(path));
+ if (lstat(path, &statbuf) != 0) {
+ dbg("stat '%s' failed: %s\n", path, strerror(errno));
+ return NULL;
+ }
+ if (S_ISLNK(statbuf.st_mode)) {
+ if (sysfs_resolve_link(devpath_real, sizeof(devpath_real)) != 0)
+ return NULL;
+
+ /* now look for device in cache after path translation */
+ list_for_each_entry(dev_loop, &dev_list, node) {
+ if (strcmp(dev_loop->devpath, devpath_real) == 0) {
+ dbg("found in cache '%s'\n", dev_loop->devpath);
+ return dev_loop;
+ }
+ }
+ }
+
+ /* it is a new device */
+ dbg("new uncached device '%s'\n", devpath_real);
+ dev = malloc(sizeof(struct sysfs_device));
+ if (dev == NULL)
+ return NULL;
+ memset(dev, 0x00, sizeof(struct sysfs_device));
+
+ sysfs_device_set_values(dev, devpath_real, NULL, NULL);
+
+ /* get subsystem name */
+ strlcpy(link_path, sysfs_path, sizeof(link_path));
+ strlcat(link_path, dev->devpath, sizeof(link_path));
+ strlcat(link_path, "/subsystem", sizeof(link_path));
+ len = readlink(link_path, link_target, sizeof(link_target));
+ if (len > 0) {
+ /* get subsystem from "subsystem" link */
+ link_target[len] = '\0';
+ dbg("subsystem link '%s' points to '%s'\n", link_path, link_target);
+ pos = strrchr(link_target, '/');
+ if (pos != NULL)
+ strlcpy(dev->subsystem, &pos[1], sizeof(dev->subsystem));
+ } else if (strstr(dev->devpath, "/drivers/") != NULL) {
+ strlcpy(dev->subsystem, "drivers", sizeof(dev->subsystem));
+ } else if (strncmp(dev->devpath, "/module/", 8) == 0) {
+ strlcpy(dev->subsystem, "module", sizeof(dev->subsystem));
+ } else if (strncmp(dev->devpath, "/subsystem/", 11) == 0) {
+ pos = strrchr(dev->devpath, '/');
+ if (pos == &dev->devpath[10])
+ strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem));
+ } else if (strncmp(dev->devpath, "/class/", 7) == 0) {
+ pos = strrchr(dev->devpath, '/');
+ if (pos == &dev->devpath[6])
+ strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem));
+ } else if (strncmp(dev->devpath, "/bus/", 5) == 0) {
+ pos = strrchr(dev->devpath, '/');
+ if (pos == &dev->devpath[4])
+ strlcpy(dev->subsystem, "subsystem", sizeof(dev->subsystem));
+ }
+
+ /* get driver name */
+ strlcpy(link_path, sysfs_path, sizeof(link_path));
+ strlcat(link_path, dev->devpath, sizeof(link_path));
+ strlcat(link_path, "/driver", sizeof(link_path));
+ len = readlink(link_path, link_target, sizeof(link_target));
+ if (len > 0) {
+ link_target[len] = '\0';
+ dbg("driver link '%s' points to '%s'\n", link_path, link_target);
+ pos = strrchr(link_target, '/');
+ if (pos != NULL)
+ strlcpy(dev->driver, &pos[1], sizeof(dev->driver));
+ }
+
+ dbg("add to cache 'devpath=%s', subsystem='%s', driver='%s'\n", dev->devpath, dev->subsystem, dev->driver);
+ list_add(&dev->node, &dev_list);
+
+ return dev;
+}
+
+struct sysfs_device *sysfs_device_get_parent(struct sysfs_device *dev)
+{
+ char parent_devpath[PATH_SIZE];
+ char *pos;
+
+ dbg("open '%s'\n", dev->devpath);
+
+ /* look if we already know the parent */
+ if (dev->parent != NULL)
+ return dev->parent;
+
+ strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
+ dbg("'%s'\n", parent_devpath);
+
+ /* strip last element */
+ pos = strrchr(parent_devpath, '/');
+ if (pos == NULL || pos == parent_devpath)
+ return NULL;
+ pos[0] = '\0';
+
+ if (strncmp(parent_devpath, "/class", 6) == 0) {
+ pos = strrchr(parent_devpath, '/');
+ if (pos == &parent_devpath[6] || pos == parent_devpath) {
+ dbg("/class top level, look for device link\n");
+ goto device_link;
+ }
+ }
+ if (strcmp(parent_devpath, "/block") == 0) {
+ dbg("/block top level, look for device link\n");
+ goto device_link;
+ }
+
+ /* are we at the top level? */
+ pos = strrchr(parent_devpath, '/');
+ if (pos == NULL || pos == parent_devpath)
+ return NULL;
+
+ /* get parent and remember it */
+ dev->parent = sysfs_device_get(parent_devpath);
+ return dev->parent;
+
+device_link:
+ strlcpy(parent_devpath, dev->devpath, sizeof(parent_devpath));
+ strlcat(parent_devpath, "/device", sizeof(parent_devpath));
+ if (sysfs_resolve_link(parent_devpath, sizeof(parent_devpath)) != 0)
+ return NULL;
+
+ /* get parent and remember it */
+ dev->parent = sysfs_device_get(parent_devpath);
+ return dev->parent;
+}
+
+struct sysfs_device *sysfs_device_get_parent_with_subsystem(struct sysfs_device *dev, const char *subsystem)
+{
+ struct sysfs_device *dev_parent;
+
+ dev_parent = sysfs_device_get_parent(dev);
+ while (dev_parent != NULL) {
+ if (strcmp(dev_parent->subsystem, subsystem) == 0)
+ return dev_parent;
+ dev_parent = sysfs_device_get_parent(dev_parent);
+ }
+ return NULL;
+}
+
+char *sysfs_attr_get_value(const char *devpath, const char *attr_name)
+{
+ char path_full[PATH_SIZE];
+ const char *path;
+ char value[NAME_SIZE];
+ struct sysfs_attr *attr_loop;
+ struct sysfs_attr *attr;
+ struct stat statbuf;
+ int fd;
+ ssize_t size;
+ size_t sysfs_len;
+
+ dbg("open '%s'/'%s'\n", devpath, attr_name);
+ sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
+ if(sysfs_len >= sizeof(path_full))
+ sysfs_len = sizeof(path_full) - 1;
+ path = &path_full[sysfs_len];
+ strlcat(path_full, devpath, sizeof(path_full));
+ strlcat(path_full, "/", sizeof(path_full));
+ strlcat(path_full, attr_name, sizeof(path_full));
+
+ /* look for attribute in cache */
+ list_for_each_entry(attr_loop, &attr_list, node) {
+ if (strcmp(attr_loop->path, path) == 0) {
+ dbg("found in cache '%s'\n", attr_loop->path);
+ return attr_loop->value;
+ }
+ }
+
+ /* store attribute in cache (also negatives are kept in cache) */
+ dbg("new uncached attribute '%s'\n", path_full);
+ attr = malloc(sizeof(struct sysfs_attr));
+ if (attr == NULL)
+ return NULL;
+ memset(attr, 0x00, sizeof(struct sysfs_attr));
+ strlcpy(attr->path, path, sizeof(attr->path));
+ dbg("add to cache '%s'\n", path_full);
+ list_add(&attr->node, &attr_list);
+
+ if (lstat(path_full, &statbuf) != 0) {
+ dbg("stat '%s' failed: %s\n", path_full, strerror(errno));
+ goto out;
+ }
+
+ if (S_ISLNK(statbuf.st_mode)) {
+ /* links return the last element of the target path */
+ char link_target[PATH_SIZE];
+ int len;
+ const char *pos;
+
+ len = readlink(path_full, link_target, sizeof(link_target));
+ if (len > 0) {
+ link_target[len] = '\0';
+ pos = strrchr(link_target, '/');
+ if (pos != NULL) {
+ dbg("cache '%s' with link value '%s'\n", path_full, value);
+ strlcpy(attr->value_local, &pos[1], sizeof(attr->value_local));
+ attr->value = attr->value_local;
+ }
+ }
+ goto out;
+ }
+
+ /* skip directories */
+ if (S_ISDIR(statbuf.st_mode))
+ goto out;
+
+ /* skip non-readable files */
+ if ((statbuf.st_mode & S_IRUSR) == 0)
+ goto out;
+
+ /* read attribute value */
+ fd = open(path_full, O_RDONLY);
+ if (fd < 0) {
+ dbg("attribute '%s' can not be opened\n", path_full);
+ goto out;
+ }
+ size = read(fd, value, sizeof(value));
+ close(fd);
+ if (size < 0)
+ goto out;
+ if (size == sizeof(value))
+ goto out;
+
+ /* got a valid value, store and return it */
+ value[size] = '\0';
+ remove_trailing_chars(value, '\n');
+ dbg("cache '%s' with attribute value '%s'\n", path_full, value);
+ strlcpy(attr->value_local, value, sizeof(attr->value_local));
+ attr->value = attr->value_local;
+
+out:
+ return attr->value;
+}
+
+int sysfs_lookup_devpath_by_subsys_id(char *devpath_full, size_t len, const char *subsystem, const char *id)
+{
+ size_t sysfs_len;
+ char path_full[PATH_SIZE];
+ char *path;
+ struct stat statbuf;
+
+ sysfs_len = strlcpy(path_full, sysfs_path, sizeof(path_full));
+ path = &path_full[sysfs_len];
+
+ if (strcmp(subsystem, "subsystem") == 0) {
+ strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+
+ strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+ goto out;
+
+ strlcpy(path, "/class/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+ }
+
+ if (strcmp(subsystem, "module") == 0) {
+ strlcpy(path, "/module/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+ goto out;
+ }
+
+ if (strcmp(subsystem, "drivers") == 0) {
+ char subsys[NAME_SIZE];
+ char *driver;
+
+ strlcpy(subsys, id, sizeof(subsys));
+ driver = strchr(subsys, ':');
+ if (driver != NULL) {
+ driver[0] = '\0';
+ driver = &driver[1];
+ strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len);
+ strlcat(path, subsys, sizeof(path_full) - sysfs_len);
+ strlcat(path, "/drivers/", sizeof(path_full) - sysfs_len);
+ strlcat(path, driver, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+
+ strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len);
+ strlcat(path, subsys, sizeof(path_full) - sysfs_len);
+ strlcat(path, "/drivers/", sizeof(path_full) - sysfs_len);
+ strlcat(path, driver, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+ }
+ goto out;
+ }
+
+ strlcpy(path, "/subsystem/", sizeof(path_full) - sysfs_len);
+ strlcat(path, subsystem, sizeof(path_full) - sysfs_len);
+ strlcat(path, "/devices/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+
+ strlcpy(path, "/bus/", sizeof(path_full) - sysfs_len);
+ strlcat(path, subsystem, sizeof(path_full) - sysfs_len);
+ strlcat(path, "/devices/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+
+ strlcpy(path, "/class/", sizeof(path_full) - sysfs_len);
+ strlcat(path, subsystem, sizeof(path_full) - sysfs_len);
+ strlcat(path, "/", sizeof(path_full) - sysfs_len);
+ strlcat(path, id, sizeof(path_full) - sysfs_len);
+ if (stat(path_full, &statbuf) == 0)
+ goto found;
+out:
+ return 0;
+found:
+ if (S_ISLNK(statbuf.st_mode))
+ sysfs_resolve_link(path, sizeof(path_full) - sysfs_len);
+ strlcpy(devpath_full, path, len);
+ return 1;
+}
diff --git a/udev/udev_utils.c b/udev/udev_utils.c
new file mode 100644
index 0000000000..00b67dadc6
--- /dev/null
+++ b/udev/udev_utils.c
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+
+#include "udev.h"
+
+
+int log_priority(const char *priority)
+{
+ char *endptr;
+ int prio;
+
+ prio = strtol(priority, &endptr, 10);
+ if (endptr[0] == '\0')
+ return prio;
+ if (strncasecmp(priority, "err", 3) == 0)
+ return LOG_ERR;
+ if (strcasecmp(priority, "info") == 0)
+ return LOG_INFO;
+ if (strcasecmp(priority, "debug") == 0)
+ return LOG_DEBUG;
+ if (string_is_true(priority))
+ return LOG_ERR;
+
+ return 0;
+}
+
+struct name_entry *name_list_add(struct list_head *name_list, const char *name, int sort)
+{
+ struct name_entry *name_loop;
+ struct name_entry *name_new;
+
+ /* avoid duplicate entries */
+ list_for_each_entry(name_loop, name_list, node) {
+ if (strcmp(name_loop->name, name) == 0) {
+ dbg("'%s' is already in the list\n", name);
+ return name_loop;
+ }
+ }
+
+ if (sort)
+ list_for_each_entry(name_loop, name_list, node) {
+ if (strcmp(name_loop->name, name) > 0)
+ break;
+ }
+
+ name_new = malloc(sizeof(struct name_entry));
+ if (name_new == NULL)
+ return NULL;
+
+ strlcpy(name_new->name, name, sizeof(name_new->name));
+ dbg("adding '%s'\n", name_new->name);
+ list_add_tail(&name_new->node, &name_loop->node);
+
+ return name_new;
+}
+
+struct name_entry *name_list_key_add(struct list_head *name_list, const char *key, const char *value)
+{
+ struct name_entry *name_loop;
+ struct name_entry *name_new;
+
+ list_for_each_entry(name_loop, name_list, node) {
+ if (strncmp(name_loop->name, key, strlen(key)) == 0) {
+ dbg("key already present '%s', replace it\n", name_loop->name);
+ snprintf(name_loop->name, sizeof(name_loop->name), "%s=%s", key, value);
+ name_loop->name[sizeof(name_loop->name)-1] = '\0';
+ return name_loop;
+ }
+ }
+
+ name_new = malloc(sizeof(struct name_entry));
+ if (name_new == NULL)
+ return NULL;
+
+ snprintf(name_new->name, sizeof(name_new->name), "%s=%s", key, value);
+ name_new->name[sizeof(name_new->name)-1] = '\0';
+ dbg("adding '%s'\n", name_new->name);
+ list_add_tail(&name_new->node, &name_loop->node);
+
+ return name_new;
+}
+
+int name_list_key_remove(struct list_head *name_list, const char *key)
+{
+ struct name_entry *name_loop;
+ struct name_entry *name_tmp;
+ size_t keylen = strlen(key);
+ int retval = 0;
+
+ list_for_each_entry_safe(name_loop, name_tmp, name_list, node) {
+ if (strncmp(name_loop->name, key, keylen) != 0)
+ continue;
+ if (name_loop->name[keylen] != '=')
+ continue;
+ list_del(&name_loop->node);
+ free(name_loop);
+ retval = 1;
+ break;
+ }
+ return retval;
+}
+
+void name_list_cleanup(struct list_head *name_list)
+{
+ struct name_entry *name_loop;
+ struct name_entry *name_tmp;
+
+ list_for_each_entry_safe(name_loop, name_tmp, name_list, node) {
+ list_del(&name_loop->node);
+ free(name_loop);
+ }
+}
+
+/* calls function for every file found in specified directory */
+int add_matching_files(struct list_head *name_list, const char *dirname, const char *suffix)
+{
+ struct dirent *ent;
+ DIR *dir;
+ char filename[PATH_SIZE];
+
+ dbg("open directory '%s'\n", dirname);
+ dir = opendir(dirname);
+ if (dir == NULL) {
+ err("unable to open '%s': %s\n", dirname, strerror(errno));
+ return -1;
+ }
+
+ while (1) {
+ ent = readdir(dir);
+ if (ent == NULL || ent->d_name[0] == '\0')
+ break;
+
+ if ((ent->d_name[0] == '.') || (ent->d_name[0] == COMMENT_CHARACTER))
+ continue;
+
+ /* look for file matching with specified suffix */
+ if (suffix != NULL) {
+ const char *ext;
+
+ ext = strrchr(ent->d_name, '.');
+ if (ext == NULL)
+ continue;
+ if (strcmp(ext, suffix) != 0)
+ continue;
+ }
+ dbg("put file '%s/%s' into list\n", dirname, ent->d_name);
+
+ snprintf(filename, sizeof(filename), "%s/%s", dirname, ent->d_name);
+ filename[sizeof(filename)-1] = '\0';
+ name_list_add(name_list, filename, 1);
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+uid_t lookup_user(const char *user)
+{
+ struct passwd *pw;
+ uid_t uid = 0;
+
+ errno = 0;
+ pw = getpwnam(user);
+ if (pw == NULL) {
+ if (errno == 0 || errno == ENOENT || errno == ESRCH)
+ err("specified user '%s' unknown\n", user);
+ else
+ err("error resolving user '%s': %s\n", user, strerror(errno));
+ } else
+ uid = pw->pw_uid;
+
+ return uid;
+}
+
+extern gid_t lookup_group(const char *group)
+{
+ struct group *gr;
+ gid_t gid = 0;
+
+ errno = 0;
+ gr = getgrnam(group);
+ if (gr == NULL) {
+ if (errno == 0 || errno == ENOENT || errno == ESRCH)
+ err("specified group '%s' unknown\n", group);
+ else
+ err("error resolving group '%s': %s\n", group, strerror(errno));
+ } else
+ gid = gr->gr_gid;
+
+ return gid;
+}
+
diff --git a/udev/udev_utils_file.c b/udev/udev_utils_file.c
new file mode 100644
index 0000000000..a492785af9
--- /dev/null
+++ b/udev/udev_utils_file.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "udev.h"
+#include "udev_selinux.h"
+
+int create_path(const char *path)
+{
+ char p[PATH_SIZE];
+ char *pos;
+ struct stat stats;
+ int ret;
+
+ strlcpy(p, path, sizeof(p));
+ pos = strrchr(p, '/');
+ if (pos == p || pos == NULL)
+ return 0;
+
+ while (pos[-1] == '/')
+ pos--;
+ pos[0] = '\0';
+
+ dbg("stat '%s'\n", p);
+ if (stat(p, &stats) == 0 && (stats.st_mode & S_IFMT) == S_IFDIR)
+ return 0;
+
+ if (create_path(p) != 0)
+ return -1;
+
+ dbg("mkdir '%s'\n", p);
+ selinux_setfscreatecon(p, NULL, S_IFDIR|0755);
+ ret = mkdir(p, 0755);
+ selinux_resetfscreatecon();
+ if (ret == 0)
+ return 0;
+
+ if (errno == EEXIST)
+ if (stat(p, &stats) == 0 && (stats.st_mode & S_IFMT) == S_IFDIR)
+ return 0;
+ return -1;
+}
+
+int delete_path(const char *path)
+{
+ char p[PATH_SIZE];
+ char *pos;
+ int retval;
+
+ strcpy (p, path);
+ pos = strrchr(p, '/');
+ if (pos == p || pos == NULL)
+ return 0;
+
+ while (1) {
+ *pos = '\0';
+ pos = strrchr(p, '/');
+
+ /* don't remove the last one */
+ if ((pos == p) || (pos == NULL))
+ break;
+
+ /* remove if empty */
+ retval = rmdir(p);
+ if (errno == ENOENT)
+ retval = 0;
+ if (retval) {
+ if (errno == ENOTEMPTY)
+ return 0;
+ err("rmdir(%s) failed: %s\n", p, strerror(errno));
+ break;
+ }
+ dbg("removed '%s'\n", p);
+ }
+ return 0;
+}
+
+/* Reset permissions on the device node, before unlinking it to make sure,
+ * that permisions of possible hard links will be removed too.
+ */
+int unlink_secure(const char *filename)
+{
+ int retval;
+
+ retval = chown(filename, 0, 0);
+ if (retval)
+ err("chown(%s, 0, 0) failed: %s\n", filename, strerror(errno));
+
+ retval = chmod(filename, 0000);
+ if (retval)
+ err("chmod(%s, 0000) failed: %s\n", filename, strerror(errno));
+
+ retval = unlink(filename);
+ if (errno == ENOENT)
+ retval = 0;
+
+ if (retval)
+ err("unlink(%s) failed: %s\n", filename, strerror(errno));
+
+ return retval;
+}
+
+int file_map(const char *filename, char **buf, size_t *bufsize)
+{
+ struct stat stats;
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ return -1;
+ }
+
+ if (fstat(fd, &stats) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ *buf = mmap(NULL, stats.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (*buf == MAP_FAILED) {
+ close(fd);
+ return -1;
+ }
+ *bufsize = stats.st_size;
+
+ close(fd);
+
+ return 0;
+}
+
+void file_unmap(void *buf, size_t bufsize)
+{
+ munmap(buf, bufsize);
+}
+
+/* return number of chars until the next newline, skip escaped newline */
+size_t buf_get_line(const char *buf, size_t buflen, size_t cur)
+{
+ int escape = 0;
+ size_t count;
+
+ for (count = cur; count < buflen; count++) {
+ if (!escape && buf[count] == '\n')
+ break;
+
+ if (buf[count] == '\\')
+ escape = 1;
+ else
+ escape = 0;
+ }
+
+ return count - cur;
+}
diff --git a/udev/udev_utils_string.c b/udev/udev_utils_string.c
new file mode 100644
index 0000000000..e3dc137e63
--- /dev/null
+++ b/udev/udev_utils_string.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2004-2005 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <syslog.h>
+#include <sys/utsname.h>
+
+#include "udev.h"
+
+int string_is_true(const char *str)
+{
+ if (strcasecmp(str, "true") == 0)
+ return 1;
+ if (strcasecmp(str, "yes") == 0)
+ return 1;
+ if (strcasecmp(str, "1") == 0)
+ return 1;
+ return 0;
+}
+
+void remove_trailing_chars(char *path, char c)
+{
+ size_t len;
+
+ len = strlen(path);
+ while (len > 0 && path[len-1] == c)
+ path[--len] = '\0';
+}
+
+size_t path_encode(char *s, size_t len)
+{
+ char t[(len * 3)+1];
+ size_t i, j;
+
+ t[0] = '\0';
+ for (i = 0, j = 0; s[i] != '\0'; i++) {
+ if (s[i] == '/') {
+ memcpy(&t[j], "\\x2f", 4);
+ j += 4;
+ } else if (s[i] == '\\') {
+ memcpy(&t[j], "\\x5c", 4);
+ j += 4;
+ } else {
+ t[j] = s[i];
+ j++;
+ }
+ }
+ t[j] = '\0';
+ strncpy(s, t, len);
+ return j;
+}
+
+size_t path_decode(char *s)
+{
+ size_t i, j;
+
+ for (i = 0, j = 0; s[i] != '\0'; j++) {
+ if (memcmp(&s[i], "\\x2f", 4) == 0) {
+ s[j] = '/';
+ i += 4;
+ }else if (memcmp(&s[i], "\\x5c", 4) == 0) {
+ s[j] = '\\';
+ i += 4;
+ } else {
+ s[j] = s[i];
+ i++;
+ }
+ }
+ s[j] = '\0';
+ return j;
+}
+
+/* count of characters used to encode one unicode char */
+static int utf8_encoded_expected_len(const char *str)
+{
+ unsigned char c = (unsigned char)str[0];
+
+ if (c < 0x80)
+ return 1;
+ if ((c & 0xe0) == 0xc0)
+ return 2;
+ if ((c & 0xf0) == 0xe0)
+ return 3;
+ if ((c & 0xf8) == 0xf0)
+ return 4;
+ if ((c & 0xfc) == 0xf8)
+ return 5;
+ if ((c & 0xfe) == 0xfc)
+ return 6;
+ return 0;
+}
+
+/* decode one unicode char */
+static int utf8_encoded_to_unichar(const char *str)
+{
+ int unichar;
+ int len;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ switch (len) {
+ case 1:
+ return (int)str[0];
+ case 2:
+ unichar = str[0] & 0x1f;
+ break;
+ case 3:
+ unichar = (int)str[0] & 0x0f;
+ break;
+ case 4:
+ unichar = (int)str[0] & 0x07;
+ break;
+ case 5:
+ unichar = (int)str[0] & 0x03;
+ break;
+ case 6:
+ unichar = (int)str[0] & 0x01;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i = 1; i < len; i++) {
+ if (((int)str[i] & 0xc0) != 0x80)
+ return -1;
+ unichar <<= 6;
+ unichar |= (int)str[i] & 0x3f;
+ }
+
+ return unichar;
+}
+
+/* expected size used to encode one unicode char */
+static int utf8_unichar_to_encoded_len(int unichar)
+{
+ if (unichar < 0x80)
+ return 1;
+ if (unichar < 0x800)
+ return 2;
+ if (unichar < 0x10000)
+ return 3;
+ if (unichar < 0x200000)
+ return 4;
+ if (unichar < 0x4000000)
+ return 5;
+ return 6;
+}
+
+/* check if unicode char has a valid numeric range */
+static int utf8_unichar_valid_range(int unichar)
+{
+ if (unichar > 0x10ffff)
+ return 0;
+ if ((unichar & 0xfffff800) == 0xd800)
+ return 0;
+ if ((unichar > 0xfdcf) && (unichar < 0xfdf0))
+ return 0;
+ if ((unichar & 0xffff) == 0xffff)
+ return 0;
+ return 1;
+}
+
+/* validate one encoded unicode char and return its length */
+int utf8_encoded_valid_unichar(const char *str)
+{
+ int len;
+ int unichar;
+ int i;
+
+ len = utf8_encoded_expected_len(str);
+ if (len == 0)
+ return -1;
+
+ /* ascii is valid */
+ if (len == 1)
+ return 1;
+
+ /* check if expected encoded chars are available */
+ for (i = 0; i < len; i++)
+ if ((str[i] & 0x80) != 0x80)
+ return -1;
+
+ unichar = utf8_encoded_to_unichar(str);
+
+ /* check if encoded length matches encoded value */
+ if (utf8_unichar_to_encoded_len(unichar) != len)
+ return -1;
+
+ /* check if value has valid range */
+ if (!utf8_unichar_valid_range(unichar))
+ return -1;
+
+ return len;
+}
+
+/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */
+int replace_chars(char *str, const char *white)
+{
+ size_t i = 0;
+ int replaced = 0;
+
+ while (str[i] != '\0') {
+ int len;
+
+ /* accept whitelist */
+ if (white != NULL && strchr(white, str[i]) != NULL) {
+ i++;
+ continue;
+ }
+
+ /* accept plain ascii char */
+ if ((str[i] >= '0' && str[i] <= '9') ||
+ (str[i] >= 'A' && str[i] <= 'Z') ||
+ (str[i] >= 'a' && str[i] <= 'z')) {
+ i++;
+ continue;
+ }
+
+ /* accept hex encoding */
+ if (str[i] == '\\' && str[i+1] == 'x') {
+ i += 2;
+ continue;
+ }
+
+ /* accept valid utf8 */
+ len = utf8_encoded_valid_unichar(&str[i]);
+ if (len > 1) {
+ i += len;
+ continue;
+ }
+
+ /* if space is allowed, replace whitespace with ordinary space */
+ if (isspace(str[i]) && strchr(white, ' ') != NULL) {
+ str[i] = ' ';
+ i++;
+ replaced++;
+ continue;
+ }
+
+ /* everything else is replaced with '_' */
+ str[i] = '_';
+ i++;
+ replaced++;
+ }
+
+ return replaced;
+}
diff --git a/udev/udevadm.c b/udev/udevadm.c
new file mode 100644
index 0000000000..6e7d7734d2
--- /dev/null
+++ b/udev/udevadm.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2007 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include "udev.h"
+
+static int debug;
+
+#ifdef USE_LOG
+void log_message(int priority, const char *format, ...)
+{
+ va_list args;
+
+ if (priority > udev_log_priority)
+ return;
+
+ va_start(args, format);
+ if (debug) {
+ vprintf(format, args);
+ } else
+ vsyslog(priority, format, args);
+ va_end(args);
+}
+#endif
+
+struct command {
+ const char *name;
+ int (*cmd)(int argc, char *argv[], char *envp[]);
+ const char *help;
+ int debug;
+};
+
+static const struct command cmds[];
+
+static int version(int argc, char *argv[], char *envp[])
+{
+ printf("%s\n", UDEV_VERSION);
+ return 0;
+}
+
+static int help(int argc, char *argv[], char *envp[])
+{
+ const struct command *cmd;
+
+ printf("Usage: udevadm COMMAND [OPTIONS]\n");
+ for (cmd = cmds; cmd->name != NULL; cmd++)
+ printf(" %-12s %s\n", cmd->name, cmd->help);
+ printf("\n");
+ return 0;
+}
+
+static const struct command cmds[] = {
+ {
+ .name = "info",
+ .cmd = udevinfo,
+ .help = "query sysfs or the udev database",
+ },
+ {
+ .name = "trigger",
+ .cmd = udevtrigger,
+ .help = "request events from the kernel",
+ },
+ {
+ .name = "settle",
+ .cmd = udevsettle, "",
+ .help = "wait for the event queue to finish",
+ },
+ {
+ .name = "control",
+ .cmd = udevcontrol,
+ .help = "control the udev daemon",
+ },
+ {
+ .name = "monitor",
+ .cmd = udevmonitor,
+ .help = "listen to kernel and udev events",
+ },
+ {
+ .name = "test",
+ .cmd = udevtest,
+ .help = "simulation run",
+ .debug = 1,
+ },
+ {
+ .name = "version",
+ .cmd = version,
+ .help = "print the version number",
+ },
+ {
+ .name = "help",
+ .cmd = help,
+ .help = "print this help text",
+ },
+ {}
+};
+
+int main(int argc, char *argv[], char *envp[])
+{
+ const char *command;
+ const char *pos;
+ const struct command *cmd;
+ int rc;
+
+ /* get binary or symlink name */
+ pos = strrchr(argv[0], '/');
+ if (pos != NULL)
+ command = &pos[1];
+ else
+ command = argv[0];
+
+ /* the trailing part of the binary or symlink name is the command */
+ if (strncmp(command, "udev", 4) == 0)
+ command = &command[4];
+
+ if (command == NULL || command[0] == '\0')
+ goto err_unknown;
+
+ /* udevadm itself needs to strip its name from the passed options */
+ if (strcmp(command, "adm") == 0) {
+ command = argv[1];
+ argv++;
+ argc--;
+ }
+
+ if (command == NULL)
+ goto err_unknown;
+
+ /* allow command to be specified as an option */
+ if (strncmp(command, "--", 2) == 0)
+ command += 2;
+
+ /* find and execute command */
+ for (cmd = cmds; cmd->name != NULL; cmd++) {
+ if (strcmp(cmd->name, command) == 0) {
+ debug = cmd->debug;
+ rc = cmd->cmd(argc, argv, envp);
+ goto out;
+ }
+ }
+
+err_unknown:
+ fprintf(stderr, "unknown command, try help\n\n");
+ rc = 2;
+out:
+ return rc;
+}
diff --git a/udev/udevadm.xml b/udev/udevadm.xml
new file mode 100644
index 0000000000..670c991457
--- /dev/null
+++ b/udev/udevadm.xml
@@ -0,0 +1,379 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<article>
+ <section>
+ <title>udevadm</title>
+ <refentry>
+ <refentryinfo>
+ <title>udevd</title>
+ <date>November 2007</date>
+ <productname>udev</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>udevadm</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="version"></refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>udevadm</refname><refpurpose>udev management tool</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>udevadm info <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm trigger <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm settle <optional>options</optional></command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm control <optional>options</optional> <replaceable>instruction</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 version</command>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>udevadm help</command>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1><title>DESCRIPTION</title>
+ <para>udevadm expects a command and command specific options. It
+ controls the runtime behavior of udev, requests kernel events,
+ manages the event queue, and provides simple debugging mechanisms.</para>
+ </refsect1>
+
+ <refsect1><title>OPTIONS</title>
+
+ <refsect2><title>udevadm info <replaceable>options</replaceable></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>--query=<replaceable>type</replaceable></option></term>
+ <listitem>
+ <para>Query the database for specified type of device data. It needs the
+ <option>--path</option> or <option>--name</option> to identify the specified
+ device. Valid queries are:
+ <command>name</command>, <command>symlink</command>, <command>path</command>,
+ <command>env</command>, <command>all</command>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--path=<replaceable>devpath</replaceable></option></term>
+ <listitem>
+ <para>The devpath of the device to query.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--name=<replaceable>file</replaceable></option></term>
+ <listitem>
+ <para>The name of the device node or a symlink to query</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--root</option></term>
+ <listitem>
+ <para>The udev root directory: <filename>/dev</filename>. If used in conjunction
+ with a <command>name</command> or <command>symlink</command> query, the
+ query returns the absolute path including the root directory.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <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>--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>--export-db</option></term>
+ <listitem>
+ <para>Export the content of the udev database.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm trigger <optional>options</optional></title>
+ <para>Request device uevents, usually used to replay events at system coldplug.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--verbose</option></term>
+ <listitem>
+ <para>Print the list of devices which will be triggered.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--dry-run</option></term>
+ <listitem>
+ <para>Do not actually trigger the event.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--retry-failed</option></term>
+ <listitem>
+ <para>Trigger only the events which are failed during a previous run.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--action=<replaceable>action</replaceable></option></term>
+ <listitem>
+ <para>Type of event to be triggered. The default value is "add".</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <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>--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>--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>--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>--socket=<replaceable>path</replaceable></option></term>
+ <listitem>
+ <para>Pass the synthesized events to the specified socket, instead of triggering
+ a global kernel event. All available event values will be send in the same format
+ the kernel sends an uevent, or <option>RUN+="socket:<replaceable>path</replaceable>"</option>
+ sends a message. If the first character of the specified path is an @ character,
+ an abstract namespace socket is used, instead of an existing socket file.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--env=<replaceable>KEY</replaceable>=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Pass an additional environemt key to the event. This works only with the
+ --socket option.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm settle <optional>options</optional></title>
+ <para>Watches the udev event queue, and exits if all current events are handled.</para>
+ <variablelist>
+ <varlistentry>
+ <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 180 seconds.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <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>--log_priority=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set the internal log level of udevd. Valid values are the numerical
+ syslog priorities or their textual representations: <option>err</option>,
+ <option>info</option> and <option>debug</option>.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--stop_exec_queue</option></term>
+ <listitem>
+ <para>Signal udevd to stop executing new events. Incoming events
+ will be queued.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--start_exec_queue</option></term>
+ <listitem>
+ <para>Signal udevd to enable the execution of events.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--reload_rules</option></term>
+ <listitem>
+ <para>Signal udevd to reload the rules from the config.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--env=<replaceable>KEY</replaceable>=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set global variable.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--max_childs=</option><replaceable>value</replaceable></term>
+ <listitem>
+ <para>Set the maximum number of events, udevd will handle at the
+ same time.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--max_childs_running=<replaceable>value</replaceable></option></term>
+ <listitem>
+ <para>Set the maximum number of events, which are allowed to run at the
+ same time.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm monitor <optional>options</optional></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>--environment</option></term>
+ <listitem>
+ <para>Print the complete environment for all events. Can be used to compare the
+ kernel supplied and the udev added environment values.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--kernel</option></term>
+ <listitem>
+ <para>Print the kernel uevents.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--udev</option></term>
+ <listitem>
+ <para>Print the udev event after the rule processing.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm test <optional>options</optional> <replaceable>devpath</replaceable></title>
+ <para>Simulate a udev event run for the given device, and print out debug
+ output. Unless forced to, no device node or symlink will be created.</para>
+ <variablelist>
+ <varlistentry>
+ <term><option>--action=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>The action string.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--subsystem=<replaceable>string</replaceable></option></term>
+ <listitem>
+ <para>The subsystem string.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--force</option></term>
+ <listitem>
+ <para>Force the creation of a device node or symlink. Usually the test run
+ prints only debug output.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect2>
+
+ <refsect2><title>udevadm version</title>
+ <para>Print version number.</para>
+ </refsect2>
+
+ <refsect2><title>udevadm help</title>
+ <para>Print help text.</para>
+ </refsect2>
+ </refsect1>
+
+ <refsect1><title>AUTHOR</title>
+ <para>Written by Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para><citerefentry>
+ <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry>
+ <citerefentry>
+ <refentrytitle>udevd</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/udev/udevcontrol.c b/udev/udevcontrol.c
new file mode 100644
index 0000000000..4c93b8f464
--- /dev/null
+++ b/udev/udevcontrol.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2005-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <time.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/un.h>
+
+#include "udev.h"
+#include "udevd.h"
+
+static int sock = -1;
+static int udev_log = 0;
+
+int udevcontrol(int argc, char *argv[], char *envp[])
+{
+ static struct udevd_ctrl_msg ctrl_msg;
+ struct sockaddr_un saddr;
+ socklen_t addrlen;
+ const char *env;
+ const char *arg;
+ const char *val;
+ int *intval;
+ int retval = 1;
+
+ env = getenv("UDEV_LOG");
+ if (env)
+ udev_log = log_priority(env);
+
+ logging_init("udevcontrol");
+ dbg("version %s\n", UDEV_VERSION);
+
+ if (argc < 2) {
+ fprintf(stderr, "missing command\n\n");
+ goto exit;
+ }
+ memset(&ctrl_msg, 0x00, sizeof(struct udevd_ctrl_msg));
+ strcpy(ctrl_msg.magic, UDEVD_CTRL_MAGIC);
+ arg = argv[1];
+
+ /* allow instructions passed as options */
+ if (strncmp(arg, "--", 2) == 0)
+ arg += 2;
+
+ if (!strcmp(arg, "stop_exec_queue"))
+ ctrl_msg.type = UDEVD_CTRL_STOP_EXEC_QUEUE;
+ else if (!strcmp(arg, "start_exec_queue"))
+ ctrl_msg.type = UDEVD_CTRL_START_EXEC_QUEUE;
+ else if (!strcmp(arg, "reload_rules"))
+ ctrl_msg.type = UDEVD_CTRL_RELOAD_RULES;
+ else if (!strncmp(arg, "log_priority=", strlen("log_priority="))) {
+ intval = (int *) ctrl_msg.buf;
+ val = &arg[strlen("log_priority=")];
+ ctrl_msg.type = UDEVD_CTRL_SET_LOG_LEVEL;
+ *intval = log_priority(val);
+ info("send log_priority=%i\n", *intval);
+ } else if (!strncmp(arg, "max_childs=", strlen("max_childs="))) {
+ char *endp;
+ int count;
+
+ intval = (int *) ctrl_msg.buf;
+ val = &arg[strlen("max_childs=")];
+ ctrl_msg.type = UDEVD_CTRL_SET_MAX_CHILDS;
+ count = strtoul(val, &endp, 0);
+ if (endp[0] != '\0' || count < 1) {
+ fprintf(stderr, "invalid number\n");
+ goto exit;
+ }
+ *intval = count;
+ info("send max_childs=%i\n", *intval);
+ } else if (!strncmp(arg, "max_childs_running=", strlen("max_childs_running="))) {
+ char *endp;
+ int count;
+
+ intval = (int *) ctrl_msg.buf;
+ val = &arg[strlen("max_childs_running=")];
+ ctrl_msg.type = UDEVD_CTRL_SET_MAX_CHILDS_RUNNING;
+ count = strtoul(val, &endp, 0);
+ if (endp[0] != '\0' || count < 1) {
+ fprintf(stderr, "invalid number\n");
+ goto exit;
+ }
+ *intval = count;
+ info("send max_childs_running=%i\n", *intval);
+ } else if (!strncmp(arg, "env", strlen("env"))) {
+ if (!strncmp(arg, "env=", strlen("env=")))
+ val = &arg[strlen("env=")];
+ else
+ val = argv[2];
+ if (val == NULL) {
+ fprintf(stderr, "missing key\n");
+ goto exit;
+ }
+ ctrl_msg.type = UDEVD_CTRL_ENV;
+ strlcpy(ctrl_msg.buf, val, sizeof(ctrl_msg.buf));
+ info("send env '%s'\n", val);
+ } else if (strcmp(arg, "help") == 0 || strcmp(arg, "-h") == 0) {
+ printf("Usage: udevadm control COMMAND\n"
+ " --log_priority=<level> set the udev log level for the daemon\n"
+ " --stop_exec_queue keep udevd from executing events, queue only\n"
+ " --start_exec_queue execute events, flush queue\n"
+ " --reload_rules reloads the rules files\n"
+ " --env=<KEY>=<value> set a global environment variable\n"
+ " --max_childs=<N> maximum number of childs\n"
+ " --max_childs_running=<N> maximum number of childs running at the same time\n"
+ " --help print this help text\n\n");
+ goto exit;
+ } else {
+ fprintf(stderr, "unrecognized command '%s'\n", arg);
+ goto exit;
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "root privileges required\n");
+ goto exit;
+ }
+
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (sock == -1) {
+ err("error getting socket: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ memset(&saddr, 0x00, sizeof(struct sockaddr_un));
+ saddr.sun_family = AF_LOCAL;
+ /* use abstract namespace for socket path */
+ strcpy(&saddr.sun_path[1], UDEVD_CTRL_SOCK_PATH);
+ addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+
+ retval = sendto(sock, &ctrl_msg, sizeof(ctrl_msg), 0, (struct sockaddr *)&saddr, addrlen);
+ if (retval == -1) {
+ err("error sending message: %s\n", strerror(errno));
+ retval = 1;
+ } else {
+ dbg("sent message type=0x%02x, %u bytes sent\n", ctrl_msg.type, retval);
+ retval = 0;
+ }
+
+ close(sock);
+exit:
+ logging_close();
+ return retval;
+}
diff --git a/udev/udevd.c b/udev/udevd.c
new file mode 100644
index 0000000000..0827a5ceb3
--- /dev/null
+++ b/udev/udevd.c
@@ -0,0 +1,1304 @@
+/*
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca>
+ *
+ * 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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stddef.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <time.h>
+#include <getopt.h>
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+#include "udevd.h"
+#include "udev_selinux.h"
+
+static int debug_trace;
+static int debug;
+
+static struct udev_rules rules;
+static int udevd_sock = -1;
+static int uevent_netlink_sock = -1;
+static int inotify_fd = -1;
+static pid_t sid;
+
+static int signal_pipe[2] = {-1, -1};
+static volatile int sigchilds_waiting;
+static volatile int udev_exit;
+static volatile int reload_config;
+static int run_exec_q;
+static int stop_exec_q;
+static int max_childs;
+static int max_childs_running;
+static char udev_log[32];
+
+static LIST_HEAD(exec_list);
+static LIST_HEAD(running_list);
+
+
+#ifdef USE_LOG
+void log_message(int priority, const char *format, ...)
+{
+ va_list args;
+
+ if (priority > udev_log_priority)
+ return;
+
+ va_start(args, format);
+ if (debug) {
+ printf("[%d] ", (int) getpid());
+ vprintf(format, args);
+ } else
+ vsyslog(priority, format, args);
+ va_end(args);
+}
+
+#endif
+
+static void asmlinkage udev_event_sig_handler(int signum)
+{
+ if (signum == SIGALRM)
+ exit(1);
+}
+
+static int udev_event_process(struct udevd_uevent_msg *msg)
+{
+ struct sigaction act;
+ struct udevice *udev;
+ int i;
+ int retval;
+
+ /* set signal handlers */
+ memset(&act, 0x00, sizeof(act));
+ act.sa_handler = (void (*)(int)) udev_event_sig_handler;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(SIGALRM, &act, NULL);
+
+ /* reset to default */
+ act.sa_handler = SIG_DFL;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGCHLD, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+
+ /* trigger timeout to prevent hanging processes */
+ alarm(UDEV_EVENT_TIMEOUT);
+
+ /* reconstruct event environment from message */
+ for (i = 0; msg->envp[i]; i++)
+ putenv(msg->envp[i]);
+
+ udev = udev_device_init(NULL);
+ if (udev == NULL)
+ return -1;
+ strlcpy(udev->action, msg->action, sizeof(udev->action));
+ sysfs_device_set_values(udev->dev, msg->devpath, msg->subsystem, msg->driver);
+ udev->devpath_old = msg->devpath_old;
+ udev->devt = msg->devt;
+
+ retval = udev_device_event(&rules, udev);
+
+ /* rules may change/disable the timeout */
+ if (udev->event_timeout >= 0)
+ alarm(udev->event_timeout);
+
+ /* run programs collected by RUN-key*/
+ if (retval == 0 && !udev->ignore_device && udev_run)
+ retval = udev_rules_run(udev);
+
+ udev_device_cleanup(udev);
+ return retval;
+}
+
+enum event_state {
+ EVENT_QUEUED,
+ EVENT_FINISHED,
+ EVENT_FAILED,
+};
+
+static void export_event_state(struct udevd_uevent_msg *msg, enum event_state state)
+{
+ char filename[PATH_SIZE];
+ char filename_failed[PATH_SIZE];
+ size_t start;
+
+ /* location of queue file */
+ snprintf(filename, sizeof(filename), "%s/"EVENT_QUEUE_DIR"/%llu", udev_root, msg->seqnum);
+
+ /* location of failed file */
+ strlcpy(filename_failed, udev_root, sizeof(filename_failed));
+ strlcat(filename_failed, "/", sizeof(filename_failed));
+ start = strlcat(filename_failed, EVENT_FAILED_DIR"/", sizeof(filename_failed));
+ strlcat(filename_failed, msg->devpath, sizeof(filename_failed));
+ path_encode(&filename_failed[start], sizeof(filename_failed) - start);
+
+ switch (state) {
+ case EVENT_QUEUED:
+ unlink(filename_failed);
+ delete_path(filename_failed);
+
+ create_path(filename);
+ selinux_setfscreatecon(filename, NULL, S_IFLNK);
+ symlink(msg->devpath, filename);
+ selinux_resetfscreatecon();
+ break;
+ case EVENT_FINISHED:
+ if (msg->devpath_old != NULL) {
+ /* "move" event - rename failed file to current name, do not delete failed */
+ char filename_failed_old[PATH_SIZE];
+
+ strlcpy(filename_failed_old, udev_root, sizeof(filename_failed_old));
+ strlcat(filename_failed_old, "/", sizeof(filename_failed_old));
+ start = strlcat(filename_failed_old, EVENT_FAILED_DIR"/", sizeof(filename_failed_old));
+ strlcat(filename_failed_old, msg->devpath_old, sizeof(filename_failed_old));
+ path_encode(&filename_failed_old[start], sizeof(filename) - start);
+
+ if (rename(filename_failed_old, filename_failed) == 0)
+ info("renamed devpath, moved failed state of '%s' to %s'\n",
+ msg->devpath_old, msg->devpath);
+ } else {
+ unlink(filename_failed);
+ delete_path(filename_failed);
+ }
+
+ unlink(filename);
+ delete_path(filename);
+ break;
+ case EVENT_FAILED:
+ /* move failed event to the failed directory */
+ create_path(filename_failed);
+ rename(filename, filename_failed);
+
+ /* clean up possibly empty queue directory */
+ delete_path(filename);
+ break;
+ }
+
+ return;
+}
+
+static void msg_queue_delete(struct udevd_uevent_msg *msg)
+{
+ list_del(&msg->node);
+
+ /* mark as failed, if "add" event returns non-zero */
+ if (msg->exitstatus && strcmp(msg->action, "add") == 0)
+ export_event_state(msg, EVENT_FAILED);
+ else
+ export_event_state(msg, EVENT_FINISHED);
+
+ free(msg);
+}
+
+static void udev_event_run(struct udevd_uevent_msg *msg)
+{
+ pid_t pid;
+ int retval;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ /* child */
+ close(uevent_netlink_sock);
+ close(udevd_sock);
+ if (inotify_fd >= 0)
+ close(inotify_fd);
+ close(signal_pipe[READ_END]);
+ close(signal_pipe[WRITE_END]);
+ logging_close();
+
+ logging_init("udevd-event");
+ setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
+
+ retval = udev_event_process(msg);
+ info("seq %llu finished with %i\n", msg->seqnum, retval);
+
+ logging_close();
+ if (retval)
+ exit(1);
+ exit(0);
+ case -1:
+ err("fork of child failed: %s\n", strerror(errno));
+ msg_queue_delete(msg);
+ break;
+ default:
+ /* get SIGCHLD in main loop */
+ info("seq %llu forked, pid [%d], '%s' '%s', %ld seconds old\n",
+ msg->seqnum, pid, msg->action, msg->subsystem, time(NULL) - msg->queue_time);
+ msg->pid = pid;
+ }
+}
+
+static void msg_queue_insert(struct udevd_uevent_msg *msg)
+{
+ char filename[PATH_SIZE];
+ int fd;
+
+ msg->queue_time = time(NULL);
+
+ export_event_state(msg, EVENT_QUEUED);
+ info("seq %llu queued, '%s' '%s'\n", msg->seqnum, msg->action, msg->subsystem);
+
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/" EVENT_SEQNUM, sizeof(filename));
+ fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0644);
+ if (fd >= 0) {
+ char str[32];
+ int len;
+
+ len = sprintf(str, "%llu\n", msg->seqnum);
+ write(fd, str, len);
+ close(fd);
+ }
+
+ /* run one event after the other in debug mode */
+ if (debug_trace) {
+ list_add_tail(&msg->node, &running_list);
+ udev_event_run(msg);
+ waitpid(msg->pid, NULL, 0);
+ msg_queue_delete(msg);
+ return;
+ }
+
+ /* run all events with a timeout set immediately */
+ if (msg->timeout != 0) {
+ list_add_tail(&msg->node, &running_list);
+ udev_event_run(msg);
+ return;
+ }
+
+ list_add_tail(&msg->node, &exec_list);
+ run_exec_q = 1;
+}
+
+static int mem_size_mb(void)
+{
+ FILE* f;
+ char buf[4096];
+ long int memsize = -1;
+
+ f = fopen("/proc/meminfo", "r");
+ if (f == NULL)
+ return -1;
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ long int value;
+
+ if (sscanf(buf, "MemTotal: %ld kB", &value) == 1) {
+ memsize = value / 1024;
+ break;
+ }
+ }
+
+ fclose(f);
+ return memsize;
+}
+
+static int cpu_count(void)
+{
+ FILE* f;
+ char buf[4096];
+ int count = 0;
+
+ f = fopen("/proc/stat", "r");
+ if (f == NULL)
+ return -1;
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ if (strncmp(buf, "cpu", 3) == 0 && isdigit(buf[3]))
+ count++;
+ }
+
+ fclose(f);
+ if (count == 0)
+ return -1;
+ return count;
+}
+
+static int running_processes(void)
+{
+ FILE* f;
+ char buf[4096];
+ int running = -1;
+
+ f = fopen("/proc/stat", "r");
+ if (f == NULL)
+ return -1;
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ int value;
+
+ if (sscanf(buf, "procs_running %u", &value) == 1) {
+ running = value;
+ break;
+ }
+ }
+
+ fclose(f);
+ return running;
+}
+
+/* return the number of process es in our session, count only until limit */
+static int running_processes_in_session(pid_t session, int limit)
+{
+ DIR *dir;
+ struct dirent *dent;
+ int running = 0;
+
+ dir = opendir("/proc");
+ if (!dir)
+ return -1;
+
+ /* read process info from /proc */
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ int f;
+ char procdir[64];
+ char line[256];
+ const char *pos;
+ char state;
+ pid_t ppid, pgrp, sess;
+ int len;
+
+ if (!isdigit(dent->d_name[0]))
+ continue;
+
+ snprintf(procdir, sizeof(procdir), "/proc/%s/stat", dent->d_name);
+ procdir[sizeof(procdir)-1] = '\0';
+
+ f = open(procdir, O_RDONLY);
+ if (f == -1)
+ continue;
+
+ len = read(f, line, sizeof(line)-1);
+ close(f);
+
+ if (len <= 0)
+ continue;
+ else
+ line[len] = '\0';
+
+ /* skip ugly program name */
+ pos = strrchr(line, ')') + 2;
+ if (pos == NULL)
+ continue;
+
+ if (sscanf(pos, "%c %d %d %d ", &state, &ppid, &pgrp, &sess) != 4)
+ continue;
+
+ /* count only processes in our session */
+ if (sess != session)
+ continue;
+
+ /* count only running, no sleeping processes */
+ if (state != 'R')
+ continue;
+
+ running++;
+ if (limit > 0 && running >= limit)
+ break;
+ }
+ closedir(dir);
+
+ return running;
+}
+
+static int compare_devpath(const char *running, const char *waiting)
+{
+ int i;
+
+ for (i = 0; i < PATH_SIZE; i++) {
+ /* identical device event found */
+ if (running[i] == '\0' && waiting[i] == '\0')
+ return 1;
+
+ /* parent device event found */
+ if (running[i] == '\0' && waiting[i] == '/')
+ return 2;
+
+ /* child device event found */
+ if (running[i] == '/' && waiting[i] == '\0')
+ return 3;
+
+ /* no matching event */
+ if (running[i] != waiting[i])
+ break;
+ }
+
+ return 0;
+}
+
+/* lookup event for identical, parent, child, or physical device */
+static int devpath_busy(struct udevd_uevent_msg *msg, int limit)
+{
+ struct udevd_uevent_msg *loop_msg;
+ int childs_count = 0;
+
+ /* check exec-queue which may still contain delayed events we depend on */
+ list_for_each_entry(loop_msg, &exec_list, node) {
+ /* skip ourself and all later events */
+ if (loop_msg->seqnum >= msg->seqnum)
+ break;
+
+ /* check our old name */
+ if (msg->devpath_old != NULL)
+ if (strcmp(loop_msg->devpath , msg->devpath_old) == 0)
+ return 2;
+
+ /* check identical, parent, or child device event */
+ if (compare_devpath(loop_msg->devpath, msg->devpath) != 0) {
+ dbg("%llu, device event still pending %llu (%s)\n",
+ msg->seqnum, loop_msg->seqnum, loop_msg->devpath);
+ return 3;
+ }
+
+ /* check for our major:minor number */
+ if (msg->devt && loop_msg->devt == msg->devt &&
+ strcmp(msg->subsystem, loop_msg->subsystem) == 0) {
+ dbg("%llu, device event still pending %llu (%d:%d)\n", msg->seqnum,
+ loop_msg->seqnum, major(loop_msg->devt), minor(loop_msg->devt));
+ return 4;
+ }
+
+ /* check physical device event (special case of parent) */
+ if (msg->physdevpath && msg->action && strcmp(msg->action, "add") == 0)
+ if (compare_devpath(loop_msg->devpath, msg->physdevpath) != 0) {
+ dbg("%llu, physical device event still pending %llu (%s)\n",
+ msg->seqnum, loop_msg->seqnum, loop_msg->devpath);
+ return 5;
+ }
+ }
+
+ /* check run queue for still running events */
+ list_for_each_entry(loop_msg, &running_list, node) {
+ if (limit && childs_count++ > limit) {
+ dbg("%llu, maximum number (%i) of childs reached\n", msg->seqnum, childs_count);
+ return 1;
+ }
+
+ /* check our old name */
+ if (msg->devpath_old != NULL)
+ if (strcmp(loop_msg->devpath , msg->devpath_old) == 0)
+ return 2;
+
+ /* check identical, parent, or child device event */
+ if (compare_devpath(loop_msg->devpath, msg->devpath) != 0) {
+ dbg("%llu, device event still running %llu (%s)\n",
+ msg->seqnum, loop_msg->seqnum, loop_msg->devpath);
+ return 3;
+ }
+
+ /* check for our major:minor number */
+ if (msg->devt && loop_msg->devt == msg->devt &&
+ strcmp(msg->subsystem, loop_msg->subsystem) == 0) {
+ dbg("%llu, device event still running %llu (%d:%d)\n", msg->seqnum,
+ loop_msg->seqnum, major(loop_msg->devt), minor(loop_msg->devt));
+ return 4;
+ }
+
+ /* check physical device event (special case of parent) */
+ if (msg->physdevpath && msg->action && strcmp(msg->action, "add") == 0)
+ if (compare_devpath(loop_msg->devpath, msg->physdevpath) != 0) {
+ dbg("%llu, physical device event still running %llu (%s)\n",
+ msg->seqnum, loop_msg->seqnum, loop_msg->devpath);
+ return 5;
+ }
+ }
+ return 0;
+}
+
+/* serializes events for the identical and parent and child devices */
+static void msg_queue_manager(void)
+{
+ struct udevd_uevent_msg *loop_msg;
+ struct udevd_uevent_msg *tmp_msg;
+ int running;
+
+ if (list_empty(&exec_list))
+ return;
+
+ running = running_processes();
+ dbg("%d processes runnning on system\n", running);
+ if (running < 0)
+ running = max_childs_running;
+
+ list_for_each_entry_safe(loop_msg, tmp_msg, &exec_list, node) {
+ /* check running processes in our session and possibly throttle */
+ if (running >= max_childs_running) {
+ running = running_processes_in_session(sid, max_childs_running+10);
+ dbg("at least %d processes running in session\n", running);
+ if (running >= max_childs_running) {
+ dbg("delay seq %llu, too many processes already running\n", loop_msg->seqnum);
+ return;
+ }
+ }
+
+ /* serialize and wait for parent or child events */
+ if (devpath_busy(loop_msg, max_childs) != 0) {
+ dbg("delay seq %llu (%s)\n", loop_msg->seqnum, loop_msg->devpath);
+ continue;
+ }
+
+ /* move event to run list */
+ list_move_tail(&loop_msg->node, &running_list);
+ udev_event_run(loop_msg);
+ running++;
+ dbg("moved seq %llu to running list\n", loop_msg->seqnum);
+ }
+}
+
+static struct udevd_uevent_msg *get_msg_from_envbuf(const char *buf, int buf_size)
+{
+ int bufpos;
+ int i;
+ struct udevd_uevent_msg *msg;
+ char *physdevdriver_key = NULL;
+ int maj = 0;
+ int min = 0;
+
+ msg = malloc(sizeof(struct udevd_uevent_msg) + buf_size);
+ if (msg == NULL)
+ return NULL;
+ memset(msg, 0x00, sizeof(struct udevd_uevent_msg) + buf_size);
+
+ /* copy environment buffer and reconstruct envp */
+ memcpy(msg->envbuf, buf, buf_size);
+ bufpos = 0;
+ for (i = 0; (bufpos < buf_size) && (i < UEVENT_NUM_ENVP-2); i++) {
+ int keylen;
+ char *key;
+
+ key = &msg->envbuf[bufpos];
+ keylen = strlen(key);
+ msg->envp[i] = key;
+ bufpos += keylen + 1;
+ dbg("add '%s' to msg.envp[%i]\n", msg->envp[i], i);
+
+ /* remember some keys for further processing */
+ if (strncmp(key, "ACTION=", 7) == 0)
+ msg->action = &key[7];
+ else if (strncmp(key, "DEVPATH=", 8) == 0)
+ msg->devpath = &key[8];
+ else if (strncmp(key, "SUBSYSTEM=", 10) == 0)
+ msg->subsystem = &key[10];
+ else if (strncmp(key, "DRIVER=", 7) == 0)
+ msg->driver = &key[7];
+ else if (strncmp(key, "SEQNUM=", 7) == 0)
+ msg->seqnum = strtoull(&key[7], NULL, 10);
+ else if (strncmp(key, "DEVPATH_OLD=", 12) == 0)
+ msg->devpath_old = &key[12];
+ else if (strncmp(key, "PHYSDEVPATH=", 12) == 0)
+ msg->physdevpath = &key[12];
+ else if (strncmp(key, "PHYSDEVDRIVER=", 14) == 0)
+ physdevdriver_key = key;
+ else if (strncmp(key, "MAJOR=", 6) == 0)
+ maj = strtoull(&key[6], NULL, 10);
+ else if (strncmp(key, "MINOR=", 6) == 0)
+ min = strtoull(&key[6], NULL, 10);
+ else if (strncmp(key, "TIMEOUT=", 8) == 0)
+ msg->timeout = strtoull(&key[8], NULL, 10);
+ }
+ msg->devt = makedev(maj, min);
+ msg->envp[i++] = "UDEVD_EVENT=1";
+
+ if (msg->driver == NULL && msg->physdevpath == NULL && physdevdriver_key != NULL) {
+ /* for older kernels DRIVER is empty for a bus device, export PHYSDEVDRIVER as DRIVER */
+ msg->envp[i++] = &physdevdriver_key[7];
+ msg->driver = &physdevdriver_key[14];
+ }
+
+ msg->envp[i] = NULL;
+
+ if (msg->devpath == NULL || msg->action == NULL) {
+ info("DEVPATH or ACTION missing, ignore message\n");
+ free(msg);
+ return NULL;
+ }
+ return msg;
+}
+
+/* receive the udevd message from userspace */
+static void get_ctrl_msg(void)
+{
+ struct udevd_ctrl_msg ctrl_msg;
+ ssize_t size;
+ struct msghdr smsg;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ struct ucred *cred;
+ char cred_msg[CMSG_SPACE(sizeof(struct ucred))];
+ int *intval;
+ char *pos;
+
+ memset(&ctrl_msg, 0x00, sizeof(struct udevd_ctrl_msg));
+ iov.iov_base = &ctrl_msg;
+ iov.iov_len = sizeof(struct udevd_ctrl_msg);
+
+ memset(&smsg, 0x00, sizeof(struct msghdr));
+ smsg.msg_iov = &iov;
+ smsg.msg_iovlen = 1;
+ smsg.msg_control = cred_msg;
+ smsg.msg_controllen = sizeof(cred_msg);
+
+ size = recvmsg(udevd_sock, &smsg, 0);
+ if (size < 0) {
+ if (errno != EINTR)
+ err("unable to receive user udevd message: %s\n", strerror(errno));
+ return;
+ }
+ cmsg = CMSG_FIRSTHDR(&smsg);
+ cred = (struct ucred *) CMSG_DATA(cmsg);
+
+ if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
+ err("no sender credentials received, message ignored\n");
+ return;
+ }
+
+ if (cred->uid != 0) {
+ err("sender uid=%i, message ignored\n", cred->uid);
+ return;
+ }
+
+ if (strncmp(ctrl_msg.magic, UDEVD_CTRL_MAGIC, sizeof(UDEVD_CTRL_MAGIC)) != 0 ) {
+ err("message magic '%s' doesn't match, ignore it\n", ctrl_msg.magic);
+ return;
+ }
+
+ switch (ctrl_msg.type) {
+ case UDEVD_CTRL_ENV:
+ pos = strchr(ctrl_msg.buf, '=');
+ if (pos == NULL) {
+ err("wrong key format '%s'\n", ctrl_msg.buf);
+ break;
+ }
+ pos[0] = '\0';
+ if (pos[1] == '\0') {
+ info("udevd message (ENV) received, unset '%s'\n", ctrl_msg.buf);
+ unsetenv(ctrl_msg.buf);
+ } else {
+ info("udevd message (ENV) received, set '%s=%s'\n", ctrl_msg.buf, &pos[1]);
+ setenv(ctrl_msg.buf, &pos[1], 1);
+ }
+ break;
+ case UDEVD_CTRL_STOP_EXEC_QUEUE:
+ info("udevd message (STOP_EXEC_QUEUE) received\n");
+ stop_exec_q = 1;
+ break;
+ case UDEVD_CTRL_START_EXEC_QUEUE:
+ info("udevd message (START_EXEC_QUEUE) received\n");
+ stop_exec_q = 0;
+ msg_queue_manager();
+ break;
+ case UDEVD_CTRL_SET_LOG_LEVEL:
+ intval = (int *) ctrl_msg.buf;
+ info("udevd message (SET_LOG_PRIORITY) received, udev_log_priority=%i\n", *intval);
+ udev_log_priority = *intval;
+ sprintf(udev_log, "UDEV_LOG=%i", udev_log_priority);
+ putenv(udev_log);
+ break;
+ case UDEVD_CTRL_SET_MAX_CHILDS:
+ intval = (int *) ctrl_msg.buf;
+ info("udevd message (UDEVD_SET_MAX_CHILDS) received, max_childs=%i\n", *intval);
+ max_childs = *intval;
+ break;
+ case UDEVD_CTRL_SET_MAX_CHILDS_RUNNING:
+ intval = (int *) ctrl_msg.buf;
+ info("udevd message (UDEVD_SET_MAX_CHILDS_RUNNING) received, max_childs=%i\n", *intval);
+ max_childs_running = *intval;
+ break;
+ case UDEVD_CTRL_RELOAD_RULES:
+ info("udevd message (RELOAD_RULES) received\n");
+ reload_config = 1;
+ break;
+ default:
+ err("unknown control message type\n");
+ }
+}
+
+/* receive the kernel user event message and do some sanity checks */
+static struct udevd_uevent_msg *get_netlink_msg(void)
+{
+ struct udevd_uevent_msg *msg;
+ int bufpos;
+ ssize_t size;
+ static char buffer[UEVENT_BUFFER_SIZE+512];
+ char *pos;
+
+ size = recv(uevent_netlink_sock, &buffer, sizeof(buffer), 0);
+ if (size < 0) {
+ if (errno != EINTR)
+ err("unable to receive kernel netlink message: %s\n", strerror(errno));
+ return NULL;
+ }
+
+ if ((size_t)size > sizeof(buffer)-1)
+ size = sizeof(buffer)-1;
+ buffer[size] = '\0';
+ dbg("uevent_size=%zi\n", size);
+
+ /* start of event payload */
+ bufpos = strlen(buffer)+1;
+ msg = get_msg_from_envbuf(&buffer[bufpos], size-bufpos);
+ if (msg == NULL)
+ return NULL;
+
+ /* validate message */
+ pos = strchr(buffer, '@');
+ if (pos == NULL) {
+ err("invalid uevent '%s'\n", buffer);
+ free(msg);
+ return NULL;
+ }
+ pos[0] = '\0';
+
+ if (msg->action == NULL) {
+ info("no ACTION in payload found, skip event '%s'\n", buffer);
+ free(msg);
+ return NULL;
+ }
+
+ if (strcmp(msg->action, buffer) != 0) {
+ err("ACTION in payload does not match uevent, skip event '%s'\n", buffer);
+ free(msg);
+ return NULL;
+ }
+
+ return msg;
+}
+
+static void asmlinkage sig_handler(int signum)
+{
+ switch (signum) {
+ case SIGINT:
+ case SIGTERM:
+ udev_exit = 1;
+ break;
+ case SIGCHLD:
+ /* set flag, then write to pipe if needed */
+ sigchilds_waiting = 1;
+ break;
+ case SIGHUP:
+ reload_config = 1;
+ break;
+ }
+
+ /* write to pipe, which will wakeup select() in our mainloop */
+ write(signal_pipe[WRITE_END], "", 1);
+}
+
+static void udev_done(int pid, int exitstatus)
+{
+ /* find msg associated with pid and delete it */
+ struct udevd_uevent_msg *msg;
+
+ list_for_each_entry(msg, &running_list, node) {
+ if (msg->pid == pid) {
+ info("seq %llu, pid [%d] exit with %i, %ld seconds old\n", msg->seqnum, msg->pid,
+ exitstatus, time(NULL) - msg->queue_time);
+ msg->exitstatus = exitstatus;
+ msg_queue_delete(msg);
+
+ /* there may be events waiting with the same devpath */
+ run_exec_q = 1;
+ return;
+ }
+ }
+}
+
+static void reap_sigchilds(void)
+{
+ pid_t pid;
+ int status;
+
+ while (1) {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (pid <= 0)
+ break;
+ if (WIFEXITED(status))
+ status = WEXITSTATUS(status);
+ else if (WIFSIGNALED(status))
+ status = WTERMSIG(status) + 128;
+ else
+ status = 0;
+ udev_done(pid, status);
+ }
+}
+
+static int init_udevd_socket(void)
+{
+ struct sockaddr_un saddr;
+ socklen_t addrlen;
+ const int feature_on = 1;
+ int retval;
+
+ memset(&saddr, 0x00, sizeof(saddr));
+ saddr.sun_family = AF_LOCAL;
+ /* use abstract namespace for socket path */
+ strcpy(&saddr.sun_path[1], UDEVD_CTRL_SOCK_PATH);
+ addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+
+ udevd_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (udevd_sock == -1) {
+ err("error getting socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* the bind takes care of ensuring only one copy running */
+ retval = bind(udevd_sock, (struct sockaddr *) &saddr, addrlen);
+ if (retval < 0) {
+ err("bind failed: %s\n", strerror(errno));
+ close(udevd_sock);
+ udevd_sock = -1;
+ return -1;
+ }
+
+ /* enable receiving of the sender credentials */
+ setsockopt(udevd_sock, SOL_SOCKET, SO_PASSCRED, &feature_on, sizeof(feature_on));
+
+ return 0;
+}
+
+static int init_uevent_netlink_sock(void)
+{
+ struct sockaddr_nl snl;
+ const int buffersize = 16 * 1024 * 1024;
+ int retval;
+
+ memset(&snl, 0x00, sizeof(struct sockaddr_nl));
+ snl.nl_family = AF_NETLINK;
+ snl.nl_pid = getpid();
+ snl.nl_groups = 1;
+
+ uevent_netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+ if (uevent_netlink_sock == -1) {
+ err("error getting socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* set receive buffersize */
+ setsockopt(uevent_netlink_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));
+
+ retval = bind(uevent_netlink_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
+ if (retval < 0) {
+ err("bind failed: %s\n", strerror(errno));
+ close(uevent_netlink_sock);
+ uevent_netlink_sock = -1;
+ return -1;
+ }
+ return 0;
+}
+
+static void export_initial_seqnum(void)
+{
+ char filename[PATH_SIZE];
+ int fd;
+ char seqnum[32];
+ ssize_t len = 0;
+
+ strlcpy(filename, sysfs_path, sizeof(filename));
+ strlcat(filename, "/kernel/uevent_seqnum", sizeof(filename));
+ fd = open(filename, O_RDONLY);
+ if (fd >= 0) {
+ len = read(fd, seqnum, sizeof(seqnum)-1);
+ close(fd);
+ }
+ if (len <= 0) {
+ strcpy(seqnum, "0\n");
+ len = 3;
+ }
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/" EVENT_SEQNUM, sizeof(filename));
+ create_path(filename);
+ fd = open(filename, O_WRONLY|O_TRUNC|O_CREAT, 0644);
+ if (fd >= 0) {
+ write(fd, seqnum, len);
+ close(fd);
+ }
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ int retval;
+ int fd;
+ struct sigaction act;
+ fd_set readfds;
+ const char *value;
+ int daemonize = 0;
+ int option;
+ static const struct option options[] = {
+ { "daemon", 0, NULL, 'd' },
+ { "debug-trace", 0, NULL, 't' },
+ { "debug", 0, NULL, 'D' },
+ { "help", 0, NULL, 'h' },
+ { "version", 0, NULL, 'V' },
+ {}
+ };
+ int rc = 1;
+ int maxfd;
+
+ logging_init("udevd");
+ udev_config_init();
+ selinux_init();
+ dbg("version %s\n", UDEV_VERSION);
+
+ while (1) {
+ option = getopt_long(argc, argv, "dDthV", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'd':
+ daemonize = 1;
+ break;
+ case 't':
+ debug_trace = 1;
+ break;
+ case 'D':
+ debug = 1;
+ if (udev_log_priority < LOG_INFO)
+ udev_log_priority = LOG_INFO;
+ break;
+ case 'h':
+ printf("Usage: udevd [--help] [--daemon] [--debug-trace] [--debug] [--version]\n");
+ goto exit;
+ case 'V':
+ printf("%s\n", UDEV_VERSION);
+ goto exit;
+ default:
+ goto exit;
+ }
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "root privileges required\n");
+ err("root privileges required\n");
+ goto exit;
+ }
+
+ /* make sure std{in,out,err} fd's are in a sane state */
+ fd = open("/dev/null", O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "cannot open /dev/null\n");
+ err("cannot open /dev/null\n");
+ }
+ if (fd > STDIN_FILENO)
+ dup2(fd, STDIN_FILENO);
+ if (write(STDOUT_FILENO, 0, 0) < 0)
+ dup2(fd, STDOUT_FILENO);
+ if (write(STDERR_FILENO, 0, 0) < 0)
+ dup2(fd, STDERR_FILENO);
+
+ /* init sockets to receive events */
+ if (init_udevd_socket() < 0) {
+ if (errno == EADDRINUSE) {
+ fprintf(stderr, "another udev daemon already running\n");
+ err("another udev daemon already running\n");
+ rc = 1;
+ } else {
+ fprintf(stderr, "error initializing udevd socket\n");
+ err("error initializing udevd socket\n");
+ rc = 2;
+ }
+ goto exit;
+ }
+
+ if (init_uevent_netlink_sock() < 0) {
+ fprintf(stderr, "error initializing netlink socket\n");
+ err("error initializing netlink socket\n");
+ rc = 3;
+ goto exit;
+ }
+
+ /* setup signal handler pipe */
+ retval = pipe(signal_pipe);
+ if (retval < 0) {
+ err("error getting pipes: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ retval = fcntl(signal_pipe[READ_END], F_GETFL, 0);
+ if (retval < 0) {
+ err("error fcntl on read pipe: %s\n", strerror(errno));
+ goto exit;
+ }
+ retval = fcntl(signal_pipe[READ_END], F_SETFL, retval | O_NONBLOCK);
+ if (retval < 0) {
+ err("error fcntl on read pipe: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ retval = fcntl(signal_pipe[WRITE_END], F_GETFL, 0);
+ if (retval < 0) {
+ err("error fcntl on write pipe: %s\n", strerror(errno));
+ goto exit;
+ }
+ retval = fcntl(signal_pipe[WRITE_END], F_SETFL, retval | O_NONBLOCK);
+ if (retval < 0) {
+ err("error fcntl on write pipe: %s\n", strerror(errno));
+ goto exit;
+ }
+
+ /* parse the rules and keep them in memory */
+ sysfs_init();
+ udev_rules_init(&rules, 1);
+
+ export_initial_seqnum();
+
+ if (daemonize) {
+ pid_t pid;
+
+ pid = fork();
+ switch (pid) {
+ case 0:
+ dbg("daemonized fork running\n");
+ break;
+ case -1:
+ err("fork of daemon failed: %s\n", strerror(errno));
+ rc = 4;
+ goto exit;
+ default:
+ dbg("child [%u] running, parent exits\n", pid);
+ rc = 0;
+ goto exit;
+ }
+ }
+
+ /* redirect std{out,err} fd's */
+ if (!debug)
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ if (fd > STDERR_FILENO)
+ close(fd);
+
+ /* set scheduling priority for the daemon */
+ setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY);
+
+ chdir("/");
+ umask(022);
+
+ /* become session leader */
+ sid = setsid();
+ dbg("our session is %d\n", sid);
+
+ /* OOM_DISABLE == -17 */
+ fd = open("/proc/self/oom_adj", O_RDWR);
+ if (fd < 0)
+ err("error disabling OOM: %s\n", strerror(errno));
+ else {
+ write(fd, "-17", 3);
+ close(fd);
+ }
+
+ fd = open("/dev/kmsg", O_WRONLY);
+ if (fd > 0) {
+ const char *str = "<6>udevd version " UDEV_VERSION " started\n";
+
+ write(fd, str, strlen(str));
+ close(fd);
+ }
+
+ /* set signal handlers */
+ memset(&act, 0x00, sizeof(struct sigaction));
+ act.sa_handler = (void (*)(int)) sig_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+ sigaction(SIGCHLD, &act, NULL);
+ sigaction(SIGHUP, &act, NULL);
+
+ /* watch rules directory */
+ inotify_fd = inotify_init();
+ if (inotify_fd >= 0) {
+ if (udev_rules_dir[0] != '\0') {
+ inotify_add_watch(inotify_fd, udev_rules_dir,
+ IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+ } else {
+ char filename[PATH_MAX];
+
+ inotify_add_watch(inotify_fd, RULES_LIB_DIR,
+ IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+ inotify_add_watch(inotify_fd, RULES_ETC_DIR,
+ IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+
+ /* watch dynamic rules directory */
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/"RULES_DYN_DIR, sizeof(filename));
+ inotify_add_watch(inotify_fd, filename,
+ IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
+ }
+ } else if (errno == ENOSYS)
+ err("the kernel does not support inotify, udevd can't monitor rules file changes\n");
+ else
+ err("inotify_init failed: %s\n", strerror(errno));
+
+ /* maximum limit of forked childs */
+ value = getenv("UDEVD_MAX_CHILDS");
+ if (value)
+ max_childs = strtoul(value, NULL, 10);
+ else {
+ int memsize = mem_size_mb();
+ if (memsize > 0)
+ max_childs = 128 + (memsize / 4);
+ else
+ max_childs = UDEVD_MAX_CHILDS;
+ }
+ info("initialize max_childs to %u\n", max_childs);
+
+ /* start to throttle forking if maximum number of _running_ childs is reached */
+ value = getenv("UDEVD_MAX_CHILDS_RUNNING");
+ if (value)
+ max_childs_running = strtoull(value, NULL, 10);
+ else {
+ int cpus = cpu_count();
+ if (cpus > 0)
+ max_childs_running = 8 + (8 * cpus);
+ else
+ max_childs_running = UDEVD_MAX_CHILDS_RUNNING;
+ }
+ info("initialize max_childs_running to %u\n", max_childs_running);
+
+ /* clear environment for forked event processes */
+ clearenv();
+
+ /* export log_priority , as called programs may want to follow that setting */
+ sprintf(udev_log, "UDEV_LOG=%i", udev_log_priority);
+ putenv(udev_log);
+ if (debug_trace)
+ putenv("DEBUG=1");
+
+ maxfd = udevd_sock;
+ maxfd = UDEV_MAX(maxfd, uevent_netlink_sock);
+ maxfd = UDEV_MAX(maxfd, signal_pipe[READ_END]);
+ maxfd = UDEV_MAX(maxfd, inotify_fd);
+
+ while (!udev_exit) {
+ struct udevd_uevent_msg *msg;
+ int fdcount;
+
+ FD_ZERO(&readfds);
+ FD_SET(signal_pipe[READ_END], &readfds);
+ FD_SET(udevd_sock, &readfds);
+ FD_SET(uevent_netlink_sock, &readfds);
+ if (inotify_fd >= 0)
+ FD_SET(inotify_fd, &readfds);
+
+ fdcount = select(maxfd+1, &readfds, NULL, NULL, NULL);
+ if (fdcount < 0) {
+ if (errno != EINTR)
+ err("error in select: %s\n", strerror(errno));
+ continue;
+ }
+
+ /* get control message */
+ if (FD_ISSET(udevd_sock, &readfds))
+ get_ctrl_msg();
+
+ /* get netlink message */
+ if (FD_ISSET(uevent_netlink_sock, &readfds)) {
+ msg = get_netlink_msg();
+ if (msg)
+ msg_queue_insert(msg);
+ }
+
+ /* received a signal, clear our notification pipe */
+ if (FD_ISSET(signal_pipe[READ_END], &readfds)) {
+ char buf[256];
+
+ read(signal_pipe[READ_END], &buf, sizeof(buf));
+ }
+
+ /* rules directory inotify watch */
+ if ((inotify_fd >= 0) && FD_ISSET(inotify_fd, &readfds)) {
+ int nbytes;
+
+ /* discard all possible events, we can just reload the config */
+ if ((ioctl(inotify_fd, FIONREAD, &nbytes) == 0) && nbytes > 0) {
+ char *buf;
+
+ reload_config = 1;
+ buf = malloc(nbytes);
+ if (buf == NULL) {
+ err("error getting buffer for inotify, disable watching\n");
+ close(inotify_fd);
+ inotify_fd = -1;
+ }
+ read(inotify_fd, buf, nbytes);
+ free(buf);
+ }
+ }
+
+ /* rules changed, set by inotify or a HUP signal */
+ if (reload_config) {
+ reload_config = 0;
+ udev_rules_cleanup(&rules);
+ udev_rules_init(&rules, 1);
+ }
+
+ /* forked child has returned */
+ if (sigchilds_waiting) {
+ sigchilds_waiting = 0;
+ reap_sigchilds();
+ }
+
+ if (run_exec_q) {
+ run_exec_q = 0;
+ if (!stop_exec_q)
+ msg_queue_manager();
+ }
+ }
+ rc = 0;
+
+exit:
+ udev_rules_cleanup(&rules);
+ sysfs_cleanup();
+ selinux_exit();
+
+ if (signal_pipe[READ_END] >= 0)
+ close(signal_pipe[READ_END]);
+ if (signal_pipe[WRITE_END] >= 0)
+ close(signal_pipe[WRITE_END]);
+
+ if (udevd_sock >= 0)
+ close(udevd_sock);
+ if (inotify_fd >= 0)
+ close(inotify_fd);
+ if (uevent_netlink_sock >= 0)
+ close(uevent_netlink_sock);
+
+ logging_close();
+
+ return rc;
+}
diff --git a/udev/udevd.h b/udev/udevd.h
new file mode 100644
index 0000000000..9be34cb0cc
--- /dev/null
+++ b/udev/udevd.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004 Ling, Xiaofeng <xiaofeng.ling@intel.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "list.h"
+
+#define UDEVD_PRIORITY -4
+#define UDEV_PRIORITY -2
+
+#define EVENT_QUEUE_DIR ".udev/queue"
+#define EVENT_FAILED_DIR ".udev/failed"
+#define EVENT_SEQNUM ".udev/uevent_seqnum"
+
+/* maximum limit of forked childs */
+#define UDEVD_MAX_CHILDS 256
+/* start to throttle forking if maximum number of running childs in our session is reached */
+#define UDEVD_MAX_CHILDS_RUNNING 16
+
+/* linux/include/linux/kobject.h */
+#define UEVENT_BUFFER_SIZE 2048
+#define UEVENT_NUM_ENVP 32
+
+#define UDEVD_CTRL_SOCK_PATH "/org/kernel/udev/udevd"
+#define UDEVD_CTRL_MAGIC "udevd_" UDEV_VERSION
+
+enum udevd_ctrl_msg_type {
+ UDEVD_CTRL_UNKNOWN,
+ UDEVD_CTRL_STOP_EXEC_QUEUE,
+ UDEVD_CTRL_START_EXEC_QUEUE,
+ UDEVD_CTRL_SET_LOG_LEVEL,
+ UDEVD_CTRL_SET_MAX_CHILDS,
+ UDEVD_CTRL_SET_MAX_CHILDS_RUNNING,
+ UDEVD_CTRL_RELOAD_RULES,
+ UDEVD_CTRL_ENV,
+};
+
+struct udevd_ctrl_msg {
+ char magic[32];
+ enum udevd_ctrl_msg_type type;
+ char buf[256];
+};
+
+struct udevd_uevent_msg {
+ struct list_head node;
+ pid_t pid;
+ int exitstatus;
+ time_t queue_time;
+ char *action;
+ char *devpath;
+ char *subsystem;
+ char *driver;
+ dev_t devt;
+ unsigned long long seqnum;
+ char *devpath_old;
+ char *physdevpath;
+ unsigned int timeout;
+ char *envp[UEVENT_NUM_ENVP+1];
+ char envbuf[];
+};
diff --git a/udev/udevd.xml b/udev/udevd.xml
new file mode 100644
index 0000000000..8d22a0c14d
--- /dev/null
+++ b/udev/udevd.xml
@@ -0,0 +1,108 @@
+<?xml version='1.0'?>
+<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<article>
+ <section>
+ <title>udevd</title>
+ <refentry>
+ <refentryinfo>
+ <title>udevd</title>
+ <date>August 2005</date>
+ <productname>udev</productname>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>udevd</refentrytitle>
+ <manvolnum>8</manvolnum>
+ <refmiscinfo class="version"></refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>udevd</refname><refpurpose>event managing daemon</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>udevd</command>
+ <arg><option>--daemon</option></arg>
+ <arg><option>--debug-trace</option></arg>
+ <arg><option>--debug</option></arg>
+ <arg><option>--version</option></arg>
+ <arg><option>--help</option></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1><title>DESCRIPTION</title>
+ <para>udevd listens to kernel uevents and passes the incoming events to
+ udev. It ensures the correct event order and takes care, that events for child
+ devices are delayed until the parent event has finished the device handling.
+ The behavior of the running daemon can be changed with
+ <command>udevadm control</command>.</para>
+ </refsect1>
+
+ <refsect1><title>OPTIONS</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>--daemon</option></term>
+ <listitem>
+ <para>Detach and run in the background.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debug-trace</option></term>
+ <listitem>
+ <para>Run all events completely serialized. This may be useful if udev triggers
+ actions or loads kernel modules which cause problems and a slow but continuous
+ operation is needed, where no events are processed in parallel.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--debug</option></term>
+ <listitem>
+ <para>Print log messages to stdout.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--version</option></term>
+ <listitem>
+ <para>Print version number.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>help</option></term>
+ <listitem>
+ <para>Print help text.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1><title>ENVIRONMENT</title>
+ <variablelist>
+ <varlistentry>
+ <term><option>UDEV_LOG</option></term>
+ <listitem>
+ <para>Overrides the syslog priority specified in the config file.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1><title>AUTHOR</title>
+ <para>Written by Kay Sievers <email>kay.sievers@vrfy.org</email>.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para><citerefentry>
+ <refentrytitle>udev</refentrytitle><manvolnum>7</manvolnum>
+ </citerefentry>,
+ <citerefentry>
+ <refentrytitle>udevadm</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry></para>
+ </refsect1>
+ </refentry>
+ </section>
+</article>
diff --git a/udev/udevinfo.c b/udev/udevinfo.c
new file mode 100644
index 0000000000..b9ee17c4f1
--- /dev/null
+++ b/udev/udevinfo.c
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+
+static void print_all_attributes(const char *devpath, const char *key)
+{
+ char path[PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+
+ strlcpy(path, sysfs_path, sizeof(path));
+ strlcat(path, devpath, sizeof(path));
+
+ dir = opendir(path);
+ if (dir != NULL) {
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ struct stat statbuf;
+ char filename[PATH_SIZE];
+ char *attr_value;
+ char value[NAME_SIZE];
+ size_t len;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ if (strcmp(dent->d_name, "uevent") == 0)
+ continue;
+ if (strcmp(dent->d_name, "dev") == 0)
+ continue;
+
+ strlcpy(filename, path, sizeof(filename));
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, dent->d_name, sizeof(filename));
+ if (lstat(filename, &statbuf) != 0)
+ continue;
+ if (S_ISLNK(statbuf.st_mode))
+ continue;
+
+ attr_value = sysfs_attr_get_value(devpath, dent->d_name);
+ if (attr_value == NULL)
+ continue;
+ len = strlcpy(value, attr_value, sizeof(value));
+ if(len >= sizeof(value))
+ len = sizeof(value) - 1;
+ dbg("attr '%s'='%s'(%zi)\n", dent->d_name, value, len);
+
+ /* remove trailing newlines */
+ while (len && value[len-1] == '\n')
+ value[--len] = '\0';
+
+ /* skip nonprintable attributes */
+ while (len && isprint(value[len-1]))
+ len--;
+ if (len) {
+ dbg("attribute value of '%s' non-printable, skip\n", dent->d_name);
+ continue;
+ }
+
+ printf(" %s{%s}==\"%s\"\n", key, dent->d_name, value);
+ }
+ }
+ printf("\n");
+}
+
+static int print_device_chain(const char *devpath)
+{
+ struct sysfs_device *dev;
+
+ dev = sysfs_device_get(devpath);
+ if (dev == NULL)
+ return -1;
+
+ printf("\n"
+ "Udevinfo 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", dev->devpath);
+ printf(" KERNEL==\"%s\"\n", dev->kernel);
+ printf(" SUBSYSTEM==\"%s\"\n", dev->subsystem);
+ printf(" DRIVER==\"%s\"\n", dev->driver);
+ print_all_attributes(dev->devpath, "ATTR");
+
+ /* walk up the chain of devices */
+ while (1) {
+ dev = sysfs_device_get_parent(dev);
+ if (dev == NULL)
+ break;
+ printf(" looking at parent device '%s':\n", dev->devpath);
+ printf(" KERNELS==\"%s\"\n", dev->kernel);
+ printf(" SUBSYSTEMS==\"%s\"\n", dev->subsystem);
+ printf(" DRIVERS==\"%s\"\n", dev->driver);
+
+ print_all_attributes(dev->devpath, "ATTRS");
+ }
+
+ return 0;
+}
+
+static void print_record(struct udevice *udev)
+{
+ struct name_entry *name_loop;
+
+ printf("P: %s\n", udev->dev->devpath);
+ printf("N: %s\n", udev->name);
+ list_for_each_entry(name_loop, &udev->symlink_list, node)
+ printf("S: %s\n", name_loop->name);
+ if (udev->link_priority != 0)
+ printf("L: %i\n", udev->link_priority);
+ if (udev->partitions != 0)
+ printf("A:%u\n", udev->partitions);
+ if (udev->ignore_remove)
+ printf("R:%u\n", udev->ignore_remove);
+ list_for_each_entry(name_loop, &udev->env_list, node)
+ printf("E: %s\n", name_loop->name);
+}
+
+static void export_db(void) {
+ LIST_HEAD(name_list);
+ struct name_entry *name_loop;
+
+ udev_db_get_all_entries(&name_list);
+ list_for_each_entry(name_loop, &name_list, node) {
+ struct udevice *udev_db;
+
+ udev_db = udev_device_init(NULL);
+ if (udev_db == NULL)
+ continue;
+ if (udev_db_get_device(udev_db, name_loop->name) == 0)
+ print_record(udev_db);
+ printf("\n");
+ udev_device_cleanup(udev_db);
+ }
+ name_list_cleanup(&name_list);
+}
+
+static int lookup_device_by_name(struct udevice *udev, const char *name)
+{
+ LIST_HEAD(name_list);
+ int count;
+ struct name_entry *device;
+ int rc = -1;
+
+ count = udev_db_get_devices_by_name(name, &name_list);
+ if (count <= 0)
+ goto out;
+
+ info("found %i devices for '%s'\n", count, name);
+
+ /* select the device that seems to match */
+ list_for_each_entry(device, &name_list, node) {
+ char filename[PATH_SIZE];
+ struct stat statbuf;
+
+ udev_device_init(udev);
+ if (udev_db_get_device(udev, device->name) != 0)
+ continue;
+ info("found db entry '%s'\n", device->name);
+
+ /* make sure, we don't get a link of a differnt device */
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/", sizeof(filename));
+ strlcat(filename, name, sizeof(filename));
+ if (stat(filename, &statbuf) != 0)
+ continue;
+ if (major(udev->devt) > 0 && udev->devt != statbuf.st_rdev) {
+ info("skip '%s', dev_t doesn't match\n", udev->name);
+ continue;
+ }
+ rc = 0;
+ break;
+ }
+out:
+ name_list_cleanup(&name_list);
+ return rc;
+}
+
+static int stat_device(const char *name, int export, const char *prefix)
+{
+ struct stat statbuf;
+
+ if (stat(name, &statbuf) != 0)
+ return -1;
+
+ if (export) {
+ if (prefix == NULL)
+ prefix = "INFO_";
+ printf("%sMAJOR=%d\n"
+ "%sMINOR=%d\n",
+ prefix, major(statbuf.st_dev),
+ prefix, minor(statbuf.st_dev));
+ } else
+ printf("%d %d\n", major(statbuf.st_dev), minor(statbuf.st_dev));
+ return 0;
+}
+
+int udevinfo(int argc, char *argv[], char *envp[])
+{
+ int option;
+ struct udevice *udev;
+ int root = 0;
+ int export = 0;
+ const char *export_prefix = NULL;
+
+ static const struct option options[] = {
+ { "name", 1, NULL, 'n' },
+ { "path", 1, NULL, 'p' },
+ { "query", 1, NULL, 'q' },
+ { "attribute-walk", 0, NULL, 'a' },
+ { "export-db", 0, NULL, 'e' },
+ { "root", 0, NULL, 'r' },
+ { "device-id-of-file", 1, NULL, 'd' },
+ { "export", 0, NULL, 'x' },
+ { "export-prefix", 1, NULL, 'P' },
+ { "version", 0, NULL, 1 }, /* -V outputs braindead format */
+ { "help", 0, NULL, 'h' },
+ {}
+ };
+
+ enum action_type {
+ ACTION_NONE,
+ ACTION_QUERY,
+ ACTION_ATTRIBUTE_WALK,
+ ACTION_ROOT,
+ ACTION_DEVICE_ID_FILE,
+ } action = ACTION_NONE;
+
+ enum query_type {
+ QUERY_NONE,
+ QUERY_NAME,
+ QUERY_PATH,
+ QUERY_SYMLINK,
+ QUERY_ENV,
+ QUERY_ALL,
+ } query = QUERY_NONE;
+
+ char path[PATH_SIZE] = "";
+ char name[PATH_SIZE] = "";
+ struct name_entry *name_loop;
+ int rc = 0;
+
+ logging_init("udevinfo");
+ udev_config_init();
+ sysfs_init();
+
+ udev = udev_device_init(NULL);
+ if (udev == NULL) {
+ rc = 1;
+ goto exit;
+ }
+
+ while (1) {
+ option = getopt_long(argc, argv, "aed:n:p:q:rxPVh", options, NULL);
+ if (option == -1)
+ break;
+
+ dbg("option '%c'\n", option);
+ switch (option) {
+ case 'n':
+ /* remove /dev if given */
+ if (strncmp(optarg, udev_root, strlen(udev_root)) == 0)
+ strlcpy(name, &optarg[strlen(udev_root)+1], sizeof(name));
+ else
+ strlcpy(name, optarg, sizeof(name));
+ remove_trailing_chars(name, '/');
+ dbg("name: %s\n", name);
+ break;
+ case 'p':
+ /* remove /sys if given */
+ if (strncmp(optarg, sysfs_path, strlen(sysfs_path)) == 0)
+ strlcpy(path, &optarg[strlen(sysfs_path)], sizeof(path));
+ else
+ strlcpy(path, optarg, sizeof(path));
+ remove_trailing_chars(path, '/');
+
+ /* possibly resolve to real devpath */
+ if (sysfs_resolve_link(path, sizeof(path)) != 0) {
+ char temp[PATH_SIZE];
+ char *pos;
+
+ /* also check if the parent is a link */
+ strlcpy(temp, path, sizeof(temp));
+ pos = strrchr(temp, '/');
+ if (pos != 0) {
+ char tail[PATH_SIZE];
+
+ strlcpy(tail, pos, sizeof(tail));
+ pos[0] = '\0';
+ if (sysfs_resolve_link(temp, sizeof(temp)) == 0) {
+ strlcpy(path, temp, sizeof(path));
+ strlcat(path, tail, sizeof(path));
+ }
+ }
+ }
+ dbg("path: %s\n", path);
+ break;
+ case 'q':
+ action = ACTION_QUERY;
+ if (strcmp(optarg, "name") == 0) {
+ query = QUERY_NAME;
+ break;
+ }
+ if (strcmp(optarg, "symlink") == 0) {
+ query = QUERY_SYMLINK;
+ break;
+ }
+ if (strcmp(optarg, "path") == 0) {
+ query = QUERY_PATH;
+ break;
+ }
+ if (strcmp(optarg, "env") == 0) {
+ query = QUERY_ENV;
+ break;
+ }
+ if (strcmp(optarg, "all") == 0) {
+ query = QUERY_ALL;
+ break;
+ }
+ fprintf(stderr, "unknown query type\n");
+ rc = 2;
+ goto exit;
+ case 'r':
+ if (action == ACTION_NONE)
+ action = ACTION_ROOT;
+ root = 1;
+ break;
+ case 'd':
+ action = ACTION_DEVICE_ID_FILE;
+ strlcpy(name, optarg, sizeof(name));
+ break;
+ case 'a':
+ action = ACTION_ATTRIBUTE_WALK;
+ break;
+ case 'e':
+ export_db();
+ goto exit;
+ case 'x':
+ export = 1;
+ break;
+ case 'P':
+ export_prefix = optarg;
+ break;
+ case 1:
+ printf("%s\n", UDEV_VERSION);
+ goto exit;
+ case 'V':
+ printf("udevinfo, version %s\n", UDEV_VERSION);
+ goto exit;
+ case 'h':
+ printf("Usage: udevadm info OPTIONS\n"
+ " --query=<type> query database for the specified value:\n"
+ " name name of device node\n"
+ " symlink pointing to node\n"
+ " path sysfs device path\n"
+ " env the device related imported environment\n"
+ " all all values\n"
+ " --path=<devpath> sysfs device path used for query or chain\n"
+ " --name=<name> node or symlink name used for query\n"
+ " --root prepend to query result or print udev_root\n"
+ " --attribute-walk print all key matches while walking along chain\n"
+ " of parent devices\n"
+ " --device-id-of-file=<file> print major/minor of underlying device\n"
+ " --export-db export the content of the udev database\n"
+ " --help print this text\n"
+ "\n");
+ goto exit;
+ default:
+ goto exit;
+ }
+ }
+
+ /* run action */
+ switch (action) {
+ case ACTION_QUERY:
+ /* needs devpath or node/symlink name for query */
+ if (path[0] != '\0') {
+ if (udev_db_get_device(udev, path) != 0) {
+ fprintf(stderr, "no record for '%s' in database\n", path);
+ rc = 3;
+ goto exit;
+ }
+ } else if (name[0] != '\0') {
+ if (lookup_device_by_name(udev, name) != 0) {
+ fprintf(stderr, "node name not found\n");
+ rc = 4;
+ goto exit;
+ }
+ } else {
+ fprintf(stderr, "query needs --path or node --name specified\n");
+ rc = 4;
+ goto exit;
+ }
+
+ switch(query) {
+ case QUERY_NAME:
+ if (root)
+ printf("%s/%s\n", udev_root, udev->name);
+ else
+ printf("%s\n", udev->name);
+ break;
+ case QUERY_SYMLINK:
+ list_for_each_entry(name_loop, &udev->symlink_list, node) {
+ char c = name_loop->node.next != &udev->symlink_list ? ' ' : '\n';
+
+ if (root)
+ printf("%s/%s%c", udev_root, name_loop->name, c);
+ else
+ printf("%s%c", name_loop->name, c);
+ }
+ break;
+ case QUERY_PATH:
+ printf("%s\n", udev->dev->devpath);
+ goto exit;
+ case QUERY_ENV:
+ list_for_each_entry(name_loop, &udev->env_list, node)
+ printf("%s\n", name_loop->name);
+ break;
+ case QUERY_ALL:
+ print_record(udev);
+ break;
+ default:
+ fprintf(stderr, "unknown query type\n");
+ break;
+ }
+ break;
+ case ACTION_ATTRIBUTE_WALK:
+ if (path[0] != '\0') {
+ if (print_device_chain(path) != 0) {
+ fprintf(stderr, "no valid sysfs device found\n");
+ rc = 4;
+ goto exit;
+ }
+ } else if (name[0] != '\0') {
+ if (lookup_device_by_name(udev, name) != 0) {
+ fprintf(stderr, "node name not found\n");
+ rc = 4;
+ goto exit;
+ }
+ if (print_device_chain(udev->dev->devpath) != 0) {
+ fprintf(stderr, "no valid sysfs device found\n");
+ rc = 4;
+ goto exit;
+ }
+ } else {
+ fprintf(stderr, "attribute walk needs --path or node --name specified\n");
+ rc = 5;
+ goto exit;
+ }
+ break;
+ case ACTION_DEVICE_ID_FILE:
+ if (stat_device(name, export, export_prefix) != 0)
+ rc = 6;
+ break;
+ case ACTION_ROOT:
+ printf("%s\n", udev_root);
+ break;
+ default:
+ fprintf(stderr, "missing option\n");
+ rc = 1;
+ break;
+ }
+
+exit:
+ udev_device_cleanup(udev);
+ sysfs_cleanup();
+ logging_close();
+ return rc;
+}
diff --git a/udev/udevmonitor.c b/udev/udevmonitor.c
new file mode 100644
index 0000000000..2430dd39a5
--- /dev/null
+++ b/udev/udevmonitor.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/select.h>
+#include <linux/types.h>
+#include <linux/netlink.h>
+
+#include "udev.h"
+#include "udevd.h"
+
+static int uevent_netlink_sock = -1;
+static int udev_monitor_sock = -1;
+static volatile int udev_exit;
+
+static int init_udev_monitor_socket(void)
+{
+ struct sockaddr_un saddr;
+ socklen_t addrlen;
+ int retval;
+
+ memset(&saddr, 0x00, sizeof(saddr));
+ saddr.sun_family = AF_LOCAL;
+ /* use abstract namespace for socket path */
+ strcpy(&saddr.sun_path[1], "/org/kernel/udev/monitor");
+ addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+
+ udev_monitor_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (udev_monitor_sock == -1) {
+ fprintf(stderr, "error getting socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ /* the bind takes care of ensuring only one copy running */
+ retval = bind(udev_monitor_sock, (struct sockaddr *) &saddr, addrlen);
+ if (retval < 0) {
+ fprintf(stderr, "bind failed: %s\n", strerror(errno));
+ close(udev_monitor_sock);
+ udev_monitor_sock = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int init_uevent_netlink_sock(void)
+{
+ struct sockaddr_nl snl;
+ int retval;
+
+ memset(&snl, 0x00, sizeof(struct sockaddr_nl));
+ snl.nl_family = AF_NETLINK;
+ snl.nl_pid = getpid();
+ snl.nl_groups = 1;
+
+ uevent_netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
+ if (uevent_netlink_sock == -1) {
+ fprintf(stderr, "error getting socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ retval = bind(uevent_netlink_sock, (struct sockaddr *) &snl,
+ sizeof(struct sockaddr_nl));
+ if (retval < 0) {
+ fprintf(stderr, "bind failed: %s\n", strerror(errno));
+ close(uevent_netlink_sock);
+ uevent_netlink_sock = -1;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void asmlinkage sig_handler(int signum)
+{
+ if (signum == SIGINT || signum == SIGTERM)
+ udev_exit = 1;
+}
+
+static const char *search_key(const char *searchkey, const char *buf, size_t buflen)
+{
+ size_t bufpos = 0;
+ size_t searchkeylen = strlen(searchkey);
+
+ while (bufpos < buflen) {
+ const char *key;
+ int keylen;
+
+ key = &buf[bufpos];
+ keylen = strlen(key);
+ if (keylen == 0)
+ break;
+ if ((strncmp(searchkey, key, searchkeylen) == 0) && key[searchkeylen] == '=')
+ return &key[searchkeylen + 1];
+ bufpos += keylen + 1;
+ }
+ return NULL;
+}
+
+int udevmonitor(int argc, char *argv[], char *envp[])
+{
+ struct sigaction act;
+ int option;
+ int env = 0;
+ int kernel = 0;
+ int udev = 0;
+ fd_set readfds;
+ int retval = 0;
+
+ static const struct option options[] = {
+ { "environment", 0, NULL, 'e' },
+ { "kernel", 0, NULL, 'k' },
+ { "udev", 0, NULL, 'u' },
+ { "help", 0, NULL, 'h' },
+ {}
+ };
+
+ while (1) {
+ option = getopt_long(argc, argv, "ekuh", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'e':
+ env = 1;
+ break;
+ case 'k':
+ kernel = 1;
+ break;
+ case 'u':
+ udev = 1;
+ break;
+ case 'h':
+ printf("Usage: udevadm monitor [--environment] [--kernel] [--udev] [--help]\n"
+ " --env print the whole event environment\n"
+ " --kernel print kernel uevents\n"
+ " --udev print udev events\n"
+ " --help print this help text\n\n");
+ default:
+ goto out;
+ }
+ }
+
+ if (!kernel && !udev) {
+ kernel = 1;
+ udev =1;
+ }
+
+ if (getuid() != 0 && kernel) {
+ fprintf(stderr, "root privileges needed to subscribe to kernel events\n");
+ goto out;
+ }
+
+ /* set signal handlers */
+ memset(&act, 0x00, sizeof(struct sigaction));
+ act.sa_handler = (void (*)(int)) sig_handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &act, NULL);
+ sigaction(SIGTERM, &act, NULL);
+
+ printf("udevmonitor will print the received events for:\n");
+ if (udev) {
+ retval = init_udev_monitor_socket();
+ if (retval)
+ goto out;
+ printf("UDEV the event which udev sends out after rule processing\n");
+ }
+ if (kernel) {
+ retval = init_uevent_netlink_sock();
+ if (retval)
+ goto out;
+ printf("UEVENT the kernel uevent\n");
+ }
+ printf("\n");
+
+ while (!udev_exit) {
+ char buf[UEVENT_BUFFER_SIZE*2];
+ ssize_t buflen;
+ ssize_t bufpos;
+ ssize_t keys;
+ int fdcount;
+ struct timeval tv;
+ struct timezone tz;
+ char timestr[64];
+ const char *source = NULL;
+ const char *devpath, *action, *subsys;
+
+ buflen = 0;
+ FD_ZERO(&readfds);
+ if (uevent_netlink_sock >= 0)
+ FD_SET(uevent_netlink_sock, &readfds);
+ if (udev_monitor_sock >= 0)
+ FD_SET(udev_monitor_sock, &readfds);
+
+ fdcount = select(UDEV_MAX(uevent_netlink_sock, udev_monitor_sock)+1, &readfds, NULL, NULL, NULL);
+ if (fdcount < 0) {
+ if (errno != EINTR)
+ fprintf(stderr, "error receiving uevent message: %s\n", strerror(errno));
+ continue;
+ }
+
+ if (gettimeofday(&tv, &tz) == 0) {
+ snprintf(timestr, sizeof(timestr), "%llu.%06u",
+ (unsigned long long) tv.tv_sec, (unsigned int) tv.tv_usec);
+ } else
+ timestr[0] = '\0';
+
+ if ((uevent_netlink_sock >= 0) && FD_ISSET(uevent_netlink_sock, &readfds)) {
+ buflen = recv(uevent_netlink_sock, &buf, sizeof(buf), 0);
+ if (buflen <= 0) {
+ fprintf(stderr, "error receiving uevent message: %s\n", strerror(errno));
+ continue;
+ }
+ source = "UEVENT";
+ }
+
+ if ((udev_monitor_sock >= 0) && FD_ISSET(udev_monitor_sock, &readfds)) {
+ buflen = recv(udev_monitor_sock, &buf, sizeof(buf), 0);
+ if (buflen <= 0) {
+ fprintf(stderr, "error receiving udev message: %s\n", strerror(errno));
+ continue;
+ }
+ source = "UDEV ";
+ }
+
+ if (buflen == 0)
+ continue;
+
+ keys = strlen(buf) + 1; /* start of payload */
+ devpath = search_key("DEVPATH", &buf[keys], buflen);
+ action = search_key("ACTION", &buf[keys], buflen);
+ subsys = search_key("SUBSYSTEM", &buf[keys], buflen);
+ printf("%s[%s] %-8s %s (%s)\n", source, timestr, action, devpath, subsys);
+
+ /* print environment */
+ bufpos = keys;
+ if (env) {
+ while (bufpos < buflen) {
+ int keylen;
+ char *key;
+
+ key = &buf[bufpos];
+ keylen = strlen(key);
+ if (keylen == 0)
+ break;
+ printf("%s\n", key);
+ bufpos += keylen + 1;
+ }
+ printf("\n");
+ }
+ }
+
+out:
+ if (uevent_netlink_sock >= 0)
+ close(uevent_netlink_sock);
+ if (udev_monitor_sock >= 0)
+ close(udev_monitor_sock);
+
+ if (retval)
+ return 1;
+ return 0;
+}
diff --git a/udev/udevsettle.c b/udev/udevsettle.c
new file mode 100644
index 0000000000..cb63a66bba
--- /dev/null
+++ b/udev/udevsettle.c
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2006 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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "udev.h"
+#include "udevd.h"
+
+#define DEFAULT_TIMEOUT 180
+#define LOOP_PER_SECOND 20
+
+static void print_queue(const char *dir)
+{
+ LIST_HEAD(files);
+ struct name_entry *item;
+
+ if (add_matching_files(&files, dir, NULL) < 0)
+ return;
+
+ printf("\n\nAfter the udevadm settle timeout, the events queue contains:\n\n");
+
+ list_for_each_entry(item, &files, node) {
+ char target[NAME_SIZE];
+ size_t len;
+ const char *filename = strrchr(item->name, '/');
+
+ if (filename == NULL)
+ continue;
+ filename++;
+ if (*filename == '\0')
+ continue;
+
+ len = readlink(item->name, target, sizeof(target));
+ if (len < 0)
+ continue;
+ target[len] = '\0';
+
+ printf("%s: %s\n", filename, target);
+ }
+
+ printf("\n\n");
+}
+
+int udevsettle(int argc, char *argv[], char *envp[])
+{
+ char queuename[PATH_SIZE];
+ char filename[PATH_SIZE];
+ unsigned long long seq_kernel;
+ unsigned long long seq_udev;
+ char seqnum[32];
+ int fd;
+ ssize_t len;
+ int timeout = DEFAULT_TIMEOUT;
+ int loop;
+ static const struct option options[] = {
+ { "timeout", 1, NULL, 't' },
+ { "help", 0, NULL, 'h' },
+ {}
+ };
+ int option;
+ int rc = 1;
+ int seconds;
+
+ logging_init("udevsettle");
+ udev_config_init();
+ dbg("version %s\n", UDEV_VERSION);
+ sysfs_init();
+
+ while (1) {
+ option = getopt_long(argc, argv, "t:h", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 't':
+ seconds = atoi(optarg);
+ if (seconds > 0)
+ timeout = seconds;
+ else
+ fprintf(stderr, "invalid timeout value\n");
+ dbg("timeout=%i\n", timeout);
+ break;
+ case 'h':
+ printf("Usage: udevadm settle [--help] [--timeout=<seconds>]\n\n");
+ goto exit;
+ }
+ }
+
+ strlcpy(queuename, udev_root, sizeof(queuename));
+ strlcat(queuename, "/" EVENT_QUEUE_DIR, sizeof(queuename));
+
+ loop = timeout * LOOP_PER_SECOND;
+ while (loop--) {
+ /* wait for events in queue to finish */
+ while (loop--) {
+ struct stat statbuf;
+
+ if (stat(queuename, &statbuf) < 0) {
+ info("queue is empty\n");
+ break;
+ }
+ usleep(1000 * 1000 / LOOP_PER_SECOND);
+ }
+ if (loop <= 0) {
+ info("timeout waiting for queue\n");
+ print_queue(queuename);
+ goto exit;
+ }
+
+ /* read current udev seqnum */
+ strlcpy(filename, udev_root, sizeof(filename));
+ strlcat(filename, "/" EVENT_SEQNUM, sizeof(filename));
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto exit;
+ len = read(fd, seqnum, sizeof(seqnum)-1);
+ close(fd);
+ if (len <= 0)
+ goto exit;
+ seqnum[len] = '\0';
+ seq_udev = strtoull(seqnum, NULL, 10);
+ info("udev seqnum = %llu\n", seq_udev);
+
+ /* read current kernel seqnum */
+ strlcpy(filename, sysfs_path, sizeof(filename));
+ strlcat(filename, "/kernel/uevent_seqnum", sizeof(filename));
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto exit;
+ len = read(fd, seqnum, sizeof(seqnum)-1);
+ close(fd);
+ if (len <= 0)
+ goto exit;
+ seqnum[len] = '\0';
+ seq_kernel = strtoull(seqnum, NULL, 10);
+ info("kernel seqnum = %llu\n", seq_kernel);
+
+ /* make sure all kernel events have arrived in the queue */
+ if (seq_udev >= seq_kernel) {
+ info("queue is empty and no pending events left\n");
+ rc = 0;
+ goto exit;
+ }
+ usleep(1000 * 1000 / LOOP_PER_SECOND);
+ info("queue is empty, but events still pending\n");
+ }
+
+exit:
+ sysfs_cleanup();
+ logging_close();
+ return rc;
+}
diff --git a/udev/udevtest.c b/udev/udevtest.c
new file mode 100644
index 0000000000..d5e90b02c6
--- /dev/null
+++ b/udev/udevtest.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
+ * Copyright (C) 2004-2006 Kay Sievers <kay.sievers@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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <syslog.h>
+#include <getopt.h>
+
+#include "udev.h"
+#include "udev_rules.h"
+
+static int import_uevent_var(const char *devpath)
+{
+ char path[PATH_SIZE];
+ static char value[4096]; /* must stay, used with putenv */
+ ssize_t size;
+ int fd;
+ char *key;
+ char *next;
+ int rc = -1;
+
+ /* read uevent file */
+ strlcpy(path, sysfs_path, sizeof(path));
+ strlcat(path, devpath, sizeof(path));
+ strlcat(path, "/uevent", sizeof(path));
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ goto out;
+ size = read(fd, value, sizeof(value));
+ close(fd);
+ if (size < 0)
+ goto out;
+ value[size] = '\0';
+
+ /* import keys into environment */
+ key = value;
+ while (key[0] != '\0') {
+ next = strchr(key, '\n');
+ if (next == NULL)
+ goto out;
+ next[0] = '\0';
+ info("import into environment: '%s'\n", key);
+ putenv(key);
+ key = &next[1];
+ }
+ rc = 0;
+out:
+ return rc;
+}
+
+int udevtest(int argc, char *argv[], char *envp[])
+{
+ int force = 0;
+ const char *action = "add";
+ const char *subsystem = NULL;
+ const char *devpath = NULL;
+ struct udevice *udev;
+ struct sysfs_device *dev;
+ struct udev_rules rules = {};
+ int retval;
+ int rc = 0;
+
+ static const struct option options[] = {
+ { "action", 1, NULL, 'a' },
+ { "subsystem", 1, NULL, 's' },
+ { "force", 0, NULL, 'f' },
+ { "help", 0, NULL, 'h' },
+ {}
+ };
+
+ info("version %s\n", UDEV_VERSION);
+ udev_config_init();
+ if (udev_log_priority < LOG_INFO) {
+ char priority[32];
+
+ udev_log_priority = LOG_INFO;
+ sprintf(priority, "%i", udev_log_priority);
+ setenv("UDEV_LOG", priority, 1);
+ }
+
+ while (1) {
+ int option;
+
+ option = getopt_long(argc, argv, "a:s:fh", options, NULL);
+ if (option == -1)
+ break;
+
+ dbg("option '%c'\n", option);
+ switch (option) {
+ case 'a':
+ action = optarg;
+ break;
+ case 's':
+ subsystem = optarg;
+ break;
+ case 'f':
+ force = 1;
+ break;
+ case 'h':
+ printf("Usage: udevadm test OPTIONS <devpath>\n"
+ " --action=<string> set action string\n"
+ " --subsystem=<string> set subsystem string\n"
+ " --force don't skip node/link creation\n"
+ " --help print this help text\n\n");
+ exit(0);
+ default:
+ exit(1);
+ }
+ }
+ devpath = argv[optind];
+
+ if (devpath == NULL) {
+ fprintf(stderr, "devpath parameter missing\n");
+ rc = 1;
+ goto exit;
+ }
+
+ 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");
+
+ sysfs_init();
+ udev_rules_init(&rules, 0);
+
+ /* remove /sys if given */
+ if (strncmp(devpath, sysfs_path, strlen(sysfs_path)) == 0)
+ devpath = &devpath[strlen(sysfs_path)];
+
+ dev = sysfs_device_get(devpath);
+ if (dev == NULL) {
+ fprintf(stderr, "unable to open device '%s'\n", devpath);
+ rc = 2;
+ goto exit;
+ }
+
+ udev = udev_device_init(NULL);
+ if (udev == NULL) {
+ fprintf(stderr, "error initializing device\n");
+ rc = 3;
+ goto exit;
+ }
+
+ if (subsystem != NULL)
+ strlcpy(dev->subsystem, subsystem, sizeof(dev->subsystem));
+
+ /* override built-in sysfs device */
+ udev->dev = dev;
+ strlcpy(udev->action, action, sizeof(udev->action));
+ udev->devt = udev_device_get_devt(udev);
+
+ /* simulate node creation with test flag */
+ if (!force)
+ udev->test_run = 1;
+
+ setenv("DEVPATH", udev->dev->devpath, 1);
+ setenv("SUBSYSTEM", udev->dev->subsystem, 1);
+ setenv("ACTION", udev->action, 1);
+ import_uevent_var(udev->dev->devpath);
+
+ info("looking at device '%s' from subsystem '%s'\n", udev->dev->devpath, udev->dev->subsystem);
+ retval = udev_device_event(&rules, udev);
+
+ if (udev->event_timeout >= 0)
+ info("custom event timeout: %i\n", udev->event_timeout);
+
+ if (retval == 0 && !udev->ignore_device && udev_run) {
+ struct name_entry *name_loop;
+
+ list_for_each_entry(name_loop, &udev->run_list, node) {
+ char program[PATH_SIZE];
+
+ strlcpy(program, name_loop->name, sizeof(program));
+ udev_rules_apply_format(udev, program, sizeof(program));
+ info("run: '%s'\n", program);
+ }
+ }
+ udev_device_cleanup(udev);
+
+exit:
+ udev_rules_cleanup(&rules);
+ sysfs_cleanup();
+ return rc;
+}
diff --git a/udev/udevtrigger.c b/udev/udevtrigger.c
new file mode 100644
index 0000000000..d4b10d06ab
--- /dev/null
+++ b/udev/udevtrigger.c
@@ -0,0 +1,712 @@
+/*
+ * Copyright (C) 2004-2006 Kay Sievers <kay@vrfy.org>
+ * Copyright (C) 2006 Hannes Reinecke <hare@suse.de>
+ *
+ * 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 version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "udev.h"
+#include "udevd.h"
+#include "udev_rules.h"
+
+static int verbose;
+static int dry_run;
+LIST_HEAD(device_list);
+LIST_HEAD(filter_subsystem_match_list);
+LIST_HEAD(filter_subsystem_nomatch_list);
+LIST_HEAD(filter_attr_match_list);
+LIST_HEAD(filter_attr_nomatch_list);
+static int sock = -1;
+static struct sockaddr_un saddr;
+static socklen_t saddrlen;
+
+/* devices that should run last cause of their dependencies */
+static int delay_device(const char *devpath)
+{
+ static const char *delay_device_list[] = {
+ "*/md*",
+ "*/dm-*",
+ NULL
+ };
+ int i;
+
+ for (i = 0; delay_device_list[i] != NULL; i++)
+ if (fnmatch(delay_device_list[i], devpath, 0) == 0)
+ return 1;
+ return 0;
+}
+
+static int device_list_insert(const char *path)
+{
+ char filename[PATH_SIZE];
+ char devpath[PATH_SIZE];
+ struct stat statbuf;
+
+ dbg("add '%s'\n" , path);
+
+ /* we only have a device, if we have an uevent file */
+ strlcpy(filename, path, sizeof(filename));
+ strlcat(filename, "/uevent", sizeof(filename));
+ if (stat(filename, &statbuf) < 0)
+ return -1;
+ if (!(statbuf.st_mode & S_IWUSR))
+ return -1;
+
+ strlcpy(devpath, &path[strlen(sysfs_path)], sizeof(devpath));
+
+ /* resolve possible link to real target */
+ if (lstat(path, &statbuf) < 0)
+ return -1;
+ if (S_ISLNK(statbuf.st_mode))
+ if (sysfs_resolve_link(devpath, sizeof(devpath)) != 0)
+ return -1;
+
+ name_list_add(&device_list, devpath, 1);
+ return 0;
+}
+
+static void trigger_uevent(const char *devpath, const char *action)
+{
+ char filename[PATH_SIZE];
+ int fd;
+
+ strlcpy(filename, sysfs_path, sizeof(filename));
+ strlcat(filename, devpath, sizeof(filename));
+ strlcat(filename, "/uevent", sizeof(filename));
+
+ if (verbose)
+ printf("%s\n", devpath);
+
+ if (dry_run)
+ return;
+
+ fd = open(filename, O_WRONLY);
+ if (fd < 0) {
+ dbg("error on opening %s: %s\n", filename, strerror(errno));
+ return;
+ }
+
+ if (write(fd, action, strlen(action)) < 0)
+ info("error writing '%s' to '%s': %s\n", action, filename, strerror(errno));
+
+ close(fd);
+}
+
+static int pass_to_socket(const char *devpath, const char *action, const char *env)
+{
+ struct udevice udev;
+ struct name_entry *name_loop;
+ char buf[4096];
+ size_t bufpos = 0;
+ ssize_t count;
+ char path[PATH_SIZE];
+ int fd;
+ char link_target[PATH_SIZE];
+ int len;
+ int err = 0;
+
+ if (verbose)
+ printf("%s\n", devpath);
+
+ udev_device_init(&udev);
+ udev_db_get_device(&udev, devpath);
+
+ /* add header */
+ bufpos = snprintf(buf, sizeof(buf)-1, "%s@%s", action, devpath);
+ bufpos++;
+
+ /* add cookie */
+ if (env != NULL) {
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "%s", env);
+ bufpos++;
+ }
+
+ /* add standard keys */
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "DEVPATH=%s", devpath);
+ bufpos++;
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "ACTION=%s", action);
+ bufpos++;
+
+ /* add subsystem */
+ strlcpy(path, sysfs_path, sizeof(path));
+ strlcat(path, devpath, sizeof(path));
+ strlcat(path, "/subsystem", sizeof(path));
+ len = readlink(path, link_target, sizeof(link_target));
+ if (len > 0) {
+ char *pos;
+
+ link_target[len] = '\0';
+ pos = strrchr(link_target, '/');
+ if (pos != NULL) {
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "SUBSYSTEM=%s", &pos[1]);
+ bufpos++;
+ }
+ }
+
+ /* add symlinks and node name */
+ path[0] = '\0';
+ list_for_each_entry(name_loop, &udev.symlink_list, node) {
+ strlcat(path, udev_root, sizeof(path));
+ strlcat(path, "/", sizeof(path));
+ strlcat(path, name_loop->name, sizeof(path));
+ strlcat(path, " ", sizeof(path));
+ }
+ remove_trailing_chars(path, ' ');
+ if (path[0] != '\0') {
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "DEVLINKS=%s", path);
+ bufpos++;
+ }
+ if (udev.name[0] != '\0') {
+ strlcpy(path, udev_root, sizeof(path));
+ strlcat(path, "/", sizeof(path));
+ strlcat(path, udev.name, sizeof(path));
+ bufpos += snprintf(&buf[bufpos], sizeof(buf)-1, "DEVNAME=%s", path);
+ bufpos++;
+ }
+
+ /* add keys from device "uevent" file */
+ strlcpy(path, sysfs_path, sizeof(path));
+ strlcat(path, devpath, sizeof(path));
+ strlcat(path, "/uevent", sizeof(path));
+ fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ char value[4096];
+
+ count = read(fd, value, sizeof(value));
+ close(fd);
+ if (count > 0) {
+ char *key;
+
+ value[count] = '\0';
+ key = value;
+ while (key[0] != '\0') {
+ char *next;
+
+ next = strchr(key, '\n');
+ if (next == NULL)
+ break;
+ next[0] = '\0';
+ bufpos += strlcpy(&buf[bufpos], key, sizeof(buf) - bufpos-1);
+ bufpos++;
+ key = &next[1];
+ }
+ }
+ }
+
+ /* add keys from database */
+ list_for_each_entry(name_loop, &udev.env_list, node) {
+ bufpos += strlcpy(&buf[bufpos], name_loop->name, sizeof(buf) - bufpos-1);
+ bufpos++;
+ }
+ if (bufpos > sizeof(buf))
+ bufpos = sizeof(buf);
+
+ count = sendto(sock, &buf, bufpos, 0, (struct sockaddr *)&saddr, saddrlen);
+ if (count < 0)
+ err = -1;
+
+ return err;
+}
+
+static void exec_list(const char *action, const char *env)
+{
+ struct name_entry *loop_device;
+ struct name_entry *tmp_device;
+
+ list_for_each_entry_safe(loop_device, tmp_device, &device_list, node) {
+ if (delay_device(loop_device->name))
+ continue;
+ if (sock >= 0)
+ pass_to_socket(loop_device->name, action, env);
+ else
+ trigger_uevent(loop_device->name, action);
+ list_del(&loop_device->node);
+ free(loop_device);
+ }
+
+ /* trigger remaining delayed devices */
+ list_for_each_entry_safe(loop_device, tmp_device, &device_list, node) {
+ if (sock >= 0)
+ pass_to_socket(loop_device->name, action, env);
+ else
+ trigger_uevent(loop_device->name, action);
+ list_del(&loop_device->node);
+ free(loop_device);
+ }
+}
+
+static int subsystem_filtered(const char *subsystem)
+{
+ struct name_entry *loop_name;
+
+ /* skip devices matching the listed subsystems */
+ list_for_each_entry(loop_name, &filter_subsystem_nomatch_list, node)
+ if (fnmatch(loop_name->name, subsystem, 0) == 0)
+ return 1;
+
+ /* skip devices not matching the listed subsystems */
+ if (!list_empty(&filter_subsystem_match_list)) {
+ list_for_each_entry(loop_name, &filter_subsystem_match_list, node)
+ if (fnmatch(loop_name->name, subsystem, 0) == 0)
+ return 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+static int attr_match(const char *path, const char *attr_value)
+{
+ char attr[NAME_SIZE];
+ char file[PATH_SIZE];
+ char *match_value;
+
+ strlcpy(attr, attr_value, sizeof(attr));
+
+ /* separate attr and match value */
+ match_value = strchr(attr, '=');
+ if (match_value != NULL) {
+ match_value[0] = '\0';
+ match_value = &match_value[1];
+ }
+
+ strlcpy(file, path, sizeof(file));
+ strlcat(file, "/", sizeof(file));
+ strlcat(file, attr, sizeof(file));
+
+ if (match_value != NULL) {
+ /* match file content */
+ char value[NAME_SIZE];
+ int fd;
+ ssize_t size;
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ return 0;
+ size = read(fd, value, sizeof(value));
+ close(fd);
+ if (size < 0)
+ return 0;
+ value[size] = '\0';
+ remove_trailing_chars(value, '\n');
+
+ /* match if attribute value matches */
+ if (fnmatch(match_value, value, 0) == 0)
+ return 1;
+ } else {
+ /* match if attribute exists */
+ struct stat statbuf;
+
+ if (stat(file, &statbuf) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int attr_filtered(const char *path)
+{
+ struct name_entry *loop_name;
+
+ /* skip devices matching the listed sysfs attributes */
+ list_for_each_entry(loop_name, &filter_attr_nomatch_list, node)
+ if (attr_match(path, loop_name->name))
+ return 1;
+
+ /* skip devices not matching the listed sysfs attributes */
+ if (!list_empty(&filter_attr_match_list)) {
+ list_for_each_entry(loop_name, &filter_attr_match_list, node)
+ if (attr_match(path, loop_name->name))
+ return 0;
+ return 1;
+ }
+ return 0;
+}
+
+enum scan_type {
+ SCAN_DEVICES,
+ SCAN_SUBSYSTEM,
+};
+
+static void scan_subsystem(const char *subsys, enum scan_type scan)
+{
+ char base[PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+ const char *subdir;
+
+ if (scan == SCAN_DEVICES)
+ subdir = "/devices";
+ else if (scan == SCAN_SUBSYSTEM)
+ subdir = "/drivers";
+ else
+ return;
+
+ strlcpy(base, sysfs_path, sizeof(base));
+ strlcat(base, "/", sizeof(base));
+ strlcat(base, subsys, sizeof(base));
+
+ dir = opendir(base);
+ if (dir != NULL) {
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char dirname[PATH_SIZE];
+ DIR *dir2;
+ struct dirent *dent2;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ if (scan == SCAN_DEVICES)
+ if (subsystem_filtered(dent->d_name))
+ continue;
+
+ strlcpy(dirname, base, sizeof(dirname));
+ strlcat(dirname, "/", sizeof(dirname));
+ strlcat(dirname, dent->d_name, sizeof(dirname));
+
+ if (scan == SCAN_SUBSYSTEM) {
+ if (!subsystem_filtered("subsystem"))
+ device_list_insert(dirname);
+ if (subsystem_filtered("drivers"))
+ continue;
+ }
+
+ strlcat(dirname, subdir, sizeof(dirname));
+
+ /* look for devices/drivers */
+ dir2 = opendir(dirname);
+ if (dir2 != NULL) {
+ for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) {
+ char dirname2[PATH_SIZE];
+
+ if (dent2->d_name[0] == '.')
+ continue;
+
+ strlcpy(dirname2, dirname, sizeof(dirname2));
+ strlcat(dirname2, "/", sizeof(dirname2));
+ strlcat(dirname2, dent2->d_name, sizeof(dirname2));
+ if (attr_filtered(dirname2))
+ continue;
+ device_list_insert(dirname2);
+ }
+ closedir(dir2);
+ }
+ }
+ closedir(dir);
+ }
+}
+
+static void scan_block(void)
+{
+ char base[PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+
+ if (subsystem_filtered("block"))
+ return;
+
+ strlcpy(base, sysfs_path, sizeof(base));
+ strlcat(base, "/block", sizeof(base));
+
+ dir = opendir(base);
+ if (dir != NULL) {
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char dirname[PATH_SIZE];
+ DIR *dir2;
+ struct dirent *dent2;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ strlcpy(dirname, base, sizeof(dirname));
+ strlcat(dirname, "/", sizeof(dirname));
+ strlcat(dirname, dent->d_name, sizeof(dirname));
+ if (attr_filtered(dirname))
+ continue;
+ if (device_list_insert(dirname) != 0)
+ continue;
+
+ /* look for partitions */
+ dir2 = opendir(dirname);
+ if (dir2 != NULL) {
+ for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) {
+ char dirname2[PATH_SIZE];
+
+ if (dent2->d_name[0] == '.')
+ continue;
+
+ if (!strcmp(dent2->d_name,"device"))
+ continue;
+
+ strlcpy(dirname2, dirname, sizeof(dirname2));
+ strlcat(dirname2, "/", sizeof(dirname2));
+ strlcat(dirname2, dent2->d_name, sizeof(dirname2));
+ if (attr_filtered(dirname2))
+ continue;
+ device_list_insert(dirname2);
+ }
+ closedir(dir2);
+ }
+ }
+ closedir(dir);
+ }
+}
+
+static void scan_class(void)
+{
+ char base[PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+
+ strlcpy(base, sysfs_path, sizeof(base));
+ strlcat(base, "/class", sizeof(base));
+
+ dir = opendir(base);
+ if (dir != NULL) {
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char dirname[PATH_SIZE];
+ DIR *dir2;
+ struct dirent *dent2;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ if (subsystem_filtered(dent->d_name))
+ continue;
+
+ strlcpy(dirname, base, sizeof(dirname));
+ strlcat(dirname, "/", sizeof(dirname));
+ strlcat(dirname, dent->d_name, sizeof(dirname));
+ dir2 = opendir(dirname);
+ if (dir2 != NULL) {
+ for (dent2 = readdir(dir2); dent2 != NULL; dent2 = readdir(dir2)) {
+ char dirname2[PATH_SIZE];
+
+ if (dent2->d_name[0] == '.')
+ continue;
+
+ if (!strcmp(dent2->d_name, "device"))
+ continue;
+
+ strlcpy(dirname2, dirname, sizeof(dirname2));
+ strlcat(dirname2, "/", sizeof(dirname2));
+ strlcat(dirname2, dent2->d_name, sizeof(dirname2));
+ if (attr_filtered(dirname2))
+ continue;
+ device_list_insert(dirname2);
+ }
+ closedir(dir2);
+ }
+ }
+ closedir(dir);
+ }
+}
+
+static void scan_failed(void)
+{
+ char base[PATH_SIZE];
+ DIR *dir;
+ struct dirent *dent;
+
+ strlcpy(base, udev_root, sizeof(base));
+ strlcat(base, "/" EVENT_FAILED_DIR, sizeof(base));
+
+ dir = opendir(base);
+ if (dir != NULL) {
+ for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+ char device[PATH_SIZE];
+ size_t start;
+
+ if (dent->d_name[0] == '.')
+ continue;
+
+ start = strlcpy(device, sysfs_path, sizeof(device));
+ if(start >= sizeof(device))
+ start = sizeof(device) - 1;
+ strlcat(device, dent->d_name, sizeof(device));
+ path_decode(&device[start]);
+ device_list_insert(device);
+ }
+ closedir(dir);
+ }
+}
+
+int udevtrigger(int argc, char *argv[], char *envp[])
+{
+ int failed = 0;
+ const char *sockpath = NULL;
+ int option;
+ const char *action = "add";
+ const char *env = NULL;
+ static const struct option options[] = {
+ { "verbose", 0, NULL, 'v' },
+ { "dry-run", 0, NULL, 'n' },
+ { "retry-failed", 0, NULL, 'F' },
+ { "socket", 1, NULL, 'o' },
+ { "help", 0, NULL, 'h' },
+ { "action", 1, NULL, 'c' },
+ { "subsystem-match", 1, NULL, 's' },
+ { "subsystem-nomatch", 1, NULL, 'S' },
+ { "attr-match", 1, NULL, 'a' },
+ { "attr-nomatch", 1, NULL, 'A' },
+ { "env", 1, NULL, 'e' },
+ {}
+ };
+
+ logging_init("udevtrigger");
+ udev_config_init();
+ dbg("version %s\n", UDEV_VERSION);
+ sysfs_init();
+
+ while (1) {
+ option = getopt_long(argc, argv, "vnFo:hce::s:S:a:A:", options, NULL);
+ if (option == -1)
+ break;
+
+ switch (option) {
+ case 'v':
+ verbose = 1;
+ break;
+ case 'n':
+ dry_run = 1;
+ break;
+ case 'F':
+ failed = 1;
+ break;
+ case 'o':
+ sockpath = optarg;
+ break;
+ case 'c':
+ action = optarg;
+ break;
+ case 'e':
+ if (strchr(optarg, '=') != NULL)
+ env = optarg;
+ break;
+ case 's':
+ name_list_add(&filter_subsystem_match_list, optarg, 0);
+ break;
+ case 'S':
+ name_list_add(&filter_subsystem_nomatch_list, optarg, 0);
+ break;
+ case 'a':
+ name_list_add(&filter_attr_match_list, optarg, 0);
+ break;
+ case 'A':
+ name_list_add(&filter_attr_nomatch_list, optarg, 0);
+ break;
+ case 'h':
+ printf("Usage: udevadm trigger OPTIONS\n"
+ " --verbose print the list of devices while running\n"
+ " --dry-run do not actually trigger the events\n"
+ " --retry-failed trigger only the events which have been\n"
+ " marked as failed during a previous run\n"
+ " --socket=<socket path> pass events to socket instead of triggering kernel events\n"
+ " --env=<KEY>=<value> pass an additional key (works only with --socket=)\n"
+ " --subsystem-match=<subsystem> trigger devices from a matching subystem\n"
+ " --subsystem-nomatch=<subsystem> exclude devices from a matching subystem\n"
+ " --attr-match=<file[=<value>]> trigger devices with a matching sysfs\n"
+ " attribute\n"
+ " --attr-nomatch=<file[=<value>]> exclude devices with a matching sysfs\n"
+ " attribute\n"
+ " --help print this text\n"
+ "\n");
+ goto exit;
+ default:
+ goto exit;
+ }
+ }
+
+ if (sockpath != NULL) {
+ struct stat stats;
+
+ sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ memset(&saddr, 0x00, sizeof(struct sockaddr_un));
+ saddr.sun_family = AF_LOCAL;
+ if (sockpath[0] == '@') {
+ /* abstract namespace socket requested */
+ strlcpy(&saddr.sun_path[1], &sockpath[1], sizeof(saddr.sun_path)-1);
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+ } else if (stat(sockpath, &stats) == 0 && S_ISSOCK(stats.st_mode)) {
+ /* existing socket file */
+ strlcpy(saddr.sun_path, sockpath, sizeof(saddr.sun_path));
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path);
+ } else {
+ /* no socket file, assume abstract namespace socket */
+ strlcpy(&saddr.sun_path[1], sockpath, sizeof(saddr.sun_path)-1);
+ saddrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(&saddr.sun_path[1]);
+ }
+ } else if (env != NULL) {
+ fprintf(stderr, "error: --env= only valid with --socket= option\n");
+ goto exit;
+ }
+
+ if (failed) {
+ scan_failed();
+ exec_list(action, env);
+ } else {
+ char base[PATH_SIZE];
+ struct stat statbuf;
+
+ /* if we have /sys/subsystem, forget all the old stuff */
+ strlcpy(base, sysfs_path, sizeof(base));
+ strlcat(base, "/subsystem", sizeof(base));
+ if (stat(base, &statbuf) == 0) {
+ scan_subsystem("subsystem", SCAN_SUBSYSTEM);
+ exec_list(action, env);
+ scan_subsystem("subsystem", SCAN_DEVICES);
+ exec_list(action, env);
+ } else {
+ scan_subsystem("bus", SCAN_SUBSYSTEM);
+ exec_list(action, env);
+ scan_subsystem("bus", SCAN_DEVICES);
+ scan_class();
+
+ /* scan "block" if it isn't a "class" */
+ strlcpy(base, sysfs_path, sizeof(base));
+ strlcat(base, "/class/block", sizeof(base));
+ if (stat(base, &statbuf) != 0)
+ scan_block();
+ exec_list(action, env);
+ }
+ }
+
+exit:
+ name_list_cleanup(&filter_subsystem_match_list);
+ name_list_cleanup(&filter_subsystem_nomatch_list);
+ name_list_cleanup(&filter_attr_match_list);
+ name_list_cleanup(&filter_attr_nomatch_list);
+
+ if (sock >= 0)
+ close(sock);
+ sysfs_cleanup();
+ logging_close();
+ return 0;
+}