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 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 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 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 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 * 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 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 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 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 290 skip_line(const grub_line_t *lp, grub_barg_t *barg) 291 { 292 return (0); 293 } 294 295 /* ARGSUSED */ 296 int 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 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 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 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 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 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 if ((pos[0] != ',' && pos[0] != ')') || 406 !IS_SLCNUM_VALID(barg->gb_slcnum = pos[1]) || 407 pos[2] != ')') 408 return (EG_FINDROOTFMT); 409 } else { 410 sz = strlen(sign); 411 } 412 413 bsz = strlen(BOOTSIGN_DIR "/"); 414 if (bsz + sz + 1 > sizeof (barg->gb_bootsign)) 415 return (E2BIG); 416 417 bcopy(BOOTSIGN_DIR "/", barg->gb_bootsign, bsz); 418 bcopy(sign, barg->gb_bootsign + bsz, sz); 419 barg->gb_bootsign [bsz + sz] = 0; 420 421 return (grub_find_bootsign(barg)); 422 } 423 424 int 425 bootfs(const grub_line_t *lp, grub_barg_t *barg) 426 { 427 zfs_handle_t *zfh; 428 grub_menu_t *mp = barg->gb_entry->ge_menu; 429 char *gfs_devp; 430 size_t gfs_dev_len; 431 432 /* Check if root is zfs */ 433 if (strcmp(barg->gb_root.gr_fstyp, MNTTYPE_ZFS) != 0) 434 return (EG_NOTZFS); 435 436 gfs_devp = barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev; 437 gfs_dev_len = sizeof (barg->gb_root.gr_fs[GRBM_ZFS_BOOTFS].gfs_dev); 438 439 /* 440 * If the bootfs value is the same as the bootfs for the pool, 441 * do nothing. 442 */ 443 if (strcmp(lp->gl_arg, gfs_devp) == 0) 444 return (0); 445 446 if (strlcpy(gfs_devp, lp->gl_arg, gfs_dev_len) >= gfs_dev_len) 447 return (E2BIG); 448 449 /* check if specified bootfs belongs to the root pool */ 450 if ((zfh = zfs_open(mp->gm_fs.gf_lzfh, 451 barg->gb_root.gr_fs[GRBM_ZFS_TOPFS].gfs_dev, 452 ZFS_TYPE_FILESYSTEM)) == NULL) 453 return (EG_OPENZFS); 454 455 barg->gb_walkret = EG_UNKBOOTFS; 456 (void) zfs_iter_filesystems(zfh, match_bootfs, barg); 457 zfs_close(zfh); 458 459 if (barg->gb_walkret == 0) 460 (void) grub_fsd_get_mountp(barg->gb_root.gr_fs + 461 GRBM_ZFS_BOOTFS, MNTTYPE_ZFS); 462 463 return (barg->gb_walkret); 464 } 465