xref: /illumos-gate/usr/src/lib/libgrubmgmt/common/libgrub_cmd.c (revision 7e322df5ee63a00c1c57398abec50fc1dc54b67a)
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 	if (lp->gl_cmdtp == GRBM_ROOT_CMD)
197 		return (EG_ROOTNOTSUPP);
198 	return (EG_INVALIDLINE);
199 }
200 
201 int
202 kernel(const grub_line_t *lp, grub_barg_t *barg)
203 {
204 	RESET_MODULE(barg);
205 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
206 	    sizeof (barg->gb_kernel))
207 		return (E2BIG);
208 
209 	return (0);
210 }
211 
212 int
213 module(const grub_line_t *lp, grub_barg_t *barg)
214 {
215 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
216 	    sizeof (barg->gb_module))
217 		return (E2BIG);
218 
219 	return (0);
220 }
221 
222 int
223 dollar_kernel(const grub_line_t *lp, grub_barg_t *barg)
224 {
225 	int	ret;
226 	size_t	bfslen, isalen;
227 	char	isadir[32];
228 	char	bootfs[BOOTARGS_MAX];
229 
230 	RESET_MODULE(barg);
231 	if (strlcpy(barg->gb_kernel, lp->gl_arg, sizeof (barg->gb_kernel)) >=
232 	    sizeof (barg->gb_kernel))
233 		return (E2BIG);
234 
235 	bfslen = barg_bootfs_var(barg, bootfs, sizeof (bootfs));
236 	isalen = barg_isadir_var(isadir, sizeof (isadir));
237 
238 	if (bfslen >= sizeof (bootfs) || isalen >= sizeof (isadir))
239 		return (EINVAL);
240 
241 	if ((ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
242 	    ZFS_BOOT_VAR, strlen(ZFS_BOOT_VAR), bootfs, bfslen)) != 0)
243 		return (ret);
244 
245 	ret = expand_var(barg->gb_kernel, sizeof (barg->gb_kernel),
246 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
247 
248 	return (ret);
249 }
250 
251 int
252 dollar_module(const grub_line_t *lp, grub_barg_t *barg)
253 {
254 	int	ret;
255 	size_t	isalen;
256 	char	isadir[32];
257 
258 	if (strlcpy(barg->gb_module, lp->gl_arg, sizeof (barg->gb_module)) >=
259 	    sizeof (barg->gb_module))
260 		return (E2BIG);
261 
262 	if ((isalen = barg_isadir_var(isadir, sizeof (isadir))) >= sizeof
263 	    (isadir))
264 		return (EINVAL);
265 
266 	ret = expand_var(barg->gb_module, sizeof (barg->gb_module),
267 	    ISADIR_VAR, strlen(ISADIR_VAR), isadir, isalen);
268 
269 	return (ret);
270 }
271 
272 
273 int
274 findroot(const grub_line_t *lp, grub_barg_t *barg)
275 {
276 	size_t sz, bsz;
277 	const char *sign;
278 
279 	reset_root(barg);
280 
281 	sign = lp->gl_arg;
282 	barg->gb_prtnum = (uint_t)PRTNUM_INVALID;
283 	barg->gb_slcnum = (uint_t)SLCNUM_WHOLE_DISK;
284 
285 	if (sign[0] == '(') {
286 		const char *pos;
287 
288 		++sign;
289 		if ((pos = strchr(sign, ',')) == NULL || (sz = pos - sign) == 0)
290 			return (EG_FINDROOTFMT);
291 
292 		++pos;
293 		if (!IS_PRTNUM_VALID(barg->gb_prtnum = pos[0] - '0'))
294 			return (EG_FINDROOTFMT);
295 
296 		++pos;
297 		if (pos[0] != ',' ||
298 		    !IS_SLCNUM_VALID(barg->gb_slcnum = pos[1]) ||
299 		    pos[2] != ')')
300 			return (EG_FINDROOTFMT);
301 	} else {
302 		sz = strlen(sign);
303 	}
304 
305 	bsz = strlen(BOOTSIGN_DIR "/");
306 	if (bsz + sz + 1 > sizeof (barg->gb_bootsign))
307 		return (E2BIG);
308 
309 	bcopy(BOOTSIGN_DIR "/", barg->gb_bootsign, bsz);
310 	bcopy(sign, barg->gb_bootsign + bsz, sz);
311 	barg->gb_bootsign [bsz + sz] = 0;
312 
313 	return (grub_find_bootsign(barg));
314 }
315 
316 int
317 bootfs(const grub_line_t *lp, grub_barg_t *barg)
318 {
319 	zfs_handle_t	*zfh;
320 	grub_menu_t	*mp = barg->gb_entry->ge_menu;
321 	char		*gfs_devp;
322 	size_t		gfs_dev_len;
323 
324 	/* Check if root is zfs */
325 	if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) != 0)
326 		return (EG_NOTZFS);
327 
328 	gfs_devp = barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev;
329 	gfs_dev_len = sizeof (barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev);
330 
331 	/*
332 	 * If the bootfs value is the same as the bootfs for the pool,
333 	 * do nothing.
334 	 */
335 	if (strcmp(lp->gl_arg, gfs_devp) == 0)
336 		return (0);
337 
338 	if (strlcpy(gfs_devp, lp->gl_arg, gfs_dev_len) >= gfs_dev_len)
339 		return (E2BIG);
340 
341 	/* check if specified bootfs belongs to the root pool */
342 	if ((zfh = zfs_open(mp->gm_fs.gf_lzfh,
343 	    barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_dev,
344 	    ZFS_TYPE_FILESYSTEM)) == NULL)
345 		return (EG_OPENZFS);
346 
347 	barg->gb_walkret = EG_UNKBOOTFS;
348 	(void) zfs_iter_filesystems(zfh, match_bootfs, barg);
349 	zfs_close(zfh);
350 
351 	if (barg->gb_walkret == 0)
352 		(void) grub_fsd_get_mountp(barg->gb_root.gr_fs +
353 		    GRBM_ZFS_BOOTFS, MNTTYPE_ZFS);
354 
355 	return (barg->gb_walkret);
356 }
357