mirror of
https://github.com/systemd/systemd.git
synced 2024-12-23 21:35:11 +03:00
udevd: optionally watch device nodes with inotify
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.
This commit is contained in:
parent
10b2d011e7
commit
bd284db142
2
NEWS
2
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
|
||||
========
|
||||
|
@ -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 \
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
236
udev/udev-watch.c
Normal file
236
udev/udev-watch.c
Normal file
@ -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;
|
||||
}
|
16
udev/udev.h
16
udev/udev.h
@ -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);
|
||||
|
@ -457,6 +457,13 @@
|
||||
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>
|
||||
|
84
udev/udevd.c
84
udev/udevd.c
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user