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 functions for manipulating the GRUB menu. 28 */ 29 #include <stdio.h> 30 #include <errno.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 #include <sys/types.h> 35 #include <sys/mount.h> 36 #include <stdarg.h> 37 #include <assert.h> 38 #include <ctype.h> 39 40 #include "libgrub_impl.h" 41 42 static const grub_cmd_desc_t grub_cmd_descs[GRBM_CMD_NUM] = { 43 #define menu_cmd(cmd, num, flag, parsef) {cmd, num, flag}, 44 #include "libgrub_cmd.def" 45 }; 46 47 static void 48 append_line(grub_menu_t *mp, grub_line_t *lp) 49 { 50 if (mp->gm_start == NULL) { 51 mp->gm_start = lp; 52 } else { 53 mp->gm_end->gl_next = lp; 54 lp->gl_prev = mp->gm_end; 55 } 56 mp->gm_end = lp; 57 lp->gl_line_num = ++mp->gm_line_num; 58 lp->gl_entry_num = GRUB_ENTRY_DEFAULT; 59 } 60 61 static void 62 process_line(grub_menu_t *mp) 63 { 64 int n; 65 grub_line_t *lp; 66 67 lp = mp->gm_end; 68 n = sizeof (grub_cmd_descs) / sizeof (grub_cmd_descs[0]); 69 70 /* search through the table of known commands */ 71 while (n-- != 0 && strcmp(lp->gl_cmd, grub_cmd_descs[n].gcd_cmd) != 0) 72 ; 73 74 /* unknown command */ 75 if (n < 0) 76 return; 77 78 /* we found command, fill lp fields */ 79 lp->gl_flags = grub_cmd_descs[n].gcd_flags; 80 lp->gl_cmdtp = grub_cmd_descs[n].gcd_num; 81 } 82 83 84 static void 85 check_entry(grub_entry_t *ent) 86 { 87 int i; 88 uint_t emask; 89 grub_line_t *lp; 90 const grub_line_t * const lend = ent->ge_end->gl_next; 91 92 emask = 0; 93 for (i = 0, lp = ent->ge_start; lend != lp; lp = lp->gl_next, ++i) { 94 lp->gl_entry_num = ent->ge_entry_num; 95 if (lp->gl_flags == GRUB_LINE_INVALID || 96 lp->gl_flags == GRUB_LINE_GLOBAL) { 97 emask |= 1 << i; 98 lp->gl_flags = GRUB_LINE_INVALID; 99 } 100 } 101 102 if ((ent->ge_emask = emask) == 0) 103 ent->ge_flags |= GRBM_VALID_FLAG; 104 } 105 106 static int 107 add_entry(grub_menu_t *mp, grub_line_t *start, grub_line_t *end) 108 { 109 grub_entry_t *ent; 110 111 if ((ent = calloc(1, sizeof (*ent))) == NULL) 112 return (errno); 113 114 ent->ge_start = start; 115 ent->ge_end = end; 116 117 if (mp->gm_ent_end == NULL) { 118 mp->gm_ent_start = ent; 119 } else { 120 mp->gm_ent_end->ge_next = ent; 121 ent->ge_prev = mp->gm_ent_end; 122 } 123 mp->gm_ent_end = ent; 124 ent->ge_entry_num = mp->gm_entry_num++; 125 ent->ge_menu = mp; 126 return (0); 127 } 128 129 static void 130 default_entry(grub_menu_t *mp) 131 { 132 uint_t defent; 133 grub_line_t *lp; 134 grub_entry_t *ent; 135 136 defent = 0; 137 lp = mp->gm_curdefault; 138 139 if (lp != NULL && lp->gl_flags == GRUB_LINE_GLOBAL && 140 lp->gl_cmdtp == GRBM_DEFAULT_CMD) { 141 defent = strtoul(lp->gl_arg, NULL, 0); 142 if (defent >= mp->gm_entry_num) 143 defent = 0; 144 } 145 146 for (ent = mp->gm_ent_start; ent != NULL && defent != ent->ge_entry_num; 147 ent = ent->ge_next) 148 ; 149 150 mp->gm_ent_default = ent; 151 } 152 153 static void 154 free_line(grub_line_t *lp) 155 { 156 if (lp == NULL) 157 return; 158 159 free(lp->gl_cmd); 160 free(lp->gl_sep); 161 free(lp->gl_arg); 162 free(lp->gl_line); 163 free(lp); 164 } 165 166 static void 167 free_linelist(grub_line_t *line) 168 { 169 grub_line_t *lp; 170 171 if (line == NULL) 172 return; 173 174 while (line) { 175 lp = line; 176 line = lp->gl_next; 177 free_line(lp); 178 } 179 } 180 181 static void 182 free_entries(grub_menu_t *mp) 183 { 184 grub_entry_t *ent, *tmp; 185 186 if (mp == NULL) 187 return; 188 189 for (ent = mp->gm_ent_start; (tmp = ent) != NULL; 190 ent = tmp->ge_next, free(tmp)) 191 ; 192 193 mp->gm_ent_start = NULL; 194 mp->gm_ent_end = NULL; 195 } 196 197 static int 198 grub_menu_append_line(grub_menu_t *mp, const char *line) 199 { 200 int rc; 201 size_t n; 202 grub_line_t *lp; 203 204 if (line == NULL) 205 return (EINVAL); 206 207 rc = 0; 208 lp = NULL; 209 if ((lp = calloc(1, sizeof (*lp))) == NULL || 210 (lp->gl_line = strdup(line)) == NULL) { 211 free(lp); 212 return (errno); 213 } 214 215 /* skip initial white space */ 216 line += strspn(line, " \t"); 217 218 /* process comment line */ 219 if (line[0] == '#') { 220 if ((lp->gl_cmd = 221 strdup(grub_cmd_descs[GRBM_COMMENT_CMD].gcd_cmd)) == NULL || 222 (lp->gl_sep = 223 strdup(grub_cmd_descs[GRBM_EMPTY_CMD].gcd_cmd)) == NULL || 224 (lp->gl_arg = strdup(line + 1)) == NULL) 225 rc = errno; 226 } else { 227 /* get command */ 228 n = strcspn(line, " \t="); 229 if ((lp->gl_cmd = malloc(n + 1)) == NULL) 230 rc = errno; 231 else 232 (void) strlcpy(lp->gl_cmd, line, n + 1); 233 234 line += n; 235 236 /* get separator */ 237 n = strspn(line, " \t="); 238 if ((lp->gl_sep = malloc(n + 1)) == NULL) 239 rc = errno; 240 else 241 (void) strlcpy(lp->gl_sep, line, n + 1); 242 243 line += n; 244 245 /* get arguments */ 246 if ((lp->gl_arg = strdup(line)) == NULL) 247 rc = errno; 248 } 249 250 if (rc != 0) { 251 free_line(lp); 252 return (rc); 253 } 254 255 append_line(mp, lp); 256 process_line(mp); 257 return (0); 258 } 259 260 static int 261 grub_menu_process(grub_menu_t *mp) 262 { 263 int ret; 264 grub_entry_t *ent; 265 grub_line_t *line, *start; 266 267 /* Free remaininig entries, if any */ 268 free_entries(mp); 269 270 /* 271 * Walk through lines, till first 'title' command is encountered. 272 * Initialize globals. 273 */ 274 for (line = mp->gm_start; line != NULL; line = line->gl_next) { 275 if (line->gl_flags == GRUB_LINE_GLOBAL && 276 line->gl_cmdtp == GRBM_DEFAULT_CMD) 277 mp->gm_curdefault = line; 278 else if (line->gl_cmdtp == GRBM_TITLE_CMD) 279 break; 280 } 281 282 /* 283 * Walk through remaining lines and recreate menu entries. 284 */ 285 for (start = NULL; line != NULL; line = line->gl_next) { 286 if (line->gl_cmdtp == GRBM_TITLE_CMD) { 287 /* is first entry */ 288 if (start != NULL && 289 (ret = add_entry(mp, start, line->gl_prev)) != 0) 290 return (ret); 291 start = line; 292 } 293 } 294 295 /* Add last entry */ 296 if (start != NULL && (ret = add_entry(mp, start, mp->gm_end)) != 0) 297 return (ret); 298 299 for (ent = mp->gm_ent_start; NULL != ent; ent = ent->ge_next) 300 check_entry(ent); 301 302 default_entry(mp); 303 304 return (0); 305 } 306 307 static int 308 grub_fs_init(grub_fs_t *fs) 309 { 310 assert(fs); 311 if ((fs->gf_lzfh = libzfs_init()) == NULL || 312 (fs->gf_diroot = di_init("/", DINFOCPYALL | DINFOPATH)) 313 == DI_NODE_NIL || 314 (fs->gf_dvlh = di_devlink_init(NULL, 0)) == DI_LINK_NIL) { 315 return (EG_INITFS); 316 } 317 return (0); 318 } 319 320 static void 321 grub_fs_fini(grub_fs_t *fs) 322 { 323 if (fs == NULL) 324 return; 325 326 if (fs->gf_dvlh != DI_LINK_NIL) 327 (void) di_devlink_fini(&fs->gf_dvlh); 328 if (fs->gf_diroot != DI_NODE_NIL) 329 di_fini(fs->gf_diroot); 330 if (fs->gf_lzfh != NULL) 331 libzfs_fini(fs->gf_lzfh); 332 (void) memset(fs, 0, sizeof (*fs)); 333 } 334 335 /* 336 * Reads and parses GRUB menu file into a grub_menu_t data structure. 337 * If grub_menu_path file path is NULL, will use 'currently active' 338 * GRUB menu file. 339 * 340 * Memory for the menu data structure is allocated within the routine. 341 * Caller must call grub_menu_fini() to release memory after calling 342 * grub_menu_init(). 343 */ 344 int 345 grub_menu_init(const char *path, grub_menu_t **menup) 346 { 347 FILE *fp; 348 char *cp; 349 grub_menu_t *mp; 350 int len, n, ret; 351 char buf[GRBM_MAXLINE]; 352 353 if (menup == NULL) 354 return (EINVAL); 355 356 /* 357 * Allocate space, perform initialization 358 */ 359 if ((mp = calloc(1, sizeof (*mp))) == NULL) { 360 *menup = mp; 361 return (errno); 362 } 363 364 if ((ret = grub_fs_init(&mp->gm_fs)) != 0 || 365 (ret = grub_current_root(&mp->gm_fs, &mp->gm_root)) != 0) 366 goto err_out1; 367 368 if (path == NULL) { 369 /* 370 * Use default grub-menu. 371 * If top dataset is not mounted, mount it now. 372 */ 373 if (mp->gm_root.gr_fs[GRBM_FS_TOP].gfs_mountp[0] == 0) { 374 if ((ret = grub_fsd_mount_tmp(mp->gm_root.gr_fs + 375 GRBM_FS_TOP, mp->gm_root.gr_fstyp)) != 0) 376 goto err_out1; 377 } 378 (void) snprintf(mp->gm_path, sizeof (mp->gm_path), 379 "%s/%s", mp->gm_root.gr_fs[GRBM_FS_TOP].gfs_mountp, 380 GRUB_MENU); 381 } else { 382 (void) strlcpy(mp->gm_path, path, sizeof (mp->gm_path)); 383 } 384 385 if ((fp = fopen(mp->gm_path, "r")) == NULL) { 386 ret = errno; 387 goto err_out1; 388 } 389 390 cp = buf; 391 len = sizeof (buf); 392 393 while (fgets(cp, len, fp) != NULL) { 394 395 if (IS_LINE2BIG(cp, len, n)) { 396 ret = E2BIG; 397 break; 398 } 399 400 /* remove white space at the end of line */ 401 for (; isspace(cp[n - 1]); --n) 402 ; 403 cp[n] = '\0'; 404 405 if (cp[n - 1] == '\\') { 406 len -= n - 1; 407 assert(len >= 2); 408 cp += n - 1; 409 continue; 410 } 411 if ((ret = grub_menu_append_line(mp, buf)) != 0) 412 break; 413 414 cp = buf; 415 len = sizeof (buf); 416 } 417 418 if (fclose(fp) == EOF) 419 ret = errno; 420 else if (ret == 0) 421 ret = grub_menu_process(mp); 422 423 err_out1: 424 grub_fsd_umount_tmp(mp->gm_root.gr_fs + GRBM_FS_TOP); 425 if (0 != ret) { 426 grub_menu_fini(mp); 427 mp = NULL; 428 } 429 *menup = mp; 430 return (ret); 431 } 432 433 void 434 grub_menu_fini(grub_menu_t *mp) 435 { 436 if (mp == NULL) 437 return; 438 439 grub_fs_fini(&mp->gm_fs); 440 free_entries(mp); 441 free_linelist(mp->gm_start); 442 free(mp); 443 } 444 445 grub_line_t * 446 grub_menu_next_line(const grub_menu_t *mp, const grub_line_t *lp) 447 { 448 assert(mp); 449 if (lp == NULL) 450 return (mp->gm_start); 451 else 452 return (lp->gl_next); 453 } 454 455 grub_line_t * 456 grub_menu_prev_line(const grub_menu_t *mp, const grub_line_t *lp) 457 { 458 assert(mp); 459 if (lp == NULL) 460 return (mp->gm_end); 461 else 462 return (lp->gl_prev); 463 } 464 465 grub_line_t * 466 grub_menu_get_line(const grub_menu_t *mp, int num) 467 { 468 grub_line_t *lp; 469 470 assert(mp); 471 if (num > mp->gm_line_num) 472 return (NULL); 473 for (lp = mp->gm_start; lp != NULL && num != lp->gl_line_num; 474 lp = lp->gl_next) 475 ; 476 return (lp); 477 } 478 479 size_t 480 grub_menu_get_cmdline(const grub_menu_t *mp, int num, char *cmdl, size_t size) 481 { 482 grub_entry_t *ent; 483 484 assert(mp); 485 if ((ent = grub_menu_get_entry(mp, num)) == NULL) 486 return (size_t)(-1); 487 488 return (grub_entry_get_cmdline(ent, cmdl, size)); 489 } 490 491 grub_entry_t * 492 grub_menu_next_entry(const grub_menu_t *mp, const grub_entry_t *ent) 493 { 494 assert(mp); 495 if (ent == NULL) { 496 return (mp->gm_ent_start); 497 } else { 498 assert(mp == ent->ge_menu); 499 return (ent->ge_next); 500 } 501 } 502 503 grub_entry_t * 504 grub_menu_prev_entry(const grub_menu_t *mp, const grub_entry_t *ent) 505 { 506 assert(mp); 507 if (ent == NULL) { 508 return (mp->gm_ent_end); 509 } else { 510 assert(mp == ent->ge_menu); 511 return (ent->ge_prev); 512 } 513 } 514 515 grub_entry_t * 516 grub_menu_get_entry(const grub_menu_t *mp, int num) 517 { 518 grub_entry_t *ent; 519 520 assert(mp); 521 if (num == GRUB_ENTRY_DEFAULT) { 522 ent = mp->gm_ent_default; 523 } else if (num >= mp->gm_entry_num) { 524 ent = NULL; 525 } else { 526 for (ent = mp->gm_ent_start; 527 ent != NULL && num != ent->ge_entry_num; 528 ent = ent->ge_next) 529 ; 530 } 531 return (ent); 532 } 533