[syslinux] [PATCH] (vesa)menu.c32: Add support for BLS

Gregory Lee Bartholomew gregory.lee.bartholomew at gmail.com
Fri May 24 15:33:02 PDT 2019


Modern distributions are moving toward a common boot scheme called "The Boot 
Loader Specification". This patch enables syslinux's (vesa)menu.c32 modules to 
parse the drop-in files that are defined by this new specification.

Link to The Boot Loader Specification:
https://systemd.io/BOOT_LOADER_SPECIFICATION

Link to demonstration bootdisk image (82MB gzipped):
https://drive.google.com/uc?export=download&id=1e4tQU3oM1kPOVDG3pjRGBpOXTwdeuzRx

Link to bash script used to create above bootdisk image (1.3KB gzipped):
https://drive.google.com/uc?export=download&id=1YzZYBCWFhhMtUJotzR7MLiQL6XTH_19Z

To test the bootdisk image in BIOS mode you can use:
$ qemu-system-x86_64 -machine accel=kvm -m 1024 -drive \
file=bootdisk.img,format=raw

To test the bootdisk image in UEFI mode you can use:
$ cp /usr/share/edk2/ovmf/OVMF_VARS.fd OVMF_VARS.fd
$ qemu-system-x86_64 -machine accel=kvm -m 1024 -drive \
if=pflash,format=raw,unit=0,file=/usr/share/edk2/ovmf/OVMF_CODE.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=OVMF_VARS.fd -drive \
file=bootdisk.img,format=raw

Signed-off-by: Gregory Lee Bartholomew <gregory.lee.bartholomew at gmail.com>

---
 com32/menu/readconfig.c | 340 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 340 insertions(+)

diff --git a/com32/menu/readconfig.c b/com32/menu/readconfig.c
index a433fadb..627596d8 100644
--- a/com32/menu/readconfig.c
+++ b/com32/menu/readconfig.c
@@ -21,11 +21,17 @@
 #include <inttypes.h>
 #include <colortbl.h>
 #include <com32.h>
+#include <dirent.h>
 #include <syslinux/adv.h>
 #include <syslinux/config.h>
 
 #include "menu.h"
 
+/* BLS entry global settings */
+#define BLS_CHUNK       16
+int bls_version_places = 3;
+char *bls_sort_by = "machine-id version title";
+
 /* Empty refstring */
 const char *empty_string;
 
@@ -1060,6 +1066,337 @@ do_include:
     }
 }
 
