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
append_line(grub_menu_t * mp,grub_line_t * lp)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
process_line(grub_menu_t * mp)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
check_entry(grub_entry_t * ent)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
add_entry(grub_menu_t * mp,grub_line_t * start,grub_line_t * end)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
default_entry(grub_menu_t * mp)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
free_line(grub_line_t * lp)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
free_linelist(grub_line_t * line)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
free_entries(grub_menu_t * mp)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
grub_menu_append_line(grub_menu_t * mp,const char * line)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
grub_menu_process(grub_menu_t * mp)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
grub_fs_init(grub_fs_t * fs)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
grub_fs_fini(grub_fs_t * fs)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
grub_menu_init(const char * path,grub_menu_t ** menup)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 (; n != 0 && isspace(cp[n - 1]); --n)
402 ;
403 cp[n] = '\0';
404
405 if (n > 0 && 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
grub_menu_fini(grub_menu_t * mp)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 *
grub_menu_next_line(const grub_menu_t * mp,const grub_line_t * lp)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 *
grub_menu_prev_line(const grub_menu_t * mp,const grub_line_t * lp)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 *
grub_menu_get_line(const grub_menu_t * mp,int num)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
grub_menu_get_cmdline(const grub_menu_t * mp,int num,char * cmdl,size_t size)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 *
grub_menu_next_entry(const grub_menu_t * mp,const grub_entry_t * ent)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 *
grub_menu_prev_entry(const grub_menu_t * mp,const grub_entry_t * ent)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 *
grub_menu_get_entry(const grub_menu_t * mp,int num)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