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