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 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <stdio.h> 29 #include <errno.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 #include <sys/types.h> 34 #include <sys/stat.h> 35 #include <limits.h> 36 #include <fcntl.h> 37 #include <strings.h> 38 39 #include <sys/mman.h> 40 #include <sys/elf.h> 41 #include <sys/multiboot.h> 42 43 #include "message.h" 44 #include "bootadm.h" 45 46 direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET; 47 hv_t bam_is_hv = BAM_HV_UNKNOWN; 48 49 error_t 50 dboot_or_multiboot(const char *root) 51 { 52 char fname[PATH_MAX]; 53 char *image; 54 uchar_t *ident; 55 int fd, m; 56 multiboot_header_t *mbh; 57 struct stat sb; 58 59 (void) snprintf(fname, PATH_MAX, "%s/%s", root, 60 "platform/i86pc/kernel/unix"); 61 fd = open(fname, O_RDONLY); 62 if (fd < 0) { 63 bam_error(OPEN_FAIL, fname, strerror(errno)); 64 return (BAM_ERROR); 65 } 66 67 /* 68 * mmap the first 8K 69 */ 70 image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0); 71 if (image == MAP_FAILED) { 72 bam_error(MMAP_FAIL, fname, strerror(errno)); 73 return (BAM_ERROR); 74 } 75 76 ident = (uchar_t *)image; 77 if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 || 78 ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) { 79 bam_error(NOT_ELF_FILE, fname); 80 return (BAM_ERROR); 81 } 82 if (ident[EI_CLASS] != ELFCLASS32) { 83 bam_error(WRONG_ELF_CLASS, fname, ident[EI_CLASS]); 84 return (BAM_ERROR); 85 } 86 87 /* 88 * The GRUB multiboot header must be 32-bit aligned and completely 89 * contained in the 1st 8K of the file. If the unix binary has 90 * a multiboot header, then it is a 'dboot' kernel. Otherwise, 91 * this kernel must be booted via multiboot -- we call this a 92 * 'multiboot' kernel. 93 */ 94 bam_direct = BAM_DIRECT_MULTIBOOT; 95 for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) { 96 mbh = (void *)(image + m); 97 if (mbh->magic == MB_HEADER_MAGIC) { 98 bam_direct = BAM_DIRECT_DBOOT; 99 break; 100 } 101 } 102 (void) munmap(image, 8192); 103 (void) close(fd); 104 105 if (bam_direct == BAM_DIRECT_DBOOT) { 106 (void) snprintf(fname, PATH_MAX, "%s/%s", root, XEN_32); 107 if (stat(fname, &sb) == 0) { 108 bam_is_hv = BAM_HV_PRESENT; 109 } else { 110 bam_is_hv = BAM_HV_NO; 111 } 112 } 113 114 return (BAM_SUCCESS); 115 } 116 117 #define INST_RELEASE "var/sadm/system/admin/INST_RELEASE" 118 119 /* 120 * Return true if root has been bfu'ed. bfu will blow away 121 * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can 122 * assume the system has not been bfu'ed. 123 */ 124 static int 125 is_bfu_system(const char *root) 126 { 127 static int is_bfu = -1; 128 char path[PATH_MAX]; 129 struct stat sb; 130 131 if (is_bfu != -1) 132 return (is_bfu); 133 134 (void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE); 135 if (stat(path, &sb) != 0) { 136 is_bfu = 1; 137 } else { 138 is_bfu = 0; 139 } 140 return (is_bfu); 141 } 142 143 #define MENU_URL(root) (is_bfu_system(root) ? \ 144 "http://www.sun.com/msg/SUNOS-8000-CF" : \ 145 "http://www.sun.com/msg/SUNOS-8000-AK") 146 147 /* 148 * Simply allocate a new line and copy in cmd + sep + arg 149 */ 150 void 151 update_line(line_t *linep) 152 { 153 size_t size; 154 155 free(linep->line); 156 size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1; 157 linep->line = s_calloc(1, size); 158 (void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep, 159 linep->arg); 160 } 161 162 /* 163 * The parse_kernel_line function examines a menu.lst kernel line. For 164 * multiboot, this is: 165 * 166 * kernel <multiboot path> <flags1> <kernel path> <flags2> 167 * 168 * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot 169 * 170 * <kernel path> may be missing, or may be any full or relative path to unix. 171 * We check for it by looking for a word ending in "/unix". If it ends 172 * in "kernel/unix", we upgrade it to a 32-bit entry. If it ends in 173 * "kernel/amd64/unix", we upgrade it to the default entry. Otherwise, 174 * it's a custom kernel, and we skip it. 175 * 176 * <flags*> are anything that doesn't fit either of the above - these will be 177 * copied over. 178 * 179 * For direct boot, the defaults are 180 * 181 * kernel$ <kernel path> <flags> 182 * 183 * <kernel path> is one of: 184 * /platform/i86pc/kernel/$ISADIR/unix 185 * /platform/i86pc/kernel/unix 186 * /platform/i86pc/kernel/amd64/unix 187 * /boot/platform/i86pc/kernel/unix 188 * 189 * If <kernel path> is any of the last three, the command may also be "kernel". 190 * 191 * <flags> is anything that isn't <kernel path>. 192 * 193 * This function is only called if it applies to our target boot environment. 194 * If we can't make any sense of the kernel line, an error is printed and 195 * BAM_ERROR is returned. 196 * 197 * The desired install type is given in the global variable bam_direct. 198 * If the kernel line is of a different install type, we change it to the 199 * preferred type. If the kernel line is already of the correct install 200 * type, we do nothing. Either way, BAM_SUCCESS is returned. 201 * 202 * For safety, we do one more check: if the kernel path starts with /boot, 203 * we verify that the new kernel exists before changing it. This is mainly 204 * done for bfu, as it may cause the failsafe archives to be a different 205 * boot architecture from the newly bfu'ed system. 206 */ 207 static error_t 208 parse_kernel_line(line_t *linep, const char *root, uint8_t *flags) 209 { 210 char path[PATH_MAX]; 211 int len, left, total_len; 212 struct stat sb; 213 char *new_ptr, *new_arg, *old_ptr; 214 menu_cmd_t which; 215 216 /* Used when changing a multiboot line to dboot */ 217 char *unix_ptr, *flags1_ptr, *flags2_ptr; 218 219 /* 220 * Note that BAM_ENTRY_DBOOT refers to the entry we're looking at, not 221 * necessarily the system type. 222 */ 223 if (strncmp(linep->arg, DIRECT_BOOT_32, 224 sizeof (DIRECT_BOOT_32) - 1) == 0) { 225 *flags |= BAM_ENTRY_DBOOT | BAM_ENTRY_32BIT; 226 } else if ((strncmp(linep->arg, DIRECT_BOOT_KERNEL, 227 sizeof (DIRECT_BOOT_KERNEL) - 1) == 0) || 228 (strncmp(linep->arg, DIRECT_BOOT_64, 229 sizeof (DIRECT_BOOT_64) - 1) == 0) || 230 (strncmp(linep->arg, DIRECT_BOOT_FAILSAFE_KERNEL, 231 sizeof (DIRECT_BOOT_FAILSAFE_KERNEL) - 1) == 0)) { 232 *flags |= BAM_ENTRY_DBOOT; 233 } else if ((strncmp(linep->arg, MULTI_BOOT, 234 sizeof (MULTI_BOOT) - 1) == 0) || 235 (strncmp(linep->arg, MULTI_BOOT_FAILSAFE, 236 sizeof (MULTI_BOOT_FAILSAFE) - 1) == 0)) { 237 *flags &= ~BAM_ENTRY_DBOOT; 238 } else { 239 bam_error(NO_KERNEL_MATCH, linep->lineNum, MENU_URL(root)); 240 return (BAM_ERROR); 241 } 242 243 if (((*flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) || 244 (((*flags & BAM_ENTRY_DBOOT) == 0) && 245 (bam_direct == BAM_DIRECT_MULTIBOOT))) { 246 247 /* No action needed */ 248 return (BAM_SUCCESS); 249 } 250 251 if (*flags & BAM_ENTRY_MINIROOT) { 252 /* 253 * We're changing boot architectures - make sure 254 * the multiboot failsafe still exists. 255 */ 256 (void) snprintf(path, PATH_MAX, "%s%s", root, 257 (*flags & BAM_ENTRY_DBOOT) ? MULTI_BOOT_FAILSAFE : 258 DIRECT_BOOT_FAILSAFE_KERNEL); 259 if (stat(path, &sb) != 0) { 260 if (bam_verbose) { 261 bam_error(FAILSAFE_MISSING, linep->lineNum); 262 } 263 return (BAM_SUCCESS); 264 } 265 } 266 267 /* 268 * Make sure we have the correct cmd - either kernel or kernel$ 269 * The failsafe entry should always be KERNEL_CMD. 270 */ 271 which = ((bam_direct == BAM_DIRECT_MULTIBOOT) || 272 (*flags & BAM_ENTRY_MINIROOT)) ? KERNEL_CMD : KERNEL_DOLLAR_CMD; 273 free(linep->cmd); 274 len = strlen(menu_cmds[which]) + 1; 275 linep->cmd = s_calloc(1, len); 276 (void) strncpy(linep->cmd, menu_cmds[which], len); 277 278 /* 279 * Since all arguments are copied, the new arg string should be close 280 * in size to the old one. Just add 32 to cover the difference in 281 * the boot path. 282 */ 283 total_len = strlen(linep->arg) + 32; 284 new_arg = s_calloc(1, total_len); 285 old_ptr = strchr(linep->arg, ' '); 286 if (old_ptr != NULL) 287 old_ptr++; 288 289 /* 290 * Transitioning from dboot to multiboot is pretty simple. We 291 * copy in multiboot and any args. 292 */ 293 if (bam_direct == BAM_DIRECT_MULTIBOOT) { 294 if (old_ptr == NULL) { 295 (void) snprintf(new_arg, total_len, "%s", 296 (*flags & BAM_ENTRY_MINIROOT) ? 297 MULTI_BOOT_FAILSAFE : MULTI_BOOT); 298 } else { 299 (void) snprintf(new_arg, total_len, "%s %s", 300 (*flags & BAM_ENTRY_MINIROOT) ? 301 MULTI_BOOT_FAILSAFE : MULTI_BOOT, old_ptr); 302 } 303 goto done; 304 } 305 306 /* 307 * Transitioning from multiboot to directboot is a bit more 308 * complicated, since we may have two sets of arguments to 309 * copy and a unix path to parse. 310 * 311 * First, figure out if there's a unix path. 312 */ 313 if ((old_ptr != NULL) && 314 ((unix_ptr = strstr(old_ptr, "/unix")) != NULL)) { 315 /* See if there's anything past unix */ 316 flags2_ptr = unix_ptr + sizeof ("/unix"); 317 if (*flags2_ptr == '\0') { 318 flags2_ptr = NULL; 319 } 320 321 while ((unix_ptr > old_ptr) && (*unix_ptr != ' ')) 322 unix_ptr--; 323 324 if (unix_ptr == old_ptr) { 325 flags1_ptr = NULL; 326 } else { 327 flags1_ptr = old_ptr; 328 } 329 330 if (strstr(unix_ptr, "kernel/unix") != NULL) { 331 *flags |= BAM_ENTRY_32BIT; 332 } else if ((strstr(unix_ptr, "kernel/amd64/unix") == NULL) && 333 (!bam_force)) { 334 /* 335 * If the above strstr returns NULL, but bam_force is 336 * set, we'll be upgrading an Install kernel. The 337 * result probably won't be what was intended, but we'll 338 * try it anyways. 339 */ 340 return (BAM_SKIP); 341 } 342 } else if (old_ptr != NULL) { 343 flags1_ptr = old_ptr; 344 unix_ptr = flags1_ptr + strlen(old_ptr); 345 flags2_ptr = NULL; 346 } else { 347 unix_ptr = flags1_ptr = flags2_ptr = NULL; 348 } 349 350 if (*flags & BAM_ENTRY_MINIROOT) { 351 (void) snprintf(new_arg, total_len, "%s", 352 DIRECT_BOOT_FAILSAFE_KERNEL); 353 } else if (*flags & BAM_ENTRY_32BIT) { 354 (void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_32); 355 } else { 356 (void) snprintf(new_arg, total_len, "%s", DIRECT_BOOT_KERNEL); 357 } 358 359 /* 360 * We now want to copy flags1_ptr through unix_ptr, and 361 * flags2_ptr through the end of the string 362 */ 363 if (flags1_ptr != NULL) { 364 len = strlcat(new_arg, " ", total_len); 365 left = total_len - len; 366 new_ptr = new_arg + len; 367 368 if ((unix_ptr - flags1_ptr) < left) 369 left = (unix_ptr - flags1_ptr) + 1; 370 (void) strlcpy(new_ptr, flags1_ptr, left); 371 } 372 if (flags2_ptr != NULL) { 373 (void) strlcat(new_arg, " ", total_len); 374 (void) strlcat(new_arg, flags2_ptr, total_len); 375 } 376 377 done: 378 free(linep->arg); 379 linep->arg = new_arg; 380 update_line(linep); 381 return (BAM_SUCCESS); 382 } 383 384 /* 385 * Similar to above, except this time we're looking at a module line, 386 * which is quite a bit simpler. 387 * 388 * Under multiboot, the archive line is: 389 * 390 * module /platform/i86pc/boot_archive 391 * 392 * Under directboot, the archive line is: 393 * 394 * module$ /platform/i86pc/$ISADIR/boot_archive 395 * 396 * which may be specified exactly as either of: 397 * 398 * module /platform/i86pc/boot_archive 399 * module /platform/i86pc/amd64/boot_archive 400 * 401 * For either dboot or multiboot, the failsafe is: 402 * 403 * module /boot/x86.miniroot-safe 404 */ 405 static error_t 406 parse_module_line(line_t *linep, const char *root, uint8_t flags) 407 { 408 int len; 409 menu_cmd_t which; 410 char *new; 411 412 /* 413 * If necessary, BAM_ENTRY_MINIROOT was already set in flags 414 * in upgrade_menu(). We re-check BAM_ENTRY_DBOOT here in here 415 * in case the kernel and module lines differ. 416 */ 417 if ((strcmp(linep->arg, DIRECT_BOOT_ARCHIVE) == 0) || 418 (strcmp(linep->arg, DIRECT_BOOT_ARCHIVE_64) == 0)) { 419 flags |= BAM_ENTRY_DBOOT; 420 } else if ((strcmp(linep->arg, MULTI_BOOT_ARCHIVE) == 0) || 421 (strcmp(linep->arg, MINIROOT) == 0)) { 422 flags &= ~BAM_ENTRY_DBOOT; 423 } else { 424 bam_error(NO_MODULE_MATCH, linep->lineNum, MENU_URL(root)); 425 return (BAM_ERROR); 426 } 427 428 if (((flags & BAM_ENTRY_DBOOT) && (bam_direct == BAM_DIRECT_DBOOT)) || 429 (((flags & BAM_ENTRY_DBOOT) == 0) && 430 (bam_direct == BAM_DIRECT_MULTIBOOT)) || 431 ((flags & BAM_ENTRY_MINIROOT) && 432 (strcmp(linep->cmd, menu_cmds[MODULE_CMD]) == 0))) { 433 434 /* No action needed */ 435 return (BAM_SUCCESS); 436 } 437 438 /* 439 * Make sure we have the correct cmd - either module or module$ 440 * The failsafe entry should always be MODULE_CMD. 441 */ 442 which = ((bam_direct == BAM_DIRECT_MULTIBOOT) || 443 (flags & BAM_ENTRY_MINIROOT)) ? MODULE_CMD : MODULE_DOLLAR_CMD; 444 free(linep->cmd); 445 len = strlen(menu_cmds[which]) + 1; 446 linep->cmd = s_calloc(1, len); 447 (void) strncpy(linep->cmd, menu_cmds[which], len); 448 449 if (flags & BAM_ENTRY_MINIROOT) { 450 new = MINIROOT; 451 } else if ((bam_direct == BAM_DIRECT_DBOOT) && 452 ((flags & BAM_ENTRY_32BIT) == 0)) { 453 new = DIRECT_BOOT_ARCHIVE; 454 } else { 455 new = MULTI_BOOT_ARCHIVE; 456 } 457 458 free(linep->arg); 459 len = strlen(new) + 1; 460 linep->arg = s_calloc(1, len); 461 (void) strncpy(linep->arg, new, len); 462 update_line(linep); 463 464 return (BAM_SUCCESS); 465 } 466 467 /*ARGSUSED*/ 468 error_t 469 upgrade_menu(menu_t *mp, char *root, char *opt) 470 { 471 entry_t *cur_entry; 472 line_t *cur_line; 473 int i, skipit, num_entries, found_hv; 474 int *hand_entries = NULL; 475 boolean_t found_kernel = B_FALSE; 476 error_t rv; 477 char *rootdev, *grubdisk = NULL; 478 479 skipit = num_entries = found_hv = 0; 480 481 rootdev = get_special(root); 482 if (rootdev) { 483 grubdisk = os_to_grubdisk(rootdev, strlen(root) == 1); 484 free(rootdev); 485 rootdev = NULL; 486 } 487 488 /* Loop through all OS entries in the menu.lst file */ 489 for (cur_entry = mp->entries; cur_entry != NULL; 490 cur_entry = cur_entry->next, skipit = 0) { 491 492 if ((cur_entry->flags & BAM_ENTRY_CHAINLOADER) || 493 ((cur_entry->flags & BAM_ENTRY_MINIROOT) && !bam_force)) 494 continue; 495 496 /* 497 * We only change entries added by bootadm and live upgrade, 498 * and warn on the rest, unless the -f flag was passed. 499 */ 500 if ((!(cur_entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))) && 501 !bam_force) { 502 if (num_entries == 0) { 503 hand_entries = s_calloc(1, sizeof (int)); 504 } else { 505 hand_entries = s_realloc(hand_entries, 506 (num_entries + 1) * sizeof (int)); 507 } 508 hand_entries[num_entries++] = cur_entry->entryNum; 509 continue; 510 } 511 512 if (cur_entry->flags & BAM_ENTRY_HV) { 513 found_hv = 1; 514 continue; 515 } 516 517 /* 518 * We make two loops through the lines. First, we check if 519 * there is a root entry, and if so, whether we should be 520 * checking this entry. 521 */ 522 if ((grubdisk != NULL) && (cur_entry->flags & BAM_ENTRY_ROOT)) { 523 for (cur_line = cur_entry->start; cur_line != NULL; 524 cur_line = cur_line->next) { 525 if ((cur_line->cmd == NULL) || 526 (cur_line->arg == NULL)) 527 continue; 528 529 if (strcmp(cur_line->cmd, 530 menu_cmds[ROOT_CMD]) == 0) { 531 if (strcmp(cur_line->arg, 532 grubdisk) != 0) { 533 /* A different slice */ 534 skipit = 1; 535 } 536 break; 537 } 538 if (cur_line == cur_entry->end) 539 break; 540 } 541 } 542 if (skipit) 543 continue; 544 545 for (cur_line = cur_entry->start; cur_line != NULL; 546 cur_line = cur_line->next) { 547 548 /* 549 * We only compare for the length of KERNEL_CMD, 550 * so that KERNEL_DOLLAR_CMD will also match. 551 */ 552 if (strncmp(cur_line->cmd, menu_cmds[KERNEL_CMD], 553 strlen(menu_cmds[KERNEL_CMD])) == 0) { 554 rv = parse_kernel_line(cur_line, root, 555 &(cur_entry->flags)); 556 if (rv == BAM_SKIP) { 557 break; 558 } else if (rv != BAM_SUCCESS) { 559 return (rv); 560 } 561 found_kernel = B_TRUE; 562 } else if (strncmp(cur_line->cmd, 563 menu_cmds[MODULE_CMD], 564 strlen(menu_cmds[MODULE_CMD])) == 0) { 565 rv = parse_module_line(cur_line, root, 566 cur_entry->flags); 567 if (rv != BAM_SUCCESS) { 568 return (rv); 569 } 570 } 571 if (cur_line == cur_entry->end) 572 break; 573 } 574 } 575 576 /* 577 * If we're upgrading to a virtualized kernel and there are no 578 * hv entries in menu.lst, we need to add one. 579 */ 580 if ((bam_is_hv == BAM_HV_PRESENT) && (found_hv == 0)) { 581 (void) add_boot_entry(mp, NEW_HV_ENTRY, grubdisk, 582 XEN_MENU, KERNEL_MODULE_LINE, DIRECT_BOOT_ARCHIVE); 583 } 584 585 /* 586 * We only want to output one error, to avoid confusing a user. We 587 * rank "No kernels changed" as a higher priority than "will not 588 * update hand-added entries", since the former implies the latter. 589 */ 590 if (found_kernel == B_FALSE) { 591 bam_error(NO_KERNELS_FOUND, MENU_URL(root)); 592 return (BAM_ERROR); 593 } else if (num_entries > 0) { 594 bam_error(HAND_ADDED_ENTRY, MENU_URL(root)); 595 bam_print_stderr("Entry Number%s: ", (num_entries > 1) ? 596 "s" : ""); 597 for (i = 0; i < num_entries; i++) { 598 bam_print_stderr("%d ", hand_entries[i]); 599 } 600 bam_print_stderr("\n"); 601 } 602 return (BAM_WRITE); 603 } 604