Merge branch 'master' of git://github.com/illumos/illumos-gate
[unleashed.git] / usr / src / lib / libgrubmgmt / common / libgrub_menu.c
blobf486cb85f9593697fef973def20d9e2b4ca32745
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
27 * This file contains functions for manipulating the GRUB menu.
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>
40 #include "libgrub_impl.h"
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"
47 static void
48 append_line(grub_menu_t *mp, grub_line_t *lp)
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;
56 mp->gm_end = lp;
57 lp->gl_line_num = ++mp->gm_line_num;
58 lp->gl_entry_num = GRUB_ENTRY_DEFAULT;
61 static void
62 process_line(grub_menu_t *mp)
64 int n;
65 grub_line_t *lp;
67 lp = mp->gm_end;
68 n = sizeof (grub_cmd_descs) / sizeof (grub_cmd_descs[0]);
70 /* search through the table of known commands */
71 while (n-- != 0 && strcmp(lp->gl_cmd, grub_cmd_descs[n].gcd_cmd) != 0)
74 /* unknown command */
75 if (n < 0)
76 return;
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;
84 static void
85 check_entry(grub_entry_t *ent)
87 int i;
88 uint_t emask;
89 grub_line_t *lp;
90 const grub_line_t * const lend = ent->ge_end->gl_next;
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;
102 if ((ent->ge_emask = emask) == 0)
103 ent->ge_flags |= GRBM_VALID_FLAG;
106 static int
107 add_entry(grub_menu_t *mp, grub_line_t *start, grub_line_t *end)
109 grub_entry_t *ent;
111 if ((ent = calloc(1, sizeof (*ent))) == NULL)
112 return (errno);
114 ent->ge_start = start;
115 ent->ge_end = end;
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;
123 mp->gm_ent_end = ent;
124 ent->ge_entry_num = mp->gm_entry_num++;
125 ent->ge_menu = mp;
126 return (0);
129 static void
130 default_entry(grub_menu_t *mp)
132 uint_t defent;
133 grub_line_t *lp;
134 grub_entry_t *ent;
136 defent = 0;
137 lp = mp->gm_curdefault;
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;
146 for (ent = mp->gm_ent_start; ent != NULL && defent != ent->ge_entry_num;
147 ent = ent->ge_next)
150 mp->gm_ent_default = ent;
153 static void
154 free_line(grub_line_t *lp)
156 if (lp == NULL)
157 return;
159 free(lp->gl_cmd);
160 free(lp->gl_sep);
161 free(lp->gl_arg);
162 free(lp->gl_line);
163 free(lp);
166 static void
167 free_linelist(grub_line_t *line)
169 grub_line_t *lp;
171 if (line == NULL)
172 return;
174 while (line) {
175 lp = line;
176 line = lp->gl_next;
177 free_line(lp);
181 static void
182 free_entries(grub_menu_t *mp)
184 grub_entry_t *ent, *tmp;
186 if (mp == NULL)
187 return;
189 for (ent = mp->gm_ent_start; (tmp = ent) != NULL;
190 ent = tmp->ge_next, free(tmp))
193 mp->gm_ent_start = NULL;
194 mp->gm_ent_end = NULL;
197 static int
198 grub_menu_append_line(grub_menu_t *mp, const char *line)
200 int rc;
201 size_t n;
202 grub_line_t *lp;
204 if (line == NULL)
205 return (EINVAL);
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);
215 /* skip initial white space */
216 line += strspn(line, " \t");
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);
234 line += n;
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);
243 line += n;
245 /* get arguments */
246 if ((lp->gl_arg = strdup(line)) == NULL)
247 rc = errno;
250 if (rc != 0) {
251 free_line(lp);
252 return (rc);
255 append_line(mp, lp);
256 process_line(mp);
257 return (0);
260 static int
261 grub_menu_process(grub_menu_t *mp)
263 int ret;
264 grub_entry_t *ent;
265 grub_line_t *line, *start;
267 /* Free remaininig entries, if any */
268 free_entries(mp);
271 * Walk through lines, till first 'title' command is encountered.
272 * Initialize globals.
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;
283 * Walk through remaining lines and recreate menu entries.
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;
295 /* Add last entry */
296 if (start != NULL && (ret = add_entry(mp, start, mp->gm_end)) != 0)
297 return (ret);
299 for (ent = mp->gm_ent_start; NULL != ent; ent = ent->ge_next)
300 check_entry(ent);
302 default_entry(mp);
304 return (0);
307 static int
308 grub_fs_init(grub_fs_t *fs)
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);
317 return (0);
320 static void
321 grub_fs_fini(grub_fs_t *fs)
323 if (fs == NULL)
324 return;
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));
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.
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().
345 grub_menu_init(const char *path, grub_menu_t **menup)
347 FILE *fp;
348 char *cp;
349 grub_menu_t *mp;
350 int len, n, ret;
351 char buf[GRBM_MAXLINE];
353 if (menup == NULL)
354 return (EINVAL);
357 * Allocate space, perform initialization
359 if ((mp = calloc(1, sizeof (*mp))) == NULL) {
360 *menup = mp;
361 return (errno);
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;
368 if (path == NULL) {
370 * Use default grub-menu.
371 * If top dataset is not mounted, mount it now.
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;
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));
385 if ((fp = fopen(mp->gm_path, "r")) == NULL) {
386 ret = errno;
387 goto err_out1;
390 cp = buf;
391 len = sizeof (buf);
393 while (fgets(cp, len, fp) != NULL) {
395 if (IS_LINE2BIG(cp, len, n)) {
396 ret = E2BIG;
397 break;
400 /* remove white space at the end of line */
401 for (; n != 0 && isspace(cp[n - 1]); --n)
403 cp[n] = '\0';
405 if (n > 0 && cp[n - 1] == '\\') {
406 len -= n - 1;
407 assert(len >= 2);
408 cp += n - 1;
409 continue;
411 if ((ret = grub_menu_append_line(mp, buf)) != 0)
412 break;
414 cp = buf;
415 len = sizeof (buf);
418 if (fclose(fp) == EOF)
419 ret = errno;
420 else if (ret == 0)
421 ret = grub_menu_process(mp);
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;
429 *menup = mp;
430 return (ret);
433 void
434 grub_menu_fini(grub_menu_t *mp)
436 if (mp == NULL)
437 return;
439 grub_fs_fini(&mp->gm_fs);
440 free_entries(mp);
441 free_linelist(mp->gm_start);
442 free(mp);
445 grub_line_t *
446 grub_menu_next_line(const grub_menu_t *mp, const grub_line_t *lp)
448 assert(mp);
449 if (lp == NULL)
450 return (mp->gm_start);
451 else
452 return (lp->gl_next);
455 grub_line_t *
456 grub_menu_prev_line(const grub_menu_t *mp, const grub_line_t *lp)
458 assert(mp);
459 if (lp == NULL)
460 return (mp->gm_end);
461 else
462 return (lp->gl_prev);
465 grub_line_t *
466 grub_menu_get_line(const grub_menu_t *mp, int num)
468 grub_line_t *lp;
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)
476 return (lp);
479 size_t
480 grub_menu_get_cmdline(const grub_menu_t *mp, int num, char *cmdl, size_t size)
482 grub_entry_t *ent;
484 assert(mp);
485 if ((ent = grub_menu_get_entry(mp, num)) == NULL)
486 return (size_t)(-1);
488 return (grub_entry_get_cmdline(ent, cmdl, size));
491 grub_entry_t *
492 grub_menu_next_entry(const grub_menu_t *mp, const grub_entry_t *ent)
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);
503 grub_entry_t *
504 grub_menu_prev_entry(const grub_menu_t *mp, const grub_entry_t *ent)
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);
515 grub_entry_t *
516 grub_menu_get_entry(const grub_menu_t *mp, int num)
518 grub_entry_t *ent;
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)
531 return (ent);