udevd.c revision 1aa1e24848903d11780db1ade355be73ad61a937
/*
* udevd.c - event listener and serializer
*
* Copyright (C) 2004-2005 Kay Sievers <kay.sievers@vrfy.org>
* Copyright (C) 2004 Chris Friesen <chris_friesen@sympatico.ca>
*
*
* under the terms of the GNU General Public License as published by the
* Free Software Foundation version 2 of the License.
*
* 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, write to the Free Software Foundation, Inc.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stddef.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <fcntl.h>
#include <syslog.h>
#include <time.h>
#include "udev.h"
#include "udev_rules.h"
#include "udevd.h"
static struct udev_rules rules;
static int udevd_sock;
static int uevent_netlink_sock;
static int inotify_fd;
static volatile int sigchilds_waiting;
static volatile int udev_exit;
static volatile int reload_config;
static int run_exec_q;
static int stop_exec_q;
static int max_childs;
static int max_childs_running;
static char udev_log[32];
static LIST_HEAD(running_list);
#ifdef USE_LOG
{
if (priority > udev_log_priority)
return;
}
#endif
{
exit(1);
}
{
int i;
int retval;
/* set signal handlers */
/* trigger timeout to prevent hanging processes */
/* reconstruct event environment from message */
udev = udev_device_init();
return -1;
/* run programs collected by RUN-key*/
if (retval == 0) {
struct name_entry *name_loop;
else
(udev_log_priority >= LOG_INFO)))
retval = -1;
}
}
return retval;
}
enum event_state {
};
{
char filename_failed[PATH_SIZE];
struct uevent_msg *loop_msg;
/* add location of queue files */
/* replace '/' to transform path into a filename */
if (filename[i] == '/')
filename[i] = PATH_TO_NAME_CHAR;
/* add location of failed files */
if (end > sizeof(filename_failed))
end = sizeof(filename_failed);
/* replace '/' to transform path into a filename */
if (filename_failed[i] == '/')
switch (state) {
case EVENT_QUEUED:
return;
case EVENT_FINISHED:
case EVENT_FAILED:
/* don't remove, if events for the same path are still pending */
return;
/* move failed events to the failed directory */
if (state == EVENT_FAILED) {
} else {
}
/* clean up the queue directory */
return;
}
}
{
/* mark as failed, if add event returns non-zero */
else
}
{
int retval;
switch (pid) {
case 0:
/* child */
if (inotify_fd > 0)
logging_init("udevd-event");
if (retval)
exit(1);
exit(0);
case -1:
break;
default:
/* get SIGCHLD in main loop */
info("seq %llu forked, pid [%d], '%s' '%s', %ld seconds old",
}
}
{
/* run all events with a timeout set immediately */
return;
}
run_exec_q = 1;
}
/* runs event and removes event from run queue when finished */
static int running_processes(void)
{
int f;
static char buf[4096];
int len;
int running;
const char *pos;
if (f == -1)
return -1;
close(f);
if (len <= 0)
return -1;
else
return -1;
return -1;
return running;
}
/* return the number of process es in our session, count only until limit */
{
int running = 0;
if (!dir)
return -1;
/* read process info from /proc */
int f;
char procdir[64];
char line[256];
const char *pos;
char state;
int len;
continue;
if (f == -1)
continue;
close(f);
if (len <= 0)
continue;
else
/* skip ugly program name */
continue;
continue;
/* count only processes in our session */
continue;
/* count only running, no sleeping processes */
if (state != 'R')
continue;
running++;
break;
}
return running;
}
{
int i;
for (i = 0; i < PATH_SIZE; i++) {
/* identical device event found */
return 1;
/* parent device event found */
return 2;
/* child device event found */
return 3;
/* no matching event */
break;
}
return 0;
}
/* returns still running task for the same device, its parent or its physical device */
{
struct uevent_msg *loop_msg;
int childs_count = 0;
return 1;
}
dbg("%llu, child device event still running %llu (%s)",
return 2;
}
/* return running physical device event */
dbg("%llu, physical device event still running %llu (%s)",
return 3;
}
}
return 0;
}
/* exec queue management routine executes the events and serializes events in the same sequence */
static void msg_queue_manager(void)
{
struct uevent_msg *loop_msg;
struct uevent_msg *tmp_msg;
int running;
if (list_empty(&exec_list))
return;
running = running_processes();
if (running < 0)
/* check running processes in our session and possibly throttle */
if (running >= max_childs_running) {
if (running >= max_childs_running) {
return;
}
}
/* don't run two processes for the same devpath and wait for the parent*/
continue;
}
/* move event to run list */
running++;
}
}
{
int bufpos;
int i;
struct uevent_msg *msg;
int major = 0;
int minor = 0;
return NULL;
/* copy environment buffer and reconstruct envp */
bufpos = 0;
int keylen;
char *key;
/* remember some keys for further processing */
}
info("DEVPATH missing, ignore message");
return NULL;
}
return msg;
}
/* receive the udevd message from userspace */
static struct uevent_msg *get_udevd_msg(void)
{
struct uevent_msg *msg;
int envbuf_size;
int *intval;
if (size < 0) {
return NULL;
}
err("no sender credentials received, message ignored");
return NULL;
}
return NULL;
}
return NULL;
}
case UDEVD_UEVENT_UDEVSEND:
case UDEVD_UEVENT_INITSEND:
info("udevd event message received");
return NULL;
return msg;
case UDEVD_STOP_EXEC_QUEUE:
info("udevd message (STOP_EXEC_QUEUE) received");
stop_exec_q = 1;
break;
case UDEVD_START_EXEC_QUEUE:
info("udevd message (START_EXEC_QUEUE) received");
stop_exec_q = 0;
break;
case UDEVD_SET_LOG_LEVEL:
break;
case UDEVD_SET_MAX_CHILDS:
max_childs = *intval;
break;
case UDEVD_RELOAD_RULES:
info("udevd message (RELOAD_RULES) received");
reload_config = 1;
break;
default:
dbg("unknown message type");
}
return NULL;
}
/* receive the kernel user event message and do some sanity checks */
static struct uevent_msg *get_netlink_msg(void)
{
struct uevent_msg *msg;
int bufpos;
char *pos;
if (size < 0) {
return NULL;
}
/* start of event payload */
return NULL;
/* validate message */
return NULL;
}
pos[0] = '\0';
return NULL;
}
return NULL;
}
return msg;
}
{
switch (signum) {
case SIGINT:
case SIGTERM:
udev_exit = 1;
break;
case SIGCHLD:
/* set flag, then write to pipe if needed */
sigchilds_waiting = 1;
break;
case SIGHUP:
reload_config = 1;
break;
}
/* write to pipe, which will wakeup select() in our mainloop */
}
{
/* find msg associated with pid and delete it */
struct uevent_msg *msg;
/* there may be events waiting with the same devpath */
run_exec_q = 1;
return;
}
}
}
static void reap_sigchilds(void)
{
int status;
while (1) {
if (pid <= 0)
break;
else if (WIFSIGNALED(status))
else
status = 0;
}
}
static int init_udevd_socket(void)
{
struct sockaddr_un saddr;
const int feature_on = 1;
int retval;
/* use abstract namespace for socket path */
if (udevd_sock == -1) {
return -1;
}
/* set receive buffersize */
/* the bind takes care of ensuring only one copy running */
if (retval < 0) {
return -1;
}
/* enable receiving of the sender credentials */
return 0;
}
static int init_uevent_netlink_sock(void)
{
struct sockaddr_nl snl;
int retval;
if (uevent_netlink_sock == -1) {
return -1;
}
/* set receive buffersize */
if (retval < 0) {
uevent_netlink_sock = -1;
return -1;
}
return 0;
}
{
int retval;
int fd;
const char *value;
int daemonize = 0;
int i;
int rc = 0;
int maxfd;
/* redirect std fd's, if the kernel forks us, we don't have them at all */
if (fd >= 0) {
if (fd != STDIN_FILENO)
if (fd != STDOUT_FILENO)
if (fd != STDERR_FILENO)
if (fd > STDERR_FILENO)
}
logging_init("udevd");
if (fd < 0)
if (getuid() != 0) {
err("need to be root, exit");
goto exit;
}
/* parse commandline options */
for (i = 1 ; i < argc; i++) {
info("will daemonize");
daemonize = 1;
}
info("will not execute events until START_EXEC_QUEUE is received");
stop_exec_q = 1;
}
}
/* init sockets to receive events */
if (init_udevd_socket() < 0) {
if (errno == EADDRINUSE) {
err("another udevd running, exit");
rc = 1;
} else {
rc = 2;
}
goto exit;
}
if (init_uevent_netlink_sock() < 0) {
err("uevent socket not available");
rc = 3;
goto exit;
}
/* parse the rules and keep it in memory */
sysfs_init();
if (daemonize) {
switch (pid) {
case 0:
dbg("daemonized fork running");
break;
case -1:
rc = 4;
goto exit;
default:
goto exit;
}
}
/* set scheduling priority for the daemon */
chdir("/");
umask(022);
/* become session leader */
/* OOM_DISABLE == -17 */
if (fd < 0)
else {
}
/* setup signal handler pipe */
if (retval < 0) {
goto exit;
}
if (retval < 0) {
goto exit;
}
if (retval < 0) {
goto exit;
}
/* set signal handlers */
/* watch rules directory */
inotify_fd = inotify_init();
if (inotify_fd > 0)
inotify_add_watch(inotify_fd, udev_rules_filename, IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
/* maximum limit of forked childs */
if (value)
else
/* start to throttle forking if maximum number of _running_ childs is reached */
if (value)
else
/* clear environment for forked event processes */
clearenv();
/* export log_priority , as called programs may want to follow that setting */
maxfd = udevd_sock;
while (!udev_exit) {
struct uevent_msg *msg;
int fdcount;
if (inotify_fd > 0)
if (fdcount < 0) {
continue;
}
/* get user socket message */
msg = get_udevd_msg();
if (msg)
}
/* get kernel netlink message */
msg = get_netlink_msg();
if (msg)
}
/* received a signal, clear our notification pipe */
char buf[256];
}
/* rules directory inotify watch */
int nbytes;
/* discard all possible events, we can just reload the config */
char *buf;
reload_config = 1;
if (!buf) {
err("error getting buffer for inotify, disable watching");
inotify_fd = -1;
}
}
}
/* rules changed, set by inotify or a signal*/
if (reload_config) {
reload_config = 0;
}
/* forked child has returned */
if (sigchilds_waiting) {
sigchilds_waiting = 0;
}
if (run_exec_q) {
run_exec_q = 0;
if (!stop_exec_q)
}
}
exit:
if (signal_pipe[READ_END] > 0)
if (signal_pipe[WRITE_END] > 0)
if (udevd_sock > 0)
if (inotify_fd > 0)
if (uevent_netlink_sock > 0)
return rc;
}