ww build: honor Go output permission modes

This commit is contained in:
2026-08-20 22:03:09 +09:00
parent 77168fd891
commit 84f5e68709
8 changed files with 513 additions and 9 deletions

View File

@@ -6308,6 +6308,103 @@ residue. Existing package tests continue to own all action/test variants,
graph identity, output ordering, cwd/environment/stdin, timeout, and broader
transaction behavior.
### 11.28 Implemented Go-like build-output permissions
Newly published build outputs now use Go 1.26.5's output-kind permission and
caller-umask contract. An ordinary linked command starts from `0777`; a
non-link archive starts from `0666`. The kernel filters either base permission
through the invoking process's umask when the request-private publication inode
is created. WW's required adjacent interface sidecar is data like its archive
and uses the same `0666` base. Assembly-only builds create neither kind of
public output.
#### Pinned Go evidence and direct pre-fix measurements
The authority is official Go 1.26.5 at commit
`c19862e5f8415b4f24b189d065ed739517c548ba`:
- `runBuild` routes explicit single and directory `-o` products through
`ModeInstall`, after deciding the caller-visible output path
([`cmd/go/internal/work/build.go`, lines 459558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L459-L558)).
- `BuildInstallFunc` begins with permission `0666`, changes it to `0777` for an
ordinary link action, creates the output parent, and gives that permission to
`moveOrCopyFile`
([`cmd/go/internal/work/exec.go`, lines 19042000](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1904-L2000)).
- On its rename path, `moveOrCopyFile` creates a destination-adjacent dummy with
the requested permission, observes the caller-filtered mode, removes the
dummy, applies that mode to the linked source, and renames it. Its copy
fallback creates the destination with the requested permission and therefore
receives the same kernel filtering
([`cmd/go/internal/work/shell.go`, lines 119220](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/shell.go#L119-L220)).
- Official `build_output.txt` asserts executable default, explicit-file,
nested-file, trailing-directory, and existing-directory command outputs
([lines 744](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_output.txt#L7-L44)).
`build_multi_main.txt` exercises directory fan-out for two main packages and
a local command-line package
([lines 116](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/build_multi_main.txt#L1-L16)).
The testdata anchors directly specify that the public command routes produce
executables. The exact `0777`/`0666` bases and caller filtering are implemented
by the pinned source. Therefore the resulting mode formula is source-derived;
it is not an observation of the installed host Go toolchain.
Before this slice, fresh public-driver measurements of one byte-identical
command gave mode `0755` from both stages under umask `000`. Under umask `077`,
Cstage still gave `0755` while WWstage gave `0700`. The C linker created through
`fopen` and unconditionally applied `chmod(0755)` after emission; the WW linker
created with base `0755`. Thus both discarded permitted group/other write bits,
and Cstage additionally reintroduced bits forbidden by a restrictive mask.
The non-link branch had a separate stage mismatch under the same official rule.
With umask `000`, Cstage published a byte-identical archive/interface pair as
`0666` while WWstage published it as `0644`; both became `0600` under umask
`077`. Cstage's fresh `fopen` data stage already had base `0666`, while
WWstage's data-copy stage explicitly used base `0644`.
#### Ownership, publication, and identity boundaries
The C and WW linkers now open every fresh linked output with base `0777`. The C
linker emits through the resulting descriptor instead of applying a fixed mode
afterward; the WW linker uses the same creation base. The WWstage driver's
archive/interface copy now uses `0666`, matching Cstage's existing data-file
creation. No driver independently reads or stores a umask.
For public build routes, the coordinator has already rejected an occupied or
dangling output `.new` before the selected linker or copy owner opens the
request-private stage. Creation therefore receives the current child process's
umask exactly once. The established transaction renames that same inode to the
caller-visible destination, so neither the final name nor replacement of an old
destination changes its mode. A failed compiler, assembler, archiver, linker,
stage, or installation preserves the old destination's bytes and mode and
removes all request stages. Directory fan-out gives each independent command
the same request-local rule; concurrent driver processes retain independent
umasks and publication paths.
Permission bits are presentation metadata, not semantic inputs. Package and
action identity, graph edges, declared names, physical directory metadata,
compiler/assembler/archive/link argv, `.wwi` contents, artifact bytes, and
persistence keys are unchanged. An unchanged warm request may reuse every
semantic action but still relinks or copies the requested public product so its
mode reflects the current invocation. Source invalidation changes the applicable
artifact bytes without changing the formula. Retained test-binary copying
remains a distinct output-policy path and already uses the same `0777` linked-
executable rule. No Go-style dummy is needed and no `-go-tmp-umask` residue is
created because WW links or copies directly into its already-private fresh
stage.
No persisted byte schema changed. Build workdir format remains `18`, test
workdir format remains `19`, and semantic storage remains `3`.
The WW-native owner `build_output_permissions_follow_umask` uses a test-only
exec launcher to arrange exact process umasks. It covers both Cstage and
WWstage; cold, warm, and invalidated persistent builds; explicit, default,
raw-file, and multi-command directory outputs; `0777`, `0700`, `0750`, and
`0770` command results; `0666` and `0600` archive/interface results;
assembly-only omission; retained test-binary non-regression; direct execution;
injected late-link request rollback over old files and modes; occupied-stage
rejection; simultaneous builds with different umasks; diagnostic parity;
artifact and binary byte identity; and absence of `.new` or umask-probe residue.
## 12. Candidate architectures and hard-gate decision
Five candidates were developed as coherent systems, not as feature bins.

View File

@@ -310,6 +310,15 @@ ImportPath = ident { "." ident } .
`fn main`; path and directory spelling do not classify commands. An ordinary
import of a package declared `main` is rejected, except for the toolchain's
colocated external-test wiring.
- A newly published `ww build` command is created with permission `0777`
filtered by the invoking process's umask. A newly published non-command
archive, and the adjacent WW interface required to consume it, use `0666`
filtered by that same umask. These inode permissions are output metadata:
they do not enter canonical package or action identity, artifact bytes,
import binding, symbols, `.wwi` contents, or persistent invalidation. A warm
build therefore reuses unchanged semantic actions while refreshing the
caller-visible output with the current invocation's mode. Assembly-only
builds publish no executable or archive.
- Only names marked `export` (§5) are visible across module boundaries.
Import paths remain unquoted and dotted. Grouped imports, quoted import paths,