master xplshn/aruu / cmd / linux / switch_root.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include <sys/mount.h>
  5#include <sys/stat.h>
  6#include <sys/vfs.h>
  7
  8#include <dirent.h>
  9#include <fcntl.h>
 10#include <limits.h>
 11#include <stdio.h>
 12#include <stdlib.h>
 13#include <string.h>
 14#include <unistd.h>
 15
 16#include "util.h"
 17
 18#define RAMFS_MAGIC		0x858458f6	/* some random number */
 19#define TMPFS_MAGIC		0x01021994
 20
 21static void
 22delete_content(const char *dir, dev_t curdevice)
 23{
 24	char path[PATH_MAX];
 25	DIR *d;
 26	struct stat st;
 27	struct dirent *dent;
 28
 29	/* don't dive into other filesystems */
 30	if (lstat(dir, &st) < 0 || st.st_dev != curdevice)
 31		return;
 32	if (!(d = opendir(dir)))
 33		return;
 34	while ((dent = readdir(d))) {
 35		if (strcmp(dent->d_name, ".") == 0 ||
 36		    strcmp(dent->d_name, "..") == 0)
 37			continue;
 38
 39		/* build path and dive deeper */
 40		if (strlcpy(path, dir, sizeof(path)) >= sizeof(path))
 41			eprintf("path too long\n");
 42		if (path[strlen(path) - 1] != '/')
 43			if (strlcat(path, "/", sizeof(path)) >= sizeof(path))
 44				eprintf("path too long\n");
 45		if (strlcat(path, dent->d_name, sizeof(path)) >= sizeof(path))
 46			eprintf("path too long\n");
 47
 48		if (lstat(path, &st) < 0)
 49			weprintf("lstat %s:", path);
 50
 51		if (S_ISDIR(st.st_mode)) {
 52			delete_content(path, curdevice);
 53			if (rmdir(path) < 0)
 54				weprintf("rmdir %s:", path);
 55		} else {
 56			if (unlink(path) < 0)
 57				weprintf("unlink %s:", path);
 58		}
 59	}
 60	closedir(d);
 61}
 62
 63static void
 64usage(void)
 65{
 66	eprintf("usage: %s [-c console] [newroot] [init] (PID 1)\n", argv0);
 67}
 68
 69// ?man switch_root: switch to another root filesystem
 70// ?man arguments: newroot] [init] (PID 1)
 71// ?man switch to another filesystem as the root of the mount tree
 72int
 73main(int argc, char *argv[])
 74{
 75	char *console = NULL;
 76	dev_t curdev;
 77	struct stat st;
 78	struct statfs stfs;
 79
 80	ARGBEGIN {
 81	// ?man -c:str: print count or perform stdout action
 82	case 'c':
 83		console = EARGF(usage());
 84		break;
 85	default:
 86		usage();
 87	} ARGEND;
 88
 89	/* check number of args and if we are PID 1 */
 90	if (argc != 2 || getpid() != 1)
 91		usage();
 92
 93	/* chdir to newroot and make sure it's a different fs */
 94	if (chdir(argv[0]))
 95		eprintf("chdir %s:", argv[0]);
 96
 97	if (stat("/", &st))
 98		eprintf("stat %s:", "/");
 99
100	curdev = st.st_dev;
101	if (stat(".", &st))
102		eprintf("stat %s:", ".");
103	if (st.st_dev == curdev)
104		usage();
105
106	/* avoids trouble with real filesystems */
107	if (stat("/init", &st) || !S_ISREG(st.st_mode))
108		eprintf("/init is not a regular file\n");
109
110	statfs("/", &stfs);
111	if ((unsigned)stfs.f_type != RAMFS_MAGIC && (unsigned)stfs.f_type != TMPFS_MAGIC)
112		eprintf("current filesystem is not a RAMFS or TMPFS\n");
113
114	/* wipe / */
115	delete_content("/", curdev);
116
117	/* overmount / with newroot and chroot into it */
118	if (mount(".", "/", NULL, MS_MOVE, NULL))
119		eprintf("mount %s:", ".");
120
121	if (chroot("."))
122		eprintf("chroot failed\n");
123
124	/* if -c is set, redirect stdin/stdout/stderr to console */
125	if (console) {
126		close(0);
127		if (open(console, O_RDWR) == -1)
128			eprintf("open %s:", console);
129		dup2(0, 1);
130		dup2(0, 2);
131	}
132
133	/* execute init */
134	execv(argv[1], argv);
135	eprintf("can't execute '%s':", argv[1]);
136	return 1;
137}