:: limine / common / lib / config.c 22.2 KB raw

1
#include <stddef.h>
2
#include <stdbool.h>
3
#include <lib/acpi.h>
4
#include <lib/config.h>
5
#include <lib/libc.h>
6
#include <lib/misc.h>
7
#include <lib/getchar.h>
8
#include <mm/pmm.h>
9
#include <fs/file.h>
10
#include <lib/print.h>
11
#include <pxe/tftp.h>
12
#include <crypt/blake2b.h>
13
#include <lib/tpm.h>
14
#include <sys/cpu.h>
15
16
#define CONFIG_B2SUM_SIGNATURE "++CONFIG_B2SUM_SIGNATURE++"
17
#define CONFIG_B2SUM_EMPTY "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
18
19
const char *config_b2sum = CONFIG_B2SUM_SIGNATURE CONFIG_B2SUM_EMPTY;
20
21
static char *config_get_entry_name(size_t index);
22
static char *config_get_entry(size_t *size, size_t index);
23
24
#define SEPARATOR '\n'
25
26
bool config_ready = false;
27
no_unwind bool bad_config = false;
28
29
static char *config_addr;
30
31
#if defined (UEFI)
32
// Snapshot of the on-disk config bytes, kept across the in-place mutations
33
// in init_config so the menu can measure them once measured_boot is known.
34
static char *config_raw_addr;
35
static size_t config_raw_size;
36
37
const char *config_get_raw(size_t *size_out) {
38
    *size_out = config_raw_size;
39
    return config_raw_addr;
40
}
41
#endif
42
43
#if defined (UEFI)
44
45
#define EFI_APP_PATH_LEN 128
46
static char efi_app_path[128] = {0};
47
48
static bool init_efi_app_path(size_t *len_out) {
49
    EFI_STATUS status;
50
    EFI_LOADED_IMAGE_PROTOCOL *loaded_image;
51
    EFI_DEVICE_PATH_PROTOCOL *path;
52
    CHAR16 *file_path, *p, *last_slash;
53
54
    EFI_GUID loaded_image_protocol_guid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
55
56
    status = gBS->HandleProtocol(efi_image_handle, &loaded_image_protocol_guid,
57
                                 (void **)&loaded_image);
58
    if (status != 0) {
59
        return false;
60
    }
61
62
    path = loaded_image->FilePath;
63
64
    while (!(path->Type == END_DEVICE_PATH_TYPE && path->SubType == END_ENTIRE_DEVICE_PATH_SUBTYPE)) {
65
        if (path->Type == MEDIA_DEVICE_PATH && path->SubType == MEDIA_FILEPATH_DP) {
66
            goto found;
67
        }
68
69
        uint16_t node_length = *((uint16_t *)&path->Length[0]);
70
        if (node_length < 4) {
71
            return false;
72
        }
73
        path = (void *)path + node_length;
74
    }
75
76
    return false;
77
78
found:
79
    file_path = (CHAR16 *)((void *)path + 4);
80
81
    last_slash = NULL;
82
    for (p = file_path; *p; p++) {
83
        if (*p == L'\\') {
84
            last_slash = p;
85
        }
86
    }
87
88
    if (last_slash) {
89
        size_t len = (last_slash - file_path) + 1;
90
        if (len >= EFI_APP_PATH_LEN) {
91
            len = EFI_APP_PATH_LEN - 1;
92
        }
93
94
        for (size_t i = 0; i < len; i++) {
95
            efi_app_path[i] = (char)(file_path[i] & 0xff);
96
            if (efi_app_path[i] == '\\') {
97
                efi_app_path[i] = '/';
98
            }
99
        }
100
        efi_app_path[len] = 0;
101
        if (len_out != NULL) {
102
            *len_out = len;
103
        }
104
    } else {
105
        efi_app_path[0] = '/';
106
        efi_app_path[1] = 0;
107
        if (len_out != NULL) {
108
            *len_out = 1;
109
        }
110
    }
111
112
    return true;
113
}
114
#endif
115
116
int init_config_disk(struct volume *part) {
117
#if defined (UEFI)
118
    bool use_default_efi_search_path = false;
119
120
    size_t len;
121
    if (!init_efi_app_path(&len)) {
122
        use_default_efi_search_path = true;
123
    } else {
124
        if (len + sizeof("limine.conf") >= EFI_APP_PATH_LEN) {
125
            use_default_efi_search_path = true;
126
        } else {
127
            strcpy(efi_app_path + len, "limine.conf");
128
        }
129
    }
130
#endif
131
132
    struct file_handle *f;
133
134
    bool old_cif = case_insensitive_fopen;
135
    case_insensitive_fopen = true;
136
    if (
137
     false
138
#if defined (UEFI)
139
     || (f = fopen(part, use_default_efi_search_path ? "/EFI/BOOT/limine.conf" : efi_app_path)) != NULL
140
#endif
141
     || (f = fopen(part, "/boot/limine/limine.conf")) != NULL
142
     || (f = fopen(part, "/boot/limine.conf")) != NULL
143
     || (f = fopen(part, "/limine/limine.conf")) != NULL
144
     || (f = fopen(part, "/limine.conf")) != NULL
145
    ) {
146
        goto opened;
147
    }
148
149
    case_insensitive_fopen = old_cif;
150
    return -1;
151
152
opened:
153
    case_insensitive_fopen = old_cif;
154
155
    if (f->size > SIZE_MAX - 2) {
156
        panic(false, "Config file too large");
157
    }
158
    size_t config_size = f->size + 2;
159
    config_addr = ext_mem_alloc(config_size);
160
161
    fread(f, config_addr, 0, f->size);
162
163
    fclose(f);
164
165
    return init_config(config_size);
166
}
167
168
struct smbios_struct_header {
169
    uint8_t type;
170
    uint8_t length;
171
    uint16_t handle;
172
} __attribute__((packed));
173
174
static size_t smbios_struct_size(struct smbios_struct_header *hdr, size_t remaining) {
175
    // Validate minimum structure header size
176
    if (remaining < sizeof(struct smbios_struct_header)) {
177
        return 0;
178
    }
179
    if (hdr->length < sizeof(struct smbios_struct_header)) {
180
        return 0;  // Invalid structure
181
    }
182
    if (hdr->length > remaining) {
183
        return 0;  // Structure header claims more than remaining
184
    }
185
186
    const char *string_data = (void *)((uintptr_t)hdr + hdr->length);
187
    size_t string_area_max = remaining - hdr->length;
188
    size_t i = 1;
189
    for (; i < string_area_max && (string_data[i - 1] != '\0' || string_data[i] != '\0'); i++);
190
    if (i >= string_area_max) {
191
        return 0;  // Unterminated string area
192
    }
193
    return hdr->length + i + 1;
194
}
195
196
bool init_config_smbios(void) {
197
    struct smbios_entry_point_32 *smbios_entry_32 = NULL;
198
    struct smbios_entry_point_64 *smbios_entry_64 = NULL;
199
    acpi_get_smbios((void **)&smbios_entry_32, (void **)&smbios_entry_64);
200
    if (smbios_entry_32 == NULL && smbios_entry_64 == NULL) {
201
        return false;
202
    }
203
204
    struct smbios_struct_header *hdr = NULL;
205
    size_t struct_count = 0;
206
    size_t table_length = 0;
207
208
    if (smbios_entry_64) {
209
        hdr = (void *)(uintptr_t) smbios_entry_64->table_address;
210
        table_length = smbios_entry_64->table_maximum_size;
211
    } else {
212
        hdr = (void *)(uintptr_t) smbios_entry_32->table_address;
213
        struct_count = smbios_entry_32->number_of_structures;
214
        table_length = smbios_entry_32->table_length;
215
    }
216
217
    if (hdr == NULL || table_length == 0) {
218
        return false;
219
    }
220
221
    size_t structure_bytes_processed = 0;
222
    for (size_t struct_num = 0; hdr && (!struct_count || struct_num < struct_count); struct_num++) {
223
        size_t remaining = table_length - structure_bytes_processed;
224
        if (remaining < sizeof(struct smbios_struct_header)) {
225
            return false;
226
        }
227
228
        if (hdr->type == 127)
229
            return false;
230
231
        size_t struct_size = smbios_struct_size(hdr, remaining);
232
        if (struct_size == 0) {
233
            return false;  // Invalid structure
234
        }
235
236
        if (hdr->type == 11 && hdr->length >= sizeof(struct smbios_struct_header)) {
237
            const char *string_data = (void *)((uintptr_t) hdr + hdr->length);
238
            size_t string_area_size = struct_size - hdr->length;
239
240
            size_t prefix_len = sizeof("limine:config:") - 1;
241
            if (string_area_size > prefix_len && !strncmp(string_data, "limine:config:", prefix_len)) {
242
                size_t total_len = strnlen(string_data, string_area_size);
243
                if (total_len <= prefix_len)
244
                    continue;
245
                size_t config_size = total_len - prefix_len + 2;
246
                config_addr = ext_mem_alloc(config_size);
247
                memcpy(config_addr, &string_data[prefix_len], config_size - 1);
248
                config_addr[config_size - 1] = '\0';
249
                return !init_config(config_size);
250
            }
251
        }
252
253
        structure_bytes_processed += struct_size;
254
        if (structure_bytes_processed >= table_length) {
255
            return false;
256
        }
257
258
        hdr = (void *)((uintptr_t) hdr + struct_size);
259
    }
260
261
    return false;
262
}
263
264
#define NOT_CHILD      (-1)
265
#define DIRECT_CHILD   0
266
#define INDIRECT_CHILD 1
267
268
static int is_child(size_t current_depth, size_t index) {
269
    char *buf = config_get_entry_name(index);
270
    if (buf == NULL)
271
        return NOT_CHILD;
272
    int ret;
273
    if (strlen(buf) < current_depth + 1) {
274
        ret = NOT_CHILD;
275
    } else {
276
        ret = DIRECT_CHILD;
277
        for (size_t j = 0; j < current_depth; j++) {
278
            if (buf[j] != '/') {
279
                ret = NOT_CHILD;
280
                break;
281
            }
282
        }
283
        if (ret == DIRECT_CHILD && buf[current_depth] == '/')
284
            ret = INDIRECT_CHILD;
285
    }
286
    pmm_free(buf, strlen(buf) + 1);
287
    return ret;
288
}
289
290
static bool is_directory(size_t current_depth, size_t index) {
291
    switch (is_child(current_depth + 1, index + 1)) {
292
        default:
293
        case NOT_CHILD:
294
            return false;
295
        case INDIRECT_CHILD:
296
            bad_config = true;
297
            panic(true, "config: Malformed config file. Parentless child.");
298
        case DIRECT_CHILD:
299
            return true;
300
    }
301
}
302
303
#define MAX_MENU_NESTING 64
304
305
static struct menu_entry *create_menu_tree(struct menu_entry *parent,
306
                                           size_t current_depth, size_t index) {
307
    if (current_depth > MAX_MENU_NESTING) {
308
        bad_config = true;
309
        panic(true, "config: Menu nesting too deep (max %u)", MAX_MENU_NESTING);
310
    }
311
312
    struct menu_entry *root = NULL, *prev = NULL;
313
314
    for (size_t i = index; ; i++) {
315
        switch (is_child(current_depth, i)) {
316
            case NOT_CHILD:
317
                return root;
318
            case INDIRECT_CHILD:
319
                continue;
320
            case DIRECT_CHILD:
321
                break;
322
        }
323
324
        struct menu_entry *entry = ext_mem_alloc(sizeof(struct menu_entry));
325
326
        if (root == NULL)
327
            root = entry;
328
329
        char *name = config_get_entry_name(i);
330
331
        bool default_expanded = name[current_depth] == '+';
332
333
        char *n = &name[current_depth + default_expanded];
334
        while (*n == ' ') {
335
            n++;
336
        }
337
338
        entry->name = strdup(n);
339
        pmm_free(name, strlen(name) + 1);
340
        entry->parent = parent;
341
342
        size_t entry_size;
343
        char *config_entry = config_get_entry(&entry_size, i);
344
        entry->body = ext_mem_alloc(entry_size + 1);
345
        memcpy(entry->body, config_entry, entry_size);
346
        entry->body[entry_size] = 0;
347
348
        if (is_directory(current_depth, i)) {
349
            entry->sub = create_menu_tree(entry, current_depth + 1, i + 1);
350
            entry->expanded = default_expanded;
351
        }
352
353
        char *comment = config_get_value(entry->body, 0, "COMMENT");
354
        if (comment != NULL) {
355
            entry->comment = strdup(comment);
356
        }
357
358
        if (prev != NULL)
359
            prev->next = entry;
360
        prev = entry;
361
    }
362
}
363
364
struct menu_entry *menu_tree = NULL;
365
366
struct macro {
367
    char name[1024];
368
    char value[2048];
369
    struct macro *next;
370
};
371
372
static struct macro *macros = NULL;
373
374
int init_config(size_t config_size) {
375
    config_b2sum += sizeof(CONFIG_B2SUM_SIGNATURE) - 1;
376
377
    if (memcmp((void *)config_b2sum, CONFIG_B2SUM_EMPTY, 128) == 0) {
378
        secure_boot_active = false;
379
    } else {
380
        editor_enabled = false;
381
382
        uint8_t out_buf[BLAKE2B_OUT_BYTES];
383
        blake2b(out_buf, config_addr, config_size - 2);
384
        uint8_t hash_buf[BLAKE2B_OUT_BYTES];
385
386
        for (size_t i = 0; i < BLAKE2B_OUT_BYTES; i++) {
387
            int hi = digit_to_int(config_b2sum[i * 2]);
388
            int lo = digit_to_int(config_b2sum[i * 2 + 1]);
389
            if (hi == -1 || lo == -1) {
390
                panic(false, "!!! INVALID CHARACTER IN CONFIG CHECKSUM !!!");
391
            }
392
            hash_buf[i] = hi << 4 | lo;
393
        }
394
395
        if (memcmp(hash_buf, out_buf, BLAKE2B_OUT_BYTES) != 0) {
396
            panic(false, "!!! CHECKSUM MISMATCH FOR CONFIG FILE !!!");
397
        }
398
    }
399
400
#if defined (UEFI)
401
    // Snapshot the raw bytes; the menu measures them once measured_boot
402
    // is final.
403
    config_raw_size = config_size - 2;
404
    config_raw_addr = ext_mem_alloc(config_raw_size);
405
    memcpy(config_raw_addr, config_addr, config_raw_size);
406
#endif
407
408
    // add trailing newline if not present
409
    config_addr[config_size - 2] = '\n';
410
411
    size_t config_alloc_size = config_size;
412
413
    // remove windows carriage returns and spaces at the start and end of lines, if any
414
    for (size_t i = 0; i < config_size; i++) {
415
        size_t skip = 0;
416
        if (config_addr[i] == ' ' || config_addr[i] == '\t') {
417
            while (i + skip < config_size && (config_addr[i + skip] == ' ' || config_addr[i + skip] == '\t')) {
418
                skip++;
419
            }
420
            if (i + skip < config_size && config_addr[i + skip] == '\n') {
421
                goto skip_loop;
422
            }
423
            skip = 0;
424
        }
425
        while (i + skip < config_size
426
            && ((config_addr[i + skip] == '\r')
427
                || ((!i || config_addr[i - 1] == '\n') && (config_addr[i + skip] == ' ' || config_addr[i + skip] == '\t')))
428
        ) {
429
            skip++;
430
        }
431
skip_loop:
432
        if (skip) {
433
            for (size_t j = i; j < config_size - skip; j++)
434
                config_addr[j] = config_addr[j + skip];
435
            config_size -= skip;
436
            i--; // re-examine character shifted into position i
437
        }
438
    }
439
440
    // Load macros
441
    struct macro *arch_macro = ext_mem_alloc(sizeof(struct macro));
442
    strcpy(arch_macro->name, "ARCH");
443
    strcpy(arch_macro->value, current_arch());
444
    arch_macro->next = macros;
445
    macros = arch_macro;
446
447
    struct macro *fw_type_macro = ext_mem_alloc(sizeof(struct macro));
448
    strcpy(fw_type_macro->name, "FW_TYPE");
449
    strcpy(fw_type_macro->value, current_firmware());
450
    fw_type_macro->next = macros;
451
    macros = fw_type_macro;
452
453
    for (size_t i = 0; i < config_size;) {
454
        if ((config_size - i >= 3 && memcmp(config_addr + i, "\n${", 3) == 0)
455
         || (config_size - i >= 2 && i == 0 && memcmp(config_addr, "${", 2) == 0)) {
456
            struct macro *macro = ext_mem_alloc(sizeof(struct macro));
457
458
            i += i ? 3 : 2;
459
            size_t j;
460
            for (j = 0; config_addr[i] != '}' && config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) {
461
                if (j >= sizeof(macro->name) - 1) {
462
                    bad_config = true;
463
                    panic(true, "config: Macro name too long (max %U)", (uint64_t)(sizeof(macro->name) - 1));
464
                }
465
                macro->name[j] = config_addr[i];
466
            }
467
468
            if (config_addr[i] == '\n' || config_addr[i] == 0 || config_addr[i+1] != '=') {
469
                pmm_free(macro, sizeof(struct macro));
470
                continue;
471
            }
472
            i += 2;
473
474
            macro->name[j] = 0;
475
476
            for (j = 0; config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) {
477
                if (j >= sizeof(macro->value) - 1) {
478
                    bad_config = true;
479
                    panic(true, "config: Macro value too long (max %U)", (uint64_t)(sizeof(macro->value) - 1));
480
                }
481
                macro->value[j] = config_addr[i];
482
            }
483
            macro->value[j] = 0;
484
485
            macro->next = macros;
486
            macros = macro;
487
488
            continue;
489
        }
490
491
        i++;
492
    }
493
494
    // Expand macros
495
    if (macros != NULL) {
496
        // Check for overflow before multiplication
497
        if (config_size > SIZE_MAX / 4) {
498
            bad_config = true;
499
            panic(true, "config: Config file too large for macro expansion");
500
        }
501
        size_t new_config_size = config_size * 4;
502
        char *new_config = ext_mem_alloc(new_config_size);
503
504
        size_t i, in;
505
        for (i = 0, in = 0; i < config_size;) {
506
            if ((config_size - i >= 3 && memcmp(config_addr + i, "\n${", 3) == 0)
507
             || (config_size - i >= 2 && i == 0 && memcmp(config_addr, "${", 2) == 0)) {
508
                size_t orig_i = i;
509
                i += i ? 3 : 2;
510
                while (i < config_size && config_addr[i] != '}') {
511
                    i++;
512
                }
513
                if (i >= config_size) {
514
                    bad_config = true;
515
                    panic(true, "config: Malformed macro usage");
516
                }
517
                i++; // skip '}'
518
                if (i >= config_size || config_addr[i++] != '=') {
519
                    i = orig_i;
520
                    goto next;
521
                }
522
                while (config_addr[i] != '\n' && config_addr[i] != 0) {
523
                    i++;
524
                    if (i >= config_size) {
525
                        bad_config = true;
526
                        panic(true, "config: Malformed macro usage");
527
                    }
528
                }
529
                continue;
530
            }
531
532
next:
533
            if (config_size - i >= 2 && memcmp(config_addr + i, "${", 2) == 0) {
534
                char *macro_name = ext_mem_alloc(1024);
535
                i += 2;
536
                size_t j;
537
                for (j = 0; j < 1023 && config_addr[i] != '}' && config_addr[i] != '\n' && config_addr[i] != 0; j++, i++) {
538
                    macro_name[j] = config_addr[i];
539
                }
540
                if (config_addr[i] != '}') {
541
                    bad_config = true;
542
                    panic(true, "config: Malformed macro usage");
543
                }
544
                i++;
545
                macro_name[j] = 0;
546
                char *macro_value = "";
547
                struct macro *macro = macros;
548
                for (;;) {
549
                    if (macro == NULL) {
550
                        break;
551
                    }
552
                    if (strcmp(macro->name, macro_name) == 0) {
553
                        macro_value = macro->value;
554
                        break;
555
                    }
556
                    macro = macro->next;
557
                }
558
                pmm_free(macro_name, 1024);
559
                for (j = 0; macro_value[j] != 0; j++, in++) {
560
                    if (in >= new_config_size) {
561
                        goto overflow;
562
                    }
563
                    new_config[in] = macro_value[j];
564
                }
565
                continue;
566
            }
567
568
            if (in >= new_config_size) {
569
overflow:
570
                bad_config = true;
571
                panic(true, "config: Macro-induced buffer overflow");
572
            }
573
            new_config[in++] = config_addr[i++];
574
        }
575
576
        pmm_free(config_addr, config_alloc_size);
577
578
        config_addr = new_config;
579
        config_size = in;
580
581
        // Free macros
582
        struct macro *macro = macros;
583
        for (;;) {
584
            if (macro == NULL) {
585
                break;
586
            }
587
            struct macro *next = macro->next;
588
            pmm_free(macro, sizeof(struct macro));
589
            macro = next;
590
        }
591
        macros = NULL;
592
    }
593
594
    config_ready = true;
595
596
    menu_tree = create_menu_tree(NULL, 1, 0);
597
598
    size_t s;
599
    char *c = config_get_entry(&s, 0);
600
    if (c != NULL) {
601
        while (*c != '/' && c > config_addr) {
602
            c--;
603
        }
604
        if (*c == '/' && c > config_addr) {
605
            c[-1] = 0;
606
        }
607
    }
608
609
    return 0;
610
}
611
612
static char *config_get_entry_name(size_t index) {
613
    if (!config_ready)
614
        return NULL;
615
616
    char *p = config_addr;
617
618
    for (size_t i = 0; i <= index; i++) {
619
        while (*p != '/') {
620
            if (!*p)
621
                return NULL;
622
            p++;
623
        }
624
        p++;
625
        if ((p - 1) != config_addr && *(p - 2) != '\n')
626
            i--;
627
    }
628
629
    p--;
630
631
    size_t len = 0;
632
    while (p[len] != SEPARATOR) {
633
        len++;
634
    }
635
636
    char *ret = ext_mem_alloc(len + 1);
637
    memcpy(ret, p, len);
638
    ret[len] = 0;
639
    return ret;
640
}
641
642
static char *config_get_entry(size_t *size, size_t index) {
643
    if (!config_ready)
644
        return NULL;
645
646
    char *ret;
647
    char *p = config_addr;
648
649
    for (size_t i = 0; i <= index; i++) {
650
        while (*p != '/') {
651
            if (!*p)
652
                return NULL;
653
            p++;
654
        }
655
        p++;
656
        if ((p - 1) != config_addr && *(p - 2) != '\n')
657
            i--;
658
    }
659
660
    do {
661
        p++;
662
    } while (*p != '\n' && *p != '\0');
663
664
    ret = p;
665
666
cont:
667
    while (*p != '/' && *p)
668
        p++;
669
670
    if (*p && *(p - 1) != '\n') {
671
        p++;
672
        goto cont;
673
    }
674
675
    *size = p - ret;
676
677
    return ret;
678
}
679
680
static const char *lastkey;
681
682
struct conf_tuple config_get_tuple(const char *config, size_t index,
683
                                   const char *key1, const char *key2) {
684
    // Static buffers for return values.
685
    // Callers must copy the result if they need persistence across calls.
686
    #define CONF_TUPLE_BUF_SIZE 4096
687
    static char value1_buf[CONF_TUPLE_BUF_SIZE];
688
    static char value2_buf[CONF_TUPLE_BUF_SIZE];
689
690
    struct conf_tuple conf_tuple;
691
692
    char *tmp = config_get_value(config, index, key1);
693
    if (tmp == NULL) {
694
        return (struct conf_tuple){0};
695
    }
696
    size_t len = strlen(tmp);
697
    if (len >= CONF_TUPLE_BUF_SIZE) {
698
        len = CONF_TUPLE_BUF_SIZE - 1;
699
    }
700
    memcpy(value1_buf, tmp, len);
701
    value1_buf[len] = '\0';
702
    conf_tuple.value1 = value1_buf;
703
704
    const char *lk1 = lastkey;
705
706
    tmp = config_get_value(lk1, 0, key2);
707
    if (tmp != NULL) {
708
        len = strlen(tmp);
709
        if (len >= CONF_TUPLE_BUF_SIZE) {
710
            len = CONF_TUPLE_BUF_SIZE - 1;
711
        }
712
        memcpy(value2_buf, tmp, len);
713
        value2_buf[len] = '\0';
714
        conf_tuple.value2 = value2_buf;
715
    } else {
716
        conf_tuple.value2 = NULL;
717
    }
718
719
    const char *lk2 = lastkey;
720
721
    const char *next_value1 = config_get_value(config, index + 1, key1);
722
723
    const char *lk3 = lastkey;
724
725
    if (conf_tuple.value2 != NULL && next_value1 != NULL) {
726
        if ((uintptr_t)lk2 > (uintptr_t)lk3) {
727
            conf_tuple.value2 = NULL;
728
        }
729
    }
730
731
    return conf_tuple;
732
}
733
734
char *config_get_value(const char *config, size_t index, const char *key) {
735
    // Static buffer for return values.
736
    // Callers must copy the result if they need persistence across calls.
737
    #define CONFIG_VALUE_BUF_SIZE 4096
738
    static char buf[CONFIG_VALUE_BUF_SIZE];
739
740
    if (!key || !config_ready)
741
        return NULL;
742
743
    if (config == NULL)
744
        config = config_addr;
745
746
    size_t key_len = strlen(key);
747
748
    for (size_t i = 0; config[i]; i++) {
749
        if (!strncasecmp(&config[i], key, key_len) && config[i + key_len] == ':') {
750
            if (i && config[i - 1] != SEPARATOR)
751
                continue;
752
            if (index--)
753
                continue;
754
            i += key_len + 1;
755
            while (config[i] == ' ' || config[i] == '\t') {
756
                i++;
757
            }
758
            size_t value_len;
759
            for (value_len = 0;
760
                 config[i + value_len] != SEPARATOR && config[i + value_len];
761
                 value_len++);
762
            if (value_len >= CONFIG_VALUE_BUF_SIZE) {
763
                value_len = CONFIG_VALUE_BUF_SIZE - 1;
764
            }
765
            memcpy(buf, config + i, value_len);
766
            buf[value_len] = '\0';
767
            lastkey = config + i;
768
            return buf;
769
        }
770
    }
771
772
    return NULL;
773
}
tab: 248 wrap: offon