summaryrefslogtreecommitdiff
path: root/cdinit.c
diff options
context:
space:
mode:
authorA. Wilcox <AWilcox@Wilcox-Tech.com>2016-12-09 02:41:11 -0600
committerA. Wilcox <AWilcox@Wilcox-Tech.com>2016-12-09 02:41:11 -0600
commit9acabb63bef0cdf55a2bf490edef91cb8b2f7190 (patch)
treeb9f476610ffae6d1fadecd6f4a2ac1712646492a /cdinit.c
downloadimage-9acabb63bef0cdf55a2bf490edef91cb8b2f7190.tar.gz
image-9acabb63bef0cdf55a2bf490edef91cb8b2f7190.tar.bz2
image-9acabb63bef0cdf55a2bf490edef91cb8b2f7190.tar.xz
image-9acabb63bef0cdf55a2bf490edef91cb8b2f7190.zip
Initial commit to Git
Diffstat (limited to 'cdinit.c')
-rw-r--r--cdinit.c329
1 files changed, 329 insertions, 0 deletions
diff --git a/cdinit.c b/cdinit.c
new file mode 100644
index 0000000..2990106
--- /dev/null
+++ b/cdinit.c
@@ -0,0 +1,329 @@
+/*
+ * 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.h> /* errno */
+#include <fcntl.h> /* ioctl */
+#include <linux/loop.h> /* LOOP_SET_FD */
+#include <stdbool.h> /* bool */
+#include <stdio.h> /* stderr, fprintf */
+#include <stdlib.h> /* EXIT_FAILURE */
+#include <string.h> /* strlen */
+#include <sys/mount.h> /* mount */
+#include <sys/stat.h> /* mkdir */
+#include <sys/wait.h> /* waitpid, W* macros */
+#include <unistd.h> /* exec, fork, etc */
+
+#include <blkid/blkid.h>/* blkid_get_tag_value */
+#include <libudev.h> /* 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(stdout, "[ !! ] failed to fork: %s\n", strerror(errno));
+ return false;
+ }
+ else
+ {
+ int status, code;
+
+ waitpid(our_pid, &status, 0);
+ if(!WIFEXITED(status))
+ {
+ fprintf(stdout, "[ !! ] %s caught signal\n", command);
+ return false;
+ }
+
+ code = WEXITSTATUS(status);
+ if(code != 0)
+ {
+ fprintf(stdout, "[ !! ] %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(stdout, "No block devices found.\n");
+ fprintf(stdout, "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", "/newroot", "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("/newroot/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(stdout, "FATAL: Can't mount devtmpfs at /dev: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if(mount("none", "/proc", "proc", 0, NULL) != 0)
+ {
+ fprintf(stdout, "FATAL: Can't mount early /proc: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ if(mount("none", "/sys", "sysfs", 0, NULL) != 0)
+ {
+ fprintf(stdout, "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(stdout, "FATAL: no boot media found\n");
+ return EXIT_FAILURE;
+ }
+
+ {
+ char * const argv[] = {"udevadm", "control", "-e", 0};
+ cdi_invoke("clean up", "/sbin/udevadm", argv);
+ }
+
+ 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, "could not pivot to /newroot: %s\n",
+ strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ chroot(".");
+ execl("/sbin/init", "init", (char *)0);
+
+ fprintf(stdout, "Going nowhere without my new root's init\n");
+ return EXIT_FAILURE;
+}