fdb128eaab28d9596f4ff9943925c5a789003d55
[petitboot] / devices / udev-helper.c
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <stdarg.h>
5 #include <stdint.h>
6 #include <unistd.h>
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <sys/socket.h>
10 #include <sys/wait.h>
11 #include <sys/un.h>
12 #include <fcntl.h>
13 #include <errno.h>
14 #include <string.h>
15 #include <asm/byteorder.h>
16 #include <linux/cdrom.h>
17 #include <sys/ioctl.h>
18
19 #include "parser.h"
20 #include "petitboot-paths.h"
21
22 /* Define below to operate without the frontend */
23 #undef USE_FAKE_SOCKET
24
25 /* Delay in seconds between polling of removable devices */
26 #define REMOVABLE_SLEEP_DELAY   2
27
28 static FILE *logf;
29 static int sock;
30
31 void pb_log(const char *fmt, ...)
32 {
33         va_list ap;
34
35         va_start(ap, fmt);
36         fprintf(logf, fmt, ap);
37         va_end(ap);
38 }
39
40 static void print_boot_option(const struct boot_option *opt)
41 {
42         pb_log("\tname: %s\n", opt->name);
43         pb_log("\tdescription: %s\n", opt->description);
44         pb_log("\tboot_image: %s\n", opt->boot_image_file);
45         pb_log("\tinitrd: %s\n", opt->initrd_file);
46         pb_log("\tboot_args: %s\n", opt->boot_args);
47
48 }
49
50 static void print_device(const struct device *dev)
51 {
52         pb_log("\tid: %s\n", dev->id);
53         pb_log("\tname: %s\n", dev->name);
54         pb_log("\tdescription: %s\n", dev->description);
55         pb_log("\tboot_image: %s\n", dev->icon_file);
56 }
57
58 static int write_action(int fd, enum device_action action)
59 {
60         uint8_t action_buf = action;
61         return write(fd, &action_buf, sizeof(action_buf)) != sizeof(action_buf);
62 }
63
64 static int write_string(int fd, const char *str)
65 {
66         int len, pos = 0;
67         uint32_t len_buf;
68
69         if (!str) {
70                 len_buf = 0;
71                 if (write(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) {
72                         pb_log("write failed: %s\n", strerror(errno));
73                         return -1;
74                 }
75                 return 0;
76         }
77
78         len = strlen(str);
79         if (len > (1ull << (sizeof(len_buf) * 8 - 1))) {
80                 pb_log("string too large\n");
81                 return -1;
82         }
83
84         len_buf = __cpu_to_be32(len);
85         if (write(fd, &len_buf, sizeof(len_buf)) != sizeof(len_buf)) {
86                 pb_log("write failed: %s\n", strerror(errno));
87                 return -1;
88         }
89
90         while (pos < len) {
91                 int rc = write(fd, str, len - pos);
92                 if (rc <= 0) {
93                         pb_log("write failed: %s\n", strerror(errno));
94                         return -1;
95                 }
96                 pos += rc;
97                 str += rc;
98         }
99
100         return 0;
101 }
102
103 int add_device(const struct device *dev)
104 {
105         int rc;
106
107         pb_log("device added:\n");
108         print_device(dev);
109         rc = write_action(sock, DEV_ACTION_ADD_DEVICE) ||
110                 write_string(sock, dev->id) ||
111                 write_string(sock, dev->name) ||
112                 write_string(sock, dev->description) ||
113                 write_string(sock, dev->icon_file);
114
115         if (rc)
116                 pb_log("error writing device %s to socket\n", dev->name);
117
118         return rc;
119 }
120
121 int add_boot_option(const struct boot_option *opt)
122 {
123         int rc;
124
125         pb_log("boot option added:\n");
126         print_boot_option(opt);
127
128         rc = write_action(sock, DEV_ACTION_ADD_OPTION) ||
129                 write_string(sock, opt->id) ||
130                 write_string(sock, opt->name) ||
131                 write_string(sock, opt->description) ||
132                 write_string(sock, opt->icon_file) ||
133                 write_string(sock, opt->boot_image_file) ||
134                 write_string(sock, opt->initrd_file) ||
135                 write_string(sock, opt->boot_args);
136
137         if (rc)
138                 pb_log("error writing boot option %s to socket\n", opt->name);
139
140         return rc;
141 }
142
143 int remove_device(const char *dev_path)
144 {
145         return write_action(sock, DEV_ACTION_REMOVE_DEVICE) ||
146                 write_string(sock, dev_path);
147 }
148
149 int connect_to_socket()
150 {
151 #ifndef USE_FAKE_SOCKET
152         int fd;
153         struct sockaddr_un addr;
154
155         fd = socket(PF_UNIX, SOCK_STREAM, 0);
156         if (fd == -1) {
157                 pb_log("can't create socket: %s\n", strerror(errno));
158                 return -1;
159         }
160
161         addr.sun_family = AF_UNIX;
162         strcpy(addr.sun_path, PBOOT_DEVICE_SOCKET);
163
164         if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) {
165                 pb_log("can't connect to %s: %s\n",
166                                 addr.sun_path, strerror(errno));
167                 return -1;
168         }
169         sock = fd;
170
171         return 0;
172 #else
173         int fd;
174         fd = open("./debug_socket", O_WRONLY | O_CREAT, 0640);
175         if (fd < 0) {
176                 pb_log("can't create output file: %s\n", strerror(errno));
177                 return -1;
178         }
179         sock = fd;
180         return 0;
181 #endif
182 }
183
184 int mount_device(const char *dev_path, char *mount_path)
185 {
186         char *dir;
187         const char *basename;
188         int pid, status, rc = -1;
189
190         basename = strrchr(dev_path, '/');
191         if (basename)
192                 basename++;
193         else
194                 basename = dev_path;
195  
196         /* create a unique mountpoint */
197         dir = malloc(strlen(TMP_DIR) + 13 + strlen(basename));
198         sprintf(dir, "%s/mnt-%s-XXXXXX", TMP_DIR, basename);
199
200         if (!mkdtemp(dir)) {
201                 pb_log("failed to create temporary directory in %s: %s",
202                                 TMP_DIR, strerror(errno));
203                 goto out;
204         }
205
206         pid = fork();
207         if (pid == -1) {
208                 pb_log("%s: fork failed: %s\n", __FUNCTION__, strerror(errno));
209                 goto out;
210         }
211
212         if (pid == 0) {
213                 execl(MOUNT_BIN, MOUNT_BIN, dev_path, dir, "-o", "ro", NULL);
214                 exit(EXIT_FAILURE);
215         }
216
217         if (waitpid(pid, &status, 0) == -1) {
218                 pb_log("%s: waitpid failed: %s\n", __FUNCTION__,
219                                 strerror(errno));
220                 goto out;
221         }
222
223         if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
224                 strcpy(mount_path, dir);
225                 rc = 0;
226         }
227
228 out:
229         free(dir);
230         return rc;
231 }
232
233 static int unmount_device(const char *dev_path)
234 {
235         int pid, status, rc;
236
237         pid = fork();
238
239         if (pid == -1) {
240                 pb_log("%s: fork failed: %s\n", __FUNCTION__, strerror(errno));
241                 return -1;
242         }
243
244         if (pid == 0) {
245                 execl(UMOUNT_BIN, UMOUNT_BIN, dev_path, NULL);
246                 exit(EXIT_FAILURE);
247         }
248
249         if (waitpid(pid, &status, 0) == -1) {
250                 pb_log("%s: waitpid failed: %s\n", __FUNCTION__,
251                                 strerror(errno));
252                 return -1;
253         }
254
255         rc = !WIFEXITED(status) || WEXITSTATUS(status) != 0;
256
257         return rc;
258 }
259
260 static const struct device fake_boot_devices[] =
261 {
262         {
263                 .id             = "fakeDisk0",
264                 .name           = "Hard Disk",
265                 .icon_file      = artwork_pathname("hdd.png"),
266         },
267         {
268                 .id             = "fakeDisk1",
269                 .name           = "PinkCat Linux CD",
270                 .icon_file      = artwork_pathname("cdrom.png"),
271         }
272 };
273
274 static const struct boot_option fake_boot_options[] =
275 {
276         {
277                 .id             = "fakeBoot0",
278                 .name           = "Bloobuntu Linux",
279                 .description    = "Boot Bloobuntu Linux",
280                 .icon_file      = artwork_pathname("hdd.png"),
281         },
282         {
283                 .id             = "fakeBoot1",
284                 .name           = "Pendora Gore 6",
285                 .description    = "Boot Pendora Gora 6",
286                 .icon_file      = artwork_pathname("hdd.png"),
287         },
288         {
289                 .id             = "fakeBoot2",
290                 .name           = "Genfoo Minux",
291                 .description    = "Boot Genfoo Minux",
292                 .icon_file      = artwork_pathname("hdd.png"),
293         },
294         {
295                 .id             = "fakeBoot3",
296                 .name           = "PinkCat Linux",
297                 .description    = "Install PinkCat Linux - Graphical install",
298                 .icon_file      = artwork_pathname("cdrom.png"),
299         },
300 };
301
302 enum generic_icon_type guess_device_type(void)
303 {
304         const char *type = getenv("ID_TYPE");
305         const char *bus = getenv("ID_BUS");
306         if (type && streq(type, "cd"))
307                 return ICON_TYPE_OPTICAL;
308         if (!bus)
309                 return ICON_TYPE_UNKNOWN;
310         if (streq(bus, "usb"))
311                 return ICON_TYPE_USB;
312         if (streq(bus, "ata") || streq(bus, "scsi"))
313                 return ICON_TYPE_DISK;
314         return ICON_TYPE_UNKNOWN;
315 }
316
317
318 static int is_removable_device(const char *sysfs_path) 
319 {
320         char full_path[PATH_MAX];
321         char buf[80];
322         int fd, buf_len;
323
324         sprintf(full_path, "/sys/%s/removable", sysfs_path);    
325         fd = open(full_path, O_RDONLY);
326         pb_log(" -> removable check on %s, fd=%d\n", full_path, fd);
327         if (fd < 0)
328                 return 0;
329         buf_len = read(fd, buf, 79);
330         close(fd);
331         if (buf_len < 0)
332                 return 0;
333         buf[buf_len] = 0;
334         return strtol(buf, NULL, 10);
335 }
336
337 static int is_ignored_device(const char *devname)
338 {
339         static const char *ignored_devices[] = { "/dev/ram", NULL };
340         const char **dev;
341
342         for (dev = ignored_devices; *dev; dev++)
343                 if (!strncmp(devname, *dev, strlen(*dev)))
344                         return 1;
345
346         return 0;
347 }
348
349 static int found_new_device(const char *dev_path)
350 {
351         char mountpoint[PATH_MAX];
352
353         if (mount_device(dev_path, mountpoint)) {
354                 pb_log("failed to mount %s\n", dev_path);
355                 return EXIT_FAILURE;
356         }
357
358         pb_log("mounted %s at %s\n", dev_path, mountpoint);
359
360         iterate_parsers(dev_path, mountpoint);
361
362         return EXIT_SUCCESS;
363 }
364
365 static void detach_and_sleep(int sec)
366 {
367         static int forked = 0;
368         int rc = 0;
369
370         if (sec <= 0)
371                 return;
372
373         if (!forked) {
374                 pb_log("running in background...");
375                 rc = fork();
376                 forked = 1;
377         }
378
379         if (rc == 0) {
380                 sleep(sec);
381
382         } else if (rc == -1) {
383                 perror("fork()");
384                 exit(EXIT_FAILURE);
385         } else {
386                 exit(EXIT_SUCCESS);
387         }
388 }
389
390 static int poll_device_plug(const char *dev_path,
391                             int *optical)
392 {
393         int rc, fd;
394
395         /* Polling loop for optical drive */
396         for (; (*optical) != 0; ) {
397                 pb_log("poll for optical drive insertion ...\n");
398                 fd = open(dev_path, O_RDONLY|O_NONBLOCK);
399                 if (fd < 0)
400                         return EXIT_FAILURE;
401                 rc = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT);
402                 close(fd);
403                 if (rc == -1) {
404                         pb_log("not an optical drive, fallback...\n");
405                         break;
406                 }
407                 *optical = 1;
408                 if (rc == CDS_DISC_OK)
409                         return EXIT_SUCCESS;
410
411                 pb_log("no... waiting\n");
412                 detach_and_sleep(REMOVABLE_SLEEP_DELAY);
413         }
414
415         /* Fall back to bare open() */
416         *optical = 0;
417         for (;;) {
418                 pb_log("poll for non-optical drive insertion ...\n");
419                 fd = open(dev_path, O_RDONLY);
420                 if (fd < 0 && errno != ENOMEDIUM)
421                         return EXIT_FAILURE;
422                 close(fd);
423                 if (fd >= 0)
424                         return EXIT_SUCCESS;
425                 pb_log("no... waiting\n");
426                 detach_and_sleep(REMOVABLE_SLEEP_DELAY);
427         }
428 }
429
430 static int poll_device_unplug(const char *dev_path, int optical)
431 {
432         int rc, fd;
433
434         for (;optical;) {
435                 pb_log("poll for optical drive removal ...\n");
436                 fd = open(dev_path, O_RDONLY|O_NONBLOCK);
437                 if (fd < 0)
438                         return EXIT_FAILURE;
439                 rc = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT);
440                 close(fd);
441                 if (rc != CDS_DISC_OK)
442                         return EXIT_SUCCESS;
443                 pb_log("no... waiting\n");
444                 detach_and_sleep(REMOVABLE_SLEEP_DELAY);
445         }
446
447         /* Fall back to bare open() */
448         for (;;) {
449                 pb_log("poll for non-optical drive removal ...\n");
450                 fd = open(dev_path, O_RDONLY);
451                 if (fd < 0 && errno != ENOMEDIUM)
452                         return EXIT_FAILURE;
453                 close(fd);
454                 if (fd < 0)
455                         return EXIT_SUCCESS;
456                 pb_log("no... waiting\n");
457                 detach_and_sleep(REMOVABLE_SLEEP_DELAY);
458         }
459 }
460
461 static int poll_removable_device(const char *sysfs_path,
462                                  const char *dev_path)
463 {
464         int rc, mounted, optical = -1;
465        
466         for (;;) {
467                 rc = poll_device_plug(dev_path, &optical);
468                 if (rc == EXIT_FAILURE)
469                         return rc;
470                 rc = found_new_device(dev_path);
471                 mounted = (rc == EXIT_SUCCESS);
472
473                 poll_device_unplug(dev_path, optical);
474
475                 remove_device(dev_path);
476
477                 /* Unmount it repeatedly, if needs be */
478                 while (mounted && !unmount_device(dev_path))
479                         ;
480                 detach_and_sleep(1);
481         }
482 }
483
484 int main(int argc, char **argv)
485 {
486         char *dev_path, *action;
487         int rc;
488
489         action = getenv("ACTION");
490
491         logf = fopen("/var/tmp/petitboot-udev-helpers.log", "a");
492         if (!logf)
493                 logf = stdout;
494         pb_log("%d started\n", getpid());
495         rc = EXIT_SUCCESS;
496
497         if (!action) {
498                 pb_log("missing environment?\n");
499                 return EXIT_FAILURE;
500         }
501
502         if (connect_to_socket())
503                 return EXIT_FAILURE;
504
505         if (streq(action, "fake")) {
506                 pb_log("fake mode");
507
508                 add_device(&fake_boot_devices[0]);
509                 add_boot_option(&fake_boot_options[0]);
510                 add_boot_option(&fake_boot_options[1]);
511                 add_boot_option(&fake_boot_options[2]);
512                 add_device(&fake_boot_devices[1]);
513                 add_boot_option(&fake_boot_options[3]);
514
515                 return EXIT_SUCCESS;
516         }
517
518         dev_path = getenv("DEVNAME");
519         if (!dev_path) {
520                 pb_log("missing environment?\n");
521                 return EXIT_FAILURE;
522         }
523
524         if (is_ignored_device(dev_path))
525                 return EXIT_SUCCESS;
526
527         if (streq(action, "add")) {
528                 char *sysfs_path = getenv("DEVPATH");
529                 if (sysfs_path && is_removable_device(sysfs_path))
530                         rc = poll_removable_device(sysfs_path, dev_path);
531                 else
532                         rc = found_new_device(dev_path);
533         } else if (streq(action, "remove")) {
534                 pb_log("%s removed\n", dev_path);
535
536                 remove_device(dev_path);
537
538                 /* Unmount it repeatedly, if needs be */
539                 while (!unmount_device(dev_path))
540                         ;
541
542         } else {
543                 pb_log("invalid action '%s'\n", action);
544                 rc = EXIT_FAILURE;
545         }
546         return rc;
547 }