udevd.c revision 799a108c074bb3d6b4b44a9f2b69342cd60bb137
/*
* Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org>
* Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca>
* Copyright (C) 2009 Canonical Ltd.
* Copyright (C) 2009 Scott James Remnant <scott@netsplit.com>
*
* 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 <stddef.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <fcntl.h>
#include <getopt.h>
#include <sys/signalfd.h>
#include "sd-daemon.h"
#include "rtnl-util.h"
#include "cgroup-util.h"
#include "dev-setup.h"
#include "fileio.h"
#include "selinux-util.h"
#include "udev.h"
#include "udev-util.h"
#include "formats-util.h"
#include "hashmap.h"
static struct udev_rules *rules;
static struct udev_ctrl_connection *udev_ctrl_conn;
static struct udev_monitor *monitor;
static int fd_signal = -1;
static int fd_ep = -1;
static int fd_inotify = -1;
static bool stop_exec_queue;
static bool reload;
static bool arg_debug = false;
static int arg_daemonize = false;
static int arg_resolve_names = 1;
static unsigned arg_children_max;
static int arg_exec_delay;
static sigset_t sigmask_orig;
static UDEV_LIST(event_list);
static char *udev_cgroup;
static struct udev_list properties_list;
static bool udev_exit;
enum event_state {
};
struct event {
struct udev_list_node node;
struct udev_device *dev;
struct udev_device *dev_kernel;
enum event_state state;
unsigned long long int delaying_seqnum;
unsigned long long int seqnum;
const char *devpath;
const char *devpath_old;
int ifindex;
bool is_block;
bool warned;
};
}
enum worker_state {
};
struct worker {
struct udev_list_node node;
int refcount;
struct udev_monitor *monitor;
enum worker_state state;
};
/* passed from worker to main process */
struct worker_message {
};
if (!event)
return;
}
if (!worker)
return;
}
static void workers_free(void) {
Iterator i;
}
static int worker_new(struct worker **ret, struct udev *udev, struct udev_monitor *worker_monitor, pid_t pid) {
int r;
if (!worker)
return -ENOMEM;
/* close monitor, but keep address around */
if (r < 0)
return r;
if (r < 0)
return r;
return 0;
}
}
/* listen for new events */
if (worker_monitor == NULL)
return;
/* allow the main daemon netlink address to send devices to the worker */
switch (pid) {
case 0: {
int fd_monitor;
int r = 0;
/* take initial device from queue */
workers_free();
sigfillset(&mask);
if (fd_signal < 0) {
goto out;
}
if (fd_ep < 0) {
goto out;
}
goto out;
}
/* request TERM signal if parent exits */
/* reset OOM score, we only protect the main daemon */
for (;;) {
struct udev_event *udev_event;
struct worker_message msg;
int fd_lock = -1;
if (udev_event == NULL) {
r = -ENOMEM;
goto out;
}
if (arg_exec_delay > 0)
/*
* Take a shared lock on the device node; this establishes
* a concept of device "ownership" to serialize device
* access. External processes holding an exclusive lock will
* cause udev to skip the event handling; in the case udev
* acquired the lock, the external process can block until
* udev has finished its event handling.
*/
struct udev_device *d = dev;
d = udev_device_get_parent(d);
if (d) {
log_debug_errno(errno, "Unable to flock(%s), skipping event handling: %m", udev_device_get_devnode(d));
r = -EAGAIN;
goto skip;
}
}
}
/* needed for renaming netifs */
/* apply rules, create node, symlinks */
&sigmask_orig);
&sigmask_orig);
if (udev_event->rtnl)
/* in case rtnl was initialized */
if (udev_event->inotify_watch) {
}
/* send processed event back to libudev listeners */
skip:
/* send udevd the result of the event execution */
if (r < 0)
if (udev_event->sigterm) {
goto out;
}
/* wait for more device messages from main udevd, or term signal */
int fdcount;
int i;
if (fdcount < 0) {
continue;
goto out;
}
for (i = 0; i < fdcount; i++) {
break;
struct signalfd_siginfo fdsi;
if (size != sizeof(struct signalfd_siginfo))
continue;
case SIGTERM:
goto out;
}
}
}
}
}
out:
log_close();
}
case -1:
break;
default:
{
int r;
if (r < 0)
return;
break;
}
}
}
Iterator i;
continue;
if (count < 0) {
continue;
}
return;
}
if (arg_children_max > 1)
return;
}
/* start new worker and pass initial device */
}
return -1;
return 0;
}
static void worker_kill(void) {
Iterator i;
continue;
}
}
/* lookup event for identical, parent, child device */
struct udev_list_node *loop;
/* check if queue contains events we depend on */
/* we already found a later event, earlier can not block us, no need to check again */
continue;
/* event we checked earlier still exists, no need to check again */
return true;
/* found ourself, no later event can block us */
break;
if (major(event->devnum) != 0 && event->devnum == loop_event->devnum && event->is_block == loop_event->is_block)
return true;
/* check network device ifindex */
return true;
/* check our old name */
return true;
}
/* compare devpath */
/* one devpath is contained in the other? */
continue;
/* identical device event found */
if (major(event->devnum) != 0 && (event->devnum != loop_event->devnum || event->is_block != loop_event->is_block))
continue;
continue;
return true;
}
/* parent device event found */
return true;
}
/* child device event found */
return true;
}
/* no matching device */
continue;
}
return false;
}
struct udev_list_node *loop;
continue;
/* do not start event if parent or child event is still running */
if (is_devpath_busy(event))
continue;
}
}
continue;
}
}
for (;;) {
struct worker_message msg;
};
union {
} control = {};
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
if (size < 0) {
return 1;
} else if (size != sizeof(struct worker_message)) {
continue;
}
}
continue;
}
/* lookup worker who sent the signal */
if (!worker) {
continue;
}
/* worker returned */
}
return 1;
}
struct udev_monitor *m = userdata;
struct udev_device *dev;
int r;
assert(m);
if (dev) {
r = event_queue_insert(dev);
if (r < 0)
}
return 1;
}
static void event_queue_update(void) {
int r;
if (!udev_list_node_is_empty(&event_list)) {
if (r < 0)
log_warning_errno(r, "could not touch /run/udev/queue: %m");
} else {
log_warning("could not unlink /run/udev/queue: %m");
}
}
/* receive the udevd message from userspace */
const char *str;
int i;
if (!ctrl_conn)
return 1;
if (!ctrl_msg)
return 1;
if (i >= 0) {
log_debug("udevd message (SET_LOG_LEVEL) received, log_priority=%i", i);
worker_kill();
}
if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) {
log_debug("udevd message (STOP_EXEC_QUEUE) received");
stop_exec_queue = true;
}
if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) {
log_debug("udevd message (START_EXEC_QUEUE) received");
stop_exec_queue = false;
}
if (udev_ctrl_get_reload(ctrl_msg) > 0) {
log_debug("udevd message (RELOAD) received");
reload = true;
}
char *key;
char *val;
val[0] = '\0';
if (val[0] == '\0') {
} else {
}
} else {
}
}
worker_kill();
}
if (i >= 0) {
log_debug("udevd message (SET_MAX_CHILDREN) received, children_max=%i", i);
arg_children_max = i;
}
if (udev_ctrl_get_ping(ctrl_msg) > 0) {
log_debug("udevd message (SYNC) received");
/* tell settle that we are busy or idle, this needs to be before the
* PING handling
*/
}
if (udev_ctrl_get_exit(ctrl_msg) > 0) {
log_debug("udevd message (EXIT) received");
udev_exit = true;
/* keep reference to block the client until we exit */
}
return 1;
}
char filename[UTIL_PATH_SIZE];
int r;
bool part_table_read = false;
bool has_partitions = false;
int fd;
struct udev_list_entry *item;
/*
* Try to re-read the partition table. This only succeeds if
* none of the devices is busy. The kernel returns 0 if no
* partition table is found, and we will not get an event for
* the disk.
*/
if (fd >= 0) {
if (r >= 0)
if (r >= 0)
part_table_read = true;
}
/* search for partitions */
e = udev_enumerate_new(udev);
if (!e)
return -ENOMEM;
r = udev_enumerate_add_match_parent(e, dev);
if (r < 0)
return r;
r = udev_enumerate_add_match_subsystem(e, "block");
if (r < 0)
return r;
r = udev_enumerate_scan_devices(e);
if (r < 0)
return r;
if (!d)
continue;
continue;
has_partitions = true;
break;
}
/*
* We have partitions and re-read the table, the kernel already sent
* partitions.
*/
if (part_table_read && has_partitions)
return 0;
/*
* We have partitions but re-reading the partition table did not
* work, synthesize "change" for the disk and all partitions.
*/
if (!d)
continue;
continue;
log_debug("device %s closed, synthesising partition '%s' 'change'",
}
return 0;
}
return 0;
}
union inotify_event_buffer buffer;
struct inotify_event *e;
ssize_t l;
if (l < 0) {
return 1;
}
FOREACH_INOTIFY_EVENT(e, buffer, l) {
if (!dev)
continue;
if (e->mask & IN_CLOSE_WRITE) {
/* settle might be waiting on us to determine the queue
* state. If we just handled an inotify event, we might have
* generated a "change" event, but we won't have queued up
* the resultant uevent yet. Do that.
*/
} else if (e->mask & IN_IGNORED)
}
return 1;
}
udev_exit = true;
return 1;
}
static int on_request_reload(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
reload = true;
return 1;
}
for (;;) {
int status;
if (pid <= 0)
return 1;
if (!worker) {
return 1;
}
if (WEXITSTATUS(status) == 0)
else
} else if (WIFSIGNALED(status)) {
log_warning("worker ["PID_FMT"] terminated by signal %i (%s)", pid, WTERMSIG(status), strsignal(WTERMSIG(status)));
} else if (WIFSTOPPED(status)) {
return 1;
} else if (WIFCONTINUED(status)) {
return 1;
} else
/* delete state from disk */
/* forward kernel event without amending it */
}
}
}
return 1;
}
int fd, n;
n = sd_listen_fds(true);
if (n <= 0)
return -1;
if (ctrl >= 0)
return -1;
continue;
}
if (netlink >= 0)
return -1;
continue;
}
return -1;
}
return -1;
return 0;
}
/*
* read the kernel command line, in case we need to get into debug mode
* udev.log-priority=<level> syslog priority
* udev.children-max=<number of workers> events are fully serialized if set to 1
* udev.exec-delay=<number of seconds> delay execution of every executed program
* udev.event-timeout=<number of seconds> seconds to wait before terminating an event
*/
int r;
if (!value)
return 0;
else
return 0;
int prio;
if (r < 0)
if (r < 0)
if (r < 0)
else {
}
}
return 0;
}
static void help(void) {
printf("%s [OPTIONS...]\n\n"
"Manages devices.\n\n"
" -h --help Print this message\n"
" --version Print version of the program\n"
" --daemon Detach and run in the background\n"
" --debug Enable debug output\n"
" --children-max=INT Set maximum number of workers\n"
" --exec-delay=SECONDS Seconds to wait before executing RUN=\n"
" --event-timeout=SECONDS Seconds to wait before terminating an event\n"
" --resolve-names=early|late|never\n"
" When to resolve users and groups\n"
}
{}
};
int c;
int r;
switch (c) {
case 'd':
arg_daemonize = true;
break;
case 'c':
if (r < 0)
break;
case 'e':
if (r < 0)
break;
case 't':
if (r < 0)
else {
}
break;
case 'D':
arg_debug = true;
break;
case 'N':
arg_resolve_names = 1;
arg_resolve_names = 0;
arg_resolve_names = -1;
} else {
log_error("resolve-names must be early, late or never");
return 0;
}
break;
case 'h':
help();
return 0;
case 'V':
return 0;
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
}
return 1;
}
int fd_ctrl = -1;
int fd_netlink = -1;
int fd_worker = -1;
int r = 0, one = 1;
if (!udev) {
goto exit;
}
log_open();
if (r <= 0)
goto exit;
if (r < 0)
log_warning_errno(r, "failed to parse kernel command line, ignoring: %m");
if (arg_debug)
if (getuid() != 0) {
goto exit;
}
r = mac_selinux_init("/dev");
if (r < 0) {
log_error_errno(r, "could not initialize labelling: %m");
goto exit;
}
r = chdir("/");
if (r < 0) {
goto exit;
}
umask(022);
goto exit;
}
/* before opening new files, make sure std{in,out,err} fds are in a sane state */
if (arg_daemonize) {
int fd;
if (fd >= 0) {
if (write(STDOUT_FILENO, 0, 0) < 0)
if (write(STDERR_FILENO, 0, 0) < 0)
if (fd > STDERR_FILENO)
} else {
}
}
/* get control and netlink socket from systemd */
if (!udev_ctrl) {
goto exit;
}
if (!monitor) {
goto exit;
}
/* get our own cgroup, we regularly kill everything udev has left behind */
udev_cgroup = NULL;
} else {
/* open control and netlink socket */
if (!udev_ctrl) {
goto exit;
}
if (!monitor) {
goto exit;
}
}
if (udev_monitor_enable_receiving(monitor) < 0) {
goto exit;
}
if (udev_ctrl_enable_receiving(udev_ctrl) < 0) {
goto exit;
}
if (!rules) {
goto exit;
}
if (r < 0)
log_error_errno(r, "failed to apply permissions on static device nodes: %m");
if (arg_daemonize) {
switch (pid) {
case 0:
break;
case -1:
goto exit;
default:
goto exit_daemonize;
}
setsid();
} else {
}
if (arg_children_max == 0) {
arg_children_max = 8;
}
}
if (fd_inotify < 0) {
goto exit;
}
/* block and listen to all signals on signalfd */
sigfillset(&mask);
if (fd_signal < 0) {
goto exit;
}
/* unnamed socket from workers to the main daemon */
goto exit;
}
if (r < 0)
if (fd_ep < 0) {
goto exit;
}
goto exit;
}
for (;;) {
int fdcount;
int timeout;
int i;
if (udev_exit) {
/* close sources of new events and discard buffered events */
if (fd_ctrl >= 0) {
fd_ctrl = -1;
}
}
if (fd_inotify >= 0) {
fd_inotify = -1;
}
/* discard queued events and kill workers */
worker_kill();
/* exit after all has cleaned up */
break;
/* timeout at exit for workers to finish */
/* we are idle */
timeout = -1;
/* cleanup possible left-over processes in our cgroup */
if (udev_cgroup)
} else {
/* kill idle or hanging workers */
}
/* tell settle that we are busy or idle */
if (fdcount < 0)
continue;
if (fdcount == 0) {
Iterator j;
/* timeout */
if (udev_exit) {
log_error("timeout, giving up waiting for workers to finish");
break;
}
/* kill idle workers */
if (udev_list_node_is_empty(&event_list)) {
log_debug("cleanup idle workers");
worker_kill();
}
/* check for hanging events */
continue;
}
}
}
}
for (i = 0; i < fdcount; i++) {
is_worker = true;
is_netlink = true;
is_signal = true;
is_inotify = true;
is_ctrl = true;
}
/* check for changed config, every 3 seconds at most */
reload = true;
if (udev_builtin_validate(udev))
reload = true;
}
/* reload requested, HUP signal received, rules changed, builtin changed */
if (reload) {
worker_kill();
reload = false;
}
/* event has finished */
if (is_worker)
/* uevent from kernel */
if (is_netlink)
/* start new events */
}
if (is_signal) {
struct signalfd_siginfo fdsi;
if (size == sizeof(struct signalfd_siginfo)) {
case SIGINT:
case SIGTERM:
break;
case SIGHUP:
break;
case SIGCHLD:
break;
}
}
}
/* we are shutting down, the events below are not handled anymore */
if (udev_exit)
continue;
/* device node watch */
if (is_inotify)
/*
* This needs to be after the inotify handling, to make sure,
* that the ping is send back after the possibly generated
* "change" events by the inotify device node watch.
*
* A single time we may receive a client connection which we need to
* keep open to block the client. It will be closed right before we
* exit.
*/
if (is_ctrl)
}
exit:
if (fd_ep >= 0)
workers_free();
if (fd_signal >= 0)
if (worker_watch[READ_END] >= 0)
if (worker_watch[WRITE_END] >= 0)
log_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}