summaryrefslogblamecommitdiff
path: root/cdinit.c
blob: f5ccb22775b6ba730d4e416adaae5d14becb46c0 (plain) (tree)
1
2
3

                                                                           
                                                                    





















                                                                   









                                                                       

                                  


   





















                                                                            
                                                                                








                                             
                                                                              





                                           
                                                                                













                                                                       
                                                              


                                                                       
                                            








                                                  
                                                                 


















                                                                               

                                                                        





















                                                                       


                                                                                  
                                                                           






                                                                           
                                                                      
                                                                            




                                     







                                                                             

                 
                                                                               

                                                                            
                                   








                                                                         
                                                                        

                                   
                                   







                                                        
                                   






                                                      
                                                                           








                                                                              
                                   




                                         
                           







                                          
                                                           






                      
                           

                                












                                                                                
                                                                            





                                                        
                                                                       





                                                        
                                                                      









































                                                                               


                                                            


                                                                             
                                       

                                
                                               

                                                                


                  
         
                                                                







                                                                      










                                                                     

                                                                                           


                                                     
                                             






                                                                         







                                                                    
                                                                           






                                               
                                                                      

                            
/*
 * 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)
{
	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;
}