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