/*
* 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.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 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)
{
int cmd_fd;
if(access("/proc/cmdline", R_OK) != 0)
{
fprintf(stderr, "can't access kernel command line\n");
return def;
}
cmd_fd = open("/proc/cmdline", O_RDONLY);
if(cmd_fd == -1)
{
fprintf(stderr, "can't open kernel command line\n");
return def;
}
close(cmd_fd);
return 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;
}