udevd.c revision 70a93737ecd613d9b5855cb5d7c80829b966ec6a
/*
* 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"
static struct udev_rules *rules;
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 int children;
static bool arg_debug = false;
static int arg_daemonize = false;
static int arg_resolve_names = 1;
static int arg_children_max;
static int arg_exec_delay;
static sigset_t sigmask_orig;
static UDEV_LIST(event_list);
static UDEV_LIST(worker_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;
int exitcode;
unsigned long long int delaying_seqnum;
unsigned long long int seqnum;
const char *devpath;
const char *devpath_old;
int ifindex;
bool is_block;
};
}
enum worker_state {
};
struct worker {
struct udev_list_node node;
int refcount;
struct udev_monitor *monitor;
enum worker_state state;
bool event_warned;
};
/* passed from worker to main process */
struct worker_message {
int exitcode;
};
}
}
return worker;
}
children--;
}
return;
}
}
}
struct udev_monitor *worker_monitor;
/* listen for new events */
if (worker_monitor == NULL)
return;
/* allow the main daemon netlink address to send devices to the worker */
return;
}
/* worker + event reference */
switch (pid) {
case 0: {
int fd_monitor;
int rc = EXIT_SUCCESS;
/* take initial device from queue */
sigfillset(&mask);
if (fd_signal < 0) {
rc = 2;
goto out;
}
if (fd_ep < 0) {
rc = 3;
goto out;
}
rc = 4;
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;
int err = 0;
if (udev_event == NULL) {
rc = 5;
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));
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 (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:
/* close monitor, but keep address around */
worker->event_warned = false;
children++;
break;
}
}
struct udev_list_node *loop;
continue;
if (count < 0) {
continue;
}
worker->event_warned = false;
return;
}
if (children >= arg_children_max) {
if (arg_children_max > 1)
return;
}
/* start new worker and pass initial device */
}
return -1;
return 0;
}
struct udev_list_node *loop;
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;
}
}
static void worker_returned(int fd_worker) {
for (;;) {
struct worker_message msg;
struct udev_list_node *loop;
if (size != sizeof(struct worker_message))
break;
/* lookup worker who sent the signal */
continue;
/* worker returned */
}
break;
}
}
}
/* receive the udevd message from userspace */
struct udev_ctrl_connection *ctrl_conn;
const char *str;
int i;
goto out;
goto out;
if (i >= 0) {
log_debug("udevd message (SET_LOG_LEVEL) received, log_priority=%i", i);
}
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 {
}
}
}
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");
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 */
}
out:
return udev_ctrl_connection_unref(ctrl_conn);
}
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 0;
}
FOREACH_INOTIFY_EVENT(e, buffer, l) {
struct udev_device *dev;
if (!dev)
continue;
if (e->mask & IN_CLOSE_WRITE)
else if (e->mask & IN_IGNORED)
}
return 0;
}
switch (signo) {
case SIGINT:
case SIGTERM:
udev_exit = true;
break;
case SIGCHLD:
for (;;) {
int status;
if (pid <= 0)
break;
continue;
if (WEXITSTATUS(status) == 0)
else
} else if (WIFSIGNALED(status)) {
} else if (WIFSTOPPED(status)) {
} else if (WIFCONTINUED(status)) {
} else {
}
/* delete state from disk */
/* forward kernel event without amending it */
/* drop reference taken for state 'running' */
}
}
break;
}
}
break;
case SIGHUP:
reload = true;
break;
}
}
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");
}
}
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
*/
size_t l;
int r;
r = proc_cmdline(&line);
if (r < 0) {
log_warning_errno(r, "Failed to read /proc/cmdline, ignoring: %m");
return;
}
if (!s)
break;
/* accept the same options for the initrd, prefixed with "rd." */
opt = s + 3;
else
opt = s;
int prio;
if (r < 0)
if (r < 0)
if (r < 0) {
break;
}
}
free(s);
}
}
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 rc = 1, r;
goto exit;
log_open();
if (r <= 0)
goto exit;
if (arg_debug)
if (getuid() != 0) {
log_error("root privileges required");
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 */
log_error("error taking over udev control socket");
rc = 1;
goto exit;
}
log_error("error taking over netlink socket");
rc = 3;
goto exit;
}
/* get our own cgroup, we regularly kill everything udev has left behind */
udev_cgroup = NULL;
} else {
/* open control and netlink socket */
log_error("error initializing udev control socket");
rc = 1;
goto exit;
}
log_error("error initializing netlink socket");
rc = 3;
goto exit;
}
}
if (udev_monitor_enable_receiving(monitor) < 0) {
log_error("error binding netlink socket");
rc = 3;
goto exit;
}
if (udev_ctrl_enable_receiving(udev_ctrl) < 0) {
log_error("error binding udev control socket");
rc = 1;
goto exit;
}
log_error("error reading rules");
goto exit;
}
if (rc < 0)
if (arg_daemonize) {
switch (pid) {
case 0:
break;
case -1:
rc = 4;
goto exit;
default:
rc = EXIT_SUCCESS;
goto exit_daemonize;
}
setsid();
} else {
}
if (arg_children_max <= 0) {
arg_children_max = 8;
}
}
if (fd_inotify < 0) {
log_error("error initializing inotify");
rc = 4;
goto exit;
}
/* block and listen to all signals on signalfd */
sigfillset(&mask);
if (fd_signal < 0) {
log_error("error creating signalfd");
rc = 5;
goto exit;
}
/* unnamed socket from workers to the main daemon */
log_error("error creating socketpair");
rc = 6;
goto exit;
}
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 */
/* 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) {
struct udev_list_node *loop;
/* 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");
}
/* check for hanging events */
continue;
log_error("seq %llu '%s' killed", udev_device_get_seqnum(worker->event->dev), worker->event->devpath);
} else if (!worker->event_warned) {
worker->event_warned = true;
}
}
}
}
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) {
reload = false;
}
/* event has finished */
if (is_worker)
if (is_netlink) {
struct udev_device *dev;
if (dev) {
if (event_queue_insert(dev) < 0)
}
}
/* start new events */
}
if (is_signal) {
struct signalfd_siginfo fdsi;
if (size == sizeof(struct signalfd_siginfo))
}
/* we are shutting down, the events below are not handled anymore */
if (udev_exit)
continue;
/* device node watch */
if (is_inotify) {
/*
* 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.
*
* Before we go ahead and potentially tell settle that the
* queue is empty, lets loop one more time to update the
* queue state again before deciding.
*/
continue;
}
/* tell settle that we are busy or idle, this needs to be before the
* PING handling
*/
/*
* 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)
}
rc = EXIT_SUCCESS;
exit:
if (fd_ep >= 0)
if (fd_signal >= 0)
if (worker_watch[READ_END] >= 0)
if (worker_watch[WRITE_END] >= 0)
log_close();
return rc;
}