Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dag/extdeps/exec/command.dag
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ fn argv_command(program: NonEmptyStr, arguments: List<String>) -> ArgvCommand
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "cat_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "rm_force_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "rm_recursive_force_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "ln_symbolic_force_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "timeout_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "printf_command"),
decl_ref(module_path: "extdeps.tools.gnu_coreutils", decl_name: "true_command"),
Expand Down
84 changes: 82 additions & 2 deletions dag/extdeps/linux/procfs.dag
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type ProcfsPath
| ProcNetUnix
| ProcSwaps
| ProcSysKernelHostname
| ProcNetPnp

fn procfs_path_literal(p: ProcfsPath) -> NonEmptyStr {
match p {
Expand All @@ -37,6 +38,7 @@ fn procfs_path_literal(p: ProcfsPath) -> NonEmptyStr {
ProcNetUnix => "/proc/net/unix"
ProcSwaps => "/proc/swaps"
ProcSysKernelHostname => "/proc/sys/kernel/hostname"
ProcNetPnp => "/proc/net/pnp"
}
}

Expand All @@ -48,6 +50,22 @@ fn procfs_path_literal(p: ProcfsPath) -> NonEmptyStr {
// majflt is field 12 of that line (1-based, counting pid and comm), the count of page
// faults that required reading a page back from disk — the process's own record of its
// resident pages having been evicted and refaulted.
// ProcNetPnp IS READ BY A GUEST THIS REPOSITORY BUILDS, NOT BY A PROCESS IT RUNS, and it is rostered
// here because the read is one this repository CAUSES. The kernel's IP autoconfiguration
// (Documentation/admin-guide/nfs/nfsroot.rst, "ip=" and the /proc/net/pnp section) exports the
// <dns0-ip> field of the ip= boot directive to /proc/net/pnp under the prefix "nameserver ", in a
// form DNS resolvers recognise, and states that /etc/resolv.conf is linked to it on such systems.
// gunbc.runner_guest_image installs exactly that link, so every resolver library in a booted guest
// reads this path. The file is synthesised by the kernel's ipconfig, not by proc.5's process table,
// and exists only on a kernel built with CONFIG_IP_PNP. Supplying ip= on the command line does not
// establish that its parser was compiled in, so whether the guest kernel this fleet boots provides
// /proc/net/pnp is a fact the first NETWORKED boot must establish, and the resolver frontier
// (gunbc.runner_microvm_network guest_resolver_configuration_frontier) waits on exactly that. That
// bet is carried IN THE TYPE, not in this sentence: procfs_path_provision names the option, and
// procfs_availability answers ProcfsConditionalOnKernelConfig for it rather than ProcfsAvailable
// (review 68937 on #11760: rostering the path with no such arm made an executing witness assert
// availability on Linux that this repository has not observed -- DESIGN 4d, a bet promoted to a
// deduction by omission).
data procfs_paths_read_by_this_repository: List<ProcfsPath> = [
ProcMeminfo,
ProcSelfStat,
Expand All @@ -56,6 +74,7 @@ data procfs_paths_read_by_this_repository: List<ProcfsPath> = [
ProcNetUnix,
ProcSwaps,
ProcSysKernelHostname,
ProcNetPnp,
]

// THE TRANSPORT FOR READING procfs ON A HOST THAT MAY NOT BE THIS ONE. Every earlier procfs read in
Expand Down Expand Up @@ -130,13 +149,63 @@ fn kernel_provides_procfs(kernel: KernelFamily) -> Bool {
}
}

// WHICH PATHS A LINUX KERNEL SYNTHESISES UNCONDITIONALLY, AND WHICH ONLY WHEN BUILT WITH AN
// OPTION -- AND WHICH THIS REPOSITORY HAS NOT YET AUDITED. A path owned by an optional subsystem
// exists only when that subsystem was compiled in: /proc/net/pnp needs CONFIG_IP_PNP (the kernel's
// nfsroot guide, cited on nfsroot_ip_autoconfiguration_authority), and no reading this repository
// has taken decides that for a kernel it has not booted. The option is spelled as the kernel spells
// it (the NonEmptyStr convention gunbc.runner.runner_host_filtered_egress KernelFeatureRequirement
// uses), so a host kernel-config readback can join on it.
//
// NOTHING IS CLASSIFIED UNCONDITIONAL HERE, BY DESIGN AND NOT BY OVERSIGHT. The first revision of
// this fold (review 68937 / sunny-ant-606 on #11760) named every previously rostered path
// ProvidedByEveryLinuxKernel and a witness then required each of them to be available on Linux --
// ratifying the mapping rather than testing it -- while /proc/net/unix is in fact CONFIG_UNIX-
// conditional upstream (net/unix is obj-$(CONFIG_UNIX), and its proc_create_net sits inside). So a
// path is either qualified by a specific cited option, or it is ProvisionNotYetQualified, which
// procfs_availability carries through as ProcfsProvisionUnqualified and never as ProcfsAvailable:
// availability may not be minted from KernelFamily alone. DECLARED FRONTIER (DESIGN 3c):
// ProvidedByEveryLinuxKernel has no inhabitant until a procfs census cites, per path, the kernel
// source that synthesises it unconditionally under CONFIG_PROC_FS (and a conjunction carrier if a
// path needs more than one option); that census is its own change, not the resolver PR that
// surfaced the gap. Its consumer is the same procfs_availability fold, which already carries the
// arm.
type ProcfsPathProvision
= ProvidedByEveryLinuxKernel
| ProvidedWhenConfigured { option: NonEmptyStr }
| ProvisionNotYetQualified

fn procfs_path_provision(p: ProcfsPath) -> ProcfsPathProvision {
match p {
ProcMeminfo => ProvisionNotYetQualified
ProcSelfStat => ProvisionNotYetQualified
ProcSelfStatus => ProvisionNotYetQualified
ProcSelfCgroup => ProvisionNotYetQualified
ProcNetUnix => ProvisionNotYetQualified
ProcSwaps => ProvisionNotYetQualified
ProcSysKernelHostname => ProvisionNotYetQualified
ProcNetPnp => ProvidedWhenConfigured { option: "CONFIG_IP_PNP" as NonEmptyStr }
}
}

// ProcfsConditionalOnKernelConfig IS AN OBSERVATION OBLIGATION, NOT A THIRD KIND OF AVAILABILITY.
// The kernel family can serve the path; whether THIS kernel does is decided by a config option the
// repository has to read (or a boot that has to show the file). It answers false to
// procfs_availability_is_available for that reason: a consumer that needs the path proceeds on a
// reading, never on the family.
type ProcfsAvailability
= ProcfsAvailable
| ProcfsConditionalOnKernelConfig { path: ProcfsPath, option: NonEmptyStr }
| ProcfsProvisionUnqualified { path: ProcfsPath }
| ProcfsAbsentOnKernel { kernel: KernelFamily, path: ProcfsPath }

fn procfs_availability(kernel: KernelFamily, path: ProcfsPath) -> ProcfsAvailability {
if kernel_provides_procfs(kernel: kernel) {
ProcfsAvailable
match procfs_path_provision(p: path) {
ProvidedByEveryLinuxKernel => ProcfsAvailable
ProvidedWhenConfigured { option: o } => ProcfsConditionalOnKernelConfig { path: path, option: o }
ProvisionNotYetQualified => ProcfsProvisionUnqualified { path: path }
}
} else {
ProcfsAbsentOnKernel { kernel: kernel, path: path }
}
Expand Down Expand Up @@ -167,6 +236,8 @@ fn procfs_absence_reason(kernel: KernelFamily, path: ProcfsPath) -> NonEmptyStr
fn procfs_availability_is_available(a: ProcfsAvailability) -> Bool {
match a {
ProcfsAvailable => true
ProcfsConditionalOnKernelConfig { path: _, option: _ } => false
ProcfsProvisionUnqualified { path: _ } => false
ProcfsAbsentOnKernel { kernel: _, path: _ } => false
}
}
Expand All @@ -180,5 +251,14 @@ data extdeps_model_scope: ExternalModelScope = ExternalModelScope {
}
},
first_citation: extdeps.linux.procfs.extdeps_external_authority_anchor,
further_citations: []
further_citations: [nfsroot_ip_autoconfiguration_authority]
}

// /proc/net/pnp IS DOCUMENTED BY THE KERNEL'S NFS-ROOT GUIDE, NOT BY proc.5, so the roster's
// ProcNetPnp row cites its own document rather than borrowing the anchor above.
data nfsroot_ip_autoconfiguration_authority: ExternalAuthority = ExternalAuthority {
uri: Uri {
scheme: Https
locator: "docs.kernel.org/admin-guide/nfs/nfsroot.html"
}
}
12 changes: 0 additions & 12 deletions dag/extdeps/shell.dag
Original file line number Diff line number Diff line change
Expand Up @@ -334,18 +334,6 @@ service shell.Link {
}
}

