summaryrefslogtreecommitdiffhomepage
path: root/util-linux/switch_root.c
blob: 9815a6d9e637b3c0c1a45f077b62288aaaf1842a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* vi:set ts=4:*/
/* Copyright 2005 Rob Landley <rob@landley.net>
 *
 * Switch from rootfs to another filesystem as the root of the mount tree.
 *
 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
 */

#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/vfs.h>
#include <unistd.h>

#include "busybox.h"

// Make up for header deficiencies.

#ifndef RAMFS_MAGIC
#define RAMFS_MAGIC		0x858458f6
#endif

#ifndef TMPFS_MAGIC
#define TMPFS_MAGIC		0x01021994
#endif

#ifndef MS_MOVE
#define MS_MOVE			8192
#endif

dev_t rootdev;

// Recursively delete contents of rootfs.

static void delete_contents(char *directory)
{
	DIR *dir;
	struct dirent *d;
	struct stat st;

	// Don't descend into other filesystems
	if (lstat(directory,&st) || st.st_dev != rootdev) return;

	// Recursively delete the contents of directories.
	if (S_ISDIR(st.st_mode)) {
		if((dir = opendir(directory))) {
			while ((d = readdir(dir))) {
				char *newdir=d->d_name;

				// Skip . and ..
				if(*newdir=='.' && (!newdir[1] || (newdir[1]=='.' && !newdir[2])))
					continue;

				// Recurse to delete contents
				newdir = alloca(strlen(directory) + strlen(d->d_name) + 2);
				sprintf(newdir, "%s/%s", directory, d->d_name);
				delete_contents(newdir);
			}
			closedir(dir);

			// Directory should now be empty.  Zap it.
			rmdir(directory);
		}

	// It wasn't a directory.  Zap it.

	} else unlink(directory);
}

int switch_root_main(int argc, char *argv[])
{
	char *newroot, *console=NULL;
	struct stat st1, st2;
	struct statfs stfs;

	// Parse args (-c console)

	bb_opt_complementally="-2";
	bb_getopt_ulflags(argc,argv,"c:",&console);

	// Change to new root directory and verify it's a different fs.

	newroot=argv[optind++];

	if (chdir(newroot) || lstat(".", &st1) || lstat("/", &st2) ||
		st1.st_dev == st2.st_dev)
	{
		bb_error_msg_and_die("bad newroot %s",newroot);
	}
	rootdev=st2.st_dev;

	// Additional sanity checks: we're about to rm -rf /,  so be REALLY SURE
	// we mean it.  (I could make this a CONFIG option, but I would get email
	// from all the people who WILL eat their filesystemss.)

	if (lstat("/init", &st1) || !S_ISREG(st1.st_mode) || statfs("/", &stfs) ||
		(stfs.f_type != RAMFS_MAGIC && stfs.f_type != TMPFS_MAGIC) ||
		getpid() != 1)
	{
		bb_error_msg_and_die("not rootfs");
	}

	// Zap everything out of rootdev

	delete_contents("/");

	// Overmount / with newdir and chroot into it.  The chdir is needed to
	// recalculate "." and ".." links.

	if (mount(".", "/", NULL, MS_MOVE, NULL) || chroot(".") || chdir("/"))
		bb_error_msg_and_die("moving root");

	// If a new console specified, redirect stdin/stdout/stderr to that.

	if (console) {
		close(0);
		if(open(console, O_RDWR) < 0)
			bb_error_msg_and_die("Bad console '%s'",console);
		dup2(0, 1);
		dup2(0, 2);
	}

	// Exec real init.  (This is why we must be pid 1.)
	execv(argv[optind],argv+optind);
	bb_error_msg_and_die("Bad init '%s'",argv[optind]);
}