chiark / gitweb /
[PATCH] udevd: throttle the forking of processes
[elogind.git] / udevd.c
diff --git a/udevd.c b/udevd.c
index f1e28d9ca3de7ba9e72d87ab5054159a7ebfb840..53c7bf3df1bd0055b46c24d1caaeb02f042f57f3 100644 (file)
--- a/udevd.c
+++ b/udevd.c
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/un.h>
-#include <fcntl.h>
 #include <sys/sysinfo.h>
 #include <sys/stat.h>
 
@@ -45,6 +47,7 @@
 
 /* global variables*/
 static int udevsendsock;
+static pid_t sid;
 
 static int pipefds[2];
 static long startup_time;
@@ -109,10 +112,16 @@ static void msg_queue_insert(struct hotplug_msg *msg)
        }
 
        /* sort message by sequence number into list */
-       list_for_each_entry_reverse(loop_msg, &msg_list, list)
+       list_for_each_entry_reverse(loop_msg, &msg_list, list) {
                if (loop_msg->seqnum < msg->seqnum)
                        break;
 
+               if (loop_msg->seqnum == msg->seqnum) {
+                       dbg("ignoring duplicate message seq %llu", msg->seqnum);
+                       return;
+               }
+       }
+
        /* store timestamp of queuing */
        sysinfo(&info);
        msg->queue_time = info.uptime;
@@ -123,7 +132,7 @@ static void msg_queue_insert(struct hotplug_msg *msg)
        /* run msg queue manager */
        run_msg_q = 1;
 
-       return ;
+       return;
 }
 
 /* forks event and removes event from run queue when finished */
@@ -138,6 +147,8 @@ static void udev_run(struct hotplug_msg *msg)
                /* child */
                close(udevsendsock);
                logging_close();
+
+               setpriority(PRIO_PROCESS, 0, UDEV_PRIORITY);
                execve(udev_bin, argv, msg->envp);
                dbg("exec of child failed");
                _exit(1);
@@ -145,9 +156,6 @@ static void udev_run(struct hotplug_msg *msg)
        case -1:
                dbg("fork of child failed");
                run_queue_delete(msg);
-               /* note: we never managed to run, so we had no impact on 
-                * running_with_devpath(), so don't bother setting run_exec_q
-                */
                break;
        default:
                /* get SIGCHLD in main loop */
@@ -156,6 +164,100 @@ static void udev_run(struct hotplug_msg *msg)
        }
 }
 