service shell.Symlink {
operation AtomicReplace {
input { target: FilePath, link: FilePath }
output { success: Bool from "exit_success" }
transport shell { argv: ["ln", "-sfnT", "{target}", "{link}"] }
exit {
0 => Unit "Symlink published; -T treats link as a normal file (no nest into existing dir); -f replaces existing link"
nonzero => String "ln -sfnT failed"
}
}
}

service shell.Remove {
operation FileForce {
input { path: FilePath }
Expand Down
33 changes: 33 additions & 0 deletions dag/extdeps/tools/gnu_coreutils.dag
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,39 @@ fn rm_recursive_force_command(path: String) -> ArgvCommand {
argv_command(program: coreutils_rm_binary_name, arguments: [coreutils_rm_force_recursive_flags as String, path])
}

// ln(1) -- GNU coreutils, "ln: Make links between files". -s makes a SYMBOLIC link, -f removes an
// existing destination first, -n treats a destination that is itself a symlink to a directory as a
// file to replace, and -T (--no-target-directory) makes ln REFUSE when the destination is a real
// directory instead of creating the link INSIDE it. The four are one operation -- "make exactly
// this path a symlink to that target, or refuse" -- and the name carries all of them so a caller
// cannot obtain the target-directory form by dropping a flag.
//
// THIS IS THE ONE HOME OF `ln -sfnT`. extdeps.shell carried a `shell.Symlink AtomicReplace` service
// with the same argv and the same -T rationale; its two consumers (gunbc.package_delivery
// publish_codex_runtime_current_link and the event-log real-execution witness) were moved onto this
// builder and the service deleted, so one upstream fact has one declaration (review 68813 on
// #11760) -- the direction gunbc.plans.transport_argv_anemia_dissolution names. extdeps.linux.usb_gadget configfs_op_line still
// hand-spells `ln -sf` inside a script string; that is a declared frontier of its own
// (configfs_op_render_dissolution_trigger) and folds onto this row when it dissolves.
//
// -T IS THE FAIL-CLOSED HALF AND -n IS NOT A SUBSTITUTE FOR IT (sunny-ant-606 review of #11760).
// -n covers only a symlink-to-directory; against a REAL directory at LINK_NAME, `ln -sfn T D` exits
// 0 having created D/T's-basename, so the command succeeds while the intended path was never made a
// link -- a witness pinning the argv stays green over an image with no link. With -T that case is a
// nonzero exit, which is what makes the builder's claim ("this path is now a link") true whenever it
// returns success. The target is spelled as given and never resolved: a symlink's target is text the
// kernel reads at open time, which is the whole reason gunbc.runner_guest_image can point a file
// inside an image at a path that exists only once that image has booted.
data coreutils_ln_binary_name: NonEmptyStr = "ln"
data coreutils_ln_symbolic_force_exact_destination_flags: NonEmptyStr = "-sfnT"

fn ln_symbolic_force_command(target: String, link_name: String) -> ArgvCommand {
argv_command(
program: coreutils_ln_binary_name,
arguments: [coreutils_ln_symbolic_force_exact_destination_flags as String, target, link_name],
)
}

// The format string is the first operand and the values follow it, exactly as printf(1) takes them.
fn printf_command(format: String, values: List<String>) -> ArgvCommand {
argv_command(program: printf_program, arguments: append([format], items: values))
Expand Down
19 changes: 14 additions & 5 deletions dag/gunbc/package_delivery.dag
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ import extdeps.tools.sha512sum {
Sha512FileObservationRefused,
}
import extdeps.tools.node
import extdeps.tools.gnu_coreutils { ln_symbolic_force_command }
import std.process { ExitSuccess, ExitFailure }
import gunbc.command_runner { run_shell_commands, LocalExec }
import gunbc.codex_runtime_paths {
codex_runtime_current_link_path,
codex_bundled_executable_at_current_link,
Expand Down Expand Up @@ -482,16 +485,22 @@ fn codex_runtime_version_identity_satisfied(combined_output: String) -> Bool {
)
}

// Typed symlink publish via extdeps.shell.Symlink.AtomicReplace (ln -sfnT argv binding). Runs
// inside materialize_codex_runtime_bundle during gunbc run materialize, not on the
// host_effect_apply observe/ensure/read-back transport.

// Typed symlink publish through extdeps.tools.gnu_coreutils ln_symbolic_force_command (the one
// home of `ln -sfnT`; the consumerless-looking shell.Symlink service that used to carry the same
// argv was folded onto it, review 68813 on #11760). Runs inside materialize_codex_runtime_bundle
// during gunbc run materialize, not on the host_effect_apply observe/ensure/read-back transport.
fn publish_codex_runtime_current_link(
release_parent: FilePath,
release_root: FilePath,
) -> Bool {
let link = codex_runtime_current_link_path(release_parent: release_parent)
shell.Symlink.AtomicReplace(target: release_root, link: link).success
match run_shell_commands(
commands: [ln_symbolic_force_command(target: release_root as String, link_name: link as String)],
transport: LocalExec,
) {
ExitSuccess => true
ExitFailure { code: _, reason: _ } => false
}
}

fn codex_expected_platform_version(target: CodexNpmRuntimeTarget) -> NonEmptyStr {
Expand Down
56 changes: 49 additions & 7 deletions dag/gunbc/runner/runner_guest_image.dag
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import extdeps.toolchain.types { Aarch64 }
import extdeps.tools.curl { curl_download_command }
import extdeps.tools.tar { tar_extract_command }
import extdeps.tools.mkdir { mkdir_parents_command }
import extdeps.tools.gnu_coreutils { rm_force_command, rm_recursive_force_command }
import extdeps.tools.gnu_coreutils { rm_force_command, rm_recursive_force_command, ln_symbolic_force_command }
import extdeps.linux.procfs { ProcNetPnp, procfs_path_literal }
import extdeps.tools.squashfs_tools { unsquashfs_extract_command }
import extdeps.tools.e2fsprogs { Ext2FilesystemType, Ext4, mke2fs_populate_command }
import extdeps.tools.sha256sum {
Expand Down Expand Up @@ -486,12 +487,14 @@ fn runner_guest_agent_unit_path(root: String, image: RunnerGuestImage) -> String
// actually reads was correct. A correct assertion over the wrong subject.
//
// THE REPAIR IS A DROP-IN ON THE TARGET, NOT A SYMLINK, and that choice is deliberate rather than
// convenient. A symlink would be the closer analogue of `systemctl enable`, and it is also a file
// KIND this build cannot currently create -- Filesystem.Write writes regular files. Reaching for a
// symlink here would mean minting an `ln -s` argv builder to make the image build depend on a second
// mechanism, when systemd already documents a fragment that expresses the dependency directly and IS
// a regular file honestly used as one. `<target>.d/*.conf` carrying `[Unit] Wants=` is merged into
// the target's own configuration by the ordinary drop-in path, which has no symlink requirement.
// convenient. A symlink would be the closer analogue of `systemctl enable`, but systemd documents a
// fragment that expresses the dependency directly and IS a regular file honestly used as one:
// `<target>.d/*.conf` carrying `[Unit] Wants=` is merged into the target's own configuration by the
// ordinary drop-in path, which has no symlink requirement. The build CAN now place a symlink (the
// resolver link below goes through extdeps.tools.gnu_coreutils ln_symbolic_force_command), and that
// does not move this decision: where upstream documents a regular-file form, a link is a second
// spelling of the same activation, and the regular file is the one Filesystem.Write can render from
// the modeled unit rather than a path a caller pairs with it.
//
// The unit keeps its [Install] section, and that is not a second authority for the same fact: the
// section states which target this agent BELONGS to, so `systemctl enable` inside a running guest
Expand Down Expand Up @@ -596,6 +599,7 @@ fn runner_guest_tree_commands(root: String, image: RunnerGuestImage, artifact: A
archive: runner_guest_base_artifact_path(root: root, artifact: runner_guest_base_rootfs_artifact),
into_directory: runner_guest_tree_dir(root: root, image: image),
),
runner_guest_resolver_link_command(root: root, image: image),
mkdir_parents_command(path: runner_guest_tree_runner_dir(root: root, image: image)),
tar_extract_command(
archive: runner_guest_runner_tarball_path(root: root, artifact: artifact),
Expand All @@ -607,6 +611,44 @@ fn runner_guest_tree_commands(root: String, image: RunnerGuestImage, artifact: A
]
}

// THE GUEST'S RESOLVER IS THE KERNEL'S dns0, READ THROUGH ONE LINK, AND THE LINK IS WHAT UPSTREAM
// DOCUMENTS. The host boots the guest with `ip=...:<dns0>` (gunbc.runner_microvm_network
// runner_slot_guest_ip_boot_arg, whose dns0 is runner_microvm_guest_resolver -- the one address the
// egress grant admits on port 53). The kernel does not write /etc/resolv.conf; it exports dns0 to
// /proc/net/pnp under the prefix "nameserver " and says in its own words that /etc/resolv.conf is
// linked to it (Documentation/admin-guide/nfs/nfsroot.rst, cited on extdeps.linux.procfs ProcNetPnp).
// So the image's /etc/resolv.conf IS that link, and the guest's resolver configuration is derived from
// the same row the grant is derived from, at boot, with no copy of the address baked into the image.
//
// A LINK AND NOT A FILE, BECAUSE NO FILE COULD BE RIGHT AT BUILD TIME. /proc/net/pnp exists only once
// the guest kernel has run its IP configuration, so there is nothing to copy while the tree is being
// staged; and a regular file carrying the address would be a second authority for
// runner_microvm_guest_resolver that would silently diverge from the ip= line the next time the row
// moved. A systemd unit copying the file at boot was the other modeled option and was not taken: it
// is a moving part ordered against network-online.target for a fact the kernel has already produced
// before init starts, and it would carry shell semantics (`cp` at boot) where the link carries none.
//
// Whatever the base rootfs ships at /etc/resolv.conf is unobserved here -- an Ubuntu image commonly
// carries a link to systemd-resolved's stub file, but this fleet has not read this one's -- so the
// builder is the exact-destination form (-sfnT): an existing file or link is replaced, and a real
// DIRECTORY there makes the build REFUSE rather than create the link inside it and report success
// over an image with no resolver link. The refusal is the located diagnostic; the extracted kind
// is then a fact to observe, not a guess to widen the builder around.
fn runner_guest_resolver_link_target() -> String {
procfs_path_literal(p: ProcNetPnp) as String
}

fn runner_guest_tree_resolv_conf_path(root: String, image: RunnerGuestImage) -> String {
join([runner_guest_tree_dir(root: root, image: image), "/etc/resolv.conf"], "")
}

fn runner_guest_resolver_link_command(root: String, image: RunnerGuestImage) -> ArgvCommand {
ln_symbolic_force_command(
target: runner_guest_resolver_link_target(),
link_name: runner_guest_tree_resolv_conf_path(root: root, image: image),
)
}

fn runner_guest_tree_runner_dir(root: String, image: RunnerGuestImage) -> String {
join([runner_guest_tree_dir(root: root, image: image), actions_runner_base_dir], "")
}
Expand Down
Loading