1/*
2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <sys/param.h>
30#include <sys/kernel.h>
31#include <sys/proc_internal.h>
32#include <sys/systm.h>
33#include <sys/systm.h>
34#include <sys/mount_internal.h>
35#include <sys/filedesc.h>
36#include <sys/vnode_internal.h>
37#include <sys/imageboot.h>
38#include <kern/assert.h>
39
40#include <pexpert/pexpert.h>
41
42extern struct filedesc filedesc0;
43
44extern int (*mountroot)(void);
45extern char rootdevice[];
46
47#define DEBUG_IMAGEBOOT 0
48
49#if DEBUG_IMAGEBOOT
50#define DBG_TRACE(...) printf(__VA_ARGS__)
51#else
52#define DBG_TRACE(...) do {} while(0)
53#endif
54
55extern int di_root_image(const char *path, char devname[], dev_t *dev_p);
56static boolean_t imageboot_setup_new(void);
57
58#define kIBFilePrefix "file://"
59
60__private_extern__ int
61imageboot_format_is_valid(const char *root_path)
62{
63	return (strncmp(root_path, kIBFilePrefix,
64				strlen(kIBFilePrefix)) == 0);
65}
66
67static void
68vnode_get_and_drop_always(vnode_t vp)
69{
70	vnode_getalways(vp);
71	vnode_rele(vp);
72	vnode_put(vp);
73}
74
75__private_extern__ int
76imageboot_needed(void)
77{
78	int result = 0;
79	char *root_path = NULL;
80
81	DBG_TRACE("%s: checking for presence of root path\n", __FUNCTION__);
82
83	MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
84	if (root_path == NULL)
85		panic("%s: M_NAMEI zone exhausted", __FUNCTION__);
86
87	/* Check for first layer */
88	if (!(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) ||
89			PE_parse_boot_argn("rp", root_path, MAXPATHLEN) ||
90			PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN))) {
91		goto out;
92	}
93
94	/* Sanity-check first layer */
95	if (imageboot_format_is_valid(root_path)) {
96		DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
97	} else {
98		goto out;
99	}
100
101	result = 1;
102
103	/* Check for second layer */
104	if (!(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) ||
105			PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN))) {
106		goto out;
107	}
108
109	/* Sanity-check second layer */
110	if (imageboot_format_is_valid(root_path)) {
111		DBG_TRACE("%s: Found %s\n", __FUNCTION__, root_path);
112	} else {
113		panic("%s: Invalid URL scheme for %s\n",
114				__FUNCTION__, root_path);
115	}
116
117out:
118	FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI);
119
120	return (result);
121}
122
123
124/*
125 * Swaps in new root filesystem based on image path.
126 * Current root filesystem is removed from mount list and
127 * tagged MNTK_BACKS_ROOT, MNT_ROOTFS is cleared on it, and
128 * "rootvnode" is reset.  Root vnode of currentroot filesystem
129 * is returned with usecount (no iocount).
130 */
131__private_extern__ int
132imageboot_mount_image(const char *root_path, int height)
133{
134	dev_t       	dev;
135	int 		error;
136	vnode_t 	old_rootvnode = NULL;
137	vnode_t 	newdp;
138	mount_t 	new_rootfs;
139
140	error = di_root_image(root_path, rootdevice, &dev);
141	if (error) {
142		panic("%s: di_root_image failed: %d\n", __FUNCTION__, error);
143	}
144
145	rootdev = dev;
146	mountroot = NULL;
147	printf("%s: root device 0x%x\n", __FUNCTION__, rootdev);
148	error = vfs_mountroot();
149	if (error != 0) {
150		panic("vfs_mountroot() failed.\n");
151	}
152
153	/*
154	 * Get the vnode for '/'.
155	 * Set fdp->fd_fd.fd_cdir to reference it.
156	 */
157	if (VFS_ROOT(TAILQ_LAST(&mountlist,mntlist), &newdp, vfs_context_kernel()))
158		panic("%s: cannot find root vnode", __FUNCTION__);
159
160	if (rootvnode != NULL) {
161		/* remember the old rootvnode, but remove it from mountlist */
162		mount_t 	old_rootfs;
163
164		old_rootvnode = rootvnode;
165		old_rootfs = rootvnode->v_mount;
166
167		mount_list_remove(old_rootfs);
168
169		mount_lock(old_rootfs);
170#ifdef CONFIG_IMGSRC_ACCESS
171		old_rootfs->mnt_kern_flag |= MNTK_BACKS_ROOT;
172#endif /* CONFIG_IMGSRC_ACCESS */
173		old_rootfs->mnt_flag &= ~MNT_ROOTFS;
174		mount_unlock(old_rootfs);
175	}
176
177	/* switch to the new rootvnode */
178	rootvnode = newdp;
179
180	new_rootfs = rootvnode->v_mount;
181	mount_lock(new_rootfs);
182	new_rootfs->mnt_flag |= MNT_ROOTFS;
183	mount_unlock(new_rootfs);
184
185	vnode_ref(newdp);
186	vnode_put(newdp);
187	filedesc0.fd_cdir = newdp;
188	DBG_TRACE("%s: root switched\n", __FUNCTION__);
189
190	if (old_rootvnode != NULL) {
191#ifdef CONFIG_IMGSRC_ACCESS
192	    if (height >= 0 && PE_imgsrc_mount_supported()) {
193		imgsrc_rootvnodes[height] = old_rootvnode;
194	    } else {
195		vnode_get_and_drop_always(old_rootvnode);
196	    }
197#else
198	    height = 0; /* keep the compiler from complaining */
199	    vnode_get_and_drop_always(old_rootvnode);
200#endif /* CONFIG_IMGSRC_ACCESS */
201	}
202	return 0;
203}
204
205static boolean_t
206imageboot_setup_new()
207{
208	int error;
209	char *root_path = NULL;
210	int height = 0;
211	boolean_t done = FALSE;
212
213	MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
214	assert(root_path != NULL);
215
216	if(PE_parse_boot_argn(IMAGEBOOT_CONTAINER_ARG, root_path, MAXPATHLEN) == TRUE) {
217		printf("%s: container image url is %s\n", __FUNCTION__, root_path);
218		error = imageboot_mount_image(root_path, height);
219		if (error != 0) {
220			panic("Failed to mount container image.");
221		}
222
223		height++;
224	}
225
226	if (PE_parse_boot_argn(IMAGEBOOT_ROOT_ARG, root_path, MAXPATHLEN) == FALSE) {
227		if (height > 0) {
228			panic("%s specified without %s?\n", IMAGEBOOT_CONTAINER_ARG, IMAGEBOOT_ROOT_ARG);
229		}
230		goto out;
231
232	}
233
234	printf("%s: root image url is %s\n", __FUNCTION__, root_path);
235
236	error = imageboot_mount_image(root_path, height);
237	if (error != 0) {
238		panic("Failed to mount root image.");
239	}
240
241	done = TRUE;
242
243out:
244	FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI);
245	return done;
246}
247
248__private_extern__ void
249imageboot_setup()
250{
251	int         error = 0;
252	char *root_path = NULL;
253
254	DBG_TRACE("%s: entry\n", __FUNCTION__);
255
256	if (rootvnode == NULL) {
257		panic("imageboot_setup: rootvnode is NULL.");
258	}
259
260	/*
261	 * New boot-arg scheme:
262	 * 	root-dmg : the dmg that will be the root filesystem.
263	 * 	container-dmg : an optional dmg that contains the root-dmg.
264	 */
265	if (imageboot_setup_new()) {
266		return;
267	}
268
269	MALLOC_ZONE(root_path, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK);
270	assert(root_path != NULL);
271
272	/*
273	 * Look for outermost disk image to root from.  If we're doing a nested boot,
274	 * there's some sense in which the outer image never needs to be the root filesystem,
275	 * but it does need very similar treatment: it must not be unmounted, needs a fake
276	 * device vnode created for it, and should not show up in getfsstat() until exposed
277	 * with MNT_IMGSRC. We just make it the temporary root.
278	 */
279	if((PE_parse_boot_argn("rp", root_path, MAXPATHLEN) == FALSE) &&
280		(PE_parse_boot_argn("rp0", root_path, MAXPATHLEN) == FALSE)) {
281		panic("%s: no valid path to image.\n", __FUNCTION__);
282	}
283
284	printf("%s: root image url is %s\n", __FUNCTION__, root_path);
285
286	error = imageboot_mount_image(root_path, 0);
287	if (error) {
288		panic("Failed on first stage of imageboot.");
289	}
290
291	/*
292	 * See if we are rooting from a nested image
293	 */
294	if(PE_parse_boot_argn("rp1", root_path, MAXPATHLEN) == FALSE) {
295		goto done;
296	}
297
298	printf("%s: second level root image url is %s\n", __FUNCTION__, root_path);
299
300	/*
301	 * If we fail to set up second image, it's not a given that we
302	 * can safely root off the first.
303	 */
304	error = imageboot_mount_image(root_path, 1);
305	if (error) {
306		panic("Failed on second stage of imageboot.");
307	}
308
309done:
310	FREE_ZONE(root_path, MAXPATHLEN, M_NAMEI);
311
312	DBG_TRACE("%s: exit\n", __FUNCTION__);
313
314	return;
315}
316