udev 175
========
+A writable /run directory (tmpfs) is required now for a fully functional
+udev.
+
The default 'configure' install locations have changed. Packages for systems
with the historic / vs. /usr split need to be adapted, otherwise udev will
be installed in /usr and not work properly. Example configuration options are
int priority, const char *file, int line, const char *fn,
const char *format, ...)
__attribute__((format(printf, 6, 7)));
-const char *udev_get_rules_path(struct udev *udev);
-const char *udev_get_run_config_path(struct udev *udev);
-const char *udev_set_run_path(struct udev *udev, const char *path);
+int udev_get_rules_path(struct udev *udev, char **path[], unsigned long long *ts_usec[]);
struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value);
struct udev_list_entry *udev_get_properties_list_entry(struct udev *udev);
gid_t util_lookup_group(struct udev *udev, const char *group);
int util_resolve_subsys_kernel(struct udev *udev, const char *string,
char *result, size_t maxsize, int read_value);
+unsigned long long ts_usec(const struct timespec *ts);
unsigned long long now_usec(void);
/* libudev-selinux-private.c */
#define USEC_PER_SEC 1000000ULL
#define NSEC_PER_USEC 1000ULL
+unsigned long long ts_usec(const struct timespec *ts)
+{
+ return (unsigned long long) ts->tv_sec * USEC_PER_SEC +
+ (unsigned long long) ts->tv_nsec / NSEC_PER_USEC;
+}
+
unsigned long long now_usec(void)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
return 0;
- return (unsigned long long) ts.tv_sec * USEC_PER_SEC +
- (unsigned long long) ts.tv_nsec / NSEC_PER_USEC;
+ return ts_usec(&ts);
}
#include <errno.h>
#include <string.h>
#include <ctype.h>
+#include <time.h>
#include "libudev.h"
#include "libudev-private.h"
void *userdata;
char *sys_path;
char *dev_path;
- char *rules_path;
- char *run_config_path;
+ char *rules_path[4];
+ unsigned long long rules_path_ts[4];
+ int rules_path_count;
char *run_path;
struct udev_list properties_list;
int log_priority;
continue;
}
if (strcmp(key, "udev_run") == 0) {
- set_value(&udev->run_config_path, val);
+ set_value(&udev->run_path, val);
continue;
}
if (strcmp(key, "udev_rules") == 0) {
- set_value(&udev->rules_path, val);
+ set_value(&udev->rules_path[0], val);
+ udev->rules_path_count = 1;
continue;
}
}
if (set_value(&udev->sys_path, "/sys") == NULL)
goto err;
- if (udev->run_config_path == NULL)
- if (set_value(&udev->run_config_path, "/run/udev") == NULL)
+ if (udev->run_path == NULL)
+ if (set_value(&udev->run_path, "/run/udev") == NULL)
+ goto err;
+
+ if (udev->rules_path[0] == NULL) {
+ /* /usr/lib/udev -- system rules */
+ udev->rules_path[0] = strdup(LIBEXECDIR "/rules.d");
+ if (!udev->rules_path[0])
+ goto err;
+
+ /* /etc/udev -- local administration rules */
+ udev->rules_path[1] = strdup(SYSCONFDIR "/udev/rules.d");
+ if (!udev->rules_path[1])
goto err;
+ /* /run/udev -- runtime rules */
+ if (asprintf(&udev->rules_path[2], "%s/rules.d", udev->run_path) < 0)
+ goto err;
+
+ udev->rules_path_count = 3;
+ }
+
dbg(udev, "context %p created\n", udev);
dbg(udev, "log_priority=%d\n", udev->log_priority);
dbg(udev, "config_file='%s'\n", config_file);
dbg(udev, "dev_path='%s'\n", udev->dev_path);
dbg(udev, "sys_path='%s'\n", udev->sys_path);
- dbg(udev, "run_path='%s'\n", udev->run_config_path);
- if (udev->rules_path != NULL)
- dbg(udev, "rules_path='%s'\n", udev->rules_path);
+ dbg(udev, "run_path='%s'\n", udev->run_path);
+ dbg(udev, "rules_path='%s':'%s':'%s'\n", udev->rules_path[0], udev->rules_path[1], udev->rules_path[2]);
free(config_file);
return udev;
err:
udev_list_cleanup(&udev->properties_list);
free(udev->dev_path);
free(udev->sys_path);
- free(udev->rules_path);
+ free(udev->rules_path[0]);
+ free(udev->rules_path[1]);
+ free(udev->rules_path[2]);
free(udev->run_path);
- free(udev->run_config_path);
dbg(udev, "context %p released\n", udev);
free(udev);
}
udev_add_property(udev, "UDEV_LOG", num);
}
-const char *udev_get_rules_path(struct udev *udev)
+int udev_get_rules_path(struct udev *udev, char **path[], unsigned long long *stamp_usec[])
{
- return udev->rules_path;
+ *path = udev->rules_path;
+ if (stamp_usec)
+ *stamp_usec = udev->rules_path_ts;
+ return udev->rules_path_count;
}
/**
return udev->dev_path;
}
-const char *udev_get_run_config_path(struct udev *udev)
-{
- return udev->run_config_path;
-}
-
/**
* udev_get_run_path:
* @udev: udev library context
**/
UDEV_EXPORT const char *udev_get_run_path(struct udev *udev)
{
- if (udev->run_path != NULL)
- return udev->run_path;
-
- /* check if configured path exists */
- if (access(udev->run_config_path, F_OK) < 0) {
- char filename[UTIL_PATH_SIZE];
-
- /* fall back to /dev/.udev if that exists */
- util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev", NULL);
- if (access(filename, F_OK) >= 0)
- if (set_value(&udev->run_path, filename) != NULL)
- return udev->run_path;
- }
-
- /* use default path */
- set_value(&udev->run_path, udev->run_config_path);
- if (udev->run_path == NULL)
- return udev->run_config_path;
+ if (udev == NULL)
+ return NULL;
return udev->run_path;
}
-const char *udev_set_run_path(struct udev *udev, const char *path)
-{
- return set_value(&udev->run_path, path);
-}
-
struct udev_list_entry *udev_add_property(struct udev *udev, const char *key, const char *value)
{
if (value == NULL) {
udev_main_log(data, priority, file, line, fn, format, args);
}
+/* needs to re-instantiate the context after a reload */
static int builtin_kmod(struct udev_device *dev, int argc, char *argv[], bool test)
{
struct udev *udev = udev_device_get_udev(dev);
return EXIT_SUCCESS;
}
+/* called at udev startup */
static int builtin_kmod_init(struct udev *udev)
{
if (ctx)
return 0;
}
-static int builtin_kmod_exit(struct udev *udev)
+/* called on udev shutdown and reload request */
+static void builtin_kmod_exit(struct udev *udev)
{
ctx = kmod_unref(ctx);
info(udev, "unload module index\n");
- return 0;
+}
+
+/* called every couple of seconds during event activity; 'true' if config has changed */
+static bool builtin_kmod_validate(struct udev *udev)
+{
+ info(udev, "validate module index\n");
+ return false;
}
const struct udev_builtin udev_builtin_kmod = {
.cmd = builtin_kmod,
.init = builtin_kmod_init,
.exit = builtin_kmod_exit,
+ .validate = builtin_kmod_validate,
.help = "kernel module loader",
.run_once = false,
};
int udev_builtin_init(struct udev *udev)
{
unsigned int i;
-
- for (i = 0; i < ARRAY_SIZE(builtins); i++)
- if (builtins[i]->init)
- builtins[i]->init(udev);
- return 0;
+ int err;
+
+ for (i = 0; i < ARRAY_SIZE(builtins); i++) {
+ if (builtins[i]->init) {
+ err = builtins[i]->init(udev);
+ if (err < 0)
+ break;
+ }
+ }
+ return err;
}
-int udev_builtin_exit(struct udev *udev)
+void udev_builtin_exit(struct udev *udev)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++)
if (builtins[i]->exit)
builtins[i]->exit(udev);
- return 0;
}
-int udev_builtin_list(struct udev *udev)
+bool udev_builtin_validate(struct udev *udev)
+{
+ unsigned int i;
+ bool change = false;
+
+ for (i = 0; i < ARRAY_SIZE(builtins); i++)
+ if (builtins[i]->validate)
+ if (builtins[i]->validate(udev))
+ change = true;
+ return change;
+}
+
+void udev_builtin_list(struct udev *udev)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(builtins); i++)
fprintf(stderr, " %-12s %s\n", builtins[i]->name, builtins[i]->help);
- return 0;
}
const char *udev_builtin_name(enum udev_builtin_cmd cmd)
struct udev_list file_list;
struct udev_list_entry *file_loop;
struct token end_token;
+ char **s;
rules = calloc(1, sizeof(struct udev_rules));
if (rules == NULL)
memset(rules->trie_nodes, 0x00, sizeof(struct trie_node));
rules->trie_nodes_cur = 1;
- if (udev_get_rules_path(udev) == NULL) {
- char filename[UTIL_PATH_SIZE];
-
- /* /usr/lib/udev -- system rules */
- add_matching_files(udev, &file_list, LIBEXECDIR "/rules.d", ".rules");
-
- /* /etc/udev -- local administration rules */
- add_matching_files(udev, &file_list, SYSCONFDIR "/udev/rules.d", ".rules");
-
- /* /run/udev -- runtime rules */
- util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/rules.d", NULL);
- add_matching_files(udev, &file_list, filename, ".rules");
- } else {
- /* custom rules files location for testing */
- add_matching_files(udev, &file_list, udev_get_rules_path(udev), ".rules");
- }
+ for (udev_get_rules_path(udev, &s, NULL); *s != NULL; s++)
+ add_matching_files(udev, &file_list, *s, ".rules");
/* add all filenames to the string buffer */
udev_list_entry_foreach(file_loop, udev_list_get_entry(&file_list)) {
int (*cmd)(struct udev_device *dev, int argc, char *argv[], bool test);
const char *help;
int (*init)(struct udev *udev);
- int (*exit)(struct udev *udev);
+ void (*exit)(struct udev *udev);
+ bool (*validate)(struct udev *udev);
bool run_once;
};
extern const struct udev_builtin udev_builtin_blkid;
extern const struct udev_builtin udev_builtin_kmod;
extern const struct udev_builtin udev_builtin_path_id;
extern const struct udev_builtin udev_builtin_usb_id;
-int udev_builtin_load(struct udev *udev);
-int udev_builtin_unload(struct udev *udev);
+int udev_builtin_init(struct udev *udev);
+void udev_builtin_exit(struct udev *udev);
enum udev_builtin_cmd udev_builtin_lookup(const char *command);
const char *udev_builtin_name(enum udev_builtin_cmd cmd);
bool udev_builtin_run_once(enum udev_builtin_cmd cmd);
int udev_builtin_run(struct udev_device *dev, enum udev_builtin_cmd cmd, const char *command, bool test);
-int udev_builtin_list(struct udev *udev);
+void udev_builtin_list(struct udev *udev);
int udev_builtin_add_property(struct udev_device *dev, bool test, const char *key, const char *val);
/* udev logging */
struct udev_device *dev;
ev = (struct inotify_event *)(buf + pos);
- if (ev->len) {
- const char *s;
-
- info(udev, "inotify event: %x for %s\n", ev->mask, ev->name);
- s = strstr(ev->name, ".rules");
- if (s == NULL)
- continue;
- if (strlen(s) != strlen(".rules"))
- continue;
- reload = true;
- continue;
- }
-
dev = udev_watch_lookup(udev, ev->wd);
if (dev != NULL) {
info(udev, "inotify event: %x for %s\n", ev->mask, udev_device_get_devnode(dev));
return 0;
}
+static bool check_rules_timestamp(struct udev *udev)
+{
+ char **p;
+ unsigned long long *stamp_usec;
+ int i, n;
+ bool changed = false;
+
+ n = udev_get_rules_path(udev, &p, &stamp_usec);
+ for (i = 0; i < n; i++) {
+ struct stat stats;
+
+ if (stat(p[i], &stats) < 0)
+ continue;
+
+ if (stamp_usec[i] == ts_usec(&stats.st_mtim))
+ continue;
+
+ /* first check */
+ if (stamp_usec[i] != 0) {
+ info(udev, "reload - timestamp of '%s' changed\n", p[i]);
+ changed = true;
+ }
+
+ /* update timestamp */
+ stamp_usec[i] = ts_usec(&stats.st_mtim);
+ }
+
+ return changed;
+}
+
int main(int argc, char *argv[])
{
struct udev *udev;
int fd_worker = -1;
struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker;
struct udev_ctrl_connection *ctrl_conn = NULL;
+ char **s;
int rc = 1;
udev = udev_new();
info(udev, "version %s\n", VERSION);
udev_selinux_init(udev);
- /* make sure, that our runtime dir exists and is writable */
- if (utimensat(AT_FDCWD, udev_get_run_config_path(udev), NULL, 0) < 0) {
- /* try to create our own subdirectory, do not create parent directories */
- mkdir(udev_get_run_config_path(udev), 0755);
-
- if (utimensat(AT_FDCWD, udev_get_run_config_path(udev), NULL, 0) >= 0) {
- /* directory seems writable now */
- udev_set_run_path(udev, udev_get_run_config_path(udev));
- } else {
- /* fall back to /dev/.udev */
- char filename[UTIL_PATH_SIZE];
-
- util_strscpyl(filename, sizeof(filename), udev_get_dev_path(udev), "/.udev", NULL);
- if (udev_set_run_path(udev, filename) == NULL)
- goto exit;
- mkdir(udev_get_run_path(udev), 0755);
- err(udev, "error: runtime directory '%s' not writable, for now falling back to '%s'",
- udev_get_run_config_path(udev), udev_get_run_path(udev));
- }
- }
- /* relabel runtime dir only if it resides below /dev */
- if (strncmp(udev_get_run_path(udev), udev_get_dev_path(udev), strlen(udev_get_dev_path(udev))) == 0)
- udev_selinux_lsetfilecon(udev, udev_get_run_path(udev), 0755);
- info(udev, "runtime dir '%s'\n", udev_get_run_path(udev));
-
for (;;) {
int option;
rc = 4;
goto exit;
}
-
- if (udev_get_rules_path(udev) != NULL) {
- inotify_add_watch(fd_inotify, udev_get_rules_path(udev),
- IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
- } else {
- char filename[UTIL_PATH_SIZE];
- struct stat statbuf;
-
- inotify_add_watch(fd_inotify, LIBEXECDIR "/rules.d",
- IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
- inotify_add_watch(fd_inotify, SYSCONFDIR "/udev/rules.d",
- IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
-
- /* watch dynamic rules directory */
- util_strscpyl(filename, sizeof(filename), udev_get_run_path(udev), "/rules.d", NULL);
- if (stat(filename, &statbuf) != 0) {
- util_create_path(udev, filename);
- mkdir(filename, 0755);
- }
- inotify_add_watch(fd_inotify, filename,
- IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
- }
udev_watch_restore(udev);
/* block and listen to all signals on signalfd */
udev_list_node_init(&worker_list);
for (;;) {
+ static unsigned long long last_usec;
struct epoll_event ev[8];
int fdcount;
int timeout;
is_ctrl = true;
}
+ /* check for changed config, every 3 seconds at most */
+ if ((now_usec() - last_usec) > 3 * 1000 * 1000) {
+ if (check_rules_timestamp(udev))
+ reload = true;
+ if (udev_builtin_validate(udev))
+ reload = true;
+
+ last_usec = now_usec();
+ }
+
+ /* reload requested, HUP signal received, rules changed, builtin changed */
+ if (reload) {
+ worker_kill(udev, 0);
+ rules = udev_rules_unref(rules);
+ udev_builtin_exit(udev);
+ reload = 0;
+ }
+
/* event has finished */
if (is_worker)
worker_returned(fd_worker);
if (udev_exit)
continue;
- /* device node and rules directory inotify watch */
+ /* device node watch */
if (is_inotify)
handle_inotify(udev);
*/
if (is_ctrl)
ctrl_conn = handle_ctrl_msg(udev_ctrl);
-
- /* rules changed, set by inotify or a HUP signal */
- if (reload) {
- worker_kill(udev, 0);
- rules = udev_rules_unref(rules);
- udev_builtin_exit(udev);
- reload = 0;
- }
}
rc = EXIT_SUCCESS;