xref: /illumos-gate/usr/src/lib/libgrubmgmt/common/libgrub_menu.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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