/* * cdinit - start up udev, find the squashfs, and get the hell out of dodge * Copyright (c) 2016-2018 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 Grab a parameter out of /proc/cmdline, if it is set. * @param param The parameter you wish to read. * @param def The default value if no value is set (or NULL). * @returns The value of the parameter as set in /proc/cmdline, or * the value of `default` if no value is set. * @note If a parameter is passed on the command line but has no value * (ex: 'debug'), its value will not be returned. */ const char *get_param(const char *param, const char *def) { char *str = getenv(param); return str ? str : def; } /*! * @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 the specified 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(const char *squash_name) { 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 udev link.\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 */ } if((fstype = blkid_get_tag_value(NULL, "TYPE", dev_node)) == NULL) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s: unknown FS\n", dev_node); #endif continue; } if(mount(dev_node, "/media", fstype, MS_RDONLY, NULL) != 0) { fprintf(stderr, "cdi_find_media: mounting %s:" " %s\n", dev_node, strerror(errno)); free(fstype); continue; } free(fstype); if(chdir("/media") != 0) { fprintf(stderr, "cdi_find_media: error accessing %s:" " %s\n", dev_node, strerror(errno)); umount("/media"); continue; } if(faccessat(AT_FDCWD, squash_name, F_OK, 0) != 0) { #ifdef DEBUG fprintf(stderr, "cdi_find_media: %s: system not found:" " %s\n", dev_node, strerror(errno)); #endif chdir("/"); umount("/media"); continue; } #ifdef DEBUG fprintf(stderr, "cdi_find_media: LiveFS located at %s\n", dev_node); #endif int squash_fd = openat(AT_FDCWD, squash_name, O_RDONLY); if(squash_fd == -1) { chdir("/"); umount("/media"); continue; } int dev_fd = open("/dev/loop4", O_RDWR); if(dev_fd == -1) { close(squash_fd); chdir("/"); 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); chdir("/"); umount("/media"); continue; } close(dev_fd); chdir("/"); 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; const char *squash; bool found = false; unsigned char tries = 4; #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; } } /* Figure out what our squashfs is named. */ squash = get_param("squashroot", "adelie.squashfs"); /* Now we need to iterate over the available block devices, trying to * find our CD media. */ found = cdi_find_media(squash); while(!found && tries--) { found = cdi_find_media(squash); fprintf(stderr, "Attempting to acquiesce...\n"); sleep(5); } if(!found) { 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 | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); mkdir("/upperroot/.root", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); if(mount("overlay", "/newroot", "overlay", 0, "lowerdir=/lowerroot," "upperdir=/upperroot/.root," "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; }