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  * Copyright 2015 Nexenta Systems, Inc.
27  */
28 
29 /*
30  * This file contains all the functions that implement the following
31  * GRUB commands:
32  *	kernel, kernel$, module, module$, findroot, bootfs
33  * Return 0 on success, errno on failure.
34  */
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <assert.h>
38 #include <alloca.h>
39 #include <errno.h>
40 #include <strings.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <sys/types.h>
44 #include <sys/fs/ufs_mount.h>
45 #include <sys/dktp/fdisk.h>
46 #if defined(__i386)
47 #include <sys/x86_archext.h>
48 #endif /* __i386 */
49 
50 #include "libgrub_impl.h"
51 
52 #define	RESET_MODULE(barg)	((barg)->gb_module[0] = 0)
53 
54 #define	BPROP_ZFSBOOTFS	"zfs-bootfs"
55 #define	BPROP_BOOTPATH	"bootpath"
56 
57 #if defined(__i386)
58 static const char cpuid_dev[] = "/dev/cpu/self/cpuid";
59 
60 /*
61  * Return 1 if the system supports 64-bit mode, 0 if it doesn't,
62  * or -1 on failure.
63  */
64 static int
65 cpuid_64bit_capable(void)
66 {
67 	int fd, ret = -1;
68 	struct {
69 		uint32_t cp_eax, cp_ebx, cp_ecx, cp_edx;
70 	} cpuid_regs;
71 
72 	if ((fd = open(cpuid_dev, O_RDONLY)) == -1)
73 		return (ret);
74 
75 	if (pread(fd, &cpuid_regs, sizeof (cpuid_regs), 0x80000001) ==
76 	    sizeof (cpuid_regs))
77 		ret = ((CPUID_AMD_EDX_LM & cpuid_regs.cp_edx) != 0);
78 
79 	(void) close(fd);
80 	return (ret);
81 }
82 #endif /* __i386 */
83 
84 
85 /*
86  * Expand $ISAIDR
87  */
88 #if !defined(__i386)
89 /* ARGSUSED */
90 #endif /* __i386 */
91 static size_t
92 barg_isadir_var(char *var, int sz)
93 {
94 #if defined(__i386)
95 	if (cpuid_64bit_capable() == 1)
96 		return (strlcpy(var, "amd64", sz));
97 #endif /* __i386 */
98 
99 	var[0] = 0;
100 	return (0);
101 }
102 
103 /*
104  * Expand $ZFS-BOOTFS
105  */
106 static size_t
107 barg_bootfs_var(const grub_barg_t *barg, char *var, int sz)
108 {
109 	int n;
110 
111 	assert(barg);
112 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) == 0) {
113 		n = snprintf(var, sz,
114 		    BPROP_ZFSBOOTFS "=%s," BPROP_BOOTPATH "=\"%s\"",
115 		    barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev,
116 		    barg->gb_root.gr_physpath);
117 	} else	{
118 		var[0] = 0;
119 		n = 0;
120 	}
121 	return (n);
122 }
123 
124 /*
125  * Expand all the variables without appending them more than once.
126  */
127 static int
128 expand_var(char *arg, size_t argsz, const char *var, size_t varsz,
129     char *val, size_t valsz)
130 {
131 	char	*sp = arg;
132 	size_t	sz = argsz, len;
133 	char	*buf, *dst, *src;
134 	int	ret = 0;
135 
136 	buf = alloca(argsz);
137 	dst = buf;
138 
139 	while ((src = strstr(sp, var)) != NULL) {
140 
141 		len = src - sp;
142 
143 		if (len + valsz > sz) {
144 			ret = E2BIG;
145 			break;
146 		}
147 
148 		(void) bcopy(sp, dst, len);
149 		(void) bcopy(val, dst + len, valsz);
150 		dst += len + valsz;
151 		sz -= len + valsz;
152 		sp = src + varsz;
153 	}
154 
155 	if (strlcpy(dst, sp, sz) >= sz)
156 		ret = E2BIG;
157 
158 	if (ret == 0)
159 		bcopy(buf, arg, argsz);
160 	return (ret);
161 }
162 
163 /*
164  * Searches first occurence of boot-property 'bprop' in str.
165  * str supposed to be in format:
166  * " [-B prop=[value][,prop=[value]]...]
167  */
168 static const char *
169 find_bootprop(const char *str, const char *bprop)
170 {
171 	const char *s;
172 	size_t bplen, len;
173 
174 	assert(str);
175 	assert(bprop);
176 
177 	bplen = strlen(bprop);
178 	s = str;
179 
180 	while ((str = strstr(s, " -B")) != NULL ||
181 	    (str = strstr(s, "\t-B")) != NULL) {
182 		s = str + 3;
183 		len = strspn(s, " \t");
184 
185 		/* empty -B option, skip it */
186 		if (len != 0 && s[len] == '-')
187 			continue;
188 
189 		s += len;
190 		do {
191 			len = strcspn(s, "= \t");
192 			if (s[len] !=  '=')
193 				break;
194 
195 			/* boot property we are looking for? */
196 			if (len == bplen && strncmp(s, bprop, bplen) == 0)
197 				return (s);
198 
199 			s += len;
200 
201 			/* skip boot property value */
202 			while ((s = strpbrk(s + 1, "\"\', \t")) != NULL) {
203 
204 				/* skip quoted */
205 				if (s[0] == '\"' || s[0] == '\'') {
206 					if ((s = strchr(s + 1, s[0])) == NULL) {
207 						/* unbalanced quotes */
208 						return (s);
209 					}
210 				}
211 				else
212 					break;
213 			}
214 
215 			/* no more boot properties */
216 			if (s == NULL)
217 				return (s);
218 
219 			/* no more boot properties in that -B block */
220 			if (s[0] != ',')
221 				break;
222 
223 			s += strspn(s, ",");
224 		} while (s[0] != ' ' && s[0] != '\t');
225 	}
226 	return (NULL);
227 }
228 
229 /*
230  * Add bootpath property to str if
231  * 	1. zfs-bootfs property is set explicitly
232  * and
233  * 	2. bootpath property is not set
234  */
235 static int
236 update_bootpath(char *str, size_t strsz, const char *bootpath)
237 {
238 	size_t n;
239 	char *buf;
240 	const char *bfs;
241 
242 	/* zfs-bootfs is not specified, or bootpath is allready set */
243 	if ((bfs = find_bootprop(str, BPROP_ZFSBOOTFS)) == NULL ||
244 	    find_bootprop(str, BPROP_BOOTPATH) != NULL)
245 		return (0);
246 
247 	n = bfs - str;
248 	buf = alloca(strsz);
249 
250 	bcopy(str, buf, n);
251 	if (snprintf(buf + n, strsz - n, BPROP_BOOTPATH "=\"%s\",%s",
252 	    bootpath, bfs) >= strsz - n)
253 		return (E2BIG);
254 
255 	bcopy(buf, str, strsz);
256 	return (0);
257 }
258 
259 static int
260 match_bootfs(zfs_handle_t *zfh, void *data)
261 {
262 	int		ret;
263 	const char	*zfn;
264 	grub_barg_t	*barg = (grub_barg_t *)data;
265 
266 	ret = (zfs_get_type(zfh) == ZFS_TYPE_FILESYSTEM &&
267 	    (zfn = zfs_get_name(zfh)) != NULL &&
268 	    strcmp(barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev, zfn) == 0);
269 
270 	if (ret != 0)
271 		barg->gb_walkret = 0;
272 	else
273 		(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
274 
275 	zfs_close(zfh);
276 	return (barg->gb_walkret == 0);
277 }
278 
279 static void
280 reset_root(grub_barg_t *barg)
281 {
282 	(void) memset(&barg->gb_root, 0, sizeof (barg->gb_root));
283 	barg->gb_bootsign[0] = 0;
284 	barg->gb_kernel[0] = 0;
285 	RESET_MODULE(barg);
286 }
287 
288 /* ARGSUSED */
289 int
290 skip_line(const grub_line_t *lp, grub_barg_t *barg)
291 {
292 	return (0);
293 }
294 
295 /* ARGSUSED */
296 int
297 error_line(const grub_line_t *lp, grub_barg_t *barg)
298 {
299 	if (lp->gl_cmdtp == GRBM_ROOT_CMD)
300 		return (EG_ROOTNOTSUPP);
301 	return (EG_INVALIDLINE);
302 }
303 
304 int
305 kernel(const grub_line_t *lp, grub_barg_t *barg)
306 {
307 	RESET_MODULE(barg);
308 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
309 	    sizeof (barg->gb_kernel))
310 		return (E2BIG);
311 
312 	return (0);
313 }
314 
315 int
316 module(const grub_line_t *lp, grub_barg_t *barg)
317 {
318 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
319 	    sizeof (barg->gb_module))
320 		return (E2BIG);
321 
322 	return (0);
323 }
324 
325 int
326 dollar_kernel(const grub_line_t *lp, grub_barg_t *barg)
327 {
328 	int	ret;
329 	size_t	bfslen, isalen;
330 	char	isadir[32];
331 	char	bootfs[BOOTARGS_MAX];
332 
333 	RESET_MODULE(barg);
334 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
335 	    sizeof (barg->gb_kernel))
336 		return (E2BIG);
337 
338 	bfslen = barg_bootfs_var(barg, bootfs, sizeof (bootfs));
339 	isalen = barg_isadir_var(isadir, sizeof (isadir));
340 
341 	if (bfslen >= sizeof (bootfs) || isalen >= sizeof (isadir))
342 		return (EINVAL);
343 
344 	if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
345 	    ZFS_BOOT_VAR, strlen(ZFS_BOOT_VAR), bootfs, bfslen)) != 0)
346 		return (ret);
347 
348 	if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
349 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen)) != 0)
350 		return (ret);
351 
352 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) == 0)
353 		ret = update_bootpath(barg->gb_kernel, sizeof (barg->gb_kernel),
354 		    barg->gb_root.gr_physpath);
355 
356 	return (ret);
357 }
358 
359 int
360 dollar_module(const grub_line_t *lp, grub_barg_t *barg)
361 {
362 	int	ret;
363 	size_t	isalen;
364 	char	isadir[32];
365 
366 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
367 	    sizeof (barg->gb_module))
368 		return (E2BIG);
369 
370 	if ((isalen = barg_isadir_var(isadir, sizeof (isadir))) >= sizeof
371 	    (isadir))
372 		return (EINVAL);
373 
374 	ret = expand_var(barg->gb_module, sizeof (barg->gb_module),
375 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
376 
377 	return (ret);
378 }
379 
380 
381 int
382 findroot(const grub_line_t *lp, grub_barg_t *barg)
383 {
384 	size_t sz, bsz;
385 	const char *sign;
386 
387 	reset_root(barg);
388 
389 	sign = lp->gl_arg;
390 	barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
391 	barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
392 
393 	if (sign[0] == '(') {
394 		const char *pos;
395 
396 		++sign;
397 		if ((pos = strchr(sign, ',')) == NULL || (sz = pos - sign) == 0)
398 			return (EG_FINDROOTFMT);
399 
400 		++pos;
401 		if (!IS_PRTNUM_VALID(barg->gb_prtnum = pos[0] - '0'))
402 			return (EG_FINDROOTFMT);
403 
404 		++pos;
405 		/*
406 		 * check the slice only when its presented
407 		 */
408 		if (pos[0] != ')') {
409 			if (pos[0] != ',' ||
410 			    !IS_SLCNUM_VALID(barg->gb_slcnum = pos[1]) ||
411 			    pos[2] != ')')
412 				return (EG_FINDROOTFMT);
413 		}
414 	} else {
415 		sz = strlen(sign);
416 	}
417 
418 	bsz = strlen(BOOTSIGN_DIR "/");
419 	if (bsz + sz + 1 > sizeof (barg->gb_bootsign))
420 		return (E2BIG);
421 
422 	bcopy(BOOTSIGN_DIR "/", barg->gb_bootsign, bsz);
423 	bcopy(sign, barg->gb_bootsign + bsz, sz);
424 	barg->gb_bootsign [bsz + sz] = 0;
425 
426 	return (grub_find_bootsign(barg));
427 }
428 
429 int
430 bootfs(const grub_line_t *lp, grub_barg_t *barg)
431 {
432 	zfs_handle_t	*zfh;
433 	grub_menu_t	*mp = barg->gb_entry->ge_menu;
434 	char		*gfs_devp;
435 	size_t		gfs_dev_len;
436 
437 	/* Check if root is zfs */
438 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) != 0)
439 		return (EG_NOTZFS);
440 
441 	gfs_devp = barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev;
442 	gfs_dev_len = sizeof (barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev);
443 
444 	/*
445 	 * If the bootfs value is the same as the bootfs for the pool,
446 	 * do nothing.
447 	 */
448 	if (strcmp(lp->gl_arg, gfs_devp) == 0)
449 		return (0);
450 
451 	if (strlcpy(gfs_devp, lp->gl_arg, gfs_dev_len) >= gfs_dev_len)
452 		return (E2BIG);
453 
454 	/* check if specified bootfs belongs to the root pool */
455 	if ((zfh = zfs_open(mp->gm_fs.gf_lzfh,
456 	    barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_dev,
457 	    ZFS_TYPE_FILESYSTEM)) == NULL)
458 		return (EG_OPENZFS);
459 
460 	barg->gb_walkret = EG_UNKBOOTFS;
461 	(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
462 	zfs_close(zfh);
463 
464 	if (barg->gb_walkret == 0)
465 		(void) grub_fsd_get_mountp(barg->gb_root.gr_fs +
466 		    GRBM_ZFS_BOOTFS, MNTTYPE_ZFS);
467 
468 	return (barg->gb_walkret);
469 }
470