/*
* 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 <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "alloc-util.h"
#include "cgroup-util.h"
#include "cpu-set-util.h"
#include "dev-setup.h"
#include "fd-util.h"
#include "fileio.h"
#include "formats-util.h"
#include "fs-util.h"
#include "hashmap.h"
#include "io-util.h"
#include "netlink-util.h"
#include "parse-util.h"
#include "proc-cmdline.h"
#include "process-util.h"
#include "selinux-util.h"
#include "signal-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "terminal-util.h"
#include "udev-util.h"
#include "udev.h"
#include "user-util.h"
static bool arg_debug = false;
static int arg_daemonize = false;
static unsigned arg_children_max;
static int arg_exec_delay;
typedef struct Manager {
const char *cgroup;
int fd_inotify;
} Manager;
enum event_state {
};
struct event {
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 {
int refcount;
};
/* 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;
}
if (!manager)
return;
}
}
int r = 0;
/* listen for new events */
if (worker_monitor == NULL)
return;
/* allow the main daemon netlink address to send devices to the worker */
if (r < 0)
log_error_errno(r, "worker: could not enable receiving of device: %m");
switch (pid) {
case 0: {
int fd_monitor;
/* 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 (;;) {
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));
goto skip;
}
}
}
/* needed for renaming netifs */
/* apply rules, create node, symlinks */
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;
if (size != sizeof(struct signalfd_siginfo))
continue;
case SIGTERM:
goto out;
}
}
}
}
}
out:
log_close();
}
case -1:
break;
default:
{
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 */
/* 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;
}
/* 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...");
}
return;
/* 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 (;;) {
};
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;
}
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;
}
int r;
bool part_table_read = false;
bool has_partitions = false;
int fd;
/*
* 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;
}
struct inotify_event *e;
ssize_t l;
if (l < 0) {
return 1;
}
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, r;
n = sd_listen_fds(true);
if (n < 0)
return n;
if (ctrl_fd >= 0)
return -EINVAL;
continue;
}
if (netlink_fd >= 0)
return -EINVAL;
netlink_fd = fd;
continue;
}
return -EINVAL;
}
if (ctrl_fd < 0) {
if (!udev)
return -ENOMEM;
if (!ctrl)
if (r < 0)
if (fd < 0)
if (ctrl_fd < 0)
}
if (netlink_fd < 0) {
if (!udev) {
if (!udev)
return -ENOMEM;
}
if (!monitor)
if (r < 0)
if (fd < 0)
if (ctrl_fd < 0)
}
*rnetlink = netlink_fd;
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 (prio < 0)
goto invalid;
if (r < 0)
goto invalid;
if (r < 0)
goto invalid;
if (r < 0)
goto invalid;
}
return 0;
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;
}
if (!manager)
return log_oom();
/* 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 */
if (r < 0)
return log_error_errno(r, "could not allocate event loop: %m");
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;
if (r < 0) {
r = log_error_errno(r, "failed to allocate manager object: %m");
goto exit;
}
if (r < 0)
log_error_errno(r, "failed to apply permissions on static device nodes: %m");
(void) sd_notify(false,
"READY=1\n"
"STATUS=Processing...");
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)
return r;
}
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;
}
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;
}
if (getppid() == 1) {
/* get our own cgroup, we regularly kill everything udev has left behind
we only do this on systemd systems, and only if we are directly spawned
by PID1. otherwise we are not guaranteed to have a dedicated cgroup */
if (r < 0) {
log_debug_errno(r, "did not find dedicated cgroup: %m");
else
log_warning_errno(r, "failed to get cgroup: %m");
}
}
if (r < 0) {
r = log_error_errno(r, "could not listen on fds: %m");
goto exit;
}
if (arg_daemonize) {
if (log_get_max_level() < LOG_DEBUG)
(void) make_null_stdio();
switch (pid) {
case 0:
break;
case -1:
goto exit;
default:
log_close();
}
setsid();
}
exit:
log_close();
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}