├── .swift-version ├── Sources ├── ContainerizationOS │ ├── README.md │ ├── POSIXError+Helpers.swift │ ├── Sysctl.swift │ ├── Syscall.swift │ ├── Pipe+Close.swift │ ├── BinaryInteger+Extensions.swift │ ├── Reaper.swift │ ├── Socket │ │ └── SocketType.swift │ └── URL+Extensions.swift ├── ContainerizationArchive │ ├── CArchive │ │ ├── include │ │ │ └── archive_bridge.h │ │ └── archive_swift_bridge.c │ ├── TempDir.swift │ └── FileArchiveWriterDelegate.swift ├── ContainerizationEXT4 │ ├── README.md │ ├── Documentation.docc │ │ └── ext4.md │ ├── FileTimestamps.swift │ ├── EXT4+Ptr.swift │ └── UnsafeLittleEndianBytes.swift ├── CShim │ ├── vsock.c │ ├── include │ │ ├── capability.h │ │ ├── vsock.h │ │ ├── socket_helpers.h │ │ ├── prctl.h │ │ └── exec_command.h │ ├── capability.c │ ├── socket_helpers.c │ └── prctl.c ├── Containerization │ ├── IO │ │ ├── ReaderStream.swift │ │ ├── Writer.swift │ │ └── Terminal+ReaderStream.swift │ ├── VirtualMachineManager.swift │ ├── VirtualMachineAgent+Additions.swift │ ├── NATInterface.swift │ ├── Hash.swift │ ├── Container.swift │ ├── Interface.swift │ ├── Vminitd+Rosetta.swift │ ├── ExitStatus.swift │ ├── SystemPlatform.swift │ ├── Image │ │ └── Unpacker │ │ │ └── Unpacker.swift │ ├── Vminitd+SocketRelay.swift │ ├── AttachedFilesystem.swift │ ├── VirtualMachineInstance.swift │ ├── DNSConfiguration.swift │ ├── VsockListener.swift │ ├── TimeSyncer.swift │ ├── NATNetworkInterface.swift │ └── UnixSocketConfiguration.swift ├── ContainerizationOCI │ ├── Content │ │ ├── String+Extension.swift │ │ ├── SHA256+Extensions.swift │ │ ├── URL+Extensions.swift │ │ ├── AsyncTypes.swift │ │ ├── Content.swift │ │ └── LocalContent.swift │ ├── FileManager+Size.swift │ ├── Version.swift │ ├── AnnotationKeys.swift │ ├── Client │ │ ├── Authentication.swift │ │ └── RegistryClient+Error.swift │ ├── Manifest.swift │ ├── Descriptor.swift │ ├── Index.swift │ └── State.swift ├── ContainerizationExtras │ ├── FileManager+Temporary.swift │ ├── ProgressEvent.swift │ ├── AsyncLock.swift │ ├── AsyncMutex.swift │ ├── Timeout.swift │ └── AddressAllocator.swift └── cctl │ ├── cctl+Utils.swift │ └── cctl.swift ├── Tests ├── TestImages │ ├── emptyimage │ │ └── Dockerfile │ └── dockermanifestimage │ │ └── Dockerfile ├── ContainerizationTests │ ├── ImageTests │ │ ├── Resources │ │ │ ├── scratch.tar │ │ │ └── scratch_no_annotations.tar │ │ └── ContainsAuth.swift │ ├── HashTests.swift │ ├── ImageTests.swift │ ├── MountTests.swift │ ├── DNSTests.swift │ ├── KernelTests.swift │ ├── HostsTests.swift │ └── LinuxContainerTests.swift ├── ContainerizationEXT4Tests │ └── Resources │ │ └── content │ │ └── blobs │ │ └── sha256 │ │ ├── 4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 │ │ ├── c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44 │ │ ├── 48a06049d3738991b011ca8b12473d712b7c40666a1462118dae3c403676afc2 │ │ ├── 8e2eb240a6cd7be1a0d308125afe0060b020e89275ced2e729eda7d4eeff62a2 │ │ └── ad59e9f71edceca7b1ac7c642410858489b743c97233b0a26a5e2098b1443762 ├── ContainerizationOSTests │ └── KeychainQueryTests.swift ├── ContainerizationExtrasTests │ └── TestTimeout.swift ├── ContainerizationNetlinkTests │ └── MockNetlinkSocket.swift └── ContainerizationOCITests │ ├── AuthChallengeTests.swift │ └── OCIImageTests.swift ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 02-feature.yml │ └── 01-bug.yml └── workflows │ ├── containerization-build.yml │ ├── docs-release.yaml │ ├── release.yml │ └── build-test-images.yml ├── signing └── vz.entitlements ├── .spi.yml ├── examples ├── ctr-example │ ├── ctr-example.entitlements │ ├── Makefile │ ├── lab.md │ ├── README.md │ └── Package.swift └── README.md ├── .gitignore ├── scripts ├── cz-header-style.toml ├── license-header.txt ├── ensure-hawkeye-exists.sh ├── install-hawkeye.sh ├── make-docs.sh └── check-integration-test-vm-panics.sh ├── licenserc.toml ├── kernel ├── image │ ├── Dockerfile │ └── sources.list ├── README.md ├── build.sh └── Makefile ├── MAINTAINERS.txt ├── vminitd └── Sources │ ├── vminitd │ ├── IOCloser.swift │ ├── HostStdio.swift │ ├── IOCloser+Extensions.swift │ ├── PauseCommand.swift │ ├── ContainerProcess.swift │ └── Application.swift │ ├── LCShim │ ├── include │ │ └── syscall.h │ └── syscall.c │ └── vmexec │ ├── Console.swift │ └── Mount.swift ├── SECURITY.md ├── Protobuf.Makefile └── .swift-format /.swift-version: -------------------------------------------------------------------------------- 1 | 6.2 -------------------------------------------------------------------------------- /Sources/ContainerizationOS/README.md: -------------------------------------------------------------------------------- 1 | ## OS 2 | 3 | This target contains general useful OS related definitions or wrappers. -------------------------------------------------------------------------------- /Tests/TestImages/emptyimage/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | LABEL org.opencontainers.image.source=https://github.com/apple/containerization 3 | ADD . . 4 | 5 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests/Resources/scratch.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/HEAD/Tests/ContainerizationTests/ImageTests/Resources/scratch.tar -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests/Resources/scratch_no_annotations.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/HEAD/Tests/ContainerizationTests/ImageTests/Resources/scratch_no_annotations.tar -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/CArchive/include/archive_bridge.h: -------------------------------------------------------------------------------- 1 | // 2 | 3 | #pragma once 4 | 5 | #include "archive.h" 6 | 7 | void archive_set_error_wrapper(struct archive *a, int error_number, const char *error_string); 8 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/README.md: -------------------------------------------------------------------------------- 1 | # ``ContainerizationEXT4`` 2 | 3 | `ContainerizationEXT4` provides functionality to read the superblock of an existing ext4 block device and format a new block device with 4 | the ext4 file system. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Containerization community support 4 | url: https://github.com/apple/container/discussions 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/Documentation.docc/ext4.md: -------------------------------------------------------------------------------- 1 | # ``ContainerizationEXT4`` 2 | 3 | `ContainerizationEXT4` provides functionality to read the superblock of an existing ext4 block device and format a new block device with 4 | the ext4 file system. 5 | -------------------------------------------------------------------------------- /signing/vz.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.virtualization 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Containerization, ContainerizationEXT4, ContainerizationOS, ContainerizationOCI, ContainerizationNetlink, ContainerizationIO, ContainerizationExtras, ContainerizationArchive, SendableProperty] 5 | swift_version: '6.2' 6 | -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/HEAD/Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/HEAD/Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44 -------------------------------------------------------------------------------- /examples/ctr-example/ctr-example.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.virtualization 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/TestImages/dockermanifestimage/Dockerfile: -------------------------------------------------------------------------------- 1 | # This image is built as a single platform image with media type application/vnd.docker.distribution.manifest.v2+json 2 | FROM scratch 3 | LABEL org.opencontainers.image.source=https://github.com/apple/containerization 4 | # empty add so that the build doesn't error due to no build directives 5 | ADD . . 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin 3 | libexec 4 | .build 5 | .local 6 | xcuserdata/ 7 | DerivedData/ 8 | .swiftpm/ 9 | .netrc 10 | .swiftpm 11 | workdir/ 12 | installer/ 13 | .venv/ 14 | test_results/ 15 | *.pid 16 | *.log 17 | *.zip 18 | *.o 19 | *.ext4 20 | *.pkg 21 | *.swp 22 | *.tar.gz 23 | *.tar.xz 24 | vmlinux 25 | 26 | # API docs for local preview only. 27 | _site/ 28 | _serve/ 29 | -------------------------------------------------------------------------------- /scripts/cz-header-style.toml: -------------------------------------------------------------------------------- 1 | [SWIFT_STYLE] 2 | firstLine = '//===----------------------------------------------------------------------===//' 3 | endLine = "//===----------------------------------------------------------------------===//\n" 4 | beforeEachLine = '// ' 5 | afterEachLine = '' 6 | allowBlankLines = false 7 | multipleLines = true 8 | padLines = false 9 | firstLineDetectionPattern = '//\s?===' 10 | lastLineDetectionPattern = '//\s?===' 11 | skipLinePattern = '// swift-tools-version' 12 | -------------------------------------------------------------------------------- /.github/workflows/containerization-build.yml: -------------------------------------------------------------------------------- 1 | name: Build containerization 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | pull_request: 8 | types: [opened, reopened, synchronize] 9 | push: 10 | branches: 11 | - main 12 | - release/* 13 | 14 | jobs: 15 | containerization: 16 | permissions: 17 | contents: read 18 | packages: write 19 | pages: write 20 | uses: ./.github/workflows/containerization-build-template.yml 21 | secrets: inherit 22 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains example projects demonstrating how to use Containerization. 4 | 5 | ## Available Examples 6 | 7 | ### [ctr-example](ctr-example/) 8 | 9 | A basic example of launching a Linux container using Containerization. This example demonstrates: 10 | - Fetching and configuring a Linux kernel 11 | - Creating and starting containers 12 | - Basic container management operations 13 | 14 | See the [ctr-example README](ctr-example/README.md) for detailed build and run instructions. 15 | -------------------------------------------------------------------------------- /licenserc.toml: -------------------------------------------------------------------------------- 1 | additionalHeaders = ["scripts/cz-header-style.toml"] 2 | 3 | headerPath = "scripts/license-header.txt" 4 | 5 | includes = [ 6 | "Makefile", 7 | "*.Makefile", 8 | "*.swift", 9 | "*.h", 10 | "*.cpp", 11 | "*.c", 12 | "*.sh", 13 | ] 14 | 15 | excludes = [ 16 | "Sources/ContainerizationArchive/CArchive/include", 17 | ] 18 | 19 | [git] 20 | attrs = 'enable' 21 | ignore = 'enable' 22 | 23 | [properties] 24 | copyrightOwner = "Apple Inc. and the Containerization project authors" 25 | 26 | [mapping.SWIFT_STYLE] 27 | extensions = ["swift"] 28 | -------------------------------------------------------------------------------- /kernel/image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:focal 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | autoconf \ 5 | bc \ 6 | binutils-multiarch \ 7 | binutils-aarch64-linux-gnu \ 8 | bison \ 9 | flex \ 10 | gcc \ 11 | xz-utils \ 12 | gcc-aarch64-linux-gnu \ 13 | git \ 14 | libncurses-dev \ 15 | make \ 16 | openssl \ 17 | python-is-python3 \ 18 | && apt-get clean \ 19 | && rm -rf /var/lib/apt/lists/* 20 | 21 | COPY sources.list /etc/apt/sources.list 22 | 23 | RUN apt-get update \ 24 | && dpkg --add-architecture arm64 \ 25 | && apt-get install -y libelf-dev:arm64 \ 26 | && apt-get clean \ 27 | && rm -rf /var/lib/apt/lists/* -------------------------------------------------------------------------------- /MAINTAINERS.txt: -------------------------------------------------------------------------------- 1 | This file contains a list of maintainers and past maintainers who have made meaningful changes to this repository. 2 | 3 | ### Maintainers 4 | 5 | Aditya Ramani (adityaramani) 6 | AJ Emory (ajemory) 7 | Danny Canter (dcantah) 8 | Dmitry Kovba (dkovba) 9 | Eric Ernst (egernst) 10 | John Logan (jglogan) 11 | Kathryn Baldauf (katiewasnothere) 12 | Madhu Venugopal (mavenugo) 13 | Michael Crosby (crosbymichael) 14 | Raj Aryan Singh (realrajaryan) 15 | Sidhartha Mani (wlan0) 16 | Yibo Zhuang (yibozhuang) 17 | 18 | 19 | ### Emeritus maintainers 20 | 21 | Agam Dua (agamdua) 22 | Evan Hazlett (ehazlett) 23 | Gilbert Song (gilbert88) 24 | Hugh Bussell (hughbussell) 25 | Tanweer Noor (tanweernoor) 26 | Ximena Perez Diaz (ximenanperez) 27 | -------------------------------------------------------------------------------- /kernel/README.md: -------------------------------------------------------------------------------- 1 | # Containerization Kernel Configuration 2 | 3 | This directory includes an optimized kernel configuration to produce a fast and lightweight kernel for container use. 4 | 5 | - `config-arm64` includes the kernel `CONFIG_` options. 6 | - `Makefile` includes the kernel version and source package url. 7 | - `build.sh` scripts the kernel build process. 8 | - `image/` includes the configuration for an image with build tooling. 9 | 10 | ## Building 11 | 12 | 1. The build process relies on having the `container` tool installed (https://github.com/apple/container/releases). 13 | 2. Run `make`. This should create the image used for building the resulting Linux kernel, and then run a container with that image to perform the kernel build. 14 | 15 | A `kernel/vmlinux` file will be the result of the build. 16 | -------------------------------------------------------------------------------- /Sources/CShim/vsock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "vsock.h" 18 | 19 | const unsigned long VsockLocalCIDIoctl = IOCTL_VM_SOCKETS_GET_LOCAL_CID; 20 | -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/48a06049d3738991b011ca8b12473d712b7c40666a1462118dae3c403676afc2: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 2, 3 | "mediaType": "application/vnd.oci.image.manifest.v1+json", 4 | "config": { 5 | "mediaType": "application/vnd.oci.image.config.v1+json", 6 | "digest": "sha256:8e2eb240a6cd7be1a0d308125afe0060b020e89275ced2e729eda7d4eeff62a2", 7 | "size": 824 8 | }, 9 | "layers": [ 10 | { 11 | "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", 12 | "digest": "sha256:c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44", 13 | "size": 3333361 14 | }, 15 | { 16 | "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", 17 | "digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1", 18 | "size": 32 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /scripts/license-header.txt: -------------------------------------------------------------------------------- 1 | Copyright ©{{ " " }}{%- set created = attrs.git_file_created_year or attrs.disk_file_created_year -%}{%- set modified = attrs.git_file_modified_year or created -%}{%- if created != modified -%} {{created}}-{{modified}}{%- else -%}{{created}}{%- endif -%}{{ " " }}{{ props["copyrightOwner"] }}. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/8e2eb240a6cd7be1a0d308125afe0060b020e89275ced2e729eda7d4eeff62a2: -------------------------------------------------------------------------------- 1 | {"architecture":"arm64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"OnBuild":null},"created":"2024-03-16T00:09:03.929767682Z","history":[{"created":"2024-01-26T23:44:55.650290626Z","created_by":"/bin/sh -c #(nop) ADD file:6dc287a22d6cc7723b0576dd3a9a644468d133c54d42c8a8eda403e3117648f7 in / "},{"created":"2024-01-26T23:44:55.750082605Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true},{"created":"2024-03-16T00:09:03.929767682Z","created_by":"RUN /bin/sh -c echo \"test\" # buildkit","comment":"buildkit.dockerfile.v0"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:7c504f21be85c8ade51b7ade32a39a4269bcbcf0e593352923f1b8ea6278e5ef","sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"]},"variant":"v8"} -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "archive_bridge.h" 18 | 19 | void archive_set_error_wrapper(struct archive *a, int error_number, const char *error_string) { 20 | archive_set_error(a, error_number, "%s", error_string); 21 | } 22 | -------------------------------------------------------------------------------- /Sources/CShim/include/capability.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __CAPABILITY_H 18 | #define __CAPABILITY_H 19 | 20 | #if defined(__linux__) 21 | 22 | // Capability syscall wrappers 23 | int CZ_capget(void *header, void *data); 24 | int CZ_capset(void *header, void *data); 25 | 26 | #endif 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /kernel/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -e 17 | 18 | mkdir -p /kbuild 19 | tar -xf /kernel/source.tar.xz -C /kbuild --strip-components=1 20 | cp /kernel/config-arm64 /kbuild/.config 21 | 22 | ( 23 | cd /kbuild 24 | make olddefconfig && \ 25 | make -j$((`nproc`-1)) && \ 26 | cp arch/arm64/boot/Image /kernel/vmlinux 27 | ) 28 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/IOCloser.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | protocol IOCloser: Sendable { 18 | var fileDescriptor: Int32 { get } 19 | 20 | func close() throws 21 | } 22 | -------------------------------------------------------------------------------- /scripts/ensure-hawkeye-exists.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | echo "Checking existence of hawkeye..." 17 | 18 | if command -v .local/bin/hawkeye >/dev/null 2>&1; then 19 | echo "hawkeye found!" 20 | else 21 | echo "hawkeye not found in PATH" 22 | echo "please install hawkeye. For convenience, you can run scripts/install-hawkeye.sh" 23 | exit 1 24 | fi 25 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/HostStdio.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | struct HostStdio: Sendable { 18 | let stdin: UInt32? 19 | let stdout: UInt32? 20 | let stderr: UInt32? 21 | let terminal: Bool 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Containerization/IO/ReaderStream.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// A type that returns a stream of Data. 20 | public protocol ReaderStream: Sendable { 21 | func stream() -> AsyncStream 22 | } 23 | -------------------------------------------------------------------------------- /Sources/CShim/include/vsock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // 18 | 19 | #ifndef vsock_h 20 | #define vsock_h 21 | 22 | #include 23 | 24 | #ifdef __APPLE__ 25 | #include 26 | #else 27 | #include 28 | #include 29 | #endif /* __APPLE__ */ 30 | 31 | extern const unsigned long VsockLocalCIDIoctl; 32 | 33 | #endif /* vsock_h */ 34 | -------------------------------------------------------------------------------- /Sources/Containerization/IO/Writer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// A type that writes the provided Data. 20 | public protocol Writer: Sendable { 21 | func write(_ data: Data) throws 22 | func close() throws 23 | } 24 | -------------------------------------------------------------------------------- /kernel/image/sources.list: -------------------------------------------------------------------------------- 1 | deb [arch=arm64] http://ports.ubuntu.com/ focal main restricted 2 | deb [arch=arm64] http://ports.ubuntu.com/ focal-updates main restricted 3 | deb [arch=arm64] http://ports.ubuntu.com/ focal universe 4 | deb [arch=arm64] http://ports.ubuntu.com/ focal-updates universe 5 | deb [arch=arm64] http://ports.ubuntu.com/ focal multiverse 6 | deb [arch=arm64] http://ports.ubuntu.com/ focal-updates multiverse 7 | deb [arch=arm64] http://ports.ubuntu.com/ focal-backports main restricted universe multiverse 8 | 9 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal main 10 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal-updates main restricted 11 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal universe 12 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal-updates universe 13 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal multiverse 14 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal-updates multiverse 15 | deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse -------------------------------------------------------------------------------- /scripts/install-hawkeye.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | if command -v .local/bin/hawkeye >/dev/null 2>&1; then 17 | echo "hawkeye already installed" 18 | else 19 | echo "Installing hawkeye" 20 | export VERSION=v6.1.0 21 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/korandoru/hawkeye/releases/download/${VERSION}/hawkeye-installer.sh | CARGO_HOME=.local sh -s -- --no-modify-path 22 | fi 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-feature.yml: -------------------------------------------------------------------------------- 1 | name: Feature or enhancement request 2 | description: File a request for a feature or enhancement 3 | title: "[Request]: " 4 | type: "Feature" 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for contributing to the containerization project! 10 | - type: textarea 11 | id: request 12 | attributes: 13 | label: Feature or enhancement request details 14 | description: Describe your proposed feature or enhancement. Code samples that show what's missing, or what new capabilities will be possible, are very helpful! Provide links to existing issues or external references/discussions, if appropriate. 15 | validations: 16 | required: true 17 | - type: checkboxes 18 | id: terms 19 | attributes: 20 | label: Code of Conduct 21 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/apple/.github/blob/main/CODE_OF_CONDUCT.md). 22 | options: 23 | - label: I agree to follow this project's Code of Conduct 24 | required: true 25 | -------------------------------------------------------------------------------- /Sources/CShim/capability.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #if defined(__linux__) 18 | 19 | #include 20 | #include 21 | #include "capability.h" 22 | 23 | // Capability syscall wrappers 24 | int CZ_capget(void *header, void *data) { 25 | return syscall(SYS_capget, header, data); 26 | } 27 | 28 | int CZ_capset(void *header, void *data) { 29 | return syscall(SYS_capset, header, data); 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineManager.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// A protocol to implement for virtual machine isolated containers. 18 | public protocol VirtualMachineManager: Sendable { 19 | func create(config: some VMCreationConfig) async throws -> any VirtualMachineInstance 20 | } 21 | -------------------------------------------------------------------------------- /Sources/CShim/socket_helpers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "socket_helpers.h" 18 | 19 | struct cmsghdr* CZ_CMSG_FIRSTHDR(struct msghdr *msg) { 20 | return CMSG_FIRSTHDR(msg); 21 | } 22 | 23 | void* CZ_CMSG_DATA(struct cmsghdr *cmsg) { 24 | return CMSG_DATA(cmsg); 25 | } 26 | 27 | size_t CZ_CMSG_SPACE(size_t length) { 28 | return CMSG_SPACE(length); 29 | } 30 | 31 | size_t CZ_CMSG_LEN(size_t length) { 32 | return CMSG_LEN(length); 33 | } 34 | -------------------------------------------------------------------------------- /Sources/CShim/include/socket_helpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef socket_helpers_h 18 | #define socket_helpers_h 19 | 20 | #include 21 | #include 22 | 23 | // Helper functions to access CMSG macros from Swift 24 | struct cmsghdr* CZ_CMSG_FIRSTHDR(struct msghdr *msg); 25 | void* CZ_CMSG_DATA(struct cmsghdr *cmsg); 26 | size_t CZ_CMSG_SPACE(size_t length); 27 | size_t CZ_CMSG_LEN(size_t length); 28 | 29 | #endif /* socket_helpers_h */ 30 | -------------------------------------------------------------------------------- /Sources/CShim/include/prctl.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __PRCTL_H 18 | #define __PRCTL_H 19 | 20 | #if defined(__linux__) 21 | 22 | #include 23 | 24 | // Capability management prctl wrappers 25 | int CZ_prctl_set_keepcaps(); 26 | int CZ_prctl_clear_keepcaps(); 27 | int CZ_prctl_capbset_drop(unsigned int capability); 28 | int CZ_prctl_cap_ambient_clear_all(); 29 | int CZ_prctl_cap_ambient_raise(unsigned int capability); 30 | 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/IOCloser+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOS 18 | import Foundation 19 | 20 | extension Socket: IOCloser {} 21 | 22 | extension Terminal: IOCloser { 23 | var fileDescriptor: Int32 { 24 | self.handle.fileDescriptor 25 | } 26 | } 27 | 28 | extension FileHandle: IOCloser {} 29 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/String+Extension.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | extension String { 18 | /// Removes any prefix (sha256:) from a digest string. 19 | public var trimmingDigestPrefix: String { 20 | let split = self.split(separator: ":") 21 | if split.count == 2 { 22 | return String(split[1]) 23 | } 24 | return self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/POSIXError+Helpers.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension POSIXError { 20 | public static func fromErrno() -> POSIXError { 21 | guard let errCode = POSIXErrorCode(rawValue: errno) else { 22 | fatalError("failed to convert errno to POSIXErrorCode") 23 | } 24 | return POSIXError(errCode) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineAgent+Additions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// Protocol to conform to if your agent is capable of relaying unix domain socket 18 | /// connections. 19 | public protocol SocketRelayAgent { 20 | func relaySocket(port: UInt32, configuration: UnixSocketConfiguration) async throws 21 | func stopSocketRelay(configuration: UnixSocketConfiguration) async throws 22 | } 23 | -------------------------------------------------------------------------------- /vminitd/Sources/LCShim/include/syscall.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef __SYSCALL_H 18 | #define __SYSCALL_H 19 | 20 | #include 21 | 22 | int CZ_pivot_root(const char *new_root, const char *put_old); 23 | 24 | int CZ_set_sub_reaper(); 25 | 26 | #ifndef SYS_pidfd_open 27 | #define SYS_pidfd_open 434 28 | #endif 29 | 30 | int CZ_pidfd_open(pid_t pid, unsigned int flags); 31 | 32 | #ifndef SYS_pidfd_getfd 33 | #define SYS_pidfd_getfd 438 34 | #endif 35 | 36 | int CZ_pidfd_getfd(int pidfd, int targetfd, unsigned int flags); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Sources/Containerization/NATInterface.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | public struct NATInterface: Interface { 18 | public var address: String 19 | public var gateway: String? 20 | public var macAddress: String? 21 | 22 | public init(address: String, gateway: String?, macAddress: String? = nil) { 23 | self.address = address 24 | self.gateway = gateway 25 | self.macAddress = macAddress 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Containerization/Hash.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | #if os(macOS) 18 | 19 | import Crypto 20 | import ContainerizationError 21 | 22 | public func hashMountSource(source: String) throws -> String { 23 | guard let data = source.data(using: .utf8) else { 24 | throw ContainerizationError(.invalidArgument, message: "\(source) could not be converted to Data") 25 | } 26 | return String(SHA256.hash(data: data).encoded.prefix(36)) 27 | } 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/FileManager+Size.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension FileManager { 20 | func fileSize(atPath path: String) -> Int64? { 21 | do { 22 | let attributes = try attributesOfItem(atPath: path) 23 | guard let fileSize = attributes[.size] as? NSNumber else { 24 | return nil 25 | } 26 | return fileSize.int64Value 27 | } catch { 28 | return nil 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/docs-release.yaml: -------------------------------------------------------------------------------- 1 | # Manual workflow for releasing docs ad-hoc. Workflow can only be run for main or release branches. 2 | # Workflow does NOT publish a release of containerization. 3 | name: Deploy application website 4 | 5 | permissions: 6 | contents: read 7 | 8 | on: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | checkBranch: 13 | runs-on: ubuntu-latest 14 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags') || startsWith(github.ref, 'refs/heads/release') 15 | steps: 16 | - name: Branch validation 17 | run: echo "Branch ${{ github.ref_name }} is allowed" 18 | 19 | buildSite: 20 | name: Build application website 21 | needs: checkBranch 22 | uses: ./.github/workflows/containerization-build-template.yml 23 | secrets: inherit 24 | permissions: 25 | contents: read 26 | packages: write 27 | pages: write 28 | 29 | deployDocs: 30 | runs-on: ubuntu-latest 31 | needs: [checkBranch, buildSite] 32 | permissions: 33 | contents: read 34 | pages: write 35 | id-token: write 36 | 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | 41 | steps: 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /Sources/Containerization/Container.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// The core protocol container implementations must implement. 18 | public protocol Container { 19 | /// ID for the container. 20 | var id: String { get } 21 | /// The amount of cpus assigned to the container. 22 | var cpus: Int { get } 23 | /// The memory in bytes assigned to the container. 24 | var memoryInBytes: UInt64 { get } 25 | /// The network interfaces assigned to the container. 26 | var interfaces: [any Interface] { get } 27 | } 28 | -------------------------------------------------------------------------------- /kernel/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | KSOURCE ?= https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.14.9.tar.xz 16 | KIMAGE ?= kernel-build:0.1 17 | CURDIR := $(shell pwd) 18 | 19 | .DEFAULT_GOAL := all 20 | 21 | .PHONY: all 22 | all: kernel-build-image 23 | all: kernel-build 24 | 25 | .PHONY: kernel-build-image 26 | kernel-build-image: 27 | container build image/ -f image/Dockerfile -t ${KIMAGE} 28 | 29 | .PHONY: kernel-build 30 | kernel-build: 31 | ifeq (,$(wildcard source.tar.xz)) 32 | curl -SsL -o source.tar.xz ${KSOURCE} 33 | endif 34 | container run \ 35 | --cpus 8 \ 36 | --rm \ 37 | --memory 16g \ 38 | -v ${CURDIR}:/kernel \ 39 | --cwd /kernel \ 40 | ${KIMAGE} \ 41 | /bin/bash -c "./build.sh" -------------------------------------------------------------------------------- /Sources/Containerization/Interface.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// A network interface. 18 | public protocol Interface: Sendable { 19 | /// The interface IPv4 address and subnet prefix length, as a CIDR address. 20 | /// Example: `192.168.64.3/24` 21 | var address: String { get } 22 | 23 | /// The IP address for the default route, or nil for no default route. 24 | var gateway: String? { get } 25 | 26 | /// The interface MAC address, or nil to auto-configure the address. 27 | var macAddress: String? { get } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Version.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | public struct RuntimeSpecVersion: Sendable { 18 | public let major, minor, patch: Int 19 | public let dev: String 20 | 21 | public static let current = RuntimeSpecVersion( 22 | major: 1, 23 | minor: 0, 24 | patch: 2, 25 | dev: "-dev" 26 | ) 27 | 28 | public init(major: Int, minor: Int, patch: Int, dev: String) { 29 | self.major = major 30 | self.minor = minor 31 | self.patch = patch 32 | self.dev = dev 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Sysctl.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// Helper type to deal with system control functionalities. 20 | public struct Sysctl { 21 | #if os(macOS) 22 | /// Simple `sysctlbyname` wrapper. 23 | public static func byName(_ name: String) throws -> Int64 { 24 | var num: Int64 = 0 25 | var size = MemoryLayout.size 26 | if sysctlbyname(name, &num, &size, nil, 0) != 0 { 27 | throw POSIXError.fromErrno() 28 | } 29 | return num 30 | } 31 | #endif 32 | } 33 | -------------------------------------------------------------------------------- /vminitd/Sources/LCShim/syscall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #include "syscall.h" 22 | 23 | int CZ_pivot_root(const char *new_root, const char *put_old) { 24 | return syscall(SYS_pivot_root, new_root, put_old); 25 | } 26 | 27 | int CZ_set_sub_reaper() { return prctl(PR_SET_CHILD_SUBREAPER, 1); } 28 | 29 | int CZ_pidfd_open(pid_t pid, unsigned int flags) { 30 | // Musl doesn't have pidfd_open. 31 | return syscall(SYS_pidfd_open, pid, flags); 32 | } 33 | 34 | int CZ_pidfd_getfd(int pidfd, int targetfd, unsigned int flags) { 35 | // Musl doesn't have pidfd_getfd. 36 | return syscall(SYS_pidfd_getfd, pidfd, targetfd, flags); 37 | } 38 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/SHA256+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Crypto 18 | import Foundation 19 | 20 | extension SHA256.Digest { 21 | /// Returns the digest as a string. 22 | public var digestString: String { 23 | let parts = self.description.split(separator: ": ") 24 | return "sha256:\(parts[1])" 25 | } 26 | 27 | /// Returns the digest without a 'sha256:' prefix. 28 | public var encoded: String { 29 | let parts = self.description.split(separator: ": ") 30 | return String(parts[1]) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/FileManager+Temporary.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension FileManager { 20 | /// Returns a unique temporary directory to use. 21 | public func uniqueTemporaryDirectory(create: Bool = true) -> URL { 22 | let tempDirectoryURL = temporaryDirectory 23 | let uniqueDirectoryURL = tempDirectoryURL.appendingPathComponent(UUID().uuidString) 24 | if create { 25 | try? createDirectory(at: uniqueDirectoryURL, withIntermediateDirectories: true, attributes: nil) 26 | } 27 | return uniqueDirectoryURL 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/TempDir.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationExtras 18 | import Foundation 19 | 20 | internal func createTemporaryDirectory(baseName: String) -> URL? { 21 | let url = FileManager.default.uniqueTemporaryDirectory().appendingPathComponent( 22 | "\(baseName).XXXXXX") 23 | guard let templatePathData = (url.absoluteURL.path as NSString).utf8String else { 24 | return nil 25 | } 26 | 27 | let pathData = UnsafeMutablePointer(mutating: templatePathData) 28 | mkdtemp(pathData) 29 | 30 | return URL(fileURLWithPath: String(cString: pathData), isDirectory: true) 31 | } 32 | -------------------------------------------------------------------------------- /examples/ctr-example/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # ctr-example Makefile 16 | 17 | SWIFT = /usr/bin/swift 18 | 19 | .PHONY: all build clean run 20 | 21 | all: run 22 | 23 | build: 24 | $(SWIFT) build --configuration release 25 | codesign --force --sign - --entitlements ctr-example.entitlements ./.build/release/ctr-example 26 | cp ./.build/release/ctr-example ./ctr-example 27 | 28 | clean: 29 | $(SWIFT) package clean 30 | rm -f ./ctr-example 31 | 32 | run: build 33 | ./ctr-example 34 | 35 | # Development targets 36 | debug: 37 | $(SWIFT) build 38 | codesign --force --sign - --entitlements ctr-example.entitlements ./.build/debug/ctr-example 39 | 40 | fmt: 41 | $(SWIFT) format --in-place --recursive Sources/ 42 | fetch-default-kernel: 43 | $(MAKE) -C ../.. fetch-default-kernel 44 | cp -L ../../.local/vmlinux ./vmlinux 45 | -------------------------------------------------------------------------------- /Sources/Containerization/Vminitd+Rosetta.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOS 18 | import Foundation 19 | 20 | extension Vminitd { 21 | /// Enable Rosetta's x86_64 emulation. 22 | public func enableRosetta() async throws { 23 | let path = "/run/rosetta" 24 | try await self.mount( 25 | .init( 26 | type: "virtiofs", 27 | source: "rosetta", 28 | destination: path 29 | ) 30 | ) 31 | try await self.setupEmulator( 32 | binaryPath: "\(path)/rosetta", 33 | configuration: Binfmt.Entry.amd64() 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension URL { 20 | /// Returns the unescaped absolutePath of a URL joined by separator. 21 | public func absolutePath() -> String { 22 | #if os(macOS) 23 | return self.path(percentEncoded: false) 24 | #else 25 | return self.path 26 | #endif 27 | } 28 | 29 | /// Returns the domain name of a registry. 30 | public var domain: String? { 31 | guard let host = self.absoluteString.split(separator: ":").first else { 32 | return nil 33 | } 34 | return String(host) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/AnnotationKeys.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// AnnotationKeys contains a subset of "dictionary keys" for commonly used annotations in an OCI Image Descriptor 18 | /// https://github.com/opencontainers/image-spec/blob/main/annotations.md 19 | public struct AnnotationKeys: Codable, Sendable { 20 | public static let containerizationIndexIndirect = "com.apple.containerization.index.indirect" 21 | public static let containerizationImageName = "com.apple.containerization.image.name" 22 | public static let containerdImageName = "io.containerd.image.name" 23 | public static let openContainersImageName = "org.opencontainers.image.ref.name" 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Containerization/IO/Terminal+ReaderStream.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOS 18 | import Foundation 19 | 20 | extension Terminal: ReaderStream { 21 | public func stream() -> AsyncStream { 22 | .init { cont in 23 | self.handle.readabilityHandler = { handle in 24 | let data = handle.availableData 25 | if data.isEmpty { 26 | self.handle.readabilityHandler = nil 27 | cont.finish() 28 | return 29 | } 30 | cont.yield(data) 31 | } 32 | } 33 | } 34 | } 35 | 36 | extension Terminal: Writer {} 37 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security disclosure process 2 | 3 | If you believe that you have discovered a security or privacy vulnerability in our open source software, please report it to us using the [GitHub private vulnerability feature](https://github.com/apple/containerization/security/advisories/new). Reports should include specific product and software version(s) that you believe are affected; a technical description of the behavior that you observed and the behavior that you expected; the steps required to reproduce the issue; and a proof of concept or exploit. 4 | 5 | The project team will do their best to acknowledge receiving all security reports within 7 days of submission. This initial acknowledgment is neither acceptance nor rejection of your report. The project team may come back to you with further questions or invite you to collaborate while working through the details of your report. 6 | 7 | Keep these additional guidelines in mind when submitting your report: 8 | 9 | * Reports concerning known, publicly disclosed CVEs can be submitted as normal issues to this project. 10 | * Output from automated security scans or fuzzers MUST include additional context demonstrating the vulnerability with a proof of concept or working exploit. 11 | * Application crashes due to malformed inputs are typically not treated as security vulnerabilities, unless they are shown to also impact other processes on the system. 12 | 13 | While we welcome reports for open source software projects, they are not eligible for Apple Security Bounties. 14 | -------------------------------------------------------------------------------- /Sources/Containerization/ExitStatus.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// ExitStatus contains the exit code for a given container process, 20 | /// as well as the timestamp at which it exited. 21 | public struct ExitStatus: Sendable { 22 | /// The exit code for the process. 23 | public var exitCode: Int32 24 | /// The timestamp when the process exited. 25 | public var exitedAt: Date 26 | 27 | public init(exitCode: Int32) { 28 | self.exitCode = exitCode 29 | self.exitedAt = .now 30 | } 31 | 32 | public init(exitCode: Int32, exitedAt: Date) { 33 | self.exitCode = exitCode 34 | self.exitedAt = exitedAt 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests/ContainsAuth.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOCI 18 | import Foundation 19 | 20 | internal protocol ContainsAuth { 21 | 22 | } 23 | 24 | extension ContainsAuth { 25 | static var hasRegistryCredentials: Bool { 26 | authentication != nil 27 | } 28 | 29 | static var authentication: Authentication? { 30 | let env = ProcessInfo.processInfo.environment 31 | guard let password = env["REGISTRY_TOKEN"], 32 | let username = env["REGISTRY_USERNAME"] 33 | else { 34 | return nil 35 | } 36 | return BasicAuthentication(username: username, password: password) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Syscall.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | #if canImport(Musl) 18 | import Musl 19 | #elseif canImport(Glibc) 20 | import Glibc 21 | #elseif canImport(Darwin) 22 | import Darwin 23 | #else 24 | #error("retryingSyscall not supported on this platform.") 25 | #endif 26 | 27 | /// Helper type to deal with running system calls. 28 | public struct Syscall { 29 | /// Retry a syscall on EINTR. 30 | public static func retrying(_ closure: () -> T) -> T { 31 | while true { 32 | let res = closure() 33 | if res == -1 && errno == EINTR { 34 | continue 35 | } 36 | return res 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/ctr-example/lab.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## Install and test the container tool: 4 | 5 | See https://github.com/apple/container/releases 6 | 7 | Once installed, start the service and follow prompts. 8 | 9 | ```bash 10 | container system start 11 | ``` 12 | 13 | This'll install your kernel. 14 | 15 | After this start your first container. On first launch, this'll install another artifact for our guest init process: 16 | 17 | ``` 18 | container run alpine uname 19 | ``` 20 | 21 | Container starts after this will be fast! 22 | 23 | ## Get the Containerization sources: 24 | 25 | ```bash 26 | $ git clone https://github.com/apple/containerization.git 27 | ``` 28 | 29 | > [!IMPORTANT] 30 | > There is a bug in the `vmnet` framework on macOS 26 that causes network creation to fail if the creating applications are located under your `Documents` or `Desktop` directories. To workaround this, clone the project elsewhere, such as `~/projects/containerization`, until this issue is resolved. 31 | 32 | ## Take a look at ctr-example 33 | 34 | Read through the sources: 35 | 36 | - ContainerManager: 37 | - manager.create() 38 | - container.create(), start(), wait(), stop() 39 | 40 | ## Fetch the kernel 41 | 42 | Run 43 | 44 | ```bash 45 | cp "$(ls -t ~/Library/Application\ Support/com.apple.container/kernels/vmlinux-* | head -1)" ./vmlinux 46 | ``` 47 | 48 | ## Build and run the example 49 | 50 | ```bash 51 | $ cd examples/ctr-example 52 | $ make 53 | ``` 54 | 55 | ## Modify the project 56 | 57 | - Change the command run by the container 58 | - Change the image 59 | -------------------------------------------------------------------------------- /examples/ctr-example/README.md: -------------------------------------------------------------------------------- 1 | # Container Example 2 | 3 | Very basic example of launching a Linux container using Containerization. 4 | 5 | ## Build and Run 6 | 7 | ### 1. Fetch Kernel 8 | 9 | In your terminal, change directories to examples/ctr-example and run: 10 | 11 | **Option A: Using Makefile (recommended)** 12 | ```bash 13 | make fetch-default-kernel 14 | ``` 15 | 16 | **Option B: Copy from installed container tool** 17 | ```bash 18 | cp "$(ls -t ~/Library/Application\ Support/com.apple.container/kernels/vmlinux-* | head -1)" ./vmlinux 19 | ``` 20 | 21 | You should now see the `vmlinux` image in examples/ctr-example 22 | 23 | ### 2. Build/Run ctr-example 24 | 25 | From examples/ctr-example run 26 | `make all` 27 | 28 | > [!WARNING] 29 | > If you get the following error, try building from the default macOS terminal: 30 | > `error: compiled module was created by a newer version of the compiler` 31 | 32 | After the build completes, the example will run. In your terminal you should see something like: 33 | 34 | ``` 35 | Starting container example... 36 | Fetching container initial filesystem... 37 | Creating container from docker.io/library/alpine:3.16... 38 | Starting container... 39 | / # 40 | ``` 41 | 42 | > [!WARNING] 43 | > If you get the following error, try moving the `ctr-example` binary to `/var/tmp` and run it from there. 44 | > `Swift/ErrorType.swift:254: Fatal error: Error raised at top level: unsupported: "failed to create vmnet network with status vmnet_return_t(rawValue: 1001)"` 45 | 46 | **Congratulations, you've started the example container!** 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release containerization 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | tags: 9 | - "[0-9]+\\.[0-9]+\\.[0-9]+" 10 | 11 | jobs: 12 | containerization: 13 | uses: ./.github/workflows/containerization-build-template.yml 14 | with: 15 | release: true 16 | version: ${{ github.ref_name }} 17 | secrets: inherit 18 | permissions: 19 | contents: read 20 | packages: write 21 | pages: write 22 | 23 | deployDocs: 24 | if: startsWith(github.ref, 'refs/tags/') 25 | runs-on: ubuntu-latest 26 | needs: containerization 27 | permissions: 28 | contents: read 29 | pages: write 30 | id-token: write 31 | environment: 32 | name: github-pages 33 | url: ${{ steps.deployment.outputs.page_url }} 34 | steps: 35 | - name: Deploy to GitHub Pages 36 | id: deployment 37 | uses: actions/deploy-pages@v4 38 | 39 | release: 40 | if: startsWith(github.ref, 'refs/tags/') 41 | name: Publish release 42 | timeout-minutes: 30 43 | needs: containerization 44 | runs-on: ubuntu-latest 45 | permissions: 46 | contents: write 47 | packages: read 48 | steps: 49 | - name: Create release 50 | uses: softprops/action-gh-release@v2 51 | with: 52 | token: ${{ github.token }} 53 | name: ${{ github.ref_name }}-prerelease 54 | draft: true 55 | make_latest: false 56 | prerelease: true 57 | fail_on_unmatched_files: true 58 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/HashTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import Containerization 21 | 22 | struct HashTests { 23 | 24 | @Test func hashMountSourceWithValidString() throws { 25 | let result = try hashMountSource(source: "/valid/path") 26 | 27 | // Should produce a non-empty hash 28 | #expect(!result.isEmpty) 29 | 30 | // Same input should produce same hash (deterministic) 31 | let result2 = try hashMountSource(source: "/valid/path") 32 | #expect(result == result2) 33 | 34 | // Different inputs should produce different hashes 35 | let result3 = try hashMountSource(source: "/different/path") 36 | #expect(result != result3) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/CShim/prctl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #if defined(__linux__) 18 | 19 | #include 20 | #include "prctl.h" 21 | 22 | // Set keep caps to preserve capabilities across setuid() 23 | int CZ_prctl_set_keepcaps() { 24 | return prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); 25 | } 26 | 27 | // Clear keep caps after user change 28 | int CZ_prctl_clear_keepcaps() { 29 | return prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); 30 | } 31 | 32 | // Drop capability from bounding set 33 | int CZ_prctl_capbset_drop(unsigned int capability) { 34 | return prctl(PR_CAPBSET_DROP, capability, 0, 0, 0); 35 | } 36 | 37 | // Clear all ambient capabilities 38 | int CZ_prctl_cap_ambient_clear_all() { 39 | return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); 40 | } 41 | 42 | // Raise ambient capability 43 | int CZ_prctl_cap_ambient_raise(unsigned int capability) { 44 | return prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, capability, 0, 0); 45 | } 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOCI 18 | import Foundation 19 | import Testing 20 | 21 | @testable import Containerization 22 | 23 | struct ImageTests { 24 | 25 | @Test func imageDescriptionComputedProperties() { 26 | let descriptor = Descriptor( 27 | mediaType: "application/vnd.oci.image.manifest.v1+json", 28 | digest: "sha256:abc123def456", 29 | size: 1024 30 | ) 31 | let description = Image.Description(reference: "myapp:latest", descriptor: descriptor) 32 | 33 | #expect(description.digest == "sha256:abc123def456") 34 | #expect(description.mediaType == "application/vnd.oci.image.manifest.v1+json") 35 | #expect(description.reference == "myapp:latest") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/ctr-example/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.2 2 | //===----------------------------------------------------------------------===// 3 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // https://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | //===----------------------------------------------------------------------===// 17 | 18 | import PackageDescription 19 | 20 | let scVersion = "0.13.0" 21 | 22 | let package = Package( 23 | name: "ctr-example", 24 | platforms: [ 25 | .macOS("26.0") 26 | ], 27 | products: [ 28 | .executable( 29 | name: "ctr-example", 30 | targets: ["ctr-example"] 31 | ) 32 | ], 33 | dependencies: [ 34 | .package(url: "https://github.com/apple/containerization.git", exact: Version(stringLiteral: scVersion)) 35 | ], 36 | targets: [ 37 | .executableTarget( 38 | name: "ctr-example", 39 | dependencies: [ 40 | .product(name: "Containerization", package: "containerization"), 41 | .product(name: "ContainerizationOS", package: "containerization"), 42 | ] 43 | ) 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/MountTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import Containerization 21 | 22 | struct MountTests { 23 | 24 | @Test func mountShareCreatesVirtiofsMount() { 25 | let mount = Mount.share( 26 | source: "/host/shared", 27 | destination: "/guest/shared", 28 | options: ["rw", "noatime"], 29 | runtimeOptions: ["tag=shared"] 30 | ) 31 | 32 | #expect(mount.type == "virtiofs") 33 | #expect(mount.source == "/host/shared") 34 | #expect(mount.destination == "/guest/shared") 35 | #expect(mount.options == ["rw", "noatime"]) 36 | 37 | if case .virtiofs(let opts) = mount.runtimeOptions { 38 | #expect(opts == ["tag=shared"]) 39 | } else { 40 | #expect(Bool(false), "Expected virtiofs runtime options") 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Pipe+Close.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension Pipe { 20 | /// Close both sides of the pipe. 21 | public func close() throws { 22 | var err: Swift.Error? 23 | do { 24 | try self.fileHandleForReading.close() 25 | } catch { 26 | err = error 27 | } 28 | try self.fileHandleForWriting.close() 29 | if let err { 30 | throw err 31 | } 32 | } 33 | 34 | /// Ensure that both sides of the pipe are set with O_CLOEXEC. 35 | public func setCloexec() throws { 36 | if fcntl(self.fileHandleForWriting.fileDescriptor, F_SETFD, FD_CLOEXEC) == -1 { 37 | throw POSIXError(.init(rawValue: errno)!) 38 | } 39 | if fcntl(self.fileHandleForReading.fileDescriptor, F_SETFD, FD_CLOEXEC) == -1 { 40 | throw POSIXError(.init(rawValue: errno)!) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/AsyncTypes.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | package actor AsyncStore { 18 | private var _value: T? 19 | 20 | package init(_ value: T? = nil) { 21 | self._value = value 22 | } 23 | 24 | package func get() -> T? { 25 | self._value 26 | } 27 | 28 | package func set(_ value: T) { 29 | self._value = value 30 | } 31 | } 32 | 33 | package actor AsyncSet { 34 | private var buffer: Set 35 | 36 | package init(_ elements: S) where S.Element == T { 37 | buffer = Set(elements) 38 | } 39 | 40 | package var count: Int { 41 | buffer.count 42 | } 43 | 44 | package func insert(_ element: T) { 45 | buffer.insert(element) 46 | } 47 | 48 | @discardableResult 49 | package func remove(_ element: T) -> T? { 50 | buffer.remove(element) 51 | } 52 | 53 | package func contains(_ element: T) -> Bool { 54 | buffer.contains(element) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Containerization/SystemPlatform.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOCI 18 | 19 | /// `SystemPlatform` describes an operating system and architecture pair. 20 | /// This is primarily used to choose what kind of OCI image to pull from a 21 | /// registry. 22 | public struct SystemPlatform: Sendable, Codable { 23 | public enum OS: String, CaseIterable, Sendable, Codable { 24 | case linux 25 | case darwin 26 | } 27 | public let os: OS 28 | 29 | public enum Architecture: String, CaseIterable, Sendable, Codable { 30 | case arm64 31 | case amd64 32 | } 33 | public let architecture: Architecture 34 | 35 | public func ociPlatform() -> ContainerizationOCI.Platform { 36 | ContainerizationOCI.Platform(arch: architecture.rawValue, os: os.rawValue) 37 | } 38 | 39 | public static var linuxArm: SystemPlatform { .init(os: .linux, architecture: .arm64) } 40 | public static var linuxAmd: SystemPlatform { .init(os: .linux, architecture: .amd64) } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/ProgressEvent.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// A progress update event. 18 | public struct ProgressEvent: Sendable { 19 | /// The event name. The possible values: 20 | /// - `add-items`: Increment the number of processed items by `value`. 21 | /// - `add-total-items`: Increment the total number of items to process by `value`. 22 | /// - `add-size`: Increment the size of processed items by `value`. 23 | /// - `add-total-size`: Increment the total size of items to process by `value`. 24 | public let event: String 25 | /// The event value. 26 | public let value: any Sendable 27 | 28 | /// Creates an instance. 29 | /// - Parameters: 30 | /// - event: The event name. 31 | /// - value: The event value. 32 | public init(event: String, value: any Sendable) { 33 | self.event = event 34 | self.value = value 35 | } 36 | } 37 | 38 | /// The progress update handler. 39 | public typealias ProgressHandler = @Sendable (_ events: [ProgressEvent]) async -> Void 40 | -------------------------------------------------------------------------------- /Sources/CShim/include/exec_command.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef exec_command_h 18 | #define exec_command_h 19 | 20 | #if defined(__linux__) || defined(__APPLE__) 21 | 22 | #include 23 | #include 24 | 25 | struct exec_command_attrs { 26 | int setpgid; 27 | /// parent group id 28 | pid_t pgid; 29 | /// set the controlling terminal 30 | int setctty; 31 | /// controlling terminal fd 32 | int ctty; 33 | /// set the process as session leader 34 | int setsid; 35 | /// set the process user id 36 | uid_t uid; 37 | /// set the process group id 38 | gid_t gid; 39 | /// signal mask for the child process 40 | int mask; 41 | /// parent death signal (Linux only, 0 to disable) 42 | int pdeathSignal; 43 | }; 44 | 45 | void exec_command_attrs_init(struct exec_command_attrs *attrs); 46 | 47 | /// spawn a new child process with the provided attrs 48 | int exec_command(pid_t *result, const char *executable, char *const argv[], 49 | char *const envp[], const int file_handles[], 50 | const int file_handle_count, const char *working_directory, 51 | struct exec_command_attrs *attrs); 52 | 53 | #endif /* defined(__linux__) || defined(__APPLE__) */ 54 | #endif /* exec_command_h */ 55 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/BinaryInteger+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | extension BinaryInteger { 18 | private func toUnsignedMemoryAmount(_ amount: UInt64) -> UInt64 { 19 | guard self >= 0 else { 20 | fatalError("encountered negative number during conversion to memory amount") 21 | } 22 | let val = UInt64(self) 23 | let (newVal, overflow) = val.multipliedReportingOverflow(by: amount) 24 | guard !overflow else { 25 | fatalError("UInt64 overflow when converting to memory amount") 26 | } 27 | return newVal 28 | } 29 | 30 | public func kib() -> UInt64 { 31 | self.toUnsignedMemoryAmount(1 << 10) 32 | } 33 | 34 | public func mib() -> UInt64 { 35 | self.toUnsignedMemoryAmount(1 << 20) 36 | } 37 | 38 | public func gib() -> UInt64 { 39 | self.toUnsignedMemoryAmount(1 << 30) 40 | } 41 | 42 | public func tib() -> UInt64 { 43 | self.toUnsignedMemoryAmount(1 << 40) 44 | } 45 | 46 | public func pib() -> UInt64 { 47 | self.toUnsignedMemoryAmount(1 << 50) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scripts/make-docs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | opts=() 17 | opts+=("--allow-writing-to-directory" "$1") 18 | opts+=("generate-documentation") 19 | opts+=("--target" "Containerization") 20 | opts+=("--target" "ContainerizationArchive") 21 | opts+=("--target" "ContainerizationError") 22 | opts+=("--target" "ContainerizationEXT4") 23 | opts+=("--target" "ContainerizationExtras") 24 | opts+=("--target" "ContainerizationIO") 25 | opts+=("--target" "ContainerizationNetlink") 26 | opts+=("--target" "ContainerizationOCI") 27 | opts+=("--target" "ContainerizationOS") 28 | opts+=("--output-path" "$1") 29 | opts+=("--disable-indexing") 30 | opts+=("--transform-for-static-hosting") 31 | opts+=("--enable-experimental-combined-documentation") 32 | opts+=("--experimental-documentation-coverage") 33 | 34 | if [ ! -z "$2" ] ; then 35 | opts+=("--hosting-base-path" "$2") 36 | fi 37 | 38 | /usr/bin/swift package ${opts[@]} 39 | 40 | echo '{}' > "$1/theme-settings.json" 41 | 42 | cat > "$1/index.html" <<'EOF' 43 | 44 | 45 | 46 | 47 | 48 | 49 |

