├── .github ├── ISSUE_TEMPLATE │ ├── 01-bug.yml │ ├── 02-feature.yml │ └── config.yml └── workflows │ ├── build-test-images.yml │ ├── containerization-build-template.yml │ ├── containerization-build.yml │ ├── docs-release.yaml │ └── release.yml ├── .gitignore ├── .spi.yml ├── .swift-format ├── .swift-version ├── CONTRIBUTING.md ├── CONTRIBUTORS.txt ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── Protobuf.Makefile ├── README.md ├── SECURITY.md ├── Sources ├── CShim │ ├── exec_command.c │ ├── include │ │ ├── exec_command.h │ │ └── vsock.h │ └── vsock.c ├── Containerization │ ├── Agent │ │ ├── Vminitd+Rosetta.swift │ │ ├── Vminitd+SocketRelay.swift │ │ └── Vminitd.swift │ ├── AttachedFilesystem.swift │ ├── Container.swift │ ├── DNSConfiguration.swift │ ├── Hash.swift │ ├── IO │ │ ├── ReaderStream.swift │ │ ├── Terminal+ReaderStream.swift │ │ └── Writer.swift │ ├── Image │ │ ├── Image.swift │ │ ├── ImageStore │ │ │ ├── ImageStore+Export.swift │ │ │ ├── ImageStore+Import.swift │ │ │ ├── ImageStore+OCILayout.swift │ │ │ ├── ImageStore+ReferenceManager.swift │ │ │ └── ImageStore.swift │ │ ├── InitImage.swift │ │ ├── KernelImage.swift │ │ └── Unpacker │ │ │ ├── EXT4Unpacker.swift │ │ │ └── Unpacker.swift │ ├── Interface.swift │ ├── Kernel.swift │ ├── LinuxContainer.swift │ ├── LinuxProcess.swift │ ├── Mount.swift │ ├── NATInterface.swift │ ├── NATNetworkInterface.swift │ ├── SandboxContext │ │ ├── SandboxContext.grpc.swift │ │ ├── SandboxContext.pb.swift │ │ └── SandboxContext.proto │ ├── SystemPlatform.swift │ ├── TimeSyncer.swift │ ├── UnixSocketConfiguration.swift │ ├── UnixSocketRelay.swift │ ├── VZVirtualMachine+Helpers.swift │ ├── VZVirtualMachineInstance.swift │ ├── VZVirtualMachineManager.swift │ ├── VirtualMachineAgent+Additions.swift │ ├── VirtualMachineAgent.swift │ ├── VirtualMachineInstance.swift │ ├── VirtualMachineManager.swift │ └── VsockConnectionStream.swift ├── ContainerizationArchive │ ├── ArchiveError.swift │ ├── ArchiveWriter.swift │ ├── ArchiveWriterConfiguration.swift │ ├── CArchive │ │ ├── COPYING │ │ ├── archive_swift_bridge.c │ │ └── include │ │ │ ├── archive.h │ │ │ ├── archive_bridge.h │ │ │ └── archive_entry.h │ ├── FileArchiveWriterDelegate.swift │ ├── Reader.swift │ ├── TempDir.swift │ └── WriteEntry.swift ├── ContainerizationEXT4 │ ├── Documentation.docc │ │ └── ext4.md │ ├── EXT4+Extensions.swift │ ├── EXT4+FileTree.swift │ ├── EXT4+Formatter.swift │ ├── EXT4+Ptr.swift │ ├── EXT4+Reader.swift │ ├── EXT4+Types.swift │ ├── EXT4+Xattrs.swift │ ├── EXT4.swift │ ├── EXT4Reader+Export.swift │ ├── FilePath+Extensions.swift │ ├── FileTimestamps.swift │ ├── Formatter+Unpack.swift │ ├── Integer+Extensions.swift │ ├── README.md │ └── UnsafeLittleEndianBytes.swift ├── ContainerizationError │ └── ContainerizationError.swift ├── ContainerizationExtras │ ├── AddressAllocator.swift │ ├── AsyncLock.swift │ ├── CIDRAddress.swift │ ├── FileManager+Temporary.swift │ ├── IPAddress.swift │ ├── IndexedAddressAllocator.swift │ ├── NetworkAddress+Allocator.swift │ ├── NetworkAddress.swift │ ├── ProgressEvent.swift │ ├── RotatingAddressAllocator.swift │ ├── Timeout.swift │ └── UInt8+DataBinding.swift ├── ContainerizationIO │ └── ReadStream.swift ├── ContainerizationNetlink │ ├── NetlinkSession.swift │ ├── NetlinkSocket.swift │ └── Types.swift ├── ContainerizationOCI │ ├── AnnotationKeys.swift │ ├── Bundle.swift │ ├── Client │ │ ├── Authentication.swift │ │ ├── KeychainHelper.swift │ │ ├── LocalOCILayoutClient.swift │ │ ├── RegistryClient+Error.swift │ │ ├── RegistryClient+Fetch.swift │ │ ├── RegistryClient+Push.swift │ │ ├── RegistryClient+Token.swift │ │ └── RegistryClient.swift │ ├── Content │ │ ├── AsyncTypes.swift │ │ ├── Content.swift │ │ ├── ContentStoreProtocol.swift │ │ ├── ContentWriter.swift │ │ ├── LocalContent.swift │ │ ├── LocalContentStore.swift │ │ ├── SHA256+Extensions.swift │ │ ├── String+Extension.swift │ │ └── URL+Extensions.swift │ ├── Descriptor.swift │ ├── FileManager+Size.swift │ ├── ImageConfig.swift │ ├── Index.swift │ ├── Manifest.swift │ ├── MediaType.swift │ ├── Platform.swift │ ├── Reference.swift │ ├── Spec.swift │ ├── State.swift │ └── Version.swift ├── ContainerizationOS │ ├── AsyncSignalHandler.swift │ ├── BinaryInteger+Extensions.swift │ ├── Command.swift │ ├── File.swift │ ├── Keychain │ │ └── KeychainQuery.swift │ ├── Linux │ │ ├── Binfmt.swift │ │ └── Epoll.swift │ ├── Mount │ │ └── Mount.swift │ ├── POSIXError+Helpers.swift │ ├── Path.swift │ ├── Pipe+Close.swift │ ├── README.md │ ├── Reaper.swift │ ├── Signals.swift │ ├── Socket │ │ ├── Socket.swift │ │ ├── SocketType.swift │ │ ├── UnixType.swift │ │ └── VsockType.swift │ ├── Syscall.swift │ ├── Sysctl.swift │ ├── Terminal.swift │ ├── URL+Extensions.swift │ └── User.swift ├── Integration │ ├── ProcessTests.swift │ ├── Suite.swift │ └── VMTests.swift ├── SendableProperty │ └── SendableProperty.swift ├── SendablePropertyMacros │ ├── SendablePropertyError.swift │ ├── SendablePropertyMacro.swift │ └── SendablePropertyPlugin.swift └── cctl │ ├── ContainerStore.swift │ ├── ImageCommand.swift │ ├── KernelCommand.swift │ ├── LoginCommand.swift │ ├── RootfsCommand.swift │ ├── RunCommand.swift │ ├── cctl+Utils.swift │ └── cctl.swift ├── Tests ├── ContainerizationArchiveTests │ └── ArchiveTests.swift ├── ContainerizationEXT4Tests │ ├── Resources │ │ └── content │ │ │ └── blobs │ │ │ └── sha256 │ │ │ ├── 48a06049d3738991b011ca8b12473d712b7c40666a1462118dae3c403676afc2 │ │ │ ├── 4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 │ │ │ ├── 8e2eb240a6cd7be1a0d308125afe0060b020e89275ced2e729eda7d4eeff62a2 │ │ │ ├── ad59e9f71edceca7b1ac7c642410858489b743c97233b0a26a5e2098b1443762 │ │ │ └── c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44 │ ├── TestEXT4ExtendedAttributes.swift │ ├── TestEXT4Format+Create.swift │ ├── TestEXT4Format.swift │ ├── TestEXT4Unpacker.swift │ └── TestFormatterUnpack.swift ├── ContainerizationExtrasTests │ ├── TestCIDRAddress.swift │ ├── TestIPAddress.swift │ ├── TestNetworkAddress+Allocator.swift │ └── UInt8+DataBindingTest.swift ├── ContainerizationNetlinkTests │ ├── MockNetlinkSocket.swift │ ├── NetlinkSessionTest.swift │ └── TypesTest.swift ├── ContainerizationOCITests │ ├── AuthChallengeTests.swift │ ├── OCIImageTests.swift │ ├── OCIPlatformTests.swift │ ├── ReferenceTests.swift │ └── RegistryClientTests.swift ├── ContainerizationOSTests │ ├── KeychainQueryTests.swift │ └── UserTests.swift ├── ContainerizationTests │ ├── ImageTests │ │ ├── ContainsAuth.swift │ │ ├── ImageStoreImagePullTests.swift │ │ ├── ImageStoreTests.swift │ │ └── Resources │ │ │ └── scratch.tar │ └── KernelTests.swift ├── SendablePropertyMacrosTests │ └── SendablePropertyMacrosTests.swift ├── SendablePropertyTests │ └── SendablePropertyTests.swift └── TestImages │ ├── dockermanifestimage │ └── Dockerfile │ └── emptyimage │ └── Dockerfile ├── kernel-build ├── Dockerfile └── sources.list ├── kernel ├── Dockerfile ├── Makefile ├── README.md ├── build.sh ├── config-arm64 └── image │ ├── Dockerfile │ └── sources.list ├── licenserc.toml ├── scripts ├── cz-header-style.toml ├── ensure-hawkeye-exists.sh ├── install-hawkeye.sh ├── license-header.txt └── make-docs.sh ├── signing └── vz.entitlements └── vminitd ├── Makefile ├── Package.resolved ├── Package.swift └── Sources ├── LCShim ├── include │ └── syscall.h └── syscall.c ├── vmexec ├── ExecCommand.swift ├── Mount.swift ├── RunCommand.swift └── vmexec.swift └── vminitd ├── Application.swift ├── HostStdio.swift ├── IOCloser+Extensions.swift ├── IOCloser.swift ├── IOPair.swift ├── ManagedContainer.swift ├── ManagedProcess.swift ├── OSFile+Splice.swift ├── OSFile.swift ├── ProcessSupervisor.swift ├── Server+GRPC.swift ├── Server.swift ├── StandardIO.swift ├── TerminalIO.swift └── VsockProxy.swift /.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 Beta (25A5279m) 47 | - **Swift**: Apple Swift version 6.1 (swift-6.1-RELEASE) 48 | - **Xcode**: Version 26.0 beta (17A5241e) 49 | value: | 50 | - OS: 51 | - Swift: 52 | - Xcode: 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 | render: shell 62 | - type: checkboxes 63 | id: terms 64 | attributes: 65 | label: Code of Conduct 66 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/apple/.github/blob/main/CODE_OF_CONDUCT.md). 67 | options: 68 | - label: I agree to follow this project's Code of Conduct 69 | required: true 70 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/workflows/build-test-images.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish containerization test images 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | publish: 7 | type: boolean 8 | description: "Publish the built image" 9 | default: false 10 | version: 11 | type: string 12 | description: "Version of the image to create" 13 | default: "test" 14 | image: 15 | type: choice 16 | description: Test image to build 17 | options: 18 | - dockermanifestimage 19 | - emptyimage 20 | default: 'dockermanifestimage' 21 | useBuildx: 22 | type: boolean 23 | description: "Use docker buildx to build the image" 24 | default: false 25 | 26 | jobs: 27 | image: 28 | name: Build test images 29 | timeout-minutes: 30 30 | runs-on: ubuntu-latest 31 | permissions: 32 | contents: read 33 | packages: write 34 | steps: 35 | - name: Check branch 36 | run: | 37 | if [[ "${{ github.ref }}" != "refs/heads/main" ]] && [[ "${{ github.ref }}" != refs/heads/release* ]] && [[ "${{ inputs.publish }}" == "true" ]]; then 38 | echo "❌ Cannot publish an image if we are not on main or a release branch." 39 | exit 1 40 | fi 41 | - name: Check inputs 42 | run: | 43 | if [[ "${{ inputs.image }}" == "dockermanifestimage" ]] && [[ "${{ inputs.useBuildx }}" == "true" ]]; then 44 | echo "❌ dockermanifestimage cannot be built with buildx" 45 | exit 1 46 | fi 47 | 48 | if [[ "${{ inputs.image }}" == "emptyimage" ]] && [[ "${{ inputs.useBuildx }}" != "true" ]]; then 49 | echo "❌ emptyimage should be built with buildx" 50 | exit 1 51 | fi 52 | - name: Checkout repository 53 | uses: actions/checkout@v4 54 | - name: Login to GitHub Container Registry 55 | uses: docker/login-action@v3 56 | with: 57 | registry: ghcr.io 58 | username: ${{ github.actor }} 59 | password: ${{ secrets.GITHUB_TOKEN }} 60 | - name: Set up Docker Buildx 61 | if: ${{ inputs.useBuildx }} 62 | uses: docker/setup-buildx-action@v3 63 | - name: Build dockerfile and push image 64 | uses: docker/build-push-action@v6 65 | with: 66 | push: ${{ inputs.publish }} 67 | context: Tests/TestImages/${{ inputs.image }} 68 | tags: ghcr.io/apple/containerization/${{ inputs.image }}:${{ inputs.version }} 69 | -------------------------------------------------------------------------------- /.github/workflows/containerization-build.yml: -------------------------------------------------------------------------------- 1 | name: Build containerization 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | push: 7 | branches: 8 | - main 9 | - release/* 10 | 11 | jobs: 12 | containerization: 13 | permissions: 14 | contents: read 15 | packages: write 16 | pages: write 17 | uses: ./.github/workflows/containerization-build-template.yml 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.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 | on: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | checkBranch: 9 | runs-on: ubuntu-latest 10 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags') || startsWith(github.ref, 'refs/heads/release') 11 | steps: 12 | - name: Branch validation 13 | run: echo "Branch ${{ github.ref_name }} is allowed" 14 | 15 | buildSite: 16 | name: Build application website 17 | needs: checkBranch 18 | uses: ./.github/workflows/containerization-build-template.yml 19 | secrets: inherit 20 | permissions: 21 | contents: read 22 | packages: write 23 | pages: write 24 | 25 | deployDocs: 26 | runs-on: ubuntu-latest 27 | needs: [checkBranch, buildSite] 28 | permissions: 29 | contents: read 30 | pages: write 31 | id-token: write 32 | 33 | environment: 34 | name: github-pages 35 | url: ${{ steps.deployment.outputs.page_url }} 36 | 37 | steps: 38 | - name: Deploy to GitHub Pages 39 | id: deployment 40 | uses: actions/deploy-pages@v4 41 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release containerization 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+\\.[0-9]+\\.[0-9]+" 7 | 8 | jobs: 9 | containerization: 10 | uses: ./.github/workflows/containerization-build-template.yml 11 | with: 12 | release: true 13 | version: ${{ github.ref_name }} 14 | secrets: inherit 15 | permissions: 16 | contents: read 17 | packages: write 18 | pages: write 19 | 20 | deployDocs: 21 | if: startsWith(github.ref, 'refs/tags/') 22 | runs-on: ubuntu-latest 23 | needs: containerization 24 | permissions: 25 | contents: read 26 | pages: write 27 | id-token: write 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | steps: 32 | - name: Deploy to GitHub Pages 33 | id: deployment 34 | uses: actions/deploy-pages@v4 35 | 36 | release: 37 | if: startsWith(github.ref, 'refs/tags/') 38 | name: Publish release 39 | timeout-minutes: 30 40 | needs: containerization 41 | runs-on: ubuntu-latest 42 | permissions: 43 | contents: write 44 | packages: read 45 | steps: 46 | - name: Create release 47 | uses: softprops/action-gh-release@v2 48 | with: 49 | token: ${{ github.token }} 50 | name: ${{ github.ref_name }}-prerelease 51 | draft: true 52 | make_latest: false 53 | prerelease: true 54 | fail_on_unmatched_files: true 55 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 6.2-snapshot-2025-06-25 -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | This file contains a list of contributors who have made meaningful changes to this repository. 2 | Please add your name and GitHub handle to this file as an optional step in the contribution process for attribution. 3 | Email is not required. 4 | 5 | ### Contributors 6 | 7 | Aditya Ramani (adityaramani) 8 | Agam Dua (agamdua) 9 | Danny Canter (dcantah) 10 | Dmitry Kovba (dkovba) 11 | Eric Ernst (egernst) 12 | Evan Hazlett (ehazlett) 13 | Gilbert Song (gilbert88) 14 | Hugh Bussell (hughbussell) 15 | John Logan (jglogan) 16 | Kathryn Baldauf (katiewasnothere) 17 | Madhu Venugopal (mavenugo) 18 | Michael Crosby (crosbymichael) 19 | Nandha Reddy (nandsha) 20 | Sidhartha Mani (wlan0) 21 | Tanweer Noor (tanweernoor) 22 | Ximena Perez Diaz (ximenanperez) 23 | Yibo Zhuang (yibozhuang) 24 | -------------------------------------------------------------------------------- /Protobuf.Makefile: -------------------------------------------------------------------------------- 1 | LOCAL_DIR := $(ROOT_DIR)/.local 2 | LOCALBIN := $(LOCAL_DIR)/bin 3 | 4 | ## Versions 5 | PROTOC_VERSION=26.1 6 | 7 | # protoc binary installation 8 | PROTOC_ZIP = protoc-$(PROTOC_VERSION)-osx-universal_binary.zip 9 | PROTOC = $(LOCALBIN)/protoc@$(PROTOC_VERSION)/protoc 10 | $(PROTOC): 11 | @echo Downloading protocol buffers... 12 | @mkdir -p $(LOCAL_DIR) 13 | @curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC_ZIP) 14 | @mkdir -p $(dir $@) 15 | @unzip -jo $(PROTOC_ZIP) bin/protoc -d $(dir $@) 16 | @unzip -o $(PROTOC_ZIP) 'include/*' -d $(dir $@) 17 | @rm -f $(PROTOC_ZIP) 18 | 19 | protoc_gen_grpc_swift: 20 | swift build --product protoc-gen-grpc-swift 21 | 22 | protoc-gen-swift: 23 | swift build --product protoc-gen-swift 24 | 25 | .PHONY: protos 26 | protos: $(PROTOC) protoc_gen_grpc_swift protoc-gen-swift 27 | @echo Generating protocol buffers source code... 28 | @$(PROTOC) Sources/Containerization/SandboxContext/SandboxContext.proto \ 29 | --plugin=protoc-gen-grpc-swift=$(BUILD_BIN_DIR)/protoc-gen-grpc-swift \ 30 | --plugin=protoc-gen-swift=$(BUILD_BIN_DIR)/protoc-gen-swift \ 31 | --proto_path=Sources/Containerization/SandboxContext \ 32 | --grpc-swift_out="Sources/Containerization/SandboxContext" \ 33 | --grpc-swift_opt=Visibility=Public \ 34 | --swift_out="Sources/Containerization/SandboxContext" \ 35 | --swift_opt=Visibility=Public \ 36 | -I. 37 | @"$(MAKE)" update-licenses 38 | -------------------------------------------------------------------------------- /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/CShim/include/exec_command.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | #include 21 | #include 22 | 23 | struct exec_command_attrs { 24 | int setpgid; 25 | /// parent group id 26 | pid_t pgid; 27 | /// set the controlling terminal 28 | int setctty; 29 | /// controlling terminal fd 30 | int ctty; 31 | /// set the process as session leader 32 | int setsid; 33 | /// set the process user id 34 | uid_t uid; 35 | /// set the process group id 36 | gid_t gid; 37 | /// signal mask for the child process 38 | int mask; 39 | }; 40 | 41 | void exec_command_attrs_init(struct exec_command_attrs *attrs); 42 | 43 | /// spawn a new child process with the provided attrs 44 | int exec_command(pid_t *result, const char *executable, char *const argv[], 45 | char *const envp[], const int file_handles[], 46 | const int file_handle_count, const char *working_directory, 47 | struct exec_command_attrs *attrs); 48 | 49 | #endif /* exec_command_h */ 50 | -------------------------------------------------------------------------------- /Sources/CShim/include/vsock.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/CShim/vsock.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Containerization/Agent/Vminitd+Rosetta.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/Agent/Vminitd+SocketRelay.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/AttachedFilesystem.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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.type = mount.type 37 | self.source = name 38 | case "ext4": 39 | let char = try allocator.allocate() 40 | self.type = mount.type 41 | self.source = "/dev/vd\(char)" 42 | default: 43 | self.type = mount.type 44 | self.source = mount.source 45 | } 46 | self.options = mount.options 47 | self.destination = mount.destination 48 | } 49 | #endif 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Containerization/Container.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Containerization/DNSConfiguration.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 += "opts \(options.joined(separator: " "))\n" 64 | } 65 | 66 | return text 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/Containerization/Hash.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | 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/Containerization/IO/ReaderStream.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/IO/Terminal+ReaderStream.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Containerization/IO/Writer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | } 23 | -------------------------------------------------------------------------------- /Sources/Containerization/Image/Unpacker/EXT4Unpacker.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 ContainerizationExtras 19 | import ContainerizationOCI 20 | import Foundation 21 | 22 | #if os(macOS) 23 | import ContainerizationArchive 24 | import ContainerizationEXT4 25 | import SystemPackage 26 | #endif 27 | 28 | public struct EXT4Unpacker: Unpacker { 29 | let blockSizeInBytes: UInt64 30 | 31 | public init(blockSizeInBytes: UInt64) { 32 | self.blockSizeInBytes = blockSizeInBytes 33 | } 34 | 35 | public func unpack(_ image: Image, for platform: Platform, at path: URL, progress: ProgressHandler? = nil) async throws -> Mount { 36 | #if !os(macOS) 37 | throw ContainerizationError(.unsupported, message: "Cannot unpack an image on current platform") 38 | #else 39 | let blockPath = try prepareUnpackPath(path: path) 40 | let manifest = try await image.manifest(for: platform) 41 | let filesystem = try EXT4.Formatter(FilePath(path), minDiskSize: blockSizeInBytes) 42 | defer { try? filesystem.close() } 43 | 44 | for layer in manifest.layers { 45 | try Task.checkCancellation() 46 | let content = try await image.getContent(digest: layer.digest) 47 | 48 | let compression: ContainerizationArchive.Filter 49 | switch layer.mediaType { 50 | case MediaTypes.imageLayer, MediaTypes.dockerImageLayer: 51 | compression = .none 52 | case MediaTypes.imageLayerGzip, MediaTypes.dockerImageLayerGzip: 53 | compression = .gzip 54 | default: 55 | throw ContainerizationError(.unsupported, message: "Media type \(layer.mediaType) not supported.") 56 | } 57 | try filesystem.unpack( 58 | source: content.path, 59 | format: .paxRestricted, 60 | compression: compression, 61 | progress: progress 62 | ) 63 | } 64 | 65 | return .block( 66 | format: "ext4", 67 | source: blockPath, 68 | destination: "/", 69 | options: [] 70 | ) 71 | #endif 72 | } 73 | 74 | private func prepareUnpackPath(path: URL) throws -> String { 75 | let blockPath = path.absolutePath() 76 | guard !FileManager.default.fileExists(atPath: blockPath) else { 77 | throw ContainerizationError(.exists, message: "block device already exists at \(blockPath)") 78 | } 79 | return blockPath 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Containerization/Image/Unpacker/Unpacker.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Containerization/Interface.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/NATInterface.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/SystemPlatform.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/TimeSyncer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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: Sendable { 21 | private var task: Task? 22 | private var context: Vminitd? 23 | private let logger: Logger? 24 | 25 | init(logger: Logger?) { 26 | self.logger = logger 27 | } 28 | 29 | func start(context: Vminitd, interval: Duration = .seconds(30)) { 30 | precondition(task == nil, "time syncer is already running") 31 | self.context = context 32 | self.task = Task { 33 | while true { 34 | do { 35 | do { 36 | try await Task.sleep(for: interval) 37 | } catch { 38 | return 39 | } 40 | 41 | var timeval = timeval() 42 | guard gettimeofday(&timeval, nil) == 0 else { 43 | throw POSIXError.fromErrno() 44 | } 45 | 46 | try await context.setTime( 47 | sec: Int64(timeval.tv_sec), 48 | usec: Int32(timeval.tv_usec) 49 | ) 50 | } catch { 51 | self.logger?.error("failed to sync time with guest agent: \(error)") 52 | } 53 | } 54 | } 55 | } 56 | 57 | func close() async throws { 58 | guard let task else { 59 | preconditionFailure("time syncer was already closed") 60 | } 61 | 62 | task.cancel() 63 | try await self.context?.close() 64 | self.task = nil 65 | self.context = nil 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Containerization/UnixSocketConfiguration.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Containerization/VZVirtualMachineManager.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | import ContainerizationError 19 | import ContainerizationOCI 20 | import Foundation 21 | import Logging 22 | 23 | /// A virtualization.framework backed `VirtualMachineManager` implementation. 24 | public struct VZVirtualMachineManager: VirtualMachineManager { 25 | private let kernel: Kernel 26 | private let bootlog: String? 27 | private let initialFilesystem: Mount 28 | private let logger: Logger? 29 | 30 | public init( 31 | kernel: Kernel, 32 | initialFilesystem: Mount, 33 | bootlog: String?, 34 | logger: Logger? = nil 35 | ) { 36 | self.kernel = kernel 37 | self.bootlog = bootlog 38 | self.initialFilesystem = initialFilesystem 39 | self.logger = logger 40 | } 41 | 42 | public func create(container: Container) throws -> any VirtualMachineInstance { 43 | guard let c = container as? LinuxContainer else { 44 | throw ContainerizationError( 45 | .invalidArgument, 46 | message: "provided container is not a LinuxContainer" 47 | ) 48 | } 49 | 50 | return try VZVirtualMachineInstance( 51 | logger: self.logger, 52 | with: { config in 53 | config.cpus = container.cpus 54 | config.memoryInBytes = container.memoryInBytes 55 | 56 | config.kernel = self.kernel 57 | config.initialFilesystem = self.initialFilesystem 58 | 59 | config.interfaces = container.interfaces 60 | if let bootlog { 61 | config.bootlog = URL(filePath: bootlog) 62 | } 63 | config.rosetta = c.rosetta 64 | config.nestedVirtualization = c.virtualization 65 | 66 | config.mounts = [c.rootfs] + c.mounts 67 | }) 68 | } 69 | } 70 | #endif 71 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineAgent+Additions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineAgent.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 ContainerizationOCI 19 | import Foundation 20 | 21 | /// A protocol for the agent running inside a virtual machine. If an operation isn't 22 | /// supported the implementation MUST return a ContainerizationError with a code of 23 | /// `.unsupported`. 24 | public protocol VirtualMachineAgent: Sendable { 25 | /// Perform a platform specific standard setup 26 | /// of the runtime environment. 27 | func standardSetup() async throws 28 | /// Close any resources held by the agent. 29 | func close() async throws 30 | 31 | // POSIX 32 | func getenv(key: String) async throws -> String 33 | func setenv(key: String, value: String) async throws 34 | func mount(_ mount: ContainerizationOCI.Mount) async throws 35 | func umount(path: String, flags: Int32) async throws 36 | func mkdir(path: String, all: Bool, perms: UInt32) async throws 37 | @discardableResult 38 | func kill(pid: Int32, signal: Int32) async throws -> Int32 39 | 40 | // Process lifecycle 41 | func createProcess( 42 | id: String, 43 | containerID: String?, 44 | stdinPort: UInt32?, 45 | stdoutPort: UInt32?, 46 | stderrPort: UInt32?, 47 | configuration: ContainerizationOCI.Spec, 48 | options: Data? 49 | ) async throws 50 | func startProcess(id: String, containerID: String?) async throws -> Int32 51 | func signalProcess(id: String, containerID: String?, signal: Int32) async throws 52 | func resizeProcess(id: String, containerID: String?, columns: UInt32, rows: UInt32) async throws 53 | func waitProcess(id: String, containerID: String?, timeoutInSeconds: Int64?) async throws -> Int32 54 | func deleteProcess(id: String, containerID: String?) async throws 55 | func closeProcessStdin(id: String, containerID: String?) async throws 56 | 57 | // Networking 58 | func up(name: String, mtu: UInt32?) async throws 59 | func down(name: String) async throws 60 | func addressAdd(name: String, address: String) async throws 61 | func routeAddDefault(name: String, gateway: String) async throws 62 | func configureDNS(config: DNS, location: String) async throws 63 | } 64 | 65 | extension VirtualMachineAgent { 66 | public func closeProcessStdin(id: String, containerID: String?) async throws { 67 | throw ContainerizationError(.unsupported, message: "closeProcessStdin") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineInstance.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 runtime state of the virtual machine instance. 20 | public enum VirtualMachineInstanceState: Sendable { 21 | case starting 22 | case running 23 | case stopped 24 | case stopping 25 | case unknown 26 | } 27 | 28 | /// A manager that can spawn and manage a virtual machine. 29 | public protocol VirtualMachineInstance: Sendable { 30 | associatedtype Agent: VirtualMachineAgent 31 | 32 | // The state of the virtual machine. 33 | var state: VirtualMachineInstanceState { get } 34 | 35 | var mounts: [AttachedFilesystem] { get } 36 | /// Dial the Agent. It's up the VirtualMachineInstance to determine 37 | /// what port the agent is listening on. 38 | func dialAgent() async throws -> Agent 39 | /// Dial a vsock port in the guest. 40 | func dial(_ port: UInt32) async throws -> FileHandle 41 | /// Listen on a host vsock port. 42 | func listen(_ port: UInt32) throws -> VsockConnectionStream 43 | /// Stop listening on a vsock port. 44 | func stopListen(_ port: UInt32) throws 45 | /// Start the virtual machine. 46 | func start() async throws 47 | /// Stop the virtual machine. 48 | func stop() async throws 49 | } 50 | -------------------------------------------------------------------------------- /Sources/Containerization/VirtualMachineManager.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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(container: Container) throws -> any VirtualMachineInstance 20 | } 21 | -------------------------------------------------------------------------------- /Sources/Containerization/VsockConnectionStream.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 VsockConnectionStream: NSObject, Sendable { 25 | /// A stream of connections dialed from the remote. 26 | public let connections: AsyncStream 27 | /// The port the connections are for. 28 | public let port: UInt32 29 | 30 | private let cont: AsyncStream.Continuation 31 | 32 | public init(port: UInt32) { 33 | self.port = port 34 | let (stream, continuation) = AsyncStream.makeStream(of: FileHandle.self) 35 | self.connections = stream 36 | self.cont = continuation 37 | } 38 | 39 | public func finish() { 40 | self.cont.finish() 41 | } 42 | } 43 | 44 | #if os(macOS) 45 | 46 | extension VsockConnectionStream: VZVirtioSocketListenerDelegate { 47 | public func listener( 48 | _: VZVirtioSocketListener, shouldAcceptNewConnection conn: VZVirtioSocketConnection, 49 | from _: VZVirtioSocketDevice 50 | ) -> Bool { 51 | let fd = dup(conn.fileDescriptor) 52 | conn.close() 53 | 54 | cont.yield(FileHandle(fileDescriptor: fd, closeOnDealloc: false)) 55 | return true 56 | } 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/CArchive/COPYING: -------------------------------------------------------------------------------- 1 | The libarchive distribution as a whole is Copyright by Tim Kientzle 2 | and is subject to the copyright notice reproduced at the bottom of 3 | this file. 4 | 5 | Each individual file in this distribution should have a clear 6 | copyright/licensing statement at the beginning of the file. If any do 7 | not, please let me know and I will rectify it. The following is 8 | intended to summarize the copyright status of the individual files; 9 | the actual statements in the files are controlling. 10 | 11 | * Except as listed below, all C sources (including .c and .h files) 12 | and documentation files are subject to the copyright notice reproduced 13 | at the bottom of this file. 14 | 15 | * The following source files are also subject in whole or in part to 16 | a 3-clause UC Regents copyright; please read the individual source 17 | files for details: 18 | libarchive/archive_read_support_filter_compress.c 19 | libarchive/archive_write_add_filter_compress.c 20 | libarchive/mtree.5 21 | 22 | * The following source files are in the public domain: 23 | libarchive/archive_getdate.c 24 | 25 | * The following source files are triple-licensed with the ability to choose 26 | from CC0 1.0 Universal, OpenSSL or Apache 2.0 licenses: 27 | libarchive/archive_blake2.h 28 | libarchive/archive_blake2_impl.h 29 | libarchive/archive_blake2s_ref.c 30 | libarchive/archive_blake2sp_ref.c 31 | 32 | * The build files---including Makefiles, configure scripts, 33 | and auxiliary scripts used as part of the compile process---have 34 | widely varying licensing terms. Please check individual files before 35 | distributing them to see if those restrictions apply to you. 36 | 37 | I intend for all new source code to use the license below and hope over 38 | time to replace code with other licenses with new implementations that 39 | do use the license below. The varying licensing of the build scripts 40 | seems to be an unavoidable mess. 41 | 42 | 43 | Copyright (c) 2003-2018 44 | All rights reserved. 45 | 46 | Redistribution and use in source and binary forms, with or without 47 | modification, are permitted provided that the following conditions 48 | are met: 49 | 1. Redistributions of source code must retain the above copyright 50 | notice, this list of conditions and the following disclaimer 51 | in this position and unchanged. 52 | 2. Redistributions in binary form must reproduce the above copyright 53 | notice, this list of conditions and the following disclaimer in the 54 | documentation and/or other materials provided with the distribution. 55 | 56 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 57 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 58 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 59 | IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 60 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 61 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 62 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 63 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 64 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 65 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 66 | -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/CArchive/archive_swift_bridge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/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/ContainerizationArchive/FileArchiveWriterDelegate.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationArchive/TempDir.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/EXT4+Ptr.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationEXT4/FileTimestamps.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Sources/ContainerizationEXT4/UnsafeLittleEndianBytes.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationExtras/AddressAllocator.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationExtras/AsyncLock.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/FileManager+Temporary.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationExtras/NetworkAddress.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | /// Errors related to IP and CIDR addresses. 18 | public enum NetworkAddressError: Swift.Error, Equatable, CustomStringConvertible { 19 | case invalidStringAddress(address: String) 20 | case invalidNetworkByteAddress(address: [UInt8]) 21 | case invalidCIDR(cidr: String) 22 | case invalidAddressForSubnet(address: String, cidr: String) 23 | case invalidAddressRange(lower: String, upper: String) 24 | 25 | public var description: String { 26 | switch self { 27 | case .invalidStringAddress(let address): 28 | return "invalid IP address string \(address)" 29 | case .invalidNetworkByteAddress(let address): 30 | return "invalid IP address bytes \(address)" 31 | case .invalidCIDR(let cidr): 32 | return "invalid CIDR block: \(cidr)" 33 | case .invalidAddressForSubnet(let address, let cidr): 34 | return "invalid address \(address) for subnet \(cidr)" 35 | case .invalidAddressRange(let lower, let upper): 36 | return "invalid range for addresses \(lower) and \(upper)" 37 | } 38 | } 39 | } 40 | 41 | public typealias PrefixLength = UInt8 42 | 43 | extension PrefixLength { 44 | /// Compute a bit mask that passes the suffix bits, given the network prefix mask length. 45 | public var suffixMask32: UInt32 { 46 | if self <= 0 { 47 | return 0xffff_ffff 48 | } 49 | return self >= 32 ? 0x0000_0000 : (1 << (32 - self)) - 1 50 | } 51 | 52 | /// Compute a bit mask that passes the prefix bits, given the network prefix mask length. 53 | public var prefixMask32: UInt32 { 54 | ~self.suffixMask32 55 | } 56 | 57 | /// Compute a bit mask that passes the suffix bits, given the network prefix mask length. 58 | public var suffixMask48: UInt64 { 59 | if self <= 0 { 60 | return 0x0000_ffff_ffff_ffff 61 | } 62 | return self >= 48 ? 0x0000_0000_0000_0000 : (1 << (48 - self)) - 1 63 | } 64 | 65 | /// Compute a bit mask that passes the prefix bits, given the network prefix mask length. 66 | public var prefixMask48: UInt64 { 67 | ~self.suffixMask48 & 0x0000_ffff_ffff_ffff 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/ProgressEvent.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationExtras/Timeout.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 -> T 27 | ) async throws -> T { 28 | try await withThrowingTaskGroup(of: T.self) { group in 29 | group.addTask { 30 | 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationExtras/UInt8+DataBinding.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 ArraySlice { 18 | package func hexEncodedString() -> String { 19 | self.map { String(format: "%02hhx", $0) }.joined() 20 | } 21 | } 22 | 23 | extension [UInt8] { 24 | package func hexEncodedString() -> String { 25 | self.map { String(format: "%02hhx", $0) }.joined() 26 | } 27 | 28 | package mutating func bind(as type: T.Type, offset: Int = 0, size: Int? = nil) -> UnsafeMutablePointer? { 29 | guard self.count >= (size ?? MemoryLayout.size) + offset else { 30 | return nil 31 | } 32 | 33 | return self.withUnsafeMutableBytes { $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self) } 34 | } 35 | 36 | package mutating func copyIn(as type: T.Type, value: T, offset: Int = 0, size: Int? = nil) -> Int? { 37 | let size = size ?? MemoryLayout.size 38 | guard self.count >= size - offset else { 39 | return nil 40 | } 41 | 42 | return self.withUnsafeMutableBytes { 43 | $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee = value 44 | return offset + MemoryLayout.size 45 | } 46 | } 47 | 48 | package mutating func copyOut(as type: T.Type, offset: Int = 0, size: Int? = nil) -> (Int, T)? { 49 | guard self.count >= (size ?? MemoryLayout.size) - offset else { 50 | return nil 51 | } 52 | 53 | return self.withUnsafeMutableBytes { 54 | guard let value = $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee else { 55 | return nil 56 | } 57 | return (offset + MemoryLayout.size, value) 58 | } 59 | } 60 | 61 | package mutating func copyIn(buffer: [UInt8], offset: Int = 0) -> Int? { 62 | guard offset + buffer.count <= self.count else { 63 | return nil 64 | } 65 | 66 | self[offset.. Int? { 71 | guard offset + buffer.count <= self.count else { 72 | return nil 73 | } 74 | 75 | buffer[0.. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Client/RegistryClient+Error.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/AsyncTypes.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/Content/Content.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/ContentWriter.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | import NIOCore 21 | 22 | /// Provides a context to write data into a directory. 23 | public class ContentWriter { 24 | private let base: URL 25 | private let encoder = JSONEncoder() 26 | 27 | /// Create a new ContentWriter. 28 | /// - Parameters: 29 | /// - base: The URL to write content to. If this is not a directory a 30 | /// ContainerizationError will be thrown with a code of .internalError. 31 | public init(for base: URL) throws { 32 | self.encoder.outputFormatting = [JSONEncoder.OutputFormatting.sortedKeys] 33 | 34 | self.base = base 35 | var isDirectory = ObjCBool(true) 36 | let exists = FileManager.default.fileExists(atPath: base.path, isDirectory: &isDirectory) 37 | 38 | guard exists && isDirectory.boolValue else { 39 | throw ContainerizationError(.internalError, message: "Cannot create ContentWriter for path \(base.absolutePath()). Not a directory") 40 | } 41 | } 42 | 43 | /// Writes the data blob to the base URL provided in the constructor. 44 | /// - Parameters: 45 | /// - data: The data blob to write to a file under the base path. 46 | @discardableResult 47 | public func write(_ data: Data) throws -> (size: Int64, digest: SHA256.Digest) { 48 | let digest = SHA256.hash(data: data) 49 | let destination = base.appendingPathComponent(digest.encoded) 50 | try data.write(to: destination) 51 | return (Int64(data.count), digest) 52 | } 53 | 54 | /// Reads the data present in the passed in URL and writes it to the base path. 55 | /// - Parameters: 56 | /// - url: The URL to read the data from. 57 | @discardableResult 58 | public func create(from url: URL) throws -> (size: Int64, digest: SHA256.Digest) { 59 | let data = try Data(contentsOf: url) 60 | return try self.write(data) 61 | } 62 | 63 | /// Encodes the passed in type as a JSON blob and writes it to the base path. 64 | /// - Parameters: 65 | /// - content: The type to convert to JSON. 66 | @discardableResult 67 | public func create(from content: T) throws -> (size: Int64, digest: SHA256.Digest) { 68 | let data = try self.encoder.encode(content) 69 | return try self.write(data) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Content/LocalContent.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/Content/SHA256+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/Content/String+Extension.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/Content/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/Descriptor.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/FileManager+Size.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Index.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | public let mediaType: String 29 | 30 | /// manifests references platform specific manifests. 31 | public var manifests: [Descriptor] 32 | 33 | /// annotations contains arbitrary metadata for the image index. 34 | public var annotations: [String: String]? 35 | 36 | public init( 37 | schemaVersion: Int = 2, mediaType: String = MediaTypes.index, manifests: [Descriptor], 38 | annotations: [String: String]? = nil 39 | ) { 40 | self.schemaVersion = schemaVersion 41 | self.mediaType = mediaType 42 | self.manifests = manifests 43 | self.annotations = annotations 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/ContainerizationOCI/Manifest.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/State.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOCI/Version.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/BinaryInteger+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/POSIXError+Helpers.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOS/Path.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | /// `Path` provides utilities to look for binaries in the current PATH, 20 | /// or to return the current PATH. 21 | public struct Path { 22 | /// lookPath looks up an executable's path from $PATH 23 | public static func lookPath(_ name: String) -> URL? { 24 | lookup(name, path: getPath()) 25 | } 26 | 27 | // getEnv returns the default environment of the process 28 | // with the default $PATH added for the context of a macOS application bundle 29 | public static func getEnv() -> [String: String] { 30 | var env = ProcessInfo.processInfo.environment 31 | env["PATH"] = getPath() 32 | return env 33 | } 34 | 35 | private static func lookup(_ name: String, path: String) -> URL? { 36 | if name.contains("/") { 37 | if findExec(name) { 38 | return URL(fileURLWithPath: name) 39 | } 40 | return nil 41 | } 42 | 43 | for var lookdir in path.split(separator: ":") { 44 | if lookdir.isEmpty { 45 | lookdir = "." 46 | } 47 | let file = URL(fileURLWithPath: String(lookdir)).appendingPathComponent(name) 48 | if findExec(file.path) { 49 | return file 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | /// getPath returns $PATH for the current process 56 | private static func getPath() -> String { 57 | let env = ProcessInfo.processInfo.environment 58 | return env["PATH"] ?? "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" 59 | } 60 | 61 | // findPath returns a string containing the 'PATH' environment variable 62 | private static func findPath(_ env: [String]) -> String? { 63 | env.first(where: { path in 64 | let split = path.split(separator: "=") 65 | return split.count == 2 && split[0] == "PATH" 66 | }) 67 | } 68 | 69 | // findExec returns true if the provided path is an executable 70 | private static func findExec(_ path: String) -> Bool { 71 | let fm = FileManager.default 72 | return fm.isExecutableFile(atPath: path) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Pipe+Close.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOS/README.md: -------------------------------------------------------------------------------- 1 | ## OS 2 | 3 | This target contains general useful OS related definitions or wrappers. -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Reaper.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOS/Socket/SocketType.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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/ContainerizationOS/Syscall.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/Sysctl.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/ContainerizationOS/URL+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/Integration/VMTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 ContainerizationError 20 | import ContainerizationOCI 21 | import Foundation 22 | import Logging 23 | 24 | extension IntegrationSuite { 25 | func testMounts() async throws { 26 | let id = "test-cat-mount" 27 | 28 | let bs = try await bootstrap() 29 | let container = LinuxContainer( 30 | id, 31 | rootfs: bs.rootfs, 32 | vmm: bs.vmm 33 | ) 34 | let directory = try createMountDirectory() 35 | container.mounts.append(.share(source: directory.path, destination: "/mnt")) 36 | container.arguments = ["/bin/cat", "/mnt/hi.txt"] 37 | 38 | let buffer = BufferWriter() 39 | container.stdout = buffer 40 | 41 | try await container.create() 42 | try await container.start() 43 | 44 | let status = try await container.wait() 45 | try await container.stop() 46 | 47 | guard status == 0 else { 48 | throw IntegrationError.assert(msg: "process status \(status) != 0") 49 | } 50 | 51 | let value = String(data: buffer.data, encoding: .utf8) 52 | guard value == "hello" else { 53 | throw IntegrationError.assert( 54 | msg: "process should have returned from file 'hello' != '\(String(data: buffer.data, encoding: .utf8)!)") 55 | 56 | } 57 | } 58 | 59 | func testNestedVirtualizationEnabled() async throws { 60 | let id = "test-nested-virt" 61 | 62 | let bs = try await bootstrap() 63 | let container = LinuxContainer( 64 | id, 65 | rootfs: bs.rootfs, 66 | vmm: bs.vmm 67 | ) 68 | container.arguments = ["/bin/true"] 69 | 70 | container.virtualization = true 71 | 72 | do { 73 | try await container.create() 74 | try await container.start() 75 | } catch { 76 | if let err = error as? ContainerizationError { 77 | if err.code == .unsupported { 78 | throw SkipTest(reason: err.message) 79 | } 80 | } 81 | } 82 | 83 | let status = try await container.wait() 84 | try await container.stop() 85 | 86 | guard status == 0 else { 87 | throw IntegrationError.assert(msg: "process status \(status) != 0") 88 | } 89 | } 90 | 91 | private func createMountDirectory() throws -> URL { 92 | let dir = FileManager.default.uniqueTemporaryDirectory(create: true) 93 | try "hello".write(to: dir.appendingPathComponent("hi.txt"), atomically: true, encoding: .utf8) 94 | return dir 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/SendableProperty/SendableProperty.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | // `Synchronization` will be automatically imported with `SendableProperty`. 18 | @_exported import Synchronization 19 | 20 | // A declaration of the `@SendableProperty` macro. 21 | @attached(peer, names: arbitrary) 22 | @attached(accessor) 23 | public macro SendableProperty() = #externalMacro(module: "SendablePropertyMacros", type: "SendablePropertyMacro") 24 | 25 | /// A synchronization primitive that protects shared mutable state via mutual exclusion. 26 | public final class Synchronized: Sendable { 27 | private let lock: Mutex 28 | 29 | private struct State: @unchecked Sendable { 30 | var value: T 31 | } 32 | 33 | /// Creates a new instance. 34 | /// - Parameter value: The initial value. 35 | public init(_ value: T) { 36 | self.lock = Mutex(State(value: value)) 37 | } 38 | 39 | /// Calls the given closure after acquiring the lock and returns its value. 40 | /// - Parameter body: The body of code to execute while the lock is held. 41 | public func withLock(_ body: (inout T) throws -> R) rethrows -> R { 42 | try lock.withLock { state in 43 | try body(&state.value) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SendablePropertyMacros/SendablePropertyError.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | /// Errors that can be thrown by `@SendableProperty`. 18 | enum SendablePropertyError: CustomStringConvertible, Error { 19 | case unexpectedError 20 | case onlyApplicableToVar 21 | 22 | var description: String { 23 | switch self { 24 | case .unexpectedError: return "@SendableProperty encountered an unexpected error" 25 | case .onlyApplicableToVar: return "@SendableProperty can only be applied to a variable" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/SendablePropertyMacros/SendablePropertyPlugin.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 SwiftCompilerPlugin 18 | import SwiftSyntaxMacros 19 | 20 | /// A plugin that registers the `SendablePropertyMacro`. 21 | @main 22 | struct SendablePropertyPlugin: CompilerPlugin { 23 | let providingMacros: [Macro.Type] = [ 24 | SendablePropertyMacro.self 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /Sources/cctl/KernelCommand.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 Foundation 20 | 21 | extension Application { 22 | struct KernelCommand: AsyncParsableCommand { 23 | static let configuration = CommandConfiguration( 24 | commandName: "kernel", 25 | abstract: "Manage kernel images", 26 | subcommands: [ 27 | Create.self 28 | ] 29 | ) 30 | 31 | struct Create: AsyncParsableCommand { 32 | @Option(name: .shortAndLong, help: "Name for the kernel image") 33 | var name: String 34 | 35 | @Option(name: .long, help: "Labels to add to the built image of the form =, [=,...]") 36 | var labels: [String] = [] 37 | 38 | @Argument var kernels: [String] 39 | 40 | func run() async throws { 41 | let imageStore = Application.imageStore 42 | let contentStore = Application.contentStore 43 | let labels = Application.parseKeyValuePairs(from: labels) 44 | let binaries = try parseBinaries() 45 | _ = try await KernelImage.create( 46 | reference: name, 47 | binaries: binaries, 48 | labels: labels, 49 | imageStore: imageStore, 50 | contentStore: contentStore 51 | ) 52 | } 53 | 54 | func parseBinaries() throws -> [Kernel] { 55 | var binaries = [Kernel]() 56 | for rawBinary in kernels { 57 | let parts = rawBinary.split(separator: ":") 58 | guard parts.count == 2 else { 59 | throw "Invalid binary format: \(rawBinary)" 60 | } 61 | let platform: SystemPlatform 62 | switch parts[1] { 63 | case "arm64": 64 | platform = .linuxArm 65 | case "amd64": 66 | platform = .linuxAmd 67 | default: 68 | fatalError("unsupported platform \(parts[1])") 69 | } 70 | binaries.append( 71 | .init( 72 | path: URL(fileURLWithPath: String(parts[0])), 73 | platform: platform 74 | ) 75 | ) 76 | } 77 | return binaries 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/cctl/cctl+Utils.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Sources/cctl/cctl.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/197e9b63a9b772233c78634099dd3c7267fa0e4d/Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 -------------------------------------------------------------------------------- /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"} -------------------------------------------------------------------------------- /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"}} -------------------------------------------------------------------------------- /Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/197e9b63a9b772233c78634099dd3c7267fa0e4d/Tests/ContainerizationEXT4Tests/Resources/content/blobs/sha256/c6b39de5b33961661dc939b997cc1d30cda01e38005a6c6625fd9c7e748bab44 -------------------------------------------------------------------------------- /Tests/ContainerizationNetlinkTests/MockNetlinkSocket.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/ContainerizationOCITests/AuthChallengeTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/ContainerizationOCITests/OCIImageTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/ContainerizationOCITests/OCIPlatformTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 OCIPlatformTests { 24 | @Test func identicalPlatforms() { 25 | let amd64lhs = Platform(arch: "amd64", os: "linux") 26 | let amd64rhs = Platform(arch: "amd64", os: "linux") 27 | #expect(amd64lhs == amd64rhs, "amd64 platforms should be equal") 28 | 29 | let arm64lhs = Platform(arch: "arm64", os: "linux") 30 | let arm64rhs = Platform(arch: "arm64", os: "linux") 31 | #expect(arm64lhs == arm64rhs, "arm64 platforms should be equal") 32 | } 33 | 34 | @Test func differentOS() { 35 | let lhs = Platform(arch: "arm64", os: "linux") 36 | let rhs = Platform(arch: "arm64", os: "darwin") 37 | #expect(lhs != rhs, "Different OS should not be equal") 38 | } 39 | 40 | @Test func differentArch() { 41 | let lhs = Platform(arch: "amd64", os: "linux") 42 | let rhs = Platform(arch: "arm64", os: "linux") 43 | #expect(lhs != rhs, "Different arch should not be equal") 44 | } 45 | 46 | @Test func arm64_sameVariant() { 47 | let lhs = Platform(arch: "arm64", os: "linux", variant: "v8") 48 | let rhs = Platform(arch: "arm64", os: "linux", variant: "v8") 49 | #expect(lhs == rhs, "Both OS arm64, same arch, same variant => equal") 50 | } 51 | 52 | @Test func arm64_nilAndV8() { 53 | let lhs = Platform(arch: "arm64", os: "linux", variant: nil) 54 | let rhs = Platform(arch: "arm64", os: "linux", variant: "v8") 55 | #expect(lhs == rhs, "One variant nil and other v8 => equal under special arm64 rule") 56 | } 57 | 58 | @Test func arm64_nilAndV7() { 59 | let lhs = Platform(arch: "arm64", os: "linux", variant: nil) 60 | let rhs = Platform(arch: "arm64", os: "linux", variant: "v7") 61 | #expect(lhs != rhs, "nil vs v7 is not covered by the special rule => not equal") 62 | } 63 | 64 | @Test func arm64_bothNil() { 65 | let lhs = Platform(arch: "arm64", os: "linux", variant: nil) 66 | let rhs = Platform(arch: "arm64", os: "linux", variant: nil) 67 | #expect(lhs == rhs, "Both nil variants => variantEqual is true => overall equal") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Tests/ContainerizationOSTests/KeychainQueryTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests/ContainsAuth.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/ContainerizationTests/ImageTests/Resources/scratch.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apple/containerization/197e9b63a9b772233c78634099dd3c7267fa0e4d/Tests/ContainerizationTests/ImageTests/Resources/scratch.tar -------------------------------------------------------------------------------- /Tests/ContainerizationTests/KernelTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /Tests/SendablePropertyTests/SendablePropertyTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 SendableProperty 19 | import XCTest 20 | 21 | final class SendablePropertyTests: XCTestCase { 22 | func testMacroWithTypeAnnotation() throws { 23 | final class TestMacro: Sendable { 24 | @SendableProperty 25 | var value: Int 26 | } 27 | 28 | let testMacro = TestMacro() 29 | testMacro.value = 42 30 | XCTAssertTrue(testMacro.value == 42) 31 | } 32 | 33 | func testMacroWithInitialValue() throws { 34 | final class TestMacro: Sendable { 35 | @SendableProperty 36 | var value = 0 37 | } 38 | 39 | let testMacro = TestMacro() 40 | XCTAssertTrue(type(of: testMacro.value) == Int.self) 41 | XCTAssertTrue(testMacro.value == 0) 42 | testMacro.value = 42 43 | XCTAssertTrue(testMacro.value == 42) 44 | } 45 | 46 | func testMacroWithTypeAnnotationAndInitialValue() throws { 47 | final class TestMacro: Sendable { 48 | @SendableProperty 49 | var value: Int = 0 50 | } 51 | 52 | let testMacro = TestMacro() 53 | testMacro.value = 42 54 | XCTAssertTrue(testMacro.value == 42) 55 | } 56 | 57 | func testMacroInConcurrentThreads() throws { 58 | final class TestMacro: Sendable { 59 | @SendableProperty 60 | var value = "" 61 | } 62 | 63 | let testMacro = TestMacro() 64 | let loremIpsum = 65 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 66 | 67 | let numberOfIterations = 100_000 68 | let queue = DispatchQueue(label: "com.apple.sendable-property-tests", attributes: .concurrent) 69 | let dispatchGroup = DispatchGroup() 70 | for i in 0../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 | -------------------------------------------------------------------------------- /scripts/install-hawkeye.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /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"] }}. All rights reserved. 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 | -------------------------------------------------------------------------------- /scripts/make-docs.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | # Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /signing/vz.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.virtualization 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /vminitd/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | BUILD_CONFIGURATION := debug 16 | SWIFT_CONFIGURATION := --swift-sdk aarch64-swift-linux-musl 17 | 18 | SWIFT_VERSION = 6.2-snapshot-2025-06-25 19 | SWIFT_SDK_URL = https://download.swift.org/swift-6.2-branch/static-sdk/swift-6.2-DEVELOPMENT-SNAPSHOT-2025-06-25-a/swift-6.2-DEVELOPMENT-SNAPSHOT-2025-06-25-a_static-linux-0.0.1.artifactbundle.tar.gz 20 | SWIFT_SDK_PATH = /tmp/$(notdir $(SWIFT_SDK_URL)) 21 | 22 | SWIFTLY_URL := https://download.swift.org/swiftly/darwin/swiftly.pkg 23 | SWIFTLY_FILENAME = $(notdir $(SWIFTLY_URL)) 24 | SWIFTLY_BIN_DIR ?= ~/.swiftly/bin 25 | VMINITD_BIN_PATH := $(shell swift build -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --show-bin-path) 26 | 27 | MACOS_VERSION := $(shell sw_vers -productVersion) 28 | MACOS_MAJOR := $(shell echo $(MACOS_VERSION) | cut -d. -f1) 29 | MACOS_RELEASE_TYPE := $(shell sw_vers | grep ReleaseType) 30 | 31 | .DEFAULT_GOAL := all 32 | 33 | .PHONY: all 34 | all: 35 | @echo Building vminitd and vmexec... 36 | @mkdir -p ./bin/ 37 | @rm -f ./bin/vminitd 38 | @rm -f ./bin/vmexec 39 | @swift build -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) 40 | @install "$(VMINITD_BIN_PATH)/vminitd" ./bin/ 41 | @install "$(VMINITD_BIN_PATH)/vmexec" ./bin/ 42 | 43 | .PHONY: cross-prep 44 | cross-prep: linux-sdk macos-sdk 45 | 46 | .PHONY: swiftly 47 | swiftly: 48 | @if ! command -v ${SWIFTLY_BIN_DIR}/swiftly > /dev/null 2>&1; then \ 49 | echo "Installing Swiftly..."; \ 50 | curl -o /var/tmp/$(SWIFTLY_FILENAME) $(SWIFTLY_URL) && \ 51 | installer -pkg /var/tmp/$(SWIFTLY_FILENAME) -target CurrentUserHomeDirectory && \ 52 | ${SWIFTLY_BIN_DIR}/swiftly init --quiet-shell-followup --skip-install && \ 53 | . ~/.swiftly/env.sh && \ 54 | hash -r && \ 55 | rm /var/tmp/$(SWIFTLY_FILENAME); \ 56 | fi 57 | 58 | .PHONY: swift 59 | swift: swiftly 60 | @echo Installing Swift $(SWIFT_VERSION)... 61 | @${SWIFTLY_BIN_DIR}/swiftly install $(SWIFT_VERSION) 62 | 63 | .PHONY: linux-sdk 64 | linux-sdk: swift 65 | @echo Installing Static Linux SDK... 66 | @curl -L -o $(SWIFT_SDK_PATH) $(SWIFT_SDK_URL) 67 | -@swift sdk install $(SWIFT_SDK_PATH) 68 | @rm $(SWIFT_SDK_PATH) 69 | 70 | .PHONY: macos-sdk 71 | macos-sdk: 72 | # Consider switching back to `xcode-cltools`, when possible. 73 | @if [ $(MACOS_MAJOR) -gt 15 ] && [ "$(MACOS_RELEASE_TYPE)" = "" ]; then \ 74 | "$(MAKE)" xcode; \ 75 | else \ 76 | "$(MAKE)" xcode; \ 77 | fi 78 | 79 | .PHONY: xcode-cltools 80 | xcode-cltools: 81 | @echo Activating Xcode Command Line Tools... 82 | @sudo xcode-select --switch /Library/Developer/CommandLineTools 83 | 84 | .PHONY: xcode 85 | xcode: 86 | @echo "Please install the latest version of Xcode 26 and set the path for the active developer directory using \`sudo xcode-select -s \`". 87 | 88 | .PHONY: clean 89 | clean: 90 | @echo Cleaning the vminitd build files... 91 | @rm -f ./bin/vminitd 92 | @rm -f ./bin/vmexec 93 | @swift package clean $(SWIFT_CONFIGURATION) 94 | -------------------------------------------------------------------------------- /vminitd/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 6.2 2 | //===----------------------------------------------------------------------===// 3 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | // The swift-tools-version declares the minimum version of Swift required to build this package. 19 | 20 | import PackageDescription 21 | 22 | let package = Package( 23 | name: "swift-vminitd", 24 | platforms: [.macOS("15")], 25 | products: [ 26 | .executable(name: "vminitd", targets: ["vminitd"]), 27 | .executable(name: "vmexec", targets: ["vmexec"]), 28 | ], 29 | dependencies: [ 30 | .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), 31 | .package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"), 32 | .package(url: "https://github.com/apple/swift-nio", from: "2.80.0"), 33 | .package(name: "containerization", path: "../"), 34 | ], 35 | targets: [ 36 | .target( 37 | name: "LCShim" 38 | ), 39 | .executableTarget( 40 | name: "vminitd", 41 | dependencies: [ 42 | .product(name: "Logging", package: "swift-log"), 43 | .product(name: "_NIOFileSystem", package: "swift-nio"), 44 | .product(name: "Containerization", package: "containerization"), 45 | .product(name: "ContainerizationNetlink", package: "containerization"), 46 | .product(name: "ContainerizationIO", package: "containerization"), 47 | .product(name: "ContainerizationOS", package: "containerization"), 48 | "LCShim", 49 | ] 50 | ), 51 | .executableTarget( 52 | name: "vmexec", 53 | dependencies: [ 54 | .product(name: "Logging", package: "swift-log"), 55 | .product(name: "ArgumentParser", package: "swift-argument-parser"), 56 | .product(name: "Containerization", package: "containerization"), 57 | .product(name: "ContainerizationOS", package: "containerization"), 58 | "LCShim", 59 | ] 60 | ), 61 | ] 62 | ) 63 | -------------------------------------------------------------------------------- /vminitd/Sources/LCShim/include/syscall.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /vminitd/Sources/LCShim/syscall.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /vminitd/Sources/vmexec/Mount.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | 41 | guard remove(ptmx) == 0 else { 42 | throw App.Errno(stage: "remove(ptmx)") 43 | } 44 | guard symlink("pts/ptmx", ptmx) == 0 else { 45 | throw App.Errno(stage: "symlink(pts/ptmx)") 46 | } 47 | } 48 | 49 | private func mkdirAll(_ name: String, _ perm: Int16) throws { 50 | try FileManager.default.createDirectory( 51 | atPath: name, 52 | withIntermediateDirectories: true, 53 | attributes: [.posixPermissions: perm] 54 | ) 55 | } 56 | } 57 | 58 | extension ContainerizationOCI.Mount { 59 | func toOSMount() -> ContainerizationOS.Mount { 60 | ContainerizationOS.Mount( 61 | type: self.type, 62 | source: self.source, 63 | target: self.destination, 64 | options: self.options 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/HostStdio.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/IOCloser+Extensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | -------------------------------------------------------------------------------- /vminitd/Sources/vminitd/IOCloser.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // Copyright © 2025 Apple Inc. and the Containerization project authors. All rights reserved. 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 | --------------------------------------------------------------------------------