chiark / gitweb /
udevd: optionally watch device nodes with inotify
authorScott James Remnant <scott@ubuntu.com>
Wed, 11 Feb 2009 17:38:56 +0000 (17:38 +0000)
committerScott James Remnant <scott@ubuntu.com>
Wed, 11 Feb 2009 17:38:56 +0000 (17:38 +0000)
This allows you to re-process the rules if the content of the device
has been changed, most useful for block subsystem to cause vol_id to
be run again.

NEWS
udev/Makefile.am
udev/udev-event.c
udev/udev-rules.c
udev/udev-watch.c [new file with mode: 0644]
udev/udev.h
udev/udev.xml
udev/udevd.c

diff --git a/NEWS b/NEWS
index 1737d9faea7b5354c83ca126cc1ecd4a7cfcf9cc..98f4937fa8086b43fc1ea9ddf991d388a61767ca 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,7 @@
 udev 138
 ========
+Device nodes can be watched for changes with inotify with OPTIONS="watch".  If
+closed after being opened for writing, a "change" uevent will occur.
 
 udev 137
 ========
index 2d185f477d1eb6b26ccf938a21bb4e63eb82a599..fa8279dd61663089df1c1441e750ae6fb8db9044 100644 (file)
@@ -16,6 +16,7 @@ common_files = \
        udev.h \
        udev-sysdeps.h \
        udev-event.c \
+       udev-watch.c \
        udev-node.c \
        udev-rules.c \
        udev-util.c \
index e34f09cfe3dc156d7f815d9eb319b60e2678626f..592da01099dcb9dcb057171726abf982b50a3652 100644 (file)
@@ -600,6 +600,14 @@ int udev_event_execute_rules(struct udev_event *event, struct udev_rules *rules)
 
                /* create new node and symlinks */
                err = udev_node_add(dev, event->mode, event->uid, event->gid);
+
+               /* watch for changes */
+               if (event->inotify_watch && inotify_fd != -1) {
+                       info(event->udev, "device will be watched for changes\n");
+                       udev_watch_begin(event->udev, event->dev);
+               } else {
+                       udev_watch_clear(event->udev, event->dev);
+               }
                goto exit;
        }
 
