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 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 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 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 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 * 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 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 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 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 287 skip_line(const grub_line_t *lp, grub_barg_t *barg) 288 { 289 return (0); 290 } 291 292 /* ARGSUSED */ 293 int 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 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 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 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 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 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 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