summaryrefslogtreecommitdiffstats
path: root/usr/kinit/initrd.c
blob: 5833f2f2c01f5d49a4c0041671c9f52b3a513614 (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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
 * Handle initrd, thus putting the backwards into backwards compatible
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include "do_mounts.h"
#include "kinit.h"
#include "xpio.h"

#define BUF_SIZE	65536	/* Should be a power of 2 */

/*
 * Copy the initrd to /dev/ram0, copy from the end to the beginning
 * to avoid taking 2x the memory.
 */
static int rd_copy_uncompressed(int ffd, int dfd)
{
	char buffer[BUF_SIZE];
	off_t bytes;
	struct stat st;

	dprintf("kinit: uncompressed initrd\n");

	if (ffd < 0 || fstat(ffd, &st) || !S_ISREG(st.st_mode) ||
	    (bytes = st.st_size) == 0)
		return -1;

	while (bytes) {
		ssize_t blocksize = ((bytes - 1) & (BUF_SIZE - 1)) + 1;
		off_t offset = bytes - blocksize;

		dprintf("kinit: copying %zd bytes at offset %llu\n",
			blocksize, offset);

		if (xpread(ffd, buffer, blocksize, offset) != blocksize ||
		    xpwrite(dfd, buffer, blocksize, offset) != blocksize)
			return -1;

		ftruncate(ffd, offset);	/* Free up memory */
		bytes = offset;
	}
	return 0;
}

static int rd_copy_image(const char *path)
{
	int ffd = open(path, O_RDONLY);
	int rv = -1;
	unsigned char gzip_magic[2];

	if (ffd < 0)
		goto barf;

	if (xpread(ffd, gzip_magic, 2, 0) == 2 &&
	    gzip_magic[0] == 037 && gzip_magic[1] == 0213) {
		FILE *wfd = fopen("/dev/ram0", "w");
		if (!wfd)
			goto barf;
		rv = load_ramdisk_compressed(path, wfd, 0);
		fclose(wfd);
	} else {
		int dfd = open("/dev/ram0", O_WRONLY);
		if (dfd < 0)
			goto barf;
		rv = rd_copy_uncompressed(ffd, dfd);
		close(dfd);
	}

barf:
	if (ffd >= 0)
		close(ffd);
	return rv;
}

/*
 * Run /linuxrc, for emulation of old-style initrd
 */
static int run_linuxrc(int argc, char *argv[], dev_t root_dev)
{
	int root_fd, old_fd;
	pid_t pid;
	long realroot = Root_RAM0;
	const char *ramdisk_name = "/dev/ram0";
	FILE *fp;

	dprintf("kinit: mounting initrd\n");
	mkdir("/root", 0700);
	if (!mount_block(ramdisk_name, "/root", NULL, MS_VERBOSE, NULL))
		return -errno;

	/* Write the current "real root device" out to procfs */
	dprintf("kinit: real_root_dev = %#x\n", root_dev);
	fp = fopen("/proc/sys/kernel/real-root-dev", "w");
	fprintf(fp, "%u", root_dev);
	fclose(fp);

	mkdir("/old", 0700);
	root_fd = open("/", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0);
	old_fd = open("/old", O_RDONLY|O_DIRECTORY|O_CLOEXEC, 0);

	if (root_fd < 0 || old_fd < 0)
		return -errno;

	if (chdir("/root") ||
	    mount(".", "/", NULL, MS_MOVE, NULL) || chroot("."))
		return -errno;

	pid = vfork();
	if (pid == 0) {
		setsid();
		/* Looks like linuxrc doesn't get the init environment
		   or parameters.  Weird, but so is the whole linuxrc bit. */
		execl("/linuxrc", "linuxrc", NULL);
		_exit(255);
	} else if (pid > 0) {
		dprintf("kinit: Waiting for linuxrc to complete...\n");
		while (waitpid(pid, NULL, 0) != pid)
			;
		dprintf("kinit: linuxrc done\n");
	} else {
		return -errno;
	}

	if (fchdir(old_fd) ||
	    mount("/", ".", NULL, MS_MOVE, NULL) ||
	    fchdir(root_fd) || chroot("."))
		return -errno;

	close(root_fd);
	close(old_fd);

	getintfile("/proc/sys/kernel/real-root-dev", &realroot);

	/* If realroot is Root_RAM0, then the initrd did any necessary work */
	if (realroot == Root_RAM0) {
		if (mount("/old", "/root", NULL, MS_MOVE, NULL))
			return -errno;
	} else {
		mount_root(argc, argv, (dev_t) realroot, NULL);

		/* If /root/initrd exists, move the initrd there, otherwise discard */
		if (!mount("/old", "/root/initrd", NULL, MS_MOVE, NULL)) {
			/* We're good */
		} else {
			int olddev = open(ramdisk_name, O_RDWR);
			umount2("/old", MNT_DETACH);
			if (olddev < 0 ||
			    ioctl(olddev, BLKFLSBUF, 0) ||
			    close(olddev)) {
				fprintf(stderr,
					"%s: Cannot flush initrd contents\n",
					progname);
			}
		}
	}

	rmdir("/old");
	return 0;
}

int initrd_load(int argc, char *argv[], dev_t root_dev)
{
	if (access("/initrd.image", R_OK))
		return 0;	/* No initrd */

	dprintf("kinit: initrd found\n");

	create_dev("/dev/ram0", Root_RAM0);

	if (rd_copy_image("/initrd.image") || unlink("/initrd.image")) {
		fprintf(stderr, "%s: initrd installation failed (too big?)\n",
			progname);
		return 0;	/* Failed to copy initrd */
	}

	dprintf("kinit: initrd copied\n");

	if (root_dev == Root_MULTI) {
		dprintf("kinit: skipping linuxrc: incompatible with multiple roots\n");
		/* Mounting initrd as ordinary root */
		return 0;
	}

	if (root_dev != Root_RAM0) {
		int err;
		dprintf("kinit: running linuxrc\n");
		err = run_linuxrc(argc, argv, root_dev);
		if (err)
			fprintf(stderr, "%s: running linuxrc: %s\n", progname,
				strerror(-err));
		return 1;	/* initrd is root, or run_linuxrc took care of it */
	} else {
		dprintf("kinit: permament (or pivoting) initrd, not running linuxrc\n");
		return 0;	/* Mounting initrd as ordinary root */
	}
}