chiark / gitweb /
[PATCH] udevd - cleanup and better timeout handling
[elogind.git] / udevd.c
1 /*
2  * udevd.c - hotplug event serializer
3  *
4  * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
5  *
6  *
7  *      This program is free software; you can redistribute it and/or modify it
8  *      under the terms of the GNU General Public License as published by the
9  *      Free Software Foundation version 2 of the License.
10  *
11  *      This program is distributed in the hope that it will be useful, but
12  *      WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *      General Public License for more details.
15  *
16  *      You should have received a copy of the GNU General Public License along
17  *      with this program; if not, write to the Free Software Foundation, Inc.,
18  *      675 Mass Ave, Cambridge, MA 02139, USA.
19  *
20  */
21
22 #include <stddef.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <signal.h>
26 #include <unistd.h>
27 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <fcntl.h>
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/un.h>
36 #include <pthread.h>
37
38 #include "list.h"
39 #include "udev.h"
40 #include "udev_version.h"
41 #include "udevd.h"
42 #include "logging.h"
43
44
45 static pthread_mutex_t  msg_lock;
46 static pthread_mutex_t  msg_active_lock;
47 static pthread_cond_t msg_active;
48 static pthread_mutex_t  exec_lock;
49 static pthread_mutex_t  exec_active_lock;
50 static pthread_cond_t exec_active;
51 static pthread_mutex_t  running_lock;
52 static pthread_attr_t thr_attr;
53 static int expected_seqnum = 0;
54
55 LIST_HEAD(msg_list);
56 LIST_HEAD(exec_list);
57 LIST_HEAD(running_list);
58
59
60 static void msg_dump_queue(void)
61 {
62         struct hotplug_msg *msg;
63
64         list_for_each_entry(msg, &msg_list, list)
65                 dbg("sequence %d in queue", msg->seqnum);
66 }
67
68 static void msg_dump(struct hotplug_msg *msg)
69 {
70         dbg("sequence %d, '%s', '%s', '%s'",
71             msg->seqnum, msg->action, msg->devpath, msg->subsystem);
72 }
73
74 /* allocates a new message */
75 static struct hotplug_msg *msg_create(void)
76 {
77         struct hotplug_msg *new_msg;
78
79         new_msg = malloc(sizeof(struct hotplug_msg));
80         if (new_msg == NULL) {
81                 dbg("error malloc");
82                 return NULL;
83         }
84         memset(new_msg, 0x00, sizeof(struct hotplug_msg));
85         return new_msg;
86 }
87
88 /* orders the message in the queue by sequence number */
89 static void msg_queue_insert(struct hotplug_msg *msg)
90 {
91         struct hotplug_msg *loop_msg;
92
93         /* sort message by sequence number into list*/
94         list_for_each_entry(loop_msg, &msg_list, list)
95                 if (loop_msg->seqnum > msg->seqnum)
96                         break;
97         list_add_tail(&msg->list, &loop_msg->list);
98         dbg("queued message seq %d", msg->seqnum);
99
100         /* store timestamp of queuing */
101         msg->queue_time = time(NULL);
102
103         /* signal queue activity to manager */
104         pthread_mutex_lock(&msg_active_lock);
105         pthread_cond_signal(&msg_active);
106         pthread_mutex_unlock(&msg_active_lock);
107
108         return ;
109 }
110
111 /* forks event and removes event from run queue when finished */
112 static void *run_threads(void * parm)
113 {
114         pid_t pid;
115         struct hotplug_msg *msg;
116
117         msg = parm;
118         setenv("ACTION", msg->action, 1);
119         setenv("DEVPATH", msg->devpath, 1);
120
121         pid = fork();
122         switch (pid) {
123         case 0:
124                 /* child */
125                 execl(UDEV_BIN, "udev", msg->subsystem, NULL);
126                 dbg("exec of child failed");
127                 exit(1);
128                 break;
129         case -1:
130                 dbg("fork of child failed");
131                 goto exit;
132         default:
133                 /* wait for exit of child */
134                 dbg("==> exec seq %d [%d] working at '%s'",
135                     msg->seqnum, pid, msg->devpath);
136                 wait(NULL);
137                 dbg("<== exec seq %d came back", msg->seqnum);
138         }
139
140 exit:
141         /* remove event from run list */
142         pthread_mutex_lock(&running_lock);
143         list_del_init(&msg->list);
144         pthread_mutex_unlock(&running_lock);
145
146         free(msg);
147
148         /* signal queue activity to exec manager */
149         pthread_mutex_lock(&exec_active_lock);
150         pthread_cond_signal(&exec_active);
151         pthread_mutex_unlock(&exec_active_lock);
152
153         pthread_exit(0);
154 }
155
156 /* returns already running task with devpath */
157 static struct hotplug_msg *running_with_devpath(struct hotplug_msg *msg)
158 {
159         struct hotplug_msg *loop_msg;
160         struct hotplug_msg *tmp_msg;
161
162         list_for_each_entry_safe(loop_msg, tmp_msg, &running_list, list)
163                 if (strncmp(loop_msg->devpath, msg->devpath, sizeof(loop_msg->devpath)) == 0)
164                         return loop_msg;
165         return NULL;
166 }
167
168 /* queue management executes the events and delays events for the same devpath */
169 static void *exec_queue_manager(void * parm)
170 {
171         struct hotplug_msg *loop_msg;
172         struct hotplug_msg *tmp_msg;
173         struct hotplug_msg *msg;
174         pthread_t run_tid;
175
176         while (1) {
177                 pthread_mutex_lock(&exec_lock);
178                 list_for_each_entry_safe(loop_msg, tmp_msg, &exec_list, list) {
179                         msg = running_with_devpath(loop_msg);
180                         if (msg == NULL) {
181                                 /* move event to run list */
182                                 pthread_mutex_lock(&running_lock);
183                                 list_move_tail(&loop_msg->list, &running_list);
184                                 pthread_mutex_unlock(&running_lock);
185
186                                 pthread_create(&run_tid, &thr_attr, run_threads, (void *) loop_msg);
187
188                                 dbg("moved seq %d to running list", loop_msg->seqnum);
189                         } else {
190                                 dbg("delay seq %d, cause seq %d already working on '%s'",
191                                     loop_msg->seqnum, msg->seqnum, msg->devpath);
192                         }
193                 }
194                 pthread_mutex_unlock(&exec_lock);
195
196                 /* wait for activation, new events or childs coming back */
197                 pthread_mutex_lock(&exec_active_lock);
198                 pthread_cond_wait(&exec_active, &exec_active_lock);
199                 pthread_mutex_unlock(&exec_active_lock);
200         }
201 }
202
203 /* move message from incoming to exec queue */
204 static void msg_move_exec(struct list_head *head)
205 {
206         list_move_tail(head, &exec_list);
207         /* signal queue activity to manager */
208         pthread_mutex_lock(&exec_active_lock);
209         pthread_cond_signal(&exec_active);
210         pthread_mutex_unlock(&exec_active_lock);
211 }
212
213 /* queue management thread handles the timeouts and dispatches the events */
214 static void *msg_queue_manager(void * parm)
215 {
216         struct hotplug_msg *loop_msg;
217         struct hotplug_msg *tmp_msg;
218         time_t msg_age = 0;
219         struct timespec tv;
220
221         while (1) {
222                 dbg("msg queue manager, next expected is %d", expected_seqnum);
223                 pthread_mutex_lock(&msg_lock);
224                 pthread_mutex_lock(&exec_lock);
225 recheck:
226                 list_for_each_entry_safe(loop_msg, tmp_msg, &msg_list, list) {
227                         /* move event with expected sequence to the exec list */
228                         if (loop_msg->seqnum == expected_seqnum) {
229                                 msg_move_exec(&loop_msg->list);
230                                 expected_seqnum++;
231                                 dbg("moved seq %d to exec, next expected is %d",
232                                     loop_msg->seqnum, expected_seqnum);
233                                 continue;
234                         }
235
236                         /* move event with expired timeout to the exec list */
237                         msg_age = time(NULL) - loop_msg->queue_time;
238                         if (msg_age > EVENT_TIMEOUT_SEC-1) {
239                                 msg_move_exec(&loop_msg->list);
240                                 expected_seqnum = loop_msg->seqnum+1;
241                                 dbg("moved seq %d to exec, reset next expected to %d",
242                                     loop_msg->seqnum, expected_seqnum);
243                                 goto recheck;
244                         } else {
245                                 break;
246                         }
247                 }
248
249                 msg_dump_queue();
250                 pthread_mutex_unlock(&exec_lock);
251                 pthread_mutex_unlock(&msg_lock);
252
253                 /* wait until queue gets active or next message timeout expires */
254                 pthread_mutex_lock(&msg_active_lock);
255
256                 if (list_empty(&msg_list) == 0) {
257                         tv.tv_sec = time(NULL) + EVENT_TIMEOUT_SEC - msg_age;
258                         tv.tv_nsec = 0;
259                         dbg("next event expires in %li seconds",
260                             EVENT_TIMEOUT_SEC - msg_age);
261                         pthread_cond_timedwait(&msg_active, &msg_active_lock, &tv);
262                 } else {
263                         pthread_cond_wait(&msg_active, &msg_active_lock);
264                 }
265                 pthread_mutex_unlock(&msg_active_lock);
266         }
267 }
268
269 /* every connect creates a thread which gets the msg, queues it and exits */
270 static void *client_threads(void * parm)
271 {
272         int sock;
273         struct hotplug_msg *msg;
274         int retval;
275
276         sock = (int) parm;
277
278         msg = msg_create();
279         if (msg == NULL) {
280                 dbg("unable to store message");
281                 goto exit;
282         }
283
284         retval = recv(sock, msg, sizeof(struct hotplug_msg), 0);
285         if (retval <  0) {
286                 dbg("unable to receive message");
287                 goto exit;
288         }
289
290         if (strncmp(msg->magic, UDEV_MAGIC, sizeof(UDEV_MAGIC)) != 0 ) {
291                 dbg("message magic '%s' doesn't match, ignore it", msg->magic);
292                 goto exit;
293         }
294
295         pthread_mutex_lock(&msg_lock);
296         msg_queue_insert(msg);
297         pthread_mutex_unlock(&msg_lock);
298
299 exit:
300         close(sock);
301         pthread_exit(0);
302 }
303
304 static void sig_handler(int signum)
305 {
306         switch (signum) {
307                 case SIGINT:
308                 case SIGTERM:
309                         unlink(UDEVD_LOCK);
310                         unlink(UDEVD_SOCKET);
311                         exit(20 + signum);
312                         break;
313                 default:
314                         dbg("unhandled signal");
315         }
316 }
317
318 static int one_and_only(void)
319 {
320         char string[50];
321         int lock_file;
322
323         /* see if we can open */
324         lock_file = open(UDEVD_LOCK, O_RDWR | O_CREAT, 0x640);
325         if (lock_file < 0)
326                 return -1;
327
328         /* see if we can lock */
329         if (lockf(lock_file, F_TLOCK, 0) < 0) {
330                 dbg("file is already locked, exit");
331                 close(lock_file);
332                 return -1;
333         }
334
335         snprintf(string, sizeof(string), "%d\n", getpid());
336         write(lock_file, string, strlen(string));
337
338         return 0;
339 }
340
341 int main(int argc, char *argv[])
342 {
343         int ssock;
344         int csock;
345         struct sockaddr_un saddr;
346         struct sockaddr_un caddr;
347         socklen_t clen;
348         pthread_t cli_tid;
349         pthread_t mgr_msg_tid;
350         pthread_t mgr_exec_tid;
351         int retval;
352
353         /* only let one version of the daemon run at any one time */
354         if (one_and_only() != 0)
355                 exit(0);
356
357         signal(SIGINT, sig_handler);
358         signal(SIGTERM, sig_handler);
359
360         memset(&saddr, 0x00, sizeof(saddr));
361         saddr.sun_family = AF_LOCAL;
362         strcpy(saddr.sun_path, UDEVD_SOCKET);
363
364         unlink(UDEVD_SOCKET);
365         ssock = socket(AF_LOCAL, SOCK_STREAM, 0);
366         if (ssock == -1) {
367                 dbg("error getting socket");
368                 exit(1);
369         }
370
371         retval = bind(ssock, &saddr, sizeof(saddr));
372         if (retval < 0) {
373                 dbg("bind failed\n");
374                 goto exit;
375         }
376
377         retval = listen(ssock, SOMAXCONN);
378         if (retval < 0) {
379                 dbg("listen failed\n");
380                 goto exit;
381         }
382
383         pthread_mutex_init(&msg_lock, NULL);
384         pthread_mutex_init(&msg_active_lock, NULL);
385         pthread_mutex_init(&exec_lock, NULL);
386         pthread_mutex_init(&exec_active_lock, NULL);
387         pthread_mutex_init(&running_lock, NULL);
388
389         /* set default attributes for created threads */
390         pthread_attr_init(&thr_attr);
391         pthread_attr_setdetachstate(&thr_attr, PTHREAD_CREATE_DETACHED);
392
393         /* init queue management */
394         pthread_create(&mgr_msg_tid, &thr_attr, msg_queue_manager, NULL);
395         pthread_create(&mgr_exec_tid, &thr_attr, exec_queue_manager, NULL);
396
397         clen = sizeof(caddr);
398         /* main loop */
399         while (1) {
400                 csock = accept(ssock, &caddr, &clen);
401                 if (csock < 0) {
402                         if (errno == EINTR)
403                                 continue;
404                         dbg("client accept failed\n");
405                 }
406                 pthread_create(&cli_tid, &thr_attr, client_threads, (void *) csock);
407         }
408 exit:
409         close(ssock);
410         unlink(UDEVD_SOCKET);
411         exit(1);
412 }