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