workspace.c (11843B)
1 #include "workspace.h" 2 3 #include <stdio.h> 4 #include <string.h> 5 6 static int set_err(char* err, size_t cap, const char* msg) { 7 if (err && cap) snprintf(err, cap, "%s", msg); 8 return BUILD_ERR; 9 } 10 11 static int slice_eq_cstr(KitSlice s, const char* c) { 12 size_t n = c ? strlen(c) : 0u; 13 return s.len == n && (n == 0u || memcmp(s.s, c, n) == 0); 14 } 15 16 static int token_valid(KitSlice s, size_t cap) { 17 size_t i; 18 if (!s.s || s.len == 0u || s.len >= cap) return 0; 19 for (i = 0; i < s.len; ++i) { 20 unsigned char c = (unsigned char)s.s[i]; 21 if (c == '\0' || c == '\n' || c == '\r' || c == ' ' || c == '\t' || 22 c == '/' || c == ':' || c == '\\') 23 return 0; 24 } 25 return 1; 26 } 27 28 static int value_valid(KitSlice s, size_t cap) { 29 size_t i; 30 if (!s.s || s.len >= cap) return 0; 31 for (i = 0; i < s.len; ++i) { 32 unsigned char c = (unsigned char)s.s[i]; 33 if (c == '\0' || c == '\n' || c == '\r') return 0; 34 } 35 return 1; 36 } 37 38 static int rel_path_valid(KitSlice s) { 39 size_t i, start = 0; 40 if (!s.s || s.len == 0u || s.len >= BUILD_PATH_MAX) return 0; 41 if (s.s[0] == '/') return 0; 42 for (i = 0; i <= s.len; ++i) { 43 if (i == s.len || s.s[i] == '/') { 44 size_t n = i - start; 45 if (n == 0u) return 0; 46 if (n == 1u && s.s[start] == '.') return 0; 47 if (n == 2u && s.s[start] == '.' && s.s[start + 1u] == '.') return 0; 48 start = i + 1u; 49 } else if (s.s[i] == '\\' || s.s[i] == ':') { 50 return 0; 51 } 52 } 53 return 1; 54 } 55 56 static int next_line(const uint8_t* data, size_t len, size_t* pos, 57 KitSlice* line) { 58 size_t start = *pos; 59 size_t end = start; 60 while (end < len && data[end] != '\n') ++end; 61 if (end == len) return BUILD_ERR; 62 line->data = data + start; 63 line->len = end - start; 64 *pos = end + 1u; 65 return BUILD_OK; 66 } 67 68 static void copy_slice(char* dst, size_t cap, KitSlice s) { 69 (void)cap; 70 if (s.len) memcpy(dst, s.s, s.len); 71 dst[s.len] = '\0'; 72 } 73 74 static int split_word(KitSlice line, KitSlice* key, KitSlice* rest) { 75 size_t i = 0; 76 while (i < line.len && line.s[i] != ' ' && line.s[i] != '\t') ++i; 77 key->s = line.s; 78 key->len = i; 79 while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i; 80 rest->s = line.s + i; 81 rest->len = line.len - i; 82 return key->len != 0u ? BUILD_OK : BUILD_ERR; 83 } 84 85 static int parse_header(KitSlice line, KitSlice* kind, KitSlice* arg) { 86 size_t i; 87 if (line.len < 3u || line.s[0] != '[' || line.s[line.len - 1u] != ']') 88 return BUILD_ERR; 89 line.s++; 90 line.len -= 2u; 91 for (i = 0; i < line.len && line.s[i] != ' ' && line.s[i] != '\t'; ++i) {} 92 kind->s = line.s; 93 kind->len = i; 94 while (i < line.len && (line.s[i] == ' ' || line.s[i] == '\t')) ++i; 95 arg->s = line.s + i; 96 arg->len = line.len - i; 97 return kind->len != 0u ? BUILD_OK : BUILD_ERR; 98 } 99 100 static int line_is_header(KitSlice line) { 101 return line.len >= 2u && line.s[0] == '[' && line.s[line.len - 1u] == ']'; 102 } 103 104 void build_workspace_init(BuildWorkspace* ws) { 105 size_t i; 106 if (!ws) return; 107 memset(ws, 0, sizeof *ws); 108 snprintf(ws->def_name, sizeof ws->def_name, "BUILD.kit"); 109 for (i = 0; i < sizeof ws->profiles / sizeof ws->profiles[0]; ++i) 110 build_config_init(&ws->profiles[i].cfg, ws->profiles[i].entries, 111 sizeof ws->profiles[i].entries / 112 sizeof ws->profiles[i].entries[0]); 113 } 114 115 static int parse_hex_id(KitSlice s, uint8_t out[BUILD_HASH_LEN]) { 116 char hex[BUILD_HEX_LEN]; 117 if (s.len + 1u != sizeof hex) return BUILD_ERR; 118 memcpy(hex, s.s, s.len); 119 hex[s.len] = '\0'; 120 return kit_hex_decode(out, hex, BUILD_HASH_LEN) == KIT_OK ? BUILD_OK 121 : BUILD_ERR; 122 } 123 124 int build_workspace_parse(const uint8_t* data, size_t len, BuildWorkspace* out, 125 char* err, size_t errcap) { 126 size_t pos = 0; 127 KitSlice line; 128 enum { ST_TOP, ST_CONFIG, ST_EXTERNAL } stanza = ST_TOP; 129 BuildWorkspaceConfigProfile* cur_cfg = NULL; 130 BuildWorkspaceExternal* cur_ext = NULL; 131 if (!data || !out) return set_err(err, errcap, "missing workspace"); 132 build_workspace_init(out); 133 if (next_line(data, len, &pos, &line) != BUILD_OK || 134 !kit_slice_eq_cstr(line, "kit-workspace 1")) 135 return set_err(err, errcap, "bad workspace magic/version"); 136 while (pos < len) { 137 KitSlice key, rest, kind, arg; 138 if (next_line(data, len, &pos, &line) != BUILD_OK) 139 return set_err(err, errcap, "unterminated workspace line"); 140 if (line.len == 0u) continue; 141 if (line_is_header(line)) { 142 if (parse_header(line, &kind, &arg) != BUILD_OK) 143 return set_err(err, errcap, "bad workspace stanza"); 144 cur_cfg = NULL; 145 cur_ext = NULL; 146 if (slice_eq_cstr(kind, "config")) { 147 if (!token_valid(arg, BUILD_KEY_MAX) || 148 out->n_profiles >= sizeof out->profiles / sizeof out->profiles[0]) 149 return set_err(err, errcap, "bad config profile"); 150 cur_cfg = &out->profiles[out->n_profiles++]; 151 build_config_init(&cur_cfg->cfg, cur_cfg->entries, 152 sizeof cur_cfg->entries / sizeof cur_cfg->entries[0]); 153 copy_slice(cur_cfg->name, sizeof cur_cfg->name, arg); 154 stanza = ST_CONFIG; 155 } else if (slice_eq_cstr(kind, "external")) { 156 if (!token_valid(arg, BUILD_KEY_MAX) || 157 out->n_externals >= 158 sizeof out->externals / sizeof out->externals[0]) 159 return set_err(err, errcap, "bad external repo"); 160 cur_ext = &out->externals[out->n_externals++]; 161 memset(cur_ext, 0, sizeof *cur_ext); 162 copy_slice(cur_ext->name, sizeof cur_ext->name, arg); 163 stanza = ST_EXTERNAL; 164 } else { 165 return set_err(err, errcap, "unknown workspace stanza"); 166 } 167 continue; 168 } 169 if (split_word(line, &key, &rest) != BUILD_OK) 170 return set_err(err, errcap, "bad workspace line"); 171 if (stanza == ST_TOP) { 172 if (slice_eq_cstr(key, "name")) { 173 if (!token_valid(rest, BUILD_KEY_MAX)) 174 return set_err(err, errcap, "bad workspace name"); 175 copy_slice(out->name, sizeof out->name, rest); 176 } else if (slice_eq_cstr(key, "version")) { 177 if (!value_valid(rest, BUILD_VAL_MAX)) 178 return set_err(err, errcap, "bad workspace version"); 179 copy_slice(out->version, sizeof out->version, rest); 180 } else if (slice_eq_cstr(key, "def-name")) { 181 if (!rel_path_valid(rest) || memchr(rest.s, '/', rest.len)) 182 return set_err(err, errcap, "bad workspace def-name"); 183 copy_slice(out->def_name, sizeof out->def_name, rest); 184 } else { 185 return set_err(err, errcap, "unknown workspace field"); 186 } 187 } else if (stanza == ST_CONFIG) { 188 if (!cur_cfg) return set_err(err, errcap, "internal config state"); 189 if (slice_eq_cstr(key, "inherits")) { 190 if (cur_cfg->inherits[0] || !token_valid(rest, BUILD_KEY_MAX)) 191 return set_err(err, errcap, "bad config inheritance"); 192 copy_slice(cur_cfg->inherits, sizeof cur_cfg->inherits, rest); 193 } else if (build_config_set(&cur_cfg->cfg, key, rest) != BUILD_OK) { 194 return set_err(err, errcap, "bad config entry"); 195 } 196 } else if (stanza == ST_EXTERNAL) { 197 if (!cur_ext) return set_err(err, errcap, "internal external state"); 198 if (slice_eq_cstr(key, "format")) { 199 if (slice_eq_cstr(rest, "tree")) 200 cur_ext->format = BUILD_WS_EXT_TREE; 201 else if (slice_eq_cstr(rest, "kpkg")) 202 cur_ext->format = BUILD_WS_EXT_KPKG; 203 else if (slice_eq_cstr(rest, "tar.gz")) 204 cur_ext->format = BUILD_WS_EXT_TARGZ; 205 else 206 return set_err(err, errcap, "bad external format"); 207 } else if (slice_eq_cstr(key, "archive")) { 208 if (cur_ext->has_archive || 209 parse_hex_id(rest, cur_ext->archive) != BUILD_OK) 210 return set_err(err, errcap, "bad external archive"); 211 cur_ext->has_archive = 1; 212 } else if (slice_eq_cstr(key, "package")) { 213 if (cur_ext->has_package || 214 parse_hex_id(rest, cur_ext->package) != BUILD_OK) 215 return set_err(err, errcap, "bad external package"); 216 cur_ext->has_package = 1; 217 } else if (slice_eq_cstr(key, "url")) { 218 if (!value_valid(rest, BUILD_URL_MAX) || 219 cur_ext->n_urls >= sizeof cur_ext->urls / sizeof cur_ext->urls[0]) 220 return set_err(err, errcap, "bad external url"); 221 copy_slice(cur_ext->urls[cur_ext->n_urls], 222 sizeof cur_ext->urls[cur_ext->n_urls], rest); 223 ++cur_ext->n_urls; 224 } else if (slice_eq_cstr(key, "strip-prefix")) { 225 if (!rel_path_valid(rest)) 226 return set_err(err, errcap, "bad external strip-prefix"); 227 copy_slice(cur_ext->strip_prefix, sizeof cur_ext->strip_prefix, rest); 228 } else { 229 return set_err(err, errcap, "unknown external field"); 230 } 231 } 232 } 233 for (pos = 0; pos < out->n_externals; ++pos) { 234 if (out->externals[pos].format == BUILD_WS_EXT_NONE || 235 !out->externals[pos].has_archive) 236 return set_err(err, errcap, "incomplete external repo"); 237 } 238 return BUILD_OK; 239 } 240 241 const BuildWorkspaceConfigProfile* build_workspace_profile_find( 242 const BuildWorkspace* ws, KitSlice name) { 243 size_t i; 244 if (!ws || !token_valid(name, BUILD_KEY_MAX)) return NULL; 245 for (i = 0; i < ws->n_profiles; ++i) { 246 if (strlen(ws->profiles[i].name) == name.len && 247 memcmp(ws->profiles[i].name, name.s, name.len) == 0) 248 return &ws->profiles[i]; 249 } 250 return NULL; 251 } 252 253 const BuildWorkspaceExternal* build_workspace_external_find( 254 const BuildWorkspace* ws, KitSlice name) { 255 size_t i; 256 if (!ws || !token_valid(name, BUILD_KEY_MAX)) return NULL; 257 for (i = 0; i < ws->n_externals; ++i) { 258 if (strlen(ws->externals[i].name) == name.len && 259 memcmp(ws->externals[i].name, name.s, name.len) == 0) 260 return &ws->externals[i]; 261 } 262 return NULL; 263 } 264 265 static int apply_profile(const BuildWorkspace* ws, 266 const BuildWorkspaceConfigProfile* p, 267 BuildConfig* out, int depth, char* err, 268 size_t errcap) { 269 size_t i; 270 if (!p) return BUILD_OK; 271 if (depth > 16) return set_err(err, errcap, "config inheritance cycle"); 272 if (p->inherits[0]) { 273 const BuildWorkspaceConfigProfile* parent = 274 build_workspace_profile_find(ws, kit_slice_cstr(p->inherits)); 275 if (!parent) return set_err(err, errcap, "missing inherited config"); 276 if (apply_profile(ws, parent, out, depth + 1, err, errcap) != BUILD_OK) 277 return BUILD_ERR; 278 } 279 for (i = 0; i < p->cfg.n; ++i) { 280 if (build_config_set(out, kit_slice_cstr(p->cfg.entries[i].key), 281 kit_slice_cstr(p->cfg.entries[i].value)) != BUILD_OK) 282 return set_err(err, errcap, "workspace config overflow"); 283 } 284 return BUILD_OK; 285 } 286 287 int build_workspace_config_apply(const BuildWorkspace* ws, KitSlice profile, 288 const KitBuildKV* overrides, 289 size_t noverrides, BuildConfig* out, 290 char* err, size_t errcap) { 291 const BuildWorkspaceConfigProfile* def; 292 size_t i; 293 if (!ws || !out) return BUILD_ERR; 294 out->n = 0; 295 def = build_workspace_profile_find(ws, KIT_SLICE_LIT("default")); 296 if (apply_profile(ws, def, out, 0, err, errcap) != BUILD_OK) 297 return BUILD_ERR; 298 if (profile.s && profile.len) { 299 const BuildWorkspaceConfigProfile* p = 300 build_workspace_profile_find(ws, profile); 301 if (!p) return set_err(err, errcap, "unknown workspace config profile"); 302 if (apply_profile(ws, p, out, 0, err, errcap) != BUILD_OK) 303 return BUILD_ERR; 304 } 305 for (i = 0; i < noverrides; ++i) { 306 if (build_config_set(out, overrides[i].key, overrides[i].value) != BUILD_OK) 307 return set_err(err, errcap, "workspace config override overflow"); 308 } 309 return BUILD_OK; 310 }