ww: implement package initialization

This commit is contained in:
2026-08-14 19:02:35 +09:00
parent 72e890d5aa
commit c6bec0914d
24 changed files with 5279 additions and 1009 deletions

View File

@@ -1,7 +1,167 @@
#include "gc.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
struct publication {
const char *dst;
char *stage;
char *backup;
int had_old;
int installed;
};
static char *
publish_path(const char *dst, const char *kind)
{
int n = snprintf(NULL, 0, "%s.w6c.%ld.%s", dst, (long)getpid(), kind);
if (n < 0 || (size_t)n == (size_t)-1) return NULL;
char *p = malloc((size_t)n + 1);
if (p == NULL) return NULL;
if (snprintf(p, (size_t)n + 1, "%s.w6c.%ld.%s", dst,
(long)getpid(), kind) != n) {
free(p);
return NULL;
}
return p;
}
static int
materialize_stream(FILE *src, struct publication *p)
{
p->stage = publish_path(p->dst, "new");
p->backup = publish_path(p->dst, "old");
if (p->stage == NULL || p->backup == NULL) {
fputs("w6c: out of memory\n", stderr);
return -1;
}
int fd = open(p->stage, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd < 0) {
fprintf(stderr, "w6c: cannot stage %s\n", p->dst);
return -1;
}
FILE *out = fdopen(fd, "wb");
if (out == NULL) {
close(fd);
unlink(p->stage);
return -1;
}
int bad = fflush(src) != 0 || fseek(src, 0, SEEK_SET) != 0;
unsigned char buf[65536];
while (!bad) {
size_t n = fread(buf, 1, sizeof buf, src);
if (n != 0 && fwrite(buf, 1, n, out) != n) bad = 1;
if (n < sizeof buf) {
if (ferror(src)) bad = 1;
break;
}
}
if (fclose(out) != 0) bad = 1;
if (bad) {
unlink(p->stage);
fprintf(stderr, "w6c: cannot stage %s\n", p->dst);
return -1;
}
return 0;
}
static void
publication_free(struct publication *p)
{
free(p->backup);
free(p->stage);
}
/* A publication rollback name is occupied by any terminal directory entry,
* including a dangling symlink. Never follow it while deciding whether the
* compiler may park an existing destination there. */
static int
path_exists_nofollow(const char *path)
{
struct stat st;
if (lstat(path, &st) == 0) return 1;
return errno == ENOENT ? 0 : -1;
}
static int
path_is_regular_nofollow(const char *path)
{
struct stat st;
return lstat(path, &st) == 0 && S_ISREG(st.st_mode);
}
/* Publish assembly and interface as one rollback group. Named output bytes
* never change before checking and lowering have both succeeded. */
static int
publish_all(struct publication *p, int n)
{
for (int i = 0; i < n; i++) {
if (!path_is_regular_nofollow(p[i].stage)) {
fprintf(stderr, "w6c: publication stage is not a regular file for %s\n",
p[i].dst);
goto rollback;
}
struct stat st;
if (lstat(p[i].dst, &st) == 0) {
if (!S_ISREG(st.st_mode)) {
fprintf(stderr,
"w6c: publication destination is not a regular file: %s\n",
p[i].dst);
goto rollback;
}
} else if (errno != ENOENT) {
fprintf(stderr, "w6c: cannot inspect publication destination %s\n",
p[i].dst);
goto rollback;
}
}
for (int i = 0; i < n; i++) {
int exists = path_exists_nofollow(p[i].backup);
if (exists != 0) {
if (exists < 0)
fprintf(stderr, "w6c: cannot inspect publication backup for %s\n",
p[i].dst);
else
fprintf(stderr, "w6c: publication backup exists for %s\n",
p[i].dst);
goto rollback;
}
}
for (int i = 0; i < n; i++) {
if (rename(p[i].dst, p[i].backup) == 0)
p[i].had_old = 1;
else if (errno != ENOENT) {
fprintf(stderr, "w6c: cannot preserve %s\n", p[i].dst);
goto rollback;
}
}
for (int i = 0; i < n; i++) {
if (rename(p[i].stage, p[i].dst) != 0) {
fprintf(stderr, "w6c: cannot publish %s\n", p[i].dst);
goto rollback;
}
p[i].installed = 1;
}
/* Installation is the commit point. Backup cleanup cannot truthfully
* turn a completely installed pair into a rejected compilation. */
for (int i = 0; i < n; i++)
if (p[i].had_old && unlink(p[i].backup) != 0) {
fprintf(stderr, "w6c: cannot remove backup for %s\n", p[i].dst);
}
return 0;
rollback:
for (int i = n - 1; i >= 0; i--) {
if (p[i].installed) (void)unlink(p[i].dst);
if (p[i].had_old) (void)rename(p[i].backup, p[i].dst);
if (p[i].stage != NULL) (void)unlink(p[i].stage);
}
return -1;
}
static int
slurp(const char *path, char **outbuf, u64 *outlen)
@@ -148,8 +308,10 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
}
if (name != NULL) {
u->usepkgname = name;
u->str = u->usealias ? u->usealias : name;
u->strlen = strlen(u->str);
if (!u->useblank) {
u->str = u->usealias ? u->usealias : name;
u->strlen = strlen(u->str);
}
} else if (!u->imported) {
fprintf(stderr,
"w6c: import %s has no declared package name in direct export data\n",
@@ -159,8 +321,10 @@ bind_import_names(Node *list, struct importin *imports, int nimports,
/* A closure-only import need not contribute declarations to this
* interface. Keep it canonical-path keyed without reinstalling
* the historical path-leaf qualifier. */
u->str = u->usealias ? u->usealias : u->usepath;
u->strlen = strlen(u->str);
if (!u->useblank) {
u->str = u->usealias ? u->usealias : u->usepath;
u->strlen = strlen(u->str);
}
}
}
return 0;
@@ -184,6 +348,8 @@ main(int argc, char **argv)
const char *wwiout = NULL; /* -I <out.wwi>: M2 export-data producer */
const char *testsupport = NULL;
const char *testtarget = NULL;
const char *packageinit = NULL;
const char *initdispatch = NULL;
int testmode = 0;
int testpackage = 0;
int commandpackage = 0;
@@ -215,6 +381,18 @@ main(int argc, char **argv)
commandpackage = 1;
} else if (strcmp(a, "--entry") == 0) {
entrymode = 1;
} else if (strcmp(a, "--package-init-symbol") == 0) {
if (i + 1 >= argc) {
fputs("w6c: --package-init-symbol requires arg\n", stderr);
return 2;
}
packageinit = argv[++i];
} else if (strcmp(a, "--init-dispatch-symbol") == 0) {
if (i + 1 >= argc) {
fputs("w6c: --init-dispatch-symbol requires arg\n", stderr);
return 2;
}
initdispatch = argv[++i];
} else if (strcmp(a, "--test-support-module") == 0) {
if (i + 1 >= argc) {
fputs("w6c: --test-support-module requires arg\n", stderr);
@@ -256,10 +434,14 @@ main(int argc, char **argv)
}
}
if (src == NULL) {
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--test-target-package path] [-c] [-I out.wwi] "
fputs("usage: w6c [-T|--test-package] [--command-package] [--entry] [--package-init-symbol symbol] [--init-dispatch-symbol symbol] [--test-target-package path] [-c] [-I out.wwi] "
"[--import path dep.wwi]... [--import-map source path]... [-o out.s] file.ww\n", stderr);
return 2;
}
if (out != NULL && wwiout != NULL && strcmp(out, wwiout) == 0) {
fputs("w6c: assembly and interface outputs must be distinct\n", stderr);
return 2;
}
if (nimports > 0 && !sepmode) {
fputs("w6c: --import requires -c\n", stderr);
return 2;
@@ -272,6 +454,19 @@ main(int argc, char **argv)
fputs("w6c: --entry, --test-package, and --command-package require -c\n", stderr);
return 2;
}
if ((packageinit != NULL || initdispatch != NULL) && !sepmode) {
fputs("w6c: package initialization symbols require -c\n", stderr);
return 2;
}
if (packageinit != NULL && packageinit[0] == '\0') {
fputs("w6c: --package-init-symbol is empty\n", stderr);
return 2;
}
if (initdispatch != NULL && (initdispatch[0] == '\0' || !entrymode)) {
fputs("w6c: --init-dispatch-symbol requires --entry and a non-empty symbol\n",
stderr);
return 2;
}
if (testmode && testpackage) {
fputs("w6c: -T and --test-package are mutually exclusive\n", stderr);
return 2;
@@ -427,27 +622,33 @@ main(int argc, char **argv)
if (testsupport != NULL) c.test_module = testsupport;
c.test_target = testtarget;
c.sep_mode = sepmode;
c.package_init_symbol = packageinit;
check_file(&c, file);
if (c.errs) return 1;
/* M2 export-data: write the `.wwi` after a clean check, before cgen.
* Dead on the live path (no existing invocation passes -I); the
* producer's check_exported_type may reject a dangling export. */
/* Anonymous streams keep every named destination untouched until checking,
* export writing, and lowering have all succeeded. A cgen fatal exits with
* only kernel-owned anonymous files open. */
FILE *wf = NULL;
if (wwiout) {
FILE *wf = fopen(wwiout, "wb");
wf = tmpfile();
if (wf == NULL) {
fprintf(stderr, "w6c: cannot open %s\n", wwiout);
fprintf(stderr, "w6c: cannot stage %s\n", wwiout);
return 1;
}
if (wwi_emit(&c, wf, file) != 0 || fflush(wf) != 0
|| ferror(wf)) {
fclose(wf);
return 1;
}
if (wwi_emit(&c, wf, file) != 0) { fclose(wf); return 1; }
fclose(wf);
}
FILE *of = stdout;
if (out) {
of = fopen(out, "wb");
of = tmpfile();
if (of == NULL) {
fprintf(stderr, "w6c: cannot open %s\n", out);
fprintf(stderr, "w6c: cannot stage %s\n", out);
if (wf != NULL) fclose(wf);
return 1;
}
}
@@ -458,13 +659,46 @@ main(int argc, char **argv)
* properties. Legacy raw invocations without -I remain entry-like; every
* driver package now supplies -I, and only link roots add --entry. */
cg.sep_isdep = (wwiout != NULL && !entrymode);
cg.init_dispatch_symbol = initdispatch;
cg_file(&cg, of, file);
if (of != stdout && (fflush(of) != 0 || ferror(of))) {
fclose(of);
if (wf != NULL) fclose(wf);
return 1;
}
struct publication pub[2] = {0};
int npub = 0;
if (wwiout != NULL) {
pub[npub].dst = wwiout;
if (materialize_stream(wf, &pub[npub]) < 0) goto publish_fail;
npub++;
}
if (out != NULL) {
pub[npub].dst = out;
if (materialize_stream(of, &pub[npub]) < 0) goto publish_fail;
npub++;
}
if (wf != NULL) fclose(wf);
if (of != stdout) fclose(of);
if (publish_all(pub, npub) < 0) goto publish_free_fail;
for (int i = 0; i < npub; i++) publication_free(&pub[i]);
freearena(a);
for (int i = 0; i < nimports; i++) free(imports[i].buf);
free(imports);
free(maps);
free(buf);
return 0;
publish_fail:
if (wf != NULL) fclose(wf);
if (of != stdout) fclose(of);
for (int i = 0; i < 2; i++) {
if (pub[i].stage != NULL) (void)unlink(pub[i].stage);
publication_free(&pub[i]);
}
return 1;
publish_free_fail:
for (int i = 0; i < npub; i++) publication_free(&pub[i]);
return 1;
}