nbos

nbos

https://git.tonybtw.com/nbos.git git://git.tonybtw.com/nbos.git
5,098 bytes raw
1
#include "resolve.h"
2
3
#include <stdint.h>
4
#include <stdalign.h>
5
#include <string.h>
6
7
#include "store.h"
8
9
typedef enum : uint8_t {
10
    UNVISITED = 0,
11
    VISITING,
12
    VISITED,
13
} visit_state;
14
15
typedef struct closure_node closure_node;
16
struct closure_node {
17
    const pkg    *p;
18
    closure_node *next;
19
};
20
21
static bool closure_contains(const closure_node *head, const pkg *p) {
22
    for (const closure_node *n = head; n != nullptr; n = n->next) {
23
        if (n->p == p) return true;
24
    }
25
    return false;
26
}
27
28
static bool closure_collect(
29
        arena         *a,
30
        const pkg     *p,
31
        closure_node **head,
32
        size_t        *count
33
    ) {
34
    if (closure_contains(*head, p)) return true;
35
36
    closure_node *node = arena_alloc(a, sizeof(closure_node), alignof(closure_node));
37
    if (node == nullptr) return false;
38
    node->p    = p;
39
    node->next = *head;
40
    *head      = node;
41
    (*count)++;
42
43
    for (size_t i = 0; i < p->deps.len; i++) {
44
        if (!closure_collect(a, p->deps.data[i], head, count)) return false;
45
    }
46
    return true;
47
}
48
49
static size_t closure_index(
50
    const pkg *const *closure,
51
    size_t            n,
52
    const pkg        *target)
53
{
54
    for (size_t i = 0; i < n; i++) {
55
        if (closure[i] == target) return i;
56
    }
57
    return SIZE_MAX;
58
}
59
60
static resolve_error visit(
61
    arena             *a,
62
    const pkg *const  *closure,
63
    size_t             n_closure,
64
    size_t             idx,
65
    visit_state       *state,
66
    resolved          *out,
67
    size_t            *out_len)
68
{
69
    switch (state[idx]) {
70
        case VISITED:  return RESOLVE_OK_VAL;
71
        case VISITING: return (resolve_error){
72
            .kind = RESOLVE_E_CYCLE,
73
            .pkg_name = closure[idx]->name,
74
        };
75
        case UNVISITED: break;
76
    }
77
78
    state[idx] = VISITING;
79
    const pkg *p = closure[idx];
80
81
    for (size_t i = 0; i < p->deps.len; i++) {
82
        size_t dep_idx = closure_index(closure, n_closure, p->deps.data[i]);
83
        resolve_error err = visit(a, closure, n_closure, dep_idx, state, out, out_len);
84
        if (err.kind != RESOLVE_OK) return err;
85
    }
86
87
    out[*out_len].def        = p;
88
    out[*out_len].store_path = store_path_compute(a, p, out, *out_len);
89
    (*out_len)++;
90
    state[idx] = VISITED;
91
    return RESOLVE_OK_VAL;
92
}
93
94
/**
95
 * resolve() - Compute the build closure and topologically sort it.
96
 * @a: Arena holding the resulting closure, visit-state, and resolved arrays.
97
 * @cfg: System configuration providing the package roots.
98
 * @out: Filled with topologically-sorted &resolved entries on success.
99
 *
100
 * Roots are everything the config references directly: @cfg->pkgs entries,
101
 * @cfg->boot.kernel, and each @cfg->users[i].shell. Transitive deps are
102
 * auto-discovered into the closure. The output is in post-order so
103
 * &realize can iterate it directly.
104
 *
105
 * Return: RESOLVE_OK_VAL on success, a tagged error otherwise.
106
 */
107
resolve_error resolve(
108
    arena              *a,
109
    const system_cfg   *cfg,
110
    resolved_list      *out)
111
{
112
    for (size_t i = 0; i < cfg->pkgs.len; i++) {
113
        for (size_t j = i + 1; j < cfg->pkgs.len; j++) {
114
            if (strcmp(cfg->pkgs.data[i]->name, cfg->pkgs.data[j]->name) == 0) {
115
                return (resolve_error){
116
                    .kind = RESOLVE_E_DUPLICATE_NAME,
117
                    .pkg_name = cfg->pkgs.data[i]->name,
118
                };
119
            }
120
        }
121
    }
122
123
    closure_node *head = nullptr;
124
    size_t n_closure = 0;
125
    for (size_t i = 0; i < cfg->pkgs.len; i++) {
126
        if (!closure_collect(a, cfg->pkgs.data[i], &head, &n_closure)) {
127
            return (resolve_error){ .kind = RESOLVE_E_OOM };
128
        }
129
    }
130
    if (cfg->boot.kernel != nullptr) {
131
        if (!closure_collect(a, cfg->boot.kernel, &head, &n_closure)) {
132
            return (resolve_error){ .kind = RESOLVE_E_OOM };
133
        }
134
    }
135
    for (size_t i = 0; i < cfg->users.len; i++) {
136
        const pkg *shell = cfg->users.data[i].shell;
137
        if (shell != nullptr) {
138
            if (!closure_collect(a, shell, &head, &n_closure)) {
139
                return (resolve_error){ .kind = RESOLVE_E_OOM };
140
            }
141
        }
142
    }
143
144
    const pkg **closure = arena_alloc(a, n_closure * sizeof(const pkg *),
145
                                       alignof(const pkg *));
146
    visit_state *state  = arena_alloc(a, n_closure * sizeof(visit_state),
147
                                       alignof(visit_state));
148
    resolved    *res    = arena_alloc(a, n_closure * sizeof(resolved),
149
                                       alignof(resolved));
150
    if (closure == nullptr || state == nullptr || res == nullptr) {
151
        return (resolve_error){ .kind = RESOLVE_E_OOM };
152
    }
153
154
    {
155
        size_t i = 0;
156
        for (closure_node *n = head; n != nullptr; n = n->next) {
157
            closure[i++] = n->p;
158
        }
159
    }
160
161
    size_t res_len = 0;
162
    for (size_t i = 0; i < n_closure; i++) {
163
        resolve_error err = visit(a, closure, n_closure, i, state, res, &res_len);
164
        if (err.kind != RESOLVE_OK) return err;
165
    }
166
167
    out->data = res;
168
    out->len  = res_len;
169
    return RESOLVE_OK_VAL;
170
}