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)
58static 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 */
64static int
65cpuid_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 */
91static size_t
92barg_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 */
106static size_t
107barg_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 */
127static int
128expand_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 */
168static const char *
169find_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 */
235static int
236update_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
259static int
260match_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
279static void
280reset_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 */
289int
290skip_line(const grub_line_t *lp, grub_barg_t *barg)
291{
292	return (0);
293}
294
295/* ARGSUSED */
296int
297error_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
304int
305kernel(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
315int
316module(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
325int
326dollar_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
359int
360dollar_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
381int
382findroot(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
429int
430bootfs(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