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