If you are not redirected automatically, click here.

50 | 51 | 52 | EOF 53 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Reaper.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// A process reaper that returns exited processes along 20 | /// with their exit status. 21 | public struct Reaper { 22 | /// Process's pid and exit status. 23 | typealias Exit = (pid: Int32, status: Int32) 24 | 25 | /// Reap all pending processes and return the pid and exit status. 26 | public static func reap() -> [Int32: Int32] { 27 | var reaped = [Int32: Int32]() 28 | while true { 29 | guard let exit = wait() else { 30 | return reaped 31 | } 32 | reaped[exit.pid] = exit.status 33 | } 34 | return reaped 35 | } 36 | 37 | /// Returns the exit status of the last process that exited. 38 | /// nil is returned when no pending processes exist. 39 | private static func wait() -> Exit? { 40 | var rus = rusage() 41 | var ws = Int32() 42 | 43 | let pid = wait4(-1, &ws, WNOHANG, &rus) 44 | if pid <= 0 { 45 | return nil 46 | } 47 | return (pid: pid, status: Command.toExitStatus(ws)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/FileArchiveWriterDelegate.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import SystemPackage 19 | 20 | internal final class FileArchiveWriterDelegate { 21 | public let path: FilePath 22 | private var fd: FileDescriptor! 23 | 24 | public init(path: FilePath) { 25 | self.path = path 26 | } 27 | 28 | public convenience init(url: URL) { 29 | self.init(path: FilePath(url.path)) 30 | } 31 | 32 | public func open(archive: ArchiveWriter) throws { 33 | self.fd = try FileDescriptor.open( 34 | self.path, .writeOnly, options: [.create, .append], permissions: [.groupRead, .otherRead, .ownerReadWrite]) 35 | } 36 | 37 | public func write(archive: ArchiveWriter, buffer: UnsafeRawBufferPointer) throws -> Int { 38 | try fd.write(buffer) 39 | } 40 | 41 | public func close(archive: ArchiveWriter) throws { 42 | try self.fd.close() 43 | } 44 | 45 | public func free(archive: ArchiveWriter) { 46 | self.fd = nil 47 | } 48 | 49 | deinit { 50 | if let fd = self.fd { 51 | try? fd.close() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/ContainerizationOSTests/KeychainQueryTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // 18 | 19 | import Foundation 20 | import Testing 21 | 22 | @testable import ContainerizationOS 23 | 24 | struct KeychainQueryTests { 25 | let id = "com.example.container-testing-keychain" 26 | let domain = "testing-keychain.example.com" 27 | let user = "containerization-test" 28 | 29 | let kq = KeychainQuery() 30 | 31 | @Test(.enabled(if: !isCI)) 32 | func keychainQuery() throws { 33 | defer { try? kq.delete(id: id, host: domain) } 34 | 35 | do { 36 | try kq.save(id: id, host: domain, user: user, token: "foobar") 37 | #expect(try kq.exists(id: id, host: domain)) 38 | 39 | let fetched = try kq.get(id: id, host: domain) 40 | let result = try #require(fetched) 41 | #expect(result.account == user) 42 | #expect(result.data == "foobar") 43 | } catch KeychainQuery.Error.unhandledError(status: -25308) { 44 | // ignore errSecInteractionNotAllowed 45 | } 46 | } 47 | 48 | private static var isCI: Bool { 49 | ProcessInfo.processInfo.environment["CI"] != nil 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Containerization/Image/Unpacker/Unpacker.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationExtras 18 | import ContainerizationOCI 19 | import Foundation 20 | 21 | /// The `Unpacker` protocol defines a standardized interface that involves 22 | /// decompressing, extracting image layers and preparing it for use. 23 | /// 24 | /// The `Unpacker` is responsible for managing the lifecycle of the 25 | /// unpacking process, including any temporary files or resources, until the 26 | /// `Mount` object is produced. 27 | public protocol Unpacker { 28 | 29 | /// Unpacks the provided image to a specified path for a given platform. 30 | /// 31 | /// This asynchronous method should handle the entire unpacking process, from reading 32 | /// the `Image` layers for the given `Platform` via its `Manifest`, 33 | /// to making the extracted contents available as a `Mount`. 34 | /// Implementations of this method may apply platform-specific optimizations 35 | /// or transformations during the unpacking. 36 | /// 37 | /// Progress updates can be observed via the optional `progress` handler. 38 | func unpack(_ image: Image, for platform: Platform, at path: URL, progress: ProgressHandler?) async throws -> Mount 39 | 40 | } 41 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/DNSTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import Containerization 21 | 22 | struct DNSTests { 23 | 24 | @Test func dnsResolvConfWithAllFields() { 25 | let dns = DNS( 26 | nameservers: ["8.8.8.8", "1.1.1.1"], 27 | domain: "example.com", 28 | searchDomains: ["internal.com", "test.com"], 29 | options: ["ndots:2", "timeout:1"] 30 | ) 31 | 32 | let expected = "nameserver 8.8.8.8\nnameserver 1.1.1.1\ndomain example.com\nsearch internal.com test.com\noptions ndots:2 timeout:1\n" 33 | #expect(dns.resolvConf == expected) 34 | } 35 | 36 | @Test func dnsResolvConfWithEmptyFields() { 37 | let dns = DNS( 38 | nameservers: [], 39 | domain: nil, 40 | searchDomains: [], 41 | options: [] 42 | ) 43 | 44 | // Should return empty string when all fields are empty 45 | #expect(dns.resolvConf == "") 46 | } 47 | 48 | @Test func dnsResolvConfWithOnlyNameservers() { 49 | let dns = DNS(nameservers: ["8.8.8.8"]) 50 | 51 | let expected = "nameserver 8.8.8.8\n" 52 | #expect(dns.resolvConf == expected) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Socket/SocketType.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | #if canImport(Musl) 18 | import Musl 19 | #elseif canImport(Glibc) 20 | import Glibc 21 | #elseif canImport(Darwin) 22 | import Darwin 23 | #else 24 | #error("SocketType not supported on this platform.") 25 | #endif 26 | 27 | /// Protocol used to describe the family of socket to be created with `Socket`. 28 | public protocol SocketType: Sendable, CustomStringConvertible { 29 | /// The domain for the socket (AF_UNIX, AF_VSOCK etc.) 30 | var domain: Int32 { get } 31 | /// The type of socket (SOCK_STREAM). 32 | var type: Int32 { get } 33 | 34 | /// Actions to perform before calling bind(2). 35 | func beforeBind(fd: Int32) throws 36 | /// Actions to perform before calling listen(2). 37 | func beforeListen(fd: Int32) throws 38 | 39 | /// Handle accept(2) for an implementation of a socket type. 40 | func accept(fd: Int32) throws -> (Int32, SocketType) 41 | /// Provide a sockaddr pointer (by casting a socket specific type like sockaddr_un for example). 42 | func withSockAddr(_ closure: (_ ptr: UnsafePointer, _ len: UInt32) throws -> Void) throws 43 | } 44 | 45 | extension SocketType { 46 | public func beforeBind(fd: Int32) {} 47 | public func beforeListen(fd: Int32) {} 48 | } 49 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/FileTimestamps.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | public struct FileTimestamps { 20 | public var access: Date 21 | public var modification: Date 22 | public var creation: Date 23 | public var now: Date 24 | 25 | public var accessLo: UInt32 { 26 | access.fs().lo 27 | } 28 | 29 | public var accessHi: UInt32 { 30 | access.fs().hi 31 | } 32 | 33 | public var modificationLo: UInt32 { 34 | modification.fs().lo 35 | } 36 | 37 | public var modificationHi: UInt32 { 38 | modification.fs().hi 39 | } 40 | 41 | public var creationLo: UInt32 { 42 | creation.fs().lo 43 | } 44 | 45 | public var creationHi: UInt32 { 46 | creation.fs().hi 47 | } 48 | 49 | public var nowLo: UInt32 { 50 | now.fs().lo 51 | } 52 | 53 | public var nowHi: UInt32 { 54 | now.fs().hi 55 | } 56 | 57 | public init(access: Date?, modification: Date?, creation: Date?) { 58 | now = Date() 59 | self.access = access ?? now 60 | self.modification = modification ?? now 61 | self.creation = creation ?? now 62 | } 63 | 64 | public init() { 65 | self.init(access: nil, modification: nil, creation: nil) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Tests/ContainerizationExtrasTests/TestTimeout.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import ContainerizationExtras 21 | 22 | final class TestTimeoutType { 23 | @Test 24 | func testNoCancellation() async throws { 25 | await #expect(throws: Never.self) { 26 | try await Timeout.run( 27 | for: .seconds(5), 28 | operation: { 29 | return 30 | }) 31 | } 32 | } 33 | 34 | @Test 35 | func testCancellationError() async throws { 36 | await #expect(throws: CancellationError.self) { 37 | try await Timeout.run( 38 | for: .milliseconds(50), 39 | operation: { 40 | try await Task.sleep(for: .seconds(2)) 41 | }) 42 | } 43 | } 44 | 45 | @Test 46 | func testClosureError() async throws { 47 | // Check that we get the closures error if we don't timeout, but 48 | // the closure does throw before. 49 | await #expect(throws: POSIXError.self) { 50 | try await Timeout.run( 51 | for: .seconds(10), 52 | operation: { 53 | throw POSIXError(.E2BIG) 54 | }) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/cctl/cctl+Utils.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Containerization 18 | import ContainerizationError 19 | import ContainerizationOCI 20 | import Foundation 21 | 22 | extension Application { 23 | static func fetchImage(reference: String, store: ImageStore) async throws -> Containerization.Image { 24 | do { 25 | return try await store.get(reference: reference) 26 | } catch let error as ContainerizationError { 27 | if error.code == .notFound { 28 | return try await store.pull(reference: reference) 29 | } 30 | throw error 31 | } 32 | } 33 | 34 | static func parseKeyValuePairs(from items: [String]) -> [String: String] { 35 | var parsedLabels: [String: String] = [:] 36 | for item in items { 37 | let parts = item.split(separator: "=", maxSplits: 1) 38 | guard parts.count == 2 else { 39 | continue 40 | } 41 | let key = String(parts[0]) 42 | let val = String(parts[1]) 43 | parsedLabels[key] = val 44 | } 45 | return parsedLabels 46 | } 47 | } 48 | 49 | extension ContainerizationOCI.Platform { 50 | static var arm64: ContainerizationOCI.Platform { 51 | .init(arch: "arm64", os: "linux", variant: "v8") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vminitd/Sources/vmexec/Console.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Musl 19 | 20 | class Console { 21 | let master: Int32 22 | let slavePath: String 23 | 24 | init() throws { 25 | let masterFD = open("/dev/ptmx", O_RDWR | O_NOCTTY | O_CLOEXEC) 26 | guard masterFD != -1 else { 27 | throw App.Errno(stage: "open_ptmx") 28 | } 29 | 30 | guard unlockpt(masterFD) == 0 else { 31 | throw App.Errno(stage: "unlockpt") 32 | } 33 | 34 | guard let slavePath = ptsname(masterFD) else { 35 | throw App.Errno(stage: "ptsname") 36 | } 37 | 38 | self.master = masterFD 39 | self.slavePath = String(cString: slavePath) 40 | } 41 | 42 | func configureStdIO() throws { 43 | let path = self.slavePath 44 | let slaveFD = open(path, O_RDWR) 45 | guard slaveFD != -1 else { 46 | throw App.Errno(stage: "open_pts") 47 | } 48 | defer { Musl.close(slaveFD) } 49 | 50 | for fd: Int32 in 0...2 { 51 | guard dup3(slaveFD, fd, 0) != -1 else { 52 | throw App.Errno(stage: "dup3") 53 | } 54 | } 55 | } 56 | 57 | func close() throws { 58 | guard Musl.close(self.master) == 0 else { 59 | throw App.Errno(stage: "close") 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Client/Authentication.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// Abstraction for returning a token needed for logging into an OCI compliant registry. 20 | public protocol Authentication: Sendable { 21 | func token() async throws -> String 22 | } 23 | 24 | /// Type representing authentication information for client to access the registry. 25 | public struct BasicAuthentication: Authentication { 26 | /// The username for the authentication. 27 | let username: String 28 | /// The password or identity token for the user. 29 | let password: String 30 | 31 | public init(username: String, password: String) { 32 | self.username = username 33 | self.password = password 34 | } 35 | 36 | /// Get a token using the provided username and password. This will be a 37 | /// base64 encoded string of the username and password delimited by a colon. 38 | public func token() async throws -> String { 39 | let credentials = "\(username):\(password)" 40 | if let authenticationData = credentials.data(using: .utf8)?.base64EncodedString() { 41 | return "Basic \(authenticationData)" 42 | } 43 | throw Error.invalidCredentials 44 | } 45 | 46 | /// `BasicAuthentication` errors. 47 | public enum Error: Swift.Error { 48 | case invalidCredentials 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/ContainerizationNetlinkTests/MockNetlinkSocket.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // 18 | 19 | @testable import ContainerizationNetlink 20 | 21 | class MockNetlinkSocket: NetlinkSocket { 22 | static let ENOMEM: Int32 = 12 23 | static let EOVERFLOW: Int32 = 75 24 | 25 | var pid: UInt32 = 0 26 | 27 | var requests: [[UInt8]] = [] 28 | var responses: [[UInt8]] = [] 29 | 30 | var responseIndex = 0 31 | 32 | public init() throws {} 33 | 34 | public func send(buf: UnsafeRawPointer!, len: Int, flags: Int32) throws -> Int { 35 | let ptr = buf.bindMemory(to: UInt8.self, capacity: len) 36 | requests.append(Array(UnsafeBufferPointer(start: ptr, count: len))) 37 | return len 38 | } 39 | 40 | public func recv(buf: UnsafeMutableRawPointer!, len: Int, flags: Int32) throws -> Int { 41 | guard responseIndex < responses.count else { 42 | throw NetlinkSocketError.recvFailure(rc: Self.ENOMEM) 43 | } 44 | 45 | let response = responses[responseIndex] 46 | guard len >= response.count else { 47 | throw NetlinkSocketError.recvFailure(rc: 75) 48 | } 49 | 50 | response.withUnsafeBytes { bytes in 51 | buf.copyMemory(from: bytes.baseAddress!, byteCount: response.count) 52 | } 53 | 54 | responseIndex += 1 55 | return response.count 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Containerization/Vminitd+SocketRelay.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | extension Vminitd: SocketRelayAgent { 18 | /// Sets up a relay between a host socket to a newly created guest socket, or vice versa. 19 | public func relaySocket(port: UInt32, configuration: UnixSocketConfiguration) async throws { 20 | let request = Com_Apple_Containerization_Sandbox_V3_ProxyVsockRequest.with { 21 | $0.id = configuration.id 22 | $0.vsockPort = port 23 | 24 | if let perms = configuration.permissions { 25 | $0.guestSocketPermissions = UInt32(perms.rawValue) 26 | } 27 | 28 | switch configuration.direction { 29 | case .into: 30 | $0.guestPath = configuration.destination.path 31 | $0.action = .into 32 | case .outOf: 33 | $0.guestPath = configuration.source.path 34 | $0.action = .outOf 35 | } 36 | } 37 | _ = try await client.proxyVsock(request) 38 | } 39 | 40 | /// Stops the specified socket relay. 41 | public func stopSocketRelay(configuration: UnixSocketConfiguration) async throws { 42 | let request = Com_Apple_Containerization_Sandbox_V3_StopVsockProxyRequest.with { 43 | $0.id = configuration.id 44 | } 45 | _ = try await client.stopVsockProxy(request) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/AsyncLock.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// `AsyncLock` provides a familiar locking API, with the main benefit being that it 20 | /// is safe to call async methods while holding the lock. This is primarily used in spots 21 | /// where an actor makes sense, but we may need to ensure we don't fall victim to actor 22 | /// reentrancy issues. 23 | public actor AsyncLock { 24 | private var busy = false 25 | private var queue: ArraySlice> = [] 26 | 27 | public struct Context: Sendable { 28 | fileprivate init() {} 29 | } 30 | 31 | public init() {} 32 | 33 | /// withLock provides a scoped locking API to run a function while holding the lock. 34 | public func withLock(_ body: @Sendable @escaping (Context) async throws -> T) async rethrows -> T { 35 | while self.busy { 36 | await withCheckedContinuation { cc in 37 | self.queue.append(cc) 38 | } 39 | } 40 | 41 | self.busy = true 42 | 43 | defer { 44 | self.busy = false 45 | if let next = self.queue.popFirst() { 46 | next.resume(returning: ()) 47 | } else { 48 | self.queue = [] 49 | } 50 | } 51 | 52 | let context = Context() 53 | return try await body(context) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Protobuf.Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | LOCAL_DIR := $(ROOT_DIR)/.local 16 | LOCAL_BIN_DIR := $(LOCAL_DIR)/bin 17 | 18 | # Versions 19 | PROTOC_VERSION := 26.1 20 | 21 | # Protoc binary installation 22 | PROTOC_ZIP := protoc-$(PROTOC_VERSION)-osx-universal_binary.zip 23 | PROTOC := $(LOCAL_BIN_DIR)/protoc@$(PROTOC_VERSION)/protoc 24 | $(PROTOC): 25 | @echo Downloading protocol buffers... 26 | @mkdir -p $(LOCAL_DIR) 27 | @curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC_ZIP) 28 | @mkdir -p $(dir $@) 29 | @unzip -jo $(PROTOC_ZIP) bin/protoc -d $(dir $@) 30 | @unzip -o $(PROTOC_ZIP) 'include/*' -d $(dir $@) 31 | @rm -f $(PROTOC_ZIP) 32 | 33 | .PHONY: protoc-gen-swift 34 | protoc-gen-swift: 35 | @$(SWIFT) build --product protoc-gen-swift 36 | @$(SWIFT) build --product protoc-gen-grpc-swift 37 | 38 | .PHONY: protos 39 | protos: $(PROTOC) protoc-gen-swift 40 | @echo Generating protocol buffers source code... 41 | @$(PROTOC) Sources/Containerization/SandboxContext/SandboxContext.proto \ 42 | --plugin=protoc-gen-grpc-swift=$(BUILD_BIN_DIR)/protoc-gen-grpc-swift \ 43 | --plugin=protoc-gen-swift=$(BUILD_BIN_DIR)/protoc-gen-swift \ 44 | --proto_path=Sources/Containerization/SandboxContext \ 45 | --grpc-swift_out="Sources/Containerization/SandboxContext" \ 46 | --grpc-swift_opt=Visibility=Public \ 47 | --swift_out="Sources/Containerization/SandboxContext" \ 48 | --swift_opt=Visibility=Public \ 49 | -I. 50 | @"$(MAKE)" update-licenses 51 | 52 | .PHONY: clean-proto-tools 53 | clean-proto-tools: 54 | @echo Cleaning proto tools... 55 | @rm -rf $(LOCAL_DIR)/bin/protoc* 56 | -------------------------------------------------------------------------------- /scripts/check-integration-test-vm-panics.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | 17 | # Script to scan the VM boot logs from the integration tests for kernel panics. 18 | # Looks for common kernel panic messages like "attempted to kill init" or "Kernel panic". 19 | 20 | GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) 21 | if [ -z "$GIT_ROOT" ]; then 22 | echo "Error: Not in a git repository" 23 | exit 1 24 | fi 25 | 26 | BOOT_LOGS_DIR="$GIT_ROOT/bin/integration-bootlogs" 27 | 28 | if [ ! -d "$BOOT_LOGS_DIR" ]; then 29 | echo "Error: Boot logs directory not found: $BOOT_LOGS_DIR" 30 | exit 1 31 | fi 32 | 33 | echo "Scanning boot logs in: $BOOT_LOGS_DIR" 34 | echo "========================================" 35 | echo "" 36 | 37 | PANIC_FOUND=0 38 | 39 | for logfile in "$BOOT_LOGS_DIR"/*; do 40 | if [ -f "$logfile" ]; then 41 | if grep -qi "attempted to kill init\|Kernel panic\|end Kernel panic\|Attempted to kill the idle task\|Oops:" "$logfile"; then 42 | echo "🚨 PANIC DETECTED in: $(basename "$logfile")" 43 | echo "---" 44 | grep -i -B 5 -A 10 "attempted to kill init\|Kernel panic\|end Kernel panic\|Attempted to kill the idle task\|Oops:" "$logfile" | head -30 45 | echo "" 46 | echo "========================================" 47 | echo "" 48 | PANIC_FOUND=1 49 | fi 50 | fi 51 | done 52 | 53 | if [ $PANIC_FOUND -eq 0 ]; then 54 | echo "✅ No kernel panics detected in boot logs" 55 | else 56 | echo "❌ Found kernel panics - Virtual machine(s) crashed during integration tests" 57 | fi 58 | 59 | exit $PANIC_FOUND 60 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/Content.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationExtras 18 | import Crypto 19 | import Foundation 20 | import NIOCore 21 | 22 | /// Protocol for defining a single OCI content 23 | public protocol Content: Sendable { 24 | /// URL to the content 25 | var path: URL { get } 26 | 27 | /// sha256 of content 28 | func digest() throws -> SHA256.Digest 29 | 30 | /// Size of content 31 | func size() throws -> UInt64 32 | 33 | /// Data representation of entire content 34 | func data() throws -> Data 35 | 36 | /// Data representation partial content 37 | func data(offset: UInt64, length: Int) throws -> Data? 38 | 39 | /// Decode the content into an object 40 | func decode() throws -> T where T: Decodable 41 | } 42 | 43 | /// Protocol defining methods to fetch and push OCI content 44 | public protocol ContentClient: Sendable { 45 | func fetch(name: String, descriptor: Descriptor) async throws -> T 46 | 47 | func fetchBlob(name: String, descriptor: Descriptor, into file: URL, progress: ProgressHandler?) async throws -> (Int64, SHA256Digest) 48 | 49 | func fetchData(name: String, descriptor: Descriptor) async throws -> Data 50 | 51 | func push( 52 | name: String, 53 | ref: String, 54 | descriptor: Descriptor, 55 | streamGenerator: () throws -> T, 56 | progress: ProgressHandler? 57 | ) async throws where T.Element == ByteBuffer 58 | 59 | } 60 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/PauseCommand.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Dispatch 18 | import Logging 19 | import Musl 20 | 21 | struct PauseCommand { 22 | static func run(log: Logger) throws { 23 | if getpid() != 1 { 24 | log.warning("pause should be the first process") 25 | } 26 | 27 | // NOTE: For whatever reason, using signal() for the below causes a swift compiler issue. 28 | // Can revert whenever that is understood. 29 | let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT) 30 | sigintSource.setEventHandler { 31 | log.info("Shutting down, got SIGINT") 32 | Musl.exit(0) 33 | } 34 | sigintSource.resume() 35 | 36 | let sigtermSource = DispatchSource.makeSignalSource(signal: SIGTERM) 37 | sigtermSource.setEventHandler { 38 | log.info("Shutting down, got SIGTERM") 39 | Musl.exit(0) 40 | } 41 | sigtermSource.resume() 42 | 43 | let sigchldSource = DispatchSource.makeSignalSource(signal: SIGCHLD) 44 | sigchldSource.setEventHandler { 45 | var status: Int32 = 0 46 | while waitpid(-1, &status, WNOHANG) > 0 {} 47 | } 48 | sigchldSource.resume() 49 | 50 | log.info("pause container running, waiting for signals...") 51 | 52 | while true { 53 | Musl.pause() 54 | } 55 | 56 | log.error("Error: infinite loop terminated") 57 | Musl.exit(42) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Containerization/AttachedFilesystem.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationExtras 18 | import ContainerizationOCI 19 | 20 | /// A filesystem that was attached and able to be mounted inside the runtime environment. 21 | public struct AttachedFilesystem: Sendable { 22 | /// The type of the filesystem. 23 | public var type: String 24 | /// The path to the filesystem within a sandbox. 25 | public var source: String 26 | /// Destination when mounting the filesystem inside a sandbox. 27 | public var destination: String 28 | /// The options to use when mounting the filesystem. 29 | public var options: [String] 30 | 31 | #if os(macOS) 32 | public init(mount: Mount, allocator: any AddressAllocator) throws { 33 | switch mount.type { 34 | case "virtiofs": 35 | let name = try hashMountSource(source: mount.source) 36 | self.source = name 37 | case "ext4": 38 | let char = try allocator.allocate() 39 | self.source = "/dev/vd\(char)" 40 | default: 41 | self.source = mount.source 42 | } 43 | self.type = mount.type 44 | self.options = mount.options 45 | self.destination = mount.destination 46 | } 47 | #endif 48 | 49 | public init(type: String, source: String, destination: String, options: [String]) { 50 | self.type = type 51 | self.source = source 52 | self.destination = destination 53 | self.options = options 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Manifest.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // Source: https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/manifest.go 18 | 19 | import Foundation 20 | 21 | /// Manifest provides `application/vnd.oci.image.manifest.v1+json` mediatype structure when marshalled to JSON. 22 | public struct Manifest: Codable, Sendable { 23 | /// `schemaVersion` is the image manifest schema that this image follows. 24 | public let schemaVersion: Int 25 | 26 | /// `mediaType` specifies the type of this document data structure, e.g. `application/vnd.oci.image.manifest.v1+json`. 27 | public let mediaType: String? 28 | 29 | /// `config` references a configuration object for a container, by digest. 30 | /// The referenced configuration object is a JSON blob that the runtime uses to set up the container. 31 | public let config: Descriptor 32 | 33 | /// `layers` is an indexed list of layers referenced by the manifest. 34 | public let layers: [Descriptor] 35 | 36 | /// `annotations` contains arbitrary metadata for the image manifest. 37 | public let annotations: [String: String]? 38 | 39 | public init( 40 | schemaVersion: Int = 2, mediaType: String = MediaTypes.imageManifest, config: Descriptor, layers: [Descriptor], 41 | annotations: [String: String]? = nil 42 | ) { 43 | self.schemaVersion = schemaVersion 44 | self.mediaType = mediaType 45 | self.config = config 46 | self.layers = layers 47 | self.annotations = annotations 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// The `resolvingSymlinksInPath` method of the `URL` struct does not resolve the symlinks 20 | /// for directories under `/private` which include`tmp`, `var` and `etc` 21 | /// hence adding a method to build up on the existing `resolvingSymlinksInPath` that prepends `/private` to those paths 22 | extension URL { 23 | /// returns the unescaped absolutePath of a URL joined by separator 24 | func absolutePath(_ separator: String = "/") -> String { 25 | self.pathComponents 26 | .joined(separator: separator) 27 | .dropFirst("/".count) 28 | .description 29 | } 30 | 31 | public func resolvingSymlinksInPathWithPrivate() -> URL { 32 | let url = self.resolvingSymlinksInPath() 33 | #if os(macOS) 34 | let parts = url.pathComponents 35 | if parts.count > 1 { 36 | if (parts.first == "/") && ["tmp", "var", "etc"].contains(parts[1]) { 37 | if let resolved = NSURL.fileURL(withPathComponents: ["/", "private"] + parts[1...]) { 38 | return resolved 39 | } 40 | } 41 | } 42 | #endif 43 | return url 44 | } 45 | 46 | public var isDirectory: Bool { 47 | (try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true 48 | } 49 | 50 | public var isSymlink: Bool { 51 | (try? resourceValues(forKeys: [.isSymbolicLinkKey]))?.isSymbolicLink == true 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vminitd/Sources/vmexec/Mount.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOCI 18 | import ContainerizationOS 19 | import Foundation 20 | import Musl 21 | 22 | struct ContainerMount { 23 | private let mounts: [ContainerizationOCI.Mount] 24 | private let rootfs: String 25 | 26 | init(rootfs: String, mounts: [ContainerizationOCI.Mount]) { 27 | self.rootfs = rootfs 28 | self.mounts = mounts 29 | } 30 | 31 | func mountToRootfs() throws { 32 | for m in self.mounts { 33 | let osMount = m.toOSMount() 34 | try osMount.mount(root: self.rootfs) 35 | } 36 | } 37 | 38 | func configureConsole() throws { 39 | let ptmx = self.rootfs.standardizingPath.appendingPathComponent("dev/ptmx") 40 | guard remove(ptmx) == 0 else { 41 | throw App.Errno(stage: "remove(ptmx)") 42 | } 43 | guard symlink("pts/ptmx", ptmx) == 0 else { 44 | throw App.Errno(stage: "symlink(pts/ptmx)") 45 | } 46 | } 47 | 48 | private func mkdirAll(_ name: String, _ perm: Int16) throws { 49 | try FileManager.default.createDirectory( 50 | atPath: name, 51 | withIntermediateDirectories: true, 52 | attributes: [.posixPermissions: perm] 53 | ) 54 | } 55 | } 56 | 57 | extension ContainerizationOCI.Mount { 58 | func toOSMount() -> ContainerizationOS.Mount { 59 | ContainerizationOS.Mount( 60 | type: self.type, 61 | source: self.source, 62 | target: self.destination, 63 | options: self.options 64 | ) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Descriptor.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // Source: https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/descriptor.go 18 | 19 | import Foundation 20 | 21 | /// Descriptor describes the disposition of targeted content. 22 | /// This structure provides `application/vnd.oci.descriptor.v1+json` mediatype 23 | /// when marshalled to JSON. 24 | public struct Descriptor: Codable, Sendable, Equatable { 25 | /// mediaType is the media type of the object this schema refers to. 26 | public let mediaType: String 27 | 28 | /// digest is the digest of the targeted content. 29 | public let digest: String 30 | 31 | /// size specifies the size in bytes of the blob. 32 | public let size: Int64 33 | 34 | /// urls specifies a list of URLs from which this object MAY be downloaded. 35 | public let urls: [String]? 36 | 37 | /// annotations contains arbitrary metadata relating to the targeted content. 38 | public var annotations: [String: String]? 39 | 40 | /// platform describes the platform which the image in the manifest runs on. 41 | /// 42 | /// This should only be used when referring to a manifest. 43 | public var platform: Platform? 44 | 45 | public init( 46 | mediaType: String, digest: String, size: Int64, urls: [String]? = nil, annotations: [String: String]? = nil, 47 | platform: Platform? = nil 48 | ) { 49 | self.mediaType = mediaType 50 | self.digest = digest 51 | self.size = size 52 | self.urls = urls 53 | self.annotations = annotations 54 | self.platform = platform 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/01-bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report. 3 | title: "[Bug]: " 4 | type: "Bug" 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: checkboxes 11 | id: prereqs 12 | attributes: 13 | label: I have done the following 14 | description: Select that you have completed the following prerequisites. 15 | options: 16 | - label: I have searched the existing issues 17 | required: true 18 | - label: If possible, I've reproduced the issue using the 'main' branch of this project 19 | required: false 20 | - type: textarea 21 | id: reproduce 22 | attributes: 23 | label: Steps to reproduce 24 | description: Explain how to reproduce the incorrect behavior. 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: what-happened 29 | attributes: 30 | label: Current behavior 31 | description: A concise description of what you're experiencing. 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: expected 36 | attributes: 37 | label: Expected behavior 38 | description: A concise description of what you expected to happen. 39 | validations: 40 | required: true 41 | - type: textarea 42 | attributes: 43 | label: Environment 44 | description: | 45 | Examples: 46 | - **OS**: macOS 26.0 (25A354) 47 | - **Xcode**: Version 26.0 (17A324) 48 | - **Swift**: Apple Swift version 6.2 (swift-6.2-RELEASE) 49 | value: | 50 | - OS: 51 | - Xcode: 52 | - Swift: 53 | render: markdown 54 | validations: 55 | required: true 56 | - type: textarea 57 | id: logs 58 | attributes: 59 | label: Relevant log output 60 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 61 | value: | 62 | N/A 63 | render: shell 64 | - type: checkboxes 65 | id: terms 66 | attributes: 67 | label: Code of Conduct 68 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/apple/.github/blob/main/CODE_OF_CONDUCT.md). 69 | options: 70 | - label: I agree to follow this project's Code of Conduct 71 | required: true 72 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/AsyncMutex.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// `AsyncMutex` provides a mutex that protects a piece of data, with the main benefit being that it 18 | /// is safe to call async methods while holding the lock. This is primarily used in spots 19 | /// where an actor makes sense, but we may need to ensure we don't fall victim to actor 20 | /// reentrancy issues. 21 | public actor AsyncMutex { 22 | private final class Box: @unchecked Sendable { 23 | var value: T 24 | init(_ value: T) { 25 | self.value = value 26 | } 27 | } 28 | 29 | private var busy = false 30 | private var queue: ArraySlice> = [] 31 | private let box: Box 32 | 33 | public init(_ initialValue: T) { 34 | self.box = Box(initialValue) 35 | } 36 | 37 | /// withLock provides a scoped locking API to run a function while holding the lock. 38 | /// The protected value is passed to the closure for safe access. 39 | public func withLock(_ body: @Sendable @escaping (inout T) async throws -> R) async rethrows -> R { 40 | while self.busy { 41 | await withCheckedContinuation { cc in 42 | self.queue.append(cc) 43 | } 44 | } 45 | 46 | self.busy = true 47 | 48 | defer { 49 | self.busy = false 50 | if let next = self.queue.popFirst() { 51 | next.resume(returning: ()) 52 | } else { 53 | self.queue = [] 54 | } 55 | } 56 | 57 | return try await body(&self.box.value) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineInstance.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationError 18 | import Foundation 19 | 20 | /// The runtime state of the virtual machine instance. 21 | public enum VirtualMachineInstanceState: Sendable { 22 | case starting 23 | case running 24 | case stopped 25 | case stopping 26 | case unknown 27 | } 28 | 29 | /// A live instance of a virtual machine. 30 | public protocol VirtualMachineInstance: Sendable { 31 | associatedtype Agent: VirtualMachineAgent 32 | 33 | // The state of the virtual machine. 34 | var state: VirtualMachineInstanceState { get } 35 | 36 | var mounts: [String: [AttachedFilesystem]] { get } 37 | /// Dial the Agent. It's up the VirtualMachineInstance to determine 38 | /// what port the agent is listening on. 39 | func dialAgent() async throws -> Agent 40 | /// Dial a vsock port in the guest. 41 | func dial(_ port: UInt32) async throws -> FileHandle 42 | /// Listen on a host vsock port. 43 | func listen(_ port: UInt32) throws -> VsockListener 44 | /// Start the virtual machine. 45 | func start() async throws 46 | /// Stop the virtual machine. 47 | func stop() async throws 48 | /// Pause the virtual machine. 49 | func pause() async throws 50 | /// Resume the virtual machine. 51 | func resume() async throws 52 | } 53 | 54 | extension VirtualMachineInstance { 55 | func pause() async throws { 56 | throw ContainerizationError(.unsupported, message: "pause") 57 | } 58 | func resume() async throws { 59 | throw ContainerizationError(.unsupported, message: "resume") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/KernelTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // 18 | 19 | import Foundation 20 | import Testing 21 | 22 | @testable import Containerization 23 | 24 | final class KernelTests { 25 | @Test func kernelArgs() { 26 | let commandLine = Kernel.CommandLine(debug: false, panic: 0) 27 | let kernel = Kernel(path: .init(fileURLWithPath: ""), platform: .linuxArm, commandline: commandLine) 28 | 29 | let expected = "console=hvc0 tsc=reliable panic=0" 30 | let cmdline = kernel.commandLine.kernelArgs.joined(separator: " ") 31 | #expect(cmdline == expected) 32 | } 33 | 34 | @Test func kernelDebugArgs() { 35 | let cmdLine = Kernel.CommandLine(debug: true, panic: 0) 36 | let kernel = Kernel(path: .init(fileURLWithPath: ""), platform: .linuxArm, commandline: cmdLine) 37 | 38 | let expected = "console=hvc0 tsc=reliable debug panic=0" 39 | let cmdline = kernel.commandLine.kernelArgs.joined(separator: " ") 40 | #expect(cmdline == expected) 41 | } 42 | 43 | @Test func kernelCommandLineInitWithDebugTrue() { 44 | let commandLine = Kernel.CommandLine(debug: true, panic: 5, initArgs: ["--verbose"]) 45 | 46 | #expect(commandLine.kernelArgs == ["console=hvc0", "tsc=reliable", "debug", "panic=5"]) 47 | #expect(commandLine.initArgs == ["--verbose"]) 48 | } 49 | 50 | @Test func kernelCommandLineMutatingMethods() { 51 | var commandLine = Kernel.CommandLine(kernelArgs: ["console=hvc0"], initArgs: []) 52 | 53 | commandLine.addDebug() 54 | commandLine.addPanic(level: 10) 55 | 56 | #expect(commandLine.kernelArgs == ["console=hvc0", "debug", "panic=10"]) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/ad59e9f71edceca7b1ac7c642410858489b743c97233b0a26a5e2098b1443762: -------------------------------------------------------------------------------- 1 | {"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:48a06049d3738991b011ca8b12473d712b7c40666a1462118dae3c403676afc2","size":667,"annotations":{"com.apple.container.sign.v1.certificate/ptr":"mac-Q5W6919KP6:41FB3AB2-E9B9-45CE-8252-8C17C8038670:wlan0","com.apple.container.sign.v1.signature":"MEUCIEX3psgFczBpby6sMdzBk5FF5ID5UbqM4nOpqfiVbkseAiEAlDLBr9ajHiswl8/rOyVmYdN98lakuK+dKyABEBXRXeQ="},"platform":{"architecture":"arm64","os":"linux"}}],"annotations":{"com.apple.container.info.v1.dockerfile-sha256sum":"d95983c2a8acbd4cf861c7d3b9117d3e722aebc3768ca682ddf2a427e2fd6583","com.apple.container.sign.v1.certificate/mac-Q5W6919KP6:41FB3AB2-E9B9-45CE-8252-8C17C8038670:wlan0":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURjekNDQXhpZ0F3SUJBZ0lJRW9tR0duRkFGTnd3Q2dZSUtvWkl6ajBFQXdJd2FqRWtNQ0lHQTFVRUF3d2INClFYQndiR1VnUTI5eWNHOXlZWFJsSUZCTFNVNUpWQ0JEUVNBeU1TQXdIZ1lEVlFRTERCZERaWEowYVdacFkyRjANCmFXOXVJRUYxZEdodmNtbDBlVEVUTUJFR0ExVUVDZ3dLUVhCd2JHVWdTVzVqTGpFTE1Ba0dBMVVFQmhNQ1ZWTXcNCkhoY05Nakl4TWpJd01Ua3hOREl3V2hjTk1qVXdNVEU0TVRreE5ERTVXakNCcGpFYU1CZ0dDZ21TSm9tVDhpeGsNCkFRRU1DakkzTURFd09UVTRPVFV4UWpCQUJnTlZCQU1NT1cxaFl5MVJOVmMyT1RFNVMxQTJPalF4UmtJelFVSXkNCkxVVTVRamt0TkRWRFJTMDRNalV5TFRoRE1UZERPREF6T0RZM01EcDNiR0Z1TURFVk1CTUdBMVVFQ3d3TVFYQncNCmJHVkRiMjV1WldOME1STXdFUVlEVlFRS0RBcEJjSEJzWlNCSmJtTXVNUmd3RmdZS0NaSW1pWlB5TEdRQkdSWUkNClNVUk5VeTFUVTA4d1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTcW5tSjZ3cFluWWlKRkdRaDENCjlOcGkyVnp3M1I3UFlMOXY3MnNiUDZsNy83VUt3YXV4aVk1ZkdEL29yTnhwbWNScFdPRk5mNW1JUWpFcHByMDMNCkpYUXpvNElCYVRDQ0FXVXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCVEx3K0x0QUcxai8rV1QNCkpsLzJJVDVVMEJ6WEZUQkVCZ2dyQmdFRkJRY0JBUVE0TURZd05BWUlLd1lCQlFVSE1BR0dLR2gwZEhBNkx5OXYNClkzTndMbUZ3Y0d4bExtTnZiUzl2WTNOd01ETXRjR3RwYm1sMFkyRXlNREV3VGdZRFZSMFJCRWN3UmFCREJnWXINCkJnRUZBZ0tnT1RBM29CZ2JGa0ZRVUV4RlEwOU9Ua1ZEVkM1QlVGQk1SUzVEVDAyaEd6QVpvQU1DQVFDaEVqQVENCkd3NXphV1JvWVhKMGFHRmZiV0Z1YVRBb0JnTlZIU1VFSVRBZkJnZ3JCZ0VGQlFjREFnWUtLd1lCQkFHQ054UUMNCkFnWUhLd1lCQlFJREJEQXpCZ05WSFI4RUxEQXFNQ2lnSnFBa2hpSm9kSFJ3T2k4dlkzSnNMbUZ3Y0d4bExtTnYNCmJTOXdhMmx1YVhSallUSXVZM0pzTUIwR0ExVWREZ1FXQkJTRlIxOExjWkgxY1laNHlEajVyWXkwMmZNeTREQU8NCkJnTlZIUThCQWY4RUJBTUNCNEF3RUFZSktvWklodmRqWkFZcEJBTU1BVEl3Q2dZSUtvWkl6ajBFQXdJRFNRQXcNClJnSWhBSk9YTnBEWE43QjhHZFZIVE1WdmEzSForeXlCTDlMcm1hZTJkeFpUUUVoekFpRUF4b25CRjhnMTNQeXYNCkhCRFVSY0p0ZURUYVdQdnJyUW9HUi9KZXQzb3B5R1U9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K"}} -------------------------------------------------------------------------------- /Sources/Containerization/DNSConfiguration.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// DNS configuration for a container. The values will be used to 18 | /// construct /etc/resolv.conf for a given container. 19 | public struct DNS: Sendable { 20 | /// The set of default nameservers to use if none are provided 21 | /// in the constructor. 22 | public static let defaultNameservers = ["1.1.1.1"] 23 | 24 | /// The nameservers a container should use. 25 | public var nameservers: [String] 26 | /// The DNS domain to use. 27 | public var domain: String? 28 | /// The DNS search domains to use. 29 | public var searchDomains: [String] 30 | /// The DNS options to use. 31 | public var options: [String] 32 | 33 | public init( 34 | nameservers: [String] = defaultNameservers, 35 | domain: String? = nil, 36 | searchDomains: [String] = [], 37 | options: [String] = [] 38 | ) { 39 | self.nameservers = nameservers 40 | self.domain = domain 41 | self.searchDomains = searchDomains 42 | self.options = options 43 | } 44 | } 45 | 46 | extension DNS { 47 | public var resolvConf: String { 48 | var text = "" 49 | 50 | if !nameservers.isEmpty { 51 | text += nameservers.map { "nameserver \($0)" }.joined(separator: "\n") + "\n" 52 | } 53 | 54 | if let domain { 55 | text += "domain \(domain)\n" 56 | } 57 | 58 | if !searchDomains.isEmpty { 59 | text += "search \(searchDomains.joined(separator: " "))\n" 60 | } 61 | 62 | if !options.isEmpty { 63 | text += "options \(options.joined(separator: " "))\n" 64 | } 65 | 66 | return text 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/build-test-images.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish containerization test images 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | workflow_dispatch: 8 | inputs: 9 | publish: 10 | type: boolean 11 | description: "Publish the built image" 12 | default: false 13 | version: 14 | type: string 15 | description: "Version of the image to create" 16 | default: "test" 17 | image: 18 | type: choice 19 | description: Test image to build 20 | options: 21 | - dockermanifestimage 22 | - emptyimage 23 | default: 'dockermanifestimage' 24 | useBuildx: 25 | type: boolean 26 | description: "Use docker buildx to build the image" 27 | default: false 28 | 29 | jobs: 30 | image: 31 | name: Build test images 32 | timeout-minutes: 30 33 | runs-on: ubuntu-latest 34 | permissions: 35 | contents: read 36 | packages: write 37 | steps: 38 | - name: Check branch 39 | run: | 40 | if [[ "${{ github.ref }}" != "refs/heads/main" ]] && [[ "${{ github.ref }}" != refs/heads/release* ]] && [[ "${{ inputs.publish }}" == "true" ]]; then 41 | echo "❌ Cannot publish an image if we are not on main or a release branch." 42 | exit 1 43 | fi 44 | - name: Check inputs 45 | run: | 46 | if [[ "${{ inputs.image }}" == "dockermanifestimage" ]] && [[ "${{ inputs.useBuildx }}" == "true" ]]; then 47 | echo "❌ dockermanifestimage cannot be built with buildx" 48 | exit 1 49 | fi 50 | 51 | if [[ "${{ inputs.image }}" == "emptyimage" ]] && [[ "${{ inputs.useBuildx }}" != "true" ]]; then 52 | echo "❌ emptyimage should be built with buildx" 53 | exit 1 54 | fi 55 | - name: Checkout repository 56 | uses: actions/checkout@v4 57 | - name: Login to GitHub Container Registry 58 | uses: docker/login-action@v3 59 | with: 60 | registry: ghcr.io 61 | username: ${{ github.actor }} 62 | password: ${{ secrets.GITHUB_TOKEN }} 63 | - name: Set up Docker Buildx 64 | if: ${{ inputs.useBuildx }} 65 | uses: docker/setup-buildx-action@v3 66 | - name: Build dockerfile and push image 67 | uses: docker/build-push-action@v6 68 | with: 69 | push: ${{ inputs.publish }} 70 | context: Tests/TestImages/${{ inputs.image }} 71 | tags: ghcr.io/apple/containerization/${{ inputs.image }}:${{ inputs.version }} 72 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/HostsTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import Containerization 21 | 22 | struct HostsTests { 23 | 24 | @Test func hostsEntryRenderedWithAllFields() { 25 | let entry = Hosts.Entry( 26 | ipAddress: "192.168.1.100", 27 | hostnames: ["myserver", "server.local"], 28 | comment: "My local server" 29 | ) 30 | 31 | let expected = "192.168.1.100 myserver server.local # My local server " 32 | #expect(entry.rendered == expected) 33 | } 34 | 35 | @Test func hostsEntryRenderedWithoutComment() { 36 | let entry = Hosts.Entry( 37 | ipAddress: "10.0.0.1", 38 | hostnames: ["gateway"] 39 | ) 40 | 41 | let expected = "10.0.0.1 gateway" 42 | #expect(entry.rendered == expected) 43 | } 44 | 45 | @Test func hostsEntryRenderedWithEmptyHostnames() { 46 | let entry = Hosts.Entry( 47 | ipAddress: "172.16.0.1", 48 | hostnames: [], 49 | comment: "Empty hostnames" 50 | ) 51 | 52 | let expected = "172.16.0.1 # Empty hostnames " 53 | #expect(entry.rendered == expected) 54 | } 55 | 56 | @Test func hostsFileWithCommentAndEntries() { 57 | let hosts = Hosts( 58 | entries: [ 59 | Hosts.Entry(ipAddress: "127.0.0.1", hostnames: ["localhost"]), 60 | Hosts.Entry(ipAddress: "192.168.1.10", hostnames: ["server"], comment: "Main server"), 61 | ], 62 | comment: "Generated hosts file" 63 | ) 64 | 65 | let expected = "# Generated hosts file\n127.0.0.1 localhost\n192.168.1.10 server # Main server \n" 66 | #expect(hosts.hostsFile == expected) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "fileScopedDeclarationPrivacy" : { 3 | "accessLevel" : "private" 4 | }, 5 | "indentation" : { 6 | "spaces" : 4 7 | }, 8 | "indentConditionalCompilationBlocks" : false, 9 | "indentSwitchCaseLabels" : false, 10 | "lineBreakAroundMultilineExpressionChainComponents" : false, 11 | "lineBreakBeforeControlFlowKeywords" : false, 12 | "lineBreakBeforeEachArgument" : false, 13 | "lineBreakBeforeEachGenericRequirement" : false, 14 | "lineLength" : 180, 15 | "maximumBlankLines" : 1, 16 | "multiElementCollectionTrailingCommas" : true, 17 | "noAssignmentInExpressions" : { 18 | "allowedFunctions" : [ 19 | "XCTAssertNoThrow" 20 | ] 21 | }, 22 | "prioritizeKeepingFunctionOutputTogether" : false, 23 | "respectsExistingLineBreaks" : true, 24 | "rules" : { 25 | "AllPublicDeclarationsHaveDocumentation" : false, 26 | "AlwaysUseLowerCamelCase" : true, 27 | "AmbiguousTrailingClosureOverload" : false, 28 | "BeginDocumentationCommentWithOneLineSummary" : false, 29 | "DoNotUseSemicolons" : true, 30 | "DontRepeatTypeInStaticProperties" : true, 31 | "FileScopedDeclarationPrivacy" : true, 32 | "FullyIndirectEnum" : true, 33 | "GroupNumericLiterals" : true, 34 | "IdentifiersMustBeASCII" : true, 35 | "NeverForceUnwrap" : true, 36 | "NeverUseForceTry" : true, 37 | "NeverUseImplicitlyUnwrappedOptionals" : true, 38 | "NoAccessLevelOnExtensionDeclaration" : true, 39 | "NoAssignmentInExpressions" : true, 40 | "NoBlockComments" : false, 41 | "NoCasesWithOnlyFallthrough" : true, 42 | "NoEmptyTrailingClosureParentheses" : true, 43 | "NoLabelsInCasePatterns" : true, 44 | "NoLeadingUnderscores" : false, 45 | "NoParensAroundConditions" : true, 46 | "NoPlaygroundLiterals" : true, 47 | "NoVoidReturnOnFunctionSignature" : true, 48 | "OmitExplicitReturns" : true, 49 | "OneCasePerLine" : true, 50 | "OneVariableDeclarationPerLine" : true, 51 | "OnlyOneTrailingClosureArgument" : true, 52 | "OrderedImports" : true, 53 | "ReplaceForEachWithForLoop" : true, 54 | "ReturnVoidInsteadOfEmptyTuple" : true, 55 | "TypeNamesShouldBeCapitalized" : true, 56 | "UseEarlyExits" : true, 57 | "UseLetInEveryBoundCaseVariable" : true, 58 | "UseShorthandTypeNames" : true, 59 | "UseSingleLinePropertyGetter" : true, 60 | "UseSynthesizedInitializer" : true, 61 | "UseTripleSlashForDocumentationComments" : true, 62 | "UseWhereClausesInForLoops" : false, 63 | "ValidateDocumentationComments" : true 64 | }, 65 | "spacesAroundRangeFormationOperators" : false, 66 | "tabWidth" : 2, 67 | "version" : 1 68 | } 69 | -------------------------------------------------------------------------------- /Sources/cctl/cctl.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ArgumentParser 18 | import Containerization 19 | import ContainerizationOCI 20 | import Foundation 21 | import Logging 22 | 23 | let log = { 24 | LoggingSystem.bootstrap(StreamLogHandler.standardError) 25 | var log = Logger(label: "com.apple.containerization") 26 | log.logLevel = .debug 27 | return log 28 | }() 29 | 30 | @main 31 | struct Application: AsyncParsableCommand { 32 | static let keychainID = "com.apple.containerization" 33 | static let appRoot: URL = { 34 | FileManager.default.urls( 35 | for: .applicationSupportDirectory, 36 | in: .userDomainMask 37 | ).first! 38 | .appendingPathComponent("com.apple.containerization") 39 | }() 40 | 41 | private static let _contentStore: ContentStore = { 42 | try! LocalContentStore(path: appRoot.appendingPathComponent("content")) 43 | }() 44 | 45 | private static let _imageStore: ImageStore = { 46 | try! ImageStore( 47 | path: appRoot, 48 | contentStore: contentStore 49 | ) 50 | }() 51 | 52 | static var imageStore: ImageStore { 53 | _imageStore 54 | } 55 | 56 | static var contentStore: ContentStore { 57 | _contentStore 58 | } 59 | 60 | static let configuration = CommandConfiguration( 61 | commandName: "cctl", 62 | abstract: "Utility CLI for Containerization", 63 | version: "2.0.0", 64 | subcommands: [ 65 | Images.self, 66 | Login.self, 67 | Rootfs.self, 68 | Run.self, 69 | ] 70 | ) 71 | } 72 | 73 | extension String { 74 | var absoluteURL: URL { 75 | URL(fileURLWithPath: self).absoluteURL 76 | } 77 | } 78 | 79 | extension String: Swift.Error { 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/Timeout.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | /// `Timeout` contains helpers to run an operation and error out if 20 | /// the operation does not finish within a provided time. 21 | public struct Timeout { 22 | /// Performs the passed in `operation` and throws a `CancellationError` if the operation 23 | /// doesn't finish in the provided `seconds` amount. 24 | public static func run( 25 | seconds: UInt32, 26 | operation: @escaping @Sendable () async throws -> T 27 | ) async throws -> T { 28 | try await withThrowingTaskGroup(of: T.self) { group in 29 | group.addTask { 30 | try await operation() 31 | } 32 | 33 | group.addTask { 34 | try await Task.sleep(for: .seconds(seconds)) 35 | throw CancellationError() 36 | } 37 | 38 | guard let result = try await group.next() else { 39 | fatalError() 40 | } 41 | 42 | group.cancelAll() 43 | return result 44 | } 45 | } 46 | 47 | public static func run( 48 | for duration: Duration, 49 | operation: @escaping @Sendable () async throws -> T 50 | ) async throws -> T { 51 | try await withThrowingTaskGroup(of: T.self) { group in 52 | group.addTask { 53 | try await operation() 54 | } 55 | 56 | group.addTask { 57 | try await Task.sleep(for: duration) 58 | throw CancellationError() 59 | } 60 | 61 | guard let result = try await group.next() else { 62 | fatalError() 63 | } 64 | 65 | group.cancelAll() 66 | return result 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Index.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // Source: https://github.com/opencontainers/image-spec/blob/main/specs-go/v1/index.go 18 | 19 | import Foundation 20 | 21 | /// Index references manifests for various platforms. 22 | /// This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON. 23 | public struct Index: Codable, Sendable { 24 | /// schemaVersion is the image manifest schema that this image follows 25 | public let schemaVersion: Int 26 | 27 | /// mediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` 28 | /// This field is optional per the OCI Image Index Specification (omitempty) 29 | public let mediaType: String 30 | 31 | /// manifests references platform specific manifests. 32 | public var manifests: [Descriptor] 33 | 34 | /// annotations contains arbitrary metadata for the image index. 35 | public var annotations: [String: String]? 36 | 37 | public init( 38 | schemaVersion: Int = 2, mediaType: String = MediaTypes.index, manifests: [Descriptor], 39 | annotations: [String: String]? = nil 40 | ) { 41 | self.schemaVersion = schemaVersion 42 | self.mediaType = mediaType 43 | self.manifests = manifests 44 | self.annotations = annotations 45 | } 46 | 47 | public init(from decoder: Decoder) throws { 48 | let container = try decoder.container(keyedBy: CodingKeys.self) 49 | self.schemaVersion = try container.decode(Int.self, forKey: .schemaVersion) 50 | self.mediaType = try container.decodeIfPresent(String.self, forKey: .mediaType) ?? "" 51 | self.manifests = try container.decode([Descriptor].self, forKey: .manifests) 52 | self.annotations = try container.decodeIfPresent([String: String].self, forKey: .annotations) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/ContainerProcess.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOS 18 | import Foundation 19 | 20 | /// Exit status information for a container process 21 | struct ContainerExitStatus: Sendable { 22 | var exitCode: Int32 23 | var exitedAt: Date 24 | } 25 | 26 | /// Protocol for managing container processes 27 | /// 28 | /// This protocol abstracts the underlying container runtime implementation, 29 | /// allowing for different backends like vmexec or runc. 30 | protocol ContainerProcess: Sendable { 31 | /// Unique identifier for the container process 32 | var id: String { get } 33 | 34 | /// Process ID of the running container (nil if not started) 35 | var pid: Int32? { get } 36 | 37 | /// Start the container process 38 | /// - Returns: The process ID of the started container 39 | /// - Throws: If the process fails to start 40 | func start() async throws -> Int32 41 | 42 | /// Wait for the container process to exit 43 | /// - Returns: Exit status information when the process exits 44 | func wait() async -> ContainerExitStatus 45 | 46 | /// Send a signal to the container process 47 | /// - Parameter signal: The signal number to send 48 | /// - Throws: If the signal cannot be sent 49 | func kill(_ signal: Int32) async throws 50 | 51 | /// Resize the terminal for the container process 52 | /// - Parameter size: The new terminal size 53 | /// - Throws: If the terminal cannot be resized or process doesn't have a terminal 54 | func resize(size: Terminal.Size) throws 55 | 56 | /// Close stdin for the container process 57 | /// - Throws: If stdin cannot be closed 58 | func closeStdin() throws 59 | 60 | /// Delete the container process and cleanup resources 61 | /// - Throws: If cleanup fails 62 | func delete() async throws 63 | 64 | /// Set the exit status of the process. 65 | func setExit(_ status: Int32) 66 | } 67 | -------------------------------------------------------------------------------- /Tests/ContainerizationOCITests/AuthChallengeTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Testing 19 | 20 | @testable import ContainerizationOCI 21 | 22 | struct AuthChallengeTests { 23 | internal struct TestCase: Sendable { 24 | let input: String 25 | let expected: AuthenticateChallenge 26 | } 27 | 28 | private static let testCases: [TestCase] = [ 29 | .init( 30 | input: """ 31 | Bearer realm="https://domain.io/token",service="domain.io",scope="repository:user/image:pull" 32 | """, 33 | expected: .init(type: "Bearer", realm: "https://domain.io/token", service: "domain.io", scope: "repository:user/image:pull", error: nil)), 34 | .init( 35 | input: """ 36 | Bearer realm="https://foo-bar-registry.com/auth",service="Awesome Registry" 37 | """, 38 | expected: .init(type: "Bearer", realm: "https://foo-bar-registry.com/auth", service: "Awesome Registry", scope: nil, error: nil)), 39 | .init( 40 | input: """ 41 | Bearer realm="users.example.com", scope="create delete" 42 | """, 43 | expected: .init(type: "Bearer", realm: "users.example.com", service: nil, scope: "create delete", error: nil)), 44 | .init( 45 | input: """ 46 | Bearer realm="https://auth.server.io/token",service="registry.server.io" 47 | """, 48 | expected: .init(type: "Bearer", realm: "https://auth.server.io/token", service: "registry.server.io", scope: nil, error: nil)), 49 | 50 | ] 51 | 52 | @Test(arguments: testCases) 53 | func parseAuthHeader(testCase: TestCase) throws { 54 | let challenges = RegistryClient.parseWWWAuthenticateHeaders(headers: [testCase.input]) 55 | #expect(challenges.count == 1) 56 | #expect(challenges[0] == testCase.expected) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/Containerization/VsockListener.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | #if os(macOS) 20 | import Virtualization 21 | #endif 22 | 23 | /// A stream of vsock connections. 24 | public final class VsockListener: NSObject, Sendable, AsyncSequence { 25 | public typealias Element = FileHandle 26 | 27 | /// The port the connections are for. 28 | public let port: UInt32 29 | 30 | private let connections: AsyncStream 31 | private let cont: AsyncStream.Continuation 32 | private let stopListening: @Sendable (_ port: UInt32) throws -> Void 33 | 34 | package init(port: UInt32, stopListen: @Sendable @escaping (_ port: UInt32) throws -> Void) { 35 | self.port = port 36 | let (stream, continuation) = AsyncStream.makeStream(of: FileHandle.self) 37 | self.connections = stream 38 | self.cont = continuation 39 | self.stopListening = stopListen 40 | } 41 | 42 | public func finish() throws { 43 | self.cont.finish() 44 | try self.stopListening(self.port) 45 | } 46 | 47 | public func makeAsyncIterator() -> AsyncStream.AsyncIterator { 48 | connections.makeAsyncIterator() 49 | } 50 | } 51 | 52 | #if os(macOS) 53 | 54 | extension VsockListener: VZVirtioSocketListenerDelegate { 55 | public func listener( 56 | _: VZVirtioSocketListener, shouldAcceptNewConnection conn: VZVirtioSocketConnection, 57 | from _: VZVirtioSocketDevice 58 | ) -> Bool { 59 | let fd = dup(conn.fileDescriptor) 60 | guard fd != -1 else { 61 | return false 62 | } 63 | conn.close() 64 | 65 | let fh = FileHandle(fileDescriptor: fd, closeOnDealloc: false) 66 | let result = cont.yield(fh) 67 | if case .terminated = result { 68 | try? fh.close() 69 | return false 70 | } 71 | 72 | return true 73 | } 74 | } 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/LinuxContainerTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOCI 18 | import Foundation 19 | import Testing 20 | 21 | @testable import Containerization 22 | 23 | struct LinuxContainerTests { 24 | 25 | @Test func processInitFromImageConfigWithAllFields() { 26 | let imageConfig = ImageConfig( 27 | user: "appuser", 28 | env: ["NODE_ENV=production", "PORT=3000"], 29 | entrypoint: ["/usr/bin/node"], 30 | cmd: ["app.js", "--verbose"], 31 | workingDir: "/app" 32 | ) 33 | 34 | let process = LinuxProcessConfiguration(from: imageConfig) 35 | 36 | #expect(process.workingDirectory == "/app") 37 | #expect(process.environmentVariables == ["NODE_ENV=production", "PORT=3000"]) 38 | #expect(process.arguments == ["/usr/bin/node", "app.js", "--verbose"]) 39 | #expect(process.user.username == "appuser") 40 | } 41 | 42 | @Test func processInitFromImageConfigWithNilValues() { 43 | let imageConfig = ImageConfig( 44 | user: nil, 45 | env: nil, 46 | entrypoint: nil, 47 | cmd: nil, 48 | workingDir: nil 49 | ) 50 | 51 | let process = LinuxProcessConfiguration(from: imageConfig) 52 | 53 | #expect(process.workingDirectory == "/") 54 | #expect(process.environmentVariables == []) 55 | #expect(process.arguments == []) 56 | #expect(process.user.username == "") // Default User() has empty string username 57 | } 58 | 59 | @Test func processInitFromImageConfigEntrypointAndCmdConcatenation() { 60 | let imageConfig = ImageConfig( 61 | entrypoint: ["/bin/sh", "-c"], 62 | cmd: ["echo 'hello'", "&&", "sleep 10"] 63 | ) 64 | 65 | let process = LinuxProcessConfiguration(from: imageConfig) 66 | 67 | #expect(process.arguments == ["/bin/sh", "-c", "echo 'hello'", "&&", "sleep 10"]) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Containerization/TimeSyncer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import Logging 19 | 20 | actor TimeSyncer { 21 | private var task: Task? 22 | private var context: Vminitd? 23 | private var paused: Bool 24 | private let logger: Logger? 25 | 26 | init(logger: Logger?) { 27 | self.paused = false 28 | self.logger = logger 29 | } 30 | 31 | func start(context: Vminitd, interval: Duration = .seconds(30)) { 32 | guard self.task == nil else { 33 | return 34 | } 35 | 36 | self.context = context 37 | self.task = Task { 38 | while true { 39 | do { 40 | do { 41 | try await Task.sleep(for: interval) 42 | } catch { 43 | return 44 | } 45 | 46 | guard !paused else { 47 | continue 48 | } 49 | 50 | var timeval = timeval() 51 | guard gettimeofday(&timeval, nil) == 0 else { 52 | throw POSIXError.fromErrno() 53 | } 54 | 55 | try await context.setTime( 56 | sec: Int64(timeval.tv_sec), 57 | usec: Int32(timeval.tv_usec) 58 | ) 59 | } catch { 60 | self.logger?.error("failed to sync time with guest agent: \(error)") 61 | } 62 | } 63 | } 64 | } 65 | 66 | func pause() async { 67 | self.paused = true 68 | } 69 | 70 | func resume() async { 71 | self.paused = false 72 | } 73 | 74 | func close() async throws { 75 | guard let task else { 76 | // Already closed, nop. 77 | return 78 | } 79 | 80 | task.cancel() 81 | await task.value 82 | 83 | try await self.context?.close() 84 | self.task = nil 85 | self.context = nil 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/State.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | public enum ContainerState: String, Codable, Sendable { 18 | case creating 19 | case created 20 | case running 21 | case stopped 22 | } 23 | 24 | public struct State: Codable, Sendable { 25 | public init( 26 | version: String, 27 | id: String, 28 | status: ContainerState, 29 | pid: Int, 30 | bundle: String, 31 | annotations: [String: String]? 32 | ) { 33 | self.ociVersion = version 34 | self.id = id 35 | self.status = status 36 | self.pid = pid 37 | self.bundle = bundle 38 | self.annotations = annotations 39 | } 40 | 41 | public init(instance: State) { 42 | self.ociVersion = instance.ociVersion 43 | self.id = instance.id 44 | self.status = instance.status 45 | self.pid = instance.pid 46 | self.bundle = instance.bundle 47 | self.annotations = instance.annotations 48 | } 49 | 50 | public let ociVersion: String 51 | public let id: String 52 | public let status: ContainerState 53 | public let pid: Int 54 | public let bundle: String 55 | public var annotations: [String: String]? 56 | } 57 | 58 | public let seccompFdName: String = "seccompFd" 59 | 60 | public struct ContainerProcessState: Codable, Sendable { 61 | public init(version: String, fds: [String], pid: Int, metadata: String, state: State) { 62 | self.ociVersion = version 63 | self.fds = fds 64 | self.pid = pid 65 | self.metadata = metadata 66 | self.state = state 67 | } 68 | 69 | public init(instance: ContainerProcessState) { 70 | self.ociVersion = instance.ociVersion 71 | self.fds = instance.fds 72 | self.pid = instance.pid 73 | self.metadata = instance.metadata 74 | self.state = instance.state 75 | } 76 | 77 | public let ociVersion: String 78 | public var fds: [String] 79 | public let pid: Int 80 | public let metadata: String 81 | public let state: State 82 | } 83 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/AddressAllocator.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | /// Conforming objects can allocate and free various address types. 18 | public protocol AddressAllocator: Sendable { 19 | associatedtype AddressType: Sendable 20 | 21 | /// Allocate a new address. 22 | func allocate() throws -> AddressType 23 | 24 | /// Attempt to reserve a specific address. 25 | func reserve(_ address: AddressType) throws 26 | 27 | /// Free an allocated address. 28 | func release(_ address: AddressType) throws 29 | 30 | /// If no addresses are allocated, prevent future allocations and return true. 31 | func disableAllocator() -> Bool 32 | } 33 | 34 | /// Errors that a type implementing AddressAllocator should throw. 35 | public enum AllocatorError: Swift.Error, CustomStringConvertible, Equatable { 36 | case allocatorDisabled 37 | case allocatorFull 38 | case alreadyAllocated(_ address: String) 39 | case invalidAddress(_ index: String) 40 | case invalidArgument(_ msg: String) 41 | case invalidIndex(_ index: Int) 42 | case notAllocated(_ address: String) 43 | case rangeExceeded 44 | 45 | public var description: String { 46 | switch self { 47 | case .allocatorDisabled: 48 | return "the allocator is shutting down" 49 | case .allocatorFull: 50 | return "no free indices are available for allocation" 51 | case .alreadyAllocated(let address): 52 | return "cannot choose already-allocated address \(address)" 53 | case .invalidAddress(let address): 54 | return "cannot create index using address \(address)" 55 | case .invalidArgument(let msg): 56 | return "invalid argument: \(msg)" 57 | case .invalidIndex(let index): 58 | return "cannot create address using index \(index)" 59 | case .notAllocated(let address): 60 | return "cannot free unallocated address \(address)" 61 | case .rangeExceeded: 62 | return "cannot create allocator that overflows maximum address value" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Containerization/NATNetworkInterface.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | #if os(macOS) 18 | 19 | import vmnet 20 | import Virtualization 21 | import ContainerizationError 22 | import Foundation 23 | import Synchronization 24 | 25 | /// An interface that uses NAT to provide an IP address for a given 26 | /// container/virtual machine. 27 | @available(macOS 26, *) 28 | public final class NATNetworkInterface: Interface, Sendable { 29 | public let address: String 30 | public let gateway: String? 31 | public let macAddress: String? 32 | 33 | @available(macOS 26, *) 34 | // `reference` isn't used concurrently. 35 | public nonisolated(unsafe) let reference: vmnet_network_ref! 36 | 37 | @available(macOS 26, *) 38 | public init( 39 | address: String, 40 | gateway: String?, 41 | reference: sending vmnet_network_ref, 42 | macAddress: String? = nil 43 | ) { 44 | self.address = address 45 | self.gateway = gateway 46 | self.macAddress = macAddress 47 | self.reference = reference 48 | } 49 | 50 | @available(macOS, obsoleted: 26, message: "Use init(address:gateway:reference:macAddress:) instead") 51 | public init( 52 | address: String, 53 | gateway: String?, 54 | macAddress: String? = nil 55 | ) { 56 | self.address = address 57 | self.gateway = gateway 58 | self.macAddress = macAddress 59 | self.reference = nil 60 | } 61 | } 62 | 63 | @available(macOS 26, *) 64 | extension NATNetworkInterface: VZInterface { 65 | public func device() throws -> VZVirtioNetworkDeviceConfiguration { 66 | let config = VZVirtioNetworkDeviceConfiguration() 67 | if let macAddress = self.macAddress { 68 | guard let mac = VZMACAddress(string: macAddress) else { 69 | throw ContainerizationError(.invalidArgument, message: "invalid mac address \(macAddress)") 70 | } 71 | config.macAddress = mac 72 | } 73 | 74 | config.attachment = VZVmnetNetworkDeviceAttachment(network: self.reference) 75 | return config 76 | } 77 | } 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /Tests/ContainerizationOCITests/OCIImageTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | // 18 | 19 | import Testing 20 | 21 | @testable import ContainerizationOCI 22 | 23 | struct OCITests { 24 | @Test func config() { 25 | let config = ContainerizationOCI.ImageConfig() 26 | let rootfs = ContainerizationOCI.Rootfs(type: "foo", diffIDs: ["diff1", "diff2"]) 27 | let history = ContainerizationOCI.History() 28 | 29 | let image = ContainerizationOCI.Image(architecture: "arm64", os: "linux", config: config, rootfs: rootfs, history: [history]) 30 | #expect(image.rootfs.type == "foo") 31 | } 32 | 33 | @Test func descriptor() { 34 | let platform = ContainerizationOCI.Platform(arch: "arm64", os: "linux") 35 | let descriptor = ContainerizationOCI.Descriptor(mediaType: MediaTypes.descriptor, digest: "123", size: 0, platform: platform) 36 | 37 | #expect(descriptor.platform?.architecture == "arm64") 38 | #expect(descriptor.platform?.os == "linux") 39 | } 40 | 41 | @Test func index() { 42 | var descriptors: [ContainerizationOCI.Descriptor] = [] 43 | for i in 0..<5 { 44 | let descriptor = ContainerizationOCI.Descriptor(mediaType: MediaTypes.descriptor, digest: "\(i)", size: Int64(i)) 45 | descriptors.append(descriptor) 46 | } 47 | 48 | let index = ContainerizationOCI.Index(schemaVersion: 1, manifests: descriptors) 49 | #expect(index.manifests.count == 5) 50 | } 51 | 52 | @Test func manifests() { 53 | var descriptors: [ContainerizationOCI.Descriptor] = [] 54 | for i in 0..<5 { 55 | let descriptor = ContainerizationOCI.Descriptor(mediaType: MediaTypes.descriptor, digest: "\(i)", size: Int64(i)) 56 | descriptors.append(descriptor) 57 | } 58 | 59 | let config = ContainerizationOCI.Descriptor(mediaType: MediaTypes.descriptor, digest: "123", size: 0) 60 | 61 | let manifest = ContainerizationOCI.Manifest(schemaVersion: 1, config: config, layers: descriptors) 62 | #expect(manifest.config.digest == "123") 63 | #expect(manifest.layers.count == 5) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/EXT4+Ptr.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | extension EXT4 { 20 | class Ptr { 21 | let underlying: UnsafeMutablePointer 22 | private var capacity: Int 23 | private var initialized: Bool 24 | private var allocated: Bool 25 | 26 | var pointee: T { 27 | underlying.pointee 28 | } 29 | 30 | init(capacity: Int) { 31 | self.underlying = UnsafeMutablePointer.allocate(capacity: capacity) 32 | self.capacity = capacity 33 | self.allocated = true 34 | self.initialized = false 35 | } 36 | 37 | static func allocate(capacity: Int) -> Ptr { 38 | Ptr(capacity: capacity) 39 | } 40 | 41 | func initialize(to value: T) { 42 | guard self.allocated else { 43 | return 44 | } 45 | if self.initialized { 46 | self.underlying.deinitialize(count: self.capacity) 47 | } 48 | self.underlying.initialize(to: value) 49 | self.allocated = true 50 | self.initialized = true 51 | } 52 | 53 | func deallocate() { 54 | guard self.allocated else { 55 | return 56 | } 57 | self.underlying.deallocate() 58 | self.allocated = false 59 | self.initialized = false 60 | } 61 | 62 | func deinitialize(count: Int) { 63 | guard self.allocated else { 64 | return 65 | } 66 | guard self.initialized else { 67 | return 68 | } 69 | self.underlying.deinitialize(count: count) 70 | self.initialized = false 71 | self.allocated = true 72 | } 73 | 74 | func move() -> T { 75 | self.initialized = false 76 | self.allocated = true 77 | return self.underlying.move() 78 | } 79 | 80 | deinit { 81 | self.deinitialize(count: self.capacity) 82 | self.deallocate() 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/Containerization/UnixSocketConfiguration.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | import SystemPackage 19 | 20 | /// Represents a UnixSocket that can be shared into or out of a container/guest. 21 | public struct UnixSocketConfiguration: Sendable { 22 | // TODO: Realistically, we can just hash this struct and use it as the "id". 23 | package var id: String { 24 | _id 25 | } 26 | 27 | private let _id = UUID().uuidString 28 | 29 | /// The path to the socket you'd like relayed. For .into 30 | /// direction this should be the path on the host to a unix socket. 31 | /// For direction .outOf this should be the path in the container/guest 32 | /// to a unix socket. 33 | public var source: URL 34 | 35 | /// The path you'd like the socket to be relayed to. For .into 36 | /// direction this should be the path in the container/guest. For 37 | /// direction .outOf this should be the path on your host. 38 | public var destination: URL 39 | 40 | /// What to set the file permissions of the unix socket being created 41 | /// to. For .into direction this will be the socket in the guest. For 42 | /// .outOf direction this will be the socket on the host. 43 | public var permissions: FilePermissions? 44 | 45 | /// The direction of the relay. `.into` for sharing a unix socket on your 46 | /// host into the container/guest. `outOf` shares a socket in the container/guest 47 | /// onto your host. 48 | public var direction: Direction 49 | 50 | /// Type that denotes the direction of the unix socket relay. 51 | public enum Direction: Sendable { 52 | /// Share the socket into the container/guest. 53 | case into 54 | /// Share a socket in the container/guest onto the host. 55 | case outOf 56 | } 57 | 58 | public init( 59 | source: URL, 60 | destination: URL, 61 | permissions: FilePermissions? = nil, 62 | direction: Direction = .into 63 | ) { 64 | self.source = source 65 | self.destination = destination 66 | self.permissions = permissions 67 | self.direction = direction 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/LocalContent.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationError 18 | import Crypto 19 | import Foundation 20 | 21 | public final class LocalContent: Content { 22 | public let path: URL 23 | private let file: FileHandle 24 | 25 | public init(path: URL) throws { 26 | guard FileManager.default.fileExists(atPath: path.path) else { 27 | throw ContainerizationError(.notFound, message: "content at path \(path.absolutePath())") 28 | } 29 | 30 | self.file = try FileHandle(forReadingFrom: path) 31 | self.path = path 32 | } 33 | 34 | public func digest() throws -> SHA256.Digest { 35 | let bufferSize = 64 * 1024 // 64 KB 36 | var hasher = SHA256() 37 | 38 | try self.file.seek(toOffset: 0) 39 | while case let data = file.readData(ofLength: bufferSize), !data.isEmpty { 40 | hasher.update(data: data) 41 | } 42 | 43 | let digest = hasher.finalize() 44 | 45 | try self.file.seek(toOffset: 0) 46 | return digest 47 | } 48 | 49 | public func data(offset: UInt64 = 0, length size: Int = 0) throws -> Data? { 50 | try file.seek(toOffset: offset) 51 | if size == 0 { 52 | return try file.readToEnd() 53 | } 54 | return try file.read(upToCount: size) 55 | } 56 | 57 | public func data() throws -> Data { 58 | try Data(contentsOf: self.path) 59 | } 60 | 61 | public func size() throws -> UInt64 { 62 | let fileAttrs = try FileManager.default.attributesOfItem(atPath: self.path.absolutePath()) 63 | if let size = fileAttrs[FileAttributeKey.size] as? UInt64 { 64 | return size 65 | } 66 | throw ContainerizationError(.internalError, message: "could not determine file size for \(path.absolutePath())") 67 | } 68 | 69 | public func decode() throws -> T where T: Decodable { 70 | let json = JSONDecoder() 71 | let data = try Data(contentsOf: self.path) 72 | return try json.decode(T.self, from: data) 73 | } 74 | 75 | deinit { 76 | try? self.file.close() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/UnsafeLittleEndianBytes.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import Foundation 18 | 19 | // takes a pointer and converts its contents to native endian bytes 20 | public func withUnsafeLittleEndianBytes(of value: T, body: (UnsafeRawBufferPointer) throws -> Result) 21 | rethrows -> Result 22 | { 23 | switch Endian { 24 | case .little: 25 | return try withUnsafeBytes(of: value) { bytes in 26 | try body(bytes) 27 | } 28 | case .big: 29 | return try withUnsafeBytes(of: value) { buffer in 30 | let reversedBuffer = Array(buffer.reversed()) 31 | return try reversedBuffer.withUnsafeBytes { buf in 32 | try body(buf) 33 | } 34 | } 35 | } 36 | } 37 | 38 | public func withUnsafeLittleEndianBuffer( 39 | of value: UnsafeRawBufferPointer, body: (UnsafeRawBufferPointer) throws -> T 40 | ) rethrows -> T { 41 | switch Endian { 42 | case .little: 43 | return try body(value) 44 | case .big: 45 | let reversed = Array(value.reversed()) 46 | return try reversed.withUnsafeBytes { buf in 47 | try body(buf) 48 | } 49 | } 50 | } 51 | 52 | extension UnsafeRawBufferPointer { 53 | // loads littleEndian raw data, converts it native endian format and calls UnsafeRawBufferPointer.load 54 | public func loadLittleEndian(as type: T.Type) -> T { 55 | switch Endian { 56 | case .little: 57 | return self.load(as: T.self) 58 | case .big: 59 | let buffer = Array(self.reversed()) 60 | return buffer.withUnsafeBytes { ptr in 61 | ptr.load(as: T.self) 62 | } 63 | } 64 | } 65 | } 66 | 67 | public enum Endianness { 68 | case little 69 | case big 70 | } 71 | 72 | // returns current endianness 73 | public var Endian: Endianness { 74 | switch CFByteOrderGetCurrent() { 75 | case CFByteOrder(CFByteOrderLittleEndian.rawValue): 76 | return .little 77 | case CFByteOrder(CFByteOrderBigEndian.rawValue): 78 | return .big 79 | default: 80 | fatalError("impossible") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Client/RegistryClient+Error.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import AsyncHTTPClient 18 | import Foundation 19 | import NIOHTTP1 20 | 21 | extension RegistryClient { 22 | /// `RegistryClient` errors. 23 | public enum Error: Swift.Error, CustomStringConvertible { 24 | case invalidStatus(url: String, HTTPResponseStatus, reason: String? = nil) 25 | 26 | /// Description of the errors. 27 | public var description: String { 28 | switch self { 29 | case .invalidStatus(let u, let response, let reason): 30 | return "HTTP request to \(u) failed with response: \(response.description). Reason: \(reason ?? "Unknown")" 31 | } 32 | } 33 | } 34 | 35 | /// The container registry typically returns actionable failure reasons in the response body 36 | /// of the failing HTTP Request. This type models the structure of the error message. 37 | /// Reference: https://distribution.github.io/distribution/spec/api/#errors 38 | internal struct ErrorResponse: Codable { 39 | let errors: [RemoteError] 40 | 41 | internal struct RemoteError: Codable { 42 | let code: String 43 | let message: String 44 | let detail: String? 45 | } 46 | 47 | internal static func fromResponseBody(_ body: HTTPClientResponse.Body) async -> ErrorResponse? { 48 | guard var buffer = try? await body.collect(upTo: Int(1.mib())) else { 49 | return nil 50 | } 51 | guard let bytes = buffer.readBytes(length: buffer.readableBytes) else { 52 | return nil 53 | } 54 | let data = Data(bytes) 55 | guard let jsonError = try? JSONDecoder().decode(ErrorResponse.self, from: data) else { 56 | return nil 57 | } 58 | return jsonError 59 | } 60 | 61 | public var jsonString: String { 62 | let data = try? JSONEncoder().encode(self) 63 | guard let data else { 64 | return "{}" 65 | } 66 | return String(data: data, encoding: .utf8) ?? "{}" 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/Application.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | //===----------------------------------------------------------------------===// 16 | 17 | import ContainerizationOS 18 | import Foundation 19 | import Logging 20 | 21 | @main 22 | struct Application { 23 | static func main() async throws { 24 | LoggingSystem.bootstrap(StreamLogHandler.standardError) 25 | 26 | // Parse command line arguments 27 | let args = CommandLine.arguments 28 | let command = args.count > 1 ? args[1] : "init" 29 | 30 | switch command { 31 | case "pause": 32 | let log = Logger(label: "pause") 33 | 34 | log.info("Running pause command") 35 | try PauseCommand.run(log: log) 36 | case "init": 37 | fallthrough 38 | default: 39 | let log = Logger(label: "vminitd") 40 | 41 | log.info("Running init command") 42 | try Self.mountProc(log: log) 43 | try await InitCommand.run(log: log) 44 | } 45 | } 46 | 47 | // Swift seems like it has some fun issues trying to spawn threads if /proc isn't around, so we 48 | // do this before calling our first async function. 49 | static func mountProc(log: Logger) throws { 50 | // Is it already mounted (would only be true in debug builds where we re-exec ourselves)? 51 | if isProcMounted() { 52 | return 53 | } 54 | 55 | log.info("mounting /proc") 56 | 57 | let mnt = ContainerizationOS.Mount( 58 | type: "proc", 59 | source: "proc", 60 | target: "/proc", 61 | options: [] 62 | ) 63 | try mnt.mount(createWithPerms: 0o755) 64 | } 65 | 66 | static func isProcMounted() -> Bool { 67 | guard let data = try? String(contentsOfFile: "/proc/mounts", encoding: .utf8) else { 68 | return false 69 | } 70 | 71 | for line in data.split(separator: "\n") { 72 | let fields = line.split(separator: " ") 73 | if fields.count >= 2 { 74 | let mountPoint = String(fields[1]) 75 | if mountPoint == "/proc" { 76 | return true 77 | } 78 | } 79 | } 80 | 81 | return false 82 | } 83 | } 84 | --------------------------------------------------------------------------------