diff options
Diffstat (limited to 'udev')
-rw-r--r-- | udev/list.h | 289 | ||||
-rw-r--r-- | udev/logging.h | 73 | ||||
-rw-r--r-- | udev/test-udev.c | 175 | ||||
-rw-r--r-- | udev/udev.h | 184 | ||||
-rw-r--r-- | udev/udev.xml | 639 | ||||
-rw-r--r-- | udev/udev_config.c | 200 | ||||
-rw-r--r-- | udev/udev_db.c | 331 | ||||
-rw-r--r-- | udev/udev_device.c | 305 | ||||
-rw-r--r-- | udev/udev_node.c | 448 | ||||
-rw-r--r-- | udev/udev_rules.c | 1618 | ||||
-rw-r--r-- | udev/udev_rules.h | 131 | ||||
-rw-r--r-- | udev/udev_rules_parse.c | 804 | ||||
-rw-r--r-- | udev/udev_selinux.c | 173 | ||||
-rw-r--r-- | udev/udev_selinux.h | 38 | ||||
-rw-r--r-- | udev/udev_sysdeps.c | 73 | ||||
-rw-r--r-- | udev/udev_sysdeps.h | 172 | ||||
-rw-r--r-- | udev/udev_sysfs.c | 509 | ||||
-rw-r--r-- | udev/udev_utils.c | 222 | ||||
-rw-r--r-- | udev/udev_utils_file.c | 178 | ||||
-rw-r--r-- | udev/udev_utils_string.c | 270 | ||||
-rw-r--r-- | udev/udevadm.c | 168 | ||||
-rw-r--r-- | udev/udevadm.xml | 379 | ||||
-rw-r--r-- | udev/udevcontrol.c | 165 | ||||
-rw-r--r-- | udev/udevd.c | 1304 | ||||
-rw-r--r-- | udev/udevd.h | 74 | ||||
-rw-r--r-- | udev/udevd.xml | 108 | ||||
-rw-r--r-- | udev/udevinfo.c | 500 | ||||
-rw-r--r-- | udev/udevmonitor.c | 289 | ||||
-rw-r--r-- | udev/udevsettle.c | 178 | ||||
-rw-r--r-- | udev/udevtest.c | 207 | ||||
-rw-r--r-- | udev/udevtrigger.c | 712 |
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; +} |