+/*
+ * https://systemd.io/BOOT_LOADER_SPECIFICATION
+ * #type-1-boot-loader-specification-entries
+ */
+struct blsdata {
+    const char *title;
+    const char *version;
+    char *version0; /* version string padded with zeros */
+    const char *machine_id;
+    const char *linux_;
+    const char *initrd;
+    const char *efi;
+    char *options;
+    const char *devicetree;
+    const char *architecture;
+    char *sort_field; /* used internally for sorting */
+    const char *filename;
+};
+
+static void clear_bls_data(struct blsdata *bd)
+{
+    refstr_put(bd->title);
+    refstr_put(bd->version);
+    free(bd->version0);
+    refstr_put(bd->machine_id);
+    refstr_put(bd->linux_);
+    refstr_put(bd->initrd);
+    refstr_put(bd->efi);
+    free(bd->options);
+    refstr_put(bd->devicetree);
+    refstr_put(bd->architecture);
+    free(bd->sort_field);
+    refstr_put(bd->filename);
+
+    memset(bd, 0, sizeof *bd);
+}
+
+/*
+ * Pads the numeric fields of a version string with zeros.
+ * Used to get kernel versions to sort a little better.
+ */
+static char *padver(const char *version) {
+    int i, j, d, len = strlen(version);
+    char *bwd = NULL, *fwd = NULL, *tmp;
+
+    bwd = malloc(len + 1);
+    if (!bwd)
+        goto nomem;
+
+    d = (version[len-1] >= '0' && version[len-1] <= '9') ?
+        bls_version_places : 0;
+    for (i = len, j = 0; j <= len+1; i--, j++) {
+        if (i < 0 || version[i] == '.' || version[i] == '-') {
+            if (d > 0) {
+                len += d;
+                if ((tmp = realloc(bwd, len))) {
+                    bwd = tmp;
+                } else {
+                    goto nomem;
+                }
+                while (d--) {
+                    bwd[j++] = '0';
+                }
+            }
+            d = bls_version_places;
+        } else if (version[i] >= '0' && version[i] <= '9') {
+            d--;
+        } else if (version[i]) {
+            d = 0;
+        }
+        if (i >= 0) {
+            bwd[j] = version[i];
+        }
+    }
+
+    fwd = malloc(len + 1);
+    if (!fwd)
+        goto nomem;
+
+    tmp = bwd;
+    for (i = len; i >= 0; i--) {
+        fwd[i] = *tmp;
+        tmp++;
+    }
+    free(bwd);
+
+    return fwd;
+
+nomem:
+    dprintf("Out of memory error!\n");
+    free(bwd);
+    free(fwd);
+    return NULL;
+}
+
+static int parse_bls1_file(struct blsdata *bd, const char *filename)
+{
+    FILE *f = NULL;
+    char line[MAX_LINE], *p;
+    char *sort_field;
+
+    dprintf("Opening bls entry: %s ", filename);
+
+    f = fopen(filename, "r");
+    dprintf("%s\n", f ? "ok" : "failed");
+
+    if (!f)
+        return -1;
+
+    refstr_put(bd->filename);
+    bd->filename = refstrdup(filename);
+
+    while (fgets(line, sizeof line, f)) {
+        p = strchr(line, '\r');
+        if (p)
+            *p = '\0';
+        p = strchr(line, '\n');
+        if (p)
+            *p = '\0';
+
+        p = skipspace(line);
+
+        if (looking_at(p, "title")) {
+            refstr_put(bd->title);
+            bd->title = refstrdup(skipspace(p + 5));
+        } else if (looking_at(p, "version")) {
+            refstr_put(bd->version);
+            bd->version = refstrdup(skipspace(p + 7));
+            bd->version0 = padver(bd->version);
+        } else if (looking_at(p, "machine-id")) {
+            refstr_put(bd->machine_id);
+            bd->machine_id = refstrdup(skipspace(p + 10));
+        } else if (looking_at(p, "linux")) {
+            refstr_put(bd->linux_);
+            bd->linux_ = refstrdup(skipspace(p + 5));
+        } else if (looking_at(p, "initrd")) {
+            refstr_put(bd->initrd);
+            bd->initrd = refstrdup(skipspace(p + 6));
+        } else if (looking_at(p, "efi")) {
+            refstr_put(bd->efi);
+            bd->efi = refstrdup(skipspace(p + 3));
+        } else if (looking_at(p, "options")) {
+            /* The "options" keyword can be specified multiple times */
+            int len = bd->options ? strlen(bd->options) : 0;
+            int xlen;
+            p = skipspace(p + 7);
+            xlen = strlen(p);
+            if (len && xlen) {
+                /* Grab one space char from the file */
+                p--;
+                xlen++;
+            }
+            bd->options = realloc(bd->options, len + xlen + 1);
+            memcpy(bd->options + len, p, xlen + 1);
+        } else if (looking_at(p, "devicetree")) {
+            refstr_put(bd->devicetree);
+            bd->devicetree = refstrdup(skipspace(p + 10));
+        } else if (looking_at(p, "architecture")) {
+            refstr_put(bd->architecture);
+            bd->architecture = refstrdup(skipspace(p + 12));
+        }
+    }
+
+    fclose(f);
+
+    p = get_word(bls_sort_by, &sort_field);
+    while (sort_field && *sort_field != '\0') {
+        const char *sf = NULL;
+
+        if (looking_at(sort_field, "title")) {
+            sf = bd->title;
+        } else if (looking_at(sort_field, "version")) {
+            sf = bd->version0;
+        } else if (looking_at(sort_field, "machine-id")) {
+            sf = bd->machine_id;
+        }
+
+        if (sf) {
+            bd->sort_field = realloc(
+                bd->sort_field,
+                strlen(bd->sort_field) + strlen(sf) + 1
+            );
+            strcat(bd->sort_field, sf);
+        }
+        p = get_word(skipspace(p), &sort_field);
+    }
+
+    return (bd->linux_ || bd->efi) ? 0 : -1;
+}
+
+static int compare_blsdata(const void *p_bd1, const void *p_bd2)
+{
+    const struct blsdata *bd1 = *(const struct blsdata **)p_bd1;
+    const struct blsdata *bd2 = *(const struct blsdata **)p_bd2;
+
+    const char *a = NULL, *b = NULL;
+
+    if (bd1->sort_field && bd2->sort_field) {
+        a = bd1->sort_field;
+        b = bd2->sort_field;
+    } else if (bd1->title && bd2->title) {
+        a = bd1->title;
+        b = bd2->title;
+    } else if (bd1->linux_ && bd2->linux_) {
+        a = bd1->linux_;
+        b = bd2->linux_;
+    } else if (bd1->efi && bd2->efi) {
+        a = bd1->efi;
+        b = bd2->efi;
+    } else {
+        /* We should never get here */
+        return 0;
+    }
+
+    return strcmp(a, b);
+}
+
+static int parse_bls1_dir(const char *dirname)
+{
+    DIR *d = NULL;
+    char *filename = NULL;
+    struct dirent *de = NULL;
+    struct blsdata *nbd = NULL, **bdx = NULL;
+    int i, n_bdx = 0, n_bd = 0;
+    int rv = -1, fn_len, dn_len;
+    struct menu *m = current_menu;
+
+    if (!*dirname)
+        return -1;
+
+    dprintf("Opening bls entries directory %s ", dirname);
+
+    d = opendir(dirname);
+    dprintf("%s\n", d ? "ok" : "failed");
+
+    if (!d)
+        return -1;
+
+    dn_len = strlen(dirname);
+
+    /* Process up to 128 files */
+    /* https://wiki.syslinux.org/wiki/
+       index.php?title=Syslinux_1_Changelog#Changes_in_1.37 */
+    while ((de = readdir(d)) != NULL && n_bd < 128) {
+        if (de->d_type != DT_REG)
+            continue;
+
+        fn_len = strlen(de->d_name);
+        if (strcmp(de->d_name+(fn_len-5), ".conf"))
+            continue;
+
+        if (!(filename = malloc(dn_len + 1 + fn_len + 1)))
+            goto nomem;
+
+        strcpy(filename, dirname);
+        strcat(filename, "/");
+        strcat(filename, de->d_name);
+
+        if (n_bd >= n_bdx) {
+            struct blsdata **nbdx;
+
+            nbdx = realloc(bdx, (n_bdx + BLS_CHUNK) * sizeof *bdx);
+            if (!nbdx)
+                goto nomem;
+
+            bdx = nbdx;
+            n_bdx += BLS_CHUNK;
+        }
+
+        nbd = malloc(sizeof(struct blsdata));
+        if (!nbd)
+            goto nomem;
+
+        memset(nbd, 0, sizeof *nbd);
+        if (parse_bls1_file(nbd, filename) == 0) {
+            bdx[n_bd++] = nbd;
+            rv = 0;
+        } else {
+            clear_bls_data(nbd);
+            free(nbd);
+        }
+
+        free(filename);
+    }
+
+    closedir(d);
+
+    qsort(bdx, n_bd, sizeof *bdx, compare_blsdata);
+
+    /* For each of the BLS entries,
+       do essentially the same as the looking_at(p, "label")
+       clause of the parse_config_file() function */
+    for (i = 0; i < n_bd; i++) {
+        record(m, &ld, append);
+
+        char *label = malloc(16);
+        snprintf(label, 16, "BLS%03d", i);
+        ld.label = refstrdup(label);
+        free(label);
+
+        ld.kernel = (bdx[i]->linux_) ? refstrdup(bdx[i]->linux_) : NULL;
+        ld.type = KT_LINUX;
+        ld.passwd = NULL;
+        ld.append = (bdx[i]->options) ? refstrdup(bdx[i]->options) : NULL;
+        ld.initrd = (bdx[i]->initrd) ? refstrdup(bdx[i]->initrd) : NULL;
+        ld.menulabel = (bdx[i]->title) ? refstrdup(bdx[i]->title) : NULL;
+        ld.ipappend = ipappend;
+        ld.menudefault = ld.menuhide = ld.menuseparator =
+            ld.menudisabled = ld.menuindent = 0;
+
+        clear_bls_data(bdx[i]);
+        free(bdx[i]);
+    }
+
+    return rv;
+
+nomem:
+    dprintf("Out of memory error!\n");
+    free(filename);
+    for (i = 0; i < n_bd; i++) {
+        clear_bls_data(bdx[i]);
+        free(bdx[i]);
+    }
+    free(bdx);
+    clear_bls_data(nbd);
+    free(nbd);
+    if (d)
+        closedir(d);
+    return -1;
+}
+
 static int parse_one_config(const char *filename)
 {
     FILE *f;
@@ -1067,6 +1404,9 @@ static int parse_one_config(const char *filename)
     if (!strcmp(filename, "~"))
 	filename = syslinux_config_file();
 
+    if (!strncmp(filename, "bls1:", 5) || !strncmp(filename, "BLS1:", 5))
+        return parse_bls1_dir(filename+5);
+
     dprintf("Opening config file: %s ", filename);
 
     f = fopen(filename, "r");
-- 
2.20.1



More information about the Syslinux mailing list