1 /*-*- Mode: C; c-basic-offset: 2; indent-tabs-mode: t -*-*/
3 /* bls.c - implementation of the boot loader spec */
6 * GRUB -- GRand Unified Bootloader
8 * GRUB is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * GRUB is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
22 #include <grub/list.h>
23 #include <grub/types.h>
24 #include <grub/misc.h>
28 #include <grub/extcmd.h>
29 #include <grub/i18n.h>
32 #include <grub/file.h>
33 #include <grub/normal.h>
34 #include <grub/lib/envblk.h>
38 GRUB_MOD_LICENSE ("GPLv3+");
42 #define GRUB_BLS_CONFIG_PATH "/loader/entries/"
43 #ifdef GRUB_MACHINE_EMU
44 #define GRUB_BOOT_DEVICE "/boot"
46 #define GRUB_BOOT_DEVICE "($root)"
55 static struct bls_entry
*entries
= NULL
;
57 #define FOR_BLS_ENTRIES(var) FOR_LIST_ELEMENTS (var, entries)
59 static int bls_add_keyval(struct bls_entry
*entry
, char *key
, char *val
)
62 struct keyval
**kvs
, *kv
;
63 int new_n
= entry
->nkeyvals
+ 1;
65 kvs
= grub_realloc (entry
->keyvals
, new_n
* sizeof (struct keyval
*));
67 return grub_error (GRUB_ERR_OUT_OF_MEMORY
,
68 "couldn't find space for BLS entry");
71 kv
= grub_malloc (sizeof (struct keyval
));
73 return grub_error (GRUB_ERR_OUT_OF_MEMORY
,
74 "couldn't find space for BLS entry");
76 k
= grub_strdup (key
);
80 return grub_error (GRUB_ERR_OUT_OF_MEMORY
,
81 "couldn't find space for BLS entry");
84 v
= grub_strdup (val
);
89 return grub_error (GRUB_ERR_OUT_OF_MEMORY
,
90 "couldn't find space for BLS entry");
96 entry
->keyvals
[entry
->nkeyvals
] = kv
;
97 grub_dprintf("blscfg", "new keyval at %p:%s:%s\n", entry
->keyvals
[entry
->nkeyvals
], k
, v
);
98 entry
->nkeyvals
= new_n
;
103 /* Find they value of the key named by keyname. If there are allowed to be
104 * more than one, pass a pointer to an int set to -1 the first time, and pass
105 * the same pointer through each time after, and it'll return them in sorted
106 * order as defined in the BLS fragment file */
107 static char *bls_get_val(struct bls_entry
*entry
, const char *keyname
, int *last
)
110 struct keyval
*kv
= NULL
;
115 for (idx
= start
; idx
< entry
->nkeyvals
; idx
++) {
116 kv
= entry
->keyvals
[idx
];
118 if (!grub_strcmp (keyname
, kv
->key
))
122 if (idx
== entry
->nkeyvals
) {
134 #define goto_return(x) ({ ret = (x); goto finish; })
136 /* compare alpha and numeric segments of two versions */
137 /* return 1: a is newer than b */
138 /* 0: a and b are the same version */
139 /* -1: b is newer than a */
140 static int vercmp(const char * a
, const char * b
)
150 grub_dprintf("blscfg", "%s comparing %s and %s\n", __func__
, a
, b
);
151 if (!grub_strcmp(a
, b
))
154 abuf
= grub_malloc(grub_strlen(a
) + 1);
155 bbuf
= grub_malloc(grub_strlen(b
) + 1);
158 grub_strcpy(str1
, a
);
159 grub_strcpy(str2
, b
);
164 /* loop through each version segment of str1 and str2 and compare them */
165 while (*one
|| *two
) {
166 while (*one
&& !grub_isalnum(*one
) && *one
!= '~') one
++;
167 while (*two
&& !grub_isalnum(*two
) && *two
!= '~') two
++;
169 /* handle the tilde separator, it sorts before everything else */
170 if (*one
== '~' || *two
== '~') {
171 if (*one
!= '~') goto_return (1);
172 if (*two
!= '~') goto_return (-1);
178 /* If we ran to the end of either, we are finished with the loop */
179 if (!(*one
&& *two
)) break;
184 /* grab first completely alpha or completely numeric segment */
185 /* leave one and two pointing to the start of the alpha or numeric */
186 /* segment and walk str1 and str2 to end of segment */
187 if (grub_isdigit(*str1
)) {
188 while (*str1
&& grub_isdigit(*str1
)) str1
++;
189 while (*str2
&& grub_isdigit(*str2
)) str2
++;
192 while (*str1
&& grub_isalpha(*str1
)) str1
++;
193 while (*str2
&& grub_isalpha(*str2
)) str2
++;
197 /* save character at the end of the alpha or numeric segment */
198 /* so that they can be restored after the comparison */
204 /* this cannot happen, as we previously tested to make sure that */
205 /* the first string has a non-null segment */
206 if (one
== str1
) goto_return(-1); /* arbitrary */
208 /* take care of the case where the two version segments are */
209 /* different types: one numeric, the other alpha (i.e. empty) */
210 /* numeric segments are always newer than alpha segments */
211 /* XXX See patch #60884 (and details) from bugzilla #50977. */
212 if (two
== str2
) goto_return (isnum
? 1 : -1);
215 grub_size_t onelen
, twolen
;
216 /* this used to be done by converting the digit segments */
217 /* to ints using atoi() - it's changed because long */
218 /* digit segments can overflow an int - this should fix that. */
220 /* throw away any leading zeros - it's a number, right? */
221 while (*one
== '0') one
++;
222 while (*two
== '0') two
++;
224 /* whichever number has more digits wins */
225 onelen
= grub_strlen(one
);
226 twolen
= grub_strlen(two
);
227 if (onelen
> twolen
) goto_return (1);
228 if (twolen
> onelen
) goto_return (-1);
231 /* grub_strcmp will return which one is greater - even if the two */
232 /* segments are alpha or if they are numeric. don't return */
233 /* if they are equal because there might be more segments to */
235 rc
= grub_strcmp(one
, two
);
236 if (rc
) goto_return (rc
< 1 ? -1 : 1);
238 /* restore character that was replaced by null above */
245 /* this catches the case where all numeric and alpha segments have */
246 /* compared identically but the segment sepparating characters were */
248 if ((!*one
) && (!*two
)) goto_return (0);
250 /* whichever version still has characters left over wins */
251 if (!*one
) goto_return (-1); else goto_return (1);
259 /* returns name/version/release */
260 /* NULL string pointer returned if nothing found */
262 split_package_string (char *package_string
, char **name
,
263 char **version
, char **release
)
265 char *package_version
, *package_release
;
268 package_release
= grub_strrchr (package_string
, '-');
270 if (package_release
!= NULL
)
271 *package_release
++ = '\0';
273 *release
= package_release
;
277 *version
= package_string
;
282 package_version
= grub_strrchr(package_string
, '-');
284 if (package_version
!= NULL
)
285 *package_version
++ = '\0';
287 *version
= package_version
;
289 *name
= package_string
;
292 /* Bubble up non-null values from release to name */
293 if (name
!= NULL
&& *name
== NULL
)
295 *name
= (*version
== NULL
? *release
: *version
);
299 if (*version
== NULL
)
307 split_cmp(char *nvr0
, char *nvr1
, int has_name
)
310 char *name0
, *version0
, *release0
;
311 char *name1
, *version1
, *release1
;
313 split_package_string(nvr0
, has_name
? &name0
: NULL
, &version0
, &release0
);
314 split_package_string(nvr1
, has_name
? &name1
: NULL
, &version1
, &release1
);
318 ret
= vercmp(name0
== NULL
? "" : name0
,
319 name1
== NULL
? "" : name1
);
324 ret
= vercmp(version0
== NULL
? "" : version0
,
325 version1
== NULL
? "" : version1
);
329 ret
= vercmp(release0
== NULL
? "" : release0
,
330 release1
== NULL
? "" : release1
);
334 /* return 1: e0 is newer than e1 */
335 /* 0: e0 and e1 are the same version */
336 /* -1: e1 is newer than e0 */
337 static int bls_cmp(const struct bls_entry
*e0
, const struct bls_entry
*e1
)
342 id0
= grub_strdup(e0
->filename
);
343 id1
= grub_strdup(e1
->filename
);
345 r
= split_cmp(id0
, id1
, 1);
353 static void list_add_tail(struct bls_entry
*head
, struct bls_entry
*item
)
357 head
->prev
->next
= item
;
358 item
->prev
= head
->prev
;
362 static int bls_add_entry(struct bls_entry
*entry
)
364 struct bls_entry
*e
, *last
= NULL
;
368 grub_dprintf ("blscfg", "Add entry with id \"%s\"\n", entry
->filename
);
374 rc
= bls_cmp(entry
, e
);
377 return GRUB_ERR_BAD_ARGUMENT
;
380 grub_dprintf ("blscfg", "Add entry with id \"%s\"\n", entry
->filename
);
381 list_add_tail (e
, entry
);
392 grub_dprintf ("blscfg", "Add entry with id \"%s\"\n", entry
->filename
);
400 struct read_entry_info
{
406 static int read_entry (
407 const char *filename
,
408 const struct grub_dirhook_info
*dirhook_info UNUSED
,
411 grub_size_t m
= 0, n
, clip
= 0;
414 grub_file_t f
= NULL
;
415 struct bls_entry
*entry
;
416 struct read_entry_info
*info
= (struct read_entry_info
*)data
;
418 grub_dprintf ("blscfg", "filename: \"%s\"\n", filename
);
420 n
= grub_strlen (filename
);
428 if (filename
[0] == '.')
434 if (grub_strcmp (filename
+ n
- 5, ".conf") != 0)
437 p
= grub_xasprintf ("(%s)%s/%s", info
->devid
, info
->dirname
, filename
);
439 f
= grub_file_open (p
, GRUB_FILE_TYPE_CONFIG
);
444 entry
= grub_zalloc (sizeof (*entry
));
452 if (n
> 5 && !grub_strcmp (filename
+ n
- 5, ".conf") == 0)
455 slash
= grub_strrchr (filename
, '/');
457 slash
= grub_strrchr (filename
, '\\');
459 while (*slash
== '/' || *slash
== '\\')
462 m
= slash
? slash
- filename
: 0;
471 entry
->filename
= grub_strndup(filename
+ m
, n
- clip
);
472 if (!entry
->filename
)
475 entry
->filename
[n
- 5] = '\0';
482 buf
= grub_file_getline (f
);
486 while (buf
&& buf
[0] && (buf
[0] == ' ' || buf
[0] == '\t'))
491 separator
= grub_strchr (buf
, ' ');
494 separator
= grub_strchr (buf
, '\t');
496 if (!separator
|| separator
[1] == '\0')
506 } while (*separator
== ' ' || *separator
== '\t');
508 rc
= bls_add_keyval (entry
, buf
, separator
);
515 bls_add_entry(entry
);
527 static grub_envblk_t saved_env
= NULL
;
530 save_var (const char *name
, const char *value
, void *whitelist UNUSED
)
532 const char *val
= grub_env_get (name
);
533 grub_dprintf("blscfg", "saving \"%s\"\n", name
);
536 grub_envblk_set (saved_env
, name
, value
);
542 unset_var (const char *name
, const char *value UNUSED
, void *whitelist
)
544 grub_dprintf("blscfg", "restoring \"%s\"\n", name
);
547 grub_env_unset (name
);
551 if (test_whitelist_membership (name
,
552 (const grub_env_whitelist_t
*) whitelist
))
553 grub_env_unset (name
);
558 static char **bls_make_list (struct bls_entry
*entry
, const char *key
, int *num
)
566 list
= grub_malloc (sizeof (char *));
575 val
= bls_get_val (entry
, key
, &last
);
579 new = grub_realloc (list
, (nlist
+ 2) * sizeof (char *));
594 static char *field_append(bool is_var
, char *buffer
, char *start
, char *end
)
596 char *temp
= grub_strndup(start
, end
- start
+ 1);
597 const char *field
= temp
;
600 field
= grub_env_get (temp
);
606 buffer
= grub_strdup(field
);
610 buffer
= grub_realloc (buffer
, grub_strlen(buffer
) + grub_strlen(field
));
614 grub_stpcpy (buffer
+ grub_strlen(buffer
), field
);
620 static char *expand_val(char *value
)
633 buffer
= field_append(is_var
, buffer
, start
, end
);
641 if (!grub_isalnum(*value
) && *value
!= '_') {
642 buffer
= field_append(is_var
, buffer
, start
, end
);
653 buffer
= field_append(is_var
, buffer
, start
, end
);
661 static char **early_initrd_list (const char *initrd
)
667 while ((separator
= grub_strchr (initrd
, ' ')))
669 list
= grub_realloc (list
, (nlist
+ 2) * sizeof (char *));
673 list
[nlist
++] = grub_strndup(initrd
, separator
- initrd
);
675 initrd
= separator
+ 1;
678 list
= grub_realloc (list
, (nlist
+ 2) * sizeof (char *));
682 list
[nlist
++] = grub_strndup(initrd
, grub_strlen(initrd
));
688 static void create_entry (struct bls_entry
*entry
)
691 const char **argv
= NULL
;
695 char *options
= NULL
;
696 char **initrds
= NULL
;
698 const char *early_initrd
= NULL
;
699 char **early_initrds
= NULL
;
700 char *initrd_prefix
= NULL
;
701 char *id
= entry
->filename
;
706 char **classes
= NULL
;
715 grub_dprintf("blscfg", "%s got here\n", __func__
);
716 clinux
= bls_get_val (entry
, "linux", NULL
);
719 grub_dprintf ("blscfg", "Skipping file %s with no 'linux' key.\n", entry
->filename
);
723 bootdev
= grub_env_get("ventoy_bls_bootdev");
726 bootdev
= GRUB_BOOT_DEVICE
;
728 bootlen
= grub_strlen(bootdev
) + 2;//space and \0
731 * strip the ".conf" off the end before we make it our "id" field.
735 dotconf
= grub_strstr(dotconf
, ".conf");
736 } while (dotconf
!= NULL
&& dotconf
[5] != '\0');
740 title
= bls_get_val (entry
, "title", NULL
);
741 options
= expand_val (bls_get_val (entry
, "options", NULL
));
744 options
= expand_val ((char *)grub_env_get("default_kernelopts"));
746 initrds
= bls_make_list (entry
, "initrd", NULL
);
748 hotkey
= bls_get_val (entry
, "grub_hotkey", NULL
);
749 users
= expand_val (bls_get_val (entry
, "grub_users", NULL
));
750 classes
= bls_make_list (entry
, "grub_class", NULL
);
751 args
= bls_make_list (entry
, "grub_arg", &argc
);
754 argv
= grub_malloc ((argc
+ 1) * sizeof (char *));
755 argv
[0] = title
? title
: clinux
;
756 for (i
= 1; i
< argc
; i
++)
760 early_initrd
= grub_env_get("early_initrd");
762 grub_dprintf ("blscfg", "adding menu entry for \"%s\" with id \"%s\"\n",
766 early_initrds
= early_initrd_list(early_initrd
);
769 grub_error (GRUB_ERR_OUT_OF_MEMORY
, N_("out of memory"));
773 if (initrds
!= NULL
&& initrds
[0] != NULL
)
775 initrd_prefix
= grub_strrchr (initrds
[0], '/');
776 initrd_prefix
= grub_strndup(initrds
[0], initrd_prefix
- initrds
[0] + 1);
780 initrd_prefix
= grub_strrchr (clinux
, '/');
781 initrd_prefix
= grub_strndup(clinux
, initrd_prefix
- clinux
+ 1);
786 grub_error (GRUB_ERR_OUT_OF_MEMORY
, N_("out of memory"));
791 if (early_initrds
|| initrds
)
793 int initrd_size
= sizeof ("initrd");
796 for (i
= 0; early_initrds
!= NULL
&& early_initrds
[i
] != NULL
; i
++)
797 initrd_size
+= bootlen \
798 + grub_strlen(initrd_prefix
) \
799 + grub_strlen (early_initrds
[i
]) + 1;
801 for (i
= 0; initrds
!= NULL
&& initrds
[i
] != NULL
; i
++)
802 initrd_size
+= bootlen \
803 + grub_strlen (initrds
[i
]) + 1;
806 initrd
= grub_malloc (initrd_size
);
809 grub_error (GRUB_ERR_OUT_OF_MEMORY
, N_("out of memory"));
814 tmp
= grub_stpcpy(initrd
, "initrd");
815 for (i
= 0; early_initrds
!= NULL
&& early_initrds
[i
] != NULL
; i
++)
817 grub_dprintf ("blscfg", "adding early initrd %s\n", early_initrds
[i
]);
818 tmp
= grub_stpcpy (tmp
, " ");
819 tmp
= grub_stpcpy (tmp
, bootdev
);
820 tmp
= grub_stpcpy (tmp
, initrd_prefix
);
821 tmp
= grub_stpcpy (tmp
, early_initrds
[i
]);
822 grub_free(early_initrds
[i
]);
825 for (i
= 0; initrds
!= NULL
&& initrds
[i
] != NULL
; i
++)
827 grub_dprintf ("blscfg", "adding initrd %s\n", initrds
[i
]);
828 tmp
= grub_stpcpy (tmp
, " ");
829 tmp
= grub_stpcpy (tmp
, bootdev
);
830 tmp
= grub_stpcpy (tmp
, initrds
[i
]);
832 tmp
= grub_stpcpy (tmp
, "\n");
835 src
= grub_xasprintf ("load_video\n"
836 "set gfxpayload=keep\n"
840 bootdev
, clinux
, options
? " " : "", options
? options
: "",
841 initrd
? initrd
: "");
843 grub_normal_add_menu_entry (argc
, argv
, classes
, id
, users
, hotkey
, NULL
, src
, 0, &index
, entry
);
844 grub_dprintf ("blscfg", "Added entry %d id:\"%s\"\n", index
, id
);
848 grub_free (initrd_prefix
);
849 grub_free (early_initrds
);
858 struct find_entry_info
{
866 * info: the filesystem object the file is on.
868 static int find_entry (struct find_entry_info
*info
)
870 struct read_entry_info read_entry_info
;
871 grub_fs_t blsdir_fs
= NULL
;
872 grub_device_t blsdir_dev
= NULL
;
873 const char *blsdir
= info
->dirname
;
878 blsdir
= grub_env_get ("blsdir");
880 blsdir
= GRUB_BLS_CONFIG_PATH
;
883 read_entry_info
.file
= NULL
;
884 read_entry_info
.dirname
= blsdir
;
886 grub_dprintf ("blscfg", "scanning blsdir: %s\n", blsdir
);
888 blsdir_dev
= info
->dev
;
889 blsdir_fs
= info
->fs
;
890 read_entry_info
.devid
= info
->devid
;
893 r
= blsdir_fs
->fs_dir (blsdir_dev
, read_entry_info
.dirname
, read_entry
,
896 grub_dprintf ("blscfg", "read_entry returned error\n");
900 e
= grub_error_pop();
904 if (r
&& !info
->dirname
&& !fallback
) {
905 read_entry_info
.dirname
= "/boot" GRUB_BLS_CONFIG_PATH
;
906 grub_dprintf ("blscfg", "Entries weren't found in %s, fallback to %s\n",
907 blsdir
, read_entry_info
.dirname
);
916 bls_load_entries (const char *path
)
922 const char *devid
= NULL
;
924 struct find_entry_info info
= {
929 struct read_entry_info rei
= {
935 len
= grub_strlen (path
);
936 if (grub_strcmp (path
+ len
- 5, ".conf") == 0) {
937 rei
.file
= grub_file_open (path
, GRUB_FILE_TYPE_CONFIG
);
941 * read_entry() closes the file
943 return read_entry(path
, NULL
, &rei
);
944 } else if (path
[0] == '(') {
947 blsdir
= grub_strchr (path
, ')');
949 return grub_error (GRUB_ERR_BAD_ARGUMENT
, N_("Filepath isn't correct"));
957 #ifdef GRUB_MACHINE_EMU
959 #elif defined(GRUB_MACHINE_EFI)
960 devid
= grub_env_get ("root");
962 devid
= grub_env_get ("boot");
965 devid
= grub_env_get ("root");
969 return grub_error (GRUB_ERR_FILE_NOT_FOUND
,
970 N_("variable `%s' isn't set"), "boot");
973 grub_dprintf ("blscfg", "opening %s\n", devid
);
974 dev
= grub_device_open (devid
);
978 grub_dprintf ("blscfg", "probing fs\n");
979 fs
= grub_fs_probe (dev
);
986 info
.dirname
= blsdir
;
994 grub_device_close (dev
);
1000 is_default_entry(const char *def_entry
, struct bls_entry
*entry
, int idx
)
1008 if (grub_strcmp(def_entry
, entry
->filename
) == 0)
1011 title
= bls_get_val(entry
, "title", NULL
);
1013 if (title
&& grub_strcmp(def_entry
, title
) == 0)
1016 def_idx
= (int)grub_strtol(def_entry
, NULL
, 0);
1017 if (grub_errno
== GRUB_ERR_BAD_NUMBER
) {
1018 grub_errno
= GRUB_ERR_NONE
;
1029 bls_create_entries (bool show_default
, bool show_non_default
, char *entry_id
)
1031 const char *def_entry
= NULL
;
1032 struct bls_entry
*entry
= NULL
;
1035 def_entry
= grub_env_get("default");
1037 grub_dprintf ("blscfg", "%s Creating entries from bls\n", __func__
);
1038 FOR_BLS_ENTRIES(entry
) {
1039 if (entry
->visible
) {
1044 if ((show_default
&& is_default_entry(def_entry
, entry
, idx
)) ||
1045 (show_non_default
&& !is_default_entry(def_entry
, entry
, idx
)) ||
1046 (entry_id
&& grub_strcmp(entry_id
, entry
->filename
) == 0)) {
1047 create_entry(entry
);
1053 return GRUB_ERR_NONE
;
1057 grub_cmd_blscfg (grub_extcmd_context_t ctxt UNUSED
,
1058 int argc
, char **args
)
1062 char *entry_id
= NULL
;
1063 bool show_default
= true;
1064 bool show_non_default
= true;
1067 if (grub_strcmp (args
[0], "default") == 0) {
1068 show_non_default
= false;
1069 } else if (grub_strcmp (args
[0], "non-default") == 0) {
1070 show_default
= false;
1071 } else if (args
[0][0] == '(') {
1075 show_default
= false;
1076 show_non_default
= false;
1080 r
= bls_load_entries(path
);
1084 return bls_create_entries(show_default
, show_non_default
, entry_id
);
1087 static grub_extcmd_t cmd
;
1088 static grub_extcmd_t oldcmd
;
1090 GRUB_MOD_INIT(blscfg
)
1092 grub_dprintf("blscfg", "%s got here\n", __func__
);
1093 cmd
= grub_register_extcmd ("blscfg",
1097 N_("Import Boot Loader Specification snippets."),
1099 oldcmd
= grub_register_extcmd ("bls_import",
1103 N_("Import Boot Loader Specification snippets."),
1107 GRUB_MOD_FINI(blscfg
)
1109 grub_unregister_extcmd (cmd
);
1110 grub_unregister_extcmd (oldcmd
);