/* * cdinit - start up udev, find the squashfs, and get the hell out of dodge * Copyright (c) 2016 Adélie Linux Team. All rights reserved. * Licensed under the NCSA open source license. * See LICENSE file included with this source for more information. */ #include /* errno */ #include /* ioctl */ #include /* LOOP_SET_FD */ #include /* bool */ #include /* stderr, fprintf */ #include /* EXIT_FAILURE */ #include /* strlen */ #include /* mount */ #include /* mkdir */ #include /* waitpid, W* macros */ #include /* exec, fork, etc */ #include /* blkid_get_tag_value */ #include /* udev* */ /*! * @brief Invoke a specified command. Return if it is run successfully. * @param command User-readable description of what command is to run. * @param path The full on-disk path to the executable to run. * @param argv The full argument list to run. * @returns true if command runs successfully, false otherwise. */ bool cdi_invoke(const char *command, const char *path, char * const argv[]) { pid_t our_pid; fprintf(stdout, " * Starting %s... ", command); our_pid = fork(); if(our_pid == 0) { execv(path, argv); fprintf(stderr, "could not start %s: %s\n", command, strerror(errno)); return 255; } else if(our_pid == -1) { fprintf(stderr, "[ !! ] failed to fork: %s\n", strerror(errno)); return false; } else { int status, code; waitpid(our_pid, &status, 0); if(!WIFEXITED(status)) { fprintf(stderr, "[ !! ] %s caught signal\n", command); return false; } code = WEXITSTATUS(status); if(code != 0) { fprintf(stderr, "[ !! ] %s exited with error code %d\n", command, code); return false; } } fprintf(stdout, "[ ok ]\n"); return true; } /* We need to test each block device in the system, in case there are * multiple drives that all have media present. We can narrow it down, * however: * * - We know that our device will have 'adelie.squashfs'. * - We know it will be mountable without external helpers, as ISO9660, * FAT, and HFS+ are built in to the kernel. */ bool cdi_find_media(void) { struct udev *udev; struct udev_enumerate *dev_list; struct udev_list_entry *first, *candidate; struct udev_device *device = NULL; udev = udev_new(); if(udev == NULL) { fprintf(stderr, "Cannot establish link to udev.\n"); return false; } dev_list = udev_enumerate_new(udev); if(dev_list == NULL) { fprintf(stderr, "Cannot create enumerator (memory?)\n"); return false; } udev_enumerate_add_match_subsystem(dev_list, "block"); #ifdef DISKONLY /* support booting off USB partition unless DISKONLY */ udev_enumerate_add_match_property(dev_list, "DEVTYPE", "disk"); #endif udev_enumerate_scan_devices(dev_list); first = udev_enumerate_get_list_entry(dev_list); if(first == NULL) { fprintf(stderr, "No block devices found.\n"); fprintf(stderr, "This system cannot boot Adélie Linux " "without additional drivers.\n"); return false; } udev_list_entry_foreach(candidate, first) { const char *path = udev_list_entry_get_name(candidate); char *fstype = NULL; if(device != NULL) { udev_device_unref(device); } device = udev_device_new_from_syspath(udev, path); errno = 0; const char *dev_node = udev_device_get_devnode(device); if(dev_node == NULL) { continue; /* worthless */ } #ifdef DEBUG fprintf(stderr, "cdi_find_media: testing candidate %s\n", dev_node); #endif if((fstype = blkid_get_tag_value(NULL, "TYPE", dev_node)) == NULL) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s has unknown FS\n", dev_node); #endif continue; } if(mount(dev_node, "/media", fstype, MS_RDONLY, NULL) != 0) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s cannot be mounted:" " %s\n", dev_node, strerror(errno)); #endif free(fstype); continue; } free(fstype); if(access("/media/adelie.squashfs", F_OK) != 0) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s has no squash root:" " %s\n", dev_node, strerror(errno)); #endif umount("/media"); continue; } #ifdef DEBUG fprintf(stderr, "cdi_find_media: LiveFS located at %s\n", dev_node); #endif int squash_fd = open("/media/adelie.squashfs", O_RDONLY); if(squash_fd == -1) { umount("/media"); continue; } int dev_fd = open("/dev/loop4", O_RDWR); if(dev_fd == -1) { close(squash_fd); umount("/media"); continue; } ioctl(dev_fd, LOOP_SET_FD, squash_fd); close(squash_fd); if(mount("/dev/loop4", "/lowerroot", "squashfs", MS_RDONLY, NULL) != 0) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s contained invalid" " LiveFS image: %s\n", dev_node, strerror(errno)); #endif ioctl(dev_fd, LOOP_CLR_FD, 0); close(dev_fd); umount("/media"); continue; } close(dev_fd); udev_device_unref(device); device = NULL; break; } udev_enumerate_unref(dev_list); udev_unref(udev); return (access("/lowerroot/sbin/init", F_OK) == 0); } int main(void) { pid_t our_pid; #ifndef DEBUG if(getpid() != 1) { fprintf(stderr, "This application can only boot live media.\n"); return EXIT_FAILURE; } #endif fprintf(stdout, " * Adélie Linux is starting up...\n\n"); if(mount("none", "/dev", "devtmpfs", 0, NULL) != 0) { fprintf(stderr, "FATAL: Can't mount devtmpfs at /dev: %s\n", strerror(errno)); return EXIT_FAILURE; } if(mount("none", "/proc", "proc", 0, NULL) != 0) { fprintf(stderr, "FATAL: Can't mount early /proc: %s\n", strerror(errno)); return EXIT_FAILURE; } if(mount("none", "/sys", "sysfs", 0, NULL) != 0) { fprintf(stderr, "FATAL: Can't mount early /sys: %s\n", strerror(errno)); return EXIT_FAILURE; } /* Our entire goal for this system is to be small and fast, in both * execution and code. This is not the place for bloat and this is not * the place to do a tech demo of some shiny new API. * * Installers and rescue media must be very stable. The less surface * this system has, the less can go wrong. * * As we are pid1, we first need to start up udev. */ { char * const argv[] = {"udevd", "--daemon", #ifdef DEBUG "--debug", #endif "--event-timeout=30", "--resolve-names=never", (char *)0}; if(!cdi_invoke("early udevd", "/sbin/udevd", argv)) { return EXIT_FAILURE; } } /* udev is started. Now we need to communicate. * * First, we trigger block device uevents. This will ensure that all * block devices supported by the kernel have /dev entries that we can * use. */ { char * const argv[] = {"udevadm", "trigger", "--action=add", "--subsystem-match=blocK", 0}; if(!cdi_invoke("block uevents", "/sbin/udevadm", argv)) { return EXIT_FAILURE; } } /* Now we need to iterate over the available block devices, trying to * find our CD media. */ if(!cdi_find_media()) { fprintf(stderr, "FATAL: no boot media found\n"); return EXIT_FAILURE; } { char * const argv[] = {"udevadm", "control", "-e", 0}; cdi_invoke("clean up", "/sbin/udevadm", argv); } /* Create our OverlayFS mount, so everything is writable. * TODO: USB-based persistence */ if(mount("tmpfs", "/upperroot", "tmpfs", 0, NULL) != 0) { fprintf(stderr, "FATAL: could not mount tmpfs: %s\n", strerror(errno)); return EXIT_FAILURE; } /* If this fails, we'll find out below */ mkdir("/upperroot/.overlay_work", S_IRWXU); if(mount("overlay", "/newroot", "overlay", 0, "lowerdir=/lowerroot," "upperdir=/upperroot," "workdir=/upperroot/.overlay_work") != 0) { fprintf(stderr, "FATAL: could not mount overlayfs: %s\n", strerror(errno)); return EXIT_FAILURE; } mount("/dev", "/newroot/dev", NULL, MS_MOVE, NULL); mount("/media", "/newroot/media/live", NULL, MS_MOVE, NULL); mount("/proc", "/newroot/proc", NULL, MS_MOVE, NULL); mount("/sys", "/newroot/sys", NULL, MS_MOVE, NULL); chdir("/newroot"); if(mount("/newroot", "/", NULL, MS_MOVE, NULL) != 0) { fprintf(stderr, "FATAL: could not pivot to /newroot: %s\n", strerror(errno)); return EXIT_FAILURE; } chroot("."); execl("/sbin/init", "init", (char *)0); fprintf(stderr, "Going nowhere without my new root's init\n"); return EXIT_FAILURE; }