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  * Copyright 2017 Toomas Soome <tsoome@me.com>
  25  */
  26 
  27 #include <stdio.h>
  28 #include <errno.h>
  29 #include <stdlib.h>
  30 #include <string.h>
  31 #include <unistd.h>
  32 #include <sys/types.h>
  33 #include <sys/stat.h>
  34 #include <limits.h>
  35 #include <fcntl.h>
  36 #include <strings.h>
  37 
  38 #include <sys/mman.h>
  39 #include <sys/elf.h>
  40 #include <sys/multiboot.h>
  41 
  42 #include "bootadm.h"
  43 
  44 direct_or_multi_t bam_direct = BAM_DIRECT_NOT_SET;
  45 findroot_t bam_is_findroot = BAM_FINDROOT_UNKNOWN;
  46 
  47 static void
  48 get_findroot_cap(const char *osroot)
  49 {
  50         FILE            *fp;
  51         char            path[PATH_MAX];
  52         char            buf[BAM_MAXLINE];
  53         struct stat     sb;
  54         int             dboot;
  55         int             error;
  56         int             ret;
  57         const char      *fcn = "get_findroot_cap()";
  58 
  59         (void) snprintf(path, sizeof (path), "%s/%s",
  60             osroot, "boot/grub/capability");
  61 
  62         if (stat(path, &sb) == -1) {
  63                 bam_is_findroot = BAM_FINDROOT_ABSENT;
  64                 BAM_DPRINTF(("%s: findroot capability absent\n", fcn));
  65                 return;
  66         }
  67 
  68         fp = fopen(path, "r");
  69         error = errno;
  70         INJECT_ERROR1("GET_CAP_FINDROOT_FOPEN", fp = NULL);
  71         if (fp == NULL) {
  72                 bam_error(_("failed to open file: %s: %s\n"), path,
  73                     strerror(error));
  74                 return;
  75         }
  76 
  77         dboot = 0;
  78         while (s_fgets(buf, sizeof (buf), fp) != NULL) {
  79                 if (strcmp(buf, "findroot") == 0) {
  80                         BAM_DPRINTF(("%s: findroot capability present\n", fcn));
  81                         bam_is_findroot = BAM_FINDROOT_PRESENT;
  82                 }
  83                 if (strcmp(buf, "dboot") == 0) {
  84                         BAM_DPRINTF(("%s: dboot capability present\n", fcn));
  85                         dboot = 1;
  86                 }
  87         }
  88 
  89         assert(dboot);
  90 
  91         if (bam_is_findroot == BAM_FINDROOT_UNKNOWN) {
  92                 bam_is_findroot = BAM_FINDROOT_ABSENT;
  93                 BAM_DPRINTF(("%s: findroot capability absent\n", fcn));
  94         }
  95 
  96         ret = fclose(fp);
  97         error = errno;
  98         INJECT_ERROR1("GET_CAP_FINDROOT_FCLOSE", ret = 1);
  99         if (ret != 0) {
 100                 bam_error(_("failed to close file: %s: %s\n"),
 101                     path, strerror(error));
 102         }
 103 }
 104 
 105 error_t
 106 get_boot_cap(const char *osroot)
 107 {
 108         char            fname[PATH_MAX];
 109         char            *image;
 110         uchar_t         *ident;
 111         uchar_t         class;
 112         int             fd;
 113         int             m;
 114         multiboot_header_t *mbh;
 115         struct stat     sb;
 116         int             error;
 117         const char      *fcn = "get_boot_cap()";
 118 
 119         if (is_sparc()) {
 120                 /* there is no non dboot sparc new-boot */
 121                 bam_direct = BAM_DIRECT_DBOOT;
 122                 BAM_DPRINTF(("%s: is sparc - always DBOOT\n", fcn));
 123                 return (BAM_SUCCESS);
 124         }
 125 
 126         /*
 127          * The install media can support both 64 and 32 bit boot
 128          * by using boot archive as ramdisk image. However, to save
 129          * the memory, the ramdisk may only have either 32 or 64
 130          * bit kernel files. To avoid error message about missing unix,
 131          * we should try both variants here and only complain if neither
 132          * is found. Since the 64-bit systems are more common, we start
 133          * from amd64.
 134          */
 135         class = ELFCLASS64;
 136         (void) snprintf(fname, PATH_MAX, "%s/%s", osroot,
 137             "platform/i86pc/kernel/amd64/unix");
 138         fd = open(fname, O_RDONLY);
 139         if (fd < 0) {
 140                 class = ELFCLASS32;
 141                 (void) snprintf(fname, PATH_MAX, "%s/%s", osroot,
 142                     "platform/i86pc/kernel/unix");
 143                 fd = open(fname, O_RDONLY);
 144         }
 145         error = errno;
 146         INJECT_ERROR1("GET_CAP_UNIX_OPEN", fd = -1);
 147         if (fd < 0) {
 148                 bam_error(_("failed to open file: %s: %s\n"), fname,
 149                     strerror(error));
 150                 return (BAM_ERROR);
 151         }
 152 
 153         /*
 154          * Verify that this is a sane unix at least 8192 bytes in length
 155          */
 156         if (fstat(fd, &sb) == -1 || sb.st_size < 8192) {
 157                 (void) close(fd);
 158                 bam_error(_("invalid or corrupted binary: %s\n"), fname);
 159                 return (BAM_ERROR);
 160         }
 161 
 162         /*
 163          * mmap the first 8K
 164          */
 165         image = mmap(NULL, 8192, PROT_READ, MAP_SHARED, fd, 0);
 166         error = errno;
 167         INJECT_ERROR1("GET_CAP_MMAP", image = MAP_FAILED);
 168         if (image == MAP_FAILED) {
 169                 bam_error(_("failed to mmap file: %s: %s\n"), fname,
 170                     strerror(error));
 171                 return (BAM_ERROR);
 172         }
 173 
 174         ident = (uchar_t *)image;
 175         if (ident[EI_MAG0] != ELFMAG0 || ident[EI_MAG1] != ELFMAG1 ||
 176             ident[EI_MAG2] != ELFMAG2 || ident[EI_MAG3] != ELFMAG3) {
 177                 bam_error(_("%s is not an ELF file.\n"), fname);
 178                 return (BAM_ERROR);
 179         }
 180         if (ident[EI_CLASS] != class) {
 181                 bam_error(_("%s is wrong ELF class 0x%x\n"), fname,
 182                     ident[EI_CLASS]);
 183                 return (BAM_ERROR);
 184         }
 185 
 186         /*
 187          * The GRUB multiboot header must be 32-bit aligned and completely
 188          * contained in the 1st 8K of the file.  If the unix binary has
 189          * a multiboot header, then it is a 'dboot' kernel.  Otherwise,
 190          * this kernel must be booted via multiboot -- we call this a
 191          * 'multiboot' kernel.
 192          */
 193         bam_direct = BAM_DIRECT_MULTIBOOT;
 194         for (m = 0; m < 8192 - sizeof (multiboot_header_t); m += 4) {
 195                 mbh = (void *)(image + m);
 196                 if (mbh->magic == MB_HEADER_MAGIC) {
 197                         BAM_DPRINTF(("%s: is DBOOT unix\n", fcn));
 198                         bam_direct = BAM_DIRECT_DBOOT;
 199                         break;
 200                 }
 201         }
 202         (void) munmap(image, 8192);
 203         (void) close(fd);
 204 
 205         INJECT_ERROR1("GET_CAP_MULTIBOOT", bam_direct = BAM_DIRECT_MULTIBOOT);
 206         if (bam_direct != BAM_DIRECT_DBOOT) {
 207                 BAM_DPRINTF(("%s: is MULTIBOOT unix\n", fcn));
 208         }
 209 
 210         /* Not a fatal error if this fails */
 211         get_findroot_cap(osroot);
 212 
 213         BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
 214         return (BAM_SUCCESS);
 215 }
 216 
 217 #define INST_RELEASE    "var/sadm/system/admin/INST_RELEASE"
 218 
 219 /*
 220  * Return true if root has been bfu'ed.  bfu will blow away
 221  * var/sadm/system/admin/INST_RELEASE, so if it's still there, we can
 222  * assume the system has not been bfu'ed.
 223  */
 224 static int
 225 is_bfu_system(const char *root)
 226 {
 227         static int              is_bfu = -1;
 228         char                    path[PATH_MAX];
 229         struct stat             sb;
 230         const char              *fcn = "is_bfu_system()";
 231 
 232         if (is_bfu != -1) {
 233                 BAM_DPRINTF(("%s: already done bfu test. bfu is %s present\n",
 234                     fcn, is_bfu ? "" : "NOT"));
 235                 return (is_bfu);
 236         }
 237 
 238         (void) snprintf(path, sizeof (path), "%s/%s", root, INST_RELEASE);
 239         if (stat(path, &sb) != 0) {
 240                 is_bfu = 1;
 241                 BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
 242         } else {
 243                 is_bfu = 0;
 244                 BAM_DPRINTF(("%s: returning FAILURE\n", fcn));
 245         }
 246         return (is_bfu);
 247 }
 248 
 249 #define MENU_URL(root)  (is_bfu_system(root) ?          \
 250         "http://illumos.org/msg/SUNOS-8000-CF" :        \
 251         "http://illumos.org/msg/SUNOS-8000-AK")
 252 
 253 /*
 254  * Simply allocate a new line and copy in cmd + sep + arg
 255  */
 256 void
 257 update_line(line_t *linep)
 258 {
 259         size_t          size;
 260         const char      *fcn = "update_line()";
 261 
 262         BAM_DPRINTF(("%s: line before update: %s\n", fcn, linep->line));
 263         free(linep->line);
 264         size = strlen(linep->cmd) + strlen(linep->sep) + strlen(linep->arg) + 1;
 265         linep->line = s_calloc(1, size);
 266         (void) snprintf(linep->line, size, "%s%s%s", linep->cmd, linep->sep,
 267             linep->arg);
 268         BAM_DPRINTF(("%s: line after update: %s\n", fcn, linep->line));
 269 }
 270 
 271 static char *
 272 skip_wspace(char *ptr)
 273 {
 274         const char              *fcn = "skip_wspace()";
 275 
 276         INJECT_ERROR1("SKIP_WSPACE", ptr = NULL);
 277         if (ptr == NULL) {
 278                 BAM_DPRINTF(("%s: NULL ptr\n", fcn));
 279                 return (NULL);
 280         }
 281 
 282         BAM_DPRINTF(("%s: ptr on entry: %s\n", fcn, ptr));
 283         for (; *ptr != '\0'; ptr++) {
 284                 if ((*ptr != ' ') && (*ptr != '\t') &&
 285                     (*ptr != '\n'))
 286                         break;
 287         }
 288 
 289         ptr = (*ptr == '\0' ? NULL : ptr);
 290 
 291         BAM_DPRINTF(("%s: ptr on exit: %s\n", fcn, ptr ? ptr : "NULL"));
 292 
 293         return (ptr);
 294 }
 295 
 296 static char *
 297 rskip_bspace(char *bound, char *ptr)
 298 {
 299         const char              *fcn = "rskip_bspace()";
 300         assert(bound);
 301         assert(ptr);
 302         assert(bound <= ptr);
 303         assert(*bound != ' ' && *bound != '\t' && *bound != '\n');
 304 
 305         BAM_DPRINTF(("%s: ptr on entry: %s\n", fcn, ptr));
 306         for (; ptr > bound; ptr--) {
 307                 if (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
 308                         break;
 309         }
 310 
 311         BAM_DPRINTF(("%s: ptr on exit: %s\n", fcn, ptr));
 312         return (ptr);
 313 }
 314 
 315 /*
 316  * The parse_kernel_line function examines a menu.lst kernel line.  For
 317  * multiboot, this is:
 318  *
 319  * kernel <multiboot path> <flags1> <kernel path> <flags2>
 320  *
 321  * <multiboot path> is either /platform/i86pc/multiboot or /boot/multiboot
 322  *
 323  * <kernel path> may be missing, or may be any full or relative path to unix.
 324  *      We check for it by looking for a word ending in "/unix".  If it ends
 325  *      in "kernel/unix", we upgrade it to a 32-bit entry.  If it ends in
 326  *      "kernel/amd64/unix", we upgrade it to the default entry.  Otherwise,
 327  *      it's a custom kernel, and we skip it.
 328  *
 329  * <flags*> are anything that doesn't fit either of the above - these will be
 330  *      copied over.
 331  *
 332  * For direct boot, the defaults are
 333  *
 334  * kernel$ <kernel path> <flags>
 335  *
 336  * <kernel path> is one of:
 337  *      /platform/i86pc/kernel/$ISADIR/unix
 338  *      /boot/platform/i86pc/kernel/$ISADIR/unix
 339  *      /platform/i86pc/kernel/unix
 340  *      /platform/i86pc/kernel/amd64/unix
 341  *      /boot/platform/i86pc/kernel/unix
 342  *      /boot/platform/i86pc/kernel/amd64/unix
 343  *
 344  * If <kernel path> is any of the last four, the command may also be "kernel".
 345  *
 346  * <flags> is anything that isn't <kernel path>.
 347  *
 348  * This function is only called to convert a multiboot entry to a dboot entry
 349  *
 350  * For safety, we do one more check: if the kernel path starts with /boot,
 351  * we verify that the new kernel exists before changing it.  This is mainly
 352  * done for bfu, as it may cause the failsafe archives to be a different
 353  * boot architecture from the newly bfu'ed system.
 354  */
 355 static error_t
 356 cvt_kernel_line(line_t *line, const char *osroot, entry_t *entry)
 357 {
 358         char            path[PATH_MAX], path_64[PATH_MAX];
 359         char            linebuf[PATH_MAX];
 360         char            new_arg[PATH_MAX];
 361         struct stat     sb, sb_64;
 362         char            *old_ptr;
 363         char            *unix_ptr;
 364         char            *flags1_ptr;
 365         char            *flags2_ptr;
 366         const char      *fcn = "cvt_kernel_line()";
 367 
 368         BAM_DPRINTF(("%s: entered. args: %s %s\n", fcn, line->line, osroot));
 369 
 370         /*
 371          * We only convert multiboot to dboot and nothing else.
 372          */
 373         if (!(entry->flags & BAM_ENTRY_MULTIBOOT)) {
 374                 BAM_DPRINTF(("%s: not MULTIBOOT, not converting\n", fcn));
 375                 return (BAM_SUCCESS);
 376         }
 377 
 378         if (entry->flags & BAM_ENTRY_FAILSAFE) {
 379                 /*
 380                  * We're attempting to change failsafe to dboot.
 381                  * In the bfu case, we may not have a dboot failsafe
 382                  * kernel i.e. a "unix" under the "/boot" hierarchy.
 383                  * If so, just emit a message in verbose mode and
 384                  * return success.
 385                  */
 386                 BAM_DPRINTF(("%s: trying to convert failsafe to DBOOT\n", fcn));
 387                 (void) snprintf(path, PATH_MAX, "%s%s", osroot,
 388                     DIRECT_BOOT_FAILSAFE_32);
 389                 (void) snprintf(path_64, PATH_MAX, "%s%s", osroot,
 390                     DIRECT_BOOT_FAILSAFE_64);
 391                 if (stat(path, &sb) != 0 && stat(path_64, &sb_64) != 0) {
 392                         if (bam_verbose) {
 393                                 bam_error(_("bootadm -m upgrade run, but the "
 394                                     "failsafe archives have not been\nupdated. "
 395                                     "Not updating line %d\n"), line->lineNum);
 396                         }
 397                         BAM_DPRINTF(("%s: no FAILSAFE unix, not converting\n",
 398                             fcn));
 399                         return (BAM_SUCCESS);
 400                 }
 401         }
 402 
 403         /*
 404          * Make sure we have the correct cmd
 405          */
 406 
 407         free(line->cmd);
 408         line->cmd = s_strdup(menu_cmds[KERNEL_DOLLAR_CMD]);
 409         BAM_DPRINTF(("%s: converted kernel cmd to %s\n", fcn, line->cmd));
 410 
 411         assert(sizeof (linebuf) > strlen(line->arg) + 32);
 412         (void) strlcpy(linebuf, line->arg, sizeof (linebuf));
 413 
 414         old_ptr = strpbrk(linebuf, " \t\n");
 415         old_ptr = skip_wspace(old_ptr);
 416         if (old_ptr == NULL) {
 417                 /*
 418                  * only multiboot and nothing else
 419                  * i.e. flags1 = unix = flags2 = NULL
 420                  */
 421                 flags1_ptr = unix_ptr = flags2_ptr = NULL;
 422                 BAM_DPRINTF(("%s: NULL flags1, unix, flags2\n", fcn))
 423                 goto create;
 424         }
 425 
 426         /*
 427          *
 428          * old_ptr is either at "flags1" or "unix"
 429          */
 430         if ((unix_ptr = strstr(old_ptr, "/unix")) != NULL) {
 431 
 432                 /*
 433                  * There is a  unix.
 434                  */
 435                 BAM_DPRINTF(("%s: unix present\n", fcn));
 436 
 437                 /* See if there's a flags2 past unix */
 438                 flags2_ptr = unix_ptr + strlen("/unix");
 439                 flags2_ptr = skip_wspace(flags2_ptr);
 440                 if (flags2_ptr) {
 441                         BAM_DPRINTF(("%s: flags2 present: %s\n", fcn,
 442                             flags2_ptr));
 443                 } else {
 444                         BAM_DPRINTF(("%s: flags2 absent\n", fcn));
 445                 }
 446 
 447                 /* see if there is a flags1 before unix */
 448                 unix_ptr = rskip_bspace(old_ptr, unix_ptr);
 449 
 450                 if (unix_ptr == old_ptr) {
 451                         flags1_ptr = NULL;
 452                         BAM_DPRINTF(("%s: flags1 absent\n", fcn));
 453                 } else {
 454                         flags1_ptr = old_ptr;
 455                         *unix_ptr = '\0';
 456                         unix_ptr++;
 457                         BAM_DPRINTF(("%s: flags1 present: %s\n", fcn,
 458                             flags1_ptr));
 459                 }
 460 
 461         } else  {
 462                 /* There is no unix, there is only a bunch of flags */
 463                 flags1_ptr = old_ptr;
 464                 unix_ptr = flags2_ptr = NULL;
 465                 BAM_DPRINTF(("%s: flags1 present: %s, unix, flags2 absent\n",
 466                     fcn, flags1_ptr));
 467         }
 468 
 469         /*
 470          * With dboot, unix is fixed and is at the beginning. We need to
 471          * migrate flags1 and flags2
 472          */
 473 create:
 474         if (entry->flags & BAM_ENTRY_FAILSAFE) {
 475                 (void) snprintf(new_arg, sizeof (new_arg), "%s",
 476                     DIRECT_BOOT_FAILSAFE_KERNEL);
 477         } else {
 478                 (void) snprintf(new_arg, sizeof (new_arg), "%s",
 479                     DIRECT_BOOT_KERNEL);
 480         }
 481         BAM_DPRINTF(("%s: converted unix: %s\n", fcn, new_arg));
 482 
 483         if (flags1_ptr != NULL) {
 484                 (void) strlcat(new_arg, " ", sizeof (new_arg));
 485                 (void) strlcat(new_arg, flags1_ptr, sizeof (new_arg));
 486         }
 487 
 488         if (flags2_ptr != NULL) {
 489                 (void) strlcat(new_arg, " ", sizeof (new_arg));
 490                 (void) strlcat(new_arg, flags2_ptr, sizeof (new_arg));
 491         }
 492 
 493         BAM_DPRINTF(("%s: converted unix with flags : %s\n", fcn, new_arg));
 494 
 495         free(line->arg);
 496         line->arg = s_strdup(new_arg);
 497         update_line(line);
 498         BAM_DPRINTF(("%s: converted line is: %s\n", fcn, line->line));
 499         return (BAM_SUCCESS);
 500 }
 501 
 502 /*
 503  * Similar to above, except this time we're looking at a module line,
 504  * which is quite a bit simpler.
 505  *
 506  * Under multiboot, the archive line is:
 507  *
 508  * module /platform/i86pc/boot_archive
 509  *
 510  * Under directboot, the archive line is:
 511  *
 512  * module$ /platform/i86pc/$ISADIR/boot_archive
 513  *
 514  * which may be specified exactly as either of:
 515  *
 516  * module /platform/i86pc/boot_archive
 517  * module /platform/i86pc/amd64/boot_archive
 518  *
 519  * Under multiboot, the failsafe is:
 520  *
 521  * module /boot/x86.miniroot-safe
 522  *
 523  * Under dboot, the failsafe is:
 524  *
 525  * module$ /boot/$ISADIR/x86.miniroot-safe
 526  *
 527  * which may be specified exactly as either of:
 528  *
 529  * module /boot/x86.miniroot-safe
 530  * module /boot/amd64/x86.miniroot-safe
 531  */
 532 static error_t
 533 cvt_module_line(line_t *line, entry_t *entry)
 534 {
 535         const char              *fcn = "cvt_module_line()";
 536 
 537         BAM_DPRINTF(("%s: entered. arg: %s\n", fcn, line->line));
 538 
 539         /*
 540          * We only convert multiboot to dboot and nothing else
 541          */
 542         if (!(entry->flags & BAM_ENTRY_MULTIBOOT)) {
 543                 BAM_DPRINTF(("%s: not MULTIBOOT, not converting\n", fcn));
 544                 return (BAM_SUCCESS);
 545         }
 546 
 547         if (entry->flags & BAM_ENTRY_FAILSAFE) {
 548                 if (strcmp(line->arg, FAILSAFE_ARCHIVE) == 0) {
 549                         BAM_DPRINTF(("%s: failsafe module line needs no "
 550                             "conversion: %s\n", fcn, line->arg));
 551                         BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
 552                         return (BAM_SUCCESS);
 553                 }
 554         } else if (strcmp(line->arg, MULTIBOOT_ARCHIVE) != 0) {
 555                 bam_error(_("module command on line %d not recognized.\n"),
 556                     line->lineNum);
 557                 BAM_DPRINTF(("%s: returning FAILURE\n", fcn));
 558                 return (BAM_MSG);
 559         }
 560 
 561         free(line->cmd);
 562         free(line->arg);
 563         line->cmd = s_strdup(menu_cmds[MODULE_DOLLAR_CMD]);
 564 
 565         line->arg = s_strdup(entry->flags & BAM_ENTRY_FAILSAFE ?
 566             FAILSAFE_ARCHIVE : DIRECT_BOOT_ARCHIVE);
 567 
 568         update_line(line);
 569         BAM_DPRINTF(("%s: converted module line is: %s\n", fcn, line->line));
 570         BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
 571         return (BAM_SUCCESS);
 572 }
 573 
 574 static void
 575 bam_warn_hand_entries(menu_t *mp, char *osroot)
 576 {
 577         int             hand_num;
 578         int             hand_max;
 579         int             *hand_list;
 580         int             i;
 581         entry_t         *entry;
 582         const char      *fcn = "bam_warn_hand_entries()";
 583 
 584         if (bam_force) {
 585                 /*
 586                  * No warning needed, we are automatically converting
 587                  * the "hand" entries
 588                  */
 589                 BAM_DPRINTF(("%s: force specified, no warnings about hand "
 590                     "entries\n",  fcn));
 591                 return;
 592         }
 593 
 594         hand_num = 0;
 595         hand_max = BAM_ENTRY_NUM;
 596         hand_list = s_calloc(1, hand_max);
 597 
 598         for (entry = mp->entries; entry; entry = entry->next) {
 599                 if (entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU))
 600                         continue;
 601                 BAM_DPRINTF(("%s: found hand entry #: %d\n", fcn,
 602                     entry->entryNum));
 603                 if (++hand_num > hand_max) {
 604                         hand_max *= 2;
 605                         hand_list = s_realloc(hand_list,
 606                             hand_max * sizeof (int));
 607                 }
 608                 hand_list[hand_num - 1] = entry->entryNum;
 609         }
 610 
 611         bam_error(_("bootadm(1M) will only upgrade GRUB menu entries added "
 612             "by \nbootadm(1M) or lu(1M). The following entries on %s will "
 613             "not be upgraded.\nFor details on manually updating entries, "
 614             "see %s\n"), osroot, MENU_URL(osroot));
 615         bam_print_stderr("Entry Number%s: ", (hand_num > 1) ?
 616             "s" : "");
 617         for (i = 0; i < hand_num; i++) {
 618                 bam_print_stderr("%d ", hand_list[i]);
 619         }
 620         bam_print_stderr("\n");
 621 }
 622 
 623 static entry_t *
 624 find_matching_entry(
 625         entry_t *estart,
 626         char *grubsign,
 627         char *grubroot,
 628         int root_opt)
 629 {
 630         entry_t         *entry;
 631         line_t          *line;
 632         char            opt[10];
 633         const char      *fcn = "find_matching_entry()";
 634 
 635         assert(grubsign);
 636         assert(root_opt == 0 || root_opt == 1);
 637 
 638         (void) snprintf(opt, sizeof (opt), "%d", root_opt);
 639         BAM_DPRINTF(("%s: entered. args: %s %s %s\n", fcn, grubsign,
 640             grubroot, opt));
 641 
 642         for (entry = estart; entry; entry = entry->next) {
 643 
 644                 if (!(entry->flags & (BAM_ENTRY_BOOTADM|BAM_ENTRY_LU)) &&
 645                     !bam_force) {
 646                         BAM_DPRINTF(("%s: skipping hand entry #: %d\n",
 647                             fcn, entry->entryNum));
 648                         continue;
 649                 }
 650 
 651                 if (entry->flags & BAM_ENTRY_ROOT) {
 652                         for (line = entry->start; line; line = line->next) {
 653                                 if (line->cmd == NULL || line->arg == NULL) {
 654                                         if (line == entry->end) {
 655                                                 BAM_DPRINTF(("%s: entry has "
 656                                                     "ended\n", fcn));
 657                                                 break;
 658                                         } else {
 659                                                 BAM_DPRINTF(("%s: skipping "
 660                                                     "NULL line\n", fcn));
 661                                                 continue;
 662                                         }
 663                                 }
 664                                 if (strcmp(line->cmd, menu_cmds[ROOT_CMD])
 665                                     == 0 && strcmp(line->arg, grubroot) == 0) {
 666                                         BAM_DPRINTF(("%s: found matching root "
 667                                             "line: %s,%s\n", fcn,
 668                                             line->line, grubsign));
 669                                         return (entry);
 670                                 }
 671                                 if (line == entry->end) {
 672                                         BAM_DPRINTF(("%s: entry has ended\n",
 673                                             fcn));
 674                                         break;
 675                                 }
 676                         }
 677                 } else if (entry->flags & BAM_ENTRY_FINDROOT) {
 678                         for (line = entry->start; line; line = line->next) {
 679                                 if (line->cmd == NULL || line->arg == NULL) {
 680                                         if (line == entry->end) {
 681                                                 BAM_DPRINTF(("%s: entry has "
 682                                                     "ended\n", fcn));
 683                                                 break;
 684                                         } else {
 685                                                 BAM_DPRINTF(("%s: skipping "
 686                                                     "NULL line\n", fcn));
 687                                                 continue;
 688                                         }
 689                                 }
 690                                 if (strcmp(line->cmd, menu_cmds[FINDROOT_CMD])
 691                                     == 0 && strcmp(line->arg, grubsign) == 0) {
 692                                         BAM_DPRINTF(("%s: found matching "
 693                                             "findroot line: %s,%s\n", fcn,
 694                                             line->line, grubsign));
 695                                         return (entry);
 696                                 }
 697                                 if (line == entry->end) {
 698                                         BAM_DPRINTF(("%s: entry has ended\n",
 699                                             fcn));
 700                                         break;
 701                                 }
 702                         }
 703                 } else if (root_opt) {
 704                         /* Neither root nor findroot */
 705                         BAM_DPRINTF(("%s: no root or findroot and root is "
 706                             "opt: %d\n", fcn, entry->entryNum));
 707                         return (entry);
 708                 }
 709         }
 710 
 711         BAM_DPRINTF(("%s: no matching entry found\n", fcn));
 712         return (NULL);
 713 }
 714 
 715 /*
 716  * The following is a set of routines that attempt to convert the
 717  * menu entries for the supplied osroot into a format compatible
 718  * with the GRUB installation on osroot.
 719  *
 720  * Each of these conversion routines make no assumptions about
 721  * the current state of the menu entry, it does its best to
 722  * convert the menu entry to the new state. In the process
 723  * we may either upgrade or downgrade.
 724  *
 725  * We don't make any heroic efforts at conversion. It is better
 726  * to be conservative and bail out at the first sign of error. We will
 727  * in such cases, point the user at the knowledge-base article
 728  * so that they can upgrade manually.
 729  */
 730 static error_t
 731 bam_add_findroot(menu_t *mp, char *grubsign, char *grubroot, int root_opt)
 732 {
 733         entry_t         *entry;
 734         line_t          *line;
 735         line_t          *newlp;
 736         int             update_num;
 737         char            linebuf[PATH_MAX];
 738         const char      *fcn = "bam_add_findroot()";
 739 
 740         update_num = 0;
 741 
 742         bam_print(_("converting entries to findroot...\n"));
 743 
 744         entry = find_matching_entry(mp->entries, grubsign, grubroot, root_opt);
 745         while (entry != NULL) {
 746                 if (entry->flags & BAM_ENTRY_FINDROOT) {
 747                         /* already converted */
 748                         BAM_DPRINTF(("%s: entry %d already converted to "
 749                             "findroot\n", fcn, entry->entryNum));
 750                         entry = find_matching_entry(entry->next, grubsign,
 751                             grubroot, root_opt);
 752                         continue;
 753                 }
 754                 for (line = entry->start; line; line = line->next) {
 755                         if (line->cmd == NULL || line->arg == NULL) {
 756                                 if (line == entry->end) {
 757                                         BAM_DPRINTF(("%s: entry has ended\n",
 758                                             fcn));
 759                                         break;
 760                                 } else {
 761                                         BAM_DPRINTF(("%s: skipping NULL line\n",
 762                                             fcn));
 763                                         continue;
 764                                 }
 765                         }
 766                         if (strcmp(line->cmd, menu_cmds[TITLE_CMD]) == 0) {
 767                                 newlp = s_calloc(1, sizeof (line_t));
 768                                 newlp->cmd = s_strdup(menu_cmds[FINDROOT_CMD]);
 769                                 newlp->sep = s_strdup(" ");
 770                                 newlp->arg = s_strdup(grubsign);
 771                                 (void) snprintf(linebuf, sizeof (linebuf),
 772                                     "%s%s%s", newlp->cmd, newlp->sep,
 773                                     newlp->arg);
 774                                 newlp->line = s_strdup(linebuf);
 775                                 bam_add_line(mp, entry, line, newlp);
 776                                 update_num = 1;
 777                                 entry->flags &= ~BAM_ENTRY_ROOT;
 778                                 entry->flags |= BAM_ENTRY_FINDROOT;
 779                                 BAM_DPRINTF(("%s: added findroot line: %s\n",
 780                                     fcn, newlp->line));
 781                                 line = newlp;
 782                         }
 783                         if (strcmp(line->cmd, menu_cmds[ROOT_CMD]) == 0) {
 784                                 BAM_DPRINTF(("%s: freeing root line: %s\n",
 785                                     fcn, line->line));
 786                                 unlink_line(mp, line);
 787                                 line_free(line);
 788                         }
 789                         if (line == entry->end) {
 790                                 BAM_DPRINTF(("%s: entry has ended\n", fcn));
 791                                 break;
 792                         }
 793                 }
 794                 entry = find_matching_entry(entry->next, grubsign, grubroot,
 795                     root_opt);
 796         }
 797 
 798         if (update_num) {
 799                 BAM_DPRINTF(("%s: updated numbering\n", fcn));
 800                 update_numbering(mp);
 801         }
 802 
 803         BAM_DPRINTF(("%s: returning SUCCESS\n", fcn));
 804         return (BAM_SUCCESS);
 805 }
 806 
 807 static error_t
 808 bam_add_dboot(
 809         menu_t *mp,
 810         char *osroot,
 811         char *grubsign,
 812         char *grubroot,
 813         int root_opt)
 814 {
 815         int             msg = 0;
 816         entry_t         *entry;
 817         line_t          *line;
 818         error_t         ret;
 819         const char      *fcn = "bam_add_dboot()";
 820 
 821         bam_print(_("converting entries to dboot...\n"));
 822 
 823         entry = find_matching_entry(mp->entries, grubsign, grubroot, root_opt);
 824         while (entry != NULL) {
 825                 for (line = entry->start; line; line = line->next) {
 826                         if (line->cmd == NULL || line->arg == NULL) {
 827                                 if (line == entry->end) {
 828                                         BAM_DPRINTF(("%s: entry has ended\n",
 829                                             fcn));
 830                                         break;
 831                                 } else {
 832                                         BAM_DPRINTF(("%s: skipping NULL line\n",
 833                                             fcn));
 834                                         continue;
 835                                 }
 836                         }
 837 
 838                         /*
 839                          * If we have a kernel$ command, assume it
 840                          * is dboot already.  If it is not a dboot
 841                          * entry, something funny is going on and
 842                          * we will leave it alone
 843                          */
 844                         if (strcmp(line->cmd, menu_cmds[KERNEL_CMD]) == 0) {
 845                                 ret = cvt_kernel_line(line, osroot, entry);
 846                                 INJECT_ERROR1("ADD_DBOOT_KERN_ERR",
 847                                     ret = BAM_ERROR);
 848                                 INJECT_ERROR1("ADD_DBOOT_KERN_MSG",
 849                                     ret = BAM_MSG);
 850                                 if (ret == BAM_ERROR) {
 851                                         BAM_DPRINTF(("%s: cvt_kernel_line() "
 852                                             "failed\n", fcn));
 853                                         return (ret);
 854                                 } else if (ret == BAM_MSG) {
 855                                         msg = 1;
 856                                         BAM_DPRINTF(("%s: BAM_MSG returned "
 857                                             "from cvt_kernel_line()\n", fcn));
 858                                 }
 859                         }
 860                         if (strcmp(line->cmd, menu_cmds[MODULE_CMD]) == 0) {
 861                                 ret = cvt_module_line(line, entry);
 862                                 INJECT_ERROR1("ADD_DBOOT_MOD_ERR",
 863                                     ret = BAM_ERROR);
 864                                 INJECT_ERROR1("ADD_DBOOT_MOD_MSG",
 865                                     ret = BAM_MSG);
 866                                 if (ret == BAM_ERROR) {
 867                                         BAM_DPRINTF(("%s: cvt_module_line() "
 868                                             "failed\n", fcn));
 869                                         return (ret);
 870                                 } else if (ret == BAM_MSG) {
 871                                         BAM_DPRINTF(("%s: BAM_MSG returned "
 872                                             "from cvt_module_line()\n", fcn));
 873                                         msg = 1;
 874                                 }
 875                         }
 876 
 877                         if (line == entry->end) {
 878                                 BAM_DPRINTF(("%s: entry has ended\n", fcn));
 879                                 break;
 880                         }
 881                 }
 882                 entry = find_matching_entry(entry->next, grubsign, grubroot,
 883                     root_opt);
 884         }
 885 
 886         ret = msg ? BAM_MSG : BAM_SUCCESS;
 887         BAM_DPRINTF(("%s: returning ret = %d\n", fcn, ret));
 888         return (ret);
 889 }
 890 
 891 /*ARGSUSED*/
 892 error_t
 893 upgrade_menu(menu_t *mp, char *osroot, char *menu_root)
 894 {
 895         char            *osdev;
 896         char            *grubsign;
 897         char            *grubroot;
 898         int             ret1;
 899         int             ret2;
 900         const char      *fcn = "upgrade_menu()";
 901 
 902         assert(osroot);
 903         assert(menu_root);
 904 
 905         BAM_DPRINTF(("%s: entered. args: %s %s\n", fcn, osroot, menu_root));
 906 
 907         /*
 908          * We only support upgrades.
 909          */
 910         if (bam_is_findroot != BAM_FINDROOT_PRESENT ||
 911             bam_direct != BAM_DIRECT_DBOOT) {
 912                 bam_error(_("automated downgrade of GRUB menu to older "
 913                     "version not supported.\n"));
 914                 return (BAM_ERROR);
 915         }
 916 
 917         /*
 918          * First get the GRUB signature
 919          */
 920         osdev = get_special(osroot);
 921         INJECT_ERROR1("UPGRADE_OSDEV", osdev = NULL);
 922         if (osdev == NULL) {
 923                 bam_error(_("cant find special file for mount-point %s\n"),
 924                     osroot);
 925                 return (BAM_ERROR);
 926         }
 927 
 928         grubsign = get_grubsign(osroot, osdev);
 929         INJECT_ERROR1("UPGRADE_GRUBSIGN", grubsign = NULL);
 930         if (grubsign == NULL) {
 931                 free(osdev);
 932                 bam_error(_("cannot find GRUB signature for %s\n"), osroot);
 933                 return (BAM_ERROR);
 934         }
 935 
 936         /* not fatal if we can't get grubroot */
 937         grubroot = get_grubroot(osroot, osdev, menu_root);
 938         INJECT_ERROR1("UPGRADE_GRUBROOT", grubroot = NULL);
 939 
 940         free(osdev);
 941 
 942         ret1 = bam_add_findroot(mp, grubsign,
 943             grubroot, root_optional(osroot, menu_root));
 944         INJECT_ERROR1("UPGRADE_ADD_FINDROOT", ret1 = BAM_ERROR);
 945         if (ret1 == BAM_ERROR)
 946                 goto abort;
 947 
 948         ret2 = bam_add_dboot(mp, osroot, grubsign,
 949             grubroot, root_optional(osroot, menu_root));
 950         INJECT_ERROR1("UPGRADE_ADD_DBOOT", ret2 = BAM_ERROR);
 951         if (ret2 == BAM_ERROR)
 952                 goto abort;
 953 
 954         if (ret1 == BAM_MSG || ret2 == BAM_MSG) {
 955                 bam_error(_("one or more GRUB menu entries were not "
 956                     "automatically upgraded\nFor details on manually "
 957                     "updating entries, see %s\n"), MENU_URL(osroot));
 958         } else {
 959                 bam_warn_hand_entries(mp, osroot);
 960         }
 961 
 962         free(grubsign);
 963 
 964         BAM_DPRINTF(("%s: returning ret = %d\n", fcn, BAM_WRITE));
 965         return (BAM_WRITE);
 966 
 967 abort:
 968         free(grubsign);
 969         bam_error(_("error upgrading GRUB menu entries on %s. Aborting.\n"
 970             "For details on manually updating entries, see %s\n"), osroot,
 971             MENU_URL(osroot));
 972         return (BAM_ERROR);
 973 }