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