:: commit c34c8a5bb4859284869d47e032da5ef26f8cc21a

Mintsuki <mintsuki@protonmail.com> — 2026-03-23 06:49

parents: f0e8264e1a

menu: Use escaped path format for LimineLastBootedEntry EFI variable

diff --git a/common/menu.c b/common/menu.c
index b967d0b0..66103349 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -567,6 +567,65 @@ static inline bool should_skip_entry(struct menu_entry *entry) {
 }
 
 #if defined(UEFI)
+// Count visible (non-skipped) entries in a subtree, respecting expansion state.
+static size_t count_visible_entries(struct menu_entry *entry) {
+    size_t count = 0;
+    while (entry != NULL) {
+        if (should_skip_entry(entry)) {
+            entry = entry->next;
+            continue;
+        }
+        count++;
+        if (entry->sub && entry->expanded) {
+            count += count_visible_entries(entry->sub);
+        }
+        entry = entry->next;
+    }
+    return count;
+}
+
+// Count same-named non-skipped siblings preceding this entry (for #N suffix).
+static size_t get_sibling_dup_index(struct menu_entry *entry) {
+    struct menu_entry *first = entry->parent != NULL ? entry->parent->sub : menu_tree;
+    size_t index = 0;
+    for (struct menu_entry *e = first; e != entry; e = e->next) {
+        if (should_skip_entry(e)) {
+            continue;
+        }
+        if (strcmp(e->name, entry->name) == 0) {
+            index++;
+        }
+    }
+    return index;
+}
+
+// Write a name into buf, escaping \, / and # characters.
+// Returns number of bytes written (not counting NUL terminator).
+static size_t escape_name(const char *name, char *buf, size_t buf_size) {
+    if (buf_size == 0) {
+        return 0;
+    }
+    size_t j = 0;
+    for (size_t i = 0; name[i] != '\0'; i++) {
+        if (name[i] == '\\' || name[i] == '/' || name[i] == '#') {
+            if (j + 2 < buf_size) {
+                buf[j++] = '\\';
+                buf[j++] = name[i];
+            } else {
+                break;
+            }
+        } else {
+            if (j + 1 < buf_size) {
+                buf[j++] = name[i];
+            } else {
+                break;
+            }
+        }
+    }
+    buf[j] = '\0';
+    return j;
+}
+
 static void get_entry_path(struct menu_entry *entry, char *buf, size_t buf_size, size_t *pos) {
     if (entry == NULL || buf_size == 0) {
         return;
@@ -579,61 +638,127 @@ static void get_entry_path(struct menu_entry *entry, char *buf, size_t buf_size,
         }
     }
 
-    size_t name_len = strlen(entry->name);
-    size_t copy_len = name_len;
-    if (*pos + copy_len >= buf_size) {
-        copy_len = buf_size - *pos - 1;
+    size_t remaining = *pos < buf_size ? buf_size - *pos : 0;
+    *pos += escape_name(entry->name, buf + *pos, remaining);
+
+    size_t dup_index = get_sibling_dup_index(entry);
+    if (dup_index > 0 && *pos < buf_size - 1) {
+        buf[(*pos)++] = '#';
+        char digits[16];
+        size_t ndigits = 0;
+        size_t val = dup_index;
+        do {
+            digits[ndigits++] = '0' + (val % 10);
+            val /= 10;
+        } while (val > 0);
+        for (size_t i = ndigits; i > 0 && *pos < buf_size - 1; i--) {
+            buf[(*pos)++] = digits[i - 1];
+        }
     }
-    memcpy(buf + *pos, entry->name, copy_len);
-    *pos += copy_len;
 
     if (*pos < buf_size) {
         buf[*pos] = '\0';
     }
 }
 
-// Find entry by path string. If expand_dirs is true, searches all directories regardless
-// of expansion state and expands parent directories when the entry is found.
-static size_t find_entry_by_path(const char *path, struct menu_entry *current_entry,
-                                 size_t base_index, struct menu_entry **found_entry,
-                                 size_t *found_index, bool expand_dirs) {
-    size_t max_entries = 0;
-    bool found_in_subtree = false;
+// Parse one component from an escaped path string.
+// Writes the unescaped name into name_buf and the duplicate index into *dup_index.
+// Returns a pointer to the remainder of the path (past the separator).
+static const char *parse_path_component(const char *path, char *name_buf, size_t name_buf_size, size_t *dup_index) {
+    *dup_index = 0;
+    if (name_buf_size == 0) {
+        return path;
+    }
+    size_t j = 0;
+    const char *p = path;
+
+    while (*p != '\0' && *p != '/') {
+        if (*p == '\\' && p[1] != '\0') {
+            if (j < name_buf_size - 1) {
+                name_buf[j++] = p[1];
+            }
+            p += 2;
+            continue;
+        }
+        if (*p == '#') {
+            const char *q = p + 1;
+            if (*q >= '0' && *q <= '9') {
+                const char *start = q;
+                while (*q >= '0' && *q <= '9') {
+                    q++;
+                }
+                if (*q == '\0' || *q == '/') {
+                    *dup_index = strtoui(start, NULL, 10);
+                    p = q;
+                    break;
+                }
+            }
+        }
+        if (j < name_buf_size - 1) {
+            name_buf[j++] = *p;
+        }
+        p++;
+    }
+
+    name_buf[j] = '\0';
+
+    if (*p == '/') {
+        p++;
+    }
+    return p;
+}
+
+// Find an entry by its escaped path string. If expand_dirs is true, directories on the
+// path to the target are expanded. Returns true if found, writing the entry and its
+// visible index to *found_entry and *found_index.
+static bool find_entry_by_path(const char *path, struct menu_entry *current_entry,
+                                size_t base_index, struct menu_entry **found_entry,
+                                size_t *found_index, bool expand_dirs) {
+    char comp_name[64];
+    size_t dup_index = 0;
+    const char *rest = parse_path_component(path, comp_name, sizeof(comp_name), &dup_index);
+    bool is_last = (*rest == '\0');
+
+    size_t idx = base_index;
+    size_t same_name_count = 0;
 
     while (current_entry != NULL) {
         if (should_skip_entry(current_entry)) {
             current_entry = current_entry->next;
             continue;
         }
-        if (current_entry->sub == NULL) {
-            // Check if this entry matches the path
-            char entry_path[256];
-            size_t pos = 0;
-            get_entry_path(current_entry, entry_path, sizeof(entry_path), &pos);
-            if (strcmp(entry_path, path) == 0) {
+
+        bool name_matches = (strcmp(current_entry->name, comp_name) == 0);
+
+        if (name_matches && same_name_count == dup_index) {
+            if (is_last && current_entry->sub == NULL) {
                 *found_entry = current_entry;
                 if (found_index != NULL) {
-                    *found_index = base_index + max_entries;
+                    *found_index = idx;
+                }
+                return true;
+            } else if (!is_last && current_entry->sub != NULL) {
+                if (expand_dirs) {
+                    current_entry->expanded = true;
                 }
-                found_in_subtree = true;
+                return find_entry_by_path(rest, current_entry->sub,
+                                          idx + 1, found_entry, found_index, expand_dirs);
             }
         }
-        if (current_entry->sub && (expand_dirs || current_entry->expanded)) {
-            size_t sub_entries = find_entry_by_path(path, current_entry->sub,
-                                                    base_index + max_entries + 1,
-                                                    found_entry, found_index, expand_dirs);
-            if (expand_dirs && *found_entry != NULL && !found_in_subtree) {
-                current_entry->expanded = true;
-            }
-            // Only count sub-entries if directory is expanded.
-            if (current_entry->expanded) {
-                max_entries += sub_entries;
-            }
+
+        if (name_matches) {
+            same_name_count++;
         }
-        max_entries++;
+
+        idx++;
+        if (current_entry->sub && current_entry->expanded) {
+            idx += count_visible_entries(current_entry->sub);
+        }
+
         current_entry = current_entry->next;
     }
-    return max_entries;
+
+    return false;
 }
 #endif
 
tab: 248 wrap: offon