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