1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * This file contains all the functions that implement the following
28  * GRUB commands:
29  *	kernel, kernel$, module, module$, findroot, bootfs
30  * Return 0 on success, errno on failure.
31  */
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <assert.h>
35 #include <alloca.h>
36 #include <errno.h>
37 #include <strings.h>
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <sys/types.h>
41 #include <sys/fs/ufs_mount.h>
42 #include <sys/dktp/fdisk.h>
43 #if defined(__i386)
44 #include <sys/x86_archext.h>
45 #endif /* __i386 */
46 
47 #include "libgrub_impl.h"
48 
49 #define	RESET_MODULE(barg)	((barg)->gb_module[0] = 0)
50 
51 #if defined(__i386)
52 static const char cpuid_dev[] = "/dev/cpu/self/cpuid";
53 
54 /*
55  * Return 1 if the system supports 64-bit mode, 0 if it doesn't,
56  * or -1 on failure.
57  */
58 static int
59 cpuid_64bit_capable(void)
60 {
61 	int fd, ret = -1;
62 	struct {
63 		uint32_t cp_eax, cp_ebx, cp_ecx, cp_edx;
64 	} cpuid_regs;
65 
66 	if ((fd = open(cpuid_dev, O_RDONLY)) == -1)
67 		return (ret);
68 
69 	if (pread(fd, &cpuid_regs, sizeof (cpuid_regs), 0x80000001) ==
70 	    sizeof (cpuid_regs))
71 		ret = ((CPUID_AMD_EDX_LM & cpuid_regs.cp_edx) != 0);
72 
73 	(void) close(fd);
74 	return (ret);
75 }
76 #endif /* __i386 */
77 
78 
79 /*
80  * Expand $ISAIDR
81  */
82 #if !defined(__i386)
83 /* ARGSUSED */
84 #endif /* __i386 */
85 static size_t
86 barg_isadir_var(char *var, int sz)
87 {
88 #if defined(__i386)
89 	if (cpuid_64bit_capable() == 1)
90 		return (strlcpy(var, "amd64", sz));
91 #endif /* __i386 */
92 
93 	var[0] = 0;
94 	return (0);
95 }
96 
97 /*
98  * Expand $ZFS-BOOTFS
99  */
100 static size_t
101 barg_bootfs_var(const grub_barg_t *barg, char *var, int sz)
102 {
103 	int n;
104 
105 	assert(barg);
106 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) == 0) {
107 		n = snprintf(var, sz, "zfs-bootfs=%s,bootpath=\"%s\"",
108 		    barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev,
109 		    barg->gb_root.gr_physpath);
110 	} else	{
111 		var[0] = 0;
112 		n = 0;
113 	}
114 	return (n);
115 }
116 
117 /*
118  * Expand all the variables without appending them more than once.
119  */
120 static int
121 expand_var(char *arg, size_t argsz, const char *var, size_t varsz,
122     char *val, size_t valsz)
123 {
124 	char	*sp = arg;
125 	size_t	sz = argsz, len;
126 	char	*buf, *dst, *src;
127 	int	ret = 0;
128 
129 	buf = alloca(argsz);
130 	dst = buf;
131 
132 	while ((src = strstr(sp, var)) != NULL) {
133 
134 		len = src - sp;
135 
136 		if (len + valsz > sz) {
137 			ret = E2BIG;
138 			break;
139 		}
140 
141 		(void) bcopy(sp, dst, len);
142 		(void) bcopy(val, dst + len, valsz);
143 		dst += len + valsz;
144 		sz -= len + valsz;
145 		sp = src + varsz;
146 	}
147 
148 	if (strlcpy(dst, sp, sz) >= sz)
149 		ret = E2BIG;
150 
151 	if (ret == 0)
152 		bcopy(buf, arg, argsz);
153 	return (ret);
154 }
155 
156 static int
157 match_bootfs(zfs_handle_t *zfh, void *data)
158 {
159 	int		ret;
160 	const char	*zfn;
161 	grub_barg_t	*barg = (grub_barg_t *)data;
162 
163 	ret = (zfs_get_type(zfh) == ZFS_TYPE_FILESYSTEM &&
164 	    (zfn = zfs_get_name(zfh)) != NULL &&
165 	    strcmp(barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev, zfn) == 0);
166 
167 	if (ret != 0)
168 		barg->gb_walkret = 0;
169 	else
170 		(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
171 
172 	zfs_close(zfh);
173 	return (barg->gb_walkret == 0);
174 }
175 
176 static void
177 reset_root(grub_barg_t *barg)
178 {
179 	(void) memset(&barg->gb_root, 0, sizeof (barg->gb_root));
180 	barg->gb_bootsign[0] = 0;
181 	barg->gb_kernel[0] = 0;
182 	RESET_MODULE(barg);
183 }
184 
185 /* ARGSUSED */
186 int
187 skip_line(const grub_line_t *lp, grub_barg_t *barg)
188 {
189 	return (0);
190 }
191 
192 /* ARGSUSED */
193 int
194 error_line(const grub_line_t *lp, grub_barg_t *barg)
195 {
196 	return (EG_INVALIDLINE);
197 }
198 
199 int
200 kernel(const grub_line_t *lp, grub_barg_t *barg)
201 {
202 	RESET_MODULE(barg);
203 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
204 	    sizeof (barg->gb_kernel))
205 		return (E2BIG);
206 
207 	return (0);
208 }
209 
210 int
211 module(const grub_line_t *lp, grub_barg_t *barg)
212 {
213 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
214 	    sizeof (barg->gb_module))
215 		return (E2BIG);
216 
217 	return (0);
218 }
219 
220 int
221 dollar_kernel(const grub_line_t *lp, grub_barg_t *barg)
222 {
223 	int	ret;
224 	size_t	bfslen, isalen;
225 	char	isadir[32];
226 	char	bootfs[BOOTARGS_MAX];
227 
228 	RESET_MODULE(barg);
229 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
230 	    sizeof (barg->gb_kernel))
231 		return (E2BIG);
232 
233 	bfslen = barg_bootfs_var(barg, bootfs, sizeof (bootfs));
234 	isalen = barg_isadir_var(isadir, sizeof (isadir));
235 
236 	if (bfslen >= sizeof (bootfs) || isalen >= sizeof (isadir))
237 		return (EINVAL);
238 
239 	if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
240 	    ZFS_BOOT_VAR, strlen(ZFS_BOOT_VAR), bootfs, bfslen)) != 0)
241 		return (ret);
242 
243 	ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
244 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
245 
246 	return (ret);
247 }
248 
249 int
250 dollar_module(const grub_line_t *lp, grub_barg_t *barg)
251 {
252 	int	ret;
253 	size_t	isalen;
254 	char	isadir[32];
255 
256 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
257 	    sizeof (barg->gb_module))
258 		return (E2BIG);
259 
260 	if ((isalen = barg_isadir_var(isadir, sizeof (isadir))) >= sizeof
261 	    (isadir))
262 		return (EINVAL);
263 
264 	ret = expand_var(barg->gb_module, sizeof (barg->gb_module),
265 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
266 
267 	return (ret);
268 }
269 
270 
271 int
272 findroot(const grub_line_t *lp, grub_barg_t *barg)
273 {
274 	size_t sz, bsz;
275 	const char *sign;
276 
277 	reset_root(barg);
278 
279 	sign = lp->gl_arg;
280 	barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
281 	barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
282 
283 	if (sign[0] == '(') {
284 		const char *pos;
285 
286 		++sign;
287 		if ((pos = strchr(sign, ',')) == NULL || (sz = pos - sign) == 0)
288 			return (EG_FINDROOTFMT);
289 
290 		++pos;
291 		if (!IS_PRTNUM_VALID(barg->gb_prtnum = pos[0] - '0'))
292 			return (EG_FINDROOTFMT);
293 
294 		++pos;
295 		if (pos[0] != ',' ||
296 		    !IS_SLCNUM_VALID(barg->gb_slcnum = pos[1]) ||
297 		    pos[2] != ')')
298 			return (EG_FINDROOTFMT);
299 	} else {
300 		sz = strlen(sign);
301 	}
302 
303 	bsz = strlen(BOOTSIGN_DIR "/");
304 	if (bsz + sz + 1 > sizeof (barg->gb_bootsign))
305 		return (E2BIG);
306 
307 	bcopy(BOOTSIGN_DIR "/", barg->gb_bootsign, bsz);
308 	bcopy(sign, barg->gb_bootsign + bsz, sz);
309 	barg->gb_bootsign [bsz + sz] = 0;
310 
311 	return (grub_find_bootsign(barg));
312 }
313 
314 int
315 bootfs(const grub_line_t *lp, grub_barg_t *barg)
316 {
317 	zfs_handle_t	*zfh;
318 	grub_menu_t	*mp = barg->gb_entry->ge_menu;
319 	char		*gfs_devp;
320 	size_t		gfs_dev_len;
321 
322 	/* Check if root is zfs */
323 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) != 0)
324 		return (EG_NOTZFS);
325 
326 	gfs_devp = barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev;
327 	gfs_dev_len = sizeof (barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev);
328 
329 	/*
330 	 * If the bootfs value is the same as the bootfs for the pool,
331 	 * do nothing.
332 	 */
333 	if (strcmp(lp->gl_arg, gfs_devp) == 0)
334 		return (0);
335 
336 	if (strlcpy(gfs_devp, lp->gl_arg, gfs_dev_len) >= gfs_dev_len)
337 		return (E2BIG);
338 
339 	/* check if specified bootfs belongs to the root pool */
340 	if ((zfh = zfs_open(mp->gm_fs.gf_lzfh,
341 	    barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_dev,
342 	    ZFS_TYPE_FILESYSTEM)) == NULL)
343 		return (EG_OPENZFS);
344 
345 	barg->gb_walkret = EG_UNKBOOTFS;
346 	(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
347 	zfs_close(zfh);
348 
349 	if (barg->gb_walkret == 0)
350 		(void) grub_fsd_get_mountp(barg->gb_root.gr_fs +
351 		    GRBM_ZFS_BOOTFS, MNTTYPE_ZFS);
352 
353 	return (barg->gb_walkret);
354 }
355