From: Kay Sievers Date: Thu, 28 Aug 2008 21:05:01 +0000 (+0200) Subject: libudev: add udev event monitor API X-Git-Tag: 174~1620 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=ba6929f6690a72485ac3c6b7610ffbd5ab319be7 libudev: add udev event monitor API --- diff --git a/NEWS b/NEWS index 016ec198e..1a9aa6905 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,13 @@ udev 127 ======== Bugfixes. +A shared library "libudev" will be installed now, to access udev +device information. DeviceKit, the successor of HAL, will need this +library to access the udev database and search sysfs for devices. +The library is currently in an experimental state, also the API is +expected to change, as long as the DeviceKit integration isn't +finished. + udev 126 ======== We use ./configure now. See INSTALL for details. Current diff --git a/udev/lib/Makefile.am b/udev/lib/Makefile.am index 2e5b81d4e..a1fd77b87 100644 --- a/udev/lib/Makefile.am +++ b/udev/lib/Makefile.am @@ -24,6 +24,7 @@ libudev_la_SOURCES =\ libudev-utils.c \ libudev-device.c \ libudev-enumerate.c \ + libudev-monitor.c \ ../list.h \ ../udev.h \ ../udev_utils.c \ diff --git a/udev/lib/exported_symbols b/udev/lib/exported_symbols index c3a7cafe0..0826e6f6f 100644 --- a/udev/lib/exported_symbols +++ b/udev/lib/exported_symbols @@ -15,3 +15,9 @@ udev_device_get_subsystem udev_device_get_devlinks udev_device_get_properties udev_devices_enumerate +udev_monitor_new_from_socket +udev_monitor_ref +udev_monitor_unref +udev_monitor_get_udev +udev_monitor_get_fd +udev_monitor_get_device diff --git a/udev/lib/libudev-device.c b/udev/lib/libudev-device.c index 805eef946..f6ad541de 100644 --- a/udev/lib/libudev-device.c +++ b/udev/lib/libudev-device.c @@ -32,16 +32,22 @@ #include "libudev-private.h" #include "../udev.h" -static struct udev_device *device_init(struct udev *udev) +struct udev_device *device_init(struct udev *udev) { struct udev_device *udev_device; + if (udev == NULL) + return NULL; + udev_device = malloc(sizeof(struct udev_device)); if (udev_device == NULL) return NULL; memset(udev_device, 0x00, sizeof(struct udev_device)); udev_device->refcount = 1; udev_device->udev = udev; + INIT_LIST_HEAD(&udev_device->link_list); + INIT_LIST_HEAD(&udev_device->env_list); + log_info(udev_device->udev, "udev_device: %p created\n", udev_device); return udev_device; } @@ -64,8 +70,15 @@ struct udev_device *udev_device_new_from_devpath(struct udev *udev, const char * char path[PATH_SIZE]; struct stat statbuf; struct udev_device *udev_device; + struct udevice *udevice; + struct name_entry *name_loop; int err; + if (udev == NULL) + return NULL; + if (devpath == NULL) + return NULL; + strlcpy(path, udev_get_sys_path(udev), sizeof(path)); strlcat(path, devpath, sizeof(path)); if (stat(path, &statbuf) != 0) @@ -77,20 +90,38 @@ struct udev_device *udev_device_new_from_devpath(struct udev *udev, const char * if (udev_device == NULL) return NULL; - udev_device->udevice = udev_device_init(NULL); - if (udev_device->udevice == NULL) { + udevice = udev_device_init(NULL); + if (udevice == NULL) { free(udev_device); return NULL; } - log_info(udev, "device %p created\n", udev_device); + /* resolve possible symlink to real path */ strlcpy(path, devpath, sizeof(path)); sysfs_resolve_link(path, sizeof(path)); + udev_device->devpath = strdup(path); + log_info(udev, "device %p has devpath '%s'\n", udev_device, udev_device_get_devpath(udev_device)); - err = udev_db_get_device(udev_device->udevice, path); + err = udev_db_get_device(udevice, path); if (err >= 0) log_info(udev, "device %p filled with udev database data\n", udev_device); - log_info(udev, "device %p filled with %s data\n", udev_device, udev_device_get_devpath(udev_device)); + + if (udevice->name[0] != '\0') + asprintf(&udev_device->devname, "%s/%s", udev_get_dev_path(udev), udevice->name); + + list_for_each_entry(name_loop, &udevice->symlink_list, node) { + char name[PATH_SIZE]; + + strlcpy(name, udev_get_dev_path(udev), sizeof(name)); + strlcat(name, "/", sizeof(name)); + strlcat(name, name_loop->name, sizeof(name)); + name_list_add(&udev_device->link_list, name, 0); + } + + list_for_each_entry(name_loop, &udevice->env_list, node) + name_list_add(&udev_device->env_list, name_loop->name, 0); + + udev_device_cleanup(udevice); return udev_device; } @@ -103,6 +134,8 @@ struct udev_device *udev_device_new_from_devpath(struct udev *udev, const char * **/ struct udev *udev_device_get_udev(struct udev_device *udev_device) { + if (udev_device == NULL) + return NULL; return udev_device->udev; } @@ -116,6 +149,8 @@ struct udev *udev_device_get_udev(struct udev_device *udev_device) **/ struct udev_device *udev_device_ref(struct udev_device *udev_device) { + if (udev_device == NULL) + return NULL; udev_device->refcount++; return udev_device; } @@ -130,10 +165,17 @@ struct udev_device *udev_device_ref(struct udev_device *udev_device) **/ void udev_device_unref(struct udev_device *udev_device) { + if (udev_device == NULL) + return; udev_device->refcount--; if (udev_device->refcount > 0) return; - udev_device_cleanup(udev_device->udevice); + free(udev_device->devpath); + free(udev_device->devname); + free(udev_device->subsystem); + name_list_cleanup(&udev_device->link_list); + name_list_cleanup(&udev_device->env_list); + log_info(udev_device->udev, "udev_device: %p released\n", udev_device); free(udev_device); } @@ -148,7 +190,9 @@ void udev_device_unref(struct udev_device *udev_device) **/ const char *udev_device_get_devpath(struct udev_device *udev_device) { - return udev_device->udevice->dev->devpath; + if (udev_device == NULL) + return NULL; + return udev_device->devpath; } /** @@ -156,16 +200,15 @@ const char *udev_device_get_devpath(struct udev_device *udev_device) * @udev_device: udev device * * Retrieve the device node file name belonging to the udev device. - * The path does not contain the device directory, and does not contain - * a leading '/'. + * The path is an absolute path, and starts with the device directory. * * Returns: the device node file name of the udev device, or #NULL if no device node exists **/ const char *udev_device_get_devname(struct udev_device *udev_device) { - if (udev_device->udevice->name[0] == '\0') + if (udev_device == NULL) return NULL; - return udev_device->udevice->name; + return udev_device->devname; } /** @@ -179,13 +222,16 @@ const char *udev_device_get_devname(struct udev_device *udev_device) **/ const char *udev_device_get_subsystem(struct udev_device *udev_device) { - struct sysfs_device *dev = udev_device->udevice->dev; - if (dev->subsystem[0] != '\0') - return dev->subsystem; - if (util_get_sys_subsystem(udev_device->udev, dev->devpath, - dev->subsystem, sizeof(dev->subsystem)) < 2) + char subsystem[NAME_SIZE]; + + if (udev_device == NULL) + return NULL; + if (udev_device->subsystem != NULL) + return udev_device->subsystem; + if (util_get_sys_subsystem(udev_device->udev, udev_device->devpath, subsystem, sizeof(subsystem)) < 2) return NULL; - return dev->subsystem; + udev_device->subsystem = strdup(subsystem); + return udev_device->subsystem; } /** @@ -196,10 +242,9 @@ const char *udev_device_get_subsystem(struct udev_device *udev_device) * * Retrieve the device links pointing to the device file of the * udev device. For every device link, the passed function will be - * called with the device link string. If the function returns 1, - * remaning device links will be ignored. The device link path - * does not contain the device directory, and does not contain - * a leading '/'. + * called with the device link string. + * The path is an absolute path, and starts with the device directory. + * If the function returns 1, remaning device links will be ignored. * * Returns: the number of device links passed to the caller, or a negative value on error **/ @@ -210,7 +255,9 @@ int udev_device_get_devlinks(struct udev_device *udev_device, struct name_entry *name_loop; int count = 0; - list_for_each_entry(name_loop, &udev_device->udevice->symlink_list, node) { + if (udev_device == NULL) + return -1; + list_for_each_entry(name_loop, &udev_device->link_list, node) { count++; if (cb(udev_device, name_loop->name, data) != 0) break; @@ -238,7 +285,9 @@ int udev_device_get_properties(struct udev_device *udev_device, struct name_entry *name_loop; int count = 0; - list_for_each_entry(name_loop, &udev_device->udevice->env_list, node) { + if (udev_device == NULL) + return -1; + list_for_each_entry(name_loop, &udev_device->env_list, node) { char name[PATH_SIZE]; char *val; @@ -249,6 +298,7 @@ int udev_device_get_properties(struct udev_device *udev_device, continue; val[0] = '\0'; val = &val[1]; + count++; if (cb(udev_device, name, val, data) != 0) break; } diff --git a/udev/lib/libudev-monitor.c b/udev/lib/libudev-monitor.c new file mode 100644 index 000000000..fbb21d04e --- /dev/null +++ b/udev/lib/libudev-monitor.c @@ -0,0 +1,216 @@ +/* + * libudev - interface to udev device information + * + * Copyright (C) 2008 Kay Sievers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include "libudev-private.h" +#include "../udev.h" + +struct udev_monitor { + struct udev *udev; + int refcount; + int socket; +}; + +struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path) +{ + struct udev_monitor *udev_monitor; + struct sockaddr_un saddr; + socklen_t addrlen; + const int on = 1; + + if (udev == NULL) + return NULL; + if (socket_path == NULL) + return NULL; + udev_monitor = malloc(sizeof(struct udev_monitor)); + if (udev_monitor == NULL) + return NULL; + memset(udev_monitor, 0x00, sizeof(struct udev_monitor)); + udev_monitor->refcount = 1; + udev_monitor->udev = udev; + + memset(&saddr, 0x00, sizeof(saddr)); + saddr.sun_family = AF_LOCAL; + strcpy(saddr.sun_path, socket_path); + addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path); + + /* translate leading '@' to abstract namespace */ + if (saddr.sun_path[0] == '@') + saddr.sun_path[0] = '\0'; + + udev_monitor->socket = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (udev_monitor->socket == -1) { + log_err(udev, "error getting socket: %s\n", strerror(errno)); + free(udev_monitor); + return NULL; + } + + if (bind(udev_monitor->socket, (struct sockaddr *) &saddr, addrlen) < 0) { + log_err(udev, "bind failed: %s\n", strerror(errno)); + close(udev_monitor->socket); + free(udev_monitor); + return NULL; + } + + /* enable receiving of the sender credentials */ + setsockopt(udev_monitor->socket, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + log_info(udev_monitor->udev, "udev_monitor: %p created\n", udev_monitor); + + return udev_monitor; +} + +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount++; + return udev_monitor; +} + +void udev_monitor_unref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return; + udev_monitor->refcount--; + if (udev_monitor->refcount > 0) + return; + close(udev_monitor->socket); + log_info(udev_monitor->udev, "udev_monitor: %p released\n", udev_monitor); + free(udev_monitor); +} + +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + return udev_monitor->udev; +} + +int udev_monitor_get_fd(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return -1; + return udev_monitor->socket; +} + +struct udev_device *udev_monitor_get_device(struct udev_monitor *udev_monitor) +{ + struct udev_device *udev_device; + struct msghdr smsg; + struct cmsghdr *cmsg; + struct iovec iov; + struct ucred *cred; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + char buf[4096]; + size_t bufpos; + + if (udev_monitor == NULL) + return NULL; + memset(buf, 0x00, sizeof(buf)); + iov.iov_base = &buf; + iov.iov_len = sizeof(buf); + 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); + + if (recvmsg(udev_monitor->socket, &smsg, 0) < 0) { + if (errno != EINTR) + log_info(udev_monitor->udev, "unable to receive message"); + return NULL; + } + cmsg = CMSG_FIRSTHDR(&smsg); + cred = (struct ucred *)CMSG_DATA (cmsg); + + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + log_info(udev_monitor->udev, "no sender credentials received, message ignored"); + return NULL; + } + + if (cred->uid != 0) { + log_info(udev_monitor->udev, "sender uid=%d, message ignored", cred->uid); + return NULL; + } + + /* skip header */ + bufpos = strlen(buf) + 1; + if (bufpos < sizeof("a@/d") || bufpos >= sizeof(buf)) { + log_info(udev_monitor->udev, "invalid message length"); + return NULL; + } + + /* check message header */ + if (strstr(buf, "@/") == NULL) { + log_info(udev_monitor->udev, "unrecognized message header"); + return NULL; + } + + udev_device = device_init(udev_monitor->udev); + if (udev_device == NULL) { + return NULL; + } + + while (bufpos < sizeof(buf)) { + char *key; + size_t keylen; + + key = &buf[bufpos]; + keylen = strlen(key); + if (keylen == 0) + break; + bufpos += keylen + 1; + + if (strncmp(key, "DEVPATH=", 8) == 0) { + udev_device->devpath = strdup(&key[8]); + } else if (strncmp(key, "SUBSYSTEM=", 10) == 0) { + udev_device->subsystem = strdup(&key[10]); + } else if (strncmp(key, "DEVNAME=", 8) == 0) { + udev_device->devname = strdup(&key[8]); + } else if (strncmp(key, "DEVLINKS=", 9) == 0) { + char *slink = &key[9]; + char *next = strchr(slink, ' '); + + while (next != NULL) { + next[0] = '\0'; + name_list_add(&udev_device->link_list, slink, 0); + slink = &next[1]; + next = strchr(slink, ' '); + } + if (slink[0] != '\0') + name_list_add(&udev_device->link_list, slink, 0); + } + name_list_add(&udev_device->env_list, key, 0); + } + + return udev_device; +} diff --git a/udev/lib/libudev-private.h b/udev/lib/libudev-private.h index 4697f84da..7e7d1c632 100644 --- a/udev/lib/libudev-private.h +++ b/udev/lib/libudev-private.h @@ -23,17 +23,14 @@ #include "libudev.h" #include "../udev.h" -struct udev { - int refcount; - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args); -}; - struct udev_device { int refcount; struct udev *udev; - struct udevice *udevice; + char *devpath; + char *devname; + char *subsystem; + struct list_head link_list; + struct list_head env_list; }; #ifdef USE_LOG @@ -57,6 +54,7 @@ static inline void udev_log(struct udev *udev, __attribute__ ((format(printf, 6, 7))) {} #endif +extern struct udev_device *device_init(struct udev *udev); extern ssize_t util_get_sys_subsystem(struct udev *udev, const char *devpath, char *subsystem, size_t size); #endif diff --git a/udev/lib/libudev.c b/udev/lib/libudev.c index 56bd47741..a5f33eb08 100644 --- a/udev/lib/libudev.c +++ b/udev/lib/libudev.c @@ -32,6 +32,13 @@ #include "libudev-private.h" #include "../udev.h" +struct udev { + int refcount; + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args); +}; + void udev_log(struct udev *udev, int priority, const char *file, int line, const char *fn, const char *format, ...) @@ -110,6 +117,8 @@ struct udev *udev_new(void) **/ struct udev *udev_ref(struct udev *udev) { + if (udev == NULL) + return NULL; udev->refcount++; return udev; } @@ -124,6 +133,8 @@ struct udev *udev_ref(struct udev *udev) **/ void udev_unref(struct udev *udev) { + if (udev == NULL) + return; udev->refcount--; if (udev->refcount > 0) return; @@ -164,6 +175,8 @@ void udev_set_log_fn(struct udev *udev, **/ const char *udev_get_sys_path(struct udev *udev) { + if (udev == NULL) + return NULL; return sysfs_path; } @@ -179,5 +192,7 @@ const char *udev_get_sys_path(struct udev *udev) **/ const char *udev_get_dev_path(struct udev *udev) { + if (udev == NULL) + return NULL; return udev_root; } diff --git a/udev/lib/libudev.h b/udev/lib/libudev.h index e740f3670..1eb3745e6 100644 --- a/udev/lib/libudev.h +++ b/udev/lib/libudev.h @@ -22,6 +22,7 @@ struct udev; struct udev_device; +struct udev_monitor; extern struct udev *udev_new(void); extern struct udev *udev_ref(struct udev *udev); @@ -54,4 +55,11 @@ extern int udev_devices_enumerate(struct udev *udev, const char *subsystem, const char *devpath, const char *subsystem, const char *name, void *data), void *data); +extern struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path); +extern struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); +extern void udev_monitor_unref(struct udev_monitor *udev_monitor); +extern struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); +extern int udev_monitor_get_fd(struct udev_monitor *udev_monitor); +extern struct udev_device *udev_monitor_get_device(struct udev_monitor *udev_monitor); + #endif diff --git a/udev/lib/test-libudev.c b/udev/lib/test-libudev.c index e86c660e6..3c3860019 100644 --- a/udev/lib/test-libudev.c +++ b/udev/lib/test-libudev.c @@ -21,6 +21,11 @@ #include #include +#include +#include +#include +#include + #include "libudev.h" static void log_fn(struct udev *udev, @@ -31,9 +36,49 @@ static void log_fn(struct udev *udev, vprintf(format, args); } -static int devlinks_cb(struct udev_device *udev_device, const char *value, void *data) +static int print_devlinks_cb(struct udev_device *udev_device, const char *value, void *data) { - printf("link: %s\n", value); + printf("link: '%s'\n", value); + return 0; +} + +static int print_properties_cb(struct udev_device *udev_device, const char *key, const char *value, void *data) +{ + printf("property: '%s=%s'\n", key, value); + return 0; +} + +static void print_device(struct udev_device *device) +{ + const char *str; + int count; + + printf("*** device: %p ***\n", device); + str = udev_device_get_devpath(device); + printf("devpath: '%s'\n", str); + str = udev_device_get_subsystem(device); + printf("subsystem: '%s'\n", str); + str = udev_device_get_devname(device); + printf("devname: '%s'\n", str); + count = udev_device_get_devlinks(device, print_devlinks_cb, NULL); + printf("found %i links\n", count); + count = udev_device_get_properties(device, print_properties_cb, NULL); + printf("found %i properties\n", count); + printf("\n"); +} + +static int test_device(struct udev *udev, const char *devpath) +{ + struct udev_device *device; + + printf("looking at device: %s\n", devpath); + device = udev_device_new_from_devpath(udev, devpath); + if (device == NULL) { + printf("no device\n"); + return -1; + } + print_device(device); + udev_device_unref(device); return 0; } @@ -41,28 +86,79 @@ static int devices_enum_cb(struct udev *udev, const char *devpath, const char *subsystem, const char *name, void *data) { - printf("device: %s (%s) %s\n", devpath, subsystem, name); + printf("device: '%s' (%s) '%s'\n", devpath, subsystem, name); return 0; } -static int properties_cb(struct udev_device *udev_device, const char *key, const char *value, void *data) +static int test_enumerate(struct udev *udev, const char *subsystem) { - printf("property: %s=%s\n", key, value); + int count; + + count = udev_devices_enumerate(udev, subsystem, devices_enum_cb, NULL); + printf("found %i devices\n\n", count); + return count; +} + +static int test_monitor(struct udev *udev, const char *socket_path) +{ + struct udev_monitor *udev_monitor; + fd_set readfds; + int fd; + + udev_monitor = udev_monitor_new_from_socket(udev, socket_path); + if (udev_monitor == NULL) { + printf("no socket\n"); + return -1; + } + + fd = udev_monitor_get_fd(udev_monitor); + FD_ZERO(&readfds); + + while (1) { + struct udev_device *device; + int fdcount; + + FD_SET(STDIN_FILENO, &readfds); + FD_SET(fd, &readfds); + + printf("waiting for events on %s, press ENTER to exit\n", socket_path); + fdcount = select(fd+1, &readfds, NULL, NULL, NULL); + printf("select fd count: %i\n", fdcount); + + if (FD_ISSET(fd, &readfds)) { + device = udev_monitor_get_device(udev_monitor); + if (device == NULL) { + printf("no device from socket\n"); + continue; + } + print_device(device); + udev_device_unref(device); + } + + if (FD_ISSET(STDIN_FILENO, &readfds)) { + printf("exiting loop\n"); + break; + } + } + + udev_monitor_unref(udev_monitor); return 0; } int main(int argc, char *argv[], char *envp[]) { struct udev *udev; - struct udev_device *device; - const char *str; const char *devpath = "/devices/virtual/mem/null"; const char *subsystem = NULL; + const char *socket = "@/org/kernel/udev/monitor"; + const char *str; if (argv[1] != NULL) { devpath = argv[1]; if (argv[2] != NULL) subsystem = argv[2]; + if (argv[3] != NULL) + socket = argv[3]; } udev = udev_new(); @@ -75,32 +171,13 @@ int main(int argc, char *argv[], char *envp[]) printf("set log: %p\n", log_fn); str = udev_get_sys_path(udev); - printf("sys_path: %s\n", str); + printf("sys_path: '%s'\n", str); str = udev_get_dev_path(udev); - printf("dev_path: %s\n", str); - - printf("looking at device: %s\n", devpath); - device = udev_device_new_from_devpath(udev, devpath); - printf("device: %p\n", device); - if (device == NULL) { - printf("no device\n"); - return 1; - } - str = udev_device_get_devpath(device); - printf("devpath: %s\n", str); - str = udev_device_get_subsystem(device); - printf("subsystem: %s\n", str); - str = udev_device_get_devname(device); - printf("devname: %s\n", str); - udev_device_get_devlinks(device, devlinks_cb, NULL); - udev_device_get_properties(device, properties_cb, NULL); - udev_device_unref(device); + printf("dev_path: '%s'\n", str); - if (subsystem == NULL) - printf("enumerating devices from all subsystems\n"); - else - printf("enumerating devices from subsystem: %s\n", subsystem); - udev_devices_enumerate(udev, subsystem, devices_enum_cb, NULL); + test_device(udev, devpath); + test_enumerate(udev, subsystem); + test_monitor(udev, socket); udev_unref(udev); return 0;