+static int running_processes(void)
+{
+       int f;
+       static char buf[4096];
+       int len;
+       int running;
+       const char *pos;
+
+       f = open("/proc/stat", O_RDONLY);
+       if (f == -1)
+               return -1;
+
+       len = read(f, buf, sizeof(buf));
+       close(f);
+
+       if (len <= 0)
+               return -1;
+       else
+               buf[len] = '\0';
+
+       pos = strstr(buf, "procs_running ");
+       if (pos == NULL)
+               return -1;
+
+       if (sscanf(pos, "procs_running %u", &running) != 1)
+               return -1;
+
+       return running;
+}
+
+/* return the number of process es in our session, count only until limit */
+static int running_processes_in_session(pid_t session, int limit)
+{
+       DIR *dir;
+       struct dirent *dent;
+       int running = 0;
+
+       dir = opendir("/proc");
+       if (!dir)
+               return -1;
+
+       /* read process info from /proc */
+       for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) {
+               int f;
+               char procdir[64];
+               char line[256];
+               const char *pos;
+               char state;
+               pid_t ppid, pgrp, sess;
+               int len;
+
+               if (!isdigit(dent->d_name[0]))
+                       continue;
+
+               snprintf(procdir, sizeof(procdir), "/proc/%s/stat", dent->d_name);
+               procdir[sizeof(procdir)-1] = '\0';
+
+               f = open(procdir, O_RDONLY);
+               if (f == -1)
+                       continue;
+
+               len = read(f, line, sizeof(line));
+               close(f);
+
+               if (len <= 0)
+                       continue;
+               else
+                       line[len] = '\0';
+
+               /* skip ugly program name */
+               pos = strrchr(line, ')') + 2;
+               if (pos == NULL)
+                       continue;
+
+               if (sscanf(pos, "%c %d %d %d ", &state, &ppid, &pgrp, &sess) != 4)
+                       continue;
+
+               /* count only processes in our session */
+               if (sess != session)
+                       continue;
+
+               /* count only running, no sleeping processes */
+               if (state != 'R')
+                       continue;
+
+               running++;
+               if (limit > 0 && running >= limit)
+                       break;
+       }
+       closedir(dir);
+
+       return running;
+}
+
 static int compare_devpath(const char *running, const char *waiting)
 {
        int i;
@@ -212,13 +314,30 @@ static void exec_queue_manager(void)
        struct hotplug_msg *loop_msg;
        struct hotplug_msg *tmp_msg;
        struct hotplug_msg *msg;
+       int running;
+
+       running = running_processes();
+       dbg("%d processes runnning on system", running);
+       if (running < 0)
+               running = THROTTLE_MAX_RUNNING_CHILDS;
 
        list_for_each_entry_safe(loop_msg, tmp_msg, &exec_list, list) {
+               /* check running processes in our session and possibly throttle */
+               if (running >= THROTTLE_MAX_RUNNING_CHILDS) {
+                       running = running_processes_in_session(sid, THROTTLE_MAX_RUNNING_CHILDS+10);
+                       dbg("%d processes running in session", running);
+                       if (running >= THROTTLE_MAX_RUNNING_CHILDS) {
+                               dbg("delay seq %llu, cause too many processes already running", loop_msg->seqnum);
+                               return;
+                       }
+               }
+
                msg = running_with_devpath(loop_msg);
                if (!msg) {
                        /* move event to run list */
                        list_move_tail(&loop_msg->list, &running_list);
                        udev_run(loop_msg);
+                       running++;
                        dbg("moved seq %llu to running list", loop_msg->seqnum);
                } else {
                        dbg("delay seq %llu (%s), cause seq %llu (%s) is still running",
@@ -444,6 +563,7 @@ static void reap_sigchilds(void)
 static void user_sighandler(void)
 {
        int sig;
+
        while(1) {
                int rc = read(pipefds[0], &sig, sizeof(sig));
                if (rc < 0)
@@ -453,16 +573,48 @@ static void user_sighandler(void)
        }
 }
 
-int main(int argc, char *argv[], char *envp[])
+static int init_udevsend_socket(void)
 {
-       struct sysinfo info;
-       int maxsockplus;
        struct sockaddr_un saddr;
        socklen_t addrlen;
-       int retval, fd;
        const int feature_on = 1;
+       int retval;
+
+       memset(&saddr, 0x00, sizeof(saddr));
+       saddr.sun_family = AF_LOCAL;
+       /* use abstract namespace for socket path */
+       strcpy(&saddr.sun_path[1], UDEVD_SOCK_PATH);
+       addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path+1) + 1;
+
+       udevsendsock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+       if (udevsendsock == -1) {
+               dbg("error getting socket, %s", strerror(errno));
+               return -1;
+       }
+
+       /* the bind takes care of ensuring only one copy running */
+       retval = bind(udevsendsock, (struct sockaddr *) &saddr, addrlen);
+       if (retval < 0) {
+               dbg("bind failed, %s", strerror(errno));
+               close(udevsendsock);
+               return -1;
+       }
+
+       /* enable receiving of the sender credentials */
+       setsockopt(udevsendsock, SOL_SOCKET, SO_PASSCRED, &feature_on, sizeof(feature_on));
+
+       return 0;
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+       struct sysinfo info;
+       int maxsockplus;
+       int retval;
+       int fd;
        struct sigaction act;
        fd_set readfds;
+       const char *udevd_expected_seqnum;
 
        logging_init("udevd");
        dbg("version %s", UDEV_VERSION);
@@ -490,24 +642,27 @@ int main(int argc, char *argv[], char *envp[])
                }
        }
 
+       /* become session leader */
+       sid = setsid();
+       dbg("our session is %d", sid);
+
        /* make sure we don't lock any path */
        chdir("/");
        umask(umask(077) | 022);
 
+       /*set a reasonable scheduling priority for the daemon */
+       setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY);
+
        /* Set fds to dev/null */
        fd = open( "/dev/null", O_RDWR );
-       if ( fd < 0 ) {
+       if (fd >= 0)  {
+               dup2(fd, 0);
+               dup2(fd, 1);
+               dup2(fd, 2);
+               if (fd > 2)
+                       close(fd);
+       } else
                dbg("error opening /dev/null %s", strerror(errno));
-               goto exit;
-       }
-       dup2(fd, 0);
-       dup2(fd, 1);
-       dup2(fd, 2);
-       if (fd > 2) 
-               close(fd);
-
-       /* become session leader */
-       setsid();
 
        /* setup signal handler pipe */
        retval = pipe(pipefds);
@@ -522,10 +677,8 @@ int main(int argc, char *argv[], char *envp[])
                goto exit;
        }
        retval = fcntl(pipefds[0], F_SETFD, FD_CLOEXEC);
