Fix a bug in ArchiveWriter where large files would only partly be
written into an archive.
This is done so by writing the data in chunks.
---------
Signed-off-by: Aditya Ramani <a_ramani@apple.com>
- No real notable API changes. The largest change code-wise is in
Vminitd. The connection loop is set up immediately in the new API by
calling runConnections() on the client, and this blocks until either the
task it's running on is cancelled or beginGracefulShutdown is called.
Because of this, either we'd have to modify our API to have
VirtualMachineAgent implementations call some run() method, or we just
put runConnections in an internal task on Vminitd. This change uses the
latter approach as it's simpler.
- Updated dependencies to pick up latest protobuf and grpc-swift-2
fixes.
- ClientBootstrap.withConnectedSocket(fd).wait() registers the vsock fd
with epoll/kqueue immediately. vminitd sends its HTTP/2 SETTINGS frame
right away upon accepting the connection, so those bytes can arrive on
the fd — and be read by NIO — before the gRPC pipeline is installed.
With a bare pipeline, NIO has nowhere to send the bytes and discards
them.
- Buffer until ClientConnectionHandler is in the pipeline so it can
process the SETTINGS frame and fire .ready, then schedule delivery of
the buffered data with assumeIsolatedUnsafeUnchecked().execute.
- To help produce more accurate merged logs, defines a
`StderrLogHandler` in the vminitd Application.swift that writes
timestamps to the boot log with millisecond precision and format
identical to that of the log files created using the `--log-root` arg on
`container`.
- Updates hawkeye to latest version (v6.5.1).
---------
Co-authored-by: Danny Canter <danny_canter@apple.com>
### Summary
`rootfs` create writes proc/self/exe as a symlink, but currently passes
the previous regular file buffer to `ArchiveWriter.writeEntry`. This
change switches that symlink entry to nil, which matches archive writer
expectations.
### Test
Archive tests pass.
ContainerizationArchive internally used a `FileArchiveWriterDelegate`
type that defined callback methods which were handed to
`archive_write_open2` from libarchive
```
int archive_write_open2(struct archive *, void *,
archive_open_callback *, archive_write_callback *,
archive_close_callback *, archive_free_callback *);
```
Turns out those methods were at the very least partly broken, when used
alongside gzip compression.
Utilities like gnutar and gunzip couldn't entirely process the archives
produced by them, leading to errors like
```
➜ gtar -tvzf rootfs.tar.gz
...
drwxr-xr-x 0/0 0 2026-03-07 11:24 sbin/
-rwxr-xr-x 0/0 122451504 2026-03-07 11:24 sbin/vminitd
-rwxr-xr-x 0/0 121138856 2026-03-07 11:24 sbin/vmexec
gzip: (stdin): trailing garbage ignored
..
gtar: Child returned status 2
gtar: Error is not recoverable: exiting now
```
libarchive provides alternate APIs, `archive_write_open_filename` and
`archive_write_open_fd` where these callbacks are defined within the
library.
Lets use those instead and let the library handle this / future edge
cases (in this particular case it was adding some padding at the end of
the gzipped compressed file)
Now that the static linux SDK we use has libarchive linked against it,
we can finally live our dream (/s) of supporting copying directories
in/out a little easier. This was.. kind of annoying. I think the very
simple route of writing archive to guest/host -> streaming over grpc is
simplest, but on the guest end we have a couple problems:
1. The VMs rootfs is read only today always, which is a good thing to me
and I don't want to change if we don't have to.
2. Writing the archive to the containers rootfs temporarily could work,
but it's a bit weird, and the user can make the containers rootfs
readonly which would screw that plan.
3. We could write it to /run or /tmp, but they're tmpfs and dealing with
the headache of the user possibly tarring an enormous dir is one I don't
want to care about.
So, that leaves us with the (truthfully better to me) approach of trying
to write the tar data directly to the host and skipping grpc which kinda
forces us to have a temp spot. Because of that, I made it such that we
pass a port number from host<->guest, and transfer the actual data
(either the single file or tarred dir contents) over the vsock port
instead. The stream is kinda clunky, but it just serves as a means to
exchange metadata and a "we're done" signifier.
Closes#221
This contains (in the static linux sdk but the swift version and the sdk
must match) libarchive support and mimalloc! This also (unrelated to the
sdk bump it seems) raises the amount of retries we do when dialing
vminitd. It seems CI on rare occasions recently has timed out on some
tests. We could also explore just lowering the concurrency on the
integration suite.
- Will be used by the fix for apple/container#1253.
- We use a common keychain ID between the `container CLI and
`container-core-images`, but right now keychain entries created by the
CLI must be opted-in for `container-core-images`.
- Using an access group with a notarized application should give both
apps automatic access to the keychain.
Closes#256
If the target of the uds proxy goes through a symlink (/var/run) we had
the same bug we had with mounts previously where we'd follow this and if
the resolved path existed in the root of the VM we'd end up mounting
there. Fix this by plopping the sockets in a holding spot and then bind
mounting them in (which has the correct resolution logic).
This change fixes an issue in linux pod where the exec process
does not inherit the container process config which means any
environment variables, working directory, user, rlimits,
capabilities would be different for the exec process.
This change ensures that exec process would inherit
environment variables, working directory, user, rlimits,
and capabilities from the container process config but
resets arguments, terminal, and stdio as those should not
be inherited.
This change also adds an integration test to exercise this.
Just like we have for hosts and dns configurations, we can add a
hostname field to make it easier to setup if every container is going to
be using the same hostname.
Today we don't resolve symlinks relative to the rootfs of the container
as ContainerizationOS.Mount did not handle this. This means trying to
mount something at /var/run (which typically is a symlink to /run in
most cases) would resolve to /run in the root of the VM. This change
adds some logic to handle this.
```
➜ ./bin/cctl run --kernel bin/kernel.arm64 --mount /Users/dcantah/foo:/var/run/foo bash
root@sandbox-vm:/# ls var/run/foo/
howdy
```
- Closes#532.
- Relocate all zstd calls down into CArchive, and export a function
zstd_decompress_fd(src_fd, dst_fd) that ContainerizationArchive can use.
This eliminates problems that can occur when treating Swift warnings as
errors in Xcode projects that use ContainerizationArchive.
This PR adds OCI Image Spec v1.1 artifact support. It extends Manifest,
Index, and Descriptor with the `subject` and `artifactType` fields, adds
a `referrers()` method to RegistryClient implementing the OCI
Distribution Spec v1.1 referrers API. Also, I've added some unit unit
tests for backward compatibility and roundtrip encpding of all new
fields
This PR adds a `networking: Bool = true` parameter to
`ContainerManager.create()` so callers can opt out of network interface
creation on a per-container basis.
## Motivation
Currently, `ContainerManager.create()` unconditionally allocates a vmnet
network interface for every container (when the manager has a network
configured). Some use cases don't need network access and benefit from
having it disabled to reduce attack surface.
There's no way to achieve this today without either:
- Initializing the `ContainerManager` without a network (which disables
networking for *all* containers)
- Clearing `config.interfaces` in the configuration closure (which
wastes an IP allocation from the vmnet pool since `network.create(id)`
has already been called)
## Changes
- Add `networking: Bool = true` to all three `create()` overloads on
`ContainerManager`
- When `false`, `self.network?.create(id)` is skipped entirely. No
interface is allocated, and no DNS is configured.
- `releaseNetwork`/`delete` remain safe to call regardless, since
`Allocator.release` silently ignores unknown IDs.
- Add unit test `testNetworkingFalseSkipsInterfaceCreation` using the
existing `NilGatewayNetwork` fixture
- Add integration tests `testNetworkingDisabled` and
`testNetworkingEnabled` that create containers through a network-enabled
`ContainerManager` and verify the presence/absence of `eth0` via
`/sys/class/net/`
There are some workloads that benefit from having a "true" init process
that reaps zombies. This change implements a minimal init as part of
vminitd and exposes an API to be able to use this init process in our
containers. signals will be forwarded, any child procs will be reaped
and the exit code of the actual workload will be propagated. The
LinuxContainer/LinuxPod API is a very simple bool to ask to use this as
our init process, and the init simply spawns whatever command you want
as a child underneath this.
```
➜ ./bin/cctl run --kernel bin/kernel.arm64 --init
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /.cz-init -- /bin/sh
2 root 0:00 /bin/sh
3 root 0:00 ps aux
```
## What
Raise the constant from 127 to 255 in `Reference.swift` and update the
derived `referenceTotalLengthMax` accordingly.
## Why
Closes#453.
The [OCI distribution
spec](https://github.com/opencontainers/distribution-spec/blob/main/spec.md)
states that the **name** component of an image reference (registry host
+ repository path) may be at most 255 bytes. The previous hard-coded
limit of 127 characters incorrectly rejected valid references that used
long registry hostnames or deep path hierarchies.
The old `referenceTotalLengthMax = 255` was also inconsistent: with a
127-char name cap, a 255-char total reference would only allow a very
short tag. The new value is derived explicitly as `nameTotalLengthMax
(255) + separator (1) + tagLengthMax (128) = 384`.
## How
- `nameTotalLengthMax`: 127 → 255
- `tagLengthMax`: new constant (128) documenting the maximum tag length
already enforced by the tag regex (`{0,127}` + leading char = 128
chars).
- `referenceTotalLengthMax`: computed from the two constants above (384)
rather than hard-coded to 255.
## Testing
Added three new cases in `ReferenceTests.swift`:
- ✅ Name of 128 characters (registry + path) — previously rejected, now
accepted
- ✅ Name of exactly 255 characters — at the OCI spec maximum, accepted
- ❌ Name of 256 characters — one over the limit, rejected
This adds support to LinuxContainer to be able to provide an optional
writable layer. This is useful as today the rootfs size is determined by
the size of the image block itself which is set in stone at unpack time
of the image. This leaves a lot to be desired because a user might want
a larger or smaller rootfs size, but it's not configurable on a per
container basis because of this. To support this, we can pass an
additional writable block device that we can overlayfs with the image
contents in the guest. All writes will go to this writable layer, and
the rootfs size in the container is now whatever size this writable
layer is.
The block devices (via our ext4 package, but you could use whatever) are
cheap enough to generate on the fly that you can choose whatever size
you want easily.
These two stats are import to calculate the following
- wss bytes (memory usage - inactive files)
- rss bytes (anonymous memory)
- available bytes (limit - wss bytes)
Just exposing these two in `SandboxContext.proto` as we already collect
them in `readMemoryStats()` in `Cgroup2Manager`
- Downgrades a warning to the debug level in `BidirectionalRelay`
because the shutdown syscall isn't supported for certain socket types
- Rethrows an error if we can't start a socket relay
This adds a small flag named --log-level to be able to set the log level
of vminitd and the pause command. I imagine this won't be the last time
we'll have to add flags to vminitd so I went ahead and converted vminitd
to be a Argument Parser based binary as well.
I've extracted the network cleanup step from `delete` into a new public
`releaseNetwork` method. This mirrors the existing `release` on
`VmnetNetwork` and enables stop-and-restart workflows where the network
allocation needs to be freed without removing the container's rootfs.
## Changes
- Add `ContainerManager.release(_:)` — frees network resources for a
container after it has stopped
- Refactor `delete(_:)` to call `releaseNetwork(_:)` before removing
files, preserving existing behavior
## Motivation
Currently the only way to free a container's network allocation is
`delete`, which also removes its files. This makes it impossible to stop
a container (freeing the network so the ID can be re-allocated) and
later restart it with its filesystem intact. The new `releaseNetwork `
method fills this gap.
- Adds a public method to wait for the relay to complete when both
directions close
- Improves error handling
- Moves `UnixSocketRelayManager` into a separate file
- Renames `SocketRelay` to `UnixSocketRelay`
- Adds logs at the info level after converting other logs to the debug
level previously
Without `LocalizedError` conformance, calling `localizedDescription` on
a `ContainerizationError` falls back to the default representation
(e.g., "ContainerizationError.ContainerizationError error 1.") instead
of returning the actual error message. This makes it difficult to show
meaningful errors in a UI, where `localizedDescription` is the standard
way to get a user-facing string.
- Guarded against ipv4Gateway if nil and throw
`ContainerizationError(.invalidState, ...)` instead of force unwraping
- Added a unit test
`ContainerManagerTests.testCreateThrowsWhenGatewayMissing`
If we have the same source we should dedupe these. Previously
virt.framework would yell at us for a duplicate tag because we derive
the tag from hashing the mount source.
Changes:
- Refactors a bidirectional relay into a new class
- Makes the unique identifier for a socket configuration public
Bug fixes:
- Fixes a bug where partial writes didn't advance the pointer
- Fixes a buffer memory leak
- Fixes error messages
- Fixes log messages
- Fixes log levels
- Add a `list` function that is needed for the `container registry list`
command
- Updates to `KeychainHelper` and `KeychainQuery` variable/parameter
names
- Updates to test cases