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
cpuid_64bit_capable(void)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
barg_isadir_var(char * var,int sz)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
barg_bootfs_var(const grub_barg_t * barg,char * var,int sz)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
expand_var(char * arg,size_t argsz,const char * var,size_t varsz,char * val,size_t valsz)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 *
find_bootprop(const char * str,const char * bprop)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
update_bootpath(char * str,size_t strsz,const char * bootpath)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
match_bootfs(zfs_handle_t * zfh,void * data)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
reset_root(grub_barg_t * barg)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
skip_line(const grub_line_t * lp,grub_barg_t * barg)290 skip_line(const grub_line_t *lp, grub_barg_t *barg)
291 {
292 return (0);
293 }
294
295 /* ARGSUSED */
296 int
error_line(const grub_line_t * lp,grub_barg_t * barg)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
kernel(const grub_line_t * lp,grub_barg_t * barg)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
module(const grub_line_t * lp,grub_barg_t * barg)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
dollar_kernel(const grub_line_t * lp,grub_barg_t * barg)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
dollar_module(const grub_line_t * lp,grub_barg_t * barg)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
findroot(const grub_line_t * lp,grub_barg_t * barg)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
bootfs(const grub_line_t * lp,grub_barg_t * barg)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