-       if (retval < 0) {
+       if (retval < 0)
                dbg("error fcntl on read pipe: %s", strerror(errno));
-               goto exit;
-       }
 
        retval = fcntl(pipefds[1], F_SETFL, O_NONBLOCK);
        if (retval < 0) {
@@ -533,10 +686,8 @@ int main(int argc, char *argv[], char *envp[])
                goto exit;
        }
        retval = fcntl(pipefds[1], F_SETFD, FD_CLOEXEC);
-       if (retval < 0) {
+       if (retval < 0)
                dbg("error fcntl on write pipe: %s", strerror(errno));
-               goto exit;
-       }
 
        /* set signal handlers */
        act.sa_handler = (void (*) (int))sig_handler;
@@ -547,29 +698,15 @@ int main(int argc, char *argv[], char *envp[])
        sigaction(SIGALRM, &act, NULL);
        sigaction(SIGCHLD, &act, NULL);
 
-       memset(&saddr, 0x00, sizeof(saddr));
-       saddr.sun_family = AF_LOCAL;
-       /* use abstract namespace for socket path */
-       strcpy(&saddr.sun_path[1], UDEVD_SOCK_PATH);
-       addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path+1) + 1;
-
-       udevsendsock = socket(AF_LOCAL, SOCK_DGRAM, 0);
-       if (udevsendsock == -1) {
-               dbg("error getting socket, exit");
-               goto exit;
-       }
+       if (init_udevsend_socket() < 0) {
+               if (errno == EADDRINUSE)
+                       dbg("another udevd running, exit");
+               else
+                       dbg("error initialising udevsend socket: %s", strerror(errno));
 
-       /* the bind takes care of ensuring only one copy running */
-       retval = bind(udevsendsock, (struct sockaddr *) &saddr, addrlen);
-       if (retval < 0) {
-               dbg("bind failed, exit");
-               close(udevsendsock);
                goto exit;
        }
 
-       /* enable receiving of the sender credentials */
-       setsockopt(udevsendsock, SOL_SOCKET, SO_PASSCRED, &feature_on, sizeof(feature_on));
-
        /* possible override of udev binary, used for testing */
        udev_bin = getenv("UDEV_BIN");
        if (udev_bin != NULL)
@@ -577,7 +714,14 @@ int main(int argc, char *argv[], char *envp[])
        else
                udev_bin = UDEV_BIN;
 
-       /* handle special startup timeout*/
+       /* possible init of expected_seqnum value */
+       udevd_expected_seqnum = getenv("UDEVD_EXPECTED_SEQNUM");
+       if (udevd_expected_seqnum != NULL) {
+               expected_seqnum = strtoull(udevd_expected_seqnum, NULL, 10);
+               dbg("initialize expected_seqnum to %llu", expected_seqnum);
+       }
+
+       /* get current time to provide shorter timeout on startup */
        sysinfo(&info);
        startup_time = info.uptime;