summaryrefslogtreecommitdiff
path: root/cdinit.c
blob: 1cc7ba87148423b622e06a088a724fac3918bd67 (plain) (blame)
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*
 * cdinit - start up udev, find the squashfs, and get the hell out of dodge
 * Copyright (c) 2016 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 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 'adelie.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(void)
{
	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(access("/media/adelie.squashfs", F_OK) != 0)
		{
#ifdef DEBUG
			fprintf(stderr, "cdi_find_media: %s: system not found:"
					" %s\n", dev_node, strerror(errno));
#endif
			umount("/media");
			continue;
		}

#ifdef DEBUG
		fprintf(stderr, "cdi_find_media: LiveFS located at %s\n",
			dev_node);
#endif

		int squash_fd = open("/media/adelie.squashfs", O_RDONLY);
		if(squash_fd == -1)
		{
			umount("/media");
			continue;
		}

		int dev_fd = open("/dev/loop4", O_RDWR);
		if(dev_fd == -1)
		{
			close(squash_fd);
			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);
			umount("/media");
			continue;
		}

		close(dev_fd);
		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;
	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;
		}
	}

	/* Now we need to iterate over the available block devices, trying to
	 * find our CD media.
	 */
	found = cdi_find_media();
	while(!found && tries--)
	{
		fprintf(stderr, "Attempting to acquiesce...\n");
		sleep(5);
		found = cdi_find_media();
	}

	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;
}