index f0b11aa85b9880c3c395b1336f9b0b1dce23ccdf..3751cbb36d55b311d41a19245cdd92205c15a156 100644 (file)
@@ -140,6 +140,7 @@ enum token_type {
        TK_A_IGNORE_DEVICE,
        TK_A_STRING_ESCAPE_NONE,
        TK_A_STRING_ESCAPE_REPLACE,
+       TK_A_INOTIFY_WATCH,
        TK_A_NUM_FAKE_PART,             /* int */
        TK_A_DEVLINK_PRIO,              /* int */
        TK_A_OWNER,                     /* val */
@@ -270,6 +271,7 @@ static const char *token_str(enum token_type type)
                [TK_A_IGNORE_DEVICE] =          "A IGNORE_DEVICE",
                [TK_A_STRING_ESCAPE_NONE] =     "A STRING_ESCAPE_NONE",
                [TK_A_STRING_ESCAPE_REPLACE] =  "A STRING_ESCAPE_REPLACE",
+               [TK_A_INOTIFY_WATCH] =          "A INOTIFY_WATCH",
                [TK_A_NUM_FAKE_PART] =          "A NUM_FAKE_PART",
                [TK_A_DEVLINK_PRIO] =           "A DEVLINK_PRIO",
                [TK_A_OWNER] =                  "A OWNER",
@@ -352,6 +354,7 @@ static void dump_token(struct udev_rules *rules, struct token *token)
        case TK_A_IGNORE_DEVICE:
        case TK_A_STRING_ESCAPE_NONE:
        case TK_A_STRING_ESCAPE_REPLACE:
+       case TK_A_INOTIFY_WATCH:
        case TK_A_LAST_RULE:
        case TK_A_IGNORE_REMOVE:
                dbg(rules->udev, "%s\n", token_str(type));
@@ -1020,6 +1023,7 @@ static int rule_add_key(struct rule_tmp *rule_tmp, enum token_type type,
        case TK_A_IGNORE_DEVICE:
        case TK_A_STRING_ESCAPE_NONE:
        case TK_A_STRING_ESCAPE_REPLACE:
+       case TK_A_INOTIFY_WATCH:
        case TK_A_IGNORE_REMOVE:
        case TK_A_LAST_RULE:
                break;
@@ -1512,6 +1516,11 @@ static int add_rule(struct udev_rules *rules, char *line,
                                rule_add_key(&rule_tmp, TK_A_NUM_FAKE_PART, 0, NULL, &num);
                                dbg(rules->udev, "creation of partition nodes requested\n");
                        }
+                       pos = strstr(value, "watch");
+                       if (pos != NULL) {
+                               rule_add_key(&rule_tmp, TK_A_INOTIFY_WATCH, 0, NULL, NULL);
+                               dbg(rules->udev, "inotify watch of device requested\n");
+                       }
                        continue;
                }
                err(rules->udev, "unknown key '%s' in %s:%u\n", key, filename, lineno);
@@ -2244,6 +2253,9 @@ int udev_rules_apply_to_event(struct udev_rules *rules, struct udev_event *event
                                break;
                        udev_device_set_num_fake_partitions(event->dev, cur->key.num_fake_part);
                        break;
+               case TK_A_INOTIFY_WATCH:
+                       event->inotify_watch = 1;
+                       break;
                case TK_A_DEVLINK_PRIO:
                        udev_device_set_devlink_priority(event->dev, cur->key.devlink_prio);
                        break;
diff --git a/udev/udev-watch.c b/udev/udev-watch.c
new file mode 100644 (file)
index 0000000..24df481
--- /dev/null
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2004-2008 Kay Sievers <kay.sievers@vrfy.org>
+ * Copyright (C) 2009 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_INOTIFY
+#include <sys/inotify.h>
+#endif
+
+#include "udev.h"
+
+int inotify_fd = -1;
+
+/* inotify descriptor, will be shared with rules directory;
+ * set to cloexec since we need our children to be able to add
+ * watches for us
+ */
+void udev_watch_init(struct udev *udev)
+{
+       inotify_fd = inotify_init();
+       if (inotify_fd >= 0) {
+               int flags;
+
+               flags = fcntl(inotify_fd, F_GETFD);
+               if (flags < 0)
+                       flags = FD_CLOEXEC;
+               else
+                       flags |= FD_CLOEXEC;
+               fcntl(inotify_fd, F_SETFD, flags);
+       } else if (errno == ENOSYS)
+               info(udev, "unable to use inotify, udevd will not monitor rule files changes\n");
+       else
+               err(udev, "inotify_init failed: %m\n");
+}
+
+/* move any old watches directory out of the way, and then restore
+ * the watches
+ */
+void udev_watch_restore(struct udev *udev)
+{
+       char filename[UTIL_PATH_SIZE], oldname[UTIL_PATH_SIZE];
+
+       if (inotify_fd < 0)
+               return;
+
+       util_strlcpy(oldname, udev_get_dev_path(udev), sizeof(oldname));
+       util_strlcat(oldname, "/.udev/watch.old", sizeof(oldname));
+
+       util_strlcpy(filename, udev_get_dev_path(udev), sizeof(filename));
+       util_strlcat(filename, "/.udev/watch", sizeof(filename));
+
+       if (rename(filename, oldname) == 0) {
+               DIR *dir;
+               struct dirent *ent;
+
+               dir = opendir(oldname);
+               if (dir == NULL) {
+                       err(udev, "unable to open old watches dir '%s', old watches will not be restored: %m", oldname);
+                       return;
+               }
+
+               while ((ent = readdir(dir)) != NULL) {
+                       char path[UTIL_PATH_SIZE];
+                       char buf[UTIL_PATH_SIZE];
+                       ssize_t len;
+                       struct udev_device *dev;
+
+                       if (ent->d_name[0] < '0' || ent->d_name[0] > '9')
+                               continue;
+
+                       util_strlcpy(path, oldname, sizeof(path));
+                       util_strlcat(path, "/", sizeof(path));
+                       util_strlcat(path, ent->d_name, sizeof(path));
+
+                       len = readlink(path, buf, sizeof(buf));
+                       if (len <= 0) {
+                               unlink(path);
+                               continue;
+                       }
+
+                       buf[len] = '\0';
+                       dbg(udev, "old watch to '%s' found\n", buf);
+
+                       dev = udev_device_new_from_syspath(udev, buf);
+                       if (dev == NULL) {
+                               unlink(path);
+                               continue;
+                       }
+
+                       udev_device_read_db(dev);
+                       udev_device_set_info_loaded(dev);
+
+                       info(udev, "restoring old watch on '%s'\n", udev_device_get_devnode(dev));
+                       udev_watch_begin(udev, dev);
+
+                       udev_device_unref(dev);
+
+                       unlink(path);
+               }
+
+               closedir(dir);
+               rmdir(oldname);
+
+       } else if (errno != ENOENT) {
+               err(udev, "unable to move watches dir '%s', old watches will not be restored: %m", filename);
+       }
+}
+
+static const char *udev_watch_filename(struct udev *udev, int wd)
+{
+       static char filename[UTIL_PATH_SIZE];
+       char str[32];
+
+       sprintf(str, "%d", wd);
+       util_strlcpy(filename, udev_get_dev_path(udev), sizeof(filename));
+       util_strlcat(filename, "/.udev/watch/", sizeof(filename));
+       util_strlcat(filename, str, sizeof(filename));
+
+       return filename;
+}
+
+void udev_watch_begin(struct udev *udev, struct udev_device *dev)
+{
+       const char *filename;
+       int wd;
+
+       if (inotify_fd < 0)
+               return;
+
+       wd = inotify_add_watch (inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+       if (wd < 0) {
+               err(udev, "inotify_add_watch(%d, %s, %o) failed: %m\n",
+                   inotify_fd, udev_device_get_devnode(dev), IN_CLOSE_WRITE);
+       }
+
+       filename = udev_watch_filename(udev, wd);
+       util_create_path(udev, filename);
+       unlink(filename);
+       symlink(udev_device_get_syspath(dev), filename);
+}
+
+void udev_watch_clear(struct udev *udev, struct udev_device *dev)
+{
+       static char filename[UTIL_PATH_SIZE];
+       DIR *dir;
+       struct dirent *ent;
+
+       util_strlcpy(filename, udev_get_dev_path(udev), sizeof(filename));
+       util_strlcat(filename, "/.udev/watch", sizeof(filename));
+
+       dir = opendir(filename);
+       if (dir == NULL)
+               return;
+
+       while ((ent = readdir(dir)) != NULL) {
+               char path[UTIL_PATH_SIZE];
+               char buf[UTIL_PATH_SIZE];
+               ssize_t len;
+
+               if (ent->d_name[0] < '0' || ent->d_name[0] > '9')
+                       continue;
+
+               util_strlcpy(path, filename, sizeof(path));
+               util_strlcat(path, "/", sizeof(path));
+               util_strlcat(path, ent->d_name, sizeof(path));
+
+               len = readlink(path, buf, sizeof(buf));
+               if (len <= 0)
+                       continue;
+
+               buf[len] = '\0';
+               if (strcmp(buf, udev_device_get_syspath(dev)))
+                       continue;
+
+               /* this is the watch we're looking for */
+               info(udev, "clearing existing watch on '%s'\n", udev_device_get_devnode(dev));
+               udev_watch_end(udev, atoi(ent->d_name));
+       }
+
+       closedir(dir);
+}
+
+void udev_watch_end(struct udev *udev, int wd)
+{
+       const char *filename;
+
+       if (inotify_fd < 0 || wd < 0)
+               return;
+
+       inotify_rm_watch(inotify_fd, wd);
+
+       filename = udev_watch_filename(udev, wd);
+       unlink(filename);
+}
+
+const char *udev_watch_lookup(struct udev *udev, int wd)
+{
+       const char *filename;
+       static char buf[UTIL_PATH_SIZE];
+       ssize_t len;
+
+       if (inotify_fd < 0 || wd < 0)
+               return NULL;
+
+       filename = udev_watch_filename(udev, wd);
+       len = readlink(filename, buf, sizeof(buf));
+       if (len > 0) {
+               buf[len] = '\0';
+
+               return buf;
+       }
+
+       return NULL;
+}
index d1b207196e2937cb0ff171a0b79fb19cc61f4171..57e2d73c2e656aea65f0180a66dad0449be6b786 100644 (file)
@@ -75,9 +75,16 @@ struct udev_event {
        unsigned int devlink_final:1;
        unsigned int run_final:1;
        unsigned int ignore_device:1;
+       unsigned int inotify_watch:1;
        unsigned int trace:1;
 };
 
+struct udev_watch {
+       struct udev_list_node node;
+       int handle;
+       char *name;
+};
+
 /* udev-rules.c */
 struct udev_rules;
 extern struct udev_rules *udev_rules_new(struct udev *udev, int resolve_names);
@@ -93,6 +100,15 @@ extern void udev_event_apply_format(struct udev_event *event, char *string, size
 extern int udev_event_apply_subsys_kernel(struct udev_event *event, const char *string,
                                          char *result, size_t maxsize, int read_value);
 
+/* udev-watch.c */
+extern int inotify_fd;
+extern void udev_watch_init(struct udev *udev);
+extern void udev_watch_restore(struct udev *udev);
+extern void udev_watch_begin(struct udev *udev, struct udev_device *dev);
+extern void udev_watch_clear(struct udev *udev, struct udev_device *dev);
+extern void udev_watch_end(struct udev *udev, int wd);
+extern const char *udev_watch_lookup(struct udev *udev, int wd);
+
 /* udev-node.c */
 extern int udev_node_mknod(struct udev_device *dev, const char *file, dev_t devnum, mode_t mode, uid_t uid, gid_t gid);
 extern int udev_node_add(struct udev_device *dev, mode_t mode, uid_t uid, gid_t gid);
index 6a92154cc143dbe6af405a8246f377b1d4763b9e..084c13bd98cb5d3a6f60719c5d0d1f8083c3c163 100644 (file)
                       with this option.</para>
                     </listitem>
                   </varlistentry>
+                  <varlistentry>
+                    <term><option>watch</option></term>
+                    <listitem>
+                      <para>Watch the device node with inotify, when closed after being opened for
+                     writing, a change uevent will be synthesised.</para>
+                    </listitem>
+                  </varlistentry>
                 </variablelist>
               </listitem>
             </varlistentry>
index d08cf30f29b806340f3e5099193fc831a607314d..d93781f411dfe07d73be21a629934078a810eaa2 100644 (file)
@@ -65,7 +65,6 @@ static int debug_trace;
 static struct udev_rules *rules;
 static struct udev_ctrl *udev_ctrl;
 static struct udev_monitor *kernel_monitor;
-static int inotify_fd = -1;
 static volatile int sigchilds_waiting;
 static volatile int udev_exit;
 static volatile int reload_config;
@@ -195,8 +194,6 @@ static void event_fork(struct udev_event *event)
                /* child */
                udev_monitor_unref(kernel_monitor);
                udev_ctrl_unref(udev_ctrl);
-               if (inotify_fd >= 0)
-                       close(inotify_fd);
                logging_close();
                logging_init("udevd-event");
                setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
@@ -513,6 +510,58 @@ static void handle_ctrl_msg(struct udev_ctrl *uctrl)
        udev_ctrl_msg_unref(ctrl_msg);
 }
 
+/* read inotify messages */
+static int handle_inotify(struct udev *udev)
+{
+       int nbytes, pos;
+       char *buf;
+       struct inotify_event *ev;
+       int reload_config = 0;
+
+       if ((ioctl(inotify_fd, FIONREAD, &nbytes) < 0) || (nbytes <= 0))
+               return 0;
+
+       buf = malloc(nbytes);
+       if (buf == NULL) {
+               err(udev, "error getting buffer for inotify, disable watching\n");
+               close(inotify_fd);
+               inotify_fd = -1;
+               return 0;
+       }
+
+       read(inotify_fd, buf, nbytes);
+
+       for (pos = 0, ev = (struct inotify_event *)(buf + pos); pos < nbytes; pos += sizeof(struct inotify_event) + ev->len) {
+               const char *syspath;
+
+               dbg(udev, "inotify event: %x for %d (%s)\n", ev->mask, ev->wd, ev->len ? ev->name : "*");
+
+               syspath = udev_watch_lookup(udev, ev->wd);
+               if (syspath != NULL) {
+                       dbg(udev, "inotify event: %x for %s\n", ev->mask, syspath);
+                       if (ev->mask & IN_CLOSE_WRITE) {
+                               char filename[UTIL_PATH_SIZE];
+                               int fd;
+
+                               info(udev, "device %s closed, synthesising write\n", syspath);
+                               util_strlcpy(filename, syspath, sizeof(filename));
+                               util_strlcat(filename, "/uevent", sizeof(filename));
+                               fd = open(filename, O_WRONLY);
+                               if (fd < 0 || write(fd, "change", 6) < 0)
+                                       info(udev, "error writing uevent: %m\n");
+                               close(fd);
+                       }
+                       if (ev->mask & IN_IGNORED)
+                               udev_watch_end(udev, ev->wd);
+               } else {
+                       reload_config = 1;
+               }
+       }
+
+       free (buf);
+       return reload_config;
+}
+
 static void asmlinkage sig_handler(int signum)
 {
        switch (signum) {
@@ -838,7 +887,7 @@ int main(int argc, char *argv[])
        sigaction(SIGHUP, &act, NULL);
 
        /* watch rules directory */
-       inotify_fd = inotify_init();
+       udev_watch_init(udev);
        if (inotify_fd >= 0) {
                if (udev_get_rules_path(udev) != NULL) {
                        inotify_add_watch(inotify_fd, udev_get_rules_path(udev),
@@ -857,10 +906,9 @@ int main(int argc, char *argv[])
                        inotify_add_watch(inotify_fd, filename,
                                          IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
                }
-       } else if (errno == ENOSYS)
-               info(udev, "unable to use inotify, udevd will not monitor rule files changes\n");
-       else
-               err(udev, "inotify_init failed: %m\n");
+
+               udev_watch_restore(udev);
+       }
 
        /* in trace mode run one event after the other */
        if (debug_trace) {
@@ -936,24 +984,8 @@ int main(int argc, char *argv[])
                }
 
                /* rules directory inotify watch */
-               if (inotify_poll && (inotify_poll->revents & POLLIN)) {
-                       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(udev, "error getting buffer for inotify, disable watching\n");
-                                       close(inotify_fd);
-                                       inotify_fd = -1;
-                               }
-                               read(inotify_fd, buf, nbytes);
-                               free(buf);
-                       }
-               }
+               if (inotify_poll && (inotify_poll->revents & POLLIN))
+                       reload_config = handle_inotify(udev);
 
 handle_signals:
                signal_received = 0;