From c3df0afeb0babe66b04691c2dd8ae2b9d047e9ab Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 13 Aug 2026 05:37:56 +0900 Subject: [PATCH] build: separate package identity from storage paths --- Makefile | 12 + cmd/w6a/parse.c | 124 ++-- cmd/w6c/main.c | 19 + cmd/w6c/wwi.c | 2 + cmd/wcc/check.c | 3 +- cmd/ww/main.c | 1073 ++++++++++++++++++++++----------- internal/wwpackage/package.ww | 39 +- lib/ww/syntax/parse.ww | 4 +- selfhost/cmd/w6c/main.ww | 30 + selfhost/cmd/wcc/wwi.ww | 3 + selfhost/cmd/ww/main.ww | 363 +++++++---- 11 files changed, 1167 insertions(+), 505 deletions(-) diff --git a/Makefile b/Makefile index 668df6cb..39cc3142 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,10 @@ WWTEST_BIN = $(BIN)/wwtest WWTEST_SRC = cmd/wwtest/wwtest.ww \ internal/wwpackage/package.ww lib/os/exec/exec.ww \ lib/ascii/ascii.ww lib/bytes/bytes.ww \ + lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \ + lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \ + lib/errors/errors.ww lib/hash/hash.ww \ + lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \ lib/encoding/utf8/utf8.ww lib/math/floats.ww lib/math/math.ww \ lib/os/os.ww lib/rt/malloc.ww \ lib/strconv/decimal.ww lib/strconv/ftos.ww lib/strconv/ftos_data.ww \ @@ -99,6 +103,10 @@ PACKAGE_TEST_BIN = $(BIN)/test_package PACKAGE_TEST_SRC = test/package/package_test.ww lib/os/exec/exec.ww \ lib/test/run.ww lib/fnmatch/fnmatch.ww \ lib/ascii/ascii.ww lib/bytes/bytes.ww \ + lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \ + lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \ + lib/errors/errors.ww lib/hash/hash.ww \ + lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \ lib/encoding/utf8/utf8.ww lib/os/os.ww lib/rt/malloc.ww \ lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \ lib/types/types.ww @@ -256,6 +264,10 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \ $(BIN)/ww_ww: selfhost/cmd/ww/main.ww lib/os/exec/exec.ww \ lib/os/os.ww lib/rt/malloc.ww \ lib/time/time.ww lib/types/types.ww \ + lib/crypto/math/math.ww lib/crypto/sha256/sha256.ww \ + lib/endian/big.ww lib/endian/little.ww lib/endian/network.ww \ + lib/errors/errors.ww lib/hash/hash.ww \ + lib/io/empty.ww lib/io/io.ww lib/io/stream.ww lib/io/types.ww \ lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww \ lib/ww/syntax/lex.ww lib/ww/syntax/tok.ww lib/ww/syntax/ast.ww \ lib/ww/syntax/parse.ww lib/ww/syntax/expr.ww \ diff --git a/cmd/w6a/parse.c b/cmd/w6a/parse.c index 9feef271..9a5cb8a7 100644 --- a/cmd/w6a/parse.c +++ b/cmd/w6a/parse.c @@ -50,16 +50,20 @@ a_intern(Asm *a, const char *name) return s; } -/* line iterator: returns the next line as a NUL-terminated buffer in - * line/llen pointers, advances pos. Returns 0 on EOF. - */ static int -nextline(Asm *a, char **line, size_t *llen, char *buf, size_t bufsz) +nextline(Asm *a, char **line, size_t *llen) { if (a->pos >= a->srclen) return 0; - size_t n = 0; - while (a->pos < a->srclen && a->src[a->pos] != '\n' && n + 1 < bufsz) - buf[n++] = a->src[a->pos++]; + u64 start = a->pos; + while (a->pos < a->srclen && a->src[a->pos] != '\n') a->pos++; + size_t n = (size_t)(a->pos - start); + char *buf = malloc(n + 1); + if (buf == NULL) { + err(a, "out of memory"); + a->pos = a->srclen; + return 0; + } + memcpy(buf, a->src + start, n); buf[n] = '\0'; if (a->pos < a->srclen && a->src[a->pos] == '\n') a->pos++; *line = buf; @@ -206,10 +210,13 @@ parse_operand(Asm *a, const char *s, Aoperand *out) } if (a_isidstart((unsigned char)*s)) { - char buf[256] = {0}; - int n = 0; - while (a_isidcont((unsigned char)*s) && n < 255) buf[n++] = *s++; - buf[n] = 0; + const char *start = s; + while (a_isidcont((unsigned char)*s)) s++; + size_t n = (size_t)(s - start); + char *name = malloc(n + 1); + if (name == NULL) { err(a, "out of memory"); return -1; } + memcpy(name, start, n); + name[n] = 0; /* Optional `+disp` between the ident and `(SB)`. Used by * DATAR to address bytes inside an existing .data slot @@ -227,12 +234,16 @@ parse_operand(Asm *a, const char *s, Aoperand *out) int rn = 0; s++; while (*s && *s != ')' && rn < 7) rbuf[rn++] = *s++; - if (*s != ')') { err(a, "missing ')'"); return -1; } + if (*s != ')') { + err(a, "missing ')'"); + free(name); + return -1; + } s++; int r = reg_lookup(rbuf); if (r == D_PSB) { out->type = D_EXTERN; - out->sym = strdup(buf); + out->sym = name; out->offset = sym_disp; return 0; } @@ -241,16 +252,18 @@ parse_operand(Asm *a, const char *s, Aoperand *out) * reg_lookup and silently discarded the ident and * +disp (rule 7). */ err(a, "unsupported name(reg) operand"); + free(name); return -1; } - int r = reg_lookup(buf); + int r = reg_lookup(name); if (r != 0) { out->type = r; + free(name); return 0; } out->type = D_BRANCH; - out->sym = strdup(buf); + out->sym = name; return 0; } @@ -261,17 +274,14 @@ parse_operand(Asm *a, const char *s, Aoperand *out) int a_parse(Asm *a) { - /* Big enough for a DATAW emitting a [256]u32 table (1024 bytes - * → ~4100 chars of `\xNN` escapes plus directive boilerplate). - * Selfhost w6a allocates per-line; this is the cstage equivalent. */ - static char buf[32768]; char *line; size_t len; const char *pending_label = NULL; - while (nextline(a, &line, &len, buf, sizeof buf)) { + while (nextline(a, &line, &len)) { const char *p = skipws(line); if (*p == '\0' || (*p == '/' && p[1] == '/')) { + free(line); a->line++; continue; } @@ -282,6 +292,11 @@ a_parse(Asm *a) if (*q == ':') { size_t nl = q - p; char *name = malloc(nl + 1); + if (name == NULL) { + err(a, "out of memory"); + free(line); + return a->errs; + } memcpy(name, p, nl); name[nl] = '\0'; /* If a label is already pending we'd lose it @@ -297,6 +312,7 @@ a_parse(Asm *a) a->tail = prg; } pending_label = name; + free(line); a->line++; continue; } @@ -311,6 +327,7 @@ a_parse(Asm *a) int op = opcode_lookup(mnem); if (op == 0) { err(a, "unknown opcode"); + free(line); a->line++; continue; } @@ -322,14 +339,20 @@ a_parse(Asm *a) pending_label = NULL; while (*m == ' ' || *m == '\t') m++; - const char *rest = m; + char *rest = (char *)m; if (op == A_TEXT) { - char nbuf[256] = {0}; - int nn = 0; - while (*m && *m != ',' && nn < 255) nbuf[nn++] = *m++; + const char *start = m; + while (*m && *m != ',') m++; + size_t nn = (size_t)(m - start); + char *name = malloc(nn + 1); + if (name == NULL) { + err(a, "out of memory"); free(line); return a->errs; + } + memcpy(name, start, nn); + name[nn] = 0; prg->to.type = D_EXTERN; - prg->to.sym = strdup(nbuf); + prg->to.sym = name; if (*m == ',') { m++; while (*m == ' ' || *m == '$') m++; @@ -339,11 +362,17 @@ a_parse(Asm *a) } else if (op == A_DATA || op == A_DATAW) { /* DATA name(SB),"escaped bytes" — read-only in .text * DATAW name(SB),"escaped bytes" — writable in .data */ - char nbuf[256] = {0}; - int nn = 0; - while (*m && *m != '(' && nn < 255) nbuf[nn++] = *m++; + const char *start = m; + while (*m && *m != '(') m++; + size_t nn = (size_t)(m - start); + char *name = malloc(nn + 1); + if (name == NULL) { + err(a, "out of memory"); free(line); return a->errs; + } + memcpy(name, start, nn); + name[nn] = 0; prg->to.type = D_EXTERN; - prg->to.sym = strdup(nbuf); + prg->to.sym = name; if (*m == '(') { while (*m && *m != ')') m++; if (*m == ')') m++; @@ -357,6 +386,9 @@ a_parse(Asm *a) m++; size_t cap = 32, len = 0; u8 *buf = malloc(cap); + if (buf == NULL) { + err(a, "out of memory"); free(line); return a->errs; + } while (*m && *m != '"') { int c = (unsigned char)*m++; if (c == '\\' && *m) { @@ -381,7 +413,12 @@ a_parse(Asm *a) } if (len + 1 > cap) { cap *= 2; - buf = realloc(buf, cap); + u8 *next = realloc(buf, cap); + if (next == NULL) { + free(buf); err(a, "out of memory"); + free(line); return a->errs; + } + buf = next; } buf[len++] = (u8)c; } @@ -389,27 +426,30 @@ a_parse(Asm *a) prg->nbytes = len; } } else { - const char *comma = NULL; - for (const char *q = rest; *q; q++) + int operand_bad = 0; + char *comma = NULL; + for (char *q = rest; *q; q++) if (*q == ',' && comma == NULL) comma = q; if (comma) { - char op1[256], op2[256]; - size_t l1 = comma - rest; - if (l1 >= sizeof op1) l1 = sizeof op1 - 1; - memcpy(op1, rest, l1); op1[l1] = '\0'; - size_t l2 = strlen(comma + 1); - if (l2 >= sizeof op2) l2 = sizeof op2 - 1; - memcpy(op2, comma + 1, l2); op2[l2] = '\0'; - parse_operand(a, op1, &prg->from); - parse_operand(a, op2, &prg->to); + *comma = '\0'; + if (parse_operand(a, rest, &prg->from) < 0 + || parse_operand(a, comma + 1, &prg->to) < 0) + operand_bad = 1; } else if (*rest) { - parse_operand(a, rest, &prg->to); + if (parse_operand(a, rest, &prg->to) < 0) + operand_bad = 1; + } + if (operand_bad) { + free(line); + a->line++; + continue; } } if (a->head == NULL) a->head = prg; else a->tail->link = prg; a->tail = prg; + free(line); a->line++; } return a->errs; diff --git a/cmd/w6c/main.c b/cmd/w6c/main.c index 321e1fbf..6c50e3ad 100644 --- a/cmd/w6c/main.c +++ b/cmd/w6c/main.c @@ -22,6 +22,18 @@ slurp(const char *path, char **outbuf, u64 *outlen) return 0; } +static int +export_owner_matches(const char *buf, u64 len, const char *path) +{ + static const char prefix[] = "//ww:module "; + size_t pn = strlen(path); + size_t need = sizeof prefix - 1 + pn + 1; + return len >= need + && memcmp(buf, prefix, sizeof prefix - 1) == 0 + && memcmp(buf + sizeof prefix - 1, path, pn) == 0 + && buf[need - 1] == '\n'; +} + struct importin { const char *path; const char *file; @@ -164,6 +176,13 @@ main(int argc, char **argv) imports[i].path, imports[i].file); return 1; } + if (!export_owner_matches(imports[i].buf, imports[i].len, + imports[i].path)) { + fprintf(stderr, + "w6c: import %s: export owner mismatch in %s\n", + imports[i].path, imports[i].file); + return 1; + } int bad = 0; Node *f = parseinput(a, imports[i].file, imports[i].buf, imports[i].len, imports[i].path, testsupport, 0, &bad); diff --git a/cmd/w6c/wwi.c b/cmd/w6c/wwi.c index a89ac4f2..5121e94d 100644 --- a/cmd/w6c/wwi.c +++ b/cmd/w6c/wwi.c @@ -864,6 +864,8 @@ wwi_emit(Checker *c, FILE *of, Node *file) const char *dot = strrchr(file->module, '.'); pkg = dot ? dot + 1 : file->module; } + if (file->module != NULL && file->module[0] != '\0') + fprintf(of, "//ww:module %s\n", file->module); fprintf(of, "package %s;\n", pkg); /* imports — primary N_USE, byte-sorted by import path. */ diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index a18f2272..da7eb396 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -82,9 +82,8 @@ resolve_typename(Checker *c, Node *n) * same name as the module itself (e.g. `random.random`). */ const char *dot = strrchr(nm, '.'); if (dot) { - char head[128] = {0}; size_t hl = (size_t)(dot - nm); - if (hl < sizeof head) memcpy(head, nm, hl); + char *head = astrndup(c->a, nm, hl); Sym *m = scope_lookup(c->cur, head); if (m && (m->kind == SK_USE || m->use_alias)) { /* M1 #22: map the qualifier alias to its dotted diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 73cff253..cf39f98a 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -14,6 +14,12 @@ #include #include #include +#include +#include + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif static const char *usage = "usage: ww [-V] [args...]\n" @@ -33,6 +39,7 @@ static const char *usage = static char *self_dir; static const char *self_path; +static char *sep_sprintf(const char *, ...); static const char * envpath(const char *name) @@ -46,8 +53,9 @@ toolpath(const char *envvar, const char *name) { const char *p = envpath(envvar); if (p) return p; - static char buf[1024]; - snprintf(buf, sizeof buf, "%s/%s", self_dir, name); + static char buf[PATH_MAX]; + int n = snprintf(buf, sizeof buf, "%s/%s", self_dir, name); + if (n < 0 || (size_t)n >= sizeof buf) return NULL; return strdup(buf); } @@ -110,10 +118,15 @@ exec_package_tests(int argc, char **argv, const char *target, const char *resolved, const char *root_identity, int add_dot) { const char *override = getenv("WW_WWTEST"); - char fallback[1024]; + char fallback[PATH_MAX]; const char *prog = override && override[0] ? override : fallback; - if (prog == fallback) - snprintf(fallback, sizeof fallback, "%s/wwtest", self_dir); + if (prog == fallback) { + int pn = snprintf(fallback, sizeof fallback, "%s/wwtest", self_dir); + if (pn < 0 || (size_t)pn >= sizeof fallback) { + fputs("ww test: package coordinator path is too long\n", stderr); + return 1; + } + } char **xargv = calloc((size_t)argc + 8, sizeof *xargv); if (xargv == NULL) { fputs("ww test: cannot allocate package coordinator arguments\n", @@ -175,13 +188,15 @@ import_add(struct ImportSet *s, const char *path) /* `encoding.utf8` → `encoding/utf8`. Mirrors Hare hare(1)'s * use-path → fs-path mapping (ref/hare/hare/module/srcs.ha:78 * builds the same shape via path::push per ident part). */ -static void +static int import_path_form(const char *name, char *out, size_t outsz) { - size_t i; - for (i = 0; i + 1 < outsz && name[i] != '\0'; i++) + size_t n = strlen(name); + if (n + 1 > outsz) return -1; + for (size_t i = 0; i < n; i++) out[i] = (name[i] == '.') ? '/' : name[i]; - out[i] = '\0'; + out[n] = '\0'; + return 0; } static int @@ -200,7 +215,8 @@ locate_import_in(const char *dir, const char *path_form, char *out, size_t outsz) { struct stat st; - snprintf(out, outsz, "%s/%s", dir, path_form); + int n = snprintf(out, outsz, "%s/%s", dir, path_form); + if (n < 0 || (size_t)n >= outsz) return 0; if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) return 1; return 0; @@ -217,12 +233,14 @@ locate_import(const char *dirs, const char *path_form, char *out, while (*p) { const char *e = strchr(p, ':'); size_t n = e ? (size_t)(e - p) : strlen(p); - if (n > 0 && n < outsz) { - char dir[1024]; - if (n >= sizeof dir) n = sizeof dir - 1; + if (n > 0) { + char *dir = malloc(n + 1); + if (dir == NULL) return 0; memcpy(dir, p, n); dir[n] = '\0'; - if (locate_import_in(dir, path_form, out, outsz)) + int found = locate_import_in(dir, path_form, out, outsz); + free(dir); + if (found) return 1; } if (!e) break; @@ -246,12 +264,18 @@ locate_module(const char *dirs, const char *path_form, char *out, while (*p) { const char *e = strchr(p, ':'); size_t n = e ? (size_t)(e - p) : strlen(p); - if (n > 0 && n < outsz) { - char dir[1024]; - if (n >= sizeof dir) n = sizeof dir - 1; + if (n > 0) { + char *dir = malloc(n + 1); + if (dir == NULL) return 0; memcpy(dir, p, n); dir[n] = '\0'; - snprintf(out, outsz, "%s/%s.ww", dir, path_form); + int pn = snprintf(out, outsz, "%s/%s.ww", dir, path_form); + free(dir); + if (pn < 0 || (size_t)pn >= outsz) { + if (!e) break; + p = e + 1; + continue; + } if (access(out, 0) == 0) { *is_dir = 0; return 1; @@ -341,7 +365,6 @@ source_has_test_decl(const char *path) #define SEP_ROLE_TEST_SUPPORT 1 #define SEP_ROLE_GENERATED_MAIN 2 #define SEP_TEST_SUPPORT_MODULE "__wwtest" -#define SEP_IMPORT_PATH_MAX 256 #define SEP_MAXPRODUCT 256 #define SEP_MAXCONTEXT (SEP_MAXPRODUCT + 1) @@ -349,7 +372,7 @@ source_has_test_decl(const char *path) * The coordinator chooses variants, but the command owns which real source * paths enter a package compilation. */ static int -source_package_name(const char *path, char *out, size_t outsz) +source_package_name(const char *path, char **out) { char *buf; u64 len; @@ -375,13 +398,12 @@ source_package_name(const char *path, char *out, size_t outsz) free(buf); return -1; } - if (strlen(imports->module) >= outsz) { - errorf(imports->pos, "package name is too long"); + *out = strdup(imports->module); + if (*out == NULL) { freearena(a); free(buf); return -1; } - snprintf(out, outsz, "%s", imports->module); freearena(a); free(buf); return 0; @@ -432,8 +454,16 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, int is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0; if (variant == SEP_VARIANT_PRODUCTION && is_test) continue; if (variant == SEP_VARIANT_EXTERNAL && !is_test) continue; - char path[2048]; - snprintf(path, sizeof path, "%s/%s", dirpath, nm); + char path[PATH_MAX]; + int pn = snprintf(path, sizeof path, "%s/%s", dirpath, nm); + if (pn < 0 || (size_t)pn >= sizeof path) { + fprintf(stderr, "ww: package source path is too long\n"); + source_list_free(prod, nprod); + source_list_free(tests, ntests); + closedir(d); + *out_files = NULL; + return -2; + } struct stat st; if (lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) { fprintf(stderr, @@ -463,16 +493,19 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, return -2; } if (is_test) { - char package[256]; - if (source_package_name(path, package, sizeof package) < 0) { + char *package = NULL; + if (source_package_name(path, &package) < 0) { source_list_free(prod, nprod); source_list_free(tests, ntests); closedir(d); *out_files = NULL; return -2; } - if (test_package == NULL || strcmp(package, test_package) != 0) + if (test_package == NULL || strcmp(package, test_package) != 0) { + free(package); continue; + } + free(package); } char ***list = is_test ? &tests : ∏ int *n = is_test ? &ntests : &nprod; @@ -524,13 +557,15 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package, #define SEP_MAXPKG 256 struct seppkg { - char path[512]; /* compiler/import identity; derived from import_base */ - char import_base[SEP_IMPORT_PATH_MAX]; /* canonical directory import identity */ - char entry[1024]; /* resolved package dir (or file, for a file root) */ - char canon[1024]; /* canonical location; never package identity */ - char artifact[512]; /* stable non-importable variant artifact key */ - char name[256]; /* validated declared name; directory packages only */ - char test_package[256]; /* selected test package; root variants only */ + char *path; /* complete compiler/import identity */ + char *import_base; /* complete canonical ordinary import identity */ + char *entry; /* resolved package dir (or file, for a file root) */ + char *canon; /* canonical location; never compiler identity */ + char *artifact; /* legacy short artifact key, when one exists */ + char *storage; /* internal storage basename; never package identity */ + int storage_hashed; /* storage is the bounded complete-action locator */ + char *name; /* validated declared name; directory packages only */ + char *test_package; /* selected test package; root variants only */ char **sources; /* owned, byte-sorted selected paths; dirs only */ int nsources; int is_dir; @@ -552,8 +587,8 @@ struct seppkg { }; struct sepcontext { - char root[1024]; /* selected entry directory; diagnostic identity */ - char searchpath[8192]; /* root : explicit -I roots : toolchain source */ + char *root; /* selected entry directory; diagnostic identity */ + char *searchpath; /* root : explicit -I roots : toolchain source */ }; struct sepgraph { @@ -571,7 +606,7 @@ struct sepproduct { const char *identity; /* explicit canonical lookup identity, if any */ const char *test_package; const char *status; - char artifact[64]; + const char *artifact; int variant; int context; int root; @@ -579,7 +614,7 @@ struct sepproduct { }; #define SEP_MAXLFLAGS 32 -#define SEP_ARTIFACT_MAX 1024 +#define SEP_ARTIFACT_MAX PATH_MAX struct seplinkflags { const char *libdirs[SEP_MAXLFLAGS]; int nlibdirs; @@ -587,6 +622,166 @@ struct seplinkflags { int nlibs; }; +static char * +sep_sprintf(const char *fmt, ...) +{ + va_list ap, cp; + va_start(ap, fmt); + va_copy(cp, ap); + int n = vsnprintf(NULL, 0, fmt, cp); + va_end(cp); + if (n < 0) { + va_end(ap); + return NULL; + } + char *s = malloc((size_t)n + 1); + if (s != NULL && vsnprintf(s, (size_t)n + 1, fmt, ap) != n) { + free(s); + s = NULL; + } + va_end(ap); + return s; +} + +struct sepsha256 { + u32 h[8]; + u8 block[64]; + u64 bytes; + size_t nblock; +}; + +static u32 +sep_rotr32(u32 x, int n) +{ + return (x >> n) | (x << (32 - n)); +} + +static void +sep_sha256_block(struct sepsha256 *s, const u8 *p) +{ + static const u32 k[64] = { + 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, + 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, 0xab1c5ed5U, + 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, + 0x72be5d74U, 0x80deb1feU, 0x9bdc06a7U, 0xc19bf174U, + 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, + 0x2de92c6fU, 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, + 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, + 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, + 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, 0x53380d13U, + 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, + 0xa2bfe8a1U, 0xa81a664bU, 0xc24b8b70U, 0xc76c51a3U, + 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, + 0x19a4c116U, 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, + 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, + 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, + 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, 0xc67178f2U, + }; + u32 w[64]; + for (int i = 0; i < 16; i++) + w[i] = (u32)p[4*i] << 24 | (u32)p[4*i+1] << 16 + | (u32)p[4*i+2] << 8 | p[4*i+3]; + for (int i = 16; i < 64; i++) { + u32 x = w[i-15], y = w[i-2]; + u32 a = sep_rotr32(x, 7) ^ sep_rotr32(x, 18) ^ (x >> 3); + u32 b = sep_rotr32(y, 17) ^ sep_rotr32(y, 19) ^ (y >> 10); + w[i] = w[i-16] + a + w[i-7] + b; + } + u32 a = s->h[0], b = s->h[1], c = s->h[2], d = s->h[3]; + u32 e = s->h[4], f = s->h[5], g = s->h[6], h = s->h[7]; + for (int i = 0; i < 64; i++) { + u32 s1 = sep_rotr32(e, 6) ^ sep_rotr32(e, 11) + ^ sep_rotr32(e, 25); + u32 ch = (e & f) ^ (~e & g); + u32 t1 = h + s1 + ch + k[i] + w[i]; + u32 s0 = sep_rotr32(a, 2) ^ sep_rotr32(a, 13) + ^ sep_rotr32(a, 22); + u32 maj = (a & b) ^ (a & c) ^ (b & c); + u32 t2 = s0 + maj; + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + s->h[0] += a; s->h[1] += b; s->h[2] += c; s->h[3] += d; + s->h[4] += e; s->h[5] += f; s->h[6] += g; s->h[7] += h; +} + +static void +sep_sha256_init(struct sepsha256 *s) +{ + static const u32 init[8] = { + 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU, + 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U, + }; + memset(s, 0, sizeof *s); + memcpy(s->h, init, sizeof init); +} + +static void +sep_sha256_write(struct sepsha256 *s, const void *vp, size_t n) +{ + const u8 *p = vp; + s->bytes += n; + while (n > 0) { + size_t take = sizeof s->block - s->nblock; + if (take > n) take = n; + memcpy(s->block + s->nblock, p, take); + s->nblock += take; + p += take; + n -= take; + if (s->nblock == sizeof s->block) { + sep_sha256_block(s, s->block); + s->nblock = 0; + } + } +} + +static void +sep_sha256_sum(struct sepsha256 *s, u8 out[32]) +{ + u64 bits = s->bytes << 3; + u8 pad[128] = {0x80}; + size_t n = s->nblock < 56 ? 56 - s->nblock : 120 - s->nblock; + sep_sha256_write(s, pad, n); + u8 len[8]; + for (int i = 0; i < 8; i++) len[7-i] = (u8)(bits >> (8*i)); + sep_sha256_write(s, len, sizeof len); + for (int i = 0; i < 8; i++) { + out[4*i] = (u8)(s->h[i] >> 24); + out[4*i+1] = (u8)(s->h[i] >> 16); + out[4*i+2] = (u8)(s->h[i] >> 8); + out[4*i+3] = (u8)s->h[i]; + } +} + +static char * +sep_storage_digest(const struct seppkg *p) +{ + static const char prefix[] = "ww-package-storage-v2:"; + static const char hex[] = "0123456789abcdef"; + struct sepsha256 s; + u8 sum[32]; + char tag[5] = { (char)('0' + p->variant), ':', + (char)('0' + p->role), ':', 0 }; + static const u8 zero; + sep_sha256_init(&s); + sep_sha256_write(&s, prefix, sizeof prefix - 1); + sep_sha256_write(&s, tag, 4); + sep_sha256_write(&s, p->path, strlen(p->path)); + sep_sha256_write(&s, &zero, 1); + sep_sha256_write(&s, p->canon, strlen(p->canon)); + sep_sha256_sum(&s, sum); + char *out = malloc(80); + if (out == NULL) return NULL; + int n = snprintf(out, 16, "__wwpkg.v%d.r%d.h", p->variant, p->role); + if (n < 0 || n >= 16) { free(out); return NULL; } + for (int i = 0; i < 32; i++) { + out[n + 2*i] = hex[sum[i] >> 4]; + out[n + 2*i + 1] = hex[sum[i] & 15]; + } + out[n + 64] = '\0'; + return out; +} + static int sep_directory_variant(int variant) { @@ -595,15 +790,12 @@ sep_directory_variant(int variant) || variant == SEP_VARIANT_EXTERNAL; } -static int -sep_variant_path(int variant, const char *base, char *out, size_t outsz) +static char * +sep_variant_path(int variant, const char *base) { - int n; if (variant == SEP_VARIANT_EXTERNAL) - n = snprintf(out, outsz, "%s_test", base); - else - n = snprintf(out, outsz, "%s", base); - return n >= 0 && (size_t)n < outsz ? 0 : -1; + return sep_sprintf("%s_test", base); + return strdup(base); } static void @@ -644,6 +836,7 @@ sep_import_base_valid(const char *path) static int sep_command_declared_name(const struct seppkg *p) { + if (p->name == NULL) return 0; return p->variant == SEP_VARIANT_EXTERNAL ? strcmp(p->name, "main_test") == 0 : strcmp(p->name, "main") == 0; @@ -676,27 +869,19 @@ static int sep_bind_import_base(struct sepgraph *g, int pi, const char *base) { struct seppkg *p = &g->pkg[pi]; - if (base == NULL || base[0] == '\0' - || strlen(base) >= SEP_IMPORT_PATH_MAX) { - fprintf(stderr, "ww: package path is too long (limit %d bytes)\n", - SEP_IMPORT_PATH_MAX - 1); - return -1; - } + if (base == NULL || base[0] == '\0') return -1; if (!reserved_import_path(base) && !sep_import_base_valid(base)) { fprintf(stderr, "ww: invalid package path %s\n", base); return -1; } - if (p->import_base[0] != '\0') { + if (p->import_base != NULL) { if (strcmp(p->import_base, base) == 0) return 0; g->identity_failed = 1; sep_diag_directory_identities(p->entry, p->import_base, base); return -1; } - char path[sizeof p->path]; - if (sep_variant_path(p->variant, base, path, sizeof path) < 0) { - fprintf(stderr, "ww: package variant path is too long\n"); - return -1; - } + char *path = sep_variant_path(p->variant, base); + if (path == NULL) return -1; for (int i = 0; i < g->n; i++) { if (i == pi || !g->pkg[i].is_dir || g->pkg[i].generated_main) continue; @@ -704,25 +889,28 @@ sep_bind_import_base(struct sepgraph *g, int pi, const char *base) int support_alias = p->role == SEP_ROLE_TEST_SUPPORT || g->pkg[i].role == SEP_ROLE_TEST_SUPPORT; if (!support_alias && !same_location - && g->pkg[i].import_base[0] != '\0' + && g->pkg[i].import_base != NULL && strcmp(g->pkg[i].import_base, base) == 0) { g->identity_failed = 1; sep_diag_path_locations(base, g->pkg[i].entry, p->entry); + free(path); return -1; } if (same_location && !support_alias - && g->pkg[i].import_base[0] != '\0' + && g->pkg[i].import_base != NULL && strcmp(g->pkg[i].import_base, base) != 0) { g->identity_failed = 1; sep_diag_directory_identities(p->entry, g->pkg[i].import_base, base); + free(path); return -1; } - if (g->pkg[i].path[0] != '\0' + if (g->pkg[i].path != NULL && g->pkg[i].path[0] != '\0' && strcmp(g->pkg[i].path, path) == 0) { if (!same_location) { g->identity_failed = 1; sep_diag_path_locations(path, g->pkg[i].entry, p->entry); + free(path); return -1; } if (support_alias @@ -731,12 +919,15 @@ sep_bind_import_base(struct sepgraph *g, int pi, const char *base) fprintf(stderr, "ww: package action identity collision for %s in %s\n", path, p->entry); + free(path); return -1; } } } - snprintf(p->import_base, sizeof p->import_base, "%s", base); - snprintf(p->path, sizeof p->path, "%s", path); + p->import_base = strdup(base); + if (p->import_base == NULL) { free(path); return -1; } + free(p->path); + p->path = path; return 0; } @@ -745,30 +936,16 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, const char *entry, int is_dir, int variant, const char *test_package, int role, const char *artifact, int root) { - if (path != NULL && strlen(path) >= SEP_IMPORT_PATH_MAX) { - fprintf(stderr, "ww: package path is too long (limit %d bytes)\n", - SEP_IMPORT_PATH_MAX - 1); - return -1; - } char *canon = realpath(entry, NULL); if (canon == NULL) { fprintf(stderr, "ww: cannot canonicalize package %s\n", entry); return -1; } - if (strlen(canon) >= sizeof g->pkg[0].canon) { - fprintf(stderr, "ww: canonical package path is too long\n"); - free(canon); - return -1; - } const char *base = path ? path : ""; - char incoming_path[sizeof g->pkg[0].path]; - incoming_path[0] = '\0'; - if (is_dir && base[0] != '\0' - && sep_variant_path(variant, base, incoming_path, - sizeof incoming_path) < 0) { - fprintf(stderr, "ww: package variant path is too long\n"); - free(canon); - return -1; + char *incoming_path = NULL; + if (is_dir && base[0] != '\0') { + incoming_path = sep_variant_path(variant, base); + if (incoming_path == NULL) { free(canon); return -1; } } for (int i = 0; i < g->n; i++) { struct seppkg *q = &g->pkg[i]; @@ -777,46 +954,53 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, int support_alias = role == SEP_ROLE_TEST_SUPPORT || q->role == SEP_ROLE_TEST_SUPPORT; if (!support_alias && !same_location && base[0] != '\0' - && q->import_base[0] != '\0' + && q->import_base != NULL && strcmp(base, q->import_base) == 0) { g->identity_failed = 1; sep_diag_path_locations(base, q->entry, entry); + free(incoming_path); free(canon); return -1; } - if (incoming_path[0] != '\0' && q->path[0] != '\0' + if (incoming_path != NULL && q->path[0] != '\0' && strcmp(incoming_path, q->path) == 0 && !same_location) { g->identity_failed = 1; sep_diag_path_locations(incoming_path, q->entry, entry); + free(incoming_path); free(canon); return -1; } if (same_location && q->variant == variant && q->role == role) { + const char *selected = test_package ? test_package : ""; + const char *existing = q->test_package ? q->test_package : ""; if (variant != SEP_VARIANT_PRODUCTION - && strcmp(q->test_package, - test_package ? test_package : "") != 0) { + && strcmp(existing, selected) != 0) { fprintf(stderr, "ww: incompatible package-test roots %s\n", entry); + free(incoming_path); free(canon); return -1; } if (base[0] != '\0' && sep_bind_import_base(g, i, base) < 0) { + free(incoming_path); free(canon); return -1; } q->root = q->root || root; + free(incoming_path); free(canon); return i; } if (same_location) { if (support_alias) continue; - if (q->import_base[0] != '\0' && base[0] != '\0' + if (q->import_base != NULL && base[0] != '\0' && strcmp(q->import_base, base) != 0) { g->identity_failed = 1; sep_diag_directory_identities(entry, q->import_base, base); + free(incoming_path); free(canon); return -1; } @@ -826,6 +1010,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, fprintf(stderr, "ww: package directory %s has incompatible variants\n", entry); + free(incoming_path); free(canon); return -1; } @@ -834,6 +1019,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, if (same_location && strcmp(q->path, base) == 0 && q->variant == variant && q->role == role) { q->root = q->root || root; + free(incoming_path); free(canon); return i; } @@ -841,23 +1027,31 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, if (g->n >= SEP_MAXPKG) { fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG); + free(incoming_path); free(canon); return -1; } int ni = g->n++; struct seppkg *p = &g->pkg[ni]; memset(p, 0, sizeof *p); - snprintf(p->entry, sizeof p->entry, "%s", entry); - snprintf(p->canon, sizeof p->canon, "%s", canon); - free(canon); + p->path = strdup(base); + p->entry = strdup(entry); + p->canon = canon; + free(incoming_path); + if (p->path == NULL || p->entry == NULL) { + free(p->path); free(p->entry); free(p->canon); + g->n--; + return -1; + } p->is_dir = is_dir; p->variant = variant; p->role = role; p->root = root; p->emit_context = -1; - if (test_package != NULL) - snprintf(p->test_package, sizeof p->test_package, "%s", - test_package); + if (test_package != NULL) { + p->test_package = strdup(test_package); + if (p->test_package == NULL) { g->n--; return -1; } + } if (is_dir) { const char *inherited = base; if (inherited[0] == '\0') @@ -866,7 +1060,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, && g->pkg[i].role != SEP_ROLE_TEST_SUPPORT && role != SEP_ROLE_TEST_SUPPORT && strcmp(g->pkg[i].canon, p->canon) == 0 - && g->pkg[i].import_base[0] != '\0') { + && g->pkg[i].import_base != NULL) { inherited = g->pkg[i].import_base; break; } @@ -876,9 +1070,7 @@ sep_find_or_add_variant(struct sepgraph *g, const char *path, return -1; } } else { - snprintf(p->path, sizeof p->path, "%s", base); - if (artifact != NULL) - snprintf(p->artifact, sizeof p->artifact, "%s", artifact); + if (artifact != NULL) p->artifact = strdup(artifact); } return ni; } @@ -912,6 +1104,18 @@ sep_graph_free(struct sepgraph *g) for (int j = 0; j < g->pkg[i].bindings.n; j++) free(g->pkg[i].bindings.paths[j]); free(g->pkg[i].bindings.paths); + free(g->pkg[i].path); + free(g->pkg[i].import_base); + free(g->pkg[i].entry); + free(g->pkg[i].canon); + free(g->pkg[i].artifact); + free(g->pkg[i].storage); + free(g->pkg[i].name); + free(g->pkg[i].test_package); + } + for (int i = 0; i < g->ncontext; i++) { + free(g->context[i].root); + free(g->context[i].searchpath); } free(g); } @@ -923,82 +1127,178 @@ static int sep_context_for(struct sepgraph *g, const char *root, const char *extra_includes, const char *toolsrcdir) { - char searchpath[8192]; - int n; + char *searchpath; if (extra_includes != NULL && extra_includes[0] != '\0') - n = snprintf(searchpath, sizeof searchpath, "%s:%s:%s", + searchpath = sep_sprintf("%s:%s:%s", root, extra_includes, toolsrcdir); else - n = snprintf(searchpath, sizeof searchpath, "%s:%s", + searchpath = sep_sprintf("%s:%s", root, toolsrcdir); - if (n < 0 || (size_t)n >= sizeof searchpath) { - fprintf(stderr, "ww: package import search path is too long\n"); - return -1; - } + if (searchpath == NULL) return -1; for (int i = 0; i < g->ncontext; i++) - if (strcmp(g->context[i].searchpath, searchpath) == 0) + if (strcmp(g->context[i].searchpath, searchpath) == 0) { + free(searchpath); return i; + } if (g->ncontext >= SEP_MAXCONTEXT) { fprintf(stderr, "ww: too many package import contexts\n"); + free(searchpath); return -1; } struct sepcontext *c = &g->context[g->ncontext]; - if (snprintf(c->root, sizeof c->root, "%s", root) - >= (int)sizeof c->root) { - fprintf(stderr, "ww: package root path is too long\n"); - return -1; - } - snprintf(c->searchpath, sizeof c->searchpath, "%s", searchpath); + c->root = strdup(root); + c->searchpath = searchpath; + if (c->root == NULL) { free(searchpath); return -1; } return g->ncontext++; } -/* Dots stay (legal in filenames). Product roots have distinct artifact names - * even though each compiler unit resets to the bare executable namespace. */ -static void +/* Semantic package identity never enters a bounded filesystem component. + * Short actions retain their established basename. Overflow actions use a + * tagged SHA-256 locator; the complete owner remains in the unit/export and + * is checked before warm reuse. */ +#define SEP_NAME_MAX 255 + +static const char * +sep_legacy_artifact(const struct seppkg *p) +{ + if (p->artifact != NULL && p->artifact[0] != '\0') return p->artifact; + if (p->path != NULL && p->path[0] != '\0') return p->path; + return "__root"; +} + +static int +sep_validate_storage_path(const struct seppkg *p, const char *scratch) +{ + size_t need = strlen(scratch) + 1 + strlen(p->storage) + + strlen(".unit.new") + 1; + if (strlen(p->storage) + strlen(".unit.new") > SEP_NAME_MAX + || need > SEP_ARTIFACT_MAX) { + fprintf(stderr, "ww: package artifact path is too long\n"); + return -1; + } + return 0; +} + +static int +sep_assign_storage(struct seppkg *p, const char *scratch) +{ + const char *base = sep_legacy_artifact(p); + size_t need = strlen(scratch) + 1 + strlen(base) + + strlen(".unit.new") + 1; + if (strlen(base) + strlen(".unit.new") <= SEP_NAME_MAX + && need <= SEP_ARTIFACT_MAX) { + p->storage = strdup(base); + p->storage_hashed = 0; + } else { + p->storage = sep_storage_digest(p); + p->storage_hashed = 1; + } + if (p->storage == NULL) return -1; + return sep_validate_storage_path(p, scratch); +} + +static int sep_fname(const struct sepgraph *g, int pi, const char *scratch, const char *suffix, char *out, size_t outsz) { - const char *base = g->pkg[pi].path; - if (g->pkg[pi].artifact[0] != '\0') { - base = g->pkg[pi].artifact; - } else if (g->pkg[pi].root) { - if (base[0] == '\0') base = "__root"; - } else if (base[0] == '\0') { - base = "__root"; - } - snprintf(out, outsz, "%s/%s%s", scratch, base, suffix); + int n = snprintf(out, outsz, "%s/%s%s", scratch, + g->pkg[pi].storage, suffix); + return n >= 0 && (size_t)n < outsz ? 0 : -1; } -/* All later artifact construction uses fixed SEP_ARTIFACT_MAX buffers. Check - * the longest suffix once, before any file is opened, so truncation can never - * collapse two command-owned action identities onto one path. */ static int -sep_validate_artifact_paths(const struct sepgraph *g, const char *scratch) +sep_validate_artifact_paths(struct sepgraph *g, const char *scratch) { for (int i = 0; i < g->n; i++) { if (g->pkg[i].failed || !g->pkg[i].loaded) continue; - const char *base = g->pkg[i].artifact[0] != '\0' - ? g->pkg[i].artifact - : (g->pkg[i].path[0] != '\0' ? g->pkg[i].path : "__root"); - size_t need = strlen(scratch) + 1 + strlen(base) - + strlen(".unit.new") + 1; - if (need > SEP_ARTIFACT_MAX) { - fprintf(stderr, "ww: package artifact path is too long\n"); - return -1; - } - for (int j = i + 1; j < g->n; j++) { - if (g->pkg[j].failed || !g->pkg[j].loaded) continue; - const char *other = g->pkg[j].artifact[0] != '\0' - ? g->pkg[j].artifact - : (g->pkg[j].path[0] != '\0' - ? g->pkg[j].path : "__root"); - if (strcmp(base, other) == 0) { + if (g->pkg[i].storage == NULL + && sep_assign_storage(&g->pkg[i], scratch) < 0) return -1; + } + int changed; + do { + changed = 0; + for (int i = 0; i < g->n && !changed; i++) { + if (g->pkg[i].failed || !g->pkg[i].loaded) continue; + for (int j = i + 1; j < g->n; j++) { + if (g->pkg[j].failed || !g->pkg[j].loaded + || strcmp(g->pkg[i].storage, + g->pkg[j].storage) != 0) + continue; + if (!g->pkg[i].storage_hashed + || !g->pkg[j].storage_hashed) { + int pair[2] = {i, j}; + for (int k = 0; k < 2; k++) { + struct seppkg *p = &g->pkg[pair[k]]; + if (p->storage_hashed) continue; + free(p->storage); + p->storage = sep_storage_digest(p); + if (p->storage == NULL) return -1; + p->storage_hashed = 1; + if (sep_validate_storage_path(p, scratch) < 0) + return -1; + } + changed = 1; + break; + } fprintf(stderr, - "ww: package actions share artifact identity %s\n", - base); + "ww: package storage collision for %s in %s and %s in %s at %s\n", + g->pkg[i].path, g->pkg[i].canon, + g->pkg[j].path, g->pkg[j].canon, + g->pkg[i].storage); return -1; } } + } while (changed); + return 0; +} + +static int +sep_validate_unit_owner(const char *unit, const struct seppkg *p) +{ + struct stat st; + if (lstat(unit, &st) != 0) { + if (errno == ENOENT) return 0; + fprintf(stderr, "ww: package storage owner mismatch at %s for %s\n", + p->storage, p->path[0] ? p->path : "(root)"); + return -1; + } + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, "ww: package storage owner mismatch at %s for %s\n", + p->storage, p->path[0] ? p->path : "(root)"); + return -1; + } + FILE *f = fopen(unit, "rb"); + if (f == NULL) { + fprintf(stderr, "ww: package storage owner mismatch at %s for %s\n", + p->storage, p->path[0] ? p->path : "(root)"); + return -1; + } + char *line = NULL; + size_t cap = 0; + ssize_t n = getline(&line, &cap, f); + int bad = ferror(f) || fclose(f) != 0; + char *want = p->path[0] == '\0' + ? strdup("//ww:module-reset\n") + : sep_sprintf("//ww:module-reset %s\n", p->path); + int match = !bad && n >= 0 && want != NULL && strcmp(line, want) == 0; + free(want); + free(line); + if (match) return 1; + fprintf(stderr, + "ww: package storage owner mismatch at %s for %s\n", + p->storage, p->path[0] ? p->path : "(root)"); + return -1; +} + +static int +sep_validate_workdir_owners(const struct sepgraph *g, const char *scratch) +{ + char unit[SEP_ARTIFACT_MAX]; + for (int i = 0; i < g->n; i++) { + if (g->pkg[i].failed || !g->pkg[i].loaded) continue; + if (sep_fname(g, i, scratch, ".unit.ww", unit, sizeof unit) < 0) + return -1; + if (sep_validate_unit_owner(unit, &g->pkg[i]) < 0) return -1; } return 0; } @@ -1049,7 +1349,7 @@ sep_external_production_name(const struct seppkg *pkg, const char *path, int leaf_only) { if (pkg->variant != SEP_VARIANT_EXTERNAL - || pkg->test_package[0] == '\0') + || pkg->test_package == NULL || pkg->test_package[0] == '\0') return 0; const char *name = path; if (leaf_only) { @@ -1132,17 +1432,17 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, } if (imports->module != NULL - && (owned_source || g->pkg[pi].name[0] == '\0')) { + && (owned_source || g->pkg[pi].name == NULL)) { const char *declared = imports->module; - if (strlen(declared) >= sizeof g->pkg[pi].name) { - errorf(imports->pos, "package name is too long"); - freearena(a); - free(buf); - return -1; - } struct seppkg *pkg = &g->pkg[pi]; - if (pkg->name[0] == '\0') - snprintf(pkg->name, sizeof pkg->name, "%s", declared); + if (pkg->name == NULL) { + pkg->name = strdup(declared); + if (pkg->name == NULL) { + freearena(a); + free(buf); + return -1; + } + } else if (strcmp(pkg->name, declared) != 0) { errorf(imports->pos, "conflicting package names %s and %s in %s", @@ -1191,27 +1491,20 @@ sep_scan_file(struct sepgraph *g, int pi, const char *file, rc = -1; break; } - if (strlen(name) >= sizeof g->pkg[0].path) { - errorf(u->pos, "import path is too long (limit %zu bytes)", - sizeof g->pkg[0].path - 1); - rc = -1; - break; - } - char path_form[1024]; - import_path_form(name, path_form, sizeof path_form); - if (strlen(name) + 1 > sizeof path_form) { + char path_form[PATH_MAX]; + if (import_path_form(name, path_form, sizeof path_form) < 0) { errorf(u->pos, "import path is too long"); rc = -1; break; } - char ipath[1024]; + char ipath[PATH_MAX]; int external_production = 0; /* A selected external logical root is already an exact resolved * path/directory pair. Its source import of that same full ordinary * identity reuses the colocated production action. No declaration leaf * or unrelated cached package can override normal context lookup. */ const char *bound = g->pkg[pi].variant == SEP_VARIANT_EXTERNAL - && g->pkg[pi].import_base[0] != '\0' + && g->pkg[pi].import_base != NULL && strcmp(g->pkg[pi].import_base, name) == 0 ? g->pkg[pi].canon : NULL; int located = bound != NULL; @@ -1323,22 +1616,16 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, kind = "internal"; else if (g->pkg[variant].variant == SEP_VARIANT_EXTERNAL) kind = "external"; - char path[sizeof g->pkg[0].path]; - char artifact[sizeof g->pkg[0].artifact]; - char canon[sizeof g->pkg[0].canon]; - char entry[sizeof g->pkg[0].entry]; - snprintf(entry, sizeof entry, "%s", g->pkg[variant].entry); - const char *variant_artifact = g->pkg[variant].artifact[0] - ? g->pkg[variant].artifact : g->pkg[variant].path; - int pn = snprintf(path, sizeof path, "__wwtestmain.%s.%s.main", + char *path = sep_sprintf("__wwtestmain.%s.%s.main", g->pkg[variant].path, kind); - int an = snprintf(artifact, sizeof artifact, "%s-main", variant_artifact); - int cn = snprintf(canon, sizeof canon, "%s#%s-test-main", + const char *variant_artifact = g->pkg[variant].artifact != NULL + ? g->pkg[variant].artifact : g->pkg[variant].path; + char *artifact = sep_sprintf("%s-main", variant_artifact); + char *canon = sep_sprintf("%s#%s-test-main", g->pkg[variant].canon, kind); - if (pn < 0 || (size_t)pn >= sizeof path - || an < 0 || (size_t)an >= sizeof artifact - || cn < 0 || (size_t)cn >= sizeof canon) { - fprintf(stderr, "ww: generated test-main identity is too long\n"); + char *entry = strdup(g->pkg[variant].entry); + if (path == NULL || artifact == NULL || canon == NULL || entry == NULL) { + free(path); free(artifact); free(canon); free(entry); return -1; } for (int i = 0; i < g->n; i++) { @@ -1352,25 +1639,30 @@ sep_add_generated_main(struct sepgraph *g, struct sepproduct *product, has_support = 1; } if (has_variant && has_support == wants_support - && g->pkg[i].ndeps == 1 + wants_support) + && g->pkg[i].ndeps == 1 + wants_support) { + free(path); free(artifact); free(canon); free(entry); return i; + } } fprintf(stderr, "ww: generated test-main package identity collides with source import %s\n", path); + free(path); free(artifact); free(canon); free(entry); return -1; } if (g->n >= SEP_MAXPKG) { fprintf(stderr, "ww: too many packages (limit %d)\n", SEP_MAXPKG); + free(path); free(artifact); free(canon); free(entry); return -1; } struct seppkg *p = &g->pkg[g->n]; memset(p, 0, sizeof *p); - snprintf(p->path, sizeof p->path, "%s", path); - snprintf(p->artifact, sizeof p->artifact, "%s", artifact); - snprintf(p->canon, sizeof p->canon, "%s", canon); - snprintf(p->entry, sizeof p->entry, "%s", entry); - snprintf(p->name, sizeof p->name, "main"); + p->path = path; + p->artifact = artifact; + p->canon = canon; + p->entry = entry; + p->name = strdup("main"); + if (p->name == NULL) return -1; p->variant = SEP_VARIANT_TEST_MAIN; p->role = SEP_ROLE_GENERATED_MAIN; p->root = 1; @@ -1416,8 +1708,7 @@ sep_load_pkg(struct sepgraph *g, int pi, int context) if (!g->pkg[pi].loaded) { g->pkg[pi].loaded = 1; if (g->pkg[pi].is_dir) { - const char *test_package = g->pkg[pi].test_package[0] - ? g->pkg[pi].test_package : NULL; + const char *test_package = g->pkg[pi].test_package; g->pkg[pi].nsources = enumerate_dir_ww(g->pkg[pi].entry, g->pkg[pi].variant, test_package, &g->pkg[pi].sources); @@ -1576,16 +1867,10 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg, rel = pkg->canon + rn + 1; if (rel != NULL) { int ir = sep_import_path_from_relative(rel, out, outsz); - if (ir < 0) { - fprintf(stderr, - "ww: package path is too long (limit %d bytes)\n", - SEP_IMPORT_PATH_MAX - 1); - free(canon); - return -1; - } + if (ir < 0) { free(canon); return -1; } if (ir > 0 && reserved_import_path(out)) ir = 0; if (ir > 0) { - char located[1024]; + char located[PATH_MAX]; if (locate_import(searchpath, rel, located, sizeof located)) { char *selected = realpath(located, NULL); @@ -1604,35 +1889,40 @@ sep_reverse_import_base(const struct sepgraph *g, const struct seppkg *pkg, return 0; } -static int -sep_ordinary_declared_name(const struct seppkg *p, char *out, size_t outsz) +static char * +sep_ordinary_declared_name(const struct seppkg *p) { - if (p->name[0] == '\0') return -1; + if (p->name == NULL || p->name[0] == '\0') return NULL; size_t n = strlen(p->name); if (p->variant == SEP_VARIANT_EXTERNAL) { if (n <= 5 || strcmp(p->name + n - 5, "_test") != 0) { fprintf(stderr, "ww: package-test selector does not name an external package\n"); - return -1; + return NULL; } n -= 5; } - if (n + 1 > outsz) return -1; + char *out = malloc(n + 1); + if (out == NULL) return NULL; memcpy(out, p->name, n); out[n] = '\0'; - return 0; + return out; } /* The reserved local namespace is reversible, so filesystem identity never * depends on a hash, request order, output name, or another selected package. */ -static int -sep_local_import_base(const struct seppkg *p, char *out, size_t outsz) +static char * +sep_local_import_base(const struct seppkg *p) { - char leaf[sizeof p->name]; - if (sep_ordinary_declared_name(p, leaf, sizeof leaf) < 0) return -1; + char *leaf = sep_ordinary_declared_name(p); + if (leaf == NULL) return NULL; + size_t outsz = strlen(SEP_LOCAL_IMPORT_PREFIX) + 3 + + 4 * strlen(p->canon) + strlen(leaf) + 1; + char *out = malloc(outsz); + if (out == NULL) { free(leaf); return NULL; } size_t off = 0; int n = snprintf(out, outsz, "%s.p", SEP_LOCAL_IMPORT_PREFIX); - if (n < 0 || (size_t)n >= outsz) return -1; + if (n < 0 || (size_t)n >= outsz) { free(leaf); free(out); return NULL; } off = (size_t)n; static const char hex[] = "0123456789abcdef"; for (const unsigned char *s = (const unsigned char *)p->canon; @@ -1640,14 +1930,14 @@ sep_local_import_base(const struct seppkg *p, char *out, size_t outsz) unsigned char c = *s; if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { - if (off + 1 >= outsz) return -1; + if (off + 1 >= outsz) { free(leaf); free(out); return NULL; } out[off++] = (char)c; } else if (c == '_' || c == '/') { - if (off + 2 >= outsz) return -1; + if (off + 2 >= outsz) { free(leaf); free(out); return NULL; } out[off++] = '_'; out[off++] = c == '_' ? 'u' : 's'; } else { - if (off + 4 >= outsz) return -1; + if (off + 4 >= outsz) { free(leaf); free(out); return NULL; } out[off++] = '_'; out[off++] = 'x'; out[off++] = hex[c >> 4]; @@ -1655,10 +1945,11 @@ sep_local_import_base(const struct seppkg *p, char *out, size_t outsz) } } size_t ln = strlen(leaf); - if (off + 1 + ln + 1 > outsz) return -1; + if (off + 1 + ln + 1 > outsz) { free(leaf); free(out); return NULL; } out[off++] = '.'; memcpy(out + off, leaf, ln + 1); - return 0; + free(leaf); + return out; } /* Finalization verifies every reached context before generated-main creation. @@ -1672,22 +1963,26 @@ sep_finalize_directory_identities(struct sepgraph *g) struct seppkg *p = &g->pkg[pi]; if (!p->is_dir || p->generated_main || p->failed || !p->loaded || p->role == SEP_ROLE_TEST_SUPPORT - || p->import_base[0] != '\0') + || p->import_base != NULL) continue; for (int ci = 0; ci < g->ncontext; ci++) { if (p->context_state[ci] != 2) continue; - char candidate[SEP_IMPORT_PATH_MAX]; - int found = sep_reverse_import_base(g, p, ci, candidate, - sizeof candidate); - if (found < 0) return -1; - if (found > 0 && sep_bind_import_base(g, pi, candidate) < 0) + size_t n = strlen(p->canon) + 1; + char *candidate = malloc(n); + if (candidate == NULL) return -1; + int found = sep_reverse_import_base(g, p, ci, candidate, n); + if (found < 0) { free(candidate); return -1; } + if (found > 0 && sep_bind_import_base(g, pi, candidate) < 0) { + free(candidate); return -1; + } + free(candidate); } } for (int pi = 0; pi < g->n; pi++) { struct seppkg *p = &g->pkg[pi]; if (!p->is_dir || p->generated_main || p->failed || !p->loaded - || p->import_base[0] != '\0') + || p->import_base != NULL) continue; const char *base = NULL; for (int i = 0; i < g->n; i++) { @@ -1695,22 +1990,22 @@ sep_finalize_directory_identities(struct sepgraph *g) || g->pkg[i].role == SEP_ROLE_TEST_SUPPORT || p->role == SEP_ROLE_TEST_SUPPORT || strcmp(g->pkg[i].canon, p->canon) != 0 - || g->pkg[i].import_base[0] == '\0') + || g->pkg[i].import_base == NULL) continue; base = g->pkg[i].import_base; break; } - char local[SEP_IMPORT_PATH_MAX]; + char *local = NULL; if (base == NULL) { - if (sep_local_import_base(p, local, sizeof local) < 0) { - fprintf(stderr, - "ww: local package identity is too long (limit %d bytes)\n", - SEP_IMPORT_PATH_MAX - 1); - return -1; - } + local = sep_local_import_base(p); + if (local == NULL) return -1; base = local; } - if (sep_bind_import_base(g, pi, base) < 0) return -1; + if (sep_bind_import_base(g, pi, base) < 0) { + free(local); + return -1; + } + free(local); } for (int pi = 0; pi < g->n; pi++) { struct seppkg *p = &g->pkg[pi]; @@ -1727,20 +2022,15 @@ sep_finalize_directory_identities(struct sepgraph *g) p->name, p->path); return -1; } - p->artifact[0] = '\0'; - int n = 0; - char action_path[sizeof p->path]; - snprintf(action_path, sizeof action_path, "%s", p->path); + free(p->artifact); + p->artifact = NULL; if (p->variant == SEP_VARIANT_SAME_TEST) - n = snprintf(p->artifact, sizeof p->artifact, - "%s-internal-test", action_path); + p->artifact = sep_sprintf("%s-internal-test", p->path); else if (p->variant == SEP_VARIANT_EXTERNAL) - n = snprintf(p->artifact, sizeof p->artifact, - "%s-external-test", action_path); - if (n < 0 || (size_t)n >= sizeof p->artifact) { - fprintf(stderr, "ww: package variant artifact identity is too long\n"); + p->artifact = sep_sprintf("%s-external-test", p->path); + if ((p->variant == SEP_VARIANT_SAME_TEST + || p->variant == SEP_VARIANT_EXTERNAL) && p->artifact == NULL) return -1; - } } return 0; } @@ -2011,8 +2301,9 @@ file_equal(const char *a, const char *b) static int copy_file_atomic(const char *src, const char *dst) { - char tmp[1100]; - snprintf(tmp, sizeof tmp, "%s.new", dst); + char tmp[PATH_MAX]; + int tn = snprintf(tmp, sizeof tmp, "%s.new", dst); + if (tn < 0 || (size_t)tn >= sizeof tmp) return -1; FILE *in = fopen(src, "rb"); if (in == NULL) return -1; FILE *out = fopen(tmp, "wb"); @@ -2035,8 +2326,9 @@ static int record_product_status(const char *path) { if (path == NULL) return 0; - char tmp[1100]; - snprintf(tmp, sizeof tmp, "%s.new", path); + char tmp[PATH_MAX]; + int tn = snprintf(tmp, sizeof tmp, "%s.new", path); + if (tn < 0 || (size_t)tn >= sizeof tmp) return -1; FILE *f = fopen(tmp, "wb"); if (f == NULL) return -1; int bad = fputs("ok\n", f) == EOF; @@ -2052,7 +2344,7 @@ static void workdir_stamp_text(char *buf, size_t bufsz, int is_test, int emit_asm) { snprintf(buf, bufsz, "ww workdir fmt %d mode %s asm %d\n", - is_test ? 9 : 8, is_test ? "test" : "build", emit_asm); + is_test ? 11 : 10, is_test ? "test" : "build", emit_asm); } /* A stale global builder identity invalidates every committed unit voucher in @@ -2112,22 +2404,25 @@ build_one_sep_impl(const char *src, int entry_is_dir, const char *l6 = toolpath("WW_W6L", "w6l"); const char *libdir = envpath("WW_LIB"); if (libdir == NULL) { - static char libbuf[1024]; - snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); + static char libbuf[PATH_MAX]; + int n = snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); + if (n < 0 || (size_t)n >= sizeof libbuf) return 1; libdir = libbuf; } const char *srcdir = envpath("WW_SRCLIB"); - static char srcbuf[1024]; + static char srcbuf[PATH_MAX]; if (srcdir == NULL) { - snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir); + int n = snprintf(srcbuf, sizeof srcbuf, "%s/../../lib", self_dir); + if (n < 0 || (size_t)n >= sizeof srcbuf) return 1; if (access(srcbuf, 0) == 0) srcdir = srcbuf; else if (access("lib", 0) == 0) srcdir = "lib"; else srcdir = libdir; } const char *toolsrcdir = srcdir; - char srcd[1024]; + char srcd[PATH_MAX]; if (entry_is_dir) { - snprintf(srcd, sizeof srcd, "%s", src); + if (strlen(src) + 1 > sizeof srcd) return 1; + memcpy(srcd, src, strlen(src) + 1); size_t n = strlen(srcd); while (n > 1 && srcd[n-1] == '/') srcd[--n] = '\0'; } else { @@ -2139,19 +2434,21 @@ build_one_sep_impl(const char *src, int entry_is_dir, srcd[n] = '\0'; } else { srcd[0] = '.'; srcd[1] = '\0'; } } - char stem[1024]; + char *stem = NULL; if (entry_is_dir) { const char *b = strrchr(srcd, '/'); const char *base = b ? b + 1 : srcd; - snprintf(stem, sizeof stem, "%s/%s", srcd, base); + stem = sep_sprintf("%s/%s", srcd, base); } else { - snprintf(stem, sizeof stem, "%s", src); + stem = strdup(src); + if (stem == NULL) return 1; char *dot = strrchr(stem, '.'); if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; } + if (stem == NULL) return 1; const char *ostem = (objstem && objstem[0]) ? objstem : stem; int warm = workdir != NULL && workdir[0] != 0; - char scratch[1100]; + char scratch[PATH_MAX]; if (warm) { struct stat wst; if (stat(workdir, &wst) != 0 || !S_ISDIR(wst.st_mode)) { @@ -2162,9 +2459,14 @@ build_one_sep_impl(const char *src, int entry_is_dir, /* The workdir is caller-owned and persistent: no acquisition, * no refusal, and scratchout stays empty so the wrapper never * cleans it. */ - snprintf(scratch, sizeof scratch, "%s", workdir); + if (strlen(workdir) + 1 > sizeof scratch) return 1; + memcpy(scratch, workdir, strlen(workdir) + 1); } else { - snprintf(scratch, sizeof scratch, "%s.sepwork", ostem); + int n = snprintf(scratch, sizeof scratch, "%s.sepwork", ostem); + if (n < 0 || (size_t)n >= sizeof scratch) { + fprintf(stderr, "ww: scratch path is too long\n"); + return 1; + } if (mkdir(scratch, 0755) != 0) { fprintf(stderr, "ww: cannot create scratch %s\n", scratch); return 1; @@ -2172,11 +2474,14 @@ build_one_sep_impl(const char *src, int entry_is_dir, /* Hand the scratch path back only after mkdir succeeds. The * wrapper therefore never removes a pre-existing path that this * build failed to acquire. */ - if (scratchout) snprintf(scratchout, scratchoutsz, "%s", scratch); + if (scratchout) { + if (strlen(scratch) + 1 > scratchoutsz) return 1; + memcpy(scratchout, scratch, strlen(scratch) + 1); + } } int stale_all = 0, stampok = 0; - char toolw[1200] = {0}, toolc[1200] = {0}, toola[1200] = {0}; - char stampf[1200] = {0}; + char toolw[PATH_MAX] = {0}, toolc[PATH_MAX] = {0}; + char toola[PATH_MAX] = {0}, stampf[PATH_MAX] = {0}; char stampwant[128]; if (warm) { if (self_path == NULL || !file_is_reg(self_path)) { @@ -2184,10 +2489,16 @@ build_one_sep_impl(const char *src, int entry_is_dir, self_path ? self_path : "(unknown)"); return 1; } - snprintf(toolw, sizeof toolw, "%s/.wwtool.ww", scratch); - snprintf(toolc, sizeof toolc, "%s/.wwtool.w6c", scratch); - snprintf(toola, sizeof toola, "%s/.wwtool.w6a", scratch); - snprintf(stampf, sizeof stampf, "%s/.wwtool.stamp", scratch); + int nw = snprintf(toolw, sizeof toolw, "%s/.wwtool.ww", scratch); + int nc = snprintf(toolc, sizeof toolc, "%s/.wwtool.w6c", scratch); + int na = snprintf(toola, sizeof toola, "%s/.wwtool.w6a", scratch); + int ns = snprintf(stampf, sizeof stampf, "%s/.wwtool.stamp", scratch); + if (nw < 0 || nc < 0 || na < 0 || ns < 0 + || (size_t)nw >= sizeof toolw || (size_t)nc >= sizeof toolc + || (size_t)na >= sizeof toola || (size_t)ns >= sizeof stampf) { + fprintf(stderr, "ww: workdir path is too long\n"); + return 1; + } workdir_stamp_text(stampwant, sizeof stampwant, is_test, emit_asm); char got[128] = {0}; FILE *sf = fopen(stampf, "rb"); @@ -2201,8 +2512,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, || !file_equal(toolc, c6) || (!emit_asm && !file_equal(toola, a6))) stale_all = 1; - if (stale_all && invalidate_workdir_units(scratch) != 0) - return 1; } struct sepgraph *g = calloc(1, sizeof *g); @@ -2214,7 +2523,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, for (int i = 0; i < nproducts; i++) { const char *entry = products[i].dir != NULL ? products[i].dir : src; - char contextdir[1024]; + char contextdir[PATH_MAX]; const char *contextroot = entry; if (!entry_is_dir) { const char *slash = strrchr(entry, '/'); @@ -2251,7 +2560,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, * when user source occupies that identity, the reserved graph alias keeps * it distinct. The linker receives the same support archive closure. */ if (is_test) { - char tpath[1024]; + char tpath[PATH_MAX]; int tdir = 0; if (locate_import(toolsrcdir, "test", tpath, sizeof tpath)) { tdir = 1; @@ -2270,7 +2579,7 @@ build_one_sep_impl(const char *src, int entry_is_dir, collision = 1; } for (int i = 0; i < nproducts && !collision; i++) { - char userpath[1024]; + char userpath[PATH_MAX]; int userdir = 0; if (locate_import(g->context[products[i].context].searchpath, "test", userpath, sizeof userpath)) { @@ -2367,6 +2676,10 @@ build_one_sep_impl(const char *src, int entry_is_dir, } if (sep_validate_artifact_paths(g, scratch) < 0) return 1; + if (warm && sep_validate_workdir_owners(g, scratch) < 0) + return 1; + if (warm && stale_all && invalidate_workdir_units(scratch) != 0) + return 1; int root_package = package_only; if (root_package && !g->pkg[products[0].root].failed && strcmp(g->pkg[products[0].root].name, "main") == 0) { @@ -2577,8 +2890,11 @@ build_one_sep_impl(const char *src, int entry_is_dir, free(order); return 1; } if (!stampok) { - char stampnew[1300]; - snprintf(stampnew, sizeof stampnew, "%s.new", stampf); + char stampnew[PATH_MAX]; + int sn = snprintf(stampnew, sizeof stampnew, "%s.new", stampf); + if (sn < 0 || (size_t)sn >= sizeof stampnew) { + free(order); return 1; + } FILE *sf = fopen(stampnew, "wb"); int bad = sf == NULL || fputs(stampwant, sf) == EOF; if (sf != NULL && fclose(sf) != 0) bad = 1; @@ -2597,7 +2913,12 @@ build_one_sep_impl(const char *src, int entry_is_dir, char outiface[SEP_ARTIFACT_MAX]; sep_fname(g, root, scratch, ".a", archive, sizeof archive); sep_fname(g, root, scratch, ".wwi", iface, sizeof iface); - snprintf(outiface, sizeof outiface, "%s.wwi", out); + int on = snprintf(outiface, sizeof outiface, "%s.wwi", out); + if (on < 0 || (size_t)on >= sizeof outiface) { + fprintf(stderr, "ww: package output path is too long\n"); + free(order); + return 1; + } if (copy_file_atomic(archive, out) != 0 || copy_file_atomic(iface, outiface) != 0) { fprintf(stderr, "ww: cannot write package artifact %s\n", out); @@ -2613,15 +2934,19 @@ build_one_sep_impl(const char *src, int entry_is_dir, * first, then every transitively reachable package `.a`, then libwwrt.a. An * internal test variant already contains production sources, so its * colocated production archive is omitted without dropping dependencies. */ - char rtpaths[2][1024]; + char rtpaths[2][PATH_MAX]; int nrt = 1; - snprintf(rtpaths[0], sizeof rtpaths[0], "%s/libwwrt.a", libdir); - if (access(rtpaths[0], 0) != 0) { + int rn = snprintf(rtpaths[0], sizeof rtpaths[0], "%s/libwwrt.a", libdir); + int have_archive = rn >= 0 && (size_t)rn < sizeof rtpaths[0] + && access(rtpaths[0], 0) == 0; + if (!have_archive) { nrt = 2; - snprintf(rtpaths[0], sizeof rtpaths[0], + rn = snprintf(rtpaths[0], sizeof rtpaths[0], "%s/../obj/rt/start.o", self_dir); - snprintf(rtpaths[1], sizeof rtpaths[1], + if (rn < 0 || (size_t)rn >= sizeof rtpaths[0]) return 1; + rn = snprintf(rtpaths[1], sizeof rtpaths[1], "%s/../obj/rt/syscall.o", self_dir); + if (rn < 0 || (size_t)rn >= sizeof rtpaths[1]) return 1; } int nlibdirs = linkflags ? linkflags->nlibdirs : 0; int nlibs = linkflags ? linkflags->nlibs : 0; @@ -2709,7 +3034,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity, int root_variant, const char *test_package, int emit_asm, int keepscratch, const char *workdir) { - char scratch[1100] = {0}; + char scratch[PATH_MAX] = {0}; struct sepgraph *g = NULL; struct sepproduct product = { .dir = src, @@ -2717,13 +3042,13 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity, .identity = root_identity, .test_package = test_package, .status = NULL, - .artifact = {0}, + .artifact = NULL, .variant = root_variant, .root = -1, .variant_root = -1, }; if (!package_only && !entry_is_dir) - snprintf(product.artifact, sizeof product.artifact, "__root"); + product.artifact = "__root"; int r = build_one_sep_impl(src, entry_is_dir, out, objstem, extra_includes, linkflags, package_only, is_test, &product, 1, emit_asm, workdir, scratch, @@ -2762,7 +3087,7 @@ build_package_tests(const char *src, const char *root_identity, const char *extra_includes, const char *workdir, struct sepproduct *products, int nproducts) { - char scratch[1100] = {0}; + char scratch[PATH_MAX] = {0}; struct sepgraph *g = NULL; for (int i = 0; i < nproducts; i++) products[i].identity = root_identity; @@ -2782,28 +3107,28 @@ do_version(void) /* Compose the standard module search path: cwd : : selected * source library. The `extra` string is colon-separated -I dirs from argv. */ -static const char * -search_path(const char *extra, char *buf, size_t bufsz) +static char * +search_path(const char *extra) { const char *libdir = envpath("WW_SRCLIB"); - static char libbuf[1024]; + static char libbuf[PATH_MAX]; if (libdir == NULL) { libdir = envpath("WW_LIB"); } if (libdir == NULL) { - snprintf(libbuf, sizeof libbuf, "%s/../../lib", self_dir); + int n = snprintf(libbuf, sizeof libbuf, "%s/../../lib", self_dir); + if (n < 0 || (size_t)n >= sizeof libbuf) return NULL; if (access(libbuf, 0) == 0) libdir = libbuf; else if (access("lib", 0) == 0) libdir = "lib"; else { - snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); + n = snprintf(libbuf, sizeof libbuf, "%s/../lib", self_dir); + if (n < 0 || (size_t)n >= sizeof libbuf) return NULL; libdir = libbuf; } } if (extra && extra[0]) - snprintf(buf, bufsz, ".:%s:%s", extra, libdir); - else - snprintf(buf, bufsz, ".:%s", libdir); - return buf; + return sep_sprintf(".:%s:%s", extra, libdir); + return sep_sprintf(".:%s", libdir); } static void @@ -2823,22 +3148,59 @@ resolve_module(const char *name, const char *incs, char *out, size_t outsz, struct stat st; if (stat(name, &st) == 0) { if (S_ISREG(st.st_mode)) { - snprintf(out, outsz, "%s", name); + if (strlen(name) + 1 > outsz) return 0; + memcpy(out, name, strlen(name) + 1); *is_dir = 0; return 1; } if (S_ISDIR(st.st_mode)) { - snprintf(out, outsz, "%s", name); + if (strlen(name) + 1 > outsz) return 0; + memcpy(out, name, strlen(name) + 1); *is_dir = 1; return 1; } } if (reserved_import_path(name)) return 0; - char sp[4096]; - search_path(incs, sp, sizeof sp); - char path_form[256]; - import_path_form(name, path_form, sizeof path_form); - return locate_module(sp, path_form, out, outsz, is_dir); + char *sp = search_path(incs); + char *path_form = malloc(strlen(name) + 1); + if (sp == NULL || path_form == NULL) { + free(sp); free(path_form); + return 0; + } + if (import_path_form(name, path_form, strlen(name) + 1) < 0) { + free(sp); free(path_form); + return 0; + } + int found = locate_module(sp, path_form, out, outsz, is_dir); + free(sp); + free(path_form); + return found; +} + +static int +cli_copy(const char *cmd, const char *flag, char *out, size_t outsz, + const char *value) +{ + size_t n = strlen(value); + if (n + 1 > outsz) { + fprintf(stderr, "ww %s: %s path is too long\n", cmd, flag); + return -1; + } + memcpy(out, value, n + 1); + return 0; +} + +static int +include_append(const char *cmd, char *incs, size_t incsz, const char *dir) +{ + size_t n = strlen(incs), dn = strlen(dir); + if (n + (n != 0) + dn + 1 > incsz) { + fprintf(stderr, "ww %s: -I search path is too long\n", cmd); + return -1; + } + if (n != 0) incs[n++] = ':'; + memcpy(incs + n, dir, dn + 1); + return 0; } /* Returns the index past the last arg consumed for positionals (so callers @@ -2881,13 +3243,15 @@ parse_build_flags(const char *cmd, int argc, char **argv, "ww %s: -w needs an argument\n", cmd); return -1; } - snprintf(workdir, workdirsz, "%s", argv[++i]); + if (cli_copy(cmd, "-w", workdir, workdirsz, argv[++i]) < 0) + return -1; } else if (strncmp(argv[i], "-w", 2) == 0 && argv[i][2]) { if (workdir == NULL) { fprintf(stderr, "ww %s: unknown flag\n", cmd); return -1; } - snprintf(workdir, workdirsz, "%s", argv[i] + 2); + if (cli_copy(cmd, "-w", workdir, workdirsz, argv[i] + 2) < 0) + return -1; } else if (strncmp(argv[i], "-l", 2) == 0 && argv[i][2]) { if (linkflags->nlibs >= SEP_MAXLFLAGS) { fprintf(stderr, "ww %s: too many -l\n", cmd); @@ -2928,22 +3292,22 @@ parse_build_flags(const char *cmd, int argc, char **argv, "ww %s: -I needs an argument\n", cmd); return -1; } - size_t n = strlen(incs); - snprintf(incs + n, incsz - n, - "%s%s", n ? ":" : "", argv[++i]); + if (include_append(cmd, incs, incsz, argv[++i]) < 0) + return -1; } else if (strncmp(argv[i], "-I", 2) == 0 && argv[i][2]) { - size_t n = strlen(incs); - snprintf(incs + n, incsz - n, - "%s%s", n ? ":" : "", argv[i] + 2); + if (include_append(cmd, incs, incsz, argv[i] + 2) < 0) + return -1; } else if (strcmp(argv[i], "-o") == 0) { if (i + 1 >= argc) { fprintf(stderr, "ww %s: -o needs an argument\n", cmd); return -1; } - snprintf(outpath, outsz, "%s", argv[++i]); + if (cli_copy(cmd, "-o", outpath, outsz, argv[++i]) < 0) + return -1; } else if (strncmp(argv[i], "-o", 2) == 0 && argv[i][2]) { - snprintf(outpath, outsz, "%s", argv[i] + 2); + if (cli_copy(cmd, "-o", outpath, outsz, argv[i] + 2) < 0) + return -1; } else if (argv[i][0] == '-') { fprintf(stderr, "ww %s: unknown flag\n", cmd); return -1; @@ -2961,9 +3325,12 @@ do_build(int argc, char **argv) { const char *src = NULL; struct seplinkflags linkflags = {0}; - char incs[2048] = {0}; - char outflag[1024] = {0}; - char workdir[1024] = {0}; + size_t incsz = 1; + for (int i = 0; i < argc; i++) incsz += strlen(argv[i]) + 1; + char incs[incsz]; + memset(incs, 0, sizeof incs); + char outflag[PATH_MAX] = {0}; + char workdir[PATH_MAX] = {0}; int emit_asm = 0; int package_only = 0; if (parse_build_flags("build", argc, argv, incs, sizeof incs, @@ -2978,7 +3345,7 @@ do_build(int argc, char **argv) } struct stat requested; int literal = stat(src, &requested) == 0; - char resolved[1024]; + char resolved[PATH_MAX]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { fprintf(stderr, "ww build: cannot find module %s\n", src); @@ -2988,16 +3355,16 @@ do_build(int argc, char **argv) fprintf(stderr, "ww build: -p needs a package directory\n"); return 2; } - char out[1024]; + char out[PATH_MAX]; const char *objstem = NULL; if (outflag[0]) { /* -o sets both the binary path and the intermediate stem so * artifacts land beside the requested output (T3). */ - snprintf(out, sizeof out, "%s", outflag); + memcpy(out, outflag, strlen(outflag) + 1); objstem = out; } else if (is_dir) { - char tmp[1024]; - snprintf(tmp, sizeof tmp, "%s", resolved); + char tmp[PATH_MAX]; + memcpy(tmp, resolved, strlen(resolved) + 1); size_t n = strlen(tmp); while (n > 1 && tmp[n-1] == '/') tmp[--n] = '\0'; const char *b = strrchr(tmp, '/'); @@ -3016,8 +3383,11 @@ do_run(int argc, char **argv) { const char *src = NULL; struct seplinkflags linkflags = {0}; - char incs[2048] = {0}; - char outflag[1024] = {0}; /* -o accepted+ignored: run always uses the temp */ + size_t incsz = 1; + for (int i = 0; i < argc; i++) incsz += strlen(argv[i]) + 1; + char incs[incsz]; + memset(incs, 0, sizeof incs); + char outflag[PATH_MAX] = {0}; /* -o accepted+ignored: run always uses the temp */ int next = parse_build_flags("run", argc, argv, incs, sizeof incs, &linkflags, outflag, sizeof outflag, NULL, 0, &src, NULL, NULL); @@ -3025,19 +3395,20 @@ do_run(int argc, char **argv) if (src == NULL) src = "."; struct stat requested; int literal = stat(src, &requested) == 0; - char resolved[1024]; + char resolved[PATH_MAX]; int is_dir = 0; if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) { fprintf(stderr, "ww run: cannot find module %s\n", src); return 1; } - char tmpdir[1024], tmp[1024]; + char tmpdir[PATH_MAX], tmp[PATH_MAX]; snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_run_%d", getpid()); if (mkdir(tmpdir, 0700) != 0) { fprintf(stderr, "ww: cannot create temporary directory %s\n", tmpdir); return 1; } - snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + int tn = snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + if (tn < 0 || (size_t)tn >= sizeof tmp) return 1; /* The freshly acquired directory owns both the executable and the * adjacent main.sepwork tree. Nothing outside it is adopted or removed. */ const char *root_identity = !literal && is_dir ? src : NULL; @@ -3091,7 +3462,10 @@ do_test(int argc, char **argv) const char *src = NULL; struct sepproduct products[SEP_MAXPRODUCT]; int nproducts = 0; - char incs[2048] = {0}; + size_t incsz = 1; + for (int i = 0; i < argc; i++) incsz += strlen(argv[i]) + 1; + char incs[incsz]; + memset(incs, 0, sizeof incs); /* -c (Go's `go test -c`) builds the test binary without running it. * -S + -o stops after the lib/test-inclusive package `.s` * outputs are emitted. Both routes use build_one_sep's is_test bundle @@ -3102,8 +3476,8 @@ do_test(int argc, char **argv) * wwstage twin (selfhost/cmd/ww/main.ww dotest). */ int compileonly = 0; int emit_asm = 0; - char outstem[1024] = {0}; - char workdir[1024] = {0}; + char outstem[PATH_MAX] = {0}; + char workdir[PATH_MAX] = {0}; int packageopts = 0; int afterdash = 0; const char *request_identity = NULL; @@ -3131,9 +3505,8 @@ do_test(int argc, char **argv) } dir = argv[++i]; } - size_t n = strlen(incs); - snprintf(incs + n, sizeof incs - n, - "%s%s", n ? ":" : "", dir); + if (include_append("test", incs, sizeof incs, dir) < 0) + return 2; } else if (strcmp(argv[i], "-c") == 0) { compileonly = 1; } else if (strcmp(argv[i], "--ww-root-identity") == 0) { @@ -3165,7 +3538,7 @@ do_test(int argc, char **argv) if ((strcmp(kind, "production") != 0 && strcmp(kind, "same") != 0 && strcmp(kind, "external") != 0) - || pn == 0 || pn >= sizeof ((struct seppkg *)0)->name + || pn == 0 || dir[0] == '\0' || output[0] == '\0' || status[0] == '\0' || (strcmp(kind, "external") == 0 @@ -3180,7 +3553,7 @@ do_test(int argc, char **argv) products[nproducts].out = output; products[nproducts].test_package = name; products[nproducts].status = status; - products[nproducts].artifact[0] = '\0'; + products[nproducts].artifact = NULL; products[nproducts].variant = variant; products[nproducts].root = -1; products[nproducts].variant_root = -1; @@ -3208,18 +3581,22 @@ do_test(int argc, char **argv) "ww test: -o needs an argument\n"); return 2; } - snprintf(outstem, sizeof outstem, "%s", argv[++i]); + if (cli_copy("test", "-o", outstem, sizeof outstem, + argv[++i]) < 0) return 2; } else if (argv[i][1] == 'o' && argv[i][2]) { - snprintf(outstem, sizeof outstem, "%s", argv[i] + 2); + if (cli_copy("test", "-o", outstem, sizeof outstem, + argv[i] + 2) < 0) return 2; } else if (strcmp(argv[i], "-w") == 0) { if (i + 1 >= argc) { fprintf(stderr, "ww test: -w needs an argument\n"); return 2; } - snprintf(workdir, sizeof workdir, "%s", argv[++i]); + if (cli_copy("test", "-w", workdir, sizeof workdir, + argv[++i]) < 0) return 2; } else if (argv[i][1] == 'w' && argv[i][2]) { - snprintf(workdir, sizeof workdir, "%s", argv[i] + 2); + if (cli_copy("test", "-w", workdir, sizeof workdir, + argv[i] + 2) < 0) return 2; } else { fprintf(stderr, "ww test: unknown flag\n"); return 2; @@ -3255,7 +3632,7 @@ do_test(int argc, char **argv) } /* Artifact identity is derived from canonical package identity after * discovery; product position is deliberately not an action key. */ - products[i].artifact[0] = '\0'; + products[i].artifact = NULL; } if (nproducts != 0 && packageopts) { fprintf(stderr, @@ -3303,7 +3680,7 @@ do_test(int argc, char **argv) if (stat(target, &st) != 0) { /* not a literal path — try module resolution and run as * a single test program. */ - char resolved[1024]; + char resolved[PATH_MAX]; int is_dir = 0; if (!resolve_module(target, incs, resolved, sizeof resolved, &is_dir)) { @@ -3349,14 +3726,18 @@ do_test(int argc, char **argv) "ww test: package-test variant needs one directory\n"); return 2; } - char tmpdir[1024] = {0}, tmp[1024]; + char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX]; const char *outp; int owntmp = !outstem[0] && !workdir[0]; if (outstem[0]) outp = outstem; else if (workdir[0]) { /* The workdir owns the persistent test binary the same * way it owns the package artifacts. */ - snprintf(tmp, sizeof tmp, "%s/main", workdir); + int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir); + if (tn < 0 || (size_t)tn >= sizeof tmp) { + fputs("ww: workdir path is too long\n", stderr); + return 1; + } outp = tmp; } else { snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_test_%d", getpid()); @@ -3365,7 +3746,8 @@ do_test(int argc, char **argv) tmpdir); return 1; } - snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + int tn = snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + if (tn < 0 || (size_t)tn >= sizeof tmp) return 1; outp = tmp; } /* No-o redirects internal scratch to /tmp rather than beside the @@ -3415,12 +3797,16 @@ do_test(int argc, char **argv) "ww test: package options need a directory\n"); return 2; } - char tmpdir[1024] = {0}, tmp[1024]; + char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX]; const char *outp; int owntmp = !outstem[0] && !workdir[0]; if (outstem[0]) outp = outstem; else if (workdir[0]) { - snprintf(tmp, sizeof tmp, "%s/main", workdir); + int tn = snprintf(tmp, sizeof tmp, "%s/main", workdir); + if (tn < 0 || (size_t)tn >= sizeof tmp) { + fputs("ww: workdir path is too long\n", stderr); + return 1; + } outp = tmp; } else { snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_test_%d", getpid()); @@ -3429,7 +3815,8 @@ do_test(int argc, char **argv) tmpdir); return 1; } - snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + int tn = snprintf(tmp, sizeof tmp, "%s/main", tmpdir); + if (tn < 0 || (size_t)tn >= sizeof tmp) return 1; outp = tmp; } /* See module-mode note: no-o scratch is redirected to /tmp. */ @@ -3502,8 +3889,12 @@ main(int argc, char **argv) { if (argc >= 1) { self_path = argv[0]; - char buf[1024]; - snprintf(buf, sizeof buf, "%s", argv[0]); + char buf[PATH_MAX]; + if (strlen(argv[0]) + 1 > sizeof buf) { + fputs("ww: driver path is too long\n", stderr); + return 1; + } + memcpy(buf, argv[0], strlen(argv[0]) + 1); self_dir = strdup(dirname(buf)); } if (argc < 2) { fputs(usage, stderr); return 2; } diff --git a/internal/wwpackage/package.ww b/internal/wwpackage/package.ww index 1f1fe6d1..07193cd1 100644 --- a/internal/wwpackage/package.ww +++ b/internal/wwpackage/package.ww @@ -1,5 +1,7 @@ package wwpackage; +import crypto.sha256; +import hash; import os; import os.exec; import strconv; @@ -269,9 +271,9 @@ fn pkgkeepfile(name: str) bool = { // One explicit package directory; with recurse (the DIR/... form) every // subdirectory whose name does not begin with '.' or '_' is descended as -// well, Go's ./... convention. lstat is used for the root and each -// candidate so a symlink cannot be used to cross the boundary (including -// DT_UNKNOWN files); a symlinked subdirectory is skipped, not followed. +// well, Go's ./... convention. An explicitly selected root may be a symlink; +// canonical planning coalesces it with the target directory. Recursive child +// symlinks remain skipped and symlink source files remain rejected. fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = { let rootstat: os.filestat; match (os.lstat(&rootstat, path)) { @@ -283,9 +285,14 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = { }; }; if (pkgmodeis(rootstat.mode, os.mode.LINK)) { - pkgfailpath(path, "symlink traversal is not allowed"); - st.errors += 1; - return; + match (os.stat(&rootstat, path)) { + case void => void; + case let e: os.oserror => { + pkgfailpath(path, "cannot stat package directory"); + st.errors += 1; + return; + }; + }; }; if (!pkgmodeis(rootstat.mode, os.mode.DIR)) { pkgfailpath(path, "expected one package directory"); @@ -540,7 +547,25 @@ fn pkgworkescape(s: str) str = { }; fn pkgworkkey(dir: str) str = { - return strings.concat("d_", pkgworkescape(dir)); + let escaped: str = strings.concat("d_", pkgworkescape(dir)); + if (escaped.len <= 255) { return escaped; }; + let state: sha256.state = sha256.sha256(); + let h: *hash.hash = (&state): *hash.hash; + hash.write(h, strings.toutf8("ww-request-workdir-v1:")); + hash.write(h, strings.toutf8(dir)); + let digest: [32]u8; + hash.sum(h, digest[0:32]); + let out: []u8 = alloc([], 65u64)!; + let digits: str = "0123456789abcdef"; + let i: i32 = 0; + for (i < 32) { + let high: i32 = (digest[i] / 16u8): i32; + let low: i32 = (digest[i] % 16u8): i32; + append(out, digits[high]); + append(out, digits[low]); + i += 1; + }; + return strings.concat("d_", strings.frombytes(out)); }; fn pkgsetplanpaths(p: *pkgplan, groups: []pkggroup, root: str, index: i32, diff --git a/lib/ww/syntax/parse.ww b/lib/ww/syntax/parse.ww index b0376e88..ad2e961e 100644 --- a/lib/ww/syntax/parse.ww +++ b/lib/ww/syntax/parse.ww @@ -629,7 +629,9 @@ export fn parsefile(p: *parser) *node = { || strings.compare(name, "main_test") == 0); if (strings.compare(name, last) != 0 && !testsupport && !commandpackage) { - errmsg(p, "package does not match import path"); + errmsg(p, strings.concat(strings.concat(strings.concat( + "package ", name), + " does not match import path "), active)); }; } else { p.curmod = name; diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index 5e55bf20..715f7c3b 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -60,6 +60,25 @@ fn slurp(path: *u8) (*u8, u64) = { return buf.ptr, nz; }; +fn exportownermatches(buf: *u8, n: u64, path: *u8) bool = { + let prefix: str = "//ww:module "; + let pn: u64 = cstrlen(path); + let poff: u64 = prefix.len: u64; + let need: u64 = poff + pn + 1u64; + if (n < need) { return false; }; + let i: u64 = 0u64; + for (i < poff) { + if (buf[i] != prefix.ptr[i]) { return false; }; + i += 1u64; + }; + i = 0u64; + for (i < pn) { + if (buf[poff + i] != path[i]) { return false; }; + i += 1u64; + }; + return buf[need - 1u64] == 10u8; +}; + export fn main(argc: i32, argv: **u8) i32 = { let src: *u8 = nil; let out: *u8 = nil; @@ -204,6 +223,17 @@ export fn main(argc: i32, argv: **u8) i32 = { os.write(2, nl.ptr, nl.len: u64); return 1; }; + if (!exportownermatches(ibuf, ilen, importpaths[importi])) { + let pre: str = "w6c: import "; + let mid: str = ": export owner mismatch in "; + let nl: str = "\n"; + os.write(2, pre.ptr, pre.len: u64); + os.write(2, importpaths[importi], cstrlen(importpaths[importi])); + os.write(2, mid.ptr, mid.len: u64); + os.write(2, importfiles[importi], cstrlen(importfiles[importi])); + os.write(2, nl.ptr, nl.len: u64); + return 1; + }; let il: lex; lexinit(&il, strings.dup(pathstr(importfiles[importi])), ibuf, ilen); let ips: parser; diff --git a/selfhost/cmd/wcc/wwi.ww b/selfhost/cmd/wcc/wwi.ww index 7a439b55..5ebf71ac 100644 --- a/selfhost/cmd/wcc/wwi.ww +++ b/selfhost/cmd/wcc/wwi.ww @@ -1017,6 +1017,9 @@ fn wwiemit(c: *checker, file: *syntax.node, path: str) i32 = { pkg = file.nmod; }; }; + if (file.nmod.len > 0) { + wputs(fd, "//ww:module "); wputs(fd, file.nmod); wputs(fd, "\n"); + }; wputs(fd, "package "); wputs(fd, pkg); wputs(fd, ";\n"); diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index ba667e31..e73b5281 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -13,6 +13,8 @@ package main; +import crypto.sha256; +import hash; import os; import os.exec; import rt; @@ -137,8 +139,9 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { }; fn joinpath(dir: *u8, name: *u8) *u8 = { - let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; - buf.len = os.PATH_MAX; + let need: u64 = cstrlen(dir) + 1u64 + cstrlen(name) + 1u64; + let buf: []u8 = alloc([], need)!; + buf.len = need: i32; let off: u64 = cstrinto(buf.ptr, 0u64, dir); off = byteinto(buf.ptr, off, 47u8); off = cstrinto(buf.ptr, off, name); @@ -147,8 +150,9 @@ fn joinpath(dir: *u8, name: *u8) *u8 = { }; fn joinpathlit(dir: *u8, name: str) *u8 = { - let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; - buf.len = os.PATH_MAX; + let need: u64 = cstrlen(dir) + 1u64 + name.len: u64 + 1u64; + let buf: []u8 = alloc([], need)!; + buf.len = need: i32; let off: u64 = cstrinto(buf.ptr, 0u64, dir); off = byteinto(buf.ptr, off, 47u8); off = strinto(buf.ptr, off, name); @@ -495,7 +499,6 @@ def SEP_ROLE_GENERATED_MAIN: i32 = 2; def SEP_TEST_SUPPORT_MODULE: str = "__wwtest"; def SEP_MAXPRODUCT: i32 = 256; def SEP_MAXCONTEXT: i32 = 257; -def SEP_ARTIFACT_MAX: i32 = 1024; // Classify a selected directory entry: 1 production, 2 test, 0 skipped, // -1 @test outside *_test.ww, -2 non-regular source. @@ -558,11 +561,6 @@ fn dirpackagename(path: *u8) *u8 = { cerr(": error: invalid or missing package clause\n"); return nil; }; - if (imports.nmod.len >= 256) { - cerrpos(imports.file, imports.line, imports.col); - cerr(": error: package name is too long\n"); - return nil; - }; return arenadupcstr(imports.nmod.ptr, imports.nmod.len: u64); }; @@ -759,8 +757,9 @@ fn makestem(stem: *u8, src: *u8) void = { }; fn appendlit(stem: *u8, suffix: str) *u8 = { - let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; - buf.len = os.PATH_MAX; + let need: u64 = cstrlen(stem) + suffix.len: u64 + 1u64; + let buf: []u8 = alloc([], need)!; + buf.len = need: i32; let off: u64 = cstrinto(buf.ptr, 0u64, stem); off = strinto(buf.ptr, off, suffix); cstrseal(buf.ptr, off); @@ -804,6 +803,8 @@ type seppkg = struct { entry: *u8, // resolved package dir (or file, file root), NUL-term canon: *u8, // canonical location; never package identity artifact: *u8, // stable non-importable variant artifact key + storage: *u8, // internal storage basename; never package identity + storagehashed: bool, name: *u8, // validated declared name; directory packages only testpackage: *u8, sources: **u8, // owned, byte-sorted selected paths; dirs only @@ -930,10 +931,7 @@ fn sepcommandcompilermarker(g: *sepgraph, pi: i32) bool = { // identity. The compiler path is derived from that base and the semantic // variant; root/product/artifact state never participates. fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = { - if (base == nil || base[0u64] == 0u8 || cstrlen(base) >= 256u64) { - cerr("ww: package path is too long (limit 255 bytes)\n"); - return -1; - }; + if (base == nil || base[0u64] == 0u8) { return -1; }; if (!reservedimportpath(base) && !sepimportbasevalid(base)) { cerr("ww: invalid package path "); cerr(pathstr(base)); cerr("\n"); return -1; @@ -992,10 +990,6 @@ fn sepbindimportbase(g: *sepgraph, pi: i32, base: *u8) i32 = { fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, isdir: i32, variant: i32, testpackage: *u8, role: i32, artifact: *u8, root: bool) i32 = { - if (cstrlen(path) >= 256u64) { - cerr("ww: package path is too long (limit 255 bytes)\n"); - return -1; - }; let canon: *u8 = nil; if (isdir != 0) { canon = canonicaldir(pathstr(entry)); @@ -1004,10 +998,6 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, cerr(pathstr(entry)); cerr("\n"); return -1; }; - if (cstrlen(canon) >= 1024u64) { - cerr("ww: canonical package path is too long\n"); - return -1; - }; }; let incoming: *u8 = nil; if (isdir != 0 && path[0u64] != 0u8) { @@ -1094,6 +1084,8 @@ fn sepfindoraddvariant(g: *sepgraph, path: *u8, entry: *u8, g.pkg[g.n].entry = arenadupcstr(entry, elen); g.pkg[g.n].canon = canon; g.pkg[g.n].artifact = nil; + g.pkg[g.n].storage = nil; + g.pkg[g.n].storagehashed = false; g.pkg[g.n].name = nil; g.pkg[g.n].testpackage = nil; if (testpackage != nil) { @@ -1186,17 +1178,12 @@ fn sepgraphfree(g: *sepgraph) void = { fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8, toolsrcdir: *u8) i32 = { - let cap: u64 = (os.PATH_MAX: u64) * 2u64; let need: u64 = cstrlen(root) + 1u64 + cstrlen(toolsrcdir) + 1u64; if (incs != nil && incs[0u64] != 0u8) { need += cstrlen(incs) + 1u64; }; - if (need > cap) { - cerr("ww: package import search path is too long\n"); - return -1; - }; - let search: []u8 = alloc([], cap)!; - search.len = cap: i32; + let search: []u8 = alloc([], need)!; + search.len = need: i32; let off: u64 = cstrinto(search.ptr, 0u64, root); off = byteinto(search.ptr, off, 58u8); if (incs != nil && incs[0u64] != 0u8) { @@ -1221,59 +1208,142 @@ fn sepcontextfor(g: *sepgraph, root: *u8, incs: *u8, return result; }; -// Build one artifact path. Test roots use distinct names even though both -// compiler units reset to the bare executable namespace. +def SEP_NAME_MAX: u64 = 255u64; + +fn sepstoragedigest(p: *seppkg) *u8 = { + let state: sha256.state = sha256.sha256(); + let h: *hash.hash = (&state): *hash.hash; + hash.write(h, strings.toutf8("ww-package-storage-v2:")); + let tag: [4]u8; + tag[0] = ('0': i32 + p.variant): u8; + tag[1] = ':': u8; + tag[2] = ('0': i32 + p.role): u8; + tag[3] = ':': u8; + hash.write(h, tag[0:4]); + hash.write(h, strings.toutf8(pathstr(p.path))); + let zero: [1]u8; + zero[0] = 0u8; + hash.write(h, zero[0:1]); + hash.write(h, strings.toutf8(pathstr(p.canon))); + let digest: [32]u8; + hash.sum(h, digest[0:32]); + let need: u64 = "__wwpkg.v".len: u64 + 1u64 + ".r".len: u64 + + 1u64 + ".h".len: u64 + 64u64 + 1u64; + let out: []u8 = alloc([], need)!; + out.len = need: i32; + let off: u64 = strinto(out.ptr, 0u64, "__wwpkg.v"); + off = byteinto(out.ptr, off, tag[0]); + off = strinto(out.ptr, off, ".r"); + off = byteinto(out.ptr, off, tag[2]); + off = strinto(out.ptr, off, ".h"); + let hex: str = "0123456789abcdef"; + let i: i32 = 0; + for (i < 32) { + let high: i32 = (digest[i] / 16u8): i32; + let low: i32 = (digest[i] % 16u8): i32; + off = byteinto(out.ptr, off, hex[high]); + off = byteinto(out.ptr, off, hex[low]); + i += 1; + }; + cstrseal(out.ptr, off); + return out.ptr; +}; + +fn seplegacyartifact(p: *seppkg) *u8 = { + if (p.artifact != nil && p.artifact[0u64] != 0u8) { return p.artifact; }; + if (p.path != nil && p.path[0u64] != 0u8) { return p.path; }; + return "__root\0".ptr; +}; + +fn sepvalidatestoragepath(p: *seppkg, scratch: *u8) i32 = { + let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(p.storage) + + ".unit.new".len: u64 + 1u64; + if (cstrlen(p.storage) + ".unit.new".len: u64 > SEP_NAME_MAX + || need > os.PATH_MAX: u64) { + cerr("ww: package artifact path is too long\n"); + return -1; + }; + return 0; +}; + +fn sepassignstorage(p: *seppkg, scratch: *u8) i32 = { + let base: *u8 = seplegacyartifact(p); + let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(base) + + ".unit.new".len: u64 + 1u64; + if (cstrlen(base) + ".unit.new".len: u64 <= SEP_NAME_MAX + && need <= os.PATH_MAX: u64) { + p.storage = arenadupcstr(base, cstrlen(base)); + p.storagehashed = false; + } else { + p.storage = sepstoragedigest(p); + p.storagehashed = true; + }; + return sepvalidatestoragepath(p, scratch); +}; + fn sepfname(g: *sepgraph, pi: i32, scratch: *u8, suffix: str) *u8 = { - let buf: []u8 = alloc([], SEP_ARTIFACT_MAX: u64)!; - buf.len = SEP_ARTIFACT_MAX; + let need: u64 = cstrlen(scratch) + 1u64 + + cstrlen(g.pkg[pi].storage) + suffix.len: u64 + 1u64; + let buf: []u8 = alloc([], need)!; + buf.len = need: i32; let off: u64 = cstrinto(buf.ptr, 0u64, scratch); off = byteinto(buf.ptr, off, 47u8); // '/' - if (g.pkg[pi].artifact != nil) { - off = cstrinto(buf.ptr, off, g.pkg[pi].artifact); - } else { if (g.pkg[pi].path[0u64] != 0u8) { - off = cstrinto(buf.ptr, off, g.pkg[pi].path); - } else { - off = strinto(buf.ptr, off, "__root"); - }; }; + off = cstrinto(buf.ptr, off, g.pkg[pi].storage); off = strinto(buf.ptr, off, suffix); cstrseal(buf.ptr, off); return buf.ptr; }; -// sepfname uses SEP_ARTIFACT_MAX storage. Validate the longest suffix once -// before opening files so two action identities can never alias by truncation. fn sepvalidateartifactpaths(g: *sepgraph, scratch: *u8) i32 = { let i: i32 = 0; for (i < g.n) { if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; }; - let base: *u8 = g.pkg[i].artifact; - if (base == nil) { - base = g.pkg[i].path; - if (base[0u64] == 0u8) { base = "__root\0".ptr; }; - }; - let need: u64 = cstrlen(scratch) + 1u64 + cstrlen(base) - + ".unit.new".len: u64 + 1u64; - if (need > SEP_ARTIFACT_MAX: u64) { - cerr("ww: package artifact path is too long\n"); - return -1; - }; - let j: i32 = i + 1; - for (j < g.n) { - if (g.pkg[j].failed || !g.pkg[j].loaded) { j += 1; continue; }; - let other: *u8 = g.pkg[j].artifact; - if (other == nil) { - other = g.pkg[j].path; - if (other[0u64] == 0u8) { other = "__root\0".ptr; }; - }; - if (cstreq(base, other)) { - cerr("ww: package actions share artifact identity "); - cerr(pathstr(base)); cerr("\n"); - return -1; - }; - j += 1; - }; + if (g.pkg[i].storage == nil + && sepassignstorage(&g.pkg[i], scratch) < 0) { return -1; }; i += 1; }; + let changed: bool = true; + for (changed) { + changed = false; + i = 0; + for (i < g.n && !changed) { + if (g.pkg[i].failed || !g.pkg[i].loaded) { i += 1; continue; }; + let j: i32 = i + 1; + for (j < g.n) { + if (g.pkg[j].failed || !g.pkg[j].loaded + || !cstreq(g.pkg[i].storage, g.pkg[j].storage)) { + j += 1; continue; + }; + if (!g.pkg[i].storagehashed || !g.pkg[j].storagehashed) { + if (!g.pkg[i].storagehashed) { + g.pkg[i].storage = sepstoragedigest(&g.pkg[i]); + g.pkg[i].storagehashed = true; + if (sepvalidatestoragepath(&g.pkg[i], scratch) < 0) { + return -1; + }; + }; + if (!g.pkg[j].storagehashed) { + g.pkg[j].storage = sepstoragedigest(&g.pkg[j]); + g.pkg[j].storagehashed = true; + if (sepvalidatestoragepath(&g.pkg[j], scratch) < 0) { + return -1; + }; + }; + changed = true; + j = g.n; + } else { + cerr("ww: package storage collision for "); + cerr(pathstr(g.pkg[i].path)); cerr(" in "); + cerr(pathstr(g.pkg[i].canon)); cerr(" and "); + cerr(pathstr(g.pkg[j].path)); cerr(" in "); + cerr(pathstr(g.pkg[j].canon)); cerr(" at "); + cerr(pathstr(g.pkg[i].storage)); cerr("\n"); + return -1; + }; + }; + i += 1; + }; + }; return 0; }; @@ -1384,11 +1454,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, && (ownedsource != 0 || g.pkg[pi].name == nil)) { let declared: *u8 = imports.nmod.ptr; let declaredn: u64 = imports.nmod.len: u64; - if (declaredn >= 256u64) { - cerrpos(imports.file, imports.line, imports.col); - cerr(": error: package name is too long\n"); - return -1; - }; if (g.pkg[pi].name == nil) { g.pkg[pi].name = arenadupcstr(declared, declaredn); } else { if (bytecmp(g.pkg[pi].name, cstrlen(g.pkg[pi].name), @@ -1465,11 +1530,6 @@ fn sepscanfile(g: *sepgraph, pi: i32, file: *u8, searchpath: *u8, cerr(u.usepath); cerr(" is reserved\n"); return -1; }; - if (idn >= 256u64) { - cerrpos(u.file, u.line, u.col); - cerr(": error: import path is too long (limit 255 bytes)\n"); - return -1; - }; let externalproduction: bool = false; let ipath: *u8 = nil; if (g.pkg[pi].variant == SEP_VARIANT_EXTERNAL @@ -1638,6 +1698,8 @@ fn sepaddgeneratedmain(g: *sepgraph, product: *sepproduct, ordinal: i32, let variantartifact: *u8 = g.pkg[variant].artifact; if (variantartifact == nil) { variantartifact = g.pkg[variant].path; }; p.artifact = appendlit(variantartifact, "-main"); + p.storage = nil; + p.storagehashed = false; p.name = arenadupcstr("main\0".ptr, 4u64); p.testpackage = nil; p.sources = nil; @@ -1888,10 +1950,7 @@ fn sepreverseimportbase(g: *sepgraph, p: *seppkg, context: i32, if (rel != nil) { let converted: i32 = sepimportpathfromrelative(rel, out, outsz); - if (converted < 0) { - cerr("ww: package path is too long (limit 255 bytes)\n"); - return -1; - }; + if (converted < 0) { return -1; }; if (converted > 0 && reservedimportpath(out)) { converted = 0; }; @@ -1981,12 +2040,14 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = { let ci: i32 = 0; for (ci < g.ncontext) { if (p.contextstate[ci] == 2u8) { - let candidate: [256]u8; + let candidatesz: u64 = cstrlen(p.canon) + 1u64; + let candidate: []u8 = alloc([], candidatesz)!; + candidate.len = candidatesz: i32; let found: i32 = sepreverseimportbase(g, p, ci, - &candidate[0], 256u64); + candidate.ptr, candidatesz); if (found < 0) { return -1; }; if (found > 0 && sepbindimportbase(g, pi, - &candidate[0]) < 0) { return -1; }; + candidate.ptr) < 0) { return -1; }; }; ci += 1; }; @@ -2014,10 +2075,7 @@ fn sepfinalizedirectoryidentities(g: *sepgraph) i32 = { }; if (base == nil) { base = seplocalimportbase(p); - if (base == nil || cstrlen(base) >= 256u64) { - cerr("ww: local package identity is too long (limit 255 bytes)\n"); - return -1; - }; + if (base == nil) { return -1; }; }; if (sepbindimportbase(g, pi, base) < 0) { return -1; }; }; @@ -2353,6 +2411,70 @@ fn filesizenonzero(path: *u8) bool = { return ok; }; +fn sepvalidateunitowner(g: *sepgraph, pi: i32, scratch: *u8) i32 = { + let unit: *u8 = sepfname(g, pi, scratch, ".unit.ww"); + let fi: os.filestat; + match (os.lstat(&fi, pathstr(unit))) { + case let e: os.oserror => { + if ((e: i64) == -2i64) { return 0; }; + cerr("ww: package storage owner mismatch at "); + cerr(pathstr(g.pkg[pi].storage)); cerr(" for "); + if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); } + else { cerr(pathstr(g.pkg[pi].path)); }; + cerr("\n"); + return -1; + }; + case void => void; + }; + let typ: u32 = (fi.mode: u32) & 61440u32; + if (typ != os.mode.REG: u32) { + cerr("ww: package storage owner mismatch at "); + cerr(pathstr(g.pkg[pi].storage)); cerr(" for "); + if (g.pkg[pi].path[0u64] == 0u8) { cerr("(root)"); } + else { cerr(pathstr(g.pkg[pi].path)); }; + cerr("\n"); + return -1; + }; + let buf: *u8; + let n: u64; + buf, n = slurp(unit); + let prefix: str = "//ww:module-reset"; + let pathn: u64 = cstrlen(g.pkg[pi].path); + let want: u64 = prefix.len: u64 + 1u64; + if (pathn > 0u64) { want += 1u64 + pathn; }; + let matches: bool = buf != nil && n >= want + && bytecmp(buf, prefix.len: u64, prefix.ptr, + prefix.len: u64) == 0; + if (matches) { + let off: u64 = prefix.len: u64; + if (pathn == 0u64) { + matches = buf[off] == ('\n': u8); + } else { + matches = buf[off] == (' ': u8) + && bytecmp(buf + off + 1u64, pathn, + g.pkg[pi].path, pathn) == 0 + && buf[off + 1u64 + pathn] == ('\n': u8); + }; + }; + if (matches) { return 1; }; + cerr("ww: package storage owner mismatch at "); + cerr(pathstr(g.pkg[pi].storage)); cerr(" for "); + if (pathn == 0u64) { cerr("(root)"); } + else { cerr(pathstr(g.pkg[pi].path)); }; + cerr("\n"); + return -1; +}; + +fn sepvalidateworkdirowners(g: *sepgraph, scratch: *u8) i32 = { + let i: i32 = 0; + for (i < g.n) { + if (!g.pkg[i].failed && g.pkg[i].loaded + && sepvalidateunitowner(g, i, scratch) < 0) { return -1; }; + i += 1; + }; + return 0; +}; + // Byte equality of two files; absence or IO error is inequality. fn fileequal(a: *u8, b: *u8) bool = { let fa: i32 = os.open(pathstr(a), os.flag.RDONLY, 0i32); @@ -2423,14 +2545,14 @@ fn copyfileatomic(src: *u8, dst: *u8) i32 = { fn workdirstamptext(istest: i32, emitasm: i32) str = { if (istest != 0) { if (emitasm != 0) { - return "ww workdir fmt 9 mode test asm 1\n"; + return "ww workdir fmt 11 mode test asm 1\n"; }; - return "ww workdir fmt 9 mode test asm 0\n"; + return "ww workdir fmt 11 mode test asm 0\n"; }; if (emitasm != 0) { - return "ww workdir fmt 8 mode build asm 1\n"; + return "ww workdir fmt 10 mode build asm 1\n"; }; - return "ww workdir fmt 8 mode build asm 0\n"; + return "ww workdir fmt 10 mode build asm 0\n"; }; fn stampmatches(path: *u8, want: str) bool = { @@ -2595,20 +2717,18 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; }; - let stem: []u8 = alloc([], (os.PATH_MAX: u64))!; - stem.len = os.PATH_MAX; + let stem: *u8 = nil; if (entryisdir != 0) { let dlen: u64 = cstrlen(srcd.ptr); let bo: u64 = basenameoff(srcd.ptr, dlen); - let off: u64 = cstrinto(stem.ptr, 0u64, srcd.ptr); - stem[off] = 47u8; off += 1u64; // '/' - let i: u64 = bo; - for (i < dlen) { stem[off] = srcd[i]; off += 1u64; i += 1u64; }; - cstrseal(stem.ptr, off); + stem = joinpath(srcd.ptr, srcd.ptr + bo); } else { - makestem(stem.ptr, src); + let stembuf: []u8 = alloc([], cstrlen(src) + 1u64)!; + stembuf.len = (cstrlen(src) + 1u64): i32; + makestem(stembuf.ptr, src); + stem = stembuf.ptr; }; - let effstem: *u8 = stem.ptr; + let effstem: *u8 = stem; if (objstem != nil) { effstem = objstem; }; let warm: bool = false; if (workdir != nil) { @@ -2675,7 +2795,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, if (!fileequal(toola, a6)) { staleall = true; }; }; }; - if (staleall && invalidateworkdirunits(scratch) != 0) { return 1; }; }; let rtpaths: []*u8 = alloc([], 2u64)!; @@ -2882,6 +3001,8 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, }; }; if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; }; + if (warm && sepvalidateworkdirowners(g, scratch) < 0) { return 1; }; + if (warm && staleall && invalidateworkdirunits(scratch) != 0) { return 1; }; let rootpackage: bool = packageonly != 0; if (rootpackage && !g.pkg[products[0].root].failed && cstreqlit(g.pkg[products[0].root].name, "main")) { @@ -3534,8 +3655,14 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let workdir: *u8 = nil; // -w persistent package-artifact workdir let emitasm: i32 = 0; let packageonly: i32 = 0; - let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; - incs.len = ((os.PATH_MAX: u64) * 2u64): i32; + let inccap: u64 = 1u64; + let capi: i32 = start; + for (capi < argc) { + inccap += cstrlen(argv[capi]) + 1u64; + capi += 1; + }; + let incs: []u8 = alloc([], inccap)!; + incs.len = inccap: i32; let incoff: u64 = 0u64; cstrseal(incs.ptr, 0u64); @@ -3739,8 +3866,14 @@ fn makedrivertmp(buf: *u8, prefix: str) void = { fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let src: *u8 = nil; let passstart: i32 = -1; // first argv idx to pass through to program - let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; - incs.len = ((os.PATH_MAX: u64) * 2u64): i32; + let inccap: u64 = 1u64; + let capi: i32 = start; + for (capi < argc) { + inccap += cstrlen(argv[capi]) + 1u64; + capi += 1; + }; + let incs: []u8 = alloc([], inccap)!; + incs.len = inccap: i32; let incoff: u64 = 0u64; cstrseal(incs.ptr, 0u64); @@ -4038,8 +4171,14 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { // to the test binary as argv[1] (single-file/module only; dir-mode // rejects). cstage twin: do_test `pattern`. let patarg: *u8 = nil; - let incs: []u8 = alloc([], (os.PATH_MAX: u64) * 2u64)!; - incs.len = ((os.PATH_MAX: u64) * 2u64): i32; + let inccap: u64 = 1u64; + let capi: i32 = start; + for (capi < argc) { + inccap += cstrlen(argv[capi]) + 1u64; + capi += 1; + }; + let incs: []u8 = alloc([], inccap)!; + incs.len = inccap: i32; let incoff: u64 = 0u64; cstrseal(incs.ptr, 0u64); @@ -4093,7 +4232,7 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { if ((!cstreqlit(kind, "production") && !cstreqlit(kind, "same") && !cstreqlit(kind, "external")) - || pn == 0u64 || pn >= 256u64 + || pn == 0u64 || dir[0u64] == 0u8 || output[0u64] == 0u8 || status[0u64] == 0u8 || (cstreqlit(kind, "external")