udevd.c revision b79aacbff61e77c5fa579e30f7c6fc7f2d67e9e4
/*
* 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 "sd-event.h"
#include "event-util.h"
#include "rtnl-util.h"
#include "cgroup-util.h"
#include "process-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 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;
typedef struct Manager {
struct udev_list_node events;
char *cgroup;
struct udev_rules *rules;
struct udev_list properties;
struct udev_monitor *monitor;
struct udev_ctrl_connection *ctrl_conn_blocking;
int fd_inotify;
int worker_watch[2];
bool stop_exec_queue:1;
bool exit:1;
} Manager;
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;
};
}
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 {
};
int r;
if (!event)
return;
/* only clean up the queue from the process that created it */
if (r < 0)
}
}
}
if (!worker)
return;
}
Iterator i;
}
static int worker_new(struct worker **ret, Manager *manager, 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;
}
return 1;
}
log_warning("seq %llu '%s' is taking a long time", udev_device_get_seqnum(event->dev), event->devpath);
return 1;
}
sd_event *e;
int r;
if (r < 0)
return;
}
if (!manager)
return;
}
static int worker_send_message(int fd) {
struct worker_message message = {};
}
/* 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 */
unsetenv("NOTIFY_SOCKET");
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;
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 */
&manager->sigmask_orig);
&manager->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)
log_error_errno(r, "failed to send result of seq %llu to main daemon: %m",
/* 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 */
}
int r;
/* only one process can add events to the queue */
if (!event)
return -ENOMEM;
if (r < 0)
log_warning_errno(r, "could not touch /run/udev/queue: %m");
}
return 0;
}
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;
}
return 1;
}
int r;
sd_notify(false,
"STOPPING=1\n"
"STATUS=Starting shutdown...");
/* close sources of new events and discard buffered events */
/* discard queued events and kill workers */
if (r < 0)
return;
if (r < 0)
return;
}
/* reload requested, HUP signal received, rules changed, builtin changed */
sd_notify(false,
"RELOADING=1\n"
"STATUS=Flushing configuration...");
sd_notify(false,
"READY=1\n"
"STATUS=Processing...");
}
struct udev_list_node *loop;
int r;
return;
if (r >= 0) {
/* check for changed config, every 3 seconds at most */
}
}
return;
}
continue;
/* do not start event if parent or child event is still running */
continue;
}
}
continue;
}
}
for (;;) {
struct worker_message msg;
};
union {
} control = {};
.msg_iovlen = 1,
.msg_control = &control,
.msg_controllen = sizeof(control),
};
if (size < 0) {
continue;
/* nothing more to read */
break;
} else if (size != sizeof(struct worker_message)) {
continue;
}
}
continue;
}
/* lookup worker who sent the signal */
if (!worker) {
continue;
}
/* worker returned */
}
/* we have free workers, try to schedule events */
return 1;
}
struct udev_device *dev;
int r;
if (dev) {
if (r < 0)
else
/* we have fresh events, try to schedule them */
}
return 1;
}
/* 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);
}
if (udev_ctrl_get_stop_exec_queue(ctrl_msg) > 0) {
log_debug("udevd message (STOP_EXEC_QUEUE) received");
manager->stop_exec_queue = true;
}
if (udev_ctrl_get_start_exec_queue(ctrl_msg) > 0) {
log_debug("udevd message (START_EXEC_QUEUE) received");
manager->stop_exec_queue = false;
}
if (udev_ctrl_get_reload(ctrl_msg) > 0) {
log_debug("udevd message (RELOAD) received");
}
if (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");
/* keep reference to block the client until we exit
TODO: deal with several blocking exit requests */
}
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;
}
return 1;
}
return 1;
}
for (;;) {
int status;
if (pid <= 0)
break;
if (!worker) {
continue;
}
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)) {
continue;
} else if (WIFCONTINUED(status)) {
continue;
} else
/* delete state from disk */
/* forward kernel event without amending it */
}
}
}
/* we can start new workers, try to schedule events */
return 1;
}
int r;
/* no pending events */
/* there are idle workers */
log_debug("cleanup idle workers");
} else {
/* we are idle */
if (r < 0)
return r;
/* cleanup possible left-over processes in our cgroup */
}
}
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 r;
if (!manager)
return log_oom();
if (r < 0)
return 0;
}
if (r >= 0) {
/* get control and netlink socket from systemd */
/* get our own cgroup, we regularly kill everything udev has left behind */
if (r < 0)
log_warning_errno(r, "failed to get cgroup: %m");
} else {
/* open control and netlink socket */
}
if (r < 0)
if (r < 0)
/* unnamed socket from workers to the main daemon */
if (r < 0)
if (r < 0)
if (manager->fd_inotify < 0)
/* block and listen to all signals on signalfd */
sigfillset(&mask);
if (r < 0)
return log_error_errno(r, "error creating sigint event source: %m");
if (r < 0)
return log_error_errno(r, "error creating sigterm event source: %m");
if (r < 0)
return log_error_errno(r, "error creating sighup event source: %m");
if (r < 0)
return log_error_errno(r, "error creating sigchld event source: %m");
if (r < 0)
return log_error_errno(r, "error creating watchdog event source: %m");
if (r < 0)
return log_error_errno(r, "error creating ctrl event source: %m");
/* This needs to be after the inotify and uevent handling, to make sure
* that the ping is send back after fully processing the pending uevents
* (including the synthetic ones we may create due to inotify events).
*/
if (r < 0)
return log_error_errno(r, "cold not set IDLE event priority for ctrl event source: %m");
r = sd_event_add_io(manager->event, &manager->inotify_event, manager->fd_inotify, EPOLLIN, on_inotify, manager);
if (r < 0)
return log_error_errno(r, "error creating inotify event source: %m");
r = sd_event_add_io(manager->event, &manager->uevent_event, fd_uevent, EPOLLIN, on_uevent, manager);
if (r < 0)
return log_error_errno(r, "error creating uevent event source: %m");
if (r < 0)
return log_error_errno(r, "error creating worker event source: %m");
if (r < 0)
return log_error_errno(r, "error creating post event source: %m");
return 0;
}
int r;
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;
}
if (arg_children_max == 0) {
arg_children_max = 8;
}
}
/* before opening new files, make sure std{in,out,err} fds are in a sane state */
if (arg_daemonize) {
int fd;
if (fd < 0)
else {
if (write(STDOUT_FILENO, 0, 0) < 0)
if (write(STDERR_FILENO, 0, 0) < 0)
if (fd > STDERR_FILENO)
}
}
r = chdir("/");
if (r < 0) {
goto exit;
}
umask(022);
r = mac_selinux_init("/dev");
if (r < 0) {
log_error_errno(r, "could not initialize labelling: %m");
goto exit;
}
goto exit;
}
r = manager_new(&manager);
if (r < 0)
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:
log_close();
}
setsid();
} else
sd_notify(false,
"READY=1\n"
"STATUS=Processing...");
r = manager_listen(manager);
if (r < 0)
return log_error_errno(r, "failed to set up fds and listen for events: %m");
if (r < 0) {
log_error_errno(r, "event loop failed: %m");
goto exit;
}
exit:
sd_notify(false,
"STOPPING=1\n"
"STATUS=Shutting down...");
if (manager)
log_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}