[
  {
    "path": ".github/ISSUE_TEMPLATE/01-bug.yml",
    "content": "name: Bug report\ndescription: File a bug report.\ntitle: \"[Bug]: \"\ntype: \"Bug\"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: checkboxes\n    id: prereqs\n    attributes:\n      label: I have done the following\n      description: Select that you have completed the following prerequisites. \n      options:\n        - label: I have searched the existing issues\n          required: true\n        - label: If possible, I've reproduced the issue using the 'main' branch of this project\n          required: false\n  - type: textarea\n    id: reproduce\n    attributes:\n      label: Steps to reproduce\n      description: Explain how to reproduce the incorrect behavior. \n    validations:\n      required: true\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: Current behavior\n      description: A concise description of what you're experiencing.\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected behavior\n      description: A concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: Environment \n      description: |\n        Examples: \n          - **OS**: macOS 26.0 (25A354)\n          - **Xcode**: Version 26.0 (17A324)\n          - **Container**: Container CLI version 0.1.0\n      value: |\n        - OS: \n        - Xcode: \n        - Container: \n      render: markdown\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n      value: |\n        N/A\n      render: shell\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/apple/.github/blob/main/CODE_OF_CONDUCT.md).\n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02-feature.yml",
    "content": "name: Feature or enhancement request\ndescription: File a request for a feature or enhancement\ntitle: \"[Request]: \"\ntype: \"Feature\"\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for contributing to the container project!\n  - type: textarea\n    id: request\n    attributes:\n      label: Feature or enhancement request details\n      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.\n    validations:\n      required: true\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/apple/.github/blob/main/CODE_OF_CONDUCT.md). \n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Container community support\n    url: https://github.com/apple/container/discussions\n    about: Please ask and answer questions here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    groups:\n      github-actions:\n        patterns:\n          - \"*\"\n    commit-message:\n      prefix: \"ci\"\n"
  },
  {
    "path": ".github/labeler.yml",
    "content": "cli:\n  - changed-files:\n    - any-glob-to-any-file:\n      - 'Sources/CLI/**'\n      - 'Sources/ContainerCommands/**'\n\ndocumentation:\n  - changed-files:\n    - any-glob-to-any-file:\n      - '**/*.md'\n      - 'docs/**'\n\nci:\n  - changed-files:\n    - any-glob-to-any-file:\n      - '.github/**'"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Type of Change\n- [ ] Bug fix\n- [ ] New feature  \n- [ ] Breaking change\n- [ ] Documentation update\n\n## Motivation and Context\n[Why is this change needed?]\n\n## Testing\n- [ ] Tested locally\n- [ ] Added/updated tests\n- [ ] Added/updated docs\n"
  },
  {
    "path": ".github/workflows/common.yml",
    "content": "name: container project - common jobs\n\npermissions:\n  contents: read\n\non:\n  workflow_call:\n    inputs:\n      release:\n        type: boolean\n        description: \"Publish this build for release\"\n        default: false\n\njobs:\n  buildAndTest:\n    name: Build and test the project\n    if: github.repository == 'apple/container'\n    timeout-minutes: 60\n    runs-on: [self-hosted, macos, tahoe, ARM64]\n    permissions:\n      contents: read\n      packages: read\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n        with:\n          fetch-depth: 0\n\n      - name: Check formatting\n        run: |\n          ./scripts/install-hawkeye.sh\n          make fmt\n\n          if ! git diff --quiet -- . ; then\n            echo \"❌ The following files require formatting or license header updates:\"\n            git diff --name-only -- .\n            false\n          fi\n\n      - name: Check protobuf\n        run: |\n          make protos\n          if ! git diff --quiet -- . ; then\n            echo \"❌ The following files require formatting or license header updates:\"\n            git diff --name-only -- .\n            false\n          fi\n        env:\n          DEVELOPER_DIR: \"/Applications/Xcode-latest.app/Contents/Developer\"\n\n      - name: Set build configuration\n        env:\n          RELEASE: ${{ inputs.release }}\n        run: |\n          echo \"BUILD_CONFIGURATION=debug\" >> $GITHUB_ENV\n          if [[ \"${RELEASE}\" == \"true\" ]]; then\n            echo \"BUILD_CONFIGURATION=release\" >> $GITHUB_ENV\n          fi\n\n      - name: Make the container project and docs\n        run: |\n          make container dsym docs\n          tar cfz _site.tgz _site\n        env:\n          DEVELOPER_DIR: \"/Applications/Xcode-latest.app/Contents/Developer\"\n\n      - name: Create package\n        run: |\n          mkdir -p outputs\n          mv \"bin/${BUILD_CONFIGURATION}/container-installer-unsigned.pkg\" outputs\n          mv \"bin/${BUILD_CONFIGURATION}/bundle/container-dSYM.zip\" outputs\n\n      - name: Test the container project\n        run: |\n          APP_ROOT=$(mktemp -d -p \"${RUNNER_TEMP}\")\n          LOG_ROOT=\"${APP_ROOT}/logs\"\n          echo \"created data directory: ${APP_ROOT}\"\n          echo \"hostname: $(hostname)\"\n          export NO_PROXY=\"${NO_PROXY},192.168.0.0/16,fe80::/10\"\n          echo NO_PROXY=${NO_PROXY}\n          export no_proxy=\"${no_proxy},192.168.0.0/16,fe80::/10\"\n          echo no_proxy=${no_proxy}\n          echo \"APP_ROOT=${APP_ROOT}\" >> $GITHUB_ENV\n          echo \"LOG_ROOT=${LOG_ROOT}\" >> $GITHUB_ENV\n          make APP_ROOT=\"${APP_ROOT}\" LOG_ROOT=\"${LOG_ROOT}\" test install-kernel integration\n        env:\n          DEVELOPER_DIR: \"/Applications/Xcode-latest.app/Contents/Developer\"\n\n      - name: Archive test logs\n        if: always()\n        run: |\n          if [ -d \"${APP_ROOT}/containers\" ] && [ -n \"${LOG_ROOT}\" ]; then\n            rsync -a --include='*/' --include='*.log' --exclude='*' \\\n              \"${APP_ROOT}/containers/\" \\\n              \"${LOG_ROOT}/containers/\"\n          fi\n          if [ -n \"${LOG_ROOT}\" ] && [ -d \"${LOG_ROOT}\" ] ; then\n            echo \"Collecting logs from ${LOG_ROOT}...\"\n            tar czf container-logs.tar.gz -C \"$(dirname \"${LOG_ROOT}\")\" \"$(basename \"${LOG_ROOT}\")\"\n            echo \"Log archive created: container-logs.tar.gz\"\n          fi\n          if [ -n \"${APP_ROOT}\" ] ; then\n            rm -rf \"${APP_ROOT}\"\n            echo \"Removed data directory ${APP_ROOT}\"\n          fi\n\n      - name: Upload logs if present\n        if: always()\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: container-test-logs\n          path: container-logs.tar.gz\n          retention-days: 14\n          if-no-files-found: ignore\n\n      - name: Save documentation artifact\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: api-docs\n          path: \"./_site.tgz\"\n          retention-days: 14\n\n      - name: Save package artifacts\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: container-package\n          path: ${{ github.workspace }}/outputs\n\n  uploadPages:\n    # Separate upload step required because upload-pages-artifact needs\n    # gtar which is not on the macOS runner.\n    name: Upload artifact for GitHub Pages\n    needs: buildAndTest\n    timeout-minutes: 5\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n\n      - name: Download a single artifact\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          name: api-docs\n\n      - name: Add API docs to documentation\n        run: |\n          tar xfz _site.tgz\n\n      - name: Upload Artifact\n        uses: actions/upload-pages-artifact@v4\n        with:\n          path: \"./_site\"\n"
  },
  {
    "path": ".github/workflows/docs-release.yml",
    "content": "# Manual workflow for releasing docs ad-hoc. Workflow can only be run for main or release branches.\n# Workflow does NOT publish a release of container.\nname: Deploy application website\n\npermissions:\n  contents: read\n\non:\n  workflow_dispatch:\n\njobs:\n  checkBranch:\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags') || startsWith(github.ref, 'refs/heads/release')\n    steps:\n      - name: Branch validation\n        env:\n          REF_NAME: ${{ github.ref_name }}\n        run: echo \"Branch ${REF_NAME} is allowed\"\n\n  buildSite:\n    name: Build application website\n    needs: checkBranch\n    uses: ./.github/workflows/common.yml\n    secrets: inherit\n    permissions:\n      contents: read\n      packages: write\n      pages: write\n\n  deployDocs:\n    runs-on: ubuntu-latest\n    needs: [checkBranch, buildSite]\n    permissions:\n      contents: read\n      pages: write\n      id-token: write\n\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".github/workflows/merge-build.yml",
    "content": "name: container project - merge build\n\npermissions:\n  contents: read\n\non:\n  push:\n    branches:\n      - main\n      - release/*\n\njobs:\n  build:\n    name: Invoke build\n    uses: ./.github/workflows/common.yml\n    with:\n      release: true\n    secrets: inherit\n    permissions:\n      contents: read\n      packages: read\n      pages: write\n"
  },
  {
    "path": ".github/workflows/pr-build.yml",
    "content": "name: container project - PR build\n\npermissions:\n  contents: read\n\non:\n  pull_request:\n    types: [opened, reopened, synchronize]\n\njobs:\n  verify-signatures:\n    name: Verify commit signatures\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check all commits are signed\n        env:\n          GH_TOKEN: ${{ github.token }}\n          REPO: ${{ github.repository }}\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          commits=$(gh api \"repos/${REPO}/pulls/${PR_NUMBER}/commits\" --paginate)\n          unsigned_commits=\"\"\n\n          while IFS='|' read -r sha author verified; do\n            if [ \"$verified\" != \"true\" ]; then\n              unsigned_commits=\"$unsigned_commits  - $sha by $author\\n\"\n            fi\n          done < <(echo \"$commits\" | jq -r '.[] | \"\\(.sha)|\\(.commit.author.name)|\\(.commit.verification.verified)\"')\n\n          if [ -n \"$unsigned_commits\" ]; then\n            echo \"::error::The following commits are not signed:\"\n            echo -e \"$unsigned_commits\"\n            echo \"\"\n            echo \"Please sign your commits. See:\"\n            echo  \"  - https://github.com/apple/containerization/blob/main/CONTRIBUTING.md#pull-requests\"\n            echo  \"  - https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits\"\n            exit 1\n          fi\n\n          echo \"All commits are signed!\"\n\n  build:\n    name: Invoke build\n    uses: ./.github/workflows/common.yml\n    with:\n      release: false\n    secrets: inherit\n    permissions:\n      contents: read\n      packages: read\n      pages: write\n"
  },
  {
    "path": ".github/workflows/pr-label-analysis.yml",
    "content": "name: PR Label Analysis\n\non:\n  pull_request:\n    types: [opened]\n\npermissions:\n  contents: read\n\njobs:\n  analyze:\n    name: Analyze PR for labeling\n    runs-on: ubuntu-latest\n    \n    steps:\n      - name: Save PR metadata\n        env:\n          PR_NUMBER: ${{ github.event.pull_request.number }}\n        run: |\n          mkdir -p ./pr-metadata\n          echo \"${PR_NUMBER}\" > ./pr-metadata/pr-number.txt\n      \n      - name: Upload PR metadata as artifact\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: pr-metadata-${{ github.event.pull_request.number }}\n          path: pr-metadata/\n          retention-days: 1"
  },
  {
    "path": ".github/workflows/pr-label-apply.yml",
    "content": "name: PR Label Apply\n\non:\n  workflow_run:\n    workflows: [\"PR Label Analysis\"]\n    types:\n      - completed\n\npermissions:\n  contents: read\n\njobs:\n  apply-labels:\n    name: Apply labels to PR\n    runs-on: ubuntu-latest\n    if: ${{ github.event.workflow_run.conclusion == 'success' }}\n    permissions:\n      contents: read\n      pull-requests: write\n    \n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n\n      - name: Download PR metadata artifact\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          run-id: ${{ github.event.workflow_run.id }}\n          pattern: pr-metadata-*\n          merge-multiple: false\n        id: download-artifact\n      \n      - name: Read PR number\n        id: pr-number\n        run: |\n          PR_NUMBER=$(cat \"pr-number.txt\")\n          echo \"number=${PR_NUMBER}\" >> $GITHUB_OUTPUT\n          echo \"PR Number: ${PR_NUMBER}\"\n      \n      - name: Apply labels using labeler\n        uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6\n        with:\n          pr-number: ${{ steps.pr-number.outputs.number }}\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          configuration-path: .github/labeler.yml\n          sync-labels: true"
  },
  {
    "path": ".github/workflows/release-build.yml",
    "content": "name: container project - release build\n\npermissions:\n  contents: read\n\non:\n  push:\n    tags:\n      - \"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"\n\njobs:\n  build:\n    name: Invoke build and release\n    uses: ./.github/workflows/common.yml\n    with:\n      release: true\n    secrets: inherit\n    permissions:\n      contents: read\n      packages: read\n      pages: write\n\n  release:\n    if: startsWith(github.ref, 'refs/tags/')\n    name: Publish release\n    timeout-minutes: 30\n    needs: build\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: read\n      pages: write\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          path: outputs\n\n      - name: Verify artifacts exist\n        run: |\n          echo \"Checking for expected artifacts...\"\n          ls -la outputs/container-package/\n          test -e outputs/container-package/*.zip || (echo \"Missing .zip file!\" && exit 1)\n          test -e outputs/container-package/*.pkg || (echo \"Missing .pkg file!\" && exit 1)\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2\n        with:\n          token: ${{ github.token }}\n          name: ${{ github.ref_name }}-prerelease\n          draft: true\n          make_latest: false\n          prerelease: true\n          fail_on_unmatched_files: true\n          files: |\n            outputs/container-package/*.zip\n            outputs/container-package/*.pkg\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: container project - release build\n\npermissions:\n  contents: read\n\non:\n  push:\n    tags:\n      - \"[0-9]+\\\\.[0-9]+\\\\.[0-9]+\"\n\njobs:\n  build:\n    name: Invoke build and release\n    uses: ./.github/workflows/common.yml\n    with:\n      release: true\n    secrets: inherit\n    permissions:\n      contents: read\n      packages: read\n      pages: write\n\n  release:\n    if: startsWith(github.ref, 'refs/tags/')\n    name: Publish release\n    timeout-minutes: 30\n    needs: build\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      packages: read\n      pages: write\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0\n        with:\n          path: outputs\n\n      - name: Verify artifacts exist\n        run: |\n          echo \"Checking for expected artifacts...\"\n          ls -la outputs/container-package/\n          test -e outputs/container-package/*.zip || (echo \"Missing .zip file!\" && exit 1)\n          test -e outputs/container-package/*.pkg || (echo \"Missing .pkg file!\" && exit 1)\n\n      - name: Create release\n        uses: softprops/action-gh-release@v2\n        with:\n          token: ${{ github.token }}\n          name: ${{ github.ref_name }}-prerelease\n          draft: true\n          make_latest: false\n          prerelease: true\n          fail_on_unmatched_files: true\n          files: |\n            outputs/container-package/*.zip\n            outputs/container-package/*.pkg\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nbin\nlibexec\n.build\n.local\nxcuserdata/\nDerivedData/\nPackages/\n.swiftpm/\n.netrc\n.swiftpm\napi-docs/\nworkdir/\ninstaller/\n.venv/\n.claude/\n.clitests/\ntest_results/\n*.pid\n*.log\n*.zip\n*.o\n*.ext4\n*.pkg\n*.swp\n\n# API docs for local preview only.\n_site/\n_serve/\n"
  },
  {
    "path": ".spi.yml",
    "content": "version: 1\nbuilder:\n  configs:\n    -\n      documentation_targets:\n        - ContainerAPIService\n        - ContainerAPIClient\n        - ContainerSandboxService\n        - ContainerSandboxServiceClient\n        - ContainerNetworkService\n        - ContainerNetworkServiceClient\n        - ContainerImagesService\n        - ContainerImagesServiceClient\n        - ContainerResource\n        - ContainerLog\n        - ContainerPlugin\n        - ContainerXPC\n        - TerminalProgress\n      swift_version: '6.2'\n"
  },
  {
    "path": ".swift-format",
    "content": "{\n  \"fileScopedDeclarationPrivacy\" : {\n    \"accessLevel\" : \"private\"\n  },\n  \"indentation\" : {\n    \"spaces\" : 4\n  },\n  \"indentConditionalCompilationBlocks\" : false,\n  \"indentSwitchCaseLabels\" : false,\n  \"lineBreakAroundMultilineExpressionChainComponents\" : false,\n  \"lineBreakBeforeControlFlowKeywords\" : false,\n  \"lineBreakBeforeEachArgument\" : false,\n  \"lineBreakBeforeEachGenericRequirement\" : false,\n  \"lineLength\" : 180,\n  \"maximumBlankLines\" : 1,\n  \"multiElementCollectionTrailingCommas\" : true,\n  \"noAssignmentInExpressions\" : {\n    \"allowedFunctions\" : [\n      \"XCTAssertNoThrow\"\n    ]\n  },\n  \"prioritizeKeepingFunctionOutputTogether\" : false,\n  \"respectsExistingLineBreaks\" : true,\n  \"rules\" : {\n    \"AllPublicDeclarationsHaveDocumentation\" : false,\n    \"AlwaysUseLowerCamelCase\" : true,\n    \"AmbiguousTrailingClosureOverload\" : false,\n    \"BeginDocumentationCommentWithOneLineSummary\" : false,\n    \"DoNotUseSemicolons\" : true,\n    \"DontRepeatTypeInStaticProperties\" : true,\n    \"FileScopedDeclarationPrivacy\" : true,\n    \"FullyIndirectEnum\" : true,\n    \"GroupNumericLiterals\" : true,\n    \"IdentifiersMustBeASCII\" : true,\n    \"NeverForceUnwrap\" : true,\n    \"NeverUseForceTry\" : true,\n    \"NeverUseImplicitlyUnwrappedOptionals\" : true,\n    \"NoAccessLevelOnExtensionDeclaration\" : true,\n    \"NoAssignmentInExpressions\" : true,\n    \"NoBlockComments\" : false,\n    \"NoCasesWithOnlyFallthrough\" : true,\n    \"NoEmptyTrailingClosureParentheses\" : true,\n    \"NoLabelsInCasePatterns\" : true,\n    \"NoLeadingUnderscores\" : false,\n    \"NoParensAroundConditions\" : true,\n    \"NoPlaygroundLiterals\" : true,\n    \"NoVoidReturnOnFunctionSignature\" : true,\n    \"OmitExplicitReturns\" : true,\n    \"OneCasePerLine\" : true,\n    \"OneVariableDeclarationPerLine\" : true,\n    \"OnlyOneTrailingClosureArgument\" : true,\n    \"OrderedImports\" : true,\n    \"ReplaceForEachWithForLoop\" : true,\n    \"ReturnVoidInsteadOfEmptyTuple\" : true,\n    \"TypeNamesShouldBeCapitalized\" : true,\n    \"UseEarlyExits\" : true,\n    \"UseLetInEveryBoundCaseVariable\" : true,\n    \"UseShorthandTypeNames\" : true,\n    \"UseSingleLinePropertyGetter\" : true,\n    \"UseSynthesizedInitializer\" : true,\n    \"UseTripleSlashForDocumentationComments\" : true,\n    \"UseWhereClausesInForLoops\" : false,\n    \"ValidateDocumentationComments\" : true\n  },\n  \"spacesAroundRangeFormationOperators\" : false,\n  \"tabWidth\" : 2,\n  \"version\" : 1\n}\n"
  },
  {
    "path": ".swift-format-nolint",
    "content": "{\n  \"fileScopedDeclarationPrivacy\" : {\n    \"accessLevel\" : \"private\"\n  },\n  \"indentation\" : {\n    \"spaces\" : 4\n  },\n  \"indentConditionalCompilationBlocks\" : false,\n  \"indentSwitchCaseLabels\" : false,\n  \"lineBreakAroundMultilineExpressionChainComponents\" : false,\n  \"lineBreakBeforeControlFlowKeywords\" : false,\n  \"lineBreakBeforeEachArgument\" : false,\n  \"lineBreakBeforeEachGenericRequirement\" : false,\n  \"lineLength\" : 180,\n  \"maximumBlankLines\" : 1,\n  \"multiElementCollectionTrailingCommas\" : true,\n  \"noAssignmentInExpressions\" : {\n    \"allowedFunctions\" : [\n      \"XCTAssertNoThrow\"\n    ]\n  },\n  \"prioritizeKeepingFunctionOutputTogether\" : false,\n  \"respectsExistingLineBreaks\" : true,\n  \"rules\" : {\n    \"AllPublicDeclarationsHaveDocumentation\" : false,\n    \"AlwaysUseLowerCamelCase\" : false,\n    \"AmbiguousTrailingClosureOverload\" : false,\n    \"BeginDocumentationCommentWithOneLineSummary\" : false,\n    \"DoNotUseSemicolons\" : true,\n    \"DontRepeatTypeInStaticProperties\" : false,\n    \"FileScopedDeclarationPrivacy\" : false,\n    \"FullyIndirectEnum\" : false,\n    \"GroupNumericLiterals\" : false,\n    \"IdentifiersMustBeASCII\" : false,\n    \"NeverForceUnwrap\" : false,\n    \"NeverUseForceTry\" : false,\n    \"NeverUseImplicitlyUnwrappedOptionals\" : false,\n    \"NoAccessLevelOnExtensionDeclaration\" : false,\n    \"NoAssignmentInExpressions\" : false,\n    \"NoBlockComments\" : false,\n    \"NoCasesWithOnlyFallthrough\" : false,\n    \"NoEmptyTrailingClosureParentheses\" : true,\n    \"NoLabelsInCasePatterns\" : false,\n    \"NoLeadingUnderscores\" : false,\n    \"NoParensAroundConditions\" : true,\n    \"NoPlaygroundLiterals\" : false,\n    \"NoVoidReturnOnFunctionSignature\" : true,\n    \"OmitExplicitReturns\" : false,\n    \"OneCasePerLine\" : true,\n    \"OneVariableDeclarationPerLine\" : true,\n    \"OnlyOneTrailingClosureArgument\" : false,\n    \"OrderedImports\" : true,\n    \"ReplaceForEachWithForLoop\" : false,\n    \"ReturnVoidInsteadOfEmptyTuple\" : false,\n    \"TypeNamesShouldBeCapitalized\" : false,\n    \"UseEarlyExits\" : false,\n    \"UseLetInEveryBoundCaseVariable\" : false,\n    \"UseShorthandTypeNames\" : true,\n    \"UseSingleLinePropertyGetter\" : true,\n    \"UseSynthesizedInitializer\" : false,\n    \"UseTripleSlashForDocumentationComments\" : true,\n    \"UseWhereClausesInForLoops\" : false,\n    \"ValidateDocumentationComments\" : false\n  },\n  \"spacesAroundRangeFormationOperators\" : false,\n  \"tabWidth\" : 2,\n  \"version\" : 1\n}\n"
  },
  {
    "path": "BUILDING.md",
    "content": "# Building the project\n\nTo build the `container` project, you need:\n\n- Mac with Apple silicon\n- macOS 15 minimum, macOS 26 recommended\n- Xcode 26, set as the [active developer directory](https://developer.apple.com/library/archive/technotes/tn2339/_index.html#//apple_ref/doc/uid/DTS40014588-CH1-HOW_DO_I_SELECT_THE_DEFAULT_VERSION_OF_XCODE_TO_USE_FOR_MY_COMMAND_LINE_TOOLS_)\n\n> [!IMPORTANT]\n> There is a bug in the `vmnet` framework on macOS 26 that causes network creation to fail if the `container` helper applications are located under your `Documents` or `Desktop` directories. If you use `make install`, you can simply run the `container` binary in `/usr/local`. If you prefer to use the binaries that `make all` creates in your project `bin` and `libexec` directories, locate your project elsewhere, such as `~/projects/container`, until this issue is resolved.\n\n## Compile and test\n\nBuild `container` and the background services from source, and run basic and integration tests:\n\n```bash\nmake all test integration\n```\n\nCopy the binaries to `/usr/local/bin` and `/usr/local/libexec` (requires entering an administrator password):\n\n```bash\nmake install\n```\n\nOr to install a release build, with better performance than the debug build:\n\n```bash\nBUILD_CONFIGURATION=release make all test integration\nBUILD_CONFIGURATION=release make install\n```\n\n## Compile protobufs\n\n`container` uses gRPC to communicate to the builder virtual machine that creates images from `Dockerfile`s, and depends on specific versions of `grpc-swift` and `swift-protobuf`. If you make changes to the gRPC APIs in the [container-builder-shim](https://github.com/apple/container-builder-shim) project, install the tools and re-generate the gRPC code in this project using:\n\n```bash\nmake protos\n```\n\n## Develop using a local copy of Containerization\n\nTo make changes to `container` that require changes to the Containerization project, or vice versa:\n\n1. Clone the [Containerization](https://github.com/apple/containerization) repository such that it sits next to your clone\nof the `container` repository. Ensure that you [follow containerization instructions](https://github.com/apple/containerization/blob/main/README.md#prepare-to-build-package)\nto prepare your build environment.\n\n2. In your development shell, go to the `container` project directory.\n\n    ```bash\n    cd container\n    ```\n\n3. If the `container` services are already running, stop them.\n\n    ```bash\n    bin/container system stop\n    ```\n\n4. Reconfigure the Swift project to use your local `containerization` package and update your `Package.resolved` file.\n\n    ```bash\n    /usr/bin/swift package edit --path ../containerization containerization\n    /usr/bin/swift package update containerization\n    ```\n\n    > [!IMPORTANT]\n    > If you are using Xcode, do **not** run `swift package edit`. Instead, temporarily modify `Package.swift` to replace the versioned `containerization` dependency:\n    >\n    > ```swift\n    > .package(url: \"https://github.com/apple/containerization.git\", exact: Version(stringLiteral: scVersion)),\n    > ```\n    >\n    > with the local path dependency:\n    >\n    > ```swift\n    > .package(path: \"../containerization\"),\n    > ```\n    >\n    > **Note:** If you have already run `swift package edit`, whether intentionally or by accident, follow the steps in the next section to restore the normal `containerization` dependency. Otherwise, the modified `Package.swift` file will not work, and the project may fail to build.\n\n5. If you want `container` to use any changes you made in the `vminit` subproject of Containerization, update the system property to use the locally built init filesystem image:\n\n    ```bash\n    container system property set image.init vminit:latest \n    ```\n\n6. Build `container`.\n\n    ```\n    make clean all\n    ```\n\n7. Restart the `container` services.\n\n    ```\n    bin/container system stop\n    bin/container system start\n    ```\n\nTo revert to using the Containerization dependency from your `Package.swift`:\n\n1. If you were using the local init filesystem, revert the system property to its default value:\n\n    ```bash\n    container system property clear image.init\n    ```\n\n2. Use the Swift package manager to restore the normal `containerization` dependency and update your `Package.resolved` file. If you are using Xcode, revert your `Package.swift` change instead of using `swift package unedit`.\n\n    ```bash\n    /usr/bin/swift package unedit containerization\n    /usr/bin/swift package update containerization\n    ```\n\n3. Rebuild `container`.\n\n    ```bash\n    make clean all\n    ```\n\n4. Restart the `container` services.\n\n    ```bash\n    bin/container system stop\n    bin/container system start\n    ```\n\n## Develop using a local copy of container-builder-shim\n\nTo test changes that require the `container-builder-shim` project:\n\n1. Clone the [container-builder-shim](https://github.com/apple/container-builder-shim) repository and navigate to its directory.\n\n2. After making the necessary changes, build the custom builder image, set it as the active builder image, and remove the existing `buildkit` container so the new image will be used:\n\n```bash\ncontainer build -t builder .\ncontainer system property set image.builder builder:latest\ncontainer rm -f buildkit\n```\n\n3. Run the `container` build as usual:\n\n```bash\ncontainer build ...\n```\n\n> [!IMPORTANT]\n> If your modified builder image is broken, make sure to rebuild and correctly tag the builder image before attempting to build `container-builder-shim` again.\n\n## Debug XPC Helpers\n\nAttach debugger to the XPC helpers using their launchd service labels:\n\n1. Find launchd service labels:\n\n   ```console\n   % container system start\n   % container run -d --name test debian:bookworm sleep infinity\n   test\n   % launchctl list | grep container\n   27068   0       com.apple.container.container-network-vmnet.default\n   27072   0       com.apple.container.container-core-images\n   26980   0       com.apple.container.apiserver\n   27331   0       com.apple.container.container-runtime-linux.test\n   ```\n\n2. Stop container and start again after setting the environment variable `CONTAINER_DEBUG_LAUNCHD_LABEL` to the label of service to attach debugger. Services whose label starts with the `CONTAINER_DEBUG_LAUNCHD_LABEL` will wait the debugger:\n\n    ```console\n    % export CONTAINER_DEBUG_LAUNCHD_LABEL=com.apple.container.container-runtime-linux.test\n    % container system start # Only the service `com.apple.container.container-runtime-linux.test` waits debugger\n    ```\n\n    ```console\n    % export CONTAINER_DEBUG_LAUNCHD_LABEL=com.apple.container.container-runtime-linux\n    % container system start # Every service starting with `com.apple.container.container-runtime-linux` waits debugger\n    ```\n\n3. Run the command to launch the service, and attach debugger:\n\n    ```console\n    % container run -it --name test debian:bookworm\n    ⠧ [6/6] Starting container [0s] # It hangs as the service is waiting for debugger\n    ```\n\n## Pre-commit hook\n\nRun `make pre-commit` to install a pre-commit hook that ensures that your changes have correct formatting and license headers when you run `git commit`.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nContributions are welcome and encouraged! Read our [main contributing guide](https://github.com/apple/containerization/blob/main/CONTRIBUTING.md) to get started.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS.txt",
    "content": "# Maintainers\n\nSee [MAINTAINERS](https://github.com/apple/containerization/blob/main/MAINTAINERS.txt) for the list of current and former maintainers of this project. Thank you for all your contributions!\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# Version and build configuration variables\nBUILD_CONFIGURATION ?= debug\nWARNINGS_AS_ERRORS ?= true\nSWIFT_CONFIGURATION := $(if $(filter-out false,$(WARNINGS_AS_ERRORS)),-Xswiftc -warnings-as-errors)\nexport RELEASE_VERSION ?= $(shell git describe --tags --always)\nexport GIT_COMMIT := $(shell git rev-parse HEAD)\n\n# Commonly used locations\nSWIFT := \"/usr/bin/swift\"\nDEST_DIR ?= /usr/local/\nROOT_DIR := $(shell git rev-parse --show-toplevel)\nBUILD_BIN_DIR = $(shell $(SWIFT) build -c $(BUILD_CONFIGURATION) --show-bin-path)\nCOV_DATA_DIR = $(shell $(SWIFT) test --show-coverage-path | xargs dirname)\nCOV_REPORT_FILE = $(ROOT_DIR)/code-coverage-report\nSTAGING_DIR := bin/$(BUILD_CONFIGURATION)/staging/\nPKG_PATH := bin/$(BUILD_CONFIGURATION)/container-installer-unsigned.pkg\nDSYM_DIR := bin/$(BUILD_CONFIGURATION)/bundle/container-dSYM\nDSYM_PATH := bin/$(BUILD_CONFIGURATION)/bundle/container-dSYM.zip\nCODESIGN_OPTS ?= --force --sign - --timestamp=none\n\n# Conditionally use a temporary data directory for integration tests\nSYSTEM_START_OPTS :=\nifneq ($(strip $(APP_ROOT)),)\n\tSYSTEM_START_OPTS += --app-root \"$(strip $(APP_ROOT))\"\nendif\nifneq ($(strip $(LOG_ROOT)),)\n\tSYSTEM_START_OPTS += --log-root \"$(strip $(LOG_ROOT))\"\nendif\n\nMACOS_VERSION := $(shell sw_vers -productVersion)\nMACOS_MAJOR := $(shell echo $(MACOS_VERSION) | cut -d. -f1)\n\nSUDO ?= sudo\n.DEFAULT_GOAL := all\n\ninclude Protobuf.Makefile\n\n.PHONY: all\nall: container\nall: init-block\n\n.PHONY: build\nbuild:\n\t@echo Building container binaries...\n\t@$(SWIFT) --version\n\t@$(SWIFT) build -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION)\n\n.PHONY: cli\ncli:\n\t@echo Building container CLI...\n\t@$(SWIFT) --version\n\t@$(SWIFT) build -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --product container\n\t@echo Installing container CLI to bin/...\n\t@mkdir -p bin\n\t@install \"$(BUILD_BIN_DIR)/container\" \"bin/container\"\n\n.PHONY: container\n# Install binaries under project directory\ncontainer: build\n\t@\"$(MAKE)\" BUILD_CONFIGURATION=$(BUILD_CONFIGURATION) DEST_DIR=\"$(ROOT_DIR)/\" SUDO= install\n\n.PHONY: release\nrelease: BUILD_CONFIGURATION = release\nrelease: all\n\n.PHONY: init-block\ninit-block:\n\t@echo Building initfs if containerization is in edit mode\n\t@scripts/install-init.sh $(SYSTEM_START_OPTS)\n\n.PHONY: install\ninstall: installer-pkg\n\t@echo Installing container installer package\n\t@if [ -z \"$(SUDO)\" ] ; then \\\n\t\ttemp_dir=$$(mktemp -d) ; \\\n\t\txar -xf $(PKG_PATH) -C $${temp_dir} ; \\\n\t\t(cd \"$(DEST_DIR)\" && pax -rz -f $${temp_dir}/Payload) ; \\\n\t\trm -rf $${temp_dir} ; \\\n\telse \\\n\t\t$(SUDO) installer -pkg $(PKG_PATH) -target / ; \\\n\tfi\n\n$(STAGING_DIR):\n\t@echo Installing container binaries from \"$(BUILD_BIN_DIR)\" into \"$(STAGING_DIR)\"...\n\t@rm -rf \"$(STAGING_DIR)\"\n\t@mkdir -p \"$(join $(STAGING_DIR), bin)\"\n\t@mkdir -p \"$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/bin)\"\n\t@mkdir -p \"$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/bin)\"\n\t@mkdir -p \"$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin)\"\n\n\t@install \"$(BUILD_BIN_DIR)/container\" \"$(join $(STAGING_DIR), bin/container)\"\n\t@install \"$(BUILD_BIN_DIR)/container-apiserver\" \"$(join $(STAGING_DIR), bin/container-apiserver)\"\n\t@install \"$(BUILD_BIN_DIR)/container-runtime-linux\" \"$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/bin/container-runtime-linux)\"\n\t@install config/container-runtime-linux-config.json \"$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/config.json)\"\n\t@install \"$(BUILD_BIN_DIR)/container-network-vmnet\" \"$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/bin/container-network-vmnet)\"\n\t@install config/container-network-vmnet-config.json \"$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/config.json)\"\n\t@install \"$(BUILD_BIN_DIR)/container-core-images\" \"$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin/container-core-images)\"\n\t@install config/container-core-images-config.json \"$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/config.json)\"\n\n\t@echo Install update script\n\t@install scripts/update-container.sh \"$(join $(STAGING_DIR), bin/update-container.sh)\"\n\t@echo Install uninstaller script\n\t@install scripts/uninstall-container.sh \"$(join $(STAGING_DIR), bin/uninstall-container.sh)\"\n\n.PHONY: installer-pkg\ninstaller-pkg: $(STAGING_DIR)\n\t@echo Signing container binaries...\n\t@codesign $(CODESIGN_OPTS) --identifier com.apple.container.cli \"$(join $(STAGING_DIR), bin/container)\"\n\t@codesign $(CODESIGN_OPTS) --identifier com.apple.container.apiserver \"$(join $(STAGING_DIR), bin/container-apiserver)\"\n\t@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. \"$(join $(STAGING_DIR), libexec/container/plugins/container-core-images/bin/container-core-images)\"\n\t@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. --entitlements=signing/container-runtime-linux.entitlements \"$(join $(STAGING_DIR), libexec/container/plugins/container-runtime-linux/bin/container-runtime-linux)\"\n\t@codesign $(CODESIGN_OPTS) --prefix=com.apple.container. --entitlements=signing/container-network-vmnet.entitlements \"$(join $(STAGING_DIR), libexec/container/plugins/container-network-vmnet/bin/container-network-vmnet)\"\n\n\t@echo Creating application installer\n\t@pkgbuild --root \"$(STAGING_DIR)\" --identifier com.apple.container-installer --install-location /usr/local --version ${RELEASE_VERSION} $(PKG_PATH)\n\t@rm -rf \"$(STAGING_DIR)\"\n\n.PHONY: dsym\ndsym:\n\t@echo Copying debug symbols...\n\t@rm -rf \"$(DSYM_DIR)\"\n\t@mkdir -p \"$(DSYM_DIR)\"\n\t@cp -a \"$(BUILD_BIN_DIR)/container-runtime-linux.dSYM\" \"$(DSYM_DIR)\"\n\t@cp -a \"$(BUILD_BIN_DIR)/container-network-vmnet.dSYM\" \"$(DSYM_DIR)\"\n\t@cp -a \"$(BUILD_BIN_DIR)/container-core-images.dSYM\" \"$(DSYM_DIR)\"\n\t@cp -a \"$(BUILD_BIN_DIR)/container-apiserver.dSYM\" \"$(DSYM_DIR)\"\n\t@cp -a \"$(BUILD_BIN_DIR)/container.dSYM\" \"$(DSYM_DIR)\"\n\n\t@echo Packaging the debug symbols...\n\t@(cd \"$(dir $(DSYM_DIR))\" ; zip -r $(notdir $(DSYM_PATH)) $(notdir $(DSYM_DIR)))\n\n.PHONY: test\ntest:\n\t@$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --skip TestCLI\n\n.PHONY: install-kernel\ninstall-kernel:\n\t@echo Stopping system before installing kernel\n\t@bin/container system stop || true\n\t@echo Starting system to install kernel\n\t@bin/container --debug system start --timeout 60 --enable-kernel-install $(SYSTEM_START_OPTS)\n\n.PHONY: coverage\ncoverage: init-block\n\t@echo Ensuring apiserver stopped before the coverage analysis\n\t@bin/container system stop && sleep 3 && scripts/ensure-container-stopped.sh\n\t@bin/container --debug system start $(SYSTEM_START_OPTS) && \\\n\techo \"Starting coverage analysis\" && \\\n\t{ \\\n\t\texit_code=0; \\\n\t\t$(SWIFT) test --no-parallel --enable-code-coverage -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) || exit_code=1 ; \\\n\t\techo Ensuring apiserver stopped after the CLI integration tests ; \\\n\t\tscripts/ensure-container-stopped.sh ; \\\n\t\techo Generating code coverage report... ; \\\n\t\txcrun llvm-profdata merge -sparse $(COV_DATA_DIR)/*.profraw -o $(COV_DATA_DIR)/default.profdata ; \\\n\t\txcrun llvm-cov show --compilation-dir=`pwd` \\\n\t\t\t-instr-profile=$(COV_DATA_DIR)/default.profdata \\\n\t\t\t--ignore-filename-regex=\".build/\" \\\n\t\t\t--ignore-filename-regex=\".pb.swift\" \\\n\t\t\t--ignore-filename-regex=\".proto\" \\\n\t\t\t--ignore-filename-regex=\".grpc.swift\" \\\n\t\t\t$(BUILD_BIN_DIR)/containerPackageTests.xctest/Contents/MacOS/containerPackageTests > $(COV_REPORT_FILE) ; \\\n\t\techo Code coverage report generated: $(COV_REPORT_FILE) ; \\\n\t\texit $${exit_code} ; \\\n\t}\n\n.PHONY: integration\nintegration: init-block\n\t@echo Ensuring apiserver stopped before the CLI integration tests...\n\t@bin/container system stop && sleep 3 && scripts/ensure-container-stopped.sh\n\t@echo Running the integration tests...\n\t@bin/container --debug system start --timeout 60 $(SYSTEM_START_OPTS) && \\\n\techo \"Starting CLI integration tests\" && \\\n\t{ \\\n\t\texit_code=0; \\\n\t\tCLITEST_LOG_ROOT=$(LOG_ROOT) && export CLITEST_LOG_ROOT ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINetwork || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunLifecycle || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIExecCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLICreateCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand1 || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand2 || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand3 || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIPruneCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRegistry || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIStatsCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIImagesCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunBase || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunInitImage || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIBuildBase || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIExportCommand || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIVolumes || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIKernelSet || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIAnonymousVolumes || exit_code=1 ; \\\n\t\t$(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLINoParallelCases || exit_code=1 ; \\\n\t\techo Ensuring apiserver stopped after the CLI integration tests ; \\\n\t\tscripts/ensure-container-stopped.sh ; \\\n\t\texit $${exit_code} ; \\\n\t}\n\n.PHONY: fmt\nfmt: swift-fmt update-licenses\n\n.PHONY: check\ncheck: swift-fmt-check check-licenses\n\n.PHONY: swift-fmt\nSWIFT_SRC = $(shell find . -type f -name '*.swift' -not -path \"*/.*\" -not -path \"*.pb.swift\" -not -path \"*.grpc.swift\" -not -path \"*/checkouts/*\")\nswift-fmt:\n\t@echo Applying the standard code formatting...\n\t@$(SWIFT) format --recursive --configuration .swift-format -i $(SWIFT_SRC)\n\nswift-fmt-check:\n\t@echo Applying the standard code formatting...\n\t@$(SWIFT) format lint --recursive --strict --configuration .swift-format-nolint $(SWIFT_SRC)\n\n.PHONY: update-licenses\nupdate-licenses:\n\t@echo Updating license headers...\n\t@./scripts/ensure-hawkeye-exists.sh\n\t@.local/bin/hawkeye format --fail-if-unknown --fail-if-updated false\n\n.PHONY: check-licenses\ncheck-licenses:\n\t@echo Checking license headers existence in source files...\n\t@./scripts/ensure-hawkeye-exists.sh\n\t@.local/bin/hawkeye check --fail-if-unknown\n\n.PHONY: pre-commit\npre-commit:\n\tcp Scripts/pre-commit.fmt .git/hooks\n\ttouch .git/hooks/pre-commit\n\tcat .git/hooks/pre-commit | grep -v 'hooks/pre-commit\\.fmt' > /tmp/pre-commit.new || true\n\techo 'PRECOMMIT_NOFMT=$${PRECOMMIT_NOFMT} $$(git rev-parse --show-toplevel)/.git/hooks/pre-commit.fmt' >> /tmp/pre-commit.new\n\tmv /tmp/pre-commit.new .git/hooks/pre-commit\n\tchmod +x .git/hooks/pre-commit\n\n.PHONY: serve-docs\nserve-docs:\n\t@echo 'to browse: open http://127.0.0.1:8000/container/documentation/'\n\t@rm -rf _serve\n\t@mkdir -p _serve\n\t@cp -a _site _serve/container\n\t@python3 -m http.server --bind 127.0.0.1 --directory ./_serve\n\n.PHONY: docs\ndocs:\n\t@echo Updating API documentation...\n\t@rm -rf _site\n\t@scripts/make-docs.sh _site container\n\n.PHONY: cleancontent\ncleancontent:\n\t@bin/container system stop || true\n\t@echo Cleaning the content...\n\t@rm -rf ~/Library/Application\\ Support/com.apple.container\n\n.PHONY: clean\nclean:\n\t@echo Cleaning build files...\n\t@rm -rf bin/ libexec/\n\t@rm -rf _site _serve\n\t@rm -f $(COV_REPORT_FILE)\n\t@$(SWIFT) package clean\n"
  },
  {
    "path": "Package.resolved",
    "content": "{\n  \"originHash\" : \"4ec05f4e83999a89d3397d0657536924d4a425d7f0e3f0fd6a3578e34c924502\",\n  \"pins\" : [\n    {\n      \"identity\" : \"async-http-client\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swift-server/async-http-client.git\",\n      \"state\" : {\n        \"revision\" : \"60235983163d040f343a489f7e2e77c1918a8bd9\",\n        \"version\" : \"1.26.1\"\n      }\n    },\n    {\n      \"identity\" : \"containerization\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/containerization.git\",\n      \"state\" : {\n        \"revision\" : \"420b915d8b4b0bc5d7edc638b985ee8fd32a3fbe\",\n        \"version\" : \"0.29.0\"\n      }\n    },\n    {\n      \"identity\" : \"grpc-swift\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/grpc/grpc-swift.git\",\n      \"state\" : {\n        \"revision\" : \"a56a157218877ef3e9625f7e1f7b2cb7e46ead1b\",\n        \"version\" : \"1.26.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-algorithms\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-algorithms.git\",\n      \"state\" : {\n        \"revision\" : \"87e50f483c54e6efd60e885f7f5aa946cee68023\",\n        \"version\" : \"1.2.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-argument-parser\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-argument-parser.git\",\n      \"state\" : {\n        \"revision\" : \"309a47b2b1d9b5e991f36961c983ecec72275be3\",\n        \"version\" : \"1.6.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-asn1\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-asn1.git\",\n      \"state\" : {\n        \"revision\" : \"f70225981241859eb4aa1a18a75531d26637c8cc\",\n        \"version\" : \"1.4.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-async-algorithms\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-async-algorithms.git\",\n      \"state\" : {\n        \"revision\" : \"6c050d5ef8e1aa6342528460db614e9770d7f804\",\n        \"version\" : \"1.1.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-atomics\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-atomics.git\",\n      \"state\" : {\n        \"revision\" : \"b601256eab081c0f92f059e12818ac1d4f178ff7\",\n        \"version\" : \"1.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-certificates\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-certificates.git\",\n      \"state\" : {\n        \"revision\" : \"133a347911b6ad0fc8fe3bf46ca90c66cff97130\",\n        \"version\" : \"1.17.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-collections\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-collections.git\",\n      \"state\" : {\n        \"revision\" : \"8c0c0a8b49e080e54e5e328cc552821ff07cd341\",\n        \"version\" : \"1.2.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-crypto\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-crypto.git\",\n      \"state\" : {\n        \"revision\" : \"334e682869394ee239a57dbe9262bff3cd9495bd\",\n        \"version\" : \"3.14.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-docc-plugin\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swiftlang/swift-docc-plugin.git\",\n      \"state\" : {\n        \"revision\" : \"3e4f133a77e644a5812911a0513aeb7288b07d06\",\n        \"version\" : \"1.4.5\"\n      }\n    },\n    {\n      \"identity\" : \"swift-docc-symbolkit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swiftlang/swift-docc-symbolkit\",\n      \"state\" : {\n        \"revision\" : \"b45d1f2ed151d057b54504d653e0da5552844e34\",\n        \"version\" : \"1.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-http-structured-headers\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-http-structured-headers.git\",\n      \"state\" : {\n        \"revision\" : \"76d7627bd88b47bf5a0f8497dd244885960dde0b\",\n        \"version\" : \"1.6.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-http-types\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-http-types.git\",\n      \"state\" : {\n        \"revision\" : \"45eb0224913ea070ec4fba17291b9e7ecf4749ca\",\n        \"version\" : \"1.5.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-log\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-log.git\",\n      \"state\" : {\n        \"revision\" : \"ce592ae52f982c847a4efc0dd881cc9eb32d29f2\",\n        \"version\" : \"1.6.4\"\n      }\n    },\n    {\n      \"identity\" : \"swift-nio\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-nio.git\",\n      \"state\" : {\n        \"revision\" : \"1c30f0f2053b654e3d1302492124aa6d242cdba7\",\n        \"version\" : \"2.86.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-nio-extras\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-nio-extras.git\",\n      \"state\" : {\n        \"revision\" : \"a55c3dd3a81d035af8a20ce5718889c0dcab073d\",\n        \"version\" : \"1.29.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-nio-http2\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-nio-http2.git\",\n      \"state\" : {\n        \"revision\" : \"5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5\",\n        \"version\" : \"1.38.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-nio-ssl\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-nio-ssl.git\",\n      \"state\" : {\n        \"revision\" : \"173cc69a058623525a58ae6710e2f5727c663793\",\n        \"version\" : \"2.36.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-nio-transport-services\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-nio-transport-services.git\",\n      \"state\" : {\n        \"revision\" : \"e645014baea2ec1c2db564410c51a656cf47c923\",\n        \"version\" : \"1.25.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-numerics\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-numerics.git\",\n      \"state\" : {\n        \"revision\" : \"0c0290ff6b24942dadb83a929ffaaa1481df04a2\",\n        \"version\" : \"1.1.1\"\n      }\n    },\n    {\n      \"identity\" : \"swift-protobuf\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-protobuf.git\",\n      \"state\" : {\n        \"revision\" : \"102a647b573f60f73afdce5613a51d71349fe507\",\n        \"version\" : \"1.30.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-service-lifecycle\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swift-server/swift-service-lifecycle.git\",\n      \"state\" : {\n        \"revision\" : \"e7187309187695115033536e8fc9b2eb87fd956d\",\n        \"version\" : \"2.8.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-system\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-system.git\",\n      \"state\" : {\n        \"revision\" : \"890830fff1a577dc83134890c7984020c5f6b43b\",\n        \"version\" : \"1.6.2\"\n      }\n    },\n    {\n      \"identity\" : \"zstd\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/facebook/zstd.git\",\n      \"state\" : {\n        \"revision\" : \"f8745da6ff1ad1e7bab384bd1f9d742439278e99\",\n        \"version\" : \"1.5.7\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version: 6.2\n//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n// The swift-tools-version declares the minimum version of Swift required to build this package.\n\nimport Foundation\nimport PackageDescription\n\nlet releaseVersion = ProcessInfo.processInfo.environment[\"RELEASE_VERSION\"] ?? \"0.0.0\"\nlet gitCommit = ProcessInfo.processInfo.environment[\"GIT_COMMIT\"] ?? \"unspecified\"\nlet builderShimVersion = \"0.10.0\"\nlet scVersion = \"0.29.0\"\n\nlet package = Package(\n    name: \"container\",\n    platforms: [.macOS(\"15\")],\n    products: [\n        .library(name: \"ContainerCommands\", targets: [\"ContainerCommands\"]),\n        .library(name: \"ContainerBuild\", targets: [\"ContainerBuild\"]),\n        .library(name: \"ContainerAPIService\", targets: [\"ContainerAPIService\"]),\n        .library(name: \"ContainerAPIClient\", targets: [\"ContainerAPIClient\"]),\n        .library(name: \"ContainerImagesService\", targets: [\"ContainerImagesService\", \"ContainerImagesServiceClient\"]),\n        .library(name: \"ContainerNetworkService\", targets: [\"ContainerNetworkService\", \"ContainerNetworkServiceClient\"]),\n        .library(name: \"ContainerSandboxService\", targets: [\"ContainerSandboxService\", \"ContainerSandboxServiceClient\"]),\n        .library(name: \"ContainerResource\", targets: [\"ContainerResource\"]),\n        .library(name: \"ContainerLog\", targets: [\"ContainerLog\"]),\n        .library(name: \"ContainerPersistence\", targets: [\"ContainerPersistence\"]),\n        .library(name: \"ContainerPlugin\", targets: [\"ContainerPlugin\"]),\n        .library(name: \"ContainerVersion\", targets: [\"ContainerVersion\"]),\n        .library(name: \"ContainerXPC\", targets: [\"ContainerXPC\"]),\n        .library(name: \"ContainerOS\", targets: [\"ContainerOS\"]),\n        .library(name: \"SocketForwarder\", targets: [\"SocketForwarder\"]),\n        .library(name: \"TerminalProgress\", targets: [\"TerminalProgress\"]),\n    ],\n    dependencies: [\n        .package(url: \"https://github.com/apple/containerization.git\", exact: Version(stringLiteral: scVersion)),\n        .package(url: \"https://github.com/apple/swift-argument-parser.git\", from: \"1.3.0\"),\n        .package(url: \"https://github.com/apple/swift-collections.git\", from: \"1.2.0\"),\n        .package(url: \"https://github.com/apple/swift-log.git\", from: \"1.0.0\"),\n        .package(url: \"https://github.com/apple/swift-nio.git\", from: \"2.80.0\"),\n        .package(url: \"https://github.com/apple/swift-protobuf.git\", from: \"1.29.0\"),\n        .package(url: \"https://github.com/apple/swift-system.git\", from: \"1.4.0\"),\n        .package(url: \"https://github.com/grpc/grpc-swift.git\", from: \"1.26.0\"),\n        .package(url: \"https://github.com/swift-server/async-http-client.git\", from: \"1.20.1\"),\n        .package(url: \"https://github.com/swiftlang/swift-docc-plugin.git\", from: \"1.1.0\"),\n    ],\n    targets: [\n        .executableTarget(\n            name: \"container\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                \"ContainerAPIClient\",\n                \"ContainerCommands\",\n            ],\n            path: \"Sources/CLI\"\n        ),\n        .testTarget(\n            name: \"CLITests\",\n            dependencies: [\n                .product(name: \"AsyncHTTPClient\", package: \"async-http-client\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationArchive\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                \"ContainerBuild\",\n                \"ContainerLog\",\n                \"ContainerResource\",\n            ],\n            path: \"Tests/CLITests\"\n        ),\n        .target(\n            name: \"ContainerCommands\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"SwiftProtobuf\", package: \"swift-protobuf\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationOCI\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                \"ContainerBuild\",\n                \"ContainerAPIClient\",\n                \"ContainerLog\",\n                \"ContainerNetworkService\",\n                \"ContainerPersistence\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n                \"TerminalProgress\",\n            ],\n            path: \"Sources/ContainerCommands\"\n        ),\n        .target(\n            name: \"ContainerBuild\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"NIO\", package: \"swift-nio\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationArchive\", package: \"containerization\"),\n                .product(name: \"ContainerizationOCI\", package: \"containerization\"),\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                \"ContainerAPIClient\",\n            ]\n        ),\n        .testTarget(\n            name: \"ContainerBuildTests\",\n            dependencies: [\n                \"ContainerBuild\"\n            ]\n        ),\n        .executableTarget(\n            name: \"container-apiserver\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"AsyncHTTPClient\", package: \"async-http-client\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                .product(name: \"ContainerizationEXT4\", package: \"containerization\"),\n                .product(name: \"GRPC\", package: \"grpc-swift\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                \"ContainerAPIService\",\n                \"ContainerAPIClient\",\n                \"ContainerLog\",\n                \"ContainerNetworkService\",\n                \"ContainerPersistence\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n                \"ContainerOS\",\n                \"DNSServer\",\n            ],\n            path: \"Sources/Helpers/APIServer\"\n        ),\n        .target(\n            name: \"ContainerAPIService\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationArchive\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"SystemPackage\", package: \"swift-system\"),\n                \"CVersion\",\n                \"ContainerAPIClient\",\n                \"ContainerNetworkServiceClient\",\n                \"ContainerPersistence\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerSandboxServiceClient\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n                \"TerminalProgress\",\n            ],\n            path: \"Sources/Services/ContainerAPIService/Server\"\n        ),\n        .target(\n            name: \"ContainerAPIClient\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationArchive\", package: \"containerization\"),\n                .product(name: \"ContainerizationOCI\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"NIOCore\", package: \"swift-nio\"),\n                .product(name: \"NIOPosix\", package: \"swift-nio\"),\n                .product(name: \"SystemPackage\", package: \"swift-system\"),\n                \"ContainerImagesServiceClient\",\n                \"ContainerPersistence\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerXPC\",\n                \"TerminalProgress\",\n            ],\n            path: \"Sources/Services/ContainerAPIService/Client\"\n        ),\n        .testTarget(\n            name: \"ContainerAPIClientTests\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerAPIClient\",\n                \"ContainerPersistence\",\n            ]\n        ),\n        .executableTarget(\n            name: \"container-core-images\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"SystemPackage\", package: \"swift-system\"),\n                \"ContainerImagesService\",\n                \"ContainerLog\",\n                \"ContainerPlugin\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Helpers/Images\"\n        ),\n        .target(\n            name: \"ContainerImagesService\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationArchive\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOCI\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                \"ContainerAPIClient\",\n                \"ContainerImagesServiceClient\",\n                \"ContainerLog\",\n                \"ContainerPersistence\",\n                \"ContainerResource\",\n                \"ContainerXPC\",\n                \"TerminalProgress\",\n            ],\n            path: \"Sources/Services/ContainerImagesService/Server\"\n        ),\n        .target(\n            name: \"ContainerImagesServiceClient\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerXPC\",\n                \"ContainerLog\",\n            ],\n            path: \"Sources/Services/ContainerImagesService/Client\"\n        ),\n        .executableTarget(\n            name: \"container-network-vmnet\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationIO\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                \"ContainerLog\",\n                \"ContainerNetworkService\",\n                \"ContainerNetworkServiceClient\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Helpers/NetworkVmnet\"\n        ),\n        .target(\n            name: \"ContainerNetworkService\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                \"ContainerNetworkServiceClient\",\n                \"ContainerPersistence\",\n                \"ContainerResource\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Services/ContainerNetworkService/Server\"\n        ),\n        .testTarget(\n            name: \"ContainerNetworkServiceTests\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                \"ContainerNetworkService\",\n            ]\n        ),\n        .target(\n            name: \"ContainerNetworkServiceClient\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerLog\",\n                \"ContainerResource\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Services/ContainerNetworkService/Client\"\n        ),\n        .executableTarget(\n            name: \"container-runtime-linux\",\n            dependencies: [\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"GRPC\", package: \"grpc-swift\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerLog\",\n                \"ContainerPlugin\",\n                \"ContainerResource\",\n                \"ContainerSandboxService\",\n                \"ContainerSandboxServiceClient\",\n                \"ContainerVersion\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Helpers/RuntimeLinux\"\n        ),\n        .target(\n            name: \"ContainerSandboxService\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                .product(name: \"ArgumentParser\", package: \"swift-argument-parser\"),\n                \"ContainerAPIClient\",\n                \"ContainerOS\",\n                \"ContainerPersistence\",\n                \"ContainerResource\",\n                \"ContainerSandboxServiceClient\",\n                \"ContainerXPC\",\n                \"SocketForwarder\",\n            ],\n            path: \"Sources/Services/ContainerSandboxService/Server\"\n        ),\n        .target(\n            name: \"ContainerSandboxServiceClient\",\n            dependencies: [\n                \"ContainerAPIClient\",\n                \"ContainerResource\",\n                \"ContainerXPC\",\n            ],\n            path: \"Sources/Services/ContainerSandboxService/Client\"\n        ),\n        .target(\n            name: \"ContainerResource\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerXPC\",\n                \"CAuditToken\",\n                \"CVersion\",\n            ]\n        ),\n        .testTarget(\n            name: \"ContainerResourceTests\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                \"ContainerAPIService\",\n                \"ContainerResource\",\n            ]\n        ),\n        .target(\n            name: \"ContainerLog\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"SystemPackage\", package: \"swift-system\"),\n            ]\n        ),\n        .target(\n            name: \"ContainerPersistence\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"CVersion\",\n                \"ContainerVersion\",\n            ]\n        ),\n        .target(\n            name: \"ContainerPlugin\",\n            dependencies: [\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n                .product(name: \"SystemPackage\", package: \"swift-system\"),\n                \"ContainerVersion\",\n            ]\n        ),\n        .testTarget(\n            name: \"ContainerPluginTests\",\n            dependencies: [\n                \"ContainerPlugin\"\n            ]\n        ),\n        .testTarget(\n            name: \"ContainerSandboxServiceTests\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                \"ContainerResource\",\n                \"ContainerSandboxServiceClient\",\n            ]\n        ),\n        .target(\n            name: \"ContainerXPC\",\n            dependencies: [\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                \"CAuditToken\",\n            ]\n        ),\n        .target(\n            name: \"ContainerOS\",\n            dependencies: [\n                .product(name: \"Containerization\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n            ],\n            path: \"Sources/ContainerOS\"\n        ),\n        .target(\n            name: \"TerminalProgress\",\n            dependencies: [\n                .product(name: \"ContainerizationOS\", package: \"containerization\")\n            ]\n        ),\n        .testTarget(\n            name: \"TerminalProgressTests\",\n            dependencies: [\"TerminalProgress\"]\n        ),\n        .target(\n            name: \"DNSServer\",\n            dependencies: [\n                .product(name: \"NIOCore\", package: \"swift-nio\"),\n                .product(name: \"NIOPosix\", package: \"swift-nio\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"ContainerizationExtras\", package: \"containerization\"),\n                .product(name: \"ContainerizationOS\", package: \"containerization\"),\n            ]\n        ),\n        .testTarget(\n            name: \"DNSServerTests\",\n            dependencies: [\n                \"DNSServer\"\n            ]\n        ),\n        .testTarget(\n            name: \"ContainerOSTests\",\n            dependencies: [\n                \"ContainerOS\"\n            ]\n        ),\n        .target(\n            name: \"SocketForwarder\",\n            dependencies: [\n                .product(name: \"Collections\", package: \"swift-collections\"),\n                .product(name: \"Logging\", package: \"swift-log\"),\n                .product(name: \"NIOCore\", package: \"swift-nio\"),\n                .product(name: \"NIOFoundationCompat\", package: \"swift-nio\"),\n            ]\n        ),\n        .testTarget(\n            name: \"SocketForwarderTests\",\n            dependencies: [\"SocketForwarder\"]\n        ),\n        .target(\n            name: \"ContainerVersion\",\n            dependencies: [\n                \"CVersion\"\n            ],\n        ),\n        .target(\n            name: \"CVersion\",\n            dependencies: [],\n            publicHeadersPath: \"include\",\n            cSettings: [\n                .define(\"CZ_VERSION\", to: \"\\\"\\(scVersion)\\\"\"),\n                .define(\"GIT_COMMIT\", to: \"\\\"\\(gitCommit)\\\"\"),\n                .define(\"RELEASE_VERSION\", to: \"\\\"\\(releaseVersion)\\\"\"),\n                .define(\"BUILDER_SHIM_VERSION\", to: \"\\\"\\(builderShimVersion)\\\"\"),\n            ],\n        ),\n        .target(\n            name: \"CAuditToken\",\n            dependencies: [],\n            publicHeadersPath: \"include\",\n            linkerSettings: [\n                .linkedLibrary(\"bsm\")\n            ]\n        ),\n    ]\n)\n"
  },
  {
    "path": "Protobuf.Makefile",
    "content": "# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nROOT_DIR := $(shell git rev-parse --show-toplevel)\nLOCAL_DIR := $(ROOT_DIR)/.local\nLOCAL_BIN_DIR := $(LOCAL_DIR)/bin\n\nBUILDER_SHIM_REPO ?= https://github.com/apple/container-builder-shim.git\n\n# Versions\nBUILDER_SHIM_VERSION ?= $(shell sed -n 's/let builderShimVersion *= *\"\\(.*\\)\"/\\1/p' Package.swift)\nPROTOC_VERSION := 26.1\n\n# Protoc binary installation\nPROTOC_ZIP := protoc-$(PROTOC_VERSION)-osx-universal_binary.zip\nPROTOC := $(LOCAL_BIN_DIR)/protoc@$(PROTOC_VERSION)/protoc\n$(PROTOC):\n\t@echo Downloading protocol buffers...\n\t@mkdir -p $(LOCAL_DIR)\n\t@curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC_ZIP)\n\t@mkdir -p $(dir $@)\n\t@unzip -jo $(PROTOC_ZIP) bin/protoc -d $(dir $@)\n\t@unzip -o $(PROTOC_ZIP) 'include/*' -d $(dir $@)\n\t@rm -f $(PROTOC_ZIP)\n\n.PHONY: protoc-gen-swift\nprotoc-gen-swift:\n\t@$(SWIFT) build --product protoc-gen-swift\n\t@$(SWIFT) build --product protoc-gen-grpc-swift\n\n.PHONY: protos\nprotos: $(PROTOC) protoc-gen-swift\n\t@echo Generating protocol buffers source code...\n\t@mkdir -p $(LOCAL_DIR)\n\t@if [ ! -d \"$(LOCAL_DIR)/container-builder-shim\" ]; then \\\n\t\tcd $(LOCAL_DIR) && git clone --branch $(BUILDER_SHIM_VERSION) --depth 1 $(BUILDER_SHIM_REPO); \\\n\tfi\n\t@$(PROTOC) $(LOCAL_DIR)/container-builder-shim/pkg/api/Builder.proto \\\n\t\t--plugin=protoc-gen-grpc-swift=$(BUILD_BIN_DIR)/protoc-gen-grpc-swift \\\n\t\t--plugin=protoc-gen-swift=$(BUILD_BIN_DIR)/protoc-gen-swift \\\n\t\t--proto_path=$(LOCAL_DIR)/container-builder-shim/pkg/api \\\n\t\t--grpc-swift_out=\"Sources/ContainerBuild\" \\\n\t\t--grpc-swift_opt=Visibility=Public \\\n\t\t--swift_out=\"Sources/ContainerBuild\" \\\n\t\t--swift_opt=Visibility=Public \\\n\t\t-I.\n\t@\"$(MAKE)\" update-licenses\n\n.PHONY: clean-proto-tools\nclean-proto-tools:\n\t@echo Cleaning proto tools...\n\t@rm -rf $(LOCAL_DIR)/bin/protoc*\n\t@rm -rf $(LOCAL_DIR)/container-builder-shim\n"
  },
  {
    "path": "README.md",
    "content": "# `container`\n\n`container` is a tool that you can use to create and run Linux containers as lightweight virtual machines on your Mac. It's written in Swift, and optimized for Apple silicon.\n\nThe tool consumes and produces [OCI-compatible container images](https://github.com/opencontainers/image-spec), so you can pull and run images from any standard container registry. You can push images that you build to those registries as well, and run the images in any other OCI-compatible application.\n\n`container` uses the [Containerization](https://github.com/apple/containerization) Swift package for low level container, image, and process management.\n\n![introductory movie showing some basic commands](./docs/assets/landing-movie.gif)\n\n## Get started\n\n### Requirements\n\nYou need a Mac with Apple silicon to run `container`. To build it, see the [BUILDING](./BUILDING.md) document.\n\n`container` is supported on macOS 26, since it takes advantage of new features and enhancements to virtualization and networking in this release. We do not support older versions of macOS and the `container` maintainers typically will not address issues that cannot be reproduced on the macOS 26.\n\n### Initial install\n\nDownload the latest signed installer package for `container` from the [GitHub release page](https://github.com/apple/container/releases).\n\nTo install the tool, double-click the package file and follow the instructions. Enter your administrator password when prompted, to give the installer permission to place the installed files under `/usr/local`.\n\nStart the system service with:\n\n```bash\ncontainer system start\n```\n\n### Upgrade or downgrade\n\nFor both upgrading and downgrading, you can manually download and install the signed installer package by following the steps from [initial install](#initial-install) or use the `update-container.sh` script (installed to `/usr/local/bin`).\n\nIf you're upgrading and downgrading, you must stop your existing `container`:\n\n```bash\ncontainer system stop\n```\n\nFor upgrading to the latest release version, simply run the command below:\n\n```bash\n/usr/local/bin/update-container.sh\n```\n\nIf you're downgrading, you must uninstall your existing `container` (the `-k` flag keeps your user data, while `-d` removes it):\n\n```bash\n/usr/local/bin/uninstall-container.sh -k\n/usr/local/bin/update-container.sh -v 0.3.0\n```\n\nStart the system service with:\n\n```bash\ncontainer system start\n```\n\n### Uninstall\n\nUse the `uninstall-container.sh` script (installed to `/usr/local/bin`) to remove `container` from your system. To remove your user data along with the tool, run:\n\n```bash\n/usr/local/bin/uninstall-container.sh -d\n```\n\nTo retain your user data so that it is available should you reinstall later, run:\n\n```bash\n/usr/local/bin/uninstall-container.sh -k\n```\n\n## Next steps\n\n- Take [a guided tour of `container`](./docs/tutorial.md) by building, running, and publishing a simple web server image.\n- Learn how to [use various `container` features](./docs/how-to.md).\n- Read a brief description and [technical overview](./docs/technical-overview.md) of `container`.\n- Browse the [full command reference](./docs/command-reference.md).\n- [Build and run](./BUILDING.md) `container` on your own development system.\n- View the project [API documentation](https://apple.github.io/container/documentation/).\n\n## Contributing\n\nContributions to `container` are welcomed and encouraged. Please see our [main contributing guide](https://github.com/apple/containerization/blob/main/CONTRIBUTING.md) for more information.\n\n## Project Status\n\nThe container project is currently under active development. Its stability, both for consuming the project as a Swift package and the `container` tool, is only guaranteed within patch versions, such as between 0.1.1 and 0.1.2. Minor version number releases may include breaking changes until we achieve a 1.0.0 release.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security disclosure process\n\nIf 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/container/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.\n\nThe 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.\n\nKeep these additional guidelines in mind when submitting your report:\n\n* Reports concerning known, publicly disclosed CVEs can be submitted as normal issues to this project.\n* Output from automated security scans or fuzzers MUST include additional context demonstrating the vulnerability with a proof of concept or working exploit.\n* 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.\n\nWhile we welcome reports for open source software projects, they are not eligible for Apple Security Bounties.\n"
  },
  {
    "path": "Sources/CAuditToken/AuditToken.c",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n// This file is required for Xcode to generate `CAuditToken.o`.\n"
  },
  {
    "path": "Sources/CAuditToken/include/AuditToken.h",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#include <xpc/xpc.h>\n#include <bsm/libbsm.h> \n\nvoid xpc_dictionary_get_audit_token(xpc_object_t xdict, audit_token_t *token);\n"
  },
  {
    "path": "Sources/CLI/ContainerCLI.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerCommands\n\n@main\npublic struct ContainerCLI: AsyncParsableCommand {\n    public init() {}\n\n    @Argument(parsing: .captureForPassthrough)\n    var arguments: [String] = []\n\n    public static let configuration = Application.configuration\n\n    public static func main() async throws {\n        try await Application.main()\n    }\n\n    public func run() async throws {\n        var application = try Application.parse(arguments)\n        try application.validate()\n        try application.run()\n    }\n}\n"
  },
  {
    "path": "Sources/CVersion/Version.c",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#include \"Version.h\"\n\nconst char* get_git_commit() {\n    return GIT_COMMIT;\n}\n\nconst char* get_release_version() {\n    return RELEASE_VERSION;\n}\n\nconst char* get_swift_containerization_version() {\n    return CZ_VERSION;\n}\n\nconst char* get_container_builder_shim_version() {\n    return BUILDER_SHIM_VERSION;\n}\n"
  },
  {
    "path": "Sources/CVersion/include/Version.h",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#ifndef CZ_VERSION\n#define CZ_VERSION \"latest\"\n#endif\n\n#ifndef GIT_COMMIT\n#define GIT_COMMIT \"unspecified\"\n#endif\n\n#ifndef RELEASE_VERSION\n#define RELEASE_VERSION \"0.0.0\"\n#endif\n\n#ifndef BUILDER_SHIM_VERSION\n#define BUILDER_SHIM_VERSION \"0.0.0\"\n#endif\n\nconst char* get_git_commit();\n\nconst char* get_release_version();\n\nconst char* get_swift_containerization_version();\n\nconst char* get_container_builder_shim_version();\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildAPI+Extensions.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Containerization\nimport ContainerizationOCI\n\npublic typealias IO = Com_Apple_Container_Build_V1_IO\npublic typealias InfoRequest = Com_Apple_Container_Build_V1_InfoRequest\npublic typealias InfoResponse = Com_Apple_Container_Build_V1_InfoResponse\npublic typealias ClientStream = Com_Apple_Container_Build_V1_ClientStream\npublic typealias ServerStream = Com_Apple_Container_Build_V1_ServerStream\npublic typealias ImageTransfer = Com_Apple_Container_Build_V1_ImageTransfer\npublic typealias BuildTransfer = Com_Apple_Container_Build_V1_BuildTransfer\npublic typealias BuilderClient = Com_Apple_Container_Build_V1_BuilderNIOClient\npublic typealias BuilderClientAsync = Com_Apple_Container_Build_V1_BuilderAsyncClient\npublic typealias BuilderClientProtocol = Com_Apple_Container_Build_V1_BuilderClientProtocol\npublic typealias BuilderClientAsyncProtocol = Com_Apple_Container_Build_V1_BuilderAsyncClient\n\nextension BuildTransfer {\n    func stage() -> String? {\n        let stage = self.metadata[\"stage\"]\n        return stage == \"\" ? nil : stage\n    }\n\n    func method() -> String? {\n        let method = self.metadata[\"method\"]\n        return method == \"\" ? nil : method\n    }\n\n    func includePatterns() -> [String]? {\n        guard let includePatternsString = self.metadata[\"include-patterns\"] else {\n            return nil\n        }\n        return includePatternsString == \"\" ? nil : includePatternsString.components(separatedBy: \",\")\n    }\n\n    func followPaths() -> [String]? {\n        guard let followPathString = self.metadata[\"followpaths\"] else {\n            return nil\n        }\n        return followPathString == \"\" ? nil : followPathString.components(separatedBy: \",\")\n    }\n\n    func mode() -> String? {\n        self.metadata[\"mode\"]\n    }\n\n    func size() -> Int? {\n        guard let sizeStr = self.metadata[\"size\"] else {\n            return nil\n        }\n        return sizeStr == \"\" ? nil : Int(sizeStr)\n    }\n\n    func offset() -> UInt64? {\n        guard let offsetStr = self.metadata[\"offset\"] else {\n            return nil\n        }\n        return offsetStr == \"\" ? nil : UInt64(offsetStr)\n    }\n\n    func len() -> Int? {\n        guard let lenStr = self.metadata[\"length\"] else {\n            return nil\n        }\n        return lenStr == \"\" ? nil : Int(lenStr)\n    }\n}\n\nextension ImageTransfer {\n    func stage() -> String? {\n        self.metadata[\"stage\"]\n    }\n\n    func method() -> String? {\n        self.metadata[\"method\"]\n    }\n\n    func ref() -> String? {\n        self.metadata[\"ref\"]\n    }\n\n    func platform() throws -> Platform? {\n        let metadata = self.metadata\n        guard let platform = metadata[\"platform\"] else {\n            return nil\n        }\n        return try Platform(from: platform)\n    }\n\n    func mode() -> String? {\n        self.metadata[\"mode\"]\n    }\n\n    func size() -> Int? {\n        let metadata = self.metadata\n        guard let sizeStr = metadata[\"size\"] else {\n            return nil\n        }\n        return Int(sizeStr)\n    }\n\n    func len() -> Int? {\n        let metadata = self.metadata\n        guard let lenStr = metadata[\"length\"] else {\n            return nil\n        }\n        return Int(lenStr)\n    }\n\n    func offset() -> UInt64? {\n        let metadata = self.metadata\n        guard let offsetStr = metadata[\"offset\"] else {\n            return nil\n        }\n        return UInt64(offsetStr)\n    }\n}\n\nextension ServerStream {\n    func getImageTransfer() -> ImageTransfer? {\n        if case .imageTransfer(let v) = self.packetType {\n            return v\n        }\n        return nil\n    }\n\n    func getBuildTransfer() -> BuildTransfer? {\n        if case .buildTransfer(let v) = self.packetType {\n            return v\n        }\n        return nil\n    }\n\n    func getIO() -> IO? {\n        if case .io(let v) = self.packetType {\n            return v\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildFSSync.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Collections\nimport ContainerAPIClient\nimport ContainerizationArchive\nimport ContainerizationOCI\nimport CryptoKit\nimport Foundation\nimport GRPC\n\nactor BuildFSSync: BuildPipelineHandler {\n    let contextDir: URL\n\n    init(_ contextDir: URL) throws {\n        guard FileManager.default.fileExists(atPath: contextDir.cleanPath) else {\n            throw Error.contextNotFound(contextDir.cleanPath)\n        }\n        guard try contextDir.isDir() else {\n            throw Error.contextIsNotDirectory(contextDir.cleanPath)\n        }\n\n        self.contextDir = contextDir\n    }\n\n    nonisolated func accept(_ packet: ServerStream) throws -> Bool {\n        guard let buildTransfer = packet.getBuildTransfer() else {\n            return false\n        }\n        guard buildTransfer.stage() == \"fssync\" else {\n            return false\n        }\n        return true\n    }\n\n    func handle(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ServerStream) async throws {\n        guard let buildTransfer = packet.getBuildTransfer() else {\n            throw Error.buildTransferMissing\n        }\n        guard let method = buildTransfer.method() else {\n            throw Error.methodMissing\n        }\n        switch try FSSyncMethod(method) {\n        case .read:\n            try await self.read(sender, buildTransfer, packet.buildID)\n        case .info:\n            try await self.info(sender, buildTransfer, packet.buildID)\n        case .walk:\n            try await self.walk(sender, buildTransfer, packet.buildID)\n        }\n    }\n\n    func read(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: BuildTransfer, _ buildID: String) async throws {\n        let offset: UInt64 = packet.offset() ?? 0\n        let size: Int = packet.len() ?? 0\n        var path: URL\n        if packet.source.hasPrefix(\"/\") {\n            path = URL(fileURLWithPath: packet.source).standardizedFileURL\n        } else {\n            path =\n                contextDir\n                .appendingPathComponent(packet.source)\n                .standardizedFileURL\n        }\n        if !FileManager.default.fileExists(atPath: path.cleanPath) {\n            path = URL(filePath: self.contextDir.cleanPath)\n            path.append(components: packet.source.cleanPathComponent)\n        }\n        let data = try {\n            if try path.isDir() {\n                return Data()\n            }\n            let file = try LocalContent(path: path.standardizedFileURL)\n            return try file.data(offset: offset, length: size) ?? Data()\n        }()\n\n        let transfer = try path.buildTransfer(id: packet.id, contextDir: self.contextDir, complete: true, data: data)\n        var response = ClientStream()\n        response.buildID = buildID\n        response.buildTransfer = transfer\n        response.packetType = .buildTransfer(transfer)\n        sender.yield(response)\n    }\n\n    func info(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: BuildTransfer, _ buildID: String) async throws {\n        let path: URL\n        if packet.source.hasPrefix(\"/\") {\n            path = URL(fileURLWithPath: packet.source).standardizedFileURL\n        } else {\n            path =\n                contextDir\n                .appendingPathComponent(packet.source)\n                .standardizedFileURL\n        }\n        let transfer = try path.buildTransfer(id: packet.id, contextDir: self.contextDir, complete: true)\n        var response = ClientStream()\n        response.buildID = buildID\n        response.buildTransfer = transfer\n        response.packetType = .buildTransfer(transfer)\n        sender.yield(response)\n    }\n\n    private struct DirEntry: Hashable {\n        let url: URL\n        let isDirectory: Bool\n        let relativePath: String\n\n        func hash(into hasher: inout Hasher) {\n            hasher.combine(relativePath)\n        }\n\n        static func == (lhs: DirEntry, rhs: DirEntry) -> Bool {\n            lhs.relativePath == rhs.relativePath\n        }\n    }\n\n    func walk(\n        _ sender: AsyncStream<ClientStream>.Continuation,\n        _ packet: BuildTransfer,\n        _ buildID: String\n    ) async throws {\n        let wantsTar = packet.mode() == \"tar\"\n\n        var entries: [String: Set<DirEntry>] = [:]\n        let followPaths: [String] = packet.followPaths() ?? []\n\n        let followPathsWalked = try walk(root: self.contextDir, includePatterns: followPaths)\n        for url in followPathsWalked {\n            guard self.contextDir.absoluteURL.cleanPath != url.absoluteURL.cleanPath else {\n                continue\n            }\n            guard self.contextDir.parentOf(url) else {\n                continue\n            }\n\n            let relPath = try url.relativeChildPath(to: contextDir)\n            let parentPath = try url.deletingLastPathComponent().relativeChildPath(to: contextDir)\n            let entry = DirEntry(url: url, isDirectory: url.hasDirectoryPath, relativePath: relPath)\n            entries[parentPath, default: []].insert(entry)\n\n            if url.isSymlink {\n                let target: URL = url.resolvingSymlinksInPath()\n                if self.contextDir.parentOf(target) {\n                    let relPath = try target.relativeChildPath(to: self.contextDir)\n                    let entry = DirEntry(url: target, isDirectory: target.hasDirectoryPath, relativePath: relPath)\n                    let parentPath: String = try target.deletingLastPathComponent().relativeChildPath(to: self.contextDir)\n                    entries[parentPath, default: []].insert(entry)\n                }\n            }\n        }\n\n        var fileOrder = [String]()\n        try processDirectory(\"\", inputEntries: entries, processedPaths: &fileOrder)\n\n        if !wantsTar {\n            let fileInfos = try fileOrder.map { rel -> FileInfo in\n                try FileInfo(path: contextDir.appendingPathComponent(rel), contextDir: contextDir)\n            }\n\n            let data = try JSONEncoder().encode(fileInfos)\n            let transfer = BuildTransfer(\n                id: packet.id,\n                source: packet.source,\n                complete: true,\n                isDir: false,\n                metadata: [\n                    \"os\": \"linux\",\n                    \"stage\": \"fssync\",\n                    \"mode\": \"json\",\n                ],\n                data: data\n            )\n            var resp = ClientStream()\n            resp.buildID = buildID\n            resp.buildTransfer = transfer\n            resp.packetType = .buildTransfer(transfer)\n            sender.yield(resp)\n            return\n        }\n\n        let tarURL = URL.temporaryDirectory\n            .appendingPathComponent(UUID().uuidString + \".tar\")\n\n        defer { try? FileManager.default.removeItem(at: tarURL) }\n\n        let writerCfg = ArchiveWriterConfiguration(\n            format: .paxRestricted,\n            filter: .none)\n\n        let tarHash = try Archiver.compress(\n            source: contextDir,\n            destination: tarURL,\n            writerConfiguration: writerCfg\n        ) { url in\n            guard let rel = try? url.relativeChildPath(to: contextDir) else {\n                return nil\n            }\n\n            guard let parent = try? url.deletingLastPathComponent().relativeChildPath(to: self.contextDir) else {\n                return nil\n            }\n\n            guard let items = entries[parent] else {\n                return nil\n            }\n\n            let include = items.contains { item in\n                item.relativePath == rel\n            }\n\n            guard include else {\n                return nil\n            }\n\n            return Archiver.ArchiveEntryInfo(\n                pathOnHost: url,\n                pathInArchive: URL(fileURLWithPath: rel))\n        }\n\n        let hash = tarHash.compactMap { String(format: \"%02x\", $0) }.joined()\n        let header = BuildTransfer(\n            id: packet.id,\n            source: tarURL.path,\n            complete: false,\n            isDir: false,\n            metadata: [\n                \"os\": \"linux\",\n                \"stage\": \"fssync\",\n                \"mode\": \"tar\",\n                \"hash\": hash,\n            ]\n        )\n        var resp = ClientStream()\n        resp.buildID = buildID\n        resp.buildTransfer = header\n        resp.packetType = .buildTransfer(header)\n        sender.yield(resp)\n\n        for try await chunk in try tarURL.bufferedCopyReader() {\n            let part = BuildTransfer(\n                id: packet.id,\n                source: tarURL.path,\n                complete: false,\n                isDir: false,\n                metadata: [\n                    \"os\": \"linux\",\n                    \"stage\": \"fssync\",\n                    \"mode\": \"tar\",\n                ],\n                data: chunk\n            )\n            var resp = ClientStream()\n            resp.buildID = buildID\n            resp.buildTransfer = part\n            resp.packetType = .buildTransfer(part)\n            sender.yield(resp)\n        }\n\n        let done = BuildTransfer(\n            id: packet.id,\n            source: tarURL.path,\n            complete: true,\n            isDir: false,\n            metadata: [\n                \"os\": \"linux\",\n                \"stage\": \"fssync\",\n                \"mode\": \"tar\",\n            ],\n            data: Data()\n        )\n\n        var finalResp = ClientStream()\n        finalResp.buildID = buildID\n        finalResp.buildTransfer = done\n        finalResp.packetType = .buildTransfer(done)\n        sender.yield(finalResp)\n    }\n\n    func walk(root: URL, includePatterns: [String]) throws -> [URL] {\n        let globber = Globber(root)\n\n        for p in includePatterns {\n            try globber.match(p)\n        }\n        return Array(globber.results)\n    }\n\n    private func processDirectory(\n        _ currentDir: String,\n        inputEntries: [String: Set<DirEntry>],\n        processedPaths: inout [String]\n    ) throws {\n        guard let entries = inputEntries[currentDir] else {\n            return\n        }\n\n        // Sort purely by lexicographical order of relativePath\n        let sortedEntries = entries.sorted { $0.relativePath < $1.relativePath }\n\n        for entry in sortedEntries {\n            processedPaths.append(entry.relativePath)\n\n            if entry.isDirectory {\n                try processDirectory(\n                    entry.relativePath,\n                    inputEntries: inputEntries,\n                    processedPaths: &processedPaths\n                )\n            }\n        }\n    }\n\n    struct FileInfo: Codable {\n        let name: String\n        let modTime: String\n        let mode: UInt32\n        let size: UInt64\n        let isDir: Bool\n        let uid: UInt32\n        let gid: UInt32\n        let target: String\n\n        init(path: URL, contextDir: URL) throws {\n            if path.isSymlink {\n                let target: URL = path.resolvingSymlinksInPath()\n                if contextDir.parentOf(target) {\n                    self.target = target.relativePathFrom(from: path)\n                } else {\n                    self.target = target.cleanPath\n                }\n            } else {\n                self.target = \"\"\n            }\n\n            self.name = try path.relativeChildPath(to: contextDir)\n            self.modTime = try path.modTime()\n            self.mode = try path.mode()\n            self.size = try path.size()\n            self.isDir = path.hasDirectoryPath\n            self.uid = 0\n            self.gid = 0\n        }\n    }\n\n    enum FSSyncMethod: String {\n        case read = \"Read\"\n        case info = \"Info\"\n        case walk = \"Walk\"\n\n        init(_ method: String) throws {\n            switch method {\n            case \"Read\":\n                self = .read\n            case \"Info\":\n                self = .info\n            case \"Walk\":\n                self = .walk\n            default:\n                throw Error.unknownMethod(method)\n            }\n        }\n    }\n}\n\nextension BuildFSSync {\n    enum Error: Swift.Error, CustomStringConvertible, Equatable {\n        case buildTransferMissing\n        case methodMissing\n        case unknownMethod(String)\n        case contextNotFound(String)\n        case contextIsNotDirectory(String)\n        case couldNotDetermineFileSize(String)\n        case couldNotDetermineModTime(String)\n        case couldNotDetermineFileMode(String)\n        case invalidOffsetSizeForFile(String, UInt64, Int)\n        case couldNotDetermineUID(String)\n        case couldNotDetermineGID(String)\n        case pathIsNotChild(String, String)\n\n        var description: String {\n            switch self {\n            case .buildTransferMissing:\n                return \"buildTransfer field missing in packet\"\n            case .methodMissing:\n                return \"method is missing in request\"\n            case .unknownMethod(let m):\n                return \"unknown content-store method \\(m)\"\n            case .contextNotFound(let path):\n                return \"context dir \\(path) not found\"\n            case .contextIsNotDirectory(let path):\n                return \"context \\(path) not a directory\"\n            case .couldNotDetermineFileSize(let path):\n                return \"could not determine size of file \\(path)\"\n            case .couldNotDetermineModTime(let path):\n                return \"could not determine last modified time of \\(path)\"\n            case .couldNotDetermineFileMode(let path):\n                return \"could not determine posix permissions (FileMode) of \\(path)\"\n            case .invalidOffsetSizeForFile(let digest, let offset, let size):\n                return \"invalid request for file: \\(digest) with offset: \\(offset) size: \\(size)\"\n            case .couldNotDetermineUID(let path):\n                return \"could not determine UID of file at path: \\(path)\"\n            case .couldNotDetermineGID(let path):\n                return \"could not determine GID of file at path: \\(path)\"\n            case .pathIsNotChild(let path, let parent):\n                return \"\\(path) is not a child of \\(parent)\"\n            }\n        }\n    }\n}\n\nextension BuildTransfer {\n    fileprivate init(id: String, source: String, complete: Bool, isDir: Bool, metadata: [String: String], data: Data? = nil) {\n        self.init()\n        self.id = id\n        self.source = source\n        self.direction = .outof\n        self.complete = complete\n        self.metadata = metadata\n        self.isDirectory = isDir\n        if let data {\n            self.data = data\n        }\n    }\n}\n\nextension URL {\n    fileprivate func size() throws -> UInt64 {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        if let size = attrs[FileAttributeKey.size] as? UInt64 {\n            return size\n        }\n        throw BuildFSSync.Error.couldNotDetermineFileSize(self.cleanPath)\n    }\n\n    fileprivate func modTime() throws -> String {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        if let date = attrs[FileAttributeKey.modificationDate] as? Date {\n            return date.rfc3339()\n        }\n        throw BuildFSSync.Error.couldNotDetermineModTime(self.cleanPath)\n    }\n\n    fileprivate func isDir() throws -> Bool {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        guard let t = attrs[.type] as? FileAttributeType, t == .typeDirectory else {\n            return false\n        }\n        return true\n    }\n\n    fileprivate func mode() throws -> UInt32 {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        if let mode = attrs[FileAttributeKey.posixPermissions] as? NSNumber {\n            return mode.uint32Value\n        }\n        throw BuildFSSync.Error.couldNotDetermineFileMode(self.cleanPath)\n    }\n\n    fileprivate func uid() throws -> UInt32 {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        if let uid = attrs[.ownerAccountID] as? UInt32 {\n            return uid\n        }\n        throw BuildFSSync.Error.couldNotDetermineUID(self.cleanPath)\n    }\n\n    fileprivate func gid() throws -> UInt32 {\n        let attrs = try FileManager.default.attributesOfItem(atPath: self.cleanPath)\n        if let gid = attrs[.groupOwnerAccountID] as? UInt32 {\n            return gid\n        }\n        throw BuildFSSync.Error.couldNotDetermineGID(self.cleanPath)\n    }\n\n    fileprivate func buildTransfer(\n        id: String,\n        contextDir: URL? = nil,\n        complete: Bool = false,\n        data: Data = Data()\n    ) throws -> BuildTransfer {\n        let p = try {\n            if let contextDir { return try self.relativeChildPath(to: contextDir) }\n            return self.cleanPath\n        }()\n        return BuildTransfer(\n            id: id,\n            source: String(p),\n            complete: complete,\n            isDir: try self.isDir(),\n            metadata: [\n                \"os\": \"linux\",\n                \"stage\": \"fssync\",\n                \"mode\": String(try self.mode()),\n                \"size\": String(try self.size()),\n                \"modified_at\": try self.modTime(),\n                \"uid\": String(try self.uid()),\n                \"gid\": String(try self.gid()),\n            ],\n            data: data\n        )\n    }\n}\n\nextension Date {\n    fileprivate func rfc3339() -> String {\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ssZZZZZ\"\n        dateFormatter.locale = Locale(identifier: \"en_US_POSIX\")\n        dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)  // Adjust if necessary\n\n        return dateFormatter.string(from: self)\n    }\n}\n\nextension String {\n    var cleanPathComponent: String {\n        let trimmed = self.trimmingCharacters(in: CharacterSet(charactersIn: \"/\"))\n        if let clean = trimmed.removingPercentEncoding {\n            return clean\n        }\n        return trimmed\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildFile.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\n\npublic struct BuildFile {\n    /// Tries to resolve either a Dockerfile or Containerfile relative to contextDir.\n    /// Checks for Dockerfile, then falls back to Containerfile.\n    public static func resolvePath(contextDir: String, log: Logger? = nil) throws -> String? {\n        // Check for Dockerfile then Containerfile in context directory\n        let dockerfilePath = URL(filePath: contextDir).appendingPathComponent(\"Dockerfile\").path\n        let containerfilePath = URL(filePath: contextDir).appendingPathComponent(\"Containerfile\").path\n\n        let dockerfileExists = FileManager.default.fileExists(atPath: dockerfilePath)\n        let containerfileExists = FileManager.default.fileExists(atPath: containerfilePath)\n\n        if dockerfileExists && containerfileExists {\n            log?.info(\"Detected both Dockerfile and Containerfile, choosing Dockerfile\")\n            return dockerfilePath\n        }\n\n        if dockerfileExists {\n            return dockerfilePath\n        }\n\n        if containerfileExists {\n            return containerfilePath\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildImageResolver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationOCI\nimport Foundation\nimport GRPC\nimport Logging\nimport TerminalProgress\n\nstruct BuildImageResolver: BuildPipelineHandler {\n    let contentStore: ContentStore\n    let quiet: Bool\n    let output: FileHandle\n    let pull: Bool\n\n    public init(_ contentStore: ContentStore, quiet: Bool = false, output: FileHandle = FileHandle.standardError, pull: Bool = false) throws {\n        self.contentStore = contentStore\n        self.quiet = quiet\n        self.output = output\n        self.pull = pull\n    }\n\n    func accept(_ packet: ServerStream) throws -> Bool {\n        guard let imageTransfer = packet.getImageTransfer() else {\n            return false\n        }\n        guard imageTransfer.stage() == \"resolver\" else {\n            return false\n        }\n        guard imageTransfer.method() == \"/resolve\" else {\n            return false\n        }\n        return true\n    }\n\n    func handle(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ServerStream) async throws {\n        guard let imageTransfer = packet.getImageTransfer() else {\n            throw Error.imageTransferMissing\n        }\n        guard let ref = imageTransfer.ref() else {\n            throw Error.tagMissing\n        }\n\n        guard let platform = try imageTransfer.platform() else {\n            throw Error.platformMissing\n        }\n\n        let img = try await {\n            let progressConfig = try ProgressConfig(\n                terminal: self.output,\n                description: \"Pulling \\(ref)\",\n                showPercent: true,\n                showProgressBar: true,\n                showSize: true,\n                showSpeed: true,\n                disableProgressUpdates: self.quiet\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer { progress.finish() }\n            progress.start()\n\n            if self.pull {\n                return try await ClientImage.pull(reference: ref, platform: platform, progressUpdate: progress.handler)\n            }\n            // Use fetch() which checks cache first, then pulls if needed\n            return try await ClientImage.fetch(reference: ref, platform: platform, progressUpdate: progress.handler)\n        }()\n\n        let index: Index = try await img.index()\n        let buildID = packet.buildID\n        let platforms = index.manifests.compactMap { $0.platform }\n        for pl in platforms {\n            if pl == platform {\n                let manifest = try await img.manifest(for: pl)\n                guard let ociImage: ContainerizationOCI.Image = try await self.contentStore.get(digest: manifest.config.digest) else {\n                    continue\n                }\n                let enc = JSONEncoder()\n                let data = try enc.encode(ociImage)\n                let transfer = try ImageTransfer(\n                    id: imageTransfer.id,\n                    digest: img.descriptor.digest,\n                    ref: ref,\n                    platform: platform.description,\n                    data: data\n                )\n                var response = ClientStream()\n                response.buildID = buildID\n                response.imageTransfer = transfer\n                response.packetType = .imageTransfer(transfer)\n                sender.yield(response)\n                return\n            }\n        }\n        throw Error.unknownPlatformForImage(platform.description, ref)\n    }\n}\n\nextension ImageTransfer {\n    fileprivate init(id: String, digest: String, ref: String, platform: String, data: Data) throws {\n        self.init()\n        self.id = id\n        self.tag = digest\n        self.metadata = [\n            \"os\": \"linux\",\n            \"stage\": \"resolver\",\n            \"method\": \"/resolve\",\n            \"ref\": ref,\n            \"platform\": platform,\n        ]\n        self.complete = true\n        self.direction = .into\n        self.data = data\n    }\n}\n\nextension BuildImageResolver {\n    enum Error: Swift.Error, CustomStringConvertible {\n        case imageTransferMissing\n        case tagMissing\n        case platformMissing\n        case imageNameMissing\n        case imageTagMissing\n        case imageNotFound\n        case indexDigestMissing(String)\n        case unknownRegistry(String)\n        case digestIsNotIndex(String)\n        case digestIsNotManifest(String)\n        case unknownPlatformForImage(String, String)\n\n        var description: String {\n            switch self {\n            case .imageTransferMissing:\n                return \"imageTransfer is missing\"\n            case .tagMissing:\n                return \"tag parameter missing in metadata\"\n            case .platformMissing:\n                return \"platform parameter missing in metadata\"\n            case .imageNameMissing:\n                return \"image name missing in $ref parameter\"\n            case .imageTagMissing:\n                return \"image tag missing in $ref parameter\"\n            case .imageNotFound:\n                return \"image not found\"\n            case .indexDigestMissing(let ref):\n                return \"index digest is missing for image: \\(ref)\"\n            case .unknownRegistry(let registry):\n                return \"registry \\(registry) is unknown\"\n            case .digestIsNotIndex(let digest):\n                return \"digest \\(digest) is not a descriptor to an index\"\n            case .digestIsNotManifest(let digest):\n                return \"digest \\(digest) is not a descriptor to a manifest\"\n            case .unknownPlatformForImage(let platform, let ref):\n                return \"platform \\(platform) for image \\(ref) not found\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildPipelineHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport GRPC\nimport NIO\n\nprotocol BuildPipelineHandler: Sendable {\n    func accept(_ packet: ServerStream) throws -> Bool\n    func handle(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ServerStream) async throws\n}\n\npublic actor BuildPipeline {\n    let handlers: [BuildPipelineHandler]\n    public init(_ config: Builder.BuildConfig) async throws {\n        self.handlers =\n            [\n                try BuildFSSync(URL(filePath: config.contextDir)),\n                try BuildRemoteContentProxy(config.contentStore),\n                try BuildImageResolver(config.contentStore, quiet: config.quiet, output: config.terminal?.handle ?? FileHandle.standardError, pull: config.pull),\n                try BuildStdio(quiet: config.quiet, output: config.terminal?.handle ?? FileHandle.standardError),\n            ]\n    }\n\n    public func run(\n        sender: AsyncStream<ClientStream>.Continuation,\n        receiver: GRPCAsyncResponseStream<ServerStream>\n    ) async throws {\n        defer { sender.finish() }\n        try await untilFirstError { group in\n            for try await packet in receiver {\n                try Task.checkCancellation()\n                for handler in self.handlers {\n                    try Task.checkCancellation()\n                    guard try handler.accept(packet) else {\n                        continue\n                    }\n                    try Task.checkCancellation()\n                    try await handler.handle(sender, packet)\n                    break\n                }\n            }\n        }\n    }\n\n    /// untilFirstError() throws when any one of its submitted tasks fail.\n    /// This is useful for asynchronous packet processing scenarios which\n    /// have the following 3 requirements:\n    ///   - the packet should be processed without blocking I/O\n    ///   - the packet stream is never-ending\n    ///   - when the first task fails, the error needs to be propagated to the caller\n    ///\n    /// Usage:\n    ///\n    ///   ```\n    ///     try await untilFirstError { group in\n    ///         for try await packet in receiver  {\n    ///              group.addTask {\n    ///                 try await handler.handle(sender, packet)\n    ///             }\n    ///         }\n    ///     }\n    ///     ```\n    ///\n    ///\n    /// WithThrowingTaskGroup cannot accomplish this because it\n    /// doesn't provide a mechanism to exit when one of the tasks fail\n    /// before all the tasks have been added. i.e. it is more suitable for\n    /// tasks that are limited. Here's a sample code where withThrowingTaskGroup\n    /// doesn't solve the problem:\n    ///\n    ///  ```\n    ///     withThrowingTaskGroup { group in\n    ///         for try await packet in receiver {\n    ///             group.addTask {\n    ///                 /* process packet */\n    ///             }\n    ///         }                          /* this loop blocks forever waiting for more packets */\n    ///         try await group.next()     /* this never gets called */\n    ///     }\n    ///  ```\n    ///  The above closure never returns even when a handler encounters an error\n    ///  because the blocking operation `try await group.next()` cannot be\n    ///  called while iterating over the receiver stream.\n    private func untilFirstError(body: @Sendable @escaping (UntilFirstError) async throws -> Void) async throws {\n        let group = try await UntilFirstError()\n        var taskContinuation: AsyncStream<Task<(), Error>>.Continuation?\n        let tasks = AsyncStream<Task<(), Error>> { continuation in\n            taskContinuation = continuation\n        }\n        guard let taskContinuation else {\n            throw NSError(\n                domain: \"untilFirstError\",\n                code: 1,\n                userInfo: [NSLocalizedDescriptionKey: \"failed to initialize task continuation\"])\n        }\n        defer { taskContinuation.finish() }\n        let stream = AsyncStream<Error> { continuation in\n            let processTasks = Task {\n                let taskStream = await group.tasks()\n                defer {\n                    continuation.finish()\n                }\n                for await item in taskStream {\n                    try Task.checkCancellation()\n                    let addedTask = Task {\n                        try Task.checkCancellation()\n                        do {\n                            try await item()\n                        } catch {\n                            continuation.yield(error)\n                            await group.continuation?.finish()\n                            throw error\n                        }\n                    }\n                    taskContinuation.yield(addedTask)\n                }\n            }\n            taskContinuation.yield(processTasks)\n\n            let mainTask = Task { @Sendable in\n                defer {\n                    continuation.finish()\n                    processTasks.cancel()\n                    taskContinuation.finish()\n                }\n                do {\n                    try Task.checkCancellation()\n                    try await body(group)\n                } catch {\n                    continuation.yield(error)\n                    await group.continuation?.finish()\n                    throw error\n                }\n            }\n            taskContinuation.yield(mainTask)\n        }\n\n        // when the first handler fails, cancel all tasks and throw error\n        for await item in stream {\n            try Task.checkCancellation()\n            Task {\n                for await task in tasks {\n                    task.cancel()\n                }\n            }\n            throw item\n        }\n        // if none of the handlers fail, wait for all subtasks to complete\n        for await task in tasks {\n            try Task.checkCancellation()\n            try await task.value\n        }\n    }\n\n    private actor UntilFirstError {\n        var stream: AsyncStream<@Sendable () async throws -> Void>?\n        var continuation: AsyncStream<@Sendable () async throws -> Void>.Continuation?\n\n        init() async throws {\n            self.stream = AsyncStream { cont in\n                self.continuation = cont\n            }\n            guard let _ = continuation else {\n                throw NSError()\n            }\n        }\n\n        func addTask(body: @Sendable @escaping () async throws -> Void) {\n            if !Task.isCancelled {\n                self.continuation?.yield(body)\n            }\n        }\n\n        func tasks() -> AsyncStream<@Sendable () async throws -> Void> {\n            self.stream!\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildRemoteContentProxy.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationArchive\nimport ContainerizationOCI\nimport Foundation\nimport GRPC\n\nstruct BuildRemoteContentProxy: BuildPipelineHandler {\n    let local: ContentStore\n\n    public init(_ contentStore: ContentStore) throws {\n        self.local = contentStore\n    }\n\n    func accept(_ packet: ServerStream) throws -> Bool {\n        guard let imageTransfer = packet.getImageTransfer() else {\n            return false\n        }\n        guard imageTransfer.stage() == \"content-store\" else {\n            return false\n        }\n        return true\n    }\n\n    func handle(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ServerStream) async throws {\n        guard let imageTransfer = packet.getImageTransfer() else {\n            throw Error.imageTransferMissing\n        }\n\n        guard let method = imageTransfer.method() else {\n            throw Error.methodMissing\n        }\n\n        switch try ContentStoreMethod(method) {\n        case .info:\n            try await self.info(sender, imageTransfer, packet.buildID)\n        case .readerAt:\n            try await self.readerAt(sender, imageTransfer, packet.buildID)\n        default:\n            throw Error.unknownMethod(method)\n        }\n    }\n\n    func info(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ImageTransfer, _ buildID: String) async throws {\n        let descriptor = try await local.get(digest: packet.tag)\n        let size = try descriptor?.size()\n        let transfer = try ImageTransfer(\n            id: packet.id,\n            digest: packet.tag,\n            method: ContentStoreMethod.info.rawValue,\n            size: size\n        )\n        var response = ClientStream()\n        response.buildID = buildID\n        response.imageTransfer = transfer\n        response.packetType = .imageTransfer(transfer)\n        sender.yield(response)\n    }\n\n    func readerAt(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ImageTransfer, _ buildID: String) async throws {\n        let digest = packet.descriptor.digest\n        let offset: UInt64 = packet.offset() ?? 0\n        let size: Int = packet.len() ?? 0\n        guard let descriptor = try await local.get(digest: digest) else {\n            throw Error.contentMissing\n        }\n        if offset == 0 && size == 0 {  // Metadata request\n            var transfer = try ImageTransfer(\n                id: packet.id,\n                digest: packet.tag,\n                method: ContentStoreMethod.readerAt.rawValue,\n                size: descriptor.size(),\n                data: Data()\n            )\n            transfer.complete = true\n            var response = ClientStream()\n            response.buildID = buildID\n            response.imageTransfer = transfer\n            response.packetType = .imageTransfer(transfer)\n            sender.yield(response)\n            return\n        }\n        guard let data = try descriptor.data(offset: offset, length: size) else {\n            throw Error.invalidOffsetSizeForContent(packet.descriptor.digest, offset, size)\n        }\n\n        let transfer = try ImageTransfer(\n            id: packet.id,\n            digest: packet.tag,\n            method: ContentStoreMethod.readerAt.rawValue,\n            size: UInt64(data.count),\n            data: data\n        )\n        var response = ClientStream()\n        response.buildID = buildID\n        response.imageTransfer = transfer\n        response.packetType = .imageTransfer(transfer)\n        sender.yield(response)\n    }\n\n    func delete(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ImageTransfer) async throws {\n        throw NSError(domain: \"RemoteContentProxy\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"unimplemented method \\(ContentStoreMethod.delete)\"])\n    }\n\n    func update(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ImageTransfer) async throws {\n        throw NSError(domain: \"RemoteContentProxy\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"unimplemented method \\(ContentStoreMethod.update)\"])\n    }\n\n    func walk(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ImageTransfer) async throws {\n        throw NSError(domain: \"RemoteContentProxy\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"unimplemented method \\(ContentStoreMethod.walk)\"])\n    }\n\n    enum ContentStoreMethod: String {\n        case info = \"/containerd.services.content.v1.Content/Info\"\n        case readerAt = \"/containerd.services.content.v1.Content/ReaderAt\"\n        case delete = \"/containerd.services.content.v1.Content/Delete\"\n        case update = \"/containerd.services.content.v1.Content/Update\"\n        case walk = \"/containerd.services.content.v1.Content/Walk\"\n\n        init(_ method: String) throws {\n            guard let value = ContentStoreMethod(rawValue: method) else {\n                throw Error.unknownMethod(method)\n            }\n            self = value\n        }\n    }\n}\n\nextension ImageTransfer {\n    fileprivate init(id: String, digest: String, method: String, size: UInt64? = nil, data: Data = Data()) throws {\n        self.init()\n        self.id = id\n        self.tag = digest\n        self.metadata = [\n            \"os\": \"linux\",\n            \"stage\": \"content-store\",\n            \"method\": method,\n        ]\n        if let size {\n            self.metadata[\"size\"] = String(size)\n        }\n        self.complete = true\n        self.direction = .into\n        self.data = data\n    }\n}\n\nextension BuildRemoteContentProxy {\n    enum Error: Swift.Error, CustomStringConvertible {\n        case imageTransferMissing\n        case methodMissing\n        case contentMissing\n        case unknownMethod(String)\n        case invalidOffsetSizeForContent(String, UInt64, Int)\n\n        var description: String {\n            switch self {\n            case .imageTransferMissing:\n                return \"imageTransfer is missing\"\n            case .methodMissing:\n                return \"method is missing in request\"\n            case .contentMissing:\n                return \"content cannot be found\"\n            case .unknownMethod(let m):\n                return \"unknown content-store method \\(m)\"\n            case .invalidOffsetSizeForContent(let digest, let offset, let size):\n                return \"invalid request for content: \\(digest) with offset: \\(offset) size: \\(size)\"\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/BuildStdio.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\nimport GRPC\nimport NIO\n\nactor BuildStdio: BuildPipelineHandler {\n    public let quiet: Bool\n    public let handle: FileHandle\n\n    init(quiet: Bool = false, output: FileHandle = FileHandle.standardError) throws {\n        self.quiet = quiet\n        self.handle = output\n    }\n\n    nonisolated func accept(_ packet: ServerStream) throws -> Bool {\n        guard let _ = packet.getIO() else {\n            return false\n        }\n        return true\n    }\n\n    func handle(_ sender: AsyncStream<ClientStream>.Continuation, _ packet: ServerStream) async throws {\n        guard !quiet else {\n            return\n        }\n        guard let io = packet.getIO() else {\n            throw Error.ioMissing\n        }\n        if let cmdString = try TerminalCommand().json() {\n            var response = ClientStream()\n            response.buildID = packet.buildID\n            response.command = .init()\n            response.command.id = packet.buildID\n            response.command.command = cmdString\n            sender.yield(response)\n        }\n        handle.write(io.data)\n    }\n}\n\nextension BuildStdio {\n    enum Error: Swift.Error, CustomStringConvertible {\n        case ioMissing\n        case invalidContinuation\n        var description: String {\n            switch self {\n            case .ioMissing:\n                return \"io field missing in packet\"\n            case .invalidContinuation:\n                return \"continuation could not created\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/Builder.grpc.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n//\n// DO NOT EDIT.\n// swift-format-ignore-file\n//\n// Generated by the protocol buffer compiler.\n// Source: Builder.proto\n//\nimport GRPC\nimport NIO\nimport NIOConcurrencyHelpers\nimport SwiftProtobuf\n\n\n/// Builder service implements APIs for performing an image build with\n/// Container image builder agent.\n///\n/// To perform a build:\n///\n/// 1. CreateBuild to create a new build\n/// 2. StartBuild to start the build execution where client and server\n///    both have a stream for exchanging data during the build.\n///\n///    The client may send:\n///      a) signal packet to signal to the build process (e.g. SIGINT)\n///\n///      b) command packet for executing a command in the build file on the\n///      server\n///         NOTE: the server will need to switch on the command to determine the\n///         type of command to execute (e.g. RUN, ENV, etc.)\n///\n///      c) transfer build data either to or from the server\n///         - INTO direction is for sending build data to the server at specific\n///         location (e.g. COPY)\n///         - OUTOF direction is for copying build data from the server to be\n///         used in subsequent build stages\n///\n///      d) transfer image content data either to or from the server\n///         - INTO direction is for sending inherited image content data to the\n///         server's local content store\n///         - OUTOF direction is for copying successfully built OCI image from\n///         the server to the client\n///\n///    The server may send:\n///      a) stdio packet for the build progress\n///\n///      b) build error indicating unsuccessful build\n///\n///      c) command complete packet indicating a command has finished executing\n///\n///      d) handle transfer build data either to or from the client\n///\n///      e) handle transfer image content data either to or from the client\n///\n///\n///    NOTE: The build data and image content data transfer is ALWAYS initiated\n///    by the client.\n///\n///    Sequence for transferring from the client to the server:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'INTO',\n///         destination path, and first chunk of data\n///      2. server starts to receive the data and stream to a temporary file\n///      3. client continues to send all chunks of data until last chunk, which\n///      client will\n///         send with 'complete' set to true\n///      4. server continues to receive until the last chunk with 'complete' set\n///      to true,\n///         server will finish writing the last chunk and un-archive the\n///         temporary file to the destination path\n///      5. server completes the transfer by sending a last\n///      BuildTransfer/ImageTransfer with\n///         'complete' set to true\n///      6. client waits for the last BuildTransfer/ImageTransfer with 'complete'\n///      set to true\n///         before proceeding with the rest of the commands\n///\n///    Sequence for transferring from the server to the client:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'OUTOF',\n///         source path, and empty data\n///      2. server archives the data at source path, and starts to send chunks to\n///      the client\n///      3. server continues to send all chunks until last chunk, which server\n///      will send with\n///         'complete' set to true\n///      4. client starts to receive the data and stream to a temporary file\n///      5. client continues to receive until the last chunk with 'complete' set\n///      to true,\n///         client will finish writing last chunk and un-archive the temporary\n///         file to the destination path\n///      6. client MAY choose to send one last BuildTransfer/ImageTransfer with\n///      'complete'\n///         set to true, but NOT required.\n///\n///\n///    NOTE: the client should close the send stream once it has finished\n///    receiving the build output or abandon the current build due to error.\n///    Server should keep the stream open until it receives the EOF that client\n///    has closed the stream, which the server should then close its send stream.\n///\n/// Usage: instantiate `Com_Apple_Container_Build_V1_BuilderClient`, then call methods of this protocol to make API calls.\npublic protocol Com_Apple_Container_Build_V1_BuilderClientProtocol: GRPCClient {\n  var serviceName: String { get }\n  var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? { get }\n\n  func createBuild(\n    _ request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    callOptions: CallOptions?\n  ) -> UnaryCall<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse>\n\n  func performBuild(\n    callOptions: CallOptions?,\n    handler: @escaping (Com_Apple_Container_Build_V1_ServerStream) -> Void\n  ) -> BidirectionalStreamingCall<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream>\n\n  func info(\n    _ request: Com_Apple_Container_Build_V1_InfoRequest,\n    callOptions: CallOptions?\n  ) -> UnaryCall<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse>\n}\n\nextension Com_Apple_Container_Build_V1_BuilderClientProtocol {\n  public var serviceName: String {\n    return \"com.apple.container.build.v1.Builder\"\n  }\n\n  /// Create a build request.\n  ///\n  /// - Parameters:\n  ///   - request: Request to send to CreateBuild.\n  ///   - callOptions: Call options.\n  /// - Returns: A `UnaryCall` with futures for the metadata, status and response.\n  public func createBuild(\n    _ request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    callOptions: CallOptions? = nil\n  ) -> UnaryCall<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse> {\n    return self.makeUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.createBuild.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeCreateBuildInterceptors() ?? []\n    )\n  }\n\n  /// Perform the build.\n  /// Executes the entire build sequence with attaching input/output\n  /// to handling data exchange with the server during the build.\n  ///\n  /// Callers should use the `send` method on the returned object to send messages\n  /// to the server. The caller should send an `.end` after the final message has been sent.\n  ///\n  /// - Parameters:\n  ///   - callOptions: Call options.\n  ///   - handler: A closure called when each response is received from the server.\n  /// - Returns: A `ClientStreamingCall` with futures for the metadata and status.\n  public func performBuild(\n    callOptions: CallOptions? = nil,\n    handler: @escaping (Com_Apple_Container_Build_V1_ServerStream) -> Void\n  ) -> BidirectionalStreamingCall<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream> {\n    return self.makeBidirectionalStreamingCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.performBuild.path,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makePerformBuildInterceptors() ?? [],\n      handler: handler\n    )\n  }\n\n  /// Unary call to Info\n  ///\n  /// - Parameters:\n  ///   - request: Request to send to Info.\n  ///   - callOptions: Call options.\n  /// - Returns: A `UnaryCall` with futures for the metadata, status and response.\n  public func info(\n    _ request: Com_Apple_Container_Build_V1_InfoRequest,\n    callOptions: CallOptions? = nil\n  ) -> UnaryCall<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse> {\n    return self.makeUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.info.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeInfoInterceptors() ?? []\n    )\n  }\n}\n\n@available(*, deprecated)\nextension Com_Apple_Container_Build_V1_BuilderClient: @unchecked Sendable {}\n\n@available(*, deprecated, renamed: \"Com_Apple_Container_Build_V1_BuilderNIOClient\")\npublic final class Com_Apple_Container_Build_V1_BuilderClient: Com_Apple_Container_Build_V1_BuilderClientProtocol {\n  private let lock = Lock()\n  private var _defaultCallOptions: CallOptions\n  private var _interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol?\n  public let channel: GRPCChannel\n  public var defaultCallOptions: CallOptions {\n    get { self.lock.withLock { return self._defaultCallOptions } }\n    set { self.lock.withLockVoid { self._defaultCallOptions = newValue } }\n  }\n  public var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? {\n    get { self.lock.withLock { return self._interceptors } }\n    set { self.lock.withLockVoid { self._interceptors = newValue } }\n  }\n\n  /// Creates a client for the com.apple.container.build.v1.Builder service.\n  ///\n  /// - Parameters:\n  ///   - channel: `GRPCChannel` to the service host.\n  ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.\n  ///   - interceptors: A factory providing interceptors for each RPC.\n  public init(\n    channel: GRPCChannel,\n    defaultCallOptions: CallOptions = CallOptions(),\n    interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? = nil\n  ) {\n    self.channel = channel\n    self._defaultCallOptions = defaultCallOptions\n    self._interceptors = interceptors\n  }\n}\n\npublic struct Com_Apple_Container_Build_V1_BuilderNIOClient: Com_Apple_Container_Build_V1_BuilderClientProtocol {\n  public var channel: GRPCChannel\n  public var defaultCallOptions: CallOptions\n  public var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol?\n\n  /// Creates a client for the com.apple.container.build.v1.Builder service.\n  ///\n  /// - Parameters:\n  ///   - channel: `GRPCChannel` to the service host.\n  ///   - defaultCallOptions: Options to use for each service call if the user doesn't provide them.\n  ///   - interceptors: A factory providing interceptors for each RPC.\n  public init(\n    channel: GRPCChannel,\n    defaultCallOptions: CallOptions = CallOptions(),\n    interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? = nil\n  ) {\n    self.channel = channel\n    self.defaultCallOptions = defaultCallOptions\n    self.interceptors = interceptors\n  }\n}\n\n/// Builder service implements APIs for performing an image build with\n/// Container image builder agent.\n///\n/// To perform a build:\n///\n/// 1. CreateBuild to create a new build\n/// 2. StartBuild to start the build execution where client and server\n///    both have a stream for exchanging data during the build.\n///\n///    The client may send:\n///      a) signal packet to signal to the build process (e.g. SIGINT)\n///\n///      b) command packet for executing a command in the build file on the\n///      server\n///         NOTE: the server will need to switch on the command to determine the\n///         type of command to execute (e.g. RUN, ENV, etc.)\n///\n///      c) transfer build data either to or from the server\n///         - INTO direction is for sending build data to the server at specific\n///         location (e.g. COPY)\n///         - OUTOF direction is for copying build data from the server to be\n///         used in subsequent build stages\n///\n///      d) transfer image content data either to or from the server\n///         - INTO direction is for sending inherited image content data to the\n///         server's local content store\n///         - OUTOF direction is for copying successfully built OCI image from\n///         the server to the client\n///\n///    The server may send:\n///      a) stdio packet for the build progress\n///\n///      b) build error indicating unsuccessful build\n///\n///      c) command complete packet indicating a command has finished executing\n///\n///      d) handle transfer build data either to or from the client\n///\n///      e) handle transfer image content data either to or from the client\n///\n///\n///    NOTE: The build data and image content data transfer is ALWAYS initiated\n///    by the client.\n///\n///    Sequence for transferring from the client to the server:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'INTO',\n///         destination path, and first chunk of data\n///      2. server starts to receive the data and stream to a temporary file\n///      3. client continues to send all chunks of data until last chunk, which\n///      client will\n///         send with 'complete' set to true\n///      4. server continues to receive until the last chunk with 'complete' set\n///      to true,\n///         server will finish writing the last chunk and un-archive the\n///         temporary file to the destination path\n///      5. server completes the transfer by sending a last\n///      BuildTransfer/ImageTransfer with\n///         'complete' set to true\n///      6. client waits for the last BuildTransfer/ImageTransfer with 'complete'\n///      set to true\n///         before proceeding with the rest of the commands\n///\n///    Sequence for transferring from the server to the client:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'OUTOF',\n///         source path, and empty data\n///      2. server archives the data at source path, and starts to send chunks to\n///      the client\n///      3. server continues to send all chunks until last chunk, which server\n///      will send with\n///         'complete' set to true\n///      4. client starts to receive the data and stream to a temporary file\n///      5. client continues to receive until the last chunk with 'complete' set\n///      to true,\n///         client will finish writing last chunk and un-archive the temporary\n///         file to the destination path\n///      6. client MAY choose to send one last BuildTransfer/ImageTransfer with\n///      'complete'\n///         set to true, but NOT required.\n///\n///\n///    NOTE: the client should close the send stream once it has finished\n///    receiving the build output or abandon the current build due to error.\n///    Server should keep the stream open until it receives the EOF that client\n///    has closed the stream, which the server should then close its send stream.\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\npublic protocol Com_Apple_Container_Build_V1_BuilderAsyncClientProtocol: GRPCClient {\n  static var serviceDescriptor: GRPCServiceDescriptor { get }\n  var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? { get }\n\n  func makeCreateBuildCall(\n    _ request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    callOptions: CallOptions?\n  ) -> GRPCAsyncUnaryCall<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse>\n\n  func makePerformBuildCall(\n    callOptions: CallOptions?\n  ) -> GRPCAsyncBidirectionalStreamingCall<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream>\n\n  func makeInfoCall(\n    _ request: Com_Apple_Container_Build_V1_InfoRequest,\n    callOptions: CallOptions?\n  ) -> GRPCAsyncUnaryCall<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse>\n}\n\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\nextension Com_Apple_Container_Build_V1_BuilderAsyncClientProtocol {\n  public static var serviceDescriptor: GRPCServiceDescriptor {\n    return Com_Apple_Container_Build_V1_BuilderClientMetadata.serviceDescriptor\n  }\n\n  public var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? {\n    return nil\n  }\n\n  public func makeCreateBuildCall(\n    _ request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    callOptions: CallOptions? = nil\n  ) -> GRPCAsyncUnaryCall<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse> {\n    return self.makeAsyncUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.createBuild.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeCreateBuildInterceptors() ?? []\n    )\n  }\n\n  public func makePerformBuildCall(\n    callOptions: CallOptions? = nil\n  ) -> GRPCAsyncBidirectionalStreamingCall<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream> {\n    return self.makeAsyncBidirectionalStreamingCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.performBuild.path,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makePerformBuildInterceptors() ?? []\n    )\n  }\n\n  public func makeInfoCall(\n    _ request: Com_Apple_Container_Build_V1_InfoRequest,\n    callOptions: CallOptions? = nil\n  ) -> GRPCAsyncUnaryCall<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse> {\n    return self.makeAsyncUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.info.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeInfoInterceptors() ?? []\n    )\n  }\n}\n\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\nextension Com_Apple_Container_Build_V1_BuilderAsyncClientProtocol {\n  public func createBuild(\n    _ request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    callOptions: CallOptions? = nil\n  ) async throws -> Com_Apple_Container_Build_V1_CreateBuildResponse {\n    return try await self.performAsyncUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.createBuild.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeCreateBuildInterceptors() ?? []\n    )\n  }\n\n  public func performBuild<RequestStream>(\n    _ requests: RequestStream,\n    callOptions: CallOptions? = nil\n  ) -> GRPCAsyncResponseStream<Com_Apple_Container_Build_V1_ServerStream> where RequestStream: Sequence, RequestStream.Element == Com_Apple_Container_Build_V1_ClientStream {\n    return self.performAsyncBidirectionalStreamingCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.performBuild.path,\n      requests: requests,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makePerformBuildInterceptors() ?? []\n    )\n  }\n\n  public func performBuild<RequestStream>(\n    _ requests: RequestStream,\n    callOptions: CallOptions? = nil\n  ) -> GRPCAsyncResponseStream<Com_Apple_Container_Build_V1_ServerStream> where RequestStream: AsyncSequence & Sendable, RequestStream.Element == Com_Apple_Container_Build_V1_ClientStream {\n    return self.performAsyncBidirectionalStreamingCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.performBuild.path,\n      requests: requests,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makePerformBuildInterceptors() ?? []\n    )\n  }\n\n  public func info(\n    _ request: Com_Apple_Container_Build_V1_InfoRequest,\n    callOptions: CallOptions? = nil\n  ) async throws -> Com_Apple_Container_Build_V1_InfoResponse {\n    return try await self.performAsyncUnaryCall(\n      path: Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.info.path,\n      request: request,\n      callOptions: callOptions ?? self.defaultCallOptions,\n      interceptors: self.interceptors?.makeInfoInterceptors() ?? []\n    )\n  }\n}\n\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\npublic struct Com_Apple_Container_Build_V1_BuilderAsyncClient: Com_Apple_Container_Build_V1_BuilderAsyncClientProtocol {\n  public var channel: GRPCChannel\n  public var defaultCallOptions: CallOptions\n  public var interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol?\n\n  public init(\n    channel: GRPCChannel,\n    defaultCallOptions: CallOptions = CallOptions(),\n    interceptors: Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol? = nil\n  ) {\n    self.channel = channel\n    self.defaultCallOptions = defaultCallOptions\n    self.interceptors = interceptors\n  }\n}\n\npublic protocol Com_Apple_Container_Build_V1_BuilderClientInterceptorFactoryProtocol: Sendable {\n\n  /// - Returns: Interceptors to use when invoking 'createBuild'.\n  func makeCreateBuildInterceptors() -> [ClientInterceptor<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse>]\n\n  /// - Returns: Interceptors to use when invoking 'performBuild'.\n  func makePerformBuildInterceptors() -> [ClientInterceptor<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream>]\n\n  /// - Returns: Interceptors to use when invoking 'info'.\n  func makeInfoInterceptors() -> [ClientInterceptor<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse>]\n}\n\npublic enum Com_Apple_Container_Build_V1_BuilderClientMetadata {\n  public static let serviceDescriptor = GRPCServiceDescriptor(\n    name: \"Builder\",\n    fullName: \"com.apple.container.build.v1.Builder\",\n    methods: [\n      Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.createBuild,\n      Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.performBuild,\n      Com_Apple_Container_Build_V1_BuilderClientMetadata.Methods.info,\n    ]\n  )\n\n  public enum Methods {\n    public static let createBuild = GRPCMethodDescriptor(\n      name: \"CreateBuild\",\n      path: \"/com.apple.container.build.v1.Builder/CreateBuild\",\n      type: GRPCCallType.unary\n    )\n\n    public static let performBuild = GRPCMethodDescriptor(\n      name: \"PerformBuild\",\n      path: \"/com.apple.container.build.v1.Builder/PerformBuild\",\n      type: GRPCCallType.bidirectionalStreaming\n    )\n\n    public static let info = GRPCMethodDescriptor(\n      name: \"Info\",\n      path: \"/com.apple.container.build.v1.Builder/Info\",\n      type: GRPCCallType.unary\n    )\n  }\n}\n\n/// Builder service implements APIs for performing an image build with\n/// Container image builder agent.\n///\n/// To perform a build:\n///\n/// 1. CreateBuild to create a new build\n/// 2. StartBuild to start the build execution where client and server\n///    both have a stream for exchanging data during the build.\n///\n///    The client may send:\n///      a) signal packet to signal to the build process (e.g. SIGINT)\n///\n///      b) command packet for executing a command in the build file on the\n///      server\n///         NOTE: the server will need to switch on the command to determine the\n///         type of command to execute (e.g. RUN, ENV, etc.)\n///\n///      c) transfer build data either to or from the server\n///         - INTO direction is for sending build data to the server at specific\n///         location (e.g. COPY)\n///         - OUTOF direction is for copying build data from the server to be\n///         used in subsequent build stages\n///\n///      d) transfer image content data either to or from the server\n///         - INTO direction is for sending inherited image content data to the\n///         server's local content store\n///         - OUTOF direction is for copying successfully built OCI image from\n///         the server to the client\n///\n///    The server may send:\n///      a) stdio packet for the build progress\n///\n///      b) build error indicating unsuccessful build\n///\n///      c) command complete packet indicating a command has finished executing\n///\n///      d) handle transfer build data either to or from the client\n///\n///      e) handle transfer image content data either to or from the client\n///\n///\n///    NOTE: The build data and image content data transfer is ALWAYS initiated\n///    by the client.\n///\n///    Sequence for transferring from the client to the server:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'INTO',\n///         destination path, and first chunk of data\n///      2. server starts to receive the data and stream to a temporary file\n///      3. client continues to send all chunks of data until last chunk, which\n///      client will\n///         send with 'complete' set to true\n///      4. server continues to receive until the last chunk with 'complete' set\n///      to true,\n///         server will finish writing the last chunk and un-archive the\n///         temporary file to the destination path\n///      5. server completes the transfer by sending a last\n///      BuildTransfer/ImageTransfer with\n///         'complete' set to true\n///      6. client waits for the last BuildTransfer/ImageTransfer with 'complete'\n///      set to true\n///         before proceeding with the rest of the commands\n///\n///    Sequence for transferring from the server to the client:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'OUTOF',\n///         source path, and empty data\n///      2. server archives the data at source path, and starts to send chunks to\n///      the client\n///      3. server continues to send all chunks until last chunk, which server\n///      will send with\n///         'complete' set to true\n///      4. client starts to receive the data and stream to a temporary file\n///      5. client continues to receive until the last chunk with 'complete' set\n///      to true,\n///         client will finish writing last chunk and un-archive the temporary\n///         file to the destination path\n///      6. client MAY choose to send one last BuildTransfer/ImageTransfer with\n///      'complete'\n///         set to true, but NOT required.\n///\n///\n///    NOTE: the client should close the send stream once it has finished\n///    receiving the build output or abandon the current build due to error.\n///    Server should keep the stream open until it receives the EOF that client\n///    has closed the stream, which the server should then close its send stream.\n///\n/// To build a server, implement a class that conforms to this protocol.\npublic protocol Com_Apple_Container_Build_V1_BuilderProvider: CallHandlerProvider {\n  var interceptors: Com_Apple_Container_Build_V1_BuilderServerInterceptorFactoryProtocol? { get }\n\n  /// Create a build request.\n  func createBuild(request: Com_Apple_Container_Build_V1_CreateBuildRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Container_Build_V1_CreateBuildResponse>\n\n  /// Perform the build.\n  /// Executes the entire build sequence with attaching input/output\n  /// to handling data exchange with the server during the build.\n  func performBuild(context: StreamingResponseCallContext<Com_Apple_Container_Build_V1_ServerStream>) -> EventLoopFuture<(StreamEvent<Com_Apple_Container_Build_V1_ClientStream>) -> Void>\n\n  func info(request: Com_Apple_Container_Build_V1_InfoRequest, context: StatusOnlyCallContext) -> EventLoopFuture<Com_Apple_Container_Build_V1_InfoResponse>\n}\n\nextension Com_Apple_Container_Build_V1_BuilderProvider {\n  public var serviceName: Substring {\n    return Com_Apple_Container_Build_V1_BuilderServerMetadata.serviceDescriptor.fullName[...]\n  }\n\n  /// Determines, calls and returns the appropriate request handler, depending on the request's method.\n  /// Returns nil for methods not handled by this service.\n  public func handle(\n    method name: Substring,\n    context: CallHandlerContext\n  ) -> GRPCServerHandlerProtocol? {\n    switch name {\n    case \"CreateBuild\":\n      return UnaryServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_CreateBuildRequest>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_CreateBuildResponse>(),\n        interceptors: self.interceptors?.makeCreateBuildInterceptors() ?? [],\n        userFunction: self.createBuild(request:context:)\n      )\n\n    case \"PerformBuild\":\n      return BidirectionalStreamingServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_ClientStream>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_ServerStream>(),\n        interceptors: self.interceptors?.makePerformBuildInterceptors() ?? [],\n        observerFactory: self.performBuild(context:)\n      )\n\n    case \"Info\":\n      return UnaryServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_InfoRequest>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_InfoResponse>(),\n        interceptors: self.interceptors?.makeInfoInterceptors() ?? [],\n        userFunction: self.info(request:context:)\n      )\n\n    default:\n      return nil\n    }\n  }\n}\n\n/// Builder service implements APIs for performing an image build with\n/// Container image builder agent.\n///\n/// To perform a build:\n///\n/// 1. CreateBuild to create a new build\n/// 2. StartBuild to start the build execution where client and server\n///    both have a stream for exchanging data during the build.\n///\n///    The client may send:\n///      a) signal packet to signal to the build process (e.g. SIGINT)\n///\n///      b) command packet for executing a command in the build file on the\n///      server\n///         NOTE: the server will need to switch on the command to determine the\n///         type of command to execute (e.g. RUN, ENV, etc.)\n///\n///      c) transfer build data either to or from the server\n///         - INTO direction is for sending build data to the server at specific\n///         location (e.g. COPY)\n///         - OUTOF direction is for copying build data from the server to be\n///         used in subsequent build stages\n///\n///      d) transfer image content data either to or from the server\n///         - INTO direction is for sending inherited image content data to the\n///         server's local content store\n///         - OUTOF direction is for copying successfully built OCI image from\n///         the server to the client\n///\n///    The server may send:\n///      a) stdio packet for the build progress\n///\n///      b) build error indicating unsuccessful build\n///\n///      c) command complete packet indicating a command has finished executing\n///\n///      d) handle transfer build data either to or from the client\n///\n///      e) handle transfer image content data either to or from the client\n///\n///\n///    NOTE: The build data and image content data transfer is ALWAYS initiated\n///    by the client.\n///\n///    Sequence for transferring from the client to the server:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'INTO',\n///         destination path, and first chunk of data\n///      2. server starts to receive the data and stream to a temporary file\n///      3. client continues to send all chunks of data until last chunk, which\n///      client will\n///         send with 'complete' set to true\n///      4. server continues to receive until the last chunk with 'complete' set\n///      to true,\n///         server will finish writing the last chunk and un-archive the\n///         temporary file to the destination path\n///      5. server completes the transfer by sending a last\n///      BuildTransfer/ImageTransfer with\n///         'complete' set to true\n///      6. client waits for the last BuildTransfer/ImageTransfer with 'complete'\n///      set to true\n///         before proceeding with the rest of the commands\n///\n///    Sequence for transferring from the server to the client:\n///      1. client send a BuildTransfer/ImageTransfer request with ID, direction\n///      of 'OUTOF',\n///         source path, and empty data\n///      2. server archives the data at source path, and starts to send chunks to\n///      the client\n///      3. server continues to send all chunks until last chunk, which server\n///      will send with\n///         'complete' set to true\n///      4. client starts to receive the data and stream to a temporary file\n///      5. client continues to receive until the last chunk with 'complete' set\n///      to true,\n///         client will finish writing last chunk and un-archive the temporary\n///         file to the destination path\n///      6. client MAY choose to send one last BuildTransfer/ImageTransfer with\n///      'complete'\n///         set to true, but NOT required.\n///\n///\n///    NOTE: the client should close the send stream once it has finished\n///    receiving the build output or abandon the current build due to error.\n///    Server should keep the stream open until it receives the EOF that client\n///    has closed the stream, which the server should then close its send stream.\n///\n/// To implement a server, implement an object which conforms to this protocol.\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\npublic protocol Com_Apple_Container_Build_V1_BuilderAsyncProvider: CallHandlerProvider, Sendable {\n  static var serviceDescriptor: GRPCServiceDescriptor { get }\n  var interceptors: Com_Apple_Container_Build_V1_BuilderServerInterceptorFactoryProtocol? { get }\n\n  /// Create a build request.\n  func createBuild(\n    request: Com_Apple_Container_Build_V1_CreateBuildRequest,\n    context: GRPCAsyncServerCallContext\n  ) async throws -> Com_Apple_Container_Build_V1_CreateBuildResponse\n\n  /// Perform the build.\n  /// Executes the entire build sequence with attaching input/output\n  /// to handling data exchange with the server during the build.\n  func performBuild(\n    requestStream: GRPCAsyncRequestStream<Com_Apple_Container_Build_V1_ClientStream>,\n    responseStream: GRPCAsyncResponseStreamWriter<Com_Apple_Container_Build_V1_ServerStream>,\n    context: GRPCAsyncServerCallContext\n  ) async throws\n\n  func info(\n    request: Com_Apple_Container_Build_V1_InfoRequest,\n    context: GRPCAsyncServerCallContext\n  ) async throws -> Com_Apple_Container_Build_V1_InfoResponse\n}\n\n@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)\nextension Com_Apple_Container_Build_V1_BuilderAsyncProvider {\n  public static var serviceDescriptor: GRPCServiceDescriptor {\n    return Com_Apple_Container_Build_V1_BuilderServerMetadata.serviceDescriptor\n  }\n\n  public var serviceName: Substring {\n    return Com_Apple_Container_Build_V1_BuilderServerMetadata.serviceDescriptor.fullName[...]\n  }\n\n  public var interceptors: Com_Apple_Container_Build_V1_BuilderServerInterceptorFactoryProtocol? {\n    return nil\n  }\n\n  public func handle(\n    method name: Substring,\n    context: CallHandlerContext\n  ) -> GRPCServerHandlerProtocol? {\n    switch name {\n    case \"CreateBuild\":\n      return GRPCAsyncServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_CreateBuildRequest>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_CreateBuildResponse>(),\n        interceptors: self.interceptors?.makeCreateBuildInterceptors() ?? [],\n        wrapping: { try await self.createBuild(request: $0, context: $1) }\n      )\n\n    case \"PerformBuild\":\n      return GRPCAsyncServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_ClientStream>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_ServerStream>(),\n        interceptors: self.interceptors?.makePerformBuildInterceptors() ?? [],\n        wrapping: { try await self.performBuild(requestStream: $0, responseStream: $1, context: $2) }\n      )\n\n    case \"Info\":\n      return GRPCAsyncServerHandler(\n        context: context,\n        requestDeserializer: ProtobufDeserializer<Com_Apple_Container_Build_V1_InfoRequest>(),\n        responseSerializer: ProtobufSerializer<Com_Apple_Container_Build_V1_InfoResponse>(),\n        interceptors: self.interceptors?.makeInfoInterceptors() ?? [],\n        wrapping: { try await self.info(request: $0, context: $1) }\n      )\n\n    default:\n      return nil\n    }\n  }\n}\n\npublic protocol Com_Apple_Container_Build_V1_BuilderServerInterceptorFactoryProtocol: Sendable {\n\n  /// - Returns: Interceptors to use when handling 'createBuild'.\n  ///   Defaults to calling `self.makeInterceptors()`.\n  func makeCreateBuildInterceptors() -> [ServerInterceptor<Com_Apple_Container_Build_V1_CreateBuildRequest, Com_Apple_Container_Build_V1_CreateBuildResponse>]\n\n  /// - Returns: Interceptors to use when handling 'performBuild'.\n  ///   Defaults to calling `self.makeInterceptors()`.\n  func makePerformBuildInterceptors() -> [ServerInterceptor<Com_Apple_Container_Build_V1_ClientStream, Com_Apple_Container_Build_V1_ServerStream>]\n\n  /// - Returns: Interceptors to use when handling 'info'.\n  ///   Defaults to calling `self.makeInterceptors()`.\n  func makeInfoInterceptors() -> [ServerInterceptor<Com_Apple_Container_Build_V1_InfoRequest, Com_Apple_Container_Build_V1_InfoResponse>]\n}\n\npublic enum Com_Apple_Container_Build_V1_BuilderServerMetadata {\n  public static let serviceDescriptor = GRPCServiceDescriptor(\n    name: \"Builder\",\n    fullName: \"com.apple.container.build.v1.Builder\",\n    methods: [\n      Com_Apple_Container_Build_V1_BuilderServerMetadata.Methods.createBuild,\n      Com_Apple_Container_Build_V1_BuilderServerMetadata.Methods.performBuild,\n      Com_Apple_Container_Build_V1_BuilderServerMetadata.Methods.info,\n    ]\n  )\n\n  public enum Methods {\n    public static let createBuild = GRPCMethodDescriptor(\n      name: \"CreateBuild\",\n      path: \"/com.apple.container.build.v1.Builder/CreateBuild\",\n      type: GRPCCallType.unary\n    )\n\n    public static let performBuild = GRPCMethodDescriptor(\n      name: \"PerformBuild\",\n      path: \"/com.apple.container.build.v1.Builder/PerformBuild\",\n      type: GRPCCallType.bidirectionalStreaming\n    )\n\n    public static let info = GRPCMethodDescriptor(\n      name: \"Info\",\n      path: \"/com.apple.container.build.v1.Builder/Info\",\n      type: GRPCCallType.unary\n    )\n  }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/Builder.pb.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n// DO NOT EDIT.\n// swift-format-ignore-file\n// swiftlint:disable all\n//\n// Generated by the Swift generator plugin for the protocol buffer compiler.\n// Source: Builder.proto\n//\n// For information on using the generated types, please see the documentation:\n//   https://github.com/apple/swift-protobuf/\n\nimport Foundation\nimport SwiftProtobuf\n\n// If the compiler emits an error on this type, it is because this file\n// was generated by a version of the `protoc` Swift plug-in that is\n// incompatible with the version of SwiftProtobuf to which you are linking.\n// Please ensure that you are building against the same version of the API\n// that was used to generate this file.\nfileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {\n  struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}\n  typealias Version = _2\n}\n\npublic enum Com_Apple_Container_Build_V1_TransferDirection: SwiftProtobuf.Enum, Swift.CaseIterable {\n  public typealias RawValue = Int\n  case into // = 0\n  case outof // = 1\n  case UNRECOGNIZED(Int)\n\n  public init() {\n    self = .into\n  }\n\n  public init?(rawValue: Int) {\n    switch rawValue {\n    case 0: self = .into\n    case 1: self = .outof\n    default: self = .UNRECOGNIZED(rawValue)\n    }\n  }\n\n  public var rawValue: Int {\n    switch self {\n    case .into: return 0\n    case .outof: return 1\n    case .UNRECOGNIZED(let i): return i\n    }\n  }\n\n  // The compiler won't synthesize support with the UNRECOGNIZED case.\n  public static let allCases: [Com_Apple_Container_Build_V1_TransferDirection] = [\n    .into,\n    .outof,\n  ]\n\n}\n\n/// Standard input/output.\npublic enum Com_Apple_Container_Build_V1_Stdio: SwiftProtobuf.Enum, Swift.CaseIterable {\n  public typealias RawValue = Int\n  case stdin // = 0\n  case stdout // = 1\n  case stderr // = 2\n  case UNRECOGNIZED(Int)\n\n  public init() {\n    self = .stdin\n  }\n\n  public init?(rawValue: Int) {\n    switch rawValue {\n    case 0: self = .stdin\n    case 1: self = .stdout\n    case 2: self = .stderr\n    default: self = .UNRECOGNIZED(rawValue)\n    }\n  }\n\n  public var rawValue: Int {\n    switch self {\n    case .stdin: return 0\n    case .stdout: return 1\n    case .stderr: return 2\n    case .UNRECOGNIZED(let i): return i\n    }\n  }\n\n  // The compiler won't synthesize support with the UNRECOGNIZED case.\n  public static let allCases: [Com_Apple_Container_Build_V1_Stdio] = [\n    .stdin,\n    .stdout,\n    .stderr,\n  ]\n\n}\n\n/// Build error type.\npublic enum Com_Apple_Container_Build_V1_BuildErrorType: SwiftProtobuf.Enum, Swift.CaseIterable {\n  public typealias RawValue = Int\n  case buildFailed // = 0\n  case `internal` // = 1\n  case UNRECOGNIZED(Int)\n\n  public init() {\n    self = .buildFailed\n  }\n\n  public init?(rawValue: Int) {\n    switch rawValue {\n    case 0: self = .buildFailed\n    case 1: self = .internal\n    default: self = .UNRECOGNIZED(rawValue)\n    }\n  }\n\n  public var rawValue: Int {\n    switch self {\n    case .buildFailed: return 0\n    case .internal: return 1\n    case .UNRECOGNIZED(let i): return i\n    }\n  }\n\n  // The compiler won't synthesize support with the UNRECOGNIZED case.\n  public static let allCases: [Com_Apple_Container_Build_V1_BuildErrorType] = [\n    .buildFailed,\n    .internal,\n  ]\n\n}\n\npublic struct Com_Apple_Container_Build_V1_InfoRequest: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_InfoResponse: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_CreateBuildRequest: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// The name of the build stage.\n  public var stageName: String = String()\n\n  /// The tag of the image to be created.\n  public var tag: String = String()\n\n  /// Any additional metadata to be associated with the build.\n  public var metadata: Dictionary<String,String> = [:]\n\n  /// Additional build arguments.\n  public var buildArgs: [String] = []\n\n  /// Enable debug logging.\n  public var debug: Bool = false\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_CreateBuildResponse: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the build.\n  public var buildID: String = String()\n\n  /// Any additional metadata to be associated with the build.\n  public var metadata: Dictionary<String,String> = [:]\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_ClientStream: @unchecked Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the build.\n  public var buildID: String {\n    get {return _storage._buildID}\n    set {_uniqueStorage()._buildID = newValue}\n  }\n\n  /// The packet type.\n  public var packetType: OneOf_PacketType? {\n    get {return _storage._packetType}\n    set {_uniqueStorage()._packetType = newValue}\n  }\n\n  public var signal: Com_Apple_Container_Build_V1_Signal {\n    get {\n      if case .signal(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_Signal()\n    }\n    set {_uniqueStorage()._packetType = .signal(newValue)}\n  }\n\n  public var command: Com_Apple_Container_Build_V1_Run {\n    get {\n      if case .command(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_Run()\n    }\n    set {_uniqueStorage()._packetType = .command(newValue)}\n  }\n\n  public var buildTransfer: Com_Apple_Container_Build_V1_BuildTransfer {\n    get {\n      if case .buildTransfer(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_BuildTransfer()\n    }\n    set {_uniqueStorage()._packetType = .buildTransfer(newValue)}\n  }\n\n  public var imageTransfer: Com_Apple_Container_Build_V1_ImageTransfer {\n    get {\n      if case .imageTransfer(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_ImageTransfer()\n    }\n    set {_uniqueStorage()._packetType = .imageTransfer(newValue)}\n  }\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  /// The packet type.\n  public enum OneOf_PacketType: Equatable, Sendable {\n    case signal(Com_Apple_Container_Build_V1_Signal)\n    case command(Com_Apple_Container_Build_V1_Run)\n    case buildTransfer(Com_Apple_Container_Build_V1_BuildTransfer)\n    case imageTransfer(Com_Apple_Container_Build_V1_ImageTransfer)\n\n  }\n\n  public init() {}\n\n  fileprivate var _storage = _StorageClass.defaultInstance\n}\n\npublic struct Com_Apple_Container_Build_V1_Signal: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A POSIX signal to send to the build process.\n  /// Can be used for cancelling builds.\n  public var signal: Int32 = 0\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_Run: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the execution.\n  public var id: String = String()\n\n  /// The type of command to execute.\n  public var command: String = String()\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_RunComplete: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the execution.\n  public var id: String = String()\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_BuildTransfer: @unchecked Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the transfer.\n  public var id: String = String()\n\n  /// The direction for transferring data (either to the server or from the\n  /// server).\n  public var direction: Com_Apple_Container_Build_V1_TransferDirection = .into\n\n  /// The absolute path to the source from the server perspective.\n  public var source: String {\n    get {return _source ?? String()}\n    set {_source = newValue}\n  }\n  /// Returns true if `source` has been explicitly set.\n  public var hasSource: Bool {return self._source != nil}\n  /// Clears the value of `source`. Subsequent reads from it will return its default value.\n  public mutating func clearSource() {self._source = nil}\n\n  /// The absolute path for the destination from the server perspective.\n  public var destination: String {\n    get {return _destination ?? String()}\n    set {_destination = newValue}\n  }\n  /// Returns true if `destination` has been explicitly set.\n  public var hasDestination: Bool {return self._destination != nil}\n  /// Clears the value of `destination`. Subsequent reads from it will return its default value.\n  public mutating func clearDestination() {self._destination = nil}\n\n  /// The actual data bytes to be transferred.\n  public var data: Data = Data()\n\n  /// Signal to indicate that the transfer of data for the request has finished.\n  public var complete: Bool = false\n\n  /// Boolean to indicate if the content is a directory.\n  public var isDirectory: Bool = false\n\n  /// Metadata for the transfer.\n  public var metadata: Dictionary<String,String> = [:]\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n\n  fileprivate var _source: String? = nil\n  fileprivate var _destination: String? = nil\n}\n\npublic struct Com_Apple_Container_Build_V1_ImageTransfer: @unchecked Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the transfer.\n  public var id: String = String()\n\n  /// The direction for transferring data (either to the server or from the\n  /// server).\n  public var direction: Com_Apple_Container_Build_V1_TransferDirection = .into\n\n  /// The tag for the image.\n  public var tag: String = String()\n\n  /// The descriptor for the image content.\n  public var descriptor: Com_Apple_Container_Build_V1_Descriptor {\n    get {return _descriptor ?? Com_Apple_Container_Build_V1_Descriptor()}\n    set {_descriptor = newValue}\n  }\n  /// Returns true if `descriptor` has been explicitly set.\n  public var hasDescriptor: Bool {return self._descriptor != nil}\n  /// Clears the value of `descriptor`. Subsequent reads from it will return its default value.\n  public mutating func clearDescriptor() {self._descriptor = nil}\n\n  /// The actual data bytes to be transferred.\n  public var data: Data = Data()\n\n  /// Signal to indicate that the transfer of data for the request has finished.\n  public var complete: Bool = false\n\n  /// Metadata for the image.\n  public var metadata: Dictionary<String,String> = [:]\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n\n  fileprivate var _descriptor: Com_Apple_Container_Build_V1_Descriptor? = nil\n}\n\npublic struct Com_Apple_Container_Build_V1_ServerStream: @unchecked Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// A unique ID for the build.\n  public var buildID: String {\n    get {return _storage._buildID}\n    set {_uniqueStorage()._buildID = newValue}\n  }\n\n  /// The packet type.\n  public var packetType: OneOf_PacketType? {\n    get {return _storage._packetType}\n    set {_uniqueStorage()._packetType = newValue}\n  }\n\n  public var io: Com_Apple_Container_Build_V1_IO {\n    get {\n      if case .io(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_IO()\n    }\n    set {_uniqueStorage()._packetType = .io(newValue)}\n  }\n\n  public var buildError: Com_Apple_Container_Build_V1_BuildError {\n    get {\n      if case .buildError(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_BuildError()\n    }\n    set {_uniqueStorage()._packetType = .buildError(newValue)}\n  }\n\n  public var commandComplete: Com_Apple_Container_Build_V1_RunComplete {\n    get {\n      if case .commandComplete(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_RunComplete()\n    }\n    set {_uniqueStorage()._packetType = .commandComplete(newValue)}\n  }\n\n  public var buildTransfer: Com_Apple_Container_Build_V1_BuildTransfer {\n    get {\n      if case .buildTransfer(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_BuildTransfer()\n    }\n    set {_uniqueStorage()._packetType = .buildTransfer(newValue)}\n  }\n\n  public var imageTransfer: Com_Apple_Container_Build_V1_ImageTransfer {\n    get {\n      if case .imageTransfer(let v)? = _storage._packetType {return v}\n      return Com_Apple_Container_Build_V1_ImageTransfer()\n    }\n    set {_uniqueStorage()._packetType = .imageTransfer(newValue)}\n  }\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  /// The packet type.\n  public enum OneOf_PacketType: Equatable, Sendable {\n    case io(Com_Apple_Container_Build_V1_IO)\n    case buildError(Com_Apple_Container_Build_V1_BuildError)\n    case commandComplete(Com_Apple_Container_Build_V1_RunComplete)\n    case buildTransfer(Com_Apple_Container_Build_V1_BuildTransfer)\n    case imageTransfer(Com_Apple_Container_Build_V1_ImageTransfer)\n\n  }\n\n  public init() {}\n\n  fileprivate var _storage = _StorageClass.defaultInstance\n}\n\npublic struct Com_Apple_Container_Build_V1_IO: @unchecked Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// The type of IO.\n  public var type: Com_Apple_Container_Build_V1_Stdio = .stdin\n\n  /// The IO data bytes.\n  public var data: Data = Data()\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\npublic struct Com_Apple_Container_Build_V1_BuildError: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  /// The type of build error.\n  public var type: Com_Apple_Container_Build_V1_BuildErrorType = .buildFailed\n\n  /// Additional message for the build failure.\n  public var message: String = String()\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\n/// OCI Platform metadata.\npublic struct Com_Apple_Container_Build_V1_Platform: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  public var architecture: String = String()\n\n  public var os: String = String()\n\n  public var osVersion: String = String()\n\n  public var osFeatures: [String] = []\n\n  public var variant: String = String()\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n}\n\n/// OCI Descriptor metadata.\npublic struct Com_Apple_Container_Build_V1_Descriptor: Sendable {\n  // SwiftProtobuf.Message conformance is added in an extension below. See the\n  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for\n  // methods supported on all messages.\n\n  public var mediaType: String = String()\n\n  public var digest: String = String()\n\n  public var size: Int64 = 0\n\n  public var urls: [String] = []\n\n  public var annotations: Dictionary<String,String> = [:]\n\n  public var platform: Com_Apple_Container_Build_V1_Platform {\n    get {return _platform ?? Com_Apple_Container_Build_V1_Platform()}\n    set {_platform = newValue}\n  }\n  /// Returns true if `platform` has been explicitly set.\n  public var hasPlatform: Bool {return self._platform != nil}\n  /// Clears the value of `platform`. Subsequent reads from it will return its default value.\n  public mutating func clearPlatform() {self._platform = nil}\n\n  public var unknownFields = SwiftProtobuf.UnknownStorage()\n\n  public init() {}\n\n  fileprivate var _platform: Com_Apple_Container_Build_V1_Platform? = nil\n}\n\n// MARK: - Code below here is support for the SwiftProtobuf runtime.\n\nfileprivate let _protobuf_package = \"com.apple.container.build.v1\"\n\nextension Com_Apple_Container_Build_V1_TransferDirection: SwiftProtobuf._ProtoNameProviding {\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    0: .same(proto: \"INTO\"),\n    1: .same(proto: \"OUTOF\"),\n  ]\n}\n\nextension Com_Apple_Container_Build_V1_Stdio: SwiftProtobuf._ProtoNameProviding {\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    0: .same(proto: \"STDIN\"),\n    1: .same(proto: \"STDOUT\"),\n    2: .same(proto: \"STDERR\"),\n  ]\n}\n\nextension Com_Apple_Container_Build_V1_BuildErrorType: SwiftProtobuf._ProtoNameProviding {\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    0: .same(proto: \"BUILD_FAILED\"),\n    1: .same(proto: \"INTERNAL\"),\n  ]\n}\n\nextension Com_Apple_Container_Build_V1_InfoRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".InfoRequest\"\n  public static let _protobuf_nameMap = SwiftProtobuf._NameMap()\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    // Load everything into unknown fields\n    while try decoder.nextFieldNumber() != nil {}\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_InfoRequest, rhs: Com_Apple_Container_Build_V1_InfoRequest) -> Bool {\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_InfoResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".InfoResponse\"\n  public static let _protobuf_nameMap = SwiftProtobuf._NameMap()\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    // Load everything into unknown fields\n    while try decoder.nextFieldNumber() != nil {}\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_InfoResponse, rhs: Com_Apple_Container_Build_V1_InfoResponse) -> Bool {\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_CreateBuildRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".CreateBuildRequest\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .standard(proto: \"stage_name\"),\n    2: .same(proto: \"tag\"),\n    3: .same(proto: \"metadata\"),\n    4: .standard(proto: \"build_args\"),\n    5: .same(proto: \"debug\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.stageName) }()\n      case 2: try { try decoder.decodeSingularStringField(value: &self.tag) }()\n      case 3: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &self.metadata) }()\n      case 4: try { try decoder.decodeRepeatedStringField(value: &self.buildArgs) }()\n      case 5: try { try decoder.decodeSingularBoolField(value: &self.debug) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if !self.stageName.isEmpty {\n      try visitor.visitSingularStringField(value: self.stageName, fieldNumber: 1)\n    }\n    if !self.tag.isEmpty {\n      try visitor.visitSingularStringField(value: self.tag, fieldNumber: 2)\n    }\n    if !self.metadata.isEmpty {\n      try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: self.metadata, fieldNumber: 3)\n    }\n    if !self.buildArgs.isEmpty {\n      try visitor.visitRepeatedStringField(value: self.buildArgs, fieldNumber: 4)\n    }\n    if self.debug != false {\n      try visitor.visitSingularBoolField(value: self.debug, fieldNumber: 5)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_CreateBuildRequest, rhs: Com_Apple_Container_Build_V1_CreateBuildRequest) -> Bool {\n    if lhs.stageName != rhs.stageName {return false}\n    if lhs.tag != rhs.tag {return false}\n    if lhs.metadata != rhs.metadata {return false}\n    if lhs.buildArgs != rhs.buildArgs {return false}\n    if lhs.debug != rhs.debug {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_CreateBuildResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".CreateBuildResponse\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .standard(proto: \"build_id\"),\n    2: .same(proto: \"metadata\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.buildID) }()\n      case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &self.metadata) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if !self.buildID.isEmpty {\n      try visitor.visitSingularStringField(value: self.buildID, fieldNumber: 1)\n    }\n    if !self.metadata.isEmpty {\n      try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: self.metadata, fieldNumber: 2)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_CreateBuildResponse, rhs: Com_Apple_Container_Build_V1_CreateBuildResponse) -> Bool {\n    if lhs.buildID != rhs.buildID {return false}\n    if lhs.metadata != rhs.metadata {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_ClientStream: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".ClientStream\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .standard(proto: \"build_id\"),\n    2: .same(proto: \"signal\"),\n    3: .same(proto: \"command\"),\n    4: .standard(proto: \"build_transfer\"),\n    5: .standard(proto: \"image_transfer\"),\n  ]\n\n  fileprivate class _StorageClass {\n    var _buildID: String = String()\n    var _packetType: Com_Apple_Container_Build_V1_ClientStream.OneOf_PacketType?\n\n      // This property is used as the initial default value for new instances of the type.\n      // The type itself is protecting the reference to its storage via CoW semantics.\n      // This will force a copy to be made of this reference when the first mutation occurs;\n      // hence, it is safe to mark this as `nonisolated(unsafe)`.\n      static nonisolated(unsafe) let defaultInstance = _StorageClass()\n\n    private init() {}\n\n    init(copying source: _StorageClass) {\n      _buildID = source._buildID\n      _packetType = source._packetType\n    }\n  }\n\n  fileprivate mutating func _uniqueStorage() -> _StorageClass {\n    if !isKnownUniquelyReferenced(&_storage) {\n      _storage = _StorageClass(copying: _storage)\n    }\n    return _storage\n  }\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    _ = _uniqueStorage()\n    try withExtendedLifetime(_storage) { (_storage: _StorageClass) in\n      while let fieldNumber = try decoder.nextFieldNumber() {\n        // The use of inline closures is to circumvent an issue where the compiler\n        // allocates stack space for every case branch when no optimizations are\n        // enabled. https://github.com/apple/swift-protobuf/issues/1034\n        switch fieldNumber {\n        case 1: try { try decoder.decodeSingularStringField(value: &_storage._buildID) }()\n        case 2: try {\n          var v: Com_Apple_Container_Build_V1_Signal?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .signal(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .signal(v)\n          }\n        }()\n        case 3: try {\n          var v: Com_Apple_Container_Build_V1_Run?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .command(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .command(v)\n          }\n        }()\n        case 4: try {\n          var v: Com_Apple_Container_Build_V1_BuildTransfer?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .buildTransfer(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .buildTransfer(v)\n          }\n        }()\n        case 5: try {\n          var v: Com_Apple_Container_Build_V1_ImageTransfer?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .imageTransfer(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .imageTransfer(v)\n          }\n        }()\n        default: break\n        }\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    try withExtendedLifetime(_storage) { (_storage: _StorageClass) in\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every if/case branch local when no optimizations\n      // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and\n      // https://github.com/apple/swift-protobuf/issues/1182\n      if !_storage._buildID.isEmpty {\n        try visitor.visitSingularStringField(value: _storage._buildID, fieldNumber: 1)\n      }\n      switch _storage._packetType {\n      case .signal?: try {\n        guard case .signal(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 2)\n      }()\n      case .command?: try {\n        guard case .command(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 3)\n      }()\n      case .buildTransfer?: try {\n        guard case .buildTransfer(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 4)\n      }()\n      case .imageTransfer?: try {\n        guard case .imageTransfer(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 5)\n      }()\n      case nil: break\n      }\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_ClientStream, rhs: Com_Apple_Container_Build_V1_ClientStream) -> Bool {\n    if lhs._storage !== rhs._storage {\n      let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in\n        let _storage = _args.0\n        let rhs_storage = _args.1\n        if _storage._buildID != rhs_storage._buildID {return false}\n        if _storage._packetType != rhs_storage._packetType {return false}\n        return true\n      }\n      if !storagesAreEqual {return false}\n    }\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_Signal: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".Signal\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"signal\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularInt32Field(value: &self.signal) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if self.signal != 0 {\n      try visitor.visitSingularInt32Field(value: self.signal, fieldNumber: 1)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_Signal, rhs: Com_Apple_Container_Build_V1_Signal) -> Bool {\n    if lhs.signal != rhs.signal {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_Run: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".Run\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"id\"),\n    2: .same(proto: \"command\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.id) }()\n      case 2: try { try decoder.decodeSingularStringField(value: &self.command) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if !self.id.isEmpty {\n      try visitor.visitSingularStringField(value: self.id, fieldNumber: 1)\n    }\n    if !self.command.isEmpty {\n      try visitor.visitSingularStringField(value: self.command, fieldNumber: 2)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_Run, rhs: Com_Apple_Container_Build_V1_Run) -> Bool {\n    if lhs.id != rhs.id {return false}\n    if lhs.command != rhs.command {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_RunComplete: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".RunComplete\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"id\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.id) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if !self.id.isEmpty {\n      try visitor.visitSingularStringField(value: self.id, fieldNumber: 1)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_RunComplete, rhs: Com_Apple_Container_Build_V1_RunComplete) -> Bool {\n    if lhs.id != rhs.id {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_BuildTransfer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".BuildTransfer\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"id\"),\n    2: .same(proto: \"direction\"),\n    3: .same(proto: \"source\"),\n    4: .same(proto: \"destination\"),\n    5: .same(proto: \"data\"),\n    6: .same(proto: \"complete\"),\n    7: .standard(proto: \"is_directory\"),\n    8: .same(proto: \"metadata\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.id) }()\n      case 2: try { try decoder.decodeSingularEnumField(value: &self.direction) }()\n      case 3: try { try decoder.decodeSingularStringField(value: &self._source) }()\n      case 4: try { try decoder.decodeSingularStringField(value: &self._destination) }()\n      case 5: try { try decoder.decodeSingularBytesField(value: &self.data) }()\n      case 6: try { try decoder.decodeSingularBoolField(value: &self.complete) }()\n      case 7: try { try decoder.decodeSingularBoolField(value: &self.isDirectory) }()\n      case 8: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &self.metadata) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    // The use of inline closures is to circumvent an issue where the compiler\n    // allocates stack space for every if/case branch local when no optimizations\n    // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and\n    // https://github.com/apple/swift-protobuf/issues/1182\n    if !self.id.isEmpty {\n      try visitor.visitSingularStringField(value: self.id, fieldNumber: 1)\n    }\n    if self.direction != .into {\n      try visitor.visitSingularEnumField(value: self.direction, fieldNumber: 2)\n    }\n    try { if let v = self._source {\n      try visitor.visitSingularStringField(value: v, fieldNumber: 3)\n    } }()\n    try { if let v = self._destination {\n      try visitor.visitSingularStringField(value: v, fieldNumber: 4)\n    } }()\n    if !self.data.isEmpty {\n      try visitor.visitSingularBytesField(value: self.data, fieldNumber: 5)\n    }\n    if self.complete != false {\n      try visitor.visitSingularBoolField(value: self.complete, fieldNumber: 6)\n    }\n    if self.isDirectory != false {\n      try visitor.visitSingularBoolField(value: self.isDirectory, fieldNumber: 7)\n    }\n    if !self.metadata.isEmpty {\n      try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: self.metadata, fieldNumber: 8)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_BuildTransfer, rhs: Com_Apple_Container_Build_V1_BuildTransfer) -> Bool {\n    if lhs.id != rhs.id {return false}\n    if lhs.direction != rhs.direction {return false}\n    if lhs._source != rhs._source {return false}\n    if lhs._destination != rhs._destination {return false}\n    if lhs.data != rhs.data {return false}\n    if lhs.complete != rhs.complete {return false}\n    if lhs.isDirectory != rhs.isDirectory {return false}\n    if lhs.metadata != rhs.metadata {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_ImageTransfer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".ImageTransfer\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"id\"),\n    2: .same(proto: \"direction\"),\n    3: .same(proto: \"tag\"),\n    4: .same(proto: \"descriptor\"),\n    5: .same(proto: \"data\"),\n    6: .same(proto: \"complete\"),\n    7: .same(proto: \"metadata\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.id) }()\n      case 2: try { try decoder.decodeSingularEnumField(value: &self.direction) }()\n      case 3: try { try decoder.decodeSingularStringField(value: &self.tag) }()\n      case 4: try { try decoder.decodeSingularMessageField(value: &self._descriptor) }()\n      case 5: try { try decoder.decodeSingularBytesField(value: &self.data) }()\n      case 6: try { try decoder.decodeSingularBoolField(value: &self.complete) }()\n      case 7: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &self.metadata) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    // The use of inline closures is to circumvent an issue where the compiler\n    // allocates stack space for every if/case branch local when no optimizations\n    // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and\n    // https://github.com/apple/swift-protobuf/issues/1182\n    if !self.id.isEmpty {\n      try visitor.visitSingularStringField(value: self.id, fieldNumber: 1)\n    }\n    if self.direction != .into {\n      try visitor.visitSingularEnumField(value: self.direction, fieldNumber: 2)\n    }\n    if !self.tag.isEmpty {\n      try visitor.visitSingularStringField(value: self.tag, fieldNumber: 3)\n    }\n    try { if let v = self._descriptor {\n      try visitor.visitSingularMessageField(value: v, fieldNumber: 4)\n    } }()\n    if !self.data.isEmpty {\n      try visitor.visitSingularBytesField(value: self.data, fieldNumber: 5)\n    }\n    if self.complete != false {\n      try visitor.visitSingularBoolField(value: self.complete, fieldNumber: 6)\n    }\n    if !self.metadata.isEmpty {\n      try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: self.metadata, fieldNumber: 7)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_ImageTransfer, rhs: Com_Apple_Container_Build_V1_ImageTransfer) -> Bool {\n    if lhs.id != rhs.id {return false}\n    if lhs.direction != rhs.direction {return false}\n    if lhs.tag != rhs.tag {return false}\n    if lhs._descriptor != rhs._descriptor {return false}\n    if lhs.data != rhs.data {return false}\n    if lhs.complete != rhs.complete {return false}\n    if lhs.metadata != rhs.metadata {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_ServerStream: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".ServerStream\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .standard(proto: \"build_id\"),\n    2: .same(proto: \"io\"),\n    3: .standard(proto: \"build_error\"),\n    4: .standard(proto: \"command_complete\"),\n    5: .standard(proto: \"build_transfer\"),\n    6: .standard(proto: \"image_transfer\"),\n  ]\n\n  fileprivate class _StorageClass {\n    var _buildID: String = String()\n    var _packetType: Com_Apple_Container_Build_V1_ServerStream.OneOf_PacketType?\n\n      // This property is used as the initial default value for new instances of the type.\n      // The type itself is protecting the reference to its storage via CoW semantics.\n      // This will force a copy to be made of this reference when the first mutation occurs;\n      // hence, it is safe to mark this as `nonisolated(unsafe)`.\n      static nonisolated(unsafe) let defaultInstance = _StorageClass()\n\n    private init() {}\n\n    init(copying source: _StorageClass) {\n      _buildID = source._buildID\n      _packetType = source._packetType\n    }\n  }\n\n  fileprivate mutating func _uniqueStorage() -> _StorageClass {\n    if !isKnownUniquelyReferenced(&_storage) {\n      _storage = _StorageClass(copying: _storage)\n    }\n    return _storage\n  }\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    _ = _uniqueStorage()\n    try withExtendedLifetime(_storage) { (_storage: _StorageClass) in\n      while let fieldNumber = try decoder.nextFieldNumber() {\n        // The use of inline closures is to circumvent an issue where the compiler\n        // allocates stack space for every case branch when no optimizations are\n        // enabled. https://github.com/apple/swift-protobuf/issues/1034\n        switch fieldNumber {\n        case 1: try { try decoder.decodeSingularStringField(value: &_storage._buildID) }()\n        case 2: try {\n          var v: Com_Apple_Container_Build_V1_IO?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .io(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .io(v)\n          }\n        }()\n        case 3: try {\n          var v: Com_Apple_Container_Build_V1_BuildError?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .buildError(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .buildError(v)\n          }\n        }()\n        case 4: try {\n          var v: Com_Apple_Container_Build_V1_RunComplete?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .commandComplete(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .commandComplete(v)\n          }\n        }()\n        case 5: try {\n          var v: Com_Apple_Container_Build_V1_BuildTransfer?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .buildTransfer(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .buildTransfer(v)\n          }\n        }()\n        case 6: try {\n          var v: Com_Apple_Container_Build_V1_ImageTransfer?\n          var hadOneofValue = false\n          if let current = _storage._packetType {\n            hadOneofValue = true\n            if case .imageTransfer(let m) = current {v = m}\n          }\n          try decoder.decodeSingularMessageField(value: &v)\n          if let v = v {\n            if hadOneofValue {try decoder.handleConflictingOneOf()}\n            _storage._packetType = .imageTransfer(v)\n          }\n        }()\n        default: break\n        }\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    try withExtendedLifetime(_storage) { (_storage: _StorageClass) in\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every if/case branch local when no optimizations\n      // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and\n      // https://github.com/apple/swift-protobuf/issues/1182\n      if !_storage._buildID.isEmpty {\n        try visitor.visitSingularStringField(value: _storage._buildID, fieldNumber: 1)\n      }\n      switch _storage._packetType {\n      case .io?: try {\n        guard case .io(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 2)\n      }()\n      case .buildError?: try {\n        guard case .buildError(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 3)\n      }()\n      case .commandComplete?: try {\n        guard case .commandComplete(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 4)\n      }()\n      case .buildTransfer?: try {\n        guard case .buildTransfer(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 5)\n      }()\n      case .imageTransfer?: try {\n        guard case .imageTransfer(let v)? = _storage._packetType else { preconditionFailure() }\n        try visitor.visitSingularMessageField(value: v, fieldNumber: 6)\n      }()\n      case nil: break\n      }\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_ServerStream, rhs: Com_Apple_Container_Build_V1_ServerStream) -> Bool {\n    if lhs._storage !== rhs._storage {\n      let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in\n        let _storage = _args.0\n        let rhs_storage = _args.1\n        if _storage._buildID != rhs_storage._buildID {return false}\n        if _storage._packetType != rhs_storage._packetType {return false}\n        return true\n      }\n      if !storagesAreEqual {return false}\n    }\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_IO: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".IO\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"type\"),\n    2: .same(proto: \"data\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }()\n      case 2: try { try decoder.decodeSingularBytesField(value: &self.data) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if self.type != .stdin {\n      try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1)\n    }\n    if !self.data.isEmpty {\n      try visitor.visitSingularBytesField(value: self.data, fieldNumber: 2)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_IO, rhs: Com_Apple_Container_Build_V1_IO) -> Bool {\n    if lhs.type != rhs.type {return false}\n    if lhs.data != rhs.data {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_BuildError: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".BuildError\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"type\"),\n    2: .same(proto: \"message\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }()\n      case 2: try { try decoder.decodeSingularStringField(value: &self.message) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if self.type != .buildFailed {\n      try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1)\n    }\n    if !self.message.isEmpty {\n      try visitor.visitSingularStringField(value: self.message, fieldNumber: 2)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_BuildError, rhs: Com_Apple_Container_Build_V1_BuildError) -> Bool {\n    if lhs.type != rhs.type {return false}\n    if lhs.message != rhs.message {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_Platform: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".Platform\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .same(proto: \"architecture\"),\n    2: .same(proto: \"os\"),\n    3: .standard(proto: \"os_version\"),\n    4: .standard(proto: \"os_features\"),\n    5: .same(proto: \"variant\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.architecture) }()\n      case 2: try { try decoder.decodeSingularStringField(value: &self.os) }()\n      case 3: try { try decoder.decodeSingularStringField(value: &self.osVersion) }()\n      case 4: try { try decoder.decodeRepeatedStringField(value: &self.osFeatures) }()\n      case 5: try { try decoder.decodeSingularStringField(value: &self.variant) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    if !self.architecture.isEmpty {\n      try visitor.visitSingularStringField(value: self.architecture, fieldNumber: 1)\n    }\n    if !self.os.isEmpty {\n      try visitor.visitSingularStringField(value: self.os, fieldNumber: 2)\n    }\n    if !self.osVersion.isEmpty {\n      try visitor.visitSingularStringField(value: self.osVersion, fieldNumber: 3)\n    }\n    if !self.osFeatures.isEmpty {\n      try visitor.visitRepeatedStringField(value: self.osFeatures, fieldNumber: 4)\n    }\n    if !self.variant.isEmpty {\n      try visitor.visitSingularStringField(value: self.variant, fieldNumber: 5)\n    }\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_Platform, rhs: Com_Apple_Container_Build_V1_Platform) -> Bool {\n    if lhs.architecture != rhs.architecture {return false}\n    if lhs.os != rhs.os {return false}\n    if lhs.osVersion != rhs.osVersion {return false}\n    if lhs.osFeatures != rhs.osFeatures {return false}\n    if lhs.variant != rhs.variant {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n\nextension Com_Apple_Container_Build_V1_Descriptor: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {\n  public static let protoMessageName: String = _protobuf_package + \".Descriptor\"\n  public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [\n    1: .standard(proto: \"media_type\"),\n    2: .same(proto: \"digest\"),\n    3: .same(proto: \"size\"),\n    4: .same(proto: \"urls\"),\n    5: .same(proto: \"annotations\"),\n    6: .same(proto: \"platform\"),\n  ]\n\n  public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {\n    while let fieldNumber = try decoder.nextFieldNumber() {\n      // The use of inline closures is to circumvent an issue where the compiler\n      // allocates stack space for every case branch when no optimizations are\n      // enabled. https://github.com/apple/swift-protobuf/issues/1034\n      switch fieldNumber {\n      case 1: try { try decoder.decodeSingularStringField(value: &self.mediaType) }()\n      case 2: try { try decoder.decodeSingularStringField(value: &self.digest) }()\n      case 3: try { try decoder.decodeSingularInt64Field(value: &self.size) }()\n      case 4: try { try decoder.decodeRepeatedStringField(value: &self.urls) }()\n      case 5: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &self.annotations) }()\n      case 6: try { try decoder.decodeSingularMessageField(value: &self._platform) }()\n      default: break\n      }\n    }\n  }\n\n  public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {\n    // The use of inline closures is to circumvent an issue where the compiler\n    // allocates stack space for every if/case branch local when no optimizations\n    // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and\n    // https://github.com/apple/swift-protobuf/issues/1182\n    if !self.mediaType.isEmpty {\n      try visitor.visitSingularStringField(value: self.mediaType, fieldNumber: 1)\n    }\n    if !self.digest.isEmpty {\n      try visitor.visitSingularStringField(value: self.digest, fieldNumber: 2)\n    }\n    if self.size != 0 {\n      try visitor.visitSingularInt64Field(value: self.size, fieldNumber: 3)\n    }\n    if !self.urls.isEmpty {\n      try visitor.visitRepeatedStringField(value: self.urls, fieldNumber: 4)\n    }\n    if !self.annotations.isEmpty {\n      try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: self.annotations, fieldNumber: 5)\n    }\n    try { if let v = self._platform {\n      try visitor.visitSingularMessageField(value: v, fieldNumber: 6)\n    } }()\n    try unknownFields.traverse(visitor: &visitor)\n  }\n\n  public static func ==(lhs: Com_Apple_Container_Build_V1_Descriptor, rhs: Com_Apple_Container_Build_V1_Descriptor) -> Bool {\n    if lhs.mediaType != rhs.mediaType {return false}\n    if lhs.digest != rhs.digest {return false}\n    if lhs.size != rhs.size {return false}\n    if lhs.urls != rhs.urls {return false}\n    if lhs.annotations != rhs.annotations {return false}\n    if lhs._platform != rhs._platform {return false}\n    if lhs.unknownFields != rhs.unknownFields {return false}\n    return true\n  }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/Builder.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport GRPC\nimport NIO\nimport NIOHPACK\nimport NIOHTTP2\n\npublic struct Builder: Sendable {\n    public static let builderContainerId = \"buildkit\"\n\n    let client: BuilderClientProtocol\n    let clientAsync: BuilderClientAsyncProtocol\n    let group: EventLoopGroup\n    let builderShimSocket: FileHandle\n    let channel: GRPCChannel\n\n    public init(socket: FileHandle, group: EventLoopGroup) throws {\n        try socket.setSendBufSize(4 << 20)\n        try socket.setRecvBufSize(2 << 20)\n        var config = ClientConnection.Configuration.default(\n            target: .connectedSocket(socket.fileDescriptor),\n            eventLoopGroup: group\n        )\n        config.connectionIdleTimeout = TimeAmount(.seconds(600))\n        config.connectionKeepalive = .init(\n            interval: TimeAmount(.seconds(600)),\n            timeout: TimeAmount(.seconds(500)),\n            permitWithoutCalls: true\n        )\n        config.connectionBackoff = .init(\n            initialBackoff: TimeInterval(1),\n            maximumBackoff: TimeInterval(10)\n        )\n        config.callStartBehavior = .fastFailure\n        config.httpMaxFrameSize = 8 << 10\n        config.maximumReceiveMessageLength = 512 << 20\n        config.httpTargetWindowSize = 16 << 10\n\n        let channel = ClientConnection(configuration: config)\n        self.channel = channel\n        self.clientAsync = BuilderClientAsync(channel: channel)\n        self.client = BuilderClient(channel: channel)\n        self.group = group\n        self.builderShimSocket = socket\n    }\n\n    public func info() throws -> InfoResponse {\n        let resp = self.client.info(InfoRequest(), callOptions: CallOptions())\n        return try resp.response.wait()\n    }\n\n    public func info() async throws -> InfoResponse {\n        let opts = CallOptions(timeLimit: .timeout(.seconds(30)))\n        return try await self.clientAsync.info(InfoRequest(), callOptions: opts)\n    }\n\n    // TODO\n    // - Symlinks in build context dir\n    // - cache-to, cache-from\n    // - output (other than the default OCI image output, e.g., local, tar, Docker)\n    public func build(_ config: BuildConfig) async throws {\n        var continuation: AsyncStream<ClientStream>.Continuation?\n        let reqStream = AsyncStream<ClientStream> { (cont: AsyncStream<ClientStream>.Continuation) in\n            continuation = cont\n        }\n        guard let continuation else {\n            throw Error.invalidContinuation\n        }\n\n        defer {\n            continuation.finish()\n        }\n\n        if let terminal = config.terminal {\n            Task {\n                let winchHandler = AsyncSignalHandler.create(notify: [SIGWINCH])\n                let setWinch = { (rows: UInt16, cols: UInt16) in\n                    var winch = ClientStream()\n                    winch.command = .init()\n                    if let cmdString = try TerminalCommand(rows: rows, cols: cols).json() {\n                        winch.command.command = cmdString\n                        continuation.yield(winch)\n                    }\n                }\n                let size = try terminal.size\n                var width = size.width\n                var height = size.height\n                try setWinch(height, width)\n\n                for await _ in winchHandler.signals {\n                    let size = try terminal.size\n                    let cols = size.width\n                    let rows = size.height\n                    if cols != width || rows != height {\n                        width = cols\n                        height = rows\n                        try setWinch(height, width)\n                    }\n                }\n            }\n        }\n\n        let respStream = self.clientAsync.performBuild(reqStream, callOptions: try CallOptions(config))\n        let pipeline = try await BuildPipeline(config)\n        do {\n            try await pipeline.run(sender: continuation, receiver: respStream)\n        } catch Error.buildComplete {\n            _ = channel.close()\n            try await group.shutdownGracefully()\n            return\n        }\n    }\n\n    public struct BuildExport: Sendable {\n        public let type: String\n        public var destination: URL?\n        public let additionalFields: [String: String]\n        public let rawValue: String\n\n        public init(type: String, destination: URL?, additionalFields: [String: String], rawValue: String) {\n            self.type = type\n            self.destination = destination\n            self.additionalFields = additionalFields\n            self.rawValue = rawValue\n        }\n\n        public init(from input: String) throws {\n            var typeValue: String?\n            var destinationValue: URL?\n            var additionalFields: [String: String] = [:]\n\n            let pairs = input.components(separatedBy: \",\")\n            for pair in pairs {\n                let parts = pair.components(separatedBy: \"=\")\n                guard parts.count == 2 else { continue }\n\n                let key = parts[0].trimmingCharacters(in: .whitespaces)\n                let value = parts[1].trimmingCharacters(in: .whitespaces)\n\n                switch key {\n                case \"type\":\n                    typeValue = value\n                case \"dest\":\n                    destinationValue = try Self.resolveDestination(dest: value)\n                default:\n                    additionalFields[key] = value\n                }\n            }\n\n            guard let type = typeValue else {\n                throw Builder.Error.invalidExport(input, \"type field is required\")\n            }\n\n            switch type {\n            case \"oci\":\n                break\n            case \"tar\":\n                if destinationValue == nil {\n                    throw Builder.Error.invalidExport(input, \"dest field is required\")\n                }\n            case \"local\":\n                if destinationValue == nil {\n                    throw Builder.Error.invalidExport(input, \"dest field is required\")\n                }\n            default:\n                throw Builder.Error.invalidExport(input, \"unsupported output type\")\n            }\n\n            self.init(type: type, destination: destinationValue, additionalFields: additionalFields, rawValue: input)\n        }\n\n        public var stringValue: String {\n            get throws {\n                var components = [\"type=\\(type)\"]\n\n                switch type {\n                case \"oci\", \"tar\", \"local\":\n                    break  // ignore destination\n                default:\n                    throw Builder.Error.invalidExport(rawValue, \"unsupported output type\")\n                }\n\n                for (key, value) in additionalFields {\n                    components.append(\"\\(key)=\\(value)\")\n                }\n\n                return components.joined(separator: \",\")\n            }\n        }\n\n        static func resolveDestination(dest: String) throws -> URL {\n            let destination = URL(fileURLWithPath: dest)\n            let fileManager = FileManager.default\n\n            if fileManager.fileExists(atPath: destination.path) {\n                let resourceValues = try destination.resourceValues(forKeys: [.isDirectoryKey])\n                let isDir = resourceValues.isDirectory\n                if isDir != nil && isDir == false {\n                    throw Builder.Error.invalidExport(dest, \"dest path already exists\")\n                }\n\n                var finalDestination = destination.appendingPathComponent(\"out.tar\")\n                var index = 1\n                while fileManager.fileExists(atPath: finalDestination.path) {\n                    let path = \"out.tar.\\(index)\"\n                    finalDestination = destination.appendingPathComponent(path)\n                    index += 1\n                }\n                return finalDestination\n            } else {\n                let parentDirectory = destination.deletingLastPathComponent()\n                try? fileManager.createDirectory(at: parentDirectory, withIntermediateDirectories: true, attributes: nil)\n            }\n\n            return destination\n        }\n    }\n\n    public struct BuildConfig: Sendable {\n        public let buildID: String\n        public let contentStore: ContentStore\n        public let buildArgs: [String]\n        public let secrets: [String: Data]\n        public let contextDir: String\n        public let dockerfile: Data\n        public let hiddenDockerDir: String?\n        public let labels: [String]\n        public let noCache: Bool\n        public let platforms: [Platform]\n        public let terminal: Terminal?\n        public let tags: [String]\n        public let target: String\n        public let quiet: Bool\n        public let exports: [BuildExport]\n        public let cacheIn: [String]\n        public let cacheOut: [String]\n        public let pull: Bool\n\n        public init(\n            buildID: String,\n            contentStore: ContentStore,\n            buildArgs: [String],\n            secrets: [String: Data],\n            contextDir: String,\n            dockerfile: Data,\n            hiddenDockerDir: String?,\n            labels: [String],\n            noCache: Bool,\n            platforms: [Platform],\n            terminal: Terminal?,\n            tags: [String],\n            target: String,\n            quiet: Bool,\n            exports: [BuildExport],\n            cacheIn: [String],\n            cacheOut: [String],\n            pull: Bool\n        ) {\n            self.buildID = buildID\n            self.contentStore = contentStore\n            self.buildArgs = buildArgs\n            self.secrets = secrets\n            self.contextDir = contextDir\n            self.dockerfile = dockerfile\n            self.hiddenDockerDir = hiddenDockerDir\n            self.labels = labels\n            self.noCache = noCache\n            self.platforms = platforms\n            self.terminal = terminal\n            self.tags = tags\n            self.target = target\n            self.quiet = quiet\n            self.exports = exports\n            self.cacheIn = cacheIn\n            self.cacheOut = cacheOut\n            self.pull = pull\n        }\n    }\n}\n\nextension Builder {\n    enum Error: Swift.Error, CustomStringConvertible {\n        case invalidContinuation\n        case buildComplete\n        case invalidExport(String, String)\n\n        var description: String {\n            switch self {\n            case .invalidContinuation:\n                return \"continuation could not created\"\n            case .buildComplete:\n                return \"build completed\"\n            case .invalidExport(let exp, let reason):\n                return \"export entry \\(exp) is invalid: \\(reason)\"\n            }\n        }\n    }\n}\n\nextension CallOptions {\n    public init(_ config: Builder.BuildConfig) throws {\n        var headers: [(String, String)] = [\n            (\"build-id\", config.buildID),\n            (\"context\", URL(filePath: config.contextDir).path(percentEncoded: false)),\n            (\"dockerfile\", config.dockerfile.base64EncodedString()),\n            (\"progress\", config.terminal != nil ? \"tty\" : \"plain\"),\n            (\"target\", config.target),\n        ]\n        if let hiddenDockerDir = config.hiddenDockerDir {\n            headers.append((\"hidden-docker-dir\", hiddenDockerDir))\n        }\n        for tag in config.tags {\n            headers.append((\"tag\", tag))\n        }\n        for platform in config.platforms {\n            headers.append((\"platforms\", platform.description))\n        }\n        if config.noCache {\n            headers.append((\"no-cache\", \"\"))\n        }\n        for label in config.labels {\n            headers.append((\"labels\", label))\n        }\n        for buildArg in config.buildArgs {\n            headers.append((\"build-args\", buildArg))\n        }\n        for (id, data) in config.secrets {\n            headers.append((\"secrets\", id + \"=\" + data.base64EncodedString()))\n        }\n        for output in config.exports {\n            headers.append((\"outputs\", try output.stringValue))\n        }\n        for cacheIn in config.cacheIn {\n            headers.append((\"cache-in\", cacheIn))\n        }\n        for cacheOut in config.cacheOut {\n            headers.append((\"cache-out\", cacheOut))\n        }\n\n        self.init(\n            customMetadata: HPACKHeaders(headers)\n        )\n    }\n}\n\nextension FileHandle {\n    @discardableResult\n    func setSendBufSize(_ bytes: Int) throws -> Int {\n        try setSockOpt(\n            level: SOL_SOCKET,\n            name: SO_SNDBUF,\n            value: bytes)\n        return bytes\n    }\n\n    @discardableResult\n    func setRecvBufSize(_ bytes: Int) throws -> Int {\n        try setSockOpt(\n            level: SOL_SOCKET,\n            name: SO_RCVBUF,\n            value: bytes)\n        return bytes\n    }\n\n    private func setSockOpt(level: Int32, name: Int32, value: Int) throws {\n        var v = Int32(value)\n        let res = withUnsafePointer(to: &v) { ptr -> Int32 in\n            ptr.withMemoryRebound(\n                to: UInt8.self,\n                capacity: MemoryLayout<Int32>.size\n            ) { raw in\n                #if canImport(Darwin)\n                return setsockopt(\n                    self.fileDescriptor,\n                    level, name,\n                    raw,\n                    socklen_t(MemoryLayout<Int32>.size))\n                #else\n                fatalError(\"unsupported platform\")\n                #endif\n            }\n        }\n        if res == -1 {\n            throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EPERM)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/Globber.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\npublic class Globber {\n    let input: URL\n    var results: Set<URL> = .init()\n\n    public init(_ input: URL) {\n        self.input = input\n    }\n\n    public func match(_ pattern: String) throws {\n        let adjustedPattern =\n            pattern\n            .replacingOccurrences(of: #\"^\\./(?=.)\"#, with: \"\", options: .regularExpression)\n            .replacingOccurrences(of: \"^\\\\.[/]?$\", with: \"*\", options: .regularExpression)\n            .replacingOccurrences(of: \"\\\\*{2,}[/]\", with: \"*/**/\", options: .regularExpression)\n            .replacingOccurrences(of: \"[/]\\\\*{2,}([^/])\", with: \"/**/*$1\", options: .regularExpression)\n            .replacingOccurrences(of: \"^\\\\*{2,}([^/])\", with: \"**/*$1\", options: .regularExpression)\n\n        for child in input.children {\n            try self.match(input: child, components: adjustedPattern.split(separator: \"/\").map(String.init))\n        }\n    }\n\n    private func match(input: URL, components: [String]) throws {\n        if components.isEmpty {\n            var dir = input.standardizedFileURL\n\n            while dir != self.input.standardizedFileURL {\n                results.insert(dir)\n                guard dir.pathComponents.count > 1 else { break }\n                dir.deleteLastPathComponent()\n            }\n            return input.childrenRecursive.forEach { results.insert($0) }\n        }\n\n        let head = components.first ?? \"\"\n        let tail = components.tail\n\n        if head == \"**\" {\n            var tail: [String] = tail\n            while tail.first == \"**\" {\n                tail = tail.tail\n            }\n            try self.match(input: input, components: tail)\n            for child in input.children {\n                try self.match(input: child, components: components)\n            }\n            return\n        }\n\n        if try glob(input.lastPathComponent, head) {\n            try self.match(input: input, components: tail)\n\n            for child in input.children where try glob(child.lastPathComponent, tail.first ?? \"\") {\n                try self.match(input: child, components: tail)\n            }\n            return\n        }\n    }\n\n    func glob(_ input: String, _ pattern: String) throws -> Bool {\n        let regexPattern =\n            \"^\"\n            + NSRegularExpression.escapedPattern(for: pattern)\n            .replacingOccurrences(of: \"\\\\*\", with: \"[^/]*\")\n            .replacingOccurrences(of: \"\\\\?\", with: \"[^/]\")\n            .replacingOccurrences(of: \"[\\\\^\", with: \"[^\")\n            .replacingOccurrences(of: \"\\\\[\", with: \"[\")\n            .replacingOccurrences(of: \"\\\\]\", with: \"]\") + \"$\"\n\n        // validate the regex pattern created\n        let _ = try Regex(regexPattern)\n        return input.range(of: regexPattern, options: .regularExpression) != nil\n    }\n}\n\nextension URL {\n    var children: [URL] {\n\n        (try? FileManager.default.contentsOfDirectory(at: self, includingPropertiesForKeys: nil))\n            ?? []\n    }\n\n    var childrenRecursive: [URL] {\n        var results: [URL] = []\n        if let enumerator = FileManager.default.enumerator(\n            at: self, includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey])\n        {\n            while let child = enumerator.nextObject() as? URL {\n                results.append(child)\n            }\n        }\n        return [self] + results\n    }\n}\n\nextension [String] {\n    var tail: [String] {\n        if self.count <= 1 {\n            return []\n        }\n        return Array(self.dropFirst())\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/TerminalCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nstruct TerminalCommand: Codable {\n    let commandType: String\n    let code: String\n    let rows: UInt16\n    let cols: UInt16\n\n    enum CodingKeys: String, CodingKey {\n        case commandType = \"command_type\"\n        case code\n        case rows\n        case cols\n    }\n\n    init(rows: UInt16, cols: UInt16) {\n        self.commandType = \"terminal\"\n        self.code = \"winch\"\n        self.rows = rows\n        self.cols = cols\n    }\n\n    init() {\n        self.commandType = \"terminal\"\n        self.code = \"ack\"\n        self.rows = 0\n        self.cols = 0\n    }\n\n    func json() throws -> String? {\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(self)\n        return data.base64EncodedString().trimmingCharacters(in: CharacterSet(charactersIn: \"=\"))\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerBuild/URL+Extensions.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension String {\n    fileprivate var fs_cleaned: String {\n        var value = self\n\n        if value.hasPrefix(\"file://\") {\n            value.removeFirst(\"file://\".count)\n        }\n\n        if value.count > 1 && value.last == \"/\" {\n            value.removeLast()\n        }\n\n        return value.removingPercentEncoding ?? value\n    }\n\n    fileprivate var fs_components: [String] {\n        var parts: [String] = []\n        for segment in self.split(separator: \"/\", omittingEmptySubsequences: true) {\n            switch segment {\n            case \".\":\n                continue\n            case \"..\":\n                if !parts.isEmpty { parts.removeLast() }\n            default:\n                parts.append(String(segment))\n            }\n        }\n        return parts\n    }\n\n    fileprivate var fs_isAbsolute: Bool { first == \"/\" }\n}\n\nextension URL {\n    var cleanPath: String {\n        self.path.fs_cleaned\n    }\n\n    func parentOf(_ url: URL) -> Bool {\n        let parentPath = self.absoluteURL.cleanPath\n        let childPath = url.absoluteURL.cleanPath\n\n        guard parentPath.fs_isAbsolute else {\n            return true\n        }\n\n        let parentParts = parentPath.fs_components\n        let childParts = childPath.fs_components\n\n        guard parentParts.count <= childParts.count else { return false }\n        return zip(parentParts, childParts).allSatisfy { $0 == $1 }\n    }\n\n    func relativeChildPath(to context: URL) throws -> String {\n        guard context.parentOf(self) else {\n            throw BuildFSSync.Error.pathIsNotChild(cleanPath, context.cleanPath)\n        }\n\n        let ctxParts = context.cleanPath.fs_components\n        let selfParts = cleanPath.fs_components\n\n        return selfParts.dropFirst(ctxParts.count).joined(separator: \"/\")\n    }\n\n    func relativePathFrom(from base: URL) -> String {\n        let destParts = cleanPath.fs_components\n        let baseParts = base.cleanPath.fs_components\n\n        let common = zip(destParts, baseParts).prefix { $0 == $1 }.count\n        guard common > 0 else { return cleanPath }\n\n        let ups = Array(repeating: \"..\", count: baseParts.count - common)\n        let remainder = destParts.dropFirst(common)\n        return (ups + remainder).joined(separator: \"/\")\n    }\n\n    func zeroCopyReader(\n        chunk: Int = 1024 * 1024,\n        buffer: AsyncStream<Data>.Continuation.BufferingPolicy = .unbounded\n    ) throws -> AsyncStream<Data> {\n\n        let path = self.cleanPath\n        let fd = open(path, O_RDONLY | O_NONBLOCK)\n        guard fd >= 0 else { throw POSIXError.fromErrno() }\n\n        let channel = DispatchIO(\n            type: .stream,\n            fileDescriptor: fd,\n            queue: .global(qos: .userInitiated)\n        ) { errno in\n            close(fd)\n        }\n\n        channel.setLimit(highWater: chunk)\n        return AsyncStream(bufferingPolicy: buffer) { continuation in\n\n            channel.read(\n                offset: 0, length: Int.max,\n                queue: .global(qos: .userInitiated)\n            ) { done, ddata, err in\n                if err != 0 {\n                    continuation.finish()\n                    return\n                }\n\n                if let ddata, ddata.count > -1 {\n                    let data = Data(ddata)\n\n                    switch continuation.yield(data) {\n                    case .terminated:\n                        channel.close(flags: .stop)\n                    default: break\n                    }\n                }\n\n                if done {\n                    channel.close(flags: .stop)\n                    continuation.finish()\n                }\n            }\n        }\n    }\n\n    func bufferedCopyReader(chunkSize: Int = 4 * 1024 * 1024) throws -> BufferedCopyReader {\n        try BufferedCopyReader(url: self, chunkSize: chunkSize)\n    }\n}\n\n/// A synchronous buffered reader that reads one chunk at a time from a file\n/// Uses a configurable buffer size (default 4MB) and only reads when nextChunk() is called\n/// Implements AsyncSequence for use with `for await` loops\npublic final class BufferedCopyReader: AsyncSequence {\n    public typealias Element = Data\n    public typealias AsyncIterator = BufferedCopyReaderIterator\n\n    private let inputStream: InputStream\n    private let chunkSize: Int\n    private var isFinished: Bool = false\n    private let reusableBuffer: UnsafeMutablePointer<UInt8>\n\n    /// Initialize a buffered copy reader for the given URL\n    /// - Parameters:\n    ///   - url: The file URL to read from\n    ///   - chunkSize: Size of each chunk to read (default: 4MB)\n    public init(url: URL, chunkSize: Int = 4 * 1024 * 1024) throws {\n        guard let stream = InputStream(url: url) else {\n            throw CocoaError(.fileReadNoSuchFile)\n        }\n        self.inputStream = stream\n        self.chunkSize = chunkSize\n        self.reusableBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: chunkSize)\n        self.inputStream.open()\n    }\n\n    deinit {\n        inputStream.close()\n        reusableBuffer.deallocate()\n    }\n\n    /// Create an async iterator for this sequence\n    public func makeAsyncIterator() -> BufferedCopyReaderIterator {\n        BufferedCopyReaderIterator(reader: self)\n    }\n\n    /// Read the next chunk of data from the file\n    /// - Returns: Data chunk, or nil if end of file reached\n    /// - Throws: Any file reading errors\n    public func nextChunk() throws -> Data? {\n        guard !isFinished else { return nil }\n\n        // Read directly into our reusable buffer\n        let bytesRead = inputStream.read(reusableBuffer, maxLength: chunkSize)\n\n        // Check for errors\n        if bytesRead < 0 {\n            if let error = inputStream.streamError {\n                throw error\n            }\n            throw CocoaError(.fileReadUnknown)\n        }\n\n        // If we read no data, we've reached the end\n        if bytesRead == 0 {\n            isFinished = true\n            return nil\n        }\n\n        // If we read less than the chunk size, this is the last chunk\n        if bytesRead < chunkSize {\n            isFinished = true\n        }\n\n        // Create Data object only with the bytes actually read\n        return Data(bytes: reusableBuffer, count: bytesRead)\n    }\n\n    /// Check if the reader has finished reading the file\n    public var hasFinished: Bool {\n        isFinished\n    }\n\n    /// Reset the reader to the beginning of the file\n    /// Note: InputStream doesn't support seeking, so this recreates the stream\n    /// - Throws: Any file opening errors\n    public func reset() throws {\n        inputStream.close()\n        // Note: InputStream doesn't provide a way to get the original URL,\n        // so reset functionality is limited. Consider removing this method\n        // or storing the original URL if reset is needed.\n        throw CocoaError(\n            .fileReadUnsupportedScheme,\n            userInfo: [\n                NSLocalizedDescriptionKey: \"reset not supported with InputStream-based implementation\"\n            ])\n    }\n\n    /// Get the current file offset\n    /// Note: InputStream doesn't provide offset information\n    /// - Returns: Current position in the file\n    /// - Throws: Unsupported operation error\n    public func currentOffset() throws -> UInt64 {\n        throw CocoaError(\n            .fileReadUnsupportedScheme,\n            userInfo: [\n                NSLocalizedDescriptionKey: \"offset tracking not supported with InputStream-based implementation\"\n            ])\n    }\n\n    /// Seek to a specific offset in the file\n    /// Note: InputStream doesn't support seeking\n    /// - Parameter offset: The byte offset to seek to\n    /// - Throws: Unsupported operation error\n    public func seek(to offset: UInt64) throws {\n        throw CocoaError(\n            .fileReadUnsupportedScheme,\n            userInfo: [\n                NSLocalizedDescriptionKey: \"seeking not supported with InputStream-based implementation\"\n            ])\n    }\n\n    /// Close the input stream explicitly (called automatically in deinit)\n    public func close() {\n        inputStream.close()\n        isFinished = true\n    }\n}\n\n/// AsyncIteratorProtocol implementation for BufferedCopyReader\npublic struct BufferedCopyReaderIterator: AsyncIteratorProtocol {\n    public typealias Element = Data\n\n    private let reader: BufferedCopyReader\n\n    init(reader: BufferedCopyReader) {\n        self.reader = reader\n    }\n\n    /// Get the next chunk of data asynchronously\n    /// - Returns: Next data chunk, or nil when finished\n    /// - Throws: Any file reading errors\n    public mutating func next() async throws -> Data? {\n        // Yield control to allow other tasks to run, then read synchronously\n        await Task.yield()\n        return try reader.nextChunk()\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/AggregateError.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// An error type that aggregates multiple errors into one.\n///\n/// When displayed, each underlying error is printed on its own line.\npublic struct AggregateError: Swift.Error, Sendable {\n    public let errors: [any Error]\n\n    public init(_ errors: [any Error]) {\n        self.errors = errors\n    }\n}\n\nextension AggregateError: CustomStringConvertible {\n    public var description: String {\n        errors.map { String(describing: $0) }.joined(separator: \"\\n\")\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Application.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerLog\nimport ContainerPlugin\nimport ContainerVersion\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport TerminalProgress\n\n// This logger is only used until `asyncCommand.run()`.\n// `log` is updated only once in the `validate()` method.\nprivate nonisolated(unsafe) var bootstrapLogger = {\n    LoggingSystem.bootstrap({ _ in StderrLogHandler() })\n    var log = Logger(label: \"com.apple.container\")\n    log.logLevel = .info\n    return log\n}()\n\npublic struct Application: AsyncLoggableCommand {\n    @OptionGroup\n    public var logOptions: Flags.Logging\n\n    public init() {}\n\n    public static let configuration = CommandConfiguration(\n        commandName: \"container\",\n        abstract: \"A container platform for macOS\",\n        version: ReleaseVersion.singleLine(appName: \"container CLI\"),\n        subcommands: [\n            DefaultCommand.self\n        ],\n        groupedSubcommands: [\n            CommandGroup(\n                name: \"Container\",\n                subcommands: [\n                    ContainerCreate.self,\n                    ContainerDelete.self,\n                    ContainerExec.self,\n                    ContainerExport.self,\n                    ContainerInspect.self,\n                    ContainerKill.self,\n                    ContainerList.self,\n                    ContainerLogs.self,\n                    ContainerRun.self,\n                    ContainerStart.self,\n                    ContainerStats.self,\n                    ContainerStop.self,\n                    ContainerPrune.self,\n                ]\n            ),\n            CommandGroup(\n                name: \"Image\",\n                subcommands: [\n                    BuildCommand.self,\n                    ImageCommand.self,\n                    RegistryCommand.self,\n                ]\n            ),\n            CommandGroup(\n                name: \"Volume\",\n                subcommands: [\n                    VolumeCommand.self\n                ]\n            ),\n            CommandGroup(\n                name: \"Other\",\n                subcommands: Self.otherCommands()\n            ),\n        ],\n        // Hidden command to handle plugins on unrecognized input.\n        defaultSubcommand: DefaultCommand.self\n    )\n\n    public static func main() async throws {\n        restoreCursorAtExit()\n\n        #if DEBUG\n        let warning = \"Running debug build. Performance may be degraded.\"\n        let formattedWarning: String\n        if isatty(FileHandle.standardError.fileDescriptor) == 1 {\n            formattedWarning = \"\\u{001B}[33mWarning!\\u{001B}[0m \\(warning)\\n\"\n        } else {\n            formattedWarning = \"Warning! \\(warning)\\n\"\n        }\n        let warningData = Data(formattedWarning.utf8)\n        FileHandle.standardError.write(warningData)\n        #endif\n\n        let fullArgs = CommandLine.arguments\n        let args = Array(fullArgs.dropFirst())\n\n        do {\n            // container -> defaultHelpCommand\n            var command = try Application.parseAsRoot(args)\n            if var asyncCommand = command as? AsyncParsableCommand {\n                try await asyncCommand.run()\n            } else {\n                try command.run()\n            }\n        } catch {\n            // Regular ol `command` with no args will get caught by DefaultCommand. --help\n            // on the root command will land here.\n            let containsHelp = fullArgs.contains(\"-h\") || fullArgs.contains(\"--help\")\n            if fullArgs.count <= 2 && containsHelp {\n                let pluginLoader = try? await createPluginLoader()\n                await Self.printModifiedHelpText(pluginLoader: pluginLoader)\n                return\n            }\n            let errorAsString: String = String(describing: error)\n            if errorAsString.contains(\"XPC connection error\") {\n                let modifiedError = ContainerizationError(.interrupted, message: \"\\(error)\\nEnsure container system service has been started with `container system start`.\")\n                Application.exit(withError: modifiedError)\n            } else {\n                Application.exit(withError: error)\n            }\n        }\n    }\n\n    public static func createPluginLoader() async throws -> PluginLoader {\n        let installRoot = CommandLine.executablePathUrl\n            .deletingLastPathComponent()\n            .appendingPathComponent(\"..\")\n            .standardized\n        let pluginsURL = PluginLoader.userPluginsDir(installRoot: installRoot)\n        var directoryExists: ObjCBool = false\n        _ = FileManager.default.fileExists(atPath: pluginsURL.path, isDirectory: &directoryExists)\n        let userPluginsURL = directoryExists.boolValue ? pluginsURL : nil\n\n        // plugins built into the application installed as a macOS app bundle\n        let appBundlePluginsURL = Bundle.main.resourceURL?.appending(path: \"plugins\")\n\n        // plugins built into the application installed as a Unix-like application\n        let installRootPluginsURL =\n            installRoot\n            .appendingPathComponent(\"libexec\")\n            .appendingPathComponent(\"container\")\n            .appendingPathComponent(\"plugins\")\n            .standardized\n\n        let pluginDirectories = [\n            userPluginsURL,\n            appBundlePluginsURL,\n            installRootPluginsURL,\n        ].compactMap { $0 }\n\n        let pluginFactories: [any PluginFactory] = [\n            DefaultPluginFactory(),\n            AppBundlePluginFactory(),\n        ]\n\n        guard let systemHealth = try? await ClientHealthCheck.ping(timeout: .seconds(10)) else {\n            throw ContainerizationError(.timeout, message: \"unable to retrieve application data root from API server\")\n        }\n        return try PluginLoader(\n            appRoot: systemHealth.appRoot,\n            installRoot: systemHealth.installRoot,\n            logRoot: systemHealth.logRoot,\n            pluginDirectories: pluginDirectories,\n            pluginFactories: pluginFactories,\n            log: bootstrapLogger\n        )\n    }\n\n    public func validate() throws {\n        // Not really a \"validation\", but a cheat to run this before\n        // any of the commands do their business.\n        let debugEnvVar = ProcessInfo.processInfo.environment[\"CONTAINER_DEBUG\"]\n        if self.logOptions.debug || debugEnvVar != nil {\n            bootstrapLogger.logLevel = .debug\n        }\n        // Ensure we're not running under Rosetta.\n        if try isTranslated() {\n            throw ValidationError(\n                \"\"\"\n                `container` is currently running under Rosetta Translation, which could be\n                caused by your terminal application. Please ensure this is turned off.\n                \"\"\"\n            )\n        }\n    }\n\n    private static func otherCommands() -> [any ParsableCommand.Type] {\n        guard #available(macOS 26, *) else {\n            return [\n                BuilderCommand.self,\n                SystemCommand.self,\n            ]\n        }\n\n        return [\n            BuilderCommand.self,\n            NetworkCommand.self,\n            SystemCommand.self,\n        ]\n    }\n\n    private static func restoreCursorAtExit() {\n        let signalHandler: @convention(c) (Int32) -> Void = { signal in\n            let exitCode = ExitCode(signal + 128)\n            Application.exit(withError: exitCode)\n        }\n        // Termination by Ctrl+C.\n        signal(SIGINT, signalHandler)\n        // Termination using `kill`.\n        signal(SIGTERM, signalHandler)\n        // Normal and explicit exit.\n        atexit {\n            if let progressConfig = try? ProgressConfig() {\n                let progressBar = ProgressBar(config: progressConfig)\n                progressBar.resetCursor()\n            }\n        }\n    }\n}\n\nextension Application {\n    // Because we support plugins, we need to modify the help text to display\n    // any if we found some.\n    static func printModifiedHelpText(pluginLoader: PluginLoader?) async {\n        let original = Application.helpMessage(for: Application.self)\n        guard let pluginLoader else {\n            print(original)\n            print(\"PLUGINS: not available, run `container system start`\")\n            return\n        }\n        let altered = pluginLoader.alterCLIHelpText(original: original)\n        print(altered)\n    }\n\n    public enum ListFormat: String, CaseIterable, ExpressibleByArgument {\n        case json\n        case table\n    }\n\n    func isTranslated() throws -> Bool {\n        do {\n            return try Sysctl.byName(\"sysctl.proc_translated\") == 1\n        } catch let posixErr as POSIXError {\n            if posixErr.code == .ENOENT {\n                return false\n            }\n            throw posixErr\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/AsyncLoggableCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerLog\nimport Logging\n\npublic protocol AsyncLoggableCommand: AsyncParsableCommand {\n    var logOptions: Flags.Logging { get }\n}\n\nextension AsyncLoggableCommand {\n    /// A shared logger instance configured based on the command's options\n    public var log: Logger {\n        var logger = Logger(label: \"container\", factory: { _ in StderrLogHandler() })\n\n        logger.logLevel = logOptions.debug ? .debug : .info\n\n        return logger\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/BuildCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerBuild\nimport ContainerImagesServiceClient\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport NIO\nimport TerminalProgress\n\nextension Application {\n    public struct BuildCommand: AsyncLoggableCommand {\n        private static let hiddenDockerDir = \".com.apple.container.dockerfiles\"\n\n        public init() {}\n        public static var configuration: CommandConfiguration {\n            var config = CommandConfiguration()\n            config.commandName = \"build\"\n            config.abstract = \"Build an image from a Dockerfile or Containerfile\"\n            config._superCommandName = \"container\"\n            config.helpNames = NameSpecification(arrayLiteral: .customShort(\"h\"), .customLong(\"help\"))\n            return config\n        }\n\n        enum ProgressType: String, ExpressibleByArgument {\n            case auto\n            case plain\n            case tty\n        }\n\n        enum SecretType: Decodable {\n            case data(Data)\n            case file(String)\n        }\n\n        @Option(\n            name: .shortAndLong,\n            help: ArgumentHelp(\"Add the architecture type to the build\", valueName: \"value\"),\n            transform: { val in val.split(separator: \",\").map { String($0) } }\n        )\n        var arch: [[String]] = {\n            [[Arch.hostArchitecture().rawValue]]\n        }()\n\n        @Option(name: .long, help: ArgumentHelp(\"Set build-time variables\", valueName: \"key=val\"))\n        var buildArg: [String] = []\n\n        @Option(name: .long, help: ArgumentHelp(\"Cache imports for the build\", valueName: \"value\", visibility: .hidden))\n        var cacheIn: [String] = {\n            []\n        }()\n\n        @Option(name: .long, help: ArgumentHelp(\"Cache exports for the build\", valueName: \"value\", visibility: .hidden))\n        var cacheOut: [String] = {\n            []\n        }()\n\n        @Option(name: .shortAndLong, help: \"Number of CPUs to allocate to the builder container\")\n        var cpus: Int64?\n\n        @Option(name: .shortAndLong, help: ArgumentHelp(\"Path to Dockerfile\", valueName: \"path\"))\n        var file: String?\n\n        var dockerfile: String = \"-\"\n\n        @Option(name: .shortAndLong, help: ArgumentHelp(\"Set a label\", valueName: \"key=val\"))\n        var label: [String] = []\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix\"\n        )\n        var memory: String?\n\n        @Flag(name: .long, help: \"Do not use cache\")\n        var noCache: Bool = false\n\n        @Option(name: .shortAndLong, help: ArgumentHelp(\"Output configuration for the build (format: type=<oci|tar|local>[,dest=])\", valueName: \"value\"))\n        var output: [String] = {\n            [\"type=oci\"]\n        }()\n\n        @Option(\n            name: .long,\n            help: ArgumentHelp(\"Add the OS type to the build\", valueName: \"value\"),\n            transform: { val in val.split(separator: \",\").map { String($0) } }\n        )\n        var os: [[String]] = {\n            [[\"linux\"]]\n        }()\n\n        @Option(\n            name: .long,\n            help: \"Add the platform to the build (format: os/arch[/variant], takes precedence over --os and --arch) [environment: CONTAINER_DEFAULT_PLATFORM]\",\n            transform: { val in val.split(separator: \",\").map { String($0) } }\n        )\n        var platform: [[String]] = [[]]\n\n        @Option(name: .long, help: ArgumentHelp(\"Progress type (format: auto|plain|tty)\", valueName: \"type\"))\n        var progress: ProgressType = .auto\n\n        @Flag(name: .shortAndLong, help: \"Suppress build output\")\n        var quiet: Bool = false\n\n        @Option(name: .long, help: ArgumentHelp(\"Set build-time secrets (format: id=<key>[,env=<ENV_VAR>|,src=<local/path>])\", valueName: \"id=key,...\"))\n        var secret: [String] = []\n\n        var secrets: [String: SecretType] = [:]\n\n        @Option(name: [.short, .customLong(\"tag\")], help: ArgumentHelp(\"Name for the built image\", valueName: \"name\"))\n        var targetImageNames: [String] = {\n            [UUID().uuidString.lowercased()]\n        }()\n\n        @Option(name: .long, help: ArgumentHelp(\"Set the target build stage\", valueName: \"stage\"))\n        var target: String = \"\"\n\n        @Option(name: .long, help: ArgumentHelp(\"Builder shim vsock port\", valueName: \"port\"))\n        var vsockPort: UInt32 = 8088\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @OptionGroup\n        public var dns: Flags.DNS\n\n        @Argument(help: \"Build directory\")\n        var contextDir: String = \".\"\n\n        @Flag(name: .long, help: \"Pull latest image\")\n        var pull: Bool = false\n\n        public func run() async throws {\n            do {\n                let timeout: Duration = .seconds(300)\n                let progressConfig = try ProgressConfig(\n                    showTasks: true,\n                    showItems: true\n                )\n                let progress = ProgressBar(config: progressConfig)\n                defer {\n                    progress.finish()\n                }\n                progress.start()\n\n                progress.set(description: \"Dialing builder\")\n\n                let dnsNameservers = self.dns.nameservers\n                let builder: Builder? = try await withThrowingTaskGroup(of: Builder.self) { [vsockPort, cpus, memory, dnsNameservers] group in\n                    defer {\n                        group.cancelAll()\n                    }\n\n                    group.addTask { [vsockPort, cpus, memory, log, dnsNameservers] in\n                        let client = ContainerClient()\n                        while true {\n                            do {\n                                let fh = try await client.dial(id: \"buildkit\", port: vsockPort)\n\n                                let threadGroup: MultiThreadedEventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)\n                                let b = try Builder(socket: fh, group: threadGroup)\n\n                                // If this call succeeds, then BuildKit is running.\n                                let _ = try await b.info()\n                                return b\n                            } catch {\n                                // If we get here, \"Dialing builder\" is shown for such a short period\n                                // of time that it's invisible to the user.\n                                progress.set(tasks: 0)\n                                progress.set(totalTasks: 3)\n\n                                try await BuilderStart.start(\n                                    cpus: cpus,\n                                    memory: memory,\n                                    log: log,\n                                    dnsNameservers: dnsNameservers,\n                                    progressUpdate: progress.handler\n                                )\n\n                                // wait (seconds) for builder to start listening on vsock\n                                try await Task.sleep(for: .seconds(5))\n                                continue\n                            }\n                        }\n                    }\n\n                    group.addTask {\n                        try await Task.sleep(for: timeout)\n                        throw ValidationError(\n                            \"\"\"\n                                Timeout waiting for connection to builder\n                            \"\"\"\n                        )\n                    }\n\n                    return try await group.next()\n                }\n\n                guard let builder else {\n                    throw ValidationError(\"builder is not running\")\n                }\n\n                let buildFileData: Data\n                var ignoreFileData: Data? = nil\n                var hiddenDockerDir: String? = nil\n                // Dockerfile should be read from stdin\n                if dockerfile == \"-\" {\n                    let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(\"Dockerfile-\\(UUID().uuidString)\")\n                    defer {\n                        try? FileManager.default.removeItem(at: tempFile)\n                    }\n\n                    guard FileManager.default.createFile(atPath: tempFile.path(), contents: nil) else {\n                        throw ContainerizationError(.internalError, message: \"unable to create temporary file\")\n                    }\n\n                    guard let fileHandle = try? FileHandle(forWritingTo: tempFile) else {\n                        throw ContainerizationError(.internalError, message: \"unable to open temporary file for writing\")\n                    }\n\n                    let bufferSize = 4096\n                    while true {\n                        let chunk = FileHandle.standardInput.readData(ofLength: bufferSize)\n                        if chunk.isEmpty { break }\n                        fileHandle.write(chunk)\n                    }\n                    try fileHandle.close()\n                    buildFileData = try Data(contentsOf: URL(filePath: tempFile.path()))\n                } else {\n                    let ignoreFileURL = URL(filePath: dockerfile + \".dockerignore\")\n                    buildFileData = try Data(contentsOf: URL(filePath: dockerfile))\n                    ignoreFileData = try? Data(contentsOf: ignoreFileURL)\n\n                    if var ignoreFileData {\n                        hiddenDockerDir = Self.hiddenDockerDir\n                        let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(Self.hiddenDockerDir)\n\n                        try FileManager.default.createDirectory(at: hiddenDirInContext, withIntermediateDirectories: true)\n                        try buildFileData.write(to: hiddenDirInContext.appendingPathComponent(\"Dockerfile\"))\n\n                        ignoreFileData.append(\"\\n\\(Self.hiddenDockerDir)\".data(using: .utf8) ?? Data())\n                        try ignoreFileData.write(to: hiddenDirInContext.appendingPathComponent(\"Dockerfile.dockerignore\"))\n                    }\n                }\n\n                defer {\n                    if let hiddenDockerDir {\n                        let hiddenDirInContext = URL(fileURLWithPath: contextDir).appendingPathComponent(hiddenDockerDir)\n                        try? FileManager.default.removeItem(at: hiddenDirInContext)\n                    }\n                }\n\n                let secretsData: [String: Data] = try self.secrets.mapValues { secret in\n                    switch secret {\n                    case .data(let data):\n                        return data\n                    case .file(let path):\n                        return try Data(contentsOf: URL(fileURLWithPath: path))\n                    }\n                }\n\n                let systemHealth = try await ClientHealthCheck.ping(timeout: .seconds(10))\n                let exportPath = systemHealth.appRoot\n                    .appendingPathComponent(Application.BuilderCommand.builderResourceDir)\n                let buildID = UUID().uuidString\n                let tempURL = exportPath.appendingPathComponent(buildID)\n                try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)\n                defer {\n                    try? FileManager.default.removeItem(at: tempURL)\n                }\n\n                let imageNames: [String] = try targetImageNames.map { name in\n                    let parsedReference = try Reference.parse(name)\n                    parsedReference.normalize()\n                    return parsedReference.description\n                }\n\n                var terminal: Terminal?\n                switch self.progress {\n                case .tty:\n                    terminal = try Terminal(descriptor: STDERR_FILENO)\n                case .auto:\n                    terminal = try? Terminal(descriptor: STDERR_FILENO)\n                case .plain:\n                    terminal = nil\n                }\n\n                defer { terminal?.tryReset() }\n\n                let exports: [Builder.BuildExport] = try output.map { output in\n                    var exp = try Builder.BuildExport(from: output)\n                    if exp.destination == nil {\n                        exp.destination = tempURL.appendingPathComponent(\"out.tar\")\n                    }\n                    return exp\n                }\n\n                try await withThrowingTaskGroup(of: Void.self) { [terminal] group in\n                    defer {\n                        group.cancelAll()\n                    }\n                    group.addTask {\n                        let handler = AsyncSignalHandler.create(notify: [SIGTERM, SIGINT, SIGUSR1, SIGUSR2])\n                        for await sig in handler.signals {\n                            throw ContainerizationError(.interrupted, message: \"exiting on signal \\(sig)\")\n                        }\n                    }\n                    let platforms: Set<Platform> = try {\n                        var results: Set<Platform> = []\n                        for platform in (self.platform.flatMap { $0 }) {\n                            guard let p = try? Platform(from: platform) else {\n                                throw ValidationError(\"invalid platform specified \\(platform)\")\n                            }\n                            results.insert(p)\n                        }\n\n                        if !results.isEmpty {\n                            return results\n                        }\n\n                        if let envPlatform = try DefaultPlatform.fromEnvironment(log: log) {\n                            return [envPlatform]\n                        }\n\n                        for o in (self.os.flatMap { $0 }) {\n                            for a in (self.arch.flatMap { $0 }) {\n                                guard let platform = try? Platform(from: \"\\(o)/\\(a)\") else {\n                                    throw ValidationError(\"invalid os/architecture combination \\(o)/\\(a)\")\n                                }\n                                results.insert(platform)\n                            }\n                        }\n                        return results\n                    }()\n                    group.addTask { [terminal, buildArg, secretsData, contextDir, hiddenDockerDir, label, noCache, target, quiet, cacheIn, cacheOut, pull] in\n                        let config = Builder.BuildConfig(\n                            buildID: buildID,\n                            contentStore: RemoteContentStoreClient(),\n                            buildArgs: buildArg,\n                            secrets: secretsData,\n                            contextDir: contextDir,\n                            dockerfile: buildFileData,\n                            hiddenDockerDir: hiddenDockerDir,\n                            labels: label,\n                            noCache: noCache,\n                            platforms: [Platform](platforms),\n                            terminal: terminal,\n                            tags: imageNames,\n                            target: target,\n                            quiet: quiet,\n                            exports: exports,\n                            cacheIn: cacheIn,\n                            cacheOut: cacheOut,\n                            pull: pull\n                        )\n                        progress.finish()\n\n                        try await builder.build(config)\n                    }\n\n                    try await group.next()\n                }\n\n                let unpackProgressConfig = try ProgressConfig(\n                    description: \"Unpacking built image\",\n                    itemsName: \"entries\",\n                    showTasks: exports.count > 1,\n                    totalTasks: exports.count\n                )\n                let unpackProgress = ProgressBar(config: unpackProgressConfig)\n                defer {\n                    unpackProgress.finish()\n                }\n                unpackProgress.start()\n\n                var finalMessage = \"Successfully built \\(imageNames.joined(separator: \", \"))\"\n                let taskManager = ProgressTaskCoordinator()\n                // Currently, only a single export can be specified.\n                for exp in exports {\n                    unpackProgress.add(tasks: 1)\n                    let unpackTask = await taskManager.startTask()\n                    switch exp.type {\n                    case \"oci\":\n                        try Task.checkCancellation()\n                        guard let dest = exp.destination else {\n                            throw ContainerizationError(.invalidArgument, message: \"dest is required \\(exp.rawValue)\")\n                        }\n                        let result = try await ClientImage.load(from: dest.absolutePath(), force: false)\n                        guard result.rejectedMembers.isEmpty else {\n                            log.error(\"archive contains invalid members\", metadata: [\"paths\": \"\\(result.rejectedMembers)\"])\n                            throw ContainerizationError(.internalError, message: \"failed to load archive\")\n                        }\n                        for image in result.images {\n                            try Task.checkCancellation()\n                            try await image.unpack(platform: nil, progressUpdate: ProgressTaskCoordinator.handler(for: unpackTask, from: unpackProgress.handler))\n\n                            // Tag the unpacked image with all requested tags\n                            for tagName in imageNames {\n                                try Task.checkCancellation()\n                                _ = try await image.tag(new: tagName)\n                            }\n                        }\n                    case \"tar\":\n                        guard let dest = exp.destination else {\n                            throw ContainerizationError(.invalidArgument, message: \"dest is required \\(exp.rawValue)\")\n                        }\n                        let tarURL = tempURL.appendingPathComponent(\"out.tar\")\n                        try FileManager.default.moveItem(at: tarURL, to: dest)\n                        finalMessage = \"Successfully exported to \\(dest.absolutePath())\"\n                    case \"local\":\n                        guard let dest = exp.destination else {\n                            throw ContainerizationError(.invalidArgument, message: \"dest is required \\(exp.rawValue)\")\n                        }\n                        let localDir = tempURL.appendingPathComponent(\"local\")\n\n                        guard FileManager.default.fileExists(atPath: localDir.path) else {\n                            throw ContainerizationError(.invalidArgument, message: \"expected local output not found\")\n                        }\n                        try FileManager.default.copyItem(at: localDir, to: dest)\n                        finalMessage = \"Successfully exported to \\(dest.absolutePath())\"\n                    default:\n                        throw ContainerizationError(.invalidArgument, message: \"invalid exporter \\(exp.rawValue)\")\n                    }\n                }\n                await taskManager.finish()\n                unpackProgress.finish()\n                print(finalMessage)\n            } catch {\n                throw NSError(domain: \"Build\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"\\(error)\"])\n            }\n        }\n\n        public mutating func validate() throws {\n            // NOTE: Here we check the Dockerfile exists, and set `dockerfile` to point the valid Dockerfile path or stdin\n            guard FileManager.default.fileExists(atPath: contextDir) else {\n                throw ValidationError(\"context dir does not exist \\(contextDir)\")\n            }\n            for name in targetImageNames {\n                guard let _ = try? Reference.parse(name) else {\n                    throw ValidationError(\"invalid reference \\(name)\")\n                }\n            }\n\n            switch file {\n            case \"-\":\n                dockerfile = \"-\"\n                break\n            case .some(let filepath):\n                let fileURL = URL(fileURLWithPath: filepath, relativeTo: .currentDirectory())\n                guard FileManager.default.fileExists(atPath: fileURL.path) else {\n                    throw ValidationError(\"dockerfile does not exist \\(filepath)\")\n                }\n\n                dockerfile = fileURL.path\n                break\n            case .none:\n                guard let defaultDockerfile = try BuildFile.resolvePath(contextDir: contextDir) else {\n                    throw ValidationError(\"dockerfile not found in context dir\")\n                }\n\n                guard FileManager.default.fileExists(atPath: defaultDockerfile) else {\n                    throw ValidationError(\"dockerfile does not exist \\(defaultDockerfile)\")\n                }\n\n                dockerfile = defaultDockerfile\n                break\n            }\n\n            // Parse --secret args\n            for secret in self.secret {\n                let parts = secret.split(separator: \",\", maxSplits: 1, omittingEmptySubsequences: false)\n                guard parts[0].hasPrefix(\"id=\") else {\n                    throw ValidationError(\"secret must start with id=<key> \\(secret)\")\n                }\n                let key = String(parts[0].dropFirst(3))\n                guard !key.contains(\"=\") else {\n                    throw ValidationError(\"secret id cannot contain '=' \\(key)\")\n                }\n                if parts.count == 1 || parts[1].hasPrefix(\"env=\") {\n                    let env = parts.count == 1 ? key : String(parts[1].dropFirst(4))\n                    // Using getenv/strlen over processInfo.environment to support\n                    // non-UTF-8 env var data.\n                    guard let ptr = getenv(env) else {\n                        throw ValidationError(\"secret env var doesn't exist \\(env)\")\n                    }\n                    self.secrets[key] = .data(Data(bytes: ptr, count: strlen(ptr)))\n                } else if parts[1].hasPrefix(\"src=\") {\n                    let path = String(parts[1].dropFirst(4))\n                    self.secrets[key] = .file(path)\n                } else {\n                    throw ValidationError(\"secret bad value \\(parts[1])\")\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Builder/Builder.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct BuilderCommand: AsyncLoggableCommand {\n        public init() {}\n\n        public static let builderResourceDir = \"builder\"\n        public static let configuration = CommandConfiguration(\n            commandName: \"builder\",\n            abstract: \"Manage an image builder instance\",\n            subcommands: [\n                BuilderStart.self,\n                BuilderStatus.self,\n                BuilderStop.self,\n                BuilderDelete.self,\n            ])\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Builder/BuilderDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct BuilderDelete: AsyncLoggableCommand {\n        public static var configuration: CommandConfiguration {\n            var config = CommandConfiguration()\n            config.commandName = \"delete\"\n            config.aliases = [\"rm\"]\n            config.abstract = \"Delete the builder container\"\n            return config\n        }\n\n        @Flag(name: .shortAndLong, help: \"Delete the builder even if it is running\")\n        var force = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            do {\n                let client = ContainerClient()\n                let container = try await client.get(id: \"buildkit\")\n                if container.status != .stopped {\n                    guard force else {\n                        throw ContainerizationError(.invalidState, message: \"BuildKit container is not stopped, use --force to override\")\n                    }\n                    try await client.stop(id: container.id)\n                }\n                try await client.delete(id: container.id)\n            } catch {\n                if error is ContainerizationError {\n                    if (error as? ContainerizationError)?.code == .notFound {\n                        return\n                    }\n                }\n                throw error\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Builder/BuilderStart.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerBuild\nimport ContainerPersistence\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport Logging\nimport TerminalProgress\n\nextension Application {\n    public struct BuilderStart: AsyncLoggableCommand {\n        static let defaultCPUs = 2\n        static let defaultMemoryInBytes: UInt64 = 2048.mib()\n\n        public static var configuration: CommandConfiguration {\n            var config = CommandConfiguration()\n            config.commandName = \"start\"\n            config.abstract = \"Start the builder container\"\n            return config\n        }\n\n        @Option(name: .shortAndLong, help: \"Number of CPUs to allocate to the builder container\")\n        var cpus: Int64?\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix\"\n        )\n        var memory: String?\n\n        @OptionGroup\n        public var dns: Flags.DNS\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let progressConfig = try ProgressConfig(\n                showTasks: true,\n                showItems: true,\n                totalTasks: 4\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n            try await Self.start(\n                cpus: self.cpus,\n                memory: self.memory,\n                log: log,\n                dnsNameservers: self.dns.nameservers,\n                dnsDomain: self.dns.domain,\n                dnsSearchDomains: self.dns.searchDomains,\n                dnsOptions: self.dns.options,\n                progressUpdate: progress.handler\n            )\n            progress.finish()\n        }\n\n        static func start(\n            cpus: Int64?,\n            memory: String?,\n            log: Logger,\n            dnsNameservers: [String] = [],\n            dnsDomain: String? = nil,\n            dnsSearchDomains: [String] = [],\n            dnsOptions: [String] = [],\n            progressUpdate: @escaping ProgressUpdateHandler\n        ) async throws {\n            await progressUpdate([\n                .setDescription(\"Fetching BuildKit image\"),\n                .setItemsName(\"blobs\"),\n            ])\n            let taskManager = ProgressTaskCoordinator()\n            let fetchTask = await taskManager.startTask()\n\n            let builderImage: String = DefaultsStore.get(key: .defaultBuilderImage)\n            let systemHealth = try await ClientHealthCheck.ping(timeout: .seconds(10))\n            let exportsMount: String = systemHealth.appRoot\n                .appendingPathComponent(Application.BuilderCommand.builderResourceDir)\n                .absolutePath()\n\n            if !FileManager.default.fileExists(atPath: exportsMount) {\n                try FileManager.default.createDirectory(\n                    atPath: exportsMount,\n                    withIntermediateDirectories: true,\n                    attributes: nil\n                )\n            }\n\n            let builderPlatform = ContainerizationOCI.Platform(arch: \"arm64\", os: \"linux\", variant: \"v8\")\n\n            var targetEnvVars: [String] = []\n            if let buildkitColors = ProcessInfo.processInfo.environment[\"BUILDKIT_COLORS\"] {\n                targetEnvVars.append(\"BUILDKIT_COLORS=\\(buildkitColors)\")\n            }\n            if ProcessInfo.processInfo.environment[\"NO_COLOR\"] != nil {\n                targetEnvVars.append(\"NO_COLOR=true\")\n            }\n            targetEnvVars.sort()\n\n            let client = ContainerClient()\n            let existingContainer = try? await client.get(id: \"buildkit\")\n            if let existingContainer {\n                let existingImage = existingContainer.configuration.image.reference\n                let existingResources = existingContainer.configuration.resources\n                let existingEnv = existingContainer.configuration.initProcess.environment\n                let existingDNS = existingContainer.configuration.dns\n\n                let existingManagedEnv = existingEnv.filter { envVar in\n                    envVar.hasPrefix(\"BUILDKIT_COLORS=\") || envVar.hasPrefix(\"NO_COLOR=\")\n                }.sorted()\n\n                let envChanged = existingManagedEnv != targetEnvVars\n\n                // Check if we need to recreate the builder due to different image\n                let imageChanged = existingImage != builderImage\n                let resolvedResources = try Parser.resources(\n                    cpus: cpus,\n                    memory: memory,\n                    cpuPropertyKey: .defaultBuildCPUs,\n                    memoryPropertyKey: .defaultBuildMemory,\n                    defaultCPUs: Self.defaultCPUs,\n                    defaultMemoryInBytes: Self.defaultMemoryInBytes\n                )\n                let cpuChanged = existingResources.cpus != resolvedResources.cpus\n                let memChanged = existingResources.memoryInBytes != resolvedResources.memoryInBytes\n                let dnsChanged = {\n                    if !dnsNameservers.isEmpty {\n                        return existingDNS?.nameservers != dnsNameservers\n                    }\n                    if dnsDomain != nil {\n                        return existingDNS?.domain != dnsDomain\n                    }\n                    if !dnsSearchDomains.isEmpty {\n                        return existingDNS?.searchDomains != dnsSearchDomains\n                    }\n                    if !dnsOptions.isEmpty {\n                        return existingDNS?.options != dnsOptions\n                    }\n                    return false\n                }()\n\n                switch existingContainer.status {\n                case .running:\n                    guard imageChanged || cpuChanged || memChanged || envChanged || dnsChanged else {\n                        // If image, mem, cpu, env, and DNS are the same, continue using the existing builder\n                        return\n                    }\n                    // If they changed, stop and delete the existing builder\n                    try await client.stop(id: existingContainer.id)\n                    try await client.delete(id: existingContainer.id)\n                case .stopped:\n                    // If the builder is stopped and matches our requirements, start it\n                    // Otherwise, delete it and create a new one\n                    guard imageChanged || cpuChanged || memChanged || envChanged || dnsChanged else {\n                        try await startBuildKit(client: client, id: existingContainer.id, progressUpdate, nil)\n                        return\n                    }\n                    try await client.delete(id: existingContainer.id)\n                case .stopping:\n                    throw ContainerizationError(\n                        .invalidState,\n                        message: \"builder is stopping, please wait until it is fully stopped before proceeding\"\n                    )\n                case .unknown:\n                    break\n                }\n            }\n\n            let useRosetta = DefaultsStore.getBool(key: .buildRosetta) ?? true\n            let shimArguments = [\n                \"--debug\",\n                \"--vsock\",\n                useRosetta ? nil : \"--enable-qemu\",\n            ].compactMap { $0 }\n\n            try ContainerAPIClient.Utility.validEntityName(Builder.builderContainerId)\n\n            let image = try await ClientImage.fetch(\n                reference: builderImage,\n                platform: builderPlatform,\n                progressUpdate: ProgressTaskCoordinator.handler(for: fetchTask, from: progressUpdate)\n            )\n            // Unpack fetched image before use\n            await progressUpdate([\n                .setDescription(\"Unpacking BuildKit image\"),\n                .setItemsName(\"entries\"),\n            ])\n\n            let unpackTask = await taskManager.startTask()\n            _ = try await image.getCreateSnapshot(\n                platform: builderPlatform,\n                progressUpdate: ProgressTaskCoordinator.handler(for: unpackTask, from: progressUpdate)\n            )\n\n            let imageDesc = ImageDescription(\n                reference: builderImage,\n                descriptor: image.descriptor\n            )\n\n            let imageConfig = try await image.config(for: builderPlatform).config\n            var environment = imageConfig?.env ?? []\n            environment.append(contentsOf: targetEnvVars)\n\n            let processConfig = ProcessConfiguration(\n                executable: \"/usr/local/bin/container-builder-shim\",\n                arguments: shimArguments,\n                environment: environment,\n                workingDirectory: \"/\",\n                terminal: false,\n                user: .id(uid: 0, gid: 0)\n            )\n\n            let resources = try Parser.resources(\n                cpus: cpus,\n                memory: memory,\n                cpuPropertyKey: .defaultBuildCPUs,\n                memoryPropertyKey: .defaultBuildMemory,\n                defaultCPUs: Self.defaultCPUs,\n                defaultMemoryInBytes: Self.defaultMemoryInBytes\n            )\n\n            var config = ContainerConfiguration(id: Builder.builderContainerId, image: imageDesc, process: processConfig)\n            config.resources = resources\n            config.labels = [ResourceLabelKeys.role: ResourceRoleValues.builder]\n            config.mounts = [\n                .init(\n                    type: .tmpfs,\n                    source: \"\",\n                    destination: \"/run\",\n                    options: []\n                ),\n                .init(\n                    type: .virtiofs,\n                    source: exportsMount,\n                    destination: \"/var/lib/container-builder-shim/exports\",\n                    options: []\n                ),\n            ]\n            // Enable Rosetta only if the user didn't ask to disable it\n            config.rosetta = useRosetta\n\n            guard let defaultNetwork = try await ClientNetwork.builtin else {\n                throw ContainerizationError(.invalidState, message: \"default network is not present\")\n            }\n            guard case .running(_, let networkStatus) = defaultNetwork else {\n                throw ContainerizationError(.invalidState, message: \"default network is not running\")\n            }\n            config.networks = [\n                AttachmentConfiguration(network: defaultNetwork.id, options: AttachmentOptions(hostname: Builder.builderContainerId))\n            ]\n            let subnet = networkStatus.ipv4Subnet\n            let nameserver = IPv4Address(subnet.lower.value + 1).description\n            let nameservers = dnsNameservers.isEmpty ? [nameserver] : dnsNameservers\n            config.dns = ContainerConfiguration.DNSConfiguration(\n                nameservers: nameservers,\n                domain: dnsDomain,\n                searchDomains: dnsSearchDomains,\n                options: dnsOptions\n            )\n\n            let kernel = try await {\n                await progressUpdate([\n                    .setDescription(\"Fetching kernel\"),\n                    .setItemsName(\"binary\"),\n                ])\n\n                let kernel = try await ClientKernel.getDefaultKernel(for: .current)\n                return kernel\n            }()\n\n            await progressUpdate([\n                .setDescription(\"Starting BuildKit container\")\n            ])\n\n            try await client.create(\n                configuration: config,\n                options: .default,\n                kernel: kernel\n            )\n\n            try await startBuildKit(client: client, id: Builder.builderContainerId, progressUpdate, taskManager)\n            log.debug(\"starting BuildKit and BuildKit-shim\")\n        }\n    }\n}\n\n// MARK: - BuildKit Start Helper\n\n/// Starts the BuildKit process within the container\n/// This function handles bootstrapping the container and starting the BuildKit process\nprivate func startBuildKit(\n    client: ContainerClient,\n    id: String,\n    _ progress: @escaping ProgressUpdateHandler,\n    _ taskManager: ProgressTaskCoordinator? = nil\n) async throws {\n    do {\n        let io = try ProcessIO.create(\n            tty: false,\n            interactive: false,\n            detach: true\n        )\n        defer { try? io.close() }\n\n        let process = try await client.bootstrap(id: id, stdio: io.stdio)\n        try await process.start()\n        await taskManager?.finish()\n        try io.closeAfterStart()\n    } catch {\n        try? await client.stop(id: id)\n        try? await client.delete(id: id)\n        if error is ContainerizationError {\n            throw error\n        }\n        throw ContainerizationError(.internalError, message: \"failed to start BuildKit: \\(error)\")\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Builder/BuilderStatus.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\nextension Application {\n    public struct BuilderStatus: AsyncLoggableCommand {\n        public static var configuration: CommandConfiguration {\n            var config = CommandConfiguration()\n            config.commandName = \"status\"\n            config.abstract = \"Display the builder container status\"\n            return config\n        }\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the container ID\")\n        var quiet = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            do {\n                let client = ContainerClient()\n                let container = try await client.get(id: \"buildkit\")\n                try printContainers(containers: [container], format: format)\n            } catch {\n                if error is ContainerizationError {\n                    if (error as? ContainerizationError)?.code == .notFound && !quiet {\n                        print(\"builder is not running\")\n                        return\n                    }\n                }\n                throw error\n            }\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"ID\", \"IMAGE\", \"STATE\", \"ADDR\", \"CPUS\", \"MEMORY\"]]\n        }\n\n        private func printContainers(containers: [ContainerSnapshot], format: ListFormat) throws {\n            if format == .json {\n                let printables = containers.map {\n                    PrintableContainer($0)\n                }\n                let data = try JSONEncoder().encode(printables)\n                print(String(decoding: data, as: UTF8.self))\n\n                return\n            }\n\n            if self.quiet {\n                containers\n                    .filter { $0.status == .running }\n                    .forEach { print($0.id) }\n                return\n            }\n\n            var rows = createHeader()\n            for container in containers {\n                rows.append(container.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\n\nextension ContainerSnapshot {\n    fileprivate var asRow: [String] {\n        [\n            self.id,\n            self.configuration.image.reference,\n            self.status.rawValue,\n            self.networks.compactMap { $0.ipv4Address.description }.joined(separator: \",\"),\n            \"\\(self.configuration.resources.cpus)\",\n            \"\\(self.configuration.resources.memoryInBytes / (1024 * 1024)) MB\",\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Builder/BuilderStop.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct BuilderStop: AsyncLoggableCommand {\n        public static var configuration: CommandConfiguration {\n            var config = CommandConfiguration()\n            config.commandName = \"stop\"\n            config.abstract = \"Stop the builder container\"\n            return config\n        }\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            do {\n                let client = ContainerClient()\n                try await client.stop(id: \"buildkit\")\n            } catch {\n                if error is ContainerizationError {\n                    if (error as? ContainerizationError)?.code == .notFound {\n                        print(\"builder is not running\")\n                        return\n                    }\n                }\n                throw error\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Codable+JSON.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension [any Codable] {\n    func jsonArray() throws -> String {\n        \"[\\(try self.map { String(decoding: try JSONEncoder().encode($0), as: UTF8.self) }.joined(separator: \",\"))]\"\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerCreate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct ContainerCreate: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"create\",\n            abstract: \"Create a new container\")\n\n        @OptionGroup(title: \"Process options\")\n        var processFlags: Flags.Process\n\n        @OptionGroup(title: \"Resource options\")\n        var resourceFlags: Flags.Resource\n\n        @OptionGroup(title: \"Management options\")\n        var managementFlags: Flags.Management\n\n        @OptionGroup(title: \"Registry options\")\n        var registryFlags: Flags.Registry\n\n        @OptionGroup(title: \"Image fetch options\")\n        var imageFetchFlags: Flags.ImageFetch\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Image name\")\n        var image: String\n\n        @Argument(parsing: .captureForPassthrough, help: \"Container init process arguments\")\n        var arguments: [String] = []\n\n        public func run() async throws {\n            let progressConfig = try ProgressConfig(\n                showTasks: true,\n                showItems: true,\n                ignoreSmallSize: true,\n                totalTasks: 3\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            let id = Utility.createContainerID(name: self.managementFlags.name)\n            try Utility.validEntityName(id)\n\n            let ck = try await Utility.containerConfigFromFlags(\n                id: id,\n                image: image,\n                arguments: arguments,\n                process: processFlags,\n                management: managementFlags,\n                resource: resourceFlags,\n                registry: registryFlags,\n                imageFetch: imageFetchFlags,\n                progressUpdate: progress.handler,\n                log: log\n            )\n\n            let options = ContainerCreateOptions(autoRemove: managementFlags.remove)\n            let client = ContainerClient()\n            try await client.create(configuration: ck.0, options: options, kernel: ck.1, initImage: ck.2)\n\n            if !self.managementFlags.cidfile.isEmpty {\n                let path = self.managementFlags.cidfile\n                let data = id.data(using: .utf8)\n                var attributes = [FileAttributeKey: Any]()\n                attributes[.posixPermissions] = 0o644\n                let success = FileManager.default.createFile(\n                    atPath: path,\n                    contents: data,\n                    attributes: attributes\n                )\n                guard success else {\n                    throw ContainerizationError(\n                        .internalError, message: \"failed to create cidfile at \\(path): \\(errno)\")\n                }\n            }\n            progress.finish()\n\n            print(id)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct ContainerDelete: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"delete\",\n            abstract: \"Delete one or more containers\",\n            aliases: [\"rm\"])\n\n        @Flag(name: .shortAndLong, help: \"Delete all containers\")\n        var all = false\n\n        @Flag(name: .shortAndLong, help: \"Delete containers even if they are running\")\n        var force = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container IDs\")\n        var containerIds: [String] = []\n\n        public func validate() throws {\n            if containerIds.count == 0 && !all {\n                throw ContainerizationError(.invalidArgument, message: \"no containers specified and --all not supplied\")\n            }\n            if containerIds.count > 0 && all {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"explicitly supplied container ID(s) conflict with the --all flag\"\n                )\n            }\n        }\n\n        public mutating func run() async throws {\n            let set = Set<String>(containerIds)\n            let client = ContainerClient()\n            var containers = [ContainerSnapshot]()\n\n            if all {\n                containers = try await client.list()\n            } else {\n                let ctrs = try await client.list()\n                containers = ctrs.filter { c in\n                    set.contains(c.id)\n                }\n                // If one of the containers requested isn't present, let's throw. We don't need to do\n                // this for --all as --all should be perfectly usable with no containers to remove; otherwise,\n                // it'd be quite clunky.\n                if containers.count != set.count {\n                    let missing = set.filter { id in\n                        !containers.contains { c in\n                            c.id == id\n                        }\n                    }\n                    throw ContainerizationError(\n                        .notFound,\n                        message: \"failed to delete one or more containers: \\(missing)\"\n                    )\n                }\n            }\n\n            var errors: [any Error] = []\n            let force = self.force\n            let all = self.all\n            try await withThrowingTaskGroup(of: (any Error)?.self) { group in\n                for container in containers {\n                    group.addTask {\n                        do {\n                            if container.status == .running && !force {\n                                guard all else {\n                                    throw ContainerizationError(.invalidState, message: \"container is running\")\n                                }\n                                return nil  // Skip running container when using --all\n                            }\n\n                            try await client.delete(id: container.id, force: force)\n                            print(container.id)\n                            return nil\n                        } catch {\n                            return error\n                        }\n                    }\n                }\n\n                for try await error in group {\n                    if let error {\n                        errors.append(error)\n                    }\n                }\n            }\n\n            if !errors.isEmpty {\n                throw AggregateError(errors)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerExec.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\n\nextension Application {\n    public struct ContainerExec: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"exec\",\n            abstract: \"Run a new command in a running container\")\n\n        @OptionGroup(title: \"Process options\")\n        var processFlags: Flags.Process\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Flag(name: .shortAndLong, help: \"Run the process and detach from it\")\n        var detach = false\n\n        @Argument(help: \"Container ID\")\n        var containerId: String\n\n        @Argument(parsing: .captureForPassthrough, help: \"New process arguments\")\n        var arguments: [String]\n\n        public func run() async throws {\n            var exitCode: Int32 = 127\n            let client = ContainerClient()\n            let container = try await client.get(id: containerId)\n            try ensureRunning(container: container)\n\n            let stdin = self.processFlags.interactive\n            let tty = self.processFlags.tty\n\n            var config = container.configuration.initProcess\n            config.executable = arguments.first!\n            config.arguments = [String](self.arguments.dropFirst())\n            config.terminal = tty\n            config.environment.append(\n                contentsOf: try Parser.allEnv(\n                    imageEnvs: [],\n                    envFiles: self.processFlags.envFile,\n                    envs: self.processFlags.env\n                ))\n\n            if let cwd = self.processFlags.cwd {\n                config.workingDirectory = cwd\n            }\n\n            let defaultUser = config.user\n            let (user, additionalGroups) = Parser.user(\n                user: processFlags.user, uid: processFlags.uid,\n                gid: processFlags.gid, defaultUser: defaultUser)\n            config.user = user\n            config.supplementalGroups.append(contentsOf: additionalGroups)\n\n            do {\n                let io = try ProcessIO.create(tty: tty, interactive: stdin, detach: self.detach)\n                defer {\n                    try? io.close()\n                }\n\n                let process = try await client.createProcess(\n                    containerId: container.id,\n                    processId: UUID().uuidString.lowercased(),\n                    configuration: config,\n                    stdio: io.stdio\n                )\n\n                if self.detach {\n                    try await process.start()\n                    try io.closeAfterStart()\n                    print(containerId)\n                    return\n                }\n\n                if !self.processFlags.tty {\n                    var handler = SignalThreshold(threshold: 3, signals: [SIGINT, SIGTERM])\n                    handler.start {\n                        print(\"Received 3 SIGINT/SIGTERM's, forcefully exiting.\")\n                        Darwin.exit(1)\n                    }\n                }\n\n                exitCode = try await io.handleProcess(process: process, log: log)\n            } catch {\n                if error is ContainerizationError {\n                    throw error\n                }\n                throw ContainerizationError(.internalError, message: \"failed to exec process \\(error)\")\n            }\n            throw ArgumentParser.ExitCode(exitCode)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerExport.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct ContainerExport: AsyncLoggableCommand {\n        public init() {}\n        public static var configuration: CommandConfiguration {\n            CommandConfiguration(\n                commandName: \"export\",\n                abstract: \"Export a container's filesystem as a tar archive\",\n            )\n        }\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Option(\n            name: .shortAndLong, help: \"Pathname for the saved container filesystem (defaults to stdout)\", completion: .file(),\n            transform: { str in\n                URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false)\n            })\n        var output: String?\n\n        @Argument(help: \"container ID\")\n        var id: String\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let archive = tempDir.appendingPathComponent(\"archive.tar\")\n            try await client.export(id: id, archive: archive)\n\n            if output == nil {\n                guard let fileHandle = try? FileHandle(forReadingFrom: archive) else {\n                    throw ContainerizationError(.internalError, message: \"unable to open archive for reading\")\n                }\n                let bufferSize = 4096\n                while true {\n                    let chunk = fileHandle.readData(ofLength: bufferSize)\n                    if chunk.isEmpty { break }\n                    FileHandle.standardOutput.write(chunk)\n                }\n                try fileHandle.close()\n            } else {\n                try FileManager.default.moveItem(at: archive, to: URL(fileURLWithPath: output!))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerInspect.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport Foundation\nimport SwiftProtobuf\n\nextension Application {\n    public struct ContainerInspect: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"inspect\",\n            abstract: \"Display information about one or more containers\")\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container IDs to inspect\")\n        var containerIds: [String]\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let objects: [any Codable] = try await client.list().filter {\n                containerIds.contains($0.id)\n            }.map {\n                PrintableContainer($0)\n            }\n            print(try objects.jsonArray())\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerKill.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport ContainerizationOS\nimport Darwin\n\nextension Application {\n    public struct ContainerKill: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"kill\",\n            abstract: \"Kill or signal one or more running containers\")\n\n        @Flag(name: .shortAndLong, help: \"Kill or signal all running containers\")\n        var all = false\n\n        @Option(name: .shortAndLong, help: \"Signal to send to the container(s)\")\n        var signal: String = \"KILL\"\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container IDs\")\n        var containerIds: [String] = []\n\n        public func validate() throws {\n            if containerIds.count == 0 && !all {\n                throw ContainerizationError(.invalidArgument, message: \"no containers specified and --all not supplied\")\n            }\n            if containerIds.count > 0 && all {\n                throw ContainerizationError(.invalidArgument, message: \"explicitly supplied container IDs conflict with the --all flag\")\n            }\n        }\n\n        public mutating func run() async throws {\n            let client = ContainerClient()\n\n            let containers: [String]\n            if self.all {\n                containers = try await client.list(filters: ContainerListFilters(status: .running)).map { $0.id }\n            } else {\n                containers = containerIds\n            }\n\n            let signalNumber = try Signals.parseSignal(signal)\n\n            var errors: [any Error] = []\n            for container in containers {\n                do {\n                    try await client.kill(id: container, signal: signalNumber)\n                    print(container)\n                } catch {\n                    errors.append(error)\n                }\n            }\n            if !errors.isEmpty {\n                throw AggregateError(errors)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationExtras\nimport Foundation\nimport SwiftProtobuf\n\nextension Application {\n    public struct ContainerList: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List running containers\",\n            aliases: [\"ls\"])\n\n        @Flag(name: .shortAndLong, help: \"Include containers that are not running\")\n        var all = false\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the container ID\")\n        var quiet = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let filters = self.all ? ContainerListFilters.all : ContainerListFilters(status: .running)\n            let containers = try await client.list(filters: filters)\n            try printContainers(containers: containers, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"ID\", \"IMAGE\", \"OS\", \"ARCH\", \"STATE\", \"ADDR\", \"CPUS\", \"MEMORY\", \"STARTED\"]]\n        }\n\n        private func printContainers(containers: [ContainerSnapshot], format: ListFormat) throws {\n            if format == .json {\n                let printables = containers.map {\n                    PrintableContainer($0)\n                }\n                let data = try JSONEncoder().encode(printables)\n                print(String(decoding: data, as: UTF8.self))\n\n                return\n            }\n\n            if self.quiet {\n                containers.forEach {\n                    print($0.id)\n                }\n                return\n            }\n\n            var rows = createHeader()\n            for container in containers {\n                rows.append(container.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\n\nextension ContainerSnapshot {\n    fileprivate var asRow: [String] {\n        [\n            self.id,\n            self.configuration.image.reference,\n            self.platform.os,\n            self.platform.architecture,\n            self.status.rawValue,\n            self.networks.compactMap { $0.ipv4Address.description }.joined(separator: \",\"),\n            \"\\(self.configuration.resources.cpus)\",\n            \"\\(self.configuration.resources.memoryInBytes / (1024 * 1024)) MB\",\n            self.startedDate.map { ISO8601DateFormatter().string(from: $0) } ?? \"\",\n        ]\n    }\n}\n\nstruct PrintableContainer: Codable {\n    let status: RuntimeStatus\n    let configuration: ContainerConfiguration\n    let networks: [Attachment]\n    let startedDate: Date?\n\n    init(_ container: ContainerSnapshot) {\n        self.status = container.status\n        self.configuration = container.configuration\n        self.networks = container.networks\n        self.startedDate = container.startedDate\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerLogs.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Darwin\nimport Dispatch\nimport Foundation\n\nextension Application {\n    public struct ContainerLogs: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"logs\",\n            abstract: \"Fetch container logs\"\n        )\n\n        @Flag(name: .long, help: \"Display the boot log for the container instead of stdio\")\n        var boot: Bool = false\n\n        @Flag(name: .shortAndLong, help: \"Follow log output\")\n        var follow: Bool = false\n\n        @Option(name: .short, help: \"Number of lines to show from the end of the logs. If not provided this will print all of the logs\")\n        var numLines: Int?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container ID\")\n        var containerId: String\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let fhs = try await client.logs(id: containerId)\n            let fileHandle = boot ? fhs[1] : fhs[0]\n\n            try await Self.tail(\n                fh: fileHandle,\n                n: numLines,\n                follow: follow\n            )\n        }\n\n        private static func tail(\n            fh: FileHandle,\n            n: Int?,\n            follow: Bool\n        ) async throws {\n            if let n {\n                var buffer = Data()\n                let size = try fh.seekToEnd()\n                var offset = size\n                var lines: [String] = []\n\n                while offset > 0, lines.count < n {\n                    let readSize = min(1024, offset)\n                    offset -= readSize\n                    try fh.seek(toOffset: offset)\n\n                    let data = fh.readData(ofLength: Int(readSize))\n                    buffer.insert(contentsOf: data, at: 0)\n\n                    if let chunk = String(data: buffer, encoding: .utf8) {\n                        lines = chunk.components(separatedBy: .newlines)\n                        lines = lines.filter { !$0.isEmpty }\n                    }\n                }\n\n                lines = Array(lines.suffix(n))\n                for line in lines {\n                    print(line)\n                }\n            } else {\n                // Fast path if all they want is the full file.\n                guard let data = try fh.readToEnd() else {\n                    // Seems you get nil if it's a zero byte read, or you\n                    // try and read from dev/null.\n                    return\n                }\n                guard let str = String(data: data, encoding: .utf8) else {\n                    throw ContainerizationError(\n                        .internalError,\n                        message: \"failed to convert container logs to utf8\"\n                    )\n                }\n                print(str.trimmingCharacters(in: .newlines))\n            }\n\n            fflush(stdout)\n            if follow {\n                setbuf(stdout, nil)\n                try await Self.followFile(fh: fh)\n            }\n        }\n\n        private static func followFile(fh: FileHandle) async throws {\n            _ = try fh.seekToEnd()\n            let stream = AsyncStream<String> { cont in\n                fh.readabilityHandler = { handle in\n                    let data = handle.availableData\n                    if data.isEmpty {\n                        // Triggers on container restart - can exit here as well\n                        do {\n                            _ = try fh.seekToEnd()  // To continue streaming existing truncated log files\n                        } catch {\n                            fh.readabilityHandler = nil\n                            cont.finish()\n                            return\n                        }\n                    }\n                    if let str = String(data: data, encoding: .utf8), !str.isEmpty {\n                        var lines = str.components(separatedBy: .newlines)\n                        lines = lines.filter { !$0.isEmpty }\n                        for line in lines {\n                            cont.yield(line)\n                        }\n                    }\n                }\n            }\n\n            for await line in stream {\n                print(line)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerPrune.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct ContainerPrune: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"prune\",\n            abstract: \"Remove all stopped containers\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let containersToPrune = try await client.list().filter { $0.status == .stopped }\n\n            var prunedContainerIds = [String]()\n            var totalSize: UInt64 = 0\n\n            for container in containersToPrune {\n                do {\n                    let actualSize = try await client.diskUsage(id: container.id)\n                    totalSize += actualSize\n                    try await client.delete(id: container.id)\n                    prunedContainerIds.append(container.id)\n                } catch {\n                    log.error(\n                        \"failed to prune container\",\n                        metadata: [\n                            \"id\": \"\\(container.id)\",\n                            \"error\": \"\\(error)\",\n                        ])\n                }\n            }\n\n            let formatter = ByteCountFormatter()\n            let freed = formatter.string(fromByteCount: Int64(totalSize))\n\n            for name in prunedContainerIds {\n                print(name)\n            }\n            print(\"Reclaimed \\(freed) in disk space\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerRun.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport NIOCore\nimport NIOPosix\nimport TerminalProgress\n\nextension Application {\n    public struct ContainerRun: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"run\",\n            abstract: \"Run a container\")\n\n        @OptionGroup(title: \"Process options\")\n        var processFlags: Flags.Process\n\n        @OptionGroup(title: \"Resource options\")\n        var resourceFlags: Flags.Resource\n\n        @OptionGroup(title: \"Management options\")\n        var managementFlags: Flags.Management\n\n        @OptionGroup(title: \"Registry options\")\n        var registryFlags: Flags.Registry\n\n        @OptionGroup(title: \"Progress options\")\n        var progressFlags: Flags.Progress\n\n        @OptionGroup(title: \"Image fetch options\")\n        var imageFetchFlags: Flags.ImageFetch\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Image name\")\n        var image: String\n\n        @Argument(parsing: .captureForPassthrough, help: \"Container init process arguments\")\n        var arguments: [String] = []\n\n        public func run() async throws {\n            var exitCode: Int32 = 127\n            let id = Utility.createContainerID(name: self.managementFlags.name)\n\n            var progressConfig: ProgressConfig\n            switch self.progressFlags.progress {\n            case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)\n            case .ansi:\n                progressConfig = try ProgressConfig(\n                    showTasks: true,\n                    showItems: true,\n                    ignoreSmallSize: true,\n                    totalTasks: 6\n                )\n            }\n\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            try Utility.validEntityName(id)\n\n            // Check if container with id already exists.\n            let client = ContainerClient()\n            let existing = try? await client.get(id: id)\n            guard existing == nil else {\n                throw ContainerizationError(\n                    .exists,\n                    message: \"container with id \\(id) already exists\"\n                )\n            }\n\n            let ck = try await Utility.containerConfigFromFlags(\n                id: id,\n                image: image,\n                arguments: arguments,\n                process: processFlags,\n                management: managementFlags,\n                resource: resourceFlags,\n                registry: registryFlags,\n                imageFetch: imageFetchFlags,\n                progressUpdate: progress.handler,\n                log: log\n            )\n\n            progress.set(description: \"Starting container\")\n\n            let options = ContainerCreateOptions(autoRemove: managementFlags.remove)\n            try await client.create(\n                configuration: ck.0,\n                options: options,\n                kernel: ck.1,\n                initImage: ck.2\n            )\n\n            let detach = self.managementFlags.detach\n            do {\n                let io = try ProcessIO.create(\n                    tty: self.processFlags.tty,\n                    interactive: self.processFlags.interactive,\n                    detach: detach\n                )\n                defer {\n                    try? io.close()\n                }\n\n                let process = try await client.bootstrap(id: id, stdio: io.stdio)\n                progress.finish()\n\n                if !self.managementFlags.cidfile.isEmpty {\n                    let path = self.managementFlags.cidfile\n                    let data = id.data(using: .utf8)\n                    var attributes = [FileAttributeKey: Any]()\n                    attributes[.posixPermissions] = 0o644\n                    let success = FileManager.default.createFile(\n                        atPath: path,\n                        contents: data,\n                        attributes: attributes\n                    )\n                    guard success else {\n                        throw ContainerizationError(\n                            .internalError, message: \"failed to create cidfile at \\(path): \\(errno)\")\n                    }\n                }\n\n                if detach {\n                    try await process.start()\n                    try io.closeAfterStart()\n                    print(id)\n                    return\n                }\n\n                if !self.processFlags.tty {\n                    var handler = SignalThreshold(threshold: 3, signals: [SIGINT, SIGTERM])\n                    handler.start {\n                        print(\"Received 3 SIGINT/SIGTERM's, forcefully exiting.\")\n                        Darwin.exit(1)\n                    }\n                }\n\n                exitCode = try await io.handleProcess(process: process, log: log)\n            } catch {\n                try? await client.delete(id: id)\n                if error is ContainerizationError {\n                    throw error\n                }\n                throw ContainerizationError(.internalError, message: \"failed to run container: \\(error)\")\n            }\n            throw ArgumentParser.ExitCode(exitCode)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerStart.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct ContainerStart: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Start a container\")\n\n        @Flag(name: .shortAndLong, help: \"Attach stdout/stderr\")\n        var attach = false\n\n        @Flag(name: .shortAndLong, help: \"Attach stdin\")\n        var interactive = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container ID\")\n        var containerId: String\n\n        public func run() async throws {\n            var exitCode: Int32 = 127\n\n            let progressConfig = try ProgressConfig(\n                description: \"Starting container\"\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            let detach = !self.attach && !self.interactive\n            let client = ContainerClient()\n            let container = try await client.get(id: containerId)\n\n            // Bootstrap and process start are both idempotent and don't fail the second time\n            // around, however not doing an rpc is always faster :). The other bit is we don't\n            // support attach currently, so we can't do `start -a` a second time and have it succeed.\n            if container.status == .running {\n                if !detach {\n                    throw ContainerizationError(\n                        .invalidArgument,\n                        message: \"attach is currently unsupported on already running containers\"\n                    )\n                }\n                print(containerId)\n                return\n            }\n\n            for mount in container.configuration.mounts where mount.isVirtiofs {\n                if !FileManager.default.fileExists(atPath: mount.source) {\n                    throw ContainerizationError(.invalidState, message: \"path '\\(mount.source)' is not a directory\")\n                }\n            }\n\n            do {\n                let io = try ProcessIO.create(\n                    tty: container.configuration.initProcess.terminal,\n                    interactive: self.interactive,\n                    detach: detach\n                )\n                defer {\n                    try? io.close()\n                }\n\n                let process = try await client.bootstrap(id: container.id, stdio: io.stdio)\n                progress.finish()\n\n                if detach {\n                    try await process.start()\n                    try io.closeAfterStart()\n                    print(self.containerId)\n                    return\n                }\n\n                exitCode = try await io.handleProcess(process: process, log: log)\n            } catch {\n                try? await client.stop(id: container.id)\n\n                if error is ContainerizationError {\n                    throw error\n                }\n                throw ContainerizationError(.internalError, message: \"failed to start container: \\(error)\")\n            }\n            throw ArgumentParser.ExitCode(exitCode)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerStats.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\nextension Application {\n    public struct ContainerStats: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"stats\",\n            abstract: \"Display resource usage statistics for containers\")\n\n        @Argument(help: \"Container ID or name (optional, shows all running containers if not specified)\")\n        var containers: [String] = []\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .long, help: \"Disable streaming stats and only pull the first result\")\n        var noStream = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            if format == .json || noStream {\n                // Static mode - get stats once and exit\n                try await runStatic()\n            } else {\n                // Streaming mode - continuously update like top\n                // Enter alternate screen buffer and hide cursor\n                print(\"\\u{001B}[?1049h\\u{001B}[?25l\", terminator: \"\")\n                fflush(stdout)\n\n                defer {\n                    // Exit alternate screen buffer and show cursor again\n                    print(\"\\u{001B}[?25h\\u{001B}[?1049l\", terminator: \"\")\n                    fflush(stdout)\n                }\n\n                try await runStreaming()\n            }\n        }\n\n        private func runStatic() async throws {\n            let client = ContainerClient()\n\n            let containersToShow: [ContainerSnapshot]\n            if containers.isEmpty {\n                // No containers specified - show all running containers\n                containersToShow = try await client.list(filters: ContainerListFilters(status: .running))\n            } else {\n                // Fetch specified containers by ID\n                containersToShow = try await client.list(filters: ContainerListFilters(ids: containers))\n                // Validate all specified containers were found\n                for containerId in containers {\n                    guard containersToShow.contains(where: { $0.id == containerId }) else {\n                        throw ContainerizationError(\n                            .notFound,\n                            message: \"no such container: \\(containerId)\"\n                        )\n                    }\n                }\n            }\n\n            let statsData = try await collectStats(client: client, for: containersToShow)\n\n            if format == .json {\n                let jsonStats = statsData.map { $0.stats2 }\n                let data = try JSONEncoder().encode(jsonStats)\n                print(String(decoding: data, as: UTF8.self))\n                return\n            }\n\n            printStatsTable(statsData)\n        }\n\n        private func runStreaming() async throws {\n            let client = ContainerClient()\n\n            // If containers were specified, validate they all exist upfront\n            if !containers.isEmpty {\n                let specifiedContainers = try await client.list(filters: ContainerListFilters(ids: containers))\n                for containerId in containers {\n                    guard specifiedContainers.contains(where: { $0.id == containerId }) else {\n                        throw ContainerizationError(\n                            .notFound,\n                            message: \"no such container: \\(containerId)\"\n                        )\n                    }\n                }\n            }\n\n            clearScreen()\n            // Show header right away.\n            printStatsTable([])\n\n            while true {\n                do {\n                    let containersToShow: [ContainerSnapshot]\n                    if containers.isEmpty {\n                        containersToShow = try await client.list(filters: ContainerListFilters(status: .running))\n                    } else {\n                        containersToShow = try await client.list(filters: ContainerListFilters(ids: containers))\n                    }\n\n                    let statsData = try await collectStats(client: client, for: containersToShow)\n\n                    // Clear screen and reprint\n                    clearScreen()\n                    printStatsTable(statsData)\n\n                    if statsData.isEmpty {\n                        try await Task.sleep(for: .seconds(2))\n                    }\n                } catch {\n                    clearScreen()\n                    print(\"error collecting stats: \\(error)\")\n                    try await Task.sleep(for: .seconds(2))\n                }\n            }\n        }\n\n        private struct StatsSnapshot {\n            let container: ContainerSnapshot\n            let stats1: ContainerResource.ContainerStats\n            let stats2: ContainerResource.ContainerStats\n        }\n\n        private func collectStats(client: ContainerClient, for containers: [ContainerSnapshot]) async throws -> [StatsSnapshot] {\n            var snapshots: [StatsSnapshot] = []\n\n            // First sample\n            for container in containers {\n                guard container.status == .running else { continue }\n                do {\n                    let stats1 = try await client.stats(id: container.id)\n                    snapshots.append(StatsSnapshot(container: container, stats1: stats1, stats2: stats1))\n                } catch {\n                    // Skip containers that error out\n                    continue\n                }\n            }\n\n            // Wait 2 seconds for CPU delta calculation\n            if !snapshots.isEmpty {\n                try await Task.sleep(for: .seconds(2))\n\n                // Second sample\n                for i in 0..<snapshots.count {\n                    do {\n                        let stats2 = try await client.stats(id: snapshots[i].container.id)\n                        snapshots[i] = StatsSnapshot(\n                            container: snapshots[i].container,\n                            stats1: snapshots[i].stats1,\n                            stats2: stats2\n                        )\n                    } catch {\n                        // Keep the original stats if second sample fails\n                        continue\n                    }\n                }\n            }\n\n            return snapshots\n        }\n\n        /// Calculate CPU percentage from two stat snapshots\n        /// - Parameters:\n        ///   - cpuUsageUsec1: CPU usage in microseconds from first sample\n        ///   - cpuUsageUsec2: CPU usage in microseconds from second sample\n        ///   - timeDeltaUsec: Time delta between samples in microseconds\n        /// - Returns: CPU percentage where 100% = one fully utilized core\n        static func calculateCPUPercent(\n            cpuUsage1: Duration,\n            cpuUsage2: Duration,\n            timeInterval: Duration\n        ) -> Double {\n            let cpuDelta =\n                cpuUsage2 > cpuUsage1\n                ? cpuUsage2 - cpuUsage1\n                : .seconds(0)\n            return (cpuDelta / timeInterval) * 100.0\n        }\n\n        static func formatBytes(_ bytes: UInt64) -> String {\n            let kib = 1024.0\n            let mib = kib * 1024.0\n            let gib = mib * 1024.0\n\n            let value = Double(bytes)\n\n            if value >= gib {\n                return String(format: \"%.2f GiB\", value / gib)\n            } else if value >= mib {\n                return String(format: \"%.2f MiB\", value / mib)\n            } else {\n                return String(format: \"%.2f KiB\", value / kib)\n            }\n        }\n\n        private func printStatsTable(_ statsData: [StatsSnapshot]) {\n            let headerRow = [\"Container ID\", \"Cpu %\", \"Memory Usage\", \"Net Rx/Tx\", \"Block I/O\", \"Pids\"]\n            let notAvailable = \"--\"\n            var rows = [headerRow]\n\n            for snapshot in statsData {\n                var row = [snapshot.container.id]\n                let stats1 = snapshot.stats1\n                let stats2 = snapshot.stats2\n\n                if let cpuUsageUsec1 = stats1.cpuUsageUsec, let cpuUsageUsec2 = stats2.cpuUsageUsec {\n                    let cpuPercent = Self.calculateCPUPercent(\n                        cpuUsage1: .microseconds(cpuUsageUsec1),\n                        cpuUsage2: .microseconds(cpuUsageUsec2),\n                        timeInterval: .seconds(2)\n                    )\n                    let cpuStr = String(format: \"%.2f%%\", cpuPercent)\n                    row.append(cpuStr)\n                } else {\n                    row.append(notAvailable)\n                }\n\n                let memUsageStr = stats2.memoryUsageBytes.map { Self.formatBytes($0) } ?? notAvailable\n                let memLimitStr = stats2.memoryLimitBytes.map { Self.formatBytes($0) } ?? notAvailable\n                row.append(\"\\(memUsageStr) / \\(memLimitStr)\")\n\n                let netRxStr = stats2.networkRxBytes.map { Self.formatBytes($0) } ?? notAvailable\n                let netTxStr = stats2.networkTxBytes.map { Self.formatBytes($0) } ?? notAvailable\n                row.append(\"\\(netRxStr) / \\(netTxStr)\")\n\n                let blkReadStr = stats2.blockReadBytes.map { Self.formatBytes($0) } ?? notAvailable\n                let blkWriteStr = stats2.blockWriteBytes.map { Self.formatBytes($0) } ?? notAvailable\n                row.append(\"\\(blkReadStr) / \\(blkWriteStr)\")\n\n                let pidsStr = stats2.numProcesses.map { \"\\($0)\" } ?? notAvailable\n                row.append(pidsStr)\n\n                rows.append(row)\n            }\n\n            // Always print header, even if no containers\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n\n        private func clearScreen() {\n            // Move cursor to home position and clear from cursor to end of screen\n            print(\"\\u{001B}[H\\u{001B}[J\", terminator: \"\")\n            fflush(stdout)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ContainerStop.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\nextension Application {\n    public struct ContainerStop: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"stop\",\n            abstract: \"Stop one or more running containers\")\n\n        @Flag(name: .shortAndLong, help: \"Stop all running containers\")\n        var all = false\n\n        @Option(name: .shortAndLong, help: \"Signal to send to the containers\")\n        var signal: String = \"SIGTERM\"\n\n        @Option(name: .shortAndLong, help: \"Seconds to wait before killing the containers\")\n        var time: Int32 = 5\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Container IDs\")\n        var containerIds: [String] = []\n\n        public func validate() throws {\n            if containerIds.count == 0 && !all {\n                throw ContainerizationError(.invalidArgument, message: \"no containers specified and --all not supplied\")\n            }\n            if containerIds.count > 0 && all {\n                throw ContainerizationError(\n                    .invalidArgument, message: \"explicitly supplied container IDs conflict with the --all flag\")\n            }\n        }\n\n        public mutating func run() async throws {\n            let set = Set<String>(containerIds)\n            let client = ContainerClient()\n            var containers = [ContainerSnapshot]()\n            if self.all {\n                containers = try await client.list()\n            } else {\n                containers = try await client.list().filter { c in\n                    set.contains(c.id)\n                }\n            }\n\n            let opts = ContainerStopOptions(\n                timeoutInSeconds: self.time,\n                signal: try Signals.parseSignal(self.signal)\n            )\n            try await Self.stopContainers(\n                client: client,\n                containers: containers,\n                stopOptions: opts\n            )\n        }\n\n        static func stopContainers(client: ContainerClient, containers: [ContainerSnapshot], stopOptions: ContainerStopOptions) async throws {\n            var errors: [any Error] = []\n            await withTaskGroup(of: (any Error)?.self) { group in\n                for container in containers {\n                    group.addTask {\n                        do {\n                            try await client.stop(id: container.id, opts: stopOptions)\n                            print(container.id)\n                            return nil\n                        } catch {\n                            return error\n                        }\n                    }\n                }\n\n                for await error in group {\n                    if let error {\n                        errors.append(error)\n                    }\n                }\n            }\n\n            if !errors.isEmpty {\n                throw AggregateError(errors)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Container/ProcessUtils.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\n\nextension Application {\n    static func ensureRunning(container: ContainerSnapshot) throws {\n        if container.status != .running {\n            throw ContainerizationError(.invalidState, message: \"container \\(container.id) is not running\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/DefaultCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPlugin\nimport Darwin\nimport Foundation\n\nstruct DefaultCommand: AsyncLoggableCommand {\n    public static let configuration = CommandConfiguration(\n        commandName: nil,\n        shouldDisplay: false\n    )\n\n    @OptionGroup(visibility: .hidden)\n    public var logOptions: Flags.Logging\n\n    @Argument(parsing: .captureForPassthrough)\n    var remaining: [String] = []\n\n    func run() async throws {\n        // See if we have a possible plugin command.\n        let pluginLoader = try? await Application.createPluginLoader()\n        guard let command = remaining.first else {\n            await Application.printModifiedHelpText(pluginLoader: pluginLoader)\n            return\n        }\n\n        // Check for edge cases and unknown options to match the behavior in the absence of plugins.\n        if command.isEmpty {\n            throw ValidationError(\"unknown argument '\\(command)'\")\n        } else if command.starts(with: \"-\") {\n            throw ValidationError(\"unknown option '\\(command)'\")\n        }\n\n        // Compute canonical plugin directories to show in helpful errors (avoid hard-coded paths)\n        let installRoot = CommandLine.executablePathUrl\n            .deletingLastPathComponent()\n            .appendingPathComponent(\"..\")\n            .standardized\n        let userPluginsURL = PluginLoader.userPluginsDir(installRoot: installRoot)\n        let installRootPluginsURL =\n            installRoot\n            .appendingPathComponent(\"libexec\")\n            .appendingPathComponent(\"container\")\n            .appendingPathComponent(\"plugins\")\n            .standardized\n        let hintPaths = [userPluginsURL, installRootPluginsURL]\n            .map { $0.appendingPathComponent(command).path(percentEncoded: false) }\n            .joined(separator: \"\\n  - \")\n\n        // If plugin loader couldn't be created, the system/APIServer likely isn't running.\n        if pluginLoader == nil {\n            throw ValidationError(\n                \"\"\"\n                Plugins are unavailable. Start the container system services and retry:\n\n                    container system start\n\n                Check to see that the plugin exists under:\n                  - \\(hintPaths)\n\n                \"\"\"\n            )\n        }\n\n        guard let plugin = pluginLoader?.findPlugin(name: command), plugin.config.isCLI else {\n            throw ValidationError(\n                \"\"\"\n                Plugin 'container-\\(command)' not found.\n\n                - If system services are not running, start them with: container system start\n                - If the plugin isn't installed, ensure it exists under:\n\n                Check to see that the plugin exists under:\n                  - \\(hintPaths)\n\n                \"\"\"\n            )\n        }\n        // Before execing into the plugin, restore default SIGINT/SIGTERM so the plugin can manage signals.\n        Self.resetSignalsForPluginExec()\n        // Exec performs execvp (with no fork).\n        try plugin.exec(args: remaining)\n    }\n}\n\nextension DefaultCommand {\n    // Exposed for tests to verify signal reset semantics.\n    static func resetSignalsForPluginExec() {\n        signal(SIGINT, SIG_DFL)\n        signal(SIGTERM, SIG_DFL)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct ImageCommand: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"image\",\n            abstract: \"Manage images\",\n            subcommands: [\n                ImageDelete.self,\n                ImageInspect.self,\n                ImageList.self,\n                ImageLoad.self,\n                ImagePrune.self,\n                ImagePull.self,\n                ImagePush.self,\n                ImageSave.self,\n                ImageTag.self,\n            ],\n            aliases: [\"i\"]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationError\nimport Foundation\nimport Logging\n\nextension Application {\n    public struct RemoveImageOptions: ParsableArguments {\n        public init() {}\n\n        @Flag(name: .shortAndLong, help: \"Delete all images\")\n        var all: Bool = false\n\n        @Flag(name: .shortAndLong, help: \"Ignore errors for images that are not found\")\n        var force: Bool = false\n\n        @Argument\n        var images: [String] = []\n    }\n\n    struct DeleteImageImplementation {\n        static func validate(options: RemoveImageOptions) throws {\n            if options.images.count == 0 && !options.all {\n                throw ContainerizationError(.invalidArgument, message: \"no images specified and --all not supplied\")\n            }\n            if options.images.count > 0 && options.all {\n                throw ContainerizationError(.invalidArgument, message: \"explicitly supplied images conflict with the --all flag\")\n            }\n        }\n\n        static func removeImage(options: RemoveImageOptions, log: Logger) async throws {\n            let (found, notFound) = try await {\n                if options.all {\n                    let found = try await ClientImage.list()\n                    let notFound: [String] = []\n                    return (found, notFound)\n                }\n                return try await ClientImage.get(names: options.images)\n            }()\n            var failures: [String] = options.force ? [] : notFound\n            var didDeleteAnyImage = false\n            for image in found {\n                guard !Utility.isInfraImage(name: image.reference) else {\n                    continue\n                }\n                do {\n                    try await ClientImage.delete(reference: image.reference, garbageCollect: false)\n                    print(image.reference)\n                    didDeleteAnyImage = true\n                } catch {\n                    log.error(\"failed to delete \\(image.reference): \\(error)\")\n                    failures.append(image.reference)\n                }\n            }\n            let (_, size) = try await ClientImage.cleanUpOrphanedBlobs()\n            let formatter = ByteCountFormatter()\n            let freed = formatter.string(fromByteCount: Int64(size))\n\n            if didDeleteAnyImage {\n                print(\"Reclaimed \\(freed) in disk space\")\n            }\n            if failures.count > 0 {\n                throw ContainerizationError(.internalError, message: \"failed to delete one or more images: \\(failures)\")\n            }\n        }\n    }\n\n    public struct ImageDelete: AsyncLoggableCommand {\n        @OptionGroup\n        var options: RemoveImageOptions\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"delete\",\n            abstract: \"Delete one or more images\",\n            aliases: [\"rm\"])\n\n        public init() {}\n\n        public func validate() throws {\n            try DeleteImageImplementation.validate(options: options)\n        }\n\n        public mutating func run() async throws {\n            try await DeleteImageImplementation.removeImage(options: options, log: log)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageInspect.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerLog\nimport ContainerizationError\nimport Foundation\nimport Logging\nimport SwiftProtobuf\n\nextension Application {\n    public struct ImageInspect: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"inspect\",\n            abstract: \"Display information about one or more images\")\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Images to inspect\")\n        var images: [String]\n\n        public init() {}\n\n        struct InspectError: Error {\n            let succeeded: [String]\n            let failed: [(String, Error)]\n        }\n\n        public func run() async throws {\n            var printable = [any Codable]()\n            var succeededImages: [String] = []\n            var allErrors: [(String, Error)] = []\n\n            let result = try await ClientImage.get(names: images)\n\n            for image in result.images {\n                guard !Utility.isInfraImage(name: image.reference) else { continue }\n                printable.append(try await image.details())\n                succeededImages.append(image.reference)\n            }\n\n            for missing in result.error {\n                allErrors.append((missing, ContainerizationError(.notFound, message: \"Image not found\")))\n            }\n\n            if !printable.isEmpty {\n                print(try printable.jsonArray())\n            }\n\n            if !allErrors.isEmpty {\n                for (name, error) in allErrors {\n                    log.error(\n                        \"image inspect failed\",\n                        metadata: [\n                            \"name\": \"\\(name)\",\n                            \"error\": \"\\(error.localizedDescription)\",\n                        ])\n                }\n\n                throw InspectError(succeeded: succeededImages, failed: allErrors)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\nimport SwiftProtobuf\n\nextension Application {\n    public struct ListImageOptions: ParsableArguments {\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the image name\")\n        var quiet = false\n\n        @Flag(name: .shortAndLong, help: \"Verbose output\")\n        var verbose = false\n\n        public init() {}\n    }\n\n    struct ListImageImplementation {\n        static func createHeader() -> [[String]] {\n            [[\"NAME\", \"TAG\", \"DIGEST\"]]\n        }\n\n        static func createVerboseHeader() -> [[String]] {\n            [[\"NAME\", \"TAG\", \"INDEX DIGEST\", \"OS\", \"ARCH\", \"VARIANT\", \"FULL SIZE\", \"CREATED\", \"MANIFEST DIGEST\"]]\n        }\n\n        static func printImagesVerbose(images: [ClientImage]) async throws {\n\n            var rows = createVerboseHeader()\n            for image in images {\n                let formatter = ByteCountFormatter()\n                let imageDigest = try await image.resolved().digest\n                for descriptor in try await image.index().manifests {\n                    // Don't list attestation manifests\n                    if let referenceType = descriptor.annotations?[\"vnd.docker.reference.type\"],\n                        referenceType == \"attestation-manifest\"\n                    {\n                        continue\n                    }\n\n                    guard let platform = descriptor.platform else {\n                        continue\n                    }\n\n                    let os = platform.os\n                    let arch = platform.architecture\n                    let variant = platform.variant ?? \"\"\n\n                    var config: ContainerizationOCI.Image\n                    var manifest: ContainerizationOCI.Manifest\n                    do {\n                        config = try await image.config(for: platform)\n                        manifest = try await image.manifest(for: platform)\n                    } catch {\n                        continue\n                    }\n\n                    let created = config.created ?? \"\"\n                    let size = descriptor.size + manifest.config.size + manifest.layers.reduce(0, { (l, r) in l + r.size })\n                    let formattedSize = formatter.string(fromByteCount: size)\n\n                    let processedReferenceString = try ClientImage.denormalizeReference(image.reference)\n                    let reference = try ContainerizationOCI.Reference.parse(processedReferenceString)\n                    let row = [\n                        reference.name,\n                        reference.tag ?? \"<none>\",\n                        Utility.trimDigest(digest: imageDigest),\n                        os,\n                        arch,\n                        variant,\n                        formattedSize,\n                        created,\n                        Utility.trimDigest(digest: descriptor.digest),\n                    ]\n                    rows.append(row)\n                }\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n\n        static func printImages(images: [ClientImage], format: ListFormat, options: ListImageOptions) async throws {\n            var images = images\n            images.sort {\n                $0.reference < $1.reference\n            }\n\n            if format == .json {\n                var printableImages: [PrintableImage] = []\n                for image in images {\n                    let formatter = ByteCountFormatter()\n                    let size = try await ClientImage.getFullImageSize(image: image)\n                    let formattedSize = formatter.string(fromByteCount: size)\n\n                    printableImages.append(\n                        PrintableImage(reference: image.reference, fullSize: formattedSize, descriptor: image.descriptor)\n                    )\n                }\n                let data = try JSONEncoder().encode(printableImages)\n                print(String(decoding: data, as: UTF8.self))\n                return\n            }\n\n            if options.quiet {\n                try images.forEach { image in\n                    let processedReferenceString = try ClientImage.denormalizeReference(image.reference)\n                    print(processedReferenceString)\n                }\n                return\n            }\n\n            if options.verbose {\n                try await Self.printImagesVerbose(images: images)\n                return\n            }\n\n            var rows = createHeader()\n            for image in images {\n                let processedReferenceString = try ClientImage.denormalizeReference(image.reference)\n                let reference = try ContainerizationOCI.Reference.parse(processedReferenceString)\n                let digest = try await image.resolved().digest\n                rows.append([\n                    reference.name,\n                    reference.tag ?? \"<none>\",\n                    Utility.trimDigest(digest: digest),\n                ])\n            }\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n\n        static func validate(options: ListImageOptions) throws {\n            if options.quiet && options.verbose {\n                throw ContainerizationError(.invalidArgument, message: \"cannot use flag --quiet and --verbose together\")\n            }\n            let modifier = options.quiet || options.verbose\n            if modifier && options.format == .json {\n                throw ContainerizationError(.invalidArgument, message: \"cannot use flag --quiet or --verbose along with --format json\")\n            }\n        }\n\n        static func listImages(options: ListImageOptions) async throws {\n            let images = try await ClientImage.list().filter { img in\n                !Utility.isInfraImage(name: img.reference)\n            }\n            try await printImages(images: images, format: options.format, options: options)\n        }\n\n        struct PrintableImage: Codable {\n            let reference: String\n            let fullSize: String\n            let descriptor: Descriptor\n\n            init(reference: String, fullSize: String, descriptor: Descriptor) {\n                self.reference = reference\n                self.fullSize = fullSize\n                self.descriptor = descriptor\n            }\n        }\n    }\n\n    public struct ImageList: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List images\",\n            aliases: [\"ls\"])\n\n        @OptionGroup\n        var options: ListImageOptions\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public mutating func run() async throws {\n            try ListImageImplementation.validate(options: options)\n            try await ListImageImplementation.listImages(options: options)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageLoad.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationError\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct ImageLoad: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"load\",\n            abstract: \"Load images from an OCI compatible tar archive\"\n        )\n\n        @Option(\n            name: .shortAndLong, help: \"Path to the image tar archive\", completion: .file(),\n            transform: { str in\n                URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false)\n            })\n        var input: String?\n\n        @Flag(name: .shortAndLong, help: \"Load images even if the archive contains invalid files\")\n        public var force = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public func run() async throws {\n            let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(\"\\(UUID().uuidString).tar\")\n            defer {\n                try? FileManager.default.removeItem(at: tempFile)\n            }\n\n            // Read from stdin; otherwise read from the input file\n            if input == nil {\n                guard FileManager.default.createFile(atPath: tempFile.path(), contents: nil) else {\n                    throw ContainerizationError(.internalError, message: \"unable to create temporary file\")\n                }\n\n                guard let fileHandle = try? FileHandle(forWritingTo: tempFile) else {\n                    throw ContainerizationError(.internalError, message: \"unable to open temporary file for writing\")\n                }\n\n                let bufferSize = 4096\n                while true {\n                    let chunk = FileHandle.standardInput.readData(ofLength: bufferSize)\n                    if chunk.isEmpty { break }\n                    fileHandle.write(chunk)\n                }\n                try fileHandle.close()\n            } else {\n                guard FileManager.default.fileExists(atPath: input!) else {\n                    print(\"File does not exist \\(input!)\")\n                    Application.exit(withError: ArgumentParser.ExitCode(1))\n                }\n            }\n\n            let progressConfig = try ProgressConfig(\n                showTasks: true,\n                showItems: true,\n                totalTasks: 2\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            progress.set(description: \"Loading tar archive\")\n            let result = try await ClientImage.load(\n                from: input ?? tempFile.path(),\n                force: force)\n            if !result.rejectedMembers.isEmpty {\n                log.warning(\"archive contains invalid members\", metadata: [\"paths\": \"\\(result.rejectedMembers)\"])\n            }\n\n            let taskManager = ProgressTaskCoordinator()\n            let unpackTask = await taskManager.startTask()\n            progress.set(description: \"Unpacking image\")\n            progress.set(itemsName: \"entries\")\n            for image in result.images {\n                try await image.unpack(platform: nil, progressUpdate: ProgressTaskCoordinator.handler(for: unpackTask, from: progress.handler))\n            }\n            await taskManager.finish()\n            progress.finish()\n            print(\"Loaded images:\")\n            for image in result.images {\n                print(image.reference)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImagePrune.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationOCI\nimport Foundation\n\nextension Application {\n    public struct ImagePrune: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"prune\",\n            abstract: \"Remove all dangling images. If -a is specified, also remove all images not referenced by any container.\")\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Flag(name: .shortAndLong, help: \"Remove all unused images, not just dangling ones\")\n        var all: Bool = false\n\n        public func run() async throws {\n            let allImages = try await ClientImage.list()\n\n            let imagesToPrune: [ClientImage]\n            if all {\n                // Find all images not used by any container\n                let client = ContainerClient()\n                let containers = try await client.list()\n                var imagesInUse = Set<String>()\n                for container in containers {\n                    imagesInUse.insert(container.configuration.image.reference)\n                }\n                imagesToPrune = allImages.filter { image in\n                    !imagesInUse.contains(image.reference)\n                }\n            } else {\n                // Find dangling images (images with no tag)\n                imagesToPrune = allImages.filter { image in\n                    !hasTag(image.reference)\n                }\n            }\n\n            var prunedImages = [String]()\n\n            for image in imagesToPrune {\n                do {\n                    try await ClientImage.delete(reference: image.reference, garbageCollect: false)\n                    prunedImages.append(image.reference)\n                } catch {\n                    log.error(\n                        \"failed to prune image\",\n                        metadata: [\n                            \"ref\": \"\\(image.reference)\",\n                            \"error\": \"\\(error)\",\n                        ])\n                }\n            }\n\n            let (deletedDigests, size) = try await ClientImage.cleanUpOrphanedBlobs()\n\n            for image in imagesToPrune {\n                print(\"untagged \\(image.reference)\")\n            }\n            for digest in deletedDigests {\n                print(\"deleted \\(digest)\")\n            }\n\n            let formatter = ByteCountFormatter()\n            formatter.countStyle = .file\n            let freed = formatter.string(fromByteCount: Int64(size))\n            print(\"Reclaimed \\(freed) in disk space\")\n        }\n\n        private func hasTag(_ reference: String) -> Bool {\n            do {\n                let ref = try ContainerizationOCI.Reference.parse(reference)\n                return ref.tag != nil && !ref.tag!.isEmpty\n            } catch {\n                return false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImagePull.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationOCI\nimport TerminalProgress\n\nextension Application {\n    public struct ImagePull: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"pull\",\n            abstract: \"Pull an image\"\n        )\n\n        @OptionGroup\n        var registry: Flags.Registry\n\n        @OptionGroup\n        var progressFlags: Flags.Progress\n\n        @OptionGroup\n        var imageFetchFlags: Flags.ImageFetch\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Limit the pull to the specified architecture\"\n        )\n        var arch: String?\n\n        @Option(\n            help: \"Limit the pull to the specified OS\"\n        )\n        var os: String?\n\n        @Option(\n            help: \"Limit the pull to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch) [environment: CONTAINER_DEFAULT_PLATFORM]\"\n        )\n        var platform: String?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument var reference: String\n\n        public init() {}\n\n        public init(platform: String? = nil, scheme: String = \"auto\", reference: String) {\n            self.logOptions = Flags.Logging()\n            self.registry = Flags.Registry(scheme: scheme)\n            self.platform = platform\n            self.reference = reference\n        }\n\n        public func run() async throws {\n            let p = try DefaultPlatform.resolve(platform: platform, os: os, arch: arch, log: log)\n\n            let scheme = try RequestScheme(registry.scheme)\n\n            let processedReference = try ClientImage.normalizeReference(reference)\n\n            var progressConfig: ProgressConfig\n            switch self.progressFlags.progress {\n            case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)\n            case .ansi:\n                progressConfig = try ProgressConfig(\n                    showTasks: true,\n                    showItems: true,\n                    ignoreSmallSize: true,\n                    totalTasks: 2\n                )\n            }\n\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            progress.set(description: \"Fetching image\")\n            progress.set(itemsName: \"blobs\")\n            let taskManager = ProgressTaskCoordinator()\n            let fetchTask = await taskManager.startTask()\n            let image = try await ClientImage.pull(\n                reference: processedReference, platform: p, scheme: scheme, progressUpdate: ProgressTaskCoordinator.handler(for: fetchTask, from: progress.handler),\n                maxConcurrentDownloads: self.imageFetchFlags.maxConcurrentDownloads\n            )\n\n            progress.set(description: \"Unpacking image\")\n            progress.set(itemsName: \"entries\")\n            let unpackTask = await taskManager.startTask()\n            try await image.unpack(platform: p, progressUpdate: ProgressTaskCoordinator.handler(for: unpackTask, from: progress.handler))\n            await taskManager.finish()\n            progress.finish()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImagePush.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationOCI\nimport TerminalProgress\n\nextension Application {\n    public struct ImagePush: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"push\",\n            abstract: \"Push an image\"\n        )\n\n        @OptionGroup\n        var registry: Flags.Registry\n\n        @OptionGroup\n        var progressFlags: Flags.Progress\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Limit the push to the specified architecture\"\n        )\n        var arch: String?\n\n        @Option(\n            help: \"Limit the push to the specified OS\"\n        )\n        var os: String?\n\n        @Option(help: \"Limit the push to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch) [environment: CONTAINER_DEFAULT_PLATFORM]\")\n        var platform: String?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument var reference: String\n\n        public init() {}\n\n        public func run() async throws {\n            let p = try DefaultPlatform.resolve(platform: platform, os: os, arch: arch, log: log)\n\n            let scheme = try RequestScheme(registry.scheme)\n            let image = try await ClientImage.get(reference: reference)\n\n            var progressConfig: ProgressConfig\n            switch self.progressFlags.progress {\n            case .none: progressConfig = try ProgressConfig(disableProgressUpdates: true)\n            case .ansi:\n                progressConfig = try ProgressConfig(\n                    description: \"Pushing image \\(image.reference)\",\n                    itemsName: \"blobs\",\n                    showItems: true,\n                    showSpeed: false,\n                    ignoreSmallSize: true\n                )\n            }\n\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n            _ = try await image.push(platform: p, scheme: scheme, progressUpdate: progress.handler)\n            progress.finish()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageSave.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct ImageSave: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"save\",\n            abstract: \"Save one or more images as an OCI compatible tar archive\"\n        )\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Architecture for the saved image\"\n        )\n        var arch: String?\n\n        @Option(\n            help: \"OS for the saved image\"\n        )\n        var os: String?\n\n        @Option(\n            name: .shortAndLong, help: \"Pathname for the saved image\", completion: .file(),\n            transform: { str in\n                URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false)\n            })\n        var output: String?\n\n        @Option(\n            help: \"Platform for the saved image (format: os/arch[/variant], takes precedence over --os and --arch) [environment: CONTAINER_DEFAULT_PLATFORM]\"\n        )\n        var platform: String?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument var references: [String]\n\n        public func run() async throws {\n            let p = try DefaultPlatform.resolve(platform: platform, os: os, arch: arch, log: log)\n\n            let progressConfig = try ProgressConfig(\n                description: \"Saving image(s)\"\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n\n            var images: [ImageDescription] = []\n            for reference in references {\n                do {\n                    images.append(try await ClientImage.get(reference: reference).description)\n                } catch {\n                    print(\"failed to get image for reference \\(reference): \\(error)\")\n                }\n            }\n\n            guard images.count == references.count else {\n                throw ContainerizationError(.invalidArgument, message: \"failed to save image(s)\")\n            }\n\n            // Write to stdout; otherwise write to the output file\n            if output == nil {\n                let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(\"\\(UUID().uuidString).tar\")\n                defer {\n                    try? FileManager.default.removeItem(at: tempFile)\n                }\n\n                guard FileManager.default.createFile(atPath: tempFile.path(), contents: nil) else {\n                    throw ContainerizationError(.internalError, message: \"unable to create temporary file\")\n                }\n\n                try await ClientImage.save(references: references, out: tempFile.path(), platform: p)\n\n                guard let fileHandle = try? FileHandle(forReadingFrom: tempFile) else {\n                    throw ContainerizationError(.internalError, message: \"unable to open temporary file for reading\")\n                }\n\n                let bufferSize = 4096\n                while true {\n                    let chunk = fileHandle.readData(ofLength: bufferSize)\n                    if chunk.isEmpty { break }\n                    FileHandle.standardOutput.write(chunk)\n                }\n                try fileHandle.close()\n            } else {\n                try await ClientImage.save(references: references, out: output!, platform: p)\n            }\n\n            progress.finish()\n            for reference in references {\n                print(reference)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Image/ImageTag.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct ImageTag: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"tag\",\n            abstract: \"Create a new reference for an existing image\")\n\n        @Argument(help: \"The existing image reference (format: image-name[:tag])\")\n        var source: String\n\n        @Argument(help: \"The new image reference\")\n        var target: String\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public func run() async throws {\n            let existing = try await ClientImage.get(reference: source)\n            let targetReference = try ClientImage.normalizeReference(target)\n            try await existing.tag(new: targetReference)\n            print(target)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct NetworkCommand: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"network\",\n            abstract: \"Manage container networks\",\n            subcommands: [\n                NetworkCreate.self,\n                NetworkDelete.self,\n                NetworkList.self,\n                NetworkInspect.self,\n                NetworkPrune.self,\n            ],\n            aliases: [\"n\"]\n        )\n\n        public init() {}\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkCreate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct NetworkCreate: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"create\",\n            abstract: \"Create a new network\")\n\n        @Option(name: .customLong(\"label\"), help: \"Set metadata for a network\")\n        var labels: [String] = []\n\n        @Flag(name: .customLong(\"internal\"), help: \"Restrict to host-only network\")\n        var hostOnly: Bool = false\n\n        @Option(\n            name: .customLong(\"subnet\"), help: \"Set subnet for a network\",\n            transform: {\n                try CIDRv4($0)\n            })\n        var ipv4Subnet: CIDRv4? = nil\n\n        @Option(\n            name: .customLong(\"subnet-v6\"), help: \"Set the IPv6 prefix for a network\",\n            transform: {\n                try CIDRv6($0)\n            })\n        var ipv6Subnet: CIDRv6? = nil\n\n        @Option(name: .long, help: \"Set the plugin to use to create this network.\")\n        var plugin: String = \"container-network-vmnet\"\n\n        @Option(name: .long, help: \"Set the variant of the network plugin to use.\")\n        var pluginVariant: String?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Network name\")\n        var name: String\n\n        public init() {}\n\n        public func run() async throws {\n            let parsedLabels = Utility.parseKeyValuePairs(labels)\n            let mode: NetworkMode = hostOnly ? .hostOnly : .nat\n            let config = try NetworkConfiguration(\n                id: self.name,\n                mode: mode,\n                ipv4Subnet: ipv4Subnet,\n                ipv6Subnet: ipv6Subnet,\n                labels: parsedLabels,\n                pluginInfo: NetworkPluginInfo(plugin: self.plugin, variant: self.pluginVariant)\n            )\n            let state = try await ClientNetwork.create(configuration: config)\n            print(state.id)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct NetworkDelete: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"delete\",\n            abstract: \"Delete one or more networks\",\n            aliases: [\"rm\"])\n\n        @Flag(name: .shortAndLong, help: \"Delete all networks\")\n        var all = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Network names\")\n        var networkNames: [String] = []\n\n        public init() {}\n\n        public func validate() throws {\n            if networkNames.count == 0 && !all {\n                throw ContainerizationError(.invalidArgument, message: \"no networks specified and --all not supplied\")\n            }\n            if networkNames.count > 0 && all {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"explicitly supplied network name(s) conflict with the --all flag\"\n                )\n            }\n        }\n\n        public mutating func run() async throws {\n            let uniqueNetworkNames = Set<String>(networkNames)\n            let networks: [NetworkState]\n\n            if all {\n                networks = try await ClientNetwork.list()\n                    .filter { !$0.isBuiltin }\n            } else {\n                networks = try await ClientNetwork.list()\n                    .filter { c in\n                        guard uniqueNetworkNames.contains(c.id) else {\n                            return false\n                        }\n                        guard !c.isBuiltin else {\n                            throw ContainerizationError(\n                                .invalidArgument,\n                                message: \"cannot delete a builtin network: \\(c.id)\"\n                            )\n                        }\n                        return true\n                    }\n\n                // If one of the networks requested isn't present lets throw. We don't need to do\n                // this for --all as --all should be perfectly usable with no networks to remove,\n                // otherwise it'd be quite clunky.\n                if networks.count != uniqueNetworkNames.count {\n                    let missing = uniqueNetworkNames.filter { id in\n                        !networks.contains { n in\n                            n.id == id\n                        }\n                    }\n                    throw ContainerizationError(\n                        .notFound,\n                        message: \"failed to delete one or more networks: \\(missing)\"\n                    )\n                }\n            }\n\n            var failed = [String]()\n            let _log = log\n            try await withThrowingTaskGroup(of: NetworkState?.self) { group in\n                for network in networks {\n                    group.addTask {\n                        do {\n                            // Delete atomically disables the IP allocator, then deletes\n                            // the allocator. The disable fails if any IPs are still in use.\n                            try await ClientNetwork.delete(id: network.id)\n                            print(network.id)\n                            return nil\n                        } catch {\n                            _log.error(\n                                \"failed to delete network\",\n                                metadata: [\n                                    \"id\": \"\\(network.id)\",\n                                    \"error\": \"\\(error)\",\n                                ])\n                            return network\n                        }\n                    }\n                }\n\n                for try await network in group {\n                    guard let network else {\n                        continue\n                    }\n                    failed.append(network.id)\n                }\n            }\n\n            if failed.count > 0 {\n                throw ContainerizationError(.internalError, message: \"delete failed for one or more networks: \\(failed)\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkInspect.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Foundation\nimport SwiftProtobuf\n\nextension Application {\n    public struct NetworkInspect: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"inspect\",\n            abstract: \"Display information about one or more networks\")\n\n        @Argument(help: \"Networks to inspect\")\n        var networks: [String]\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let objects: [any Codable] = try await ClientNetwork.list().filter {\n                networks.contains($0.id)\n            }.map {\n                PrintableNetwork($0)\n            }\n            print(try objects.jsonArray())\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationExtras\nimport Foundation\nimport SwiftProtobuf\n\nextension Application {\n    public struct NetworkList: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List networks\",\n            aliases: [\"ls\"])\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the network name\")\n        var quiet = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let networks = try await ClientNetwork.list()\n            try printNetworks(networks: networks, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"NETWORK\", \"STATE\", \"SUBNET\"]]\n        }\n\n        func printNetworks(networks: [NetworkState], format: ListFormat) throws {\n            if format == .json {\n                let printables = networks.map {\n                    PrintableNetwork($0)\n                }\n                let data = try JSONEncoder().encode(printables)\n                print(String(decoding: data, as: UTF8.self))\n\n                return\n            }\n\n            if self.quiet {\n                networks.forEach {\n                    print($0.id)\n                }\n                return\n            }\n\n            var rows = createHeader()\n            for network in networks {\n                rows.append(network.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\n\nextension NetworkState {\n    var asRow: [String] {\n        switch self {\n        case .created(_):\n            return [self.id, self.state, \"none\"]\n        case .running(_, let status):\n            return [self.id, self.state, status.ipv4Subnet.description]\n        }\n    }\n}\n\npublic struct PrintableNetwork: Codable {\n    let id: String\n    let state: String\n    let config: NetworkConfiguration\n    let status: NetworkStatus?\n\n    public init(_ network: NetworkState) {\n        self.id = network.id\n        self.state = network.state\n        switch network {\n        case .created(let config):\n            self.config = config\n            self.status = nil\n        case .running(let config, let status):\n            self.config = config\n            self.status = status\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Network/NetworkPrune.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Foundation\n\nextension Application.NetworkCommand {\n    public struct NetworkPrune: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"prune\",\n            abstract: \"Remove networks with no container connections\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public func run() async throws {\n            let client = ContainerClient()\n            let allContainers = try await client.list()\n            let allNetworks = try await ClientNetwork.list()\n\n            var networksInUse = Set<String>()\n            for container in allContainers {\n                for network in container.configuration.networks {\n                    networksInUse.insert(network.network)\n                }\n            }\n\n            let networksToPrune = allNetworks.filter { network in\n                !network.isBuiltin && !networksInUse.contains(network.id)\n            }\n\n            var prunedNetworks = [String]()\n\n            for network in networksToPrune {\n                do {\n                    try await ClientNetwork.delete(id: network.id)\n                    prunedNetworks.append(network.id)\n                } catch {\n                    // Note: This failure may occur due to a race condition between the network/\n                    // container collection above and a container run command that attaches to a\n                    // network listed in the networksToPrune collection.\n                    log.error(\n                        \"failed to prune network\",\n                        metadata: [\n                            \"id\": \"\\(network.id)\",\n                            \"error\": \"\\(error)\",\n                        ])\n                }\n            }\n\n            for name in prunedNetworks {\n                print(name)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Registry/RegistryCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct RegistryCommand: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"registry\",\n            abstract: \"Manage registry logins\",\n            subcommands: [\n                RegistryLogin.self,\n                RegistryLogout.self,\n                RegistryList.self,\n            ],\n            aliases: [\"r\"]\n        )\n\n        public init() {}\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Registry/RegistryList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\n\nextension Application {\n    public struct RegistryList: AsyncLoggableCommand {\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the registry name\")\n        var quiet = false\n\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List image registry logins\",\n            aliases: [\"ls\"])\n\n        public func run() async throws {\n            let keychain = KeychainHelper(securityDomain: Constants.keychainID)\n            let registryInfos = try keychain.list()\n            let registries = registryInfos.map { RegistryResource(from: $0) }\n\n            try printRegistries(registries: registries, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"HOSTNAME\", \"USERNAME\", \"MODIFIED\", \"CREATED\"]]\n        }\n\n        private func printRegistries(registries: [RegistryResource], format: ListFormat) throws {\n            if format == .json {\n                let data = try JSONEncoder().encode(registries)\n                print(String(decoding: data, as: UTF8.self))\n                return\n            }\n\n            if self.quiet {\n                registries.forEach {\n                    print($0.name)\n                }\n                return\n            }\n\n            var rows = createHeader()\n            for registry in registries {\n                rows.append(registry.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\nextension RegistryResource {\n    fileprivate var asRow: [String] {\n        [\n            self.name,\n            self.username,\n            self.modificationDate.ISO8601Format(),\n            self.creationDate.ISO8601Format(),\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Registry/RegistryLogin.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\n\nextension Application {\n    public struct RegistryLogin: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"login\",\n            abstract: \"Log in to a registry\"\n        )\n\n        @OptionGroup\n        var registry: Flags.Registry\n\n        @Flag(help: \"Take the password from stdin\")\n        var passwordStdin: Bool = false\n\n        @Option(name: .shortAndLong, help: \"Registry user name\")\n        var username: String = \"\"\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Registry server name\")\n        var server: String\n\n        public func run() async throws {\n            var username = self.username\n            var password = \"\"\n            if passwordStdin {\n                if username == \"\" {\n                    throw ContainerizationError(\n                        .invalidArgument, message: \"must provide --username with --password-stdin\")\n                }\n                guard let passwordData = try FileHandle.standardInput.readToEnd() else {\n                    throw ContainerizationError(.invalidArgument, message: \"failed to read password from stdin\")\n                }\n                password = String(decoding: passwordData, as: UTF8.self).trimmingCharacters(in: .whitespacesAndNewlines)\n            }\n            let keychain = KeychainHelper(securityDomain: Constants.keychainID)\n            if username == \"\" {\n                username = try keychain.userPrompt(hostname: server)\n            }\n            if password == \"\" {\n                password = try keychain.passwordPrompt()\n                print()\n            }\n\n            let server = Reference.resolveDomain(domain: server)\n            let scheme = try RequestScheme(registry.scheme).schemeFor(host: server)\n            let _url = \"\\(scheme)://\\(server)\"\n            guard let url = URL(string: _url) else {\n                throw ContainerizationError(.invalidArgument, message: \"cannot convert \\(_url) to URL\")\n            }\n            guard let host = url.host else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid host \\(server)\")\n            }\n\n            let client = RegistryClient(\n                host: host,\n                scheme: scheme.rawValue,\n                port: url.port,\n                authentication: BasicAuthentication(username: username, password: password),\n                retryOptions: .init(\n                    maxRetries: 10,\n                    retryInterval: 300_000_000,\n                    shouldRetry: ({ response in\n                        response.status.code >= 500\n                    })\n                )\n            )\n            try await client.ping()\n            try keychain.save(hostname: server, username: username, password: password)\n            print(\"Login succeeded\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Registry/RegistryLogout.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationOCI\n\nextension Application {\n    public struct RegistryLogout: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"logout\",\n            abstract: \"Log out from a registry\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Registry server name\")\n        var registry: String\n\n        public func run() async throws {\n            let keychain = KeychainHelper(securityDomain: Constants.keychainID)\n            let r = Reference.resolveDomain(domain: registry)\n            try keychain.delete(hostname: r)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/DNS/DNSCreate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\nextension Application {\n    public struct DNSCreate: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"create\",\n            abstract: \"Create a local DNS domain for containers (must run as an administrator)\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Option(name: .long, help: \"Set the ip address to be redirected to localhost\")\n        var localhost: String?\n\n        @Argument(help: \"The local domain name\")\n        var domainName: String\n\n        public init() {}\n\n        public func run() async throws {\n            var localhostIP: IPAddress? = nil\n            if let localhost {\n                localhostIP = try? IPAddress(localhost)\n                guard let localhostIP, case .v4(_) = localhostIP else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid IPv4 address: \\(localhost)\")\n                }\n            }\n\n            let resolver: HostDNSResolver = HostDNSResolver()\n            do {\n                try resolver.createDomain(name: domainName, localhost: localhostIP)\n            } catch let error as ContainerizationError {\n                throw error\n            } catch {\n                throw ContainerizationError(.invalidState, message: \"cannot create domain (try sudo?)\")\n            }\n\n            let pf = PacketFilter()\n            if let from = localhostIP {\n                let to = try! IPAddress(\"127.0.0.1\")\n                do {\n                    try pf.createRedirectRule(from: from, to: to, domain: domainName)\n                } catch {\n                    _ = try resolver.deleteDomain(name: domainName)\n                    throw error\n                }\n            }\n            print(domainName)\n\n            if localhostIP != nil {\n                do {\n                    try pf.reinitialize()\n                } catch let error as ContainerizationError {\n                    throw error\n                } catch {\n                    throw ContainerizationError(.invalidState, message: \"failed loading pf rules\")\n                }\n            }\n\n            do {\n                try HostDNSResolver.reinitialize()\n            } catch {\n                throw ContainerizationError(.invalidState, message: \"mDNSResponder restart failed, run `sudo killall -HUP mDNSResponder` to deactivate domain\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/DNS/DNSDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\nextension Application {\n    public struct DNSDelete: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"delete\",\n            abstract: \"Delete a local DNS domain (must run as an administrator)\",\n            aliases: [\"rm\"]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"The local domain name\")\n        var domainName: String\n\n        public init() {}\n\n        public func run() async throws {\n            let resolver = HostDNSResolver()\n            var localhostIP: IPAddress?\n            do {\n                localhostIP = try resolver.deleteDomain(name: domainName)\n            } catch {\n                throw ContainerizationError(.invalidState, message: \"cannot delete domain (try sudo?)\")\n            }\n\n            do {\n                try HostDNSResolver.reinitialize()\n            } catch {\n                throw ContainerizationError(.invalidState, message: \"mDNSResponder restart failed, run `sudo killall -HUP mDNSResponder` to deactivate domain\")\n            }\n\n            guard let localhostIP else {\n                print(domainName)\n                return\n            }\n\n            let pf = PacketFilter()\n            try pf.removeRedirectRule(from: localhostIP, to: try! IPAddress(\"127.0.0.1\"), domain: domainName)\n\n            do {\n                try pf.reinitialize()\n            } catch let error as ContainerizationError {\n                throw error\n            } catch {\n                throw ContainerizationError(.invalidState, message: \"failed loading pf rules\")\n            }\n            print(domainName)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/DNS/DNSList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Foundation\n\nextension Application {\n    public struct DNSList: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List local DNS domains\",\n            aliases: [\"ls\"]\n        )\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the domain\")\n        var quiet = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let resolver: HostDNSResolver = HostDNSResolver()\n            let domains = resolver.listDomains()\n            try printDomains(domains: domains, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"DOMAIN\"]]\n        }\n\n        func printDomains(domains: [String], format: ListFormat) throws {\n            if format == .json {\n                let data = try JSONEncoder().encode(domains)\n                print(String(decoding: data, as: UTF8.self))\n\n                return\n            }\n\n            if self.quiet {\n                domains.forEach { domain in\n                    print(domain)\n                }\n                return\n            }\n\n            var rows = createHeader()\n            for domain in domains {\n                rows.append([domain])\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/Kernel/KernelSet.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport TerminalProgress\n\nextension Application {\n    public struct KernelSet: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"set\",\n            abstract: \"Set the default kernel\"\n        )\n\n        @Option(name: .long, help: \"The architecture of the kernel binary (values: amd64, arm64)\")\n        var arch: String = ContainerizationOCI.Platform.current.architecture.description\n\n        @Option(name: .customLong(\"binary\"), help: \"Path to the kernel file (or archive member, if used with --tar)\")\n        var binaryPath: String? = nil\n\n        @Flag(name: .long, help: \"Overwrites an existing kernel with the same name\")\n        var force: Bool = false\n\n        @Flag(name: .long, help: \"Download and install the recommended kernel as the default (takes precedence over all other flags)\")\n        var recommended: Bool = false\n\n        @Option(name: .customLong(\"tar\"), help: \"Filesystem path or remote URL to a tar archive containing a kernel file\")\n        var tarPath: String? = nil\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            if recommended {\n                let url = DefaultsStore.get(key: .defaultKernelURL)\n                let path = DefaultsStore.get(key: .defaultKernelBinaryPath)\n                print(\"Installing the recommended kernel from \\(url)...\")\n                try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: url, kernelFilePath: path, force: force)\n                return\n            }\n            guard tarPath != nil else {\n                return try await self.setKernelFromBinary()\n            }\n            try await self.setKernelFromTar()\n        }\n\n        private func setKernelFromBinary() async throws {\n            guard let binaryPath else {\n                throw ArgumentParser.ValidationError(\"missing argument '--binary'\")\n            }\n            let absolutePath = URL(fileURLWithPath: binaryPath, relativeTo: .currentDirectory()).absoluteURL.absoluteString\n            let platform = try getSystemPlatform()\n            try await ClientKernel.installKernel(kernelFilePath: absolutePath, platform: platform, force: force)\n        }\n\n        private func setKernelFromTar() async throws {\n            guard let binaryPath else {\n                throw ArgumentParser.ValidationError(\"missing argument '--binary'\")\n            }\n            guard let tarPath else {\n                throw ArgumentParser.ValidationError(\"missing argument '--tar\")\n            }\n            let platform = try getSystemPlatform()\n            let localTarPath = URL(fileURLWithPath: tarPath, relativeTo: .currentDirectory()).path\n            let fm = FileManager.default\n            if fm.fileExists(atPath: localTarPath) {\n                try await ClientKernel.installKernelFromTar(tarFile: localTarPath, kernelFilePath: binaryPath, platform: platform, force: force)\n                return\n            }\n            guard let remoteURL = URL(string: tarPath) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid remote URL '\\(tarPath)' for argument '--tar'. Missing protocol?\")\n            }\n            try await Self.downloadAndInstallWithProgressBar(tarRemoteURL: remoteURL.absoluteString, kernelFilePath: binaryPath, platform: platform, force: force)\n        }\n\n        private func getSystemPlatform() throws -> SystemPlatform {\n            switch arch {\n            case \"arm64\":\n                return .linuxArm\n            case \"amd64\":\n                return .linuxAmd\n            default:\n                throw ContainerizationError(.unsupported, message: \"unsupported architecture \\(arch)\")\n            }\n        }\n\n        static func downloadAndInstallWithProgressBar(tarRemoteURL: String, kernelFilePath: String, platform: SystemPlatform = .current, force: Bool) async throws {\n            let progressConfig = try ProgressConfig(\n                showTasks: true,\n                totalTasks: 2\n            )\n            let progress = ProgressBar(config: progressConfig)\n            defer {\n                progress.finish()\n            }\n            progress.start()\n            try await ClientKernel.installKernelFromTar(tarFile: tarRemoteURL, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progress.handler, force: force)\n            progress.finish()\n        }\n\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/Property/PropertyClear.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct PropertyClear: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"clear\",\n            abstract: \"Clear a property value\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"The property ID\")\n        var id: String\n\n        public init() {}\n\n        public func run() async throws {\n            guard let key = DefaultsStore.Keys(rawValue: id) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid property ID: \\(id)\")\n            }\n\n            DefaultsStore.unset(key: key)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/Property/PropertyGet.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct PropertyGet: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"get\",\n            abstract: \"Retrieve a property value\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"The property ID\")\n        var id: String\n\n        public init() {}\n\n        public func run() async throws {\n            let value = DefaultsStore.allValues()\n                .filter { id == $0.id }\n                .first\n            guard let value else {\n                throw ContainerizationError(.invalidArgument, message: \"property ID \\(id) not found\")\n            }\n\n            guard let val = value.value?.description else {\n                return\n            }\n\n            print(val)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/Property/PropertyList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport Foundation\n\nextension Application {\n    public struct PropertyList: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List system properties\",\n            aliases: [\"ls\"]\n        )\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the property ID\")\n        var quiet = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let vals = DefaultsStore.allValues()\n            try printValues(vals, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"ID\", \"TYPE\", \"VALUE\", \"DESCRIPTION\"]]\n        }\n\n        private func printValues(_ vals: [DefaultsStoreValue], format: ListFormat) throws {\n            if format == .json {\n                let data = try JSONEncoder().encode(vals)\n                print(String(decoding: data, as: UTF8.self))\n                return\n            }\n\n            if self.quiet {\n                vals.forEach {\n                    print($0.id)\n                }\n                return\n            }\n\n            var rows = createHeader()\n            for property in vals {\n                rows.append(property.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\n\nextension DefaultsStoreValue {\n    var asRow: [String] {\n        [id, String(describing: type), value?.description.elided(to: 40) ?? \"*undefined*\", description]\n    }\n}\n\nextension String {\n    func elided(to maxCount: Int) -> String {\n        let ellipsis = \"...\"\n        guard self.count > maxCount else {\n            return self\n        }\n\n        if maxCount < ellipsis.count {\n            return ellipsis\n        }\n\n        let prefixCount = maxCount - ellipsis.count\n        return self.prefix(prefixCount) + ellipsis\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/Property/PropertySet.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\n\nextension Application {\n    public struct PropertySet: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"set\",\n            abstract: \"Set a property value\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"The property ID\")\n        var id: String\n\n        @Argument(help: \"The property value\")\n        var value: String\n\n        public init() {}\n\n        public func run() async throws {\n            guard let key = DefaultsStore.Keys(rawValue: id) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid property ID: \\(id)\")\n            }\n\n            switch key {\n            case .buildRosetta:\n                guard let boolValue = Parser.parseBool(string: value) else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid boolean value: \\(value)\")\n                }\n                DefaultsStore.setBool(value: boolValue, key: key)\n            case .defaultBuildCPUs, .defaultContainerCPUs:\n                guard let cpuCount = Int(value), cpuCount > 0 else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid CPU count: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            case .defaultBuildMemory, .defaultContainerMemory:\n                guard let memoryMiB = try? Parser.memoryStringAsMiB(value), memoryMiB > 0 else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid memory value: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            case .defaultDNSDomain, .defaultRegistryDomain:\n                guard Parser.isValidDomainName(value) else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid domain name: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            case .defaultBuilderImage, .defaultInitImage:\n                guard (try? Reference.parse(value)) != nil else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid image reference: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            case .defaultKernelBinaryPath:\n                DefaultsStore.set(value: value, key: key)\n            case .defaultKernelURL:\n                guard URL(string: value) != nil else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid URL: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n                return\n            case .defaultSubnet:\n                guard (try? CIDRv4(value)) != nil else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid CIDRv4 address: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            case .defaultIPv6Subnet:\n                guard (try? CIDRv6(value)) != nil else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid CIDRv6 address: \\(value)\")\n                }\n                DefaultsStore.set(value: value, key: key)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct SystemCommand: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"system\",\n            abstract: \"Manage system components\",\n            subcommands: [\n                SystemDF.self,\n                SystemDNS.self,\n                SystemKernel.self,\n                SystemLogs.self,\n                SystemProperty.self,\n                SystemStart.self,\n                SystemStatus.self,\n                SystemStop.self,\n                SystemVersion.self,\n            ],\n            aliases: [\"s\"]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemDF.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct SystemDF: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"df\",\n            abstract: \"Show disk usage for images, containers, and volumes\"\n        )\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let stats = try await ClientDiskUsage.get()\n\n            if format == .json {\n                let encoder = JSONEncoder()\n                encoder.outputFormatting = [.prettyPrinted, .sortedKeys]\n                let data = try encoder.encode(stats)\n                guard let jsonString = String(data: data, encoding: .utf8) else {\n                    throw ContainerizationError(\n                        .internalError,\n                        message: \"failed to encode JSON output\"\n                    )\n                }\n                print(jsonString)\n                return\n            }\n\n            printTable(stats: stats)\n        }\n\n        private func printTable(stats: DiskUsageStats) {\n            var rows: [[String]] = []\n\n            // Header row\n            rows.append([\"TYPE\", \"TOTAL\", \"ACTIVE\", \"SIZE\", \"RECLAIMABLE\"])\n\n            // Images row\n            rows.append([\n                \"Images\",\n                \"\\(stats.images.total)\",\n                \"\\(stats.images.active)\",\n                formatSize(stats.images.sizeInBytes),\n                formatReclaimable(stats.images.reclaimable, total: stats.images.sizeInBytes),\n            ])\n\n            // Containers row\n            rows.append([\n                \"Containers\",\n                \"\\(stats.containers.total)\",\n                \"\\(stats.containers.active)\",\n                formatSize(stats.containers.sizeInBytes),\n                formatReclaimable(stats.containers.reclaimable, total: stats.containers.sizeInBytes),\n            ])\n\n            // Volumes row\n            rows.append([\n                \"Local Volumes\",\n                \"\\(stats.volumes.total)\",\n                \"\\(stats.volumes.active)\",\n                formatSize(stats.volumes.sizeInBytes),\n                formatReclaimable(stats.volumes.reclaimable, total: stats.volumes.sizeInBytes),\n            ])\n\n            let tableFormatter = TableOutput(rows: rows)\n            print(tableFormatter.format())\n        }\n\n        private func formatSize(_ bytes: UInt64) -> String {\n            if bytes == 0 {\n                return \"0 B\"\n            }\n            let formatter = ByteCountFormatter()\n            formatter.countStyle = .file\n            return formatter.string(fromByteCount: Int64(bytes))\n        }\n\n        private func formatReclaimable(_ reclaimable: UInt64, total: UInt64) -> String {\n            let sizeStr = formatSize(reclaimable)\n\n            if total == 0 {\n                return \"\\(sizeStr) (0%)\"\n            }\n\n            // Cap at 100% in case reclaimable > total (shouldn't happen but be defensive)\n            let percentage = min(100, Int(round(Double(reclaimable) / Double(total) * 100.0)))\n            return \"\\(sizeStr) (\\(percentage)%)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemDNS.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct SystemDNS: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"dns\",\n            abstract: \"Manage local DNS domains\",\n            subcommands: [\n                DNSCreate.self,\n                DNSDelete.self,\n                DNSList.self,\n            ]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemKernel.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct SystemKernel: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"kernel\",\n            abstract: \"Manage the default kernel configuration\",\n            subcommands: [\n                KernelSet.self\n            ]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemLogs.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport OSLog\n\nextension Application {\n    public struct SystemLogs: AsyncLoggableCommand {\n        public static let subsystem = \"com.apple.container\"\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"logs\",\n            abstract: \"Fetch system logs for `container` services\"\n        )\n\n        @Flag(name: .shortAndLong, help: \"Follow log output\")\n        var follow: Bool = false\n\n        @Option(\n            name: .long,\n            help: \"Fetch logs starting from the specified time period (minus the current time); supported formats: m, h, d\"\n        )\n        var last: String = \"5m\"\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let process = Process()\n            let sigHandler = AsyncSignalHandler.create(notify: [SIGINT, SIGTERM])\n\n            Task {\n                for await _ in sigHandler.signals {\n                    process.terminate()\n                    Darwin.exit(0)\n                }\n            }\n\n            do {\n                var args = [\"log\"]\n                args.append(self.follow ? \"stream\" : \"show\")\n                args.append(contentsOf: [\"--info\", logOptions.debug ? \"--debug\" : nil].compactMap { $0 })\n                if !self.follow {\n                    args.append(contentsOf: [\"--last\", last])\n                }\n                args.append(contentsOf: [\"--predicate\", \"subsystem = 'com.apple.container'\"])\n\n                process.launchPath = \"/usr/bin/env\"\n                process.arguments = args\n\n                process.standardOutput = FileHandle.standardOutput\n                process.standardError = FileHandle.standardError\n\n                try process.run()\n                process.waitUntilExit()\n            } catch {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"failed to system logs: \\(error)\"\n                )\n            }\n            throw ArgumentParser.ExitCode(process.terminationStatus)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemProperty.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationError\nimport Foundation\n\nextension Application {\n    public struct SystemProperty: AsyncLoggableCommand {\n        public init() {}\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"property\",\n            abstract: \"Manage system property values\",\n            subcommands: [\n                PropertyClear.self,\n                PropertyGet.self,\n                PropertyList.self,\n                PropertySet.self,\n            ]\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemStart.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerPlugin\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\nimport SystemPackage\nimport TerminalProgress\n\nextension Application {\n    public struct SystemStart: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Start `container` services\"\n        )\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Path to the root directory for application data\",\n            transform: { URL(filePath: $0) })\n        var appRoot = ApplicationRoot.defaultURL\n\n        @Option(\n            name: .long,\n            help: \"Path to the root directory for application executables and plugins\",\n            transform: { URL(filePath: $0) })\n        var installRoot = InstallRoot.defaultURL\n\n        @Option(\n            name: .long,\n            help: \"Path to the root directory for log data, using macOS log facility if not set\",\n            transform: { FilePath($0) })\n        var logRoot: FilePath? = nil\n\n        @Flag(\n            name: .long,\n            inversion: .prefixedEnableDisable,\n            help: \"Specify whether the default kernel should be installed or not (default: prompt user)\")\n        var kernelInstall: Bool?\n\n        @Option(\n            help: \"Number of seconds to wait for API service to become responsive\",\n            transform: {\n                guard let timeoutSeconds = Double($0) else {\n                    throw ValidationError(\"Invalid timeout value: \\($0)\")\n                }\n                return .seconds(timeoutSeconds)\n            }\n        )\n        var timeout: Duration = XPCClient.xpcRegistrationTimeout\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            // Without the true path to the binary in the plist, `container-apiserver` won't launch properly.\n            // TODO: Can we use the plugin loader to bootstrap the API server?\n            let executableUrl = CommandLine.executablePathUrl\n                .deletingLastPathComponent()\n                .appendingPathComponent(\"container-apiserver\")\n                .resolvingSymlinksInPath()\n\n            var args = [executableUrl.absolutePath()]\n\n            args.append(\"start\")\n            if logOptions.debug {\n                args.append(\"--debug\")\n            }\n\n            let apiServerDataUrl = appRoot.appending(path: \"apiserver\")\n            try! FileManager.default.createDirectory(at: apiServerDataUrl, withIntermediateDirectories: true)\n\n            var env = PluginLoader.filterEnvironment()\n            env[ApplicationRoot.environmentName] = appRoot.path(percentEncoded: false)\n            env[InstallRoot.environmentName] = installRoot.path(percentEncoded: false)\n            if let logRoot {\n                env[LogRoot.environmentName] =\n                    logRoot.isAbsolute\n                    ? logRoot.string\n                    : FilePath(FileManager.default.currentDirectoryPath).appending(logRoot.components).string\n            }\n            let plist = LaunchPlist(\n                label: \"com.apple.container.apiserver\",\n                arguments: args,\n                environment: env,\n                limitLoadToSessionType: [.Aqua, .Background, .System],\n                runAtLoad: true,\n                machServices: [\"com.apple.container.apiserver\"]\n            )\n\n            let plistURL = apiServerDataUrl.appending(path: \"apiserver.plist\")\n            let data = try plist.encode()\n            try data.write(to: plistURL)\n\n            print(\"Registering API server with launchd...\")\n            try ServiceManager.register(plistPath: plistURL.path)\n\n            // Now ping our friendly daemon. Fail if we don't get a response.\n            do {\n                print(\"Verifying apiserver is running...\")\n                _ = try await ClientHealthCheck.ping(timeout: timeout)\n            } catch {\n                throw ContainerizationError(\n                    .internalError,\n                    message: \"failed to get a response from apiserver: \\(error)\"\n                )\n            }\n\n            if await !initImageExists() {\n                try? await installInitialFilesystem()\n            }\n\n            guard await !kernelExists() else {\n                return\n            }\n            try await installDefaultKernel()\n        }\n\n        private func installInitialFilesystem() async throws {\n            let dep = Dependencies.initFs\n            var pullCommand = try ImagePull.parse()\n            pullCommand.reference = dep.source\n            print(\"Installing base container filesystem...\")\n            do {\n                try await pullCommand.run()\n            } catch {\n                log.error(\"failed to install base container filesystem\", metadata: [\"error\": \"\\(error)\"])\n            }\n        }\n\n        private func installDefaultKernel() async throws {\n            let kernelDependency = Dependencies.kernel\n            let defaultKernelURL = kernelDependency.source\n            let defaultKernelBinaryPath = DefaultsStore.get(key: .defaultKernelBinaryPath)\n\n            var shouldInstallKernel = false\n            if kernelInstall == nil {\n                print(\"No default kernel configured.\")\n                print(\"Install the recommended default kernel from [\\(kernelDependency.source)]? [Y/n]: \", terminator: \"\")\n                guard let read = readLine(strippingNewline: true) else {\n                    throw ContainerizationError(.internalError, message: \"failed to read user input\")\n                }\n                guard read.lowercased() == \"y\" || read.count == 0 else {\n                    print(\"Please use the `container system kernel set --recommended` command to configure the default kernel\")\n                    return\n                }\n                shouldInstallKernel = true\n            } else {\n                shouldInstallKernel = kernelInstall ?? false\n            }\n            guard shouldInstallKernel else {\n                return\n            }\n            print(\"Installing kernel...\")\n            try await KernelSet.downloadAndInstallWithProgressBar(tarRemoteURL: defaultKernelURL, kernelFilePath: defaultKernelBinaryPath, force: true)\n        }\n\n        private func initImageExists() async -> Bool {\n            do {\n                let img = try await ClientImage.get(reference: Dependencies.initFs.source)\n                let _ = try await img.getSnapshot(platform: .current)\n                return true\n            } catch {\n                return false\n            }\n        }\n\n        private func kernelExists() async -> Bool {\n            do {\n                try await ClientKernel.getDefaultKernel(for: .current)\n                return true\n            } catch {\n                return false\n            }\n        }\n    }\n\n    private enum Dependencies: String {\n        case kernel\n        case initFs\n\n        var source: String {\n            switch self {\n            case .initFs:\n                return DefaultsStore.get(key: .defaultInitImage)\n            case .kernel:\n                return DefaultsStore.get(key: .defaultKernelURL)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemStatus.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPlugin\nimport ContainerizationError\nimport Foundation\nimport Logging\n\nextension Application {\n    public struct SystemStatus: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"status\",\n            abstract: \"Show the status of `container` services\"\n        )\n\n        @Option(name: .shortAndLong, help: \"Launchd prefix for services\")\n        var prefix: String = \"com.apple.container.\"\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        struct PrintableStatus: Codable {\n            let status: String\n            let appRoot: String\n            let installRoot: String\n            let logRoot: String?\n            let apiServerVersion: String\n            let apiServerCommit: String\n            let apiServerBuild: String\n            let apiServerAppName: String\n        }\n\n        public func run() async throws {\n            let isRegistered = try ServiceManager.isRegistered(fullServiceLabel: \"\\(prefix)apiserver\")\n            if !isRegistered {\n                if format == .json {\n                    let status = PrintableStatus(\n                        status: \"unregistered\",\n                        appRoot: \"\",\n                        installRoot: \"\",\n                        logRoot: nil,\n                        apiServerVersion: \"\",\n                        apiServerCommit: \"\",\n                        apiServerBuild: \"\",\n                        apiServerAppName: \"\"\n                    )\n                    let data = try JSONEncoder().encode(status)\n                    print(String(decoding: data, as: UTF8.self))\n                } else {\n                    print(\"apiserver is not running and not registered with launchd\")\n                }\n                Application.exit(withError: ExitCode(1))\n            }\n\n            // Now ping our friendly daemon. Fail after 10 seconds with no response.\n            do {\n                let systemHealth = try await ClientHealthCheck.ping(timeout: .seconds(10))\n\n                if format == .json {\n                    let status = PrintableStatus(\n                        status: \"running\",\n                        appRoot: systemHealth.appRoot.path(percentEncoded: false),\n                        installRoot: systemHealth.installRoot.path(percentEncoded: false),\n                        logRoot: systemHealth.logRoot?.string,\n                        apiServerVersion: systemHealth.apiServerVersion,\n                        apiServerCommit: systemHealth.apiServerCommit,\n                        apiServerBuild: systemHealth.apiServerBuild,\n                        apiServerAppName: systemHealth.apiServerAppName\n                    )\n                    let data = try JSONEncoder().encode(status)\n                    print(String(decoding: data, as: UTF8.self))\n                } else {\n                    let rows: [[String]] = [\n                        [\"FIELD\", \"VALUE\"],\n                        [\"status\", \"running\"],\n                        [\"appRoot\", systemHealth.appRoot.path(percentEncoded: false)],\n                        [\"installRoot\", systemHealth.installRoot.path(percentEncoded: false)],\n                        [\"logRoot\", systemHealth.logRoot?.string ?? \"\"],\n                        [\"apiserver.version\", systemHealth.apiServerVersion],\n                        [\"apiserver.commit\", systemHealth.apiServerCommit],\n                        [\"apiserver.build\", systemHealth.apiServerBuild],\n                        [\"apiserver.appName\", systemHealth.apiServerAppName],\n                    ]\n                    let formatter = TableOutput(rows: rows)\n                    print(formatter.format())\n                }\n            } catch {\n                if format == .json {\n                    let status = PrintableStatus(\n                        status: \"not running\",\n                        appRoot: \"\",\n                        installRoot: \"\",\n                        logRoot: nil,\n                        apiServerVersion: \"\",\n                        apiServerCommit: \"\",\n                        apiServerBuild: \"\",\n                        apiServerAppName: \"\"\n                    )\n                    let data = try JSONEncoder().encode(status)\n                    print(String(decoding: data, as: UTF8.self))\n                } else {\n                    print(\"apiserver is not running\")\n                }\n                Application.exit(withError: ExitCode(1))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemStop.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\nextension Application {\n    public struct SystemStop: AsyncLoggableCommand {\n        private static let stopTimeoutSeconds: Int32 = 5\n        private static let shutdownTimeoutSeconds: Int32 = 20\n\n        public static let configuration = CommandConfiguration(\n            commandName: \"stop\",\n            abstract: \"Stop all `container` services\"\n        )\n\n        @Option(name: .shortAndLong, help: \"Launchd prefix for services\")\n        var prefix: String = \"com.apple.container.\"\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let log = Logger(\n                label: \"com.apple.container.cli\",\n                factory: { label in\n                    StreamLogHandler.standardOutput(label: label)\n                }\n            )\n\n            let launchdDomainString = try ServiceManager.getDomainString()\n            let fullLabel = \"\\(launchdDomainString)/\\(prefix)apiserver\"\n\n            var running = true\n            do {\n                log.info(\"checking if APIServer is alive\")\n                _ = try await ClientHealthCheck.ping(timeout: .seconds(5))\n            } catch {\n                log.info(\"APIServer health check failed, skipping bootout\")\n                running = false\n            }\n\n            if running {\n                let client = ContainerClient()\n                log.info(\"stopping containers\", metadata: [\"stopTimeoutSeconds\": \"\\(Self.stopTimeoutSeconds)\"])\n                do {\n                    let containers = try await client.list()\n                    let signal = try Signals.parseSignal(\"SIGTERM\")\n                    let opts = ContainerStopOptions(timeoutInSeconds: Self.stopTimeoutSeconds, signal: signal)\n                    try await ContainerStop.stopContainers(\n                        client: client,\n                        containers: containers,\n                        stopOptions: opts,\n                    )\n                } catch {\n                    log.warning(\"failed to stop all containers\", metadata: [\"error\": \"\\(error)\"])\n                }\n\n                log.info(\"waiting for containers to exit\")\n                do {\n                    for _ in 0..<Self.shutdownTimeoutSeconds {\n                        let runningContainers = try await client.list(filters: ContainerListFilters(status: .running))\n                        guard !runningContainers.isEmpty else {\n                            break\n                        }\n                        try await Task.sleep(for: .seconds(1))\n                    }\n\n                    log.info(\"stopping service\", metadata: [\"label\": \"\\(fullLabel)\"])\n                    try ServiceManager.deregister(fullServiceLabel: fullLabel)\n                } catch {\n                    log.warning(\"failed to wait for all containers\", metadata: [\"error\": \"\\(error)\"])\n                }\n            }\n\n            // Note: The assumption here is that we would have registered the launchd services\n            // in the same domain as `launchdDomainString`. This is a fairly sane assumption since\n            // if somehow the launchd domain changed, XPC interactions would not be possible.\n            try ServiceManager.enumerate()\n                .filter { $0.hasPrefix(prefix) }\n                .filter { $0 != fullLabel }\n                .map { \"\\(launchdDomainString)/\\($0)\" }\n                .forEach {\n                    log.info(\"stopping service\", metadata: [\"label\": \"\\($0)\"])\n                    try? ServiceManager.deregister(fullServiceLabel: $0)\n                }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/System/SystemVersion.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerVersion\nimport Foundation\n\nextension Application {\n    public struct SystemVersion: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"version\",\n            abstract: \"Show version information\"\n        )\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: ListFormat = .table\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let cliInfo = VersionInfo(\n                version: ReleaseVersion.version(),\n                buildType: ReleaseVersion.buildType(),\n                commit: ReleaseVersion.gitCommit() ?? \"unspecified\",\n                appName: \"container\"\n            )\n\n            // Try to get API server version info\n            let serverInfo: VersionInfo?\n            do {\n                let health = try await ClientHealthCheck.ping(timeout: .seconds(2))\n                serverInfo = VersionInfo(\n                    version: health.apiServerVersion,\n                    buildType: health.apiServerBuild,\n                    commit: health.apiServerCommit,\n                    appName: health.apiServerAppName\n                )\n            } catch {\n                serverInfo = nil\n            }\n\n            let versions = [cliInfo, serverInfo].compactMap { $0 }\n\n            switch format {\n            case .table:\n                printVersionTable(versions: versions)\n            case .json:\n                try printVersionJSON(versions: versions)\n            }\n        }\n\n        private func printVersionTable(versions: [VersionInfo]) {\n            let header = [\"COMPONENT\", \"VERSION\", \"BUILD\", \"COMMIT\"]\n            let rows = [header] + versions.map { [$0.appName, $0.version, $0.buildType, $0.commit] }\n\n            let table = TableOutput(rows: rows)\n            print(table.format())\n        }\n\n        private func printVersionJSON(versions: [VersionInfo]) throws {\n            let data = try JSONEncoder().encode(versions)\n            print(String(data: data, encoding: .utf8) ?? \"[]\")\n        }\n    }\n\n    public struct VersionInfo: Codable {\n        let version: String\n        let buildType: String\n        let commit: String\n        let appName: String\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumeCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\n\nextension Application {\n    public struct VolumeCommand: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"volume\",\n            abstract: \"Manage container volumes\",\n            subcommands: [\n                VolumeCreate.self,\n                VolumeDelete.self,\n                VolumeList.self,\n                VolumeInspect.self,\n                VolumePrune.self,\n            ],\n            aliases: [\"v\"]\n        )\n\n        public init() {}\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumeCreate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Foundation\n\nextension Application.VolumeCommand {\n    public struct VolumeCreate: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"create\",\n            abstract: \"Create a new volume\"\n        )\n\n        @Option(name: .customLong(\"label\"), help: \"Set metadata for a volume\")\n        var labels: [String] = []\n\n        @Option(name: .customLong(\"opt\"), help: \"Set driver specific options\")\n        var driverOpts: [String] = []\n\n        @Option(name: .short, help: \"Size of the volume in bytes, with optional K, M, G, T, or P suffix\")\n        var size: String?\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Volume name\")\n        var name: String\n\n        public init() {}\n\n        public func run() async throws {\n            var parsedDriverOpts = Utility.parseKeyValuePairs(driverOpts)\n            let parsedLabels = Utility.parseKeyValuePairs(labels)\n\n            // If --size is specified, add it to driver options\n            if let size = size {\n                parsedDriverOpts[\"size\"] = size\n            }\n\n            let volume = try await ClientVolume.create(\n                name: name,\n                driver: \"local\",\n                driverOpts: parsedDriverOpts,\n                labels: parsedLabels\n            )\n            print(volume.name)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumeDelete.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationError\nimport Foundation\n\nextension Application.VolumeCommand {\n    public struct VolumeDelete: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"delete\",\n            abstract: \"Delete one or more volumes\",\n            aliases: [\"rm\"]\n        )\n\n        @Flag(name: .shortAndLong, help: \"Delete all volumes\")\n        var all = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Volume names\")\n        var names: [String] = []\n\n        public init() {}\n\n        public func run() async throws {\n            let uniqueVolumeNames = Set<String>(names)\n            let volumes: [Volume]\n\n            if all {\n                volumes = try await ClientVolume.list()\n            } else {\n                volumes = try await ClientVolume.list()\n                    .filter { v in\n                        uniqueVolumeNames.contains(v.id)\n                    }\n\n                // If one of the volumes requested isn't present lets throw. We don't need to do\n                // this for --all as --all should be perfectly usable with no volumes to remove,\n                // otherwise it'd be quite clunky.\n                if volumes.count != uniqueVolumeNames.count {\n                    let missing = uniqueVolumeNames.filter { id in\n                        !volumes.contains { v in\n                            v.id == id\n                        }\n                    }\n                    throw ContainerizationError(\n                        .notFound,\n                        message: \"failed to delete one or more volumes: \\(missing)\"\n                    )\n                }\n            }\n\n            var failed = [String]()\n            let _log = log\n            try await withThrowingTaskGroup(of: Volume?.self) { group in\n                for volume in volumes {\n                    group.addTask {\n                        do {\n                            try await ClientVolume.delete(name: volume.id)\n                            print(volume.id)\n                            return nil\n                        } catch {\n                            _log.error(\n                                \"failed to delete volume\",\n                                metadata: [\n                                    \"id\": \"\\(volume.id)\",\n                                    \"error\": \"\\(error)\",\n                                ])\n                            return volume\n                        }\n                    }\n                }\n\n                for try await volume in group {\n                    guard let volume else {\n                        continue\n                    }\n                    failed.append(volume.id)\n                }\n            }\n\n            if failed.count > 0 {\n                throw ContainerizationError(.internalError, message: \"delete failed for one or more volumes: \\(failed)\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumeInspect.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport Foundation\n\nextension Application.VolumeCommand {\n    public struct VolumeInspect: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"inspect\",\n            abstract: \"Display information about one or more volumes\"\n        )\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        @Argument(help: \"Volumes to inspect\")\n        var names: [String]\n\n        public init() {}\n\n        public func run() async throws {\n            var volumes: [Volume] = []\n\n            for name in names {\n                let volume = try await ClientVolume.inspect(name)\n                volumes.append(volume)\n            }\n\n            let encoder = JSONEncoder()\n            encoder.outputFormatting = [.prettyPrinted, .sortedKeys]\n            encoder.dateEncodingStrategy = .iso8601\n\n            let data = try encoder.encode(volumes)\n            print(String(decoding: data, as: UTF8.self))\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumeList.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerizationExtras\nimport Foundation\n\nextension Application.VolumeCommand {\n    public struct VolumeList: AsyncLoggableCommand {\n        public static let configuration = CommandConfiguration(\n            commandName: \"list\",\n            abstract: \"List volumes\",\n            aliases: [\"ls\"]\n        )\n\n        @Option(name: .long, help: \"Format of the output\")\n        var format: Application.ListFormat = .table\n\n        @Flag(name: .shortAndLong, help: \"Only output the volume name\")\n        var quiet: Bool = false\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public init() {}\n\n        public func run() async throws {\n            let volumes = try await ClientVolume.list()\n            try printVolumes(volumes: volumes, format: format)\n        }\n\n        private func createHeader() -> [[String]] {\n            [[\"NAME\", \"TYPE\", \"DRIVER\", \"OPTIONS\"]]\n        }\n\n        func printVolumes(volumes: [Volume], format: Application.ListFormat) throws {\n            if format == .json {\n                let data = try JSONEncoder().encode(volumes)\n                print(String(decoding: data, as: UTF8.self))\n                return\n            }\n\n            if quiet {\n                volumes.forEach {\n                    print($0.name)\n                }\n                return\n            }\n\n            // Sort volumes by creation time (newest first)\n            let sortedVolumes = volumes.sorted { v1, v2 in\n                v1.createdAt > v2.createdAt\n            }\n\n            var rows = createHeader()\n            for volume in sortedVolumes {\n                rows.append(volume.asRow)\n            }\n\n            let formatter = TableOutput(rows: rows)\n            print(formatter.format())\n        }\n    }\n}\n\nextension Volume {\n    var asRow: [String] {\n        let volumeType = self.isAnonymous ? \"anonymous\" : \"named\"\n        let optionsString = options.isEmpty ? \"\" : options.map { \"\\($0.key)=\\($0.value)\" }.joined(separator: \",\")\n        return [\n            self.name,\n            volumeType,\n            self.driver,\n            optionsString,\n        ]\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerCommands/Volume/VolumePrune.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport Foundation\n\nextension Application.VolumeCommand {\n    public struct VolumePrune: AsyncLoggableCommand {\n        public init() {}\n        public static let configuration = CommandConfiguration(\n            commandName: \"prune\",\n            abstract: \"Remove volumes with no container references\")\n\n        @OptionGroup\n        public var logOptions: Flags.Logging\n\n        public func run() async throws {\n            let allVolumes = try await ClientVolume.list()\n\n            // Find all volumes not used by any container\n            let client = ContainerClient()\n            let containers = try await client.list()\n            var volumesInUse = Set<String>()\n            for container in containers {\n                for mount in container.configuration.mounts {\n                    if mount.isVolume, let volumeName = mount.volumeName {\n                        volumesInUse.insert(volumeName)\n                    }\n                }\n            }\n\n            let volumesToPrune = allVolumes.filter { volume in\n                !volumesInUse.contains(volume.name)\n            }\n\n            var prunedVolumes = [String]()\n            var totalSize: UInt64 = 0\n\n            for volume in volumesToPrune {\n                do {\n                    let actualSize = try await ClientVolume.volumeDiskUsage(name: volume.name)\n                    totalSize += actualSize\n                    try await ClientVolume.delete(name: volume.name)\n                    prunedVolumes.append(volume.name)\n                } catch {\n                    log.error(\n                        \"failed to prune volume\",\n                        metadata: [\n                            \"id\": \"\\(volume.name)\",\n                            \"error\": \"\\(error)\",\n                        ])\n                }\n            }\n\n            for name in prunedVolumes {\n                print(name)\n            }\n\n            let formatter = ByteCountFormatter()\n            let freed = formatter.string(fromByteCount: Int64(totalSize))\n            print(\"Reclaimed \\(freed) in disk space\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerLog/FileLogHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\nimport SystemPackage\n\n/// Log handler that appends messages to a file, without any\n/// rotation or truncation strategy. Use for development purposes only.\npublic struct FileLogHandler: LogHandler {\n    public var logLevel: Logger.Level = .info\n    public var metadata: Logger.Metadata = [:]\n\n    private let label: String\n    private let category: String\n    private let fileHandle: FileHandle\n\n    public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get {\n            self.metadata[metadataKey]\n        }\n        set {\n            self.metadata[metadataKey] = newValue\n        }\n    }\n\n    /// Create a log handler that appends to the specified file.\n    ///\n    /// - Parameters:\n    ///   - label: A unique identifier for the application.\n    ///   - category: An identifier for the application subsystem.\n    ///   - path: The log file location. The log handler creates the\n    ///     file and parent directory if needed.\n    /// - Returns: The log handler.\n    public init(label: String, category: String, path: FilePath) throws {\n        self.label = label\n        self.category = category\n        let parentPath = path.removingLastComponent()\n        try FileManager.default.createDirectory(atPath: parentPath.string, withIntermediateDirectories: true)\n        if !FileManager.default.fileExists(atPath: path.string) {\n            FileManager.default.createFile(atPath: path.string, contents: nil)\n        }\n        guard let handle = FileHandle(forWritingAtPath: path.string) else {\n            throw FileLogFailure.openFailed\n        }\n        self.fileHandle = handle\n        self.fileHandle.seekToEndOfFile()\n    }\n\n    public func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt\n    ) {\n        let timestampFormatter: ISO8601DateFormatter = {\n            let formatter = ISO8601DateFormatter()\n            formatter.formatOptions.insert(.withFractionalSeconds)\n            return formatter\n        }()\n        let timestamp = timestampFormatter.string(from: Date())\n\n        // Merge logger-level metadata with per-message metadata\n        var effectiveMetadata = self.metadata\n        if let metadata {\n            effectiveMetadata.merge(metadata) { _, new in new }\n        }\n\n        let text: String\n        if !effectiveMetadata.isEmpty {\n            text = \"\\(timestamp) [\\(level)] \\(label) \\(category) \\(effectiveMetadata.description): \\(message)\\n\"\n        } else {\n            text = \"\\(timestamp) [\\(level)] \\(label): \\(category) \\(message)\\n\"\n        }\n        if let data = text.data(using: .utf8) {\n            fileHandle.write(data)\n        }\n    }\n\n    /// Failures relating to the log handler.\n    public enum FileLogFailure: Error {\n        /// The log handler could not open the log file.\n        case openFailed\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerLog/OSLogHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\nimport os\n\nimport struct Logging.Logger\n\npublic struct OSLogHandler: LogHandler {\n    private let logger: os.Logger\n\n    public var logLevel: Logger.Level = .info\n    private var formattedMetadata: String?\n\n    public var metadata = Logger.Metadata() {\n        didSet {\n            self.formattedMetadata = self.formatMetadata(self.metadata)\n        }\n    }\n\n    public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get {\n            self.metadata[metadataKey]\n        }\n        set {\n            self.metadata[metadataKey] = newValue\n        }\n    }\n\n    public init(label: String, category: String) {\n        self.logger = os.Logger(subsystem: label, category: category)\n    }\n}\n\nextension OSLogHandler {\n    public func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt\n    ) {\n        var formattedMetadata = self.formattedMetadata\n        if let metadataOverride = metadata, !metadataOverride.isEmpty {\n            formattedMetadata = self.formatMetadata(\n                self.metadata.merging(metadataOverride) {\n                    $1\n                }\n            )\n        }\n\n        var finalMessage = message.description\n        if let formattedMetadata {\n            finalMessage += \" \" + formattedMetadata\n        }\n\n        self.logger.log(\n            level: level.toOSLogLevel(),\n            \"\\(finalMessage, privacy: .public)\"\n        )\n    }\n\n    private func formatMetadata(_ metadata: Logger.Metadata) -> String? {\n        if metadata.isEmpty {\n            return nil\n        }\n        return metadata.map {\n            \"[\\($0)=\\($1)]\"\n        }.joined(separator: \" \")\n    }\n}\n\nextension Logger.Level {\n    func toOSLogLevel() -> OSLogType {\n        switch self {\n        case .debug, .trace:\n            return .debug\n        case .info:\n            return .info\n        case .notice, .warning:\n            return .default\n        case .error:\n            return .error\n        case .critical:\n            return .fault\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerLog/ServiceLogger.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Logging\nimport SystemPackage\n\n/// Common logging setup for application services.\npublic struct ServiceLogger {\n    /// Set up the logging system and create a root logger.\n    ///\n    /// - Parameters:\n    ///   - label: A unique identifier for the application.\n    ///   - category: An identifier for the application subsystem.\n    ///   - metadata: Metadata to include for all messsages. A message\n    ///     specific value for a duplicate key overrides these values.\n    ///   - debug: Enable debug logging.\n    ///   - logPath: If supplied, create log files under the named\n    ///     directory. Otherwise, log to the OS log facility.\n    /// - Returns: The root logger.\n    public static func bootstrap(\n        label: String = \"com.apple.container\",\n        category: String,\n        metadata: [String: String] = [:],\n        debug: Bool,\n        logPath: FilePath?\n    ) -> Logger {\n        // Select the log handler and bootstrap logging.\n        LoggingSystem.bootstrap { label in\n            if let logPath {\n                if let handler = try? FileLogHandler(\n                    label: label,\n                    category: category,\n                    path: logPath\n                ) {\n                    return handler\n                }\n            }\n            return OSLogHandler(label: label, category: category)\n        }\n\n        // Configure log level and metadata.\n        var log = Logger(label: label)\n        if debug {\n            log.logLevel = .debug\n        }\n        for (key, value) in metadata {\n            log[metadataKey: key] = \"\\(value)\"\n        }\n\n        // Log an error if for some reason FileLogHandler init failed.\n        if let logPath, log.handler as? OSLogHandler != nil {\n            log.error(\n                \"unable to initialize FileLogHandler, using OSLogHandler\",\n                metadata: [\n                    \"logPath\": \"\\(logPath)\"\n                ])\n        }\n\n        return log\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerLog/StderrLogHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\n\n/// Basic log handler for where simple message output is needed,\n/// such as CLI commands.\npublic struct StderrLogHandler: LogHandler {\n    public var logLevel: Logger.Level = .info\n    public var metadata: Logger.Metadata = [:]\n\n    public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {\n        get {\n            self.metadata[metadataKey]\n        }\n        set {\n            self.metadata[metadataKey] = newValue\n        }\n    }\n\n    public init() {}\n\n    public func log(\n        level: Logger.Level,\n        message: Logger.Message,\n        metadata: Logger.Metadata?,\n        source: String,\n        file: String,\n        function: String,\n        line: UInt\n    ) {\n        let data: Data\n        switch logLevel {\n        case .debug, .trace:\n            let timestamp = ISO8601DateFormatter().string(from: Date())\n            if let metadata, !metadata.isEmpty {\n                data =\n                    \"\\(timestamp) \\(message.description): \\(metadata.description)\"\n                    .data(using: .utf8) ?? Data()\n            } else {\n                data =\n                    \"\\(timestamp) \\(message.description)\"\n                    .data(using: .utf8) ?? Data()\n            }\n        default:\n            if let metadata, !metadata.isEmpty {\n                data =\n                    \"\\(message.description): \\(metadata.description)\"\n                    .data(using: .utf8) ?? Data()\n            } else {\n                data =\n                    message.description\n                    .data(using: .utf8) ?? Data()\n            }\n        }\n\n        // Use a single write call for atomicity\n        var output = data\n        output.append(\"\\n\".data(using: .utf8)!)\n        FileHandle.standardError.write(output)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerOS/DirectoryWatcher.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport Synchronization\n\n/// Watches a directory for changes and invokes a handler when the contents change.\n///\n/// `DirectoryWatcher` uses `DispatchSource` file system events to monitor a directory.\n/// If the target directory does not exist yet, it polls until the directory is created.\n/// the target is created, then transitions to watching the target directly.\n///\n/// Example usage:\n/// ```swift\n/// let watcher = DirectoryWatcher(directoryURL: myURL, log: logger)\n/// try watcher.startWatching { urls in\n///     print(\"Directory contents changed: \\(urls)\")\n/// }\n/// ```\npublic actor DirectoryWatcher {\n    public static let watchPeriod = Duration.seconds(1)\n\n    /// The URL of the directory being watched.\n    public let directoryURL: URL\n\n    private var task: Task<Void, any Error>?\n    private let monitorQueue: DispatchQueue\n    private let source: Mutex<DispatchSourceFileSystemObject?>\n\n    private let log: Logger?\n\n    /// Creates a new `DirectoryWatcher` for the given directory URL.\n    ///\n    /// - Parameters:\n    ///   - directoryURL: The URL of the directory to watch.\n    ///   - log: An optional logger for diagnostic messages.\n    public init(directoryURL: URL, log: Logger?) {\n        self.directoryURL = directoryURL\n        self.monitorQueue = DispatchQueue(label: \"monitor:\\(directoryURL.path)\")\n        self.log = log\n        self.source = Mutex(nil)\n    }\n\n    /// Starts watching the directory for changes.\n    ///\n    /// - Parameters:\n    ///   - handler: handler to run on directory state change.\n    public func startWatching(handler: @Sendable @escaping ([URL]) throws -> Void) {\n        self.task = Task {\n            var exists: Bool\n            var isDir: ObjCBool = false\n\n            while true {\n                do {\n                    exists = FileManager.default.fileExists(atPath: self.directoryURL.path, isDirectory: &isDir)\n                    if exists && isDir.boolValue && self.source.withLock({ $0 }) == nil {\n                        try _startWatching(handler: handler)\n                    }\n                } catch {\n                    log?.error(\"failed to start watching\", metadata: [\"error\": \"\\(error)\"])\n                }\n\n                try await Task.sleep(for: Self.watchPeriod)\n            }\n        }\n    }\n\n    private func _startWatching(\n        handler: @escaping ([URL]) throws -> Void\n    ) throws {\n        let descriptor = open(directoryURL.path, O_EVTONLY)\n        guard descriptor > 0 else {\n            throw ContainerizationError(.internalError, message: \"cannot open \\(directoryURL.path), descriptor=\\(descriptor)\")\n        }\n\n        do {\n            let files = try FileManager.default.contentsOfDirectory(atPath: directoryURL.path)\n            try handler(files.map { directoryURL.appending(path: $0) })\n        } catch {\n            throw ContainerizationError(.internalError, message: \"failed to run handler for \\(directoryURL.path)\")\n        }\n\n        log?.info(\"starting directory watcher\", metadata: [\"path\": \"\\(directoryURL.path)\"])\n\n        let dispatchSource = DispatchSource.makeFileSystemObjectSource(\n            fileDescriptor: descriptor,\n            eventMask: [.delete, .write],\n            queue: monitorQueue\n        )\n\n        dispatchSource.setCancelHandler {\n            close(descriptor)\n        }\n\n        dispatchSource.setEventHandler { [weak self] in\n            guard let self else { return }\n\n            guard !dispatchSource.data.contains(.delete) else {\n                dispatchSource.cancel()\n                self.source.withLock { $0 = nil }\n                return\n            }\n\n            do {\n                let files = try FileManager.default.contentsOfDirectory(atPath: directoryURL.path)\n                try handler(files.map { directoryURL.appending(path: $0) })\n            } catch {\n                self.log?.error(\n                    \"failed to run watch handler\",\n                    metadata: [\"error\": \"\\(error)\", \"path\": \"\\(directoryURL.path)\"])\n            }\n        }\n\n        source.withLock { $0 = dispatchSource }\n        dispatchSource.resume()\n    }\n\n    deinit {\n        self.task?.cancel()\n        source.withLock { $0?.cancel() }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerOS/LocalNetworkPrivacy.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Darwin\n\n/// Utility for triggering local network privacy alert.\n/// The local networking privacy feature introduced in\n/// macOS 15 requires users to authorize an app before it can\n/// access peers on the local network. This security feature\n/// affects runtime helpers that publish ports on the loopback\n/// interface.\n///\n/// The approach used here is for the application to trigger\n/// the alert before clients attemot to communicate with it.\n/// This is a best effort method; there is no guarantee that\n/// the alert will display.\n///\n/// See https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy\n/// for additional details.\npackage struct LocalNetworkPrivacy {\n    /// Attempts to trigger the local network privacy alert.\n    ///\n    /// This builds a list of link-local IPv6 addresses and then creates a connected\n    /// UDP socket to each in turn.  Connecting a UDP socket triggers the local\n    /// network alert without actually sending any traffic.\n    package static func triggerLocalNetworkPrivacyAlert() {\n        let addresses = selectedLinkLocalIPv6Addresses()\n        for address in addresses {\n            let sock6 = socket(AF_INET6, SOCK_DGRAM, 0)\n            guard sock6 >= 0 else { return }\n            defer { close(sock6) }\n\n            withUnsafePointer(to: address) { sa6 in\n                sa6.withMemoryRebound(to: sockaddr.self, capacity: 1) { sa in\n                    _ = connect(sock6, sa, socklen_t(sa.pointee.sa_len)) >= 0\n                }\n            }\n        }\n    }\n\n    private static func selectedLinkLocalIPv6Addresses() -> [sockaddr_in6] {\n        // Find the link-local broadcast-capable IPv6 interfaces, and\n        // for each, create two peer socket addresses for the interface\n        // with the port set to the discard service (port 9).\n        let r1 = (0..<8).map { _ in UInt8.random(in: 0...255) }\n        let r2 = (0..<8).map { _ in UInt8.random(in: 0...255) }\n        return Array(\n            ipv6AddressesOfBroadcastCapableInterfaces()\n                .filter { isIPv6AddressLinkLocal($0) }\n                .map {\n                    var addr = $0\n                    addr.sin6_port = UInt16(9).bigEndian\n                    return addr\n                }\n                .map { [setIPv6LinkLocalAddressHostPart(of: $0, to: r1), setIPv6LinkLocalAddressHostPart(of: $0, to: r2)] }\n                .joined())\n    }\n\n    private static func setIPv6LinkLocalAddressHostPart(of address: sockaddr_in6, to hostPart: [UInt8]) -> sockaddr_in6 {\n        // Set the host part (the bottom 64 bits) of the supplied\n        // IPv6 socket address.\n        precondition(hostPart.count == 8)\n        var result = address\n        withUnsafeMutableBytes(of: &result.sin6_addr) { buf in\n            buf[8...].copyBytes(from: hostPart)\n        }\n        return result\n    }\n\n    private static func isIPv6AddressLinkLocal(_ address: sockaddr_in6) -> Bool {\n        // Link-local address have the fe:c0/10 prefix.\n        address.sin6_addr.__u6_addr.__u6_addr8.0 == 0xfe\n            && (address.sin6_addr.__u6_addr.__u6_addr8.1 & 0xc0) == 0x80\n    }\n\n    private static func ipv6AddressesOfBroadcastCapableInterfaces() -> [sockaddr_in6] {\n        // Iterate all interfaces and return the IPv6 addresses\n        // for those that can broadcast.\n        var addrList: UnsafeMutablePointer<ifaddrs>? = nil\n        let err = getifaddrs(&addrList)\n        guard err == 0, let start = addrList else { return [] }\n        defer { freeifaddrs(start) }\n        return sequence(first: start, next: { $0.pointee.ifa_next })\n            .compactMap { i -> sockaddr_in6? in\n                guard\n                    (i.pointee.ifa_flags & UInt32(bitPattern: IFF_BROADCAST)) != 0,\n                    let sa = i.pointee.ifa_addr,\n                    sa.pointee.sa_family == AF_INET6,\n                    sa.pointee.sa_len >= MemoryLayout<sockaddr_in6>.size\n                else { return nil }\n                return UnsafeRawPointer(sa).load(as: sockaddr_in6.self)\n            }\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/ContainerPersistence/DefaultsStore.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport CVersion\nimport ContainerVersion\nimport ContainerizationError\nimport Foundation\n\npublic enum DefaultsStore {\n    public static let userDefaultDomain = \"com.apple.container.defaults\"\n\n    public enum Keys: String {\n        case buildRosetta = \"build.rosetta\"\n        case defaultBuildCPUs = \"build.cpus\"\n        case defaultBuildMemory = \"build.memory\"\n        case defaultContainerCPUs = \"container.cpus\"\n        case defaultContainerMemory = \"container.memory\"\n        case defaultDNSDomain = \"dns.domain\"\n        case defaultBuilderImage = \"image.builder\"\n        case defaultInitImage = \"image.init\"\n        case defaultKernelBinaryPath = \"kernel.binaryPath\"\n        case defaultKernelURL = \"kernel.url\"\n        case defaultSubnet = \"network.subnet\"\n        case defaultIPv6Subnet = \"network.subnetv6\"\n        case defaultRegistryDomain = \"registry.domain\"\n    }\n\n    public static func set(value: String, key: DefaultsStore.Keys) {\n        udSuite.set(value, forKey: key.rawValue)\n    }\n\n    public static func unset(key: DefaultsStore.Keys) {\n        udSuite.removeObject(forKey: key.rawValue)\n    }\n\n    public static func get(key: DefaultsStore.Keys) -> String {\n        let appBundle = Bundle.appBundle(executableURL: CommandLine.executablePathUrl)\n        return udSuite.string(forKey: key.rawValue)\n            ?? appBundle?.infoDictionary?[\"\\(Self.userDefaultDomain).\\(key.rawValue)\"] as? String\n            ?? key.defaultValue\n    }\n\n    public static func getOptional(key: DefaultsStore.Keys) -> String? {\n        udSuite.string(forKey: key.rawValue)\n    }\n\n    public static func setBool(value: Bool, key: DefaultsStore.Keys) {\n        udSuite.set(value, forKey: key.rawValue)\n    }\n\n    public static func getBool(key: DefaultsStore.Keys) -> Bool? {\n        if udSuite.object(forKey: key.rawValue) != nil {\n            return udSuite.bool(forKey: key.rawValue)\n        }\n        let appBundle = Bundle.appBundle(executableURL: CommandLine.executablePathUrl)\n        return appBundle?.infoDictionary?[\"\\(Self.userDefaultDomain).\\(key.rawValue)\"] as? Bool\n            ?? Bool(key.defaultValue)\n    }\n\n    public static func allValues() -> [DefaultsStoreValue] {\n        let allKeys: [(Self.Keys, (Self.Keys) -> Any?)] = [\n            (.buildRosetta, { Self.getBool(key: $0) }),\n            (.defaultBuildCPUs, { Self.getOptional(key: $0) }),\n            (.defaultBuildMemory, { Self.getOptional(key: $0) }),\n            (.defaultContainerCPUs, { Self.getOptional(key: $0) }),\n            (.defaultContainerMemory, { Self.getOptional(key: $0) }),\n            (.defaultBuilderImage, { Self.get(key: $0) }),\n            (.defaultInitImage, { Self.get(key: $0) }),\n            (.defaultKernelBinaryPath, { Self.get(key: $0) }),\n            (.defaultKernelURL, { Self.get(key: $0) }),\n            (.defaultSubnet, { Self.getOptional(key: $0) }),\n            (.defaultIPv6Subnet, { Self.getOptional(key: $0) }),\n            (.defaultDNSDomain, { Self.getOptional(key: $0) }),\n            (.defaultRegistryDomain, { Self.get(key: $0) }),\n        ]\n        return\n            allKeys\n            .map { DefaultsStoreValue(id: $0.rawValue, description: $0.summary, value: $1($0) as? (Encodable & CustomStringConvertible), type: $0.type) }\n            .sorted(by: { $0.id < $1.id })\n    }\n\n    private static var udSuite: UserDefaults {\n        guard let ud = UserDefaults.init(suiteName: self.userDefaultDomain) else {\n            fatalError(\"failed to initialize UserDefaults for domain \\(self.userDefaultDomain)\")\n        }\n        return ud\n    }\n}\n\npublic struct DefaultsStoreValue: Identifiable, CustomStringConvertible, Encodable {\n    public let id: String\n    public let description: String\n    public let value: (Encodable & CustomStringConvertible)?\n    public let type: Any.Type\n\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(id, forKey: .id)\n        try container.encode(description, forKey: .description)\n\n        if let value = value {\n            try container.encode(value, forKey: .value)\n        } else {\n            try container.encodeNil(forKey: .value)\n        }\n\n        try container.encode(String(describing: type), forKey: .type)\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case id, description, value, type\n    }\n}\n\nextension DefaultsStore.Keys {\n    public var summary: String {\n        switch self {\n        case .buildRosetta:\n            return \"Build amd64 images on arm64 using Rosetta, instead of QEMU.\"\n        case .defaultBuildCPUs:\n            return \"If defined, the default number of CPUs to allocate to the builder container.\"\n        case .defaultBuildMemory:\n            return \"If defined, the default amount of memory to allocate to the builder container.\"\n        case .defaultContainerCPUs:\n            return \"If defined, the default number of CPUs to allocate to a container.\"\n        case .defaultContainerMemory:\n            return \"If defined, the default amount of memory to allocate to a container.\"\n        case .defaultDNSDomain:\n            return \"If defined, the local DNS domain to use for containers with unqualified names.\"\n        case .defaultBuilderImage:\n            return \"The image reference for the utility container that `container build` uses.\"\n        case .defaultInitImage:\n            return \"The image reference for the default initial filesystem image.\"\n        case .defaultKernelBinaryPath:\n            return \"If the kernel URL is for an archive, the archive member pathname for the kernel file.\"\n        case .defaultKernelURL:\n            return \"The URL for the kernel file to install, or the URL for an archive containing the kernel file.\"\n        case .defaultSubnet:\n            return \"Default subnet for IPv4 allocation.\"\n        case .defaultIPv6Subnet:\n            return \"Default IPv6 network prefix.\"\n        case .defaultRegistryDomain:\n            return \"The default registry to use for image references that do not specify a registry.\"\n        }\n    }\n\n    public var type: Any.Type {\n        switch self {\n        case .buildRosetta:\n            return Bool.self\n        case .defaultBuildCPUs:\n            return String.self\n        case .defaultBuildMemory:\n            return String.self\n        case .defaultContainerCPUs:\n            return String.self\n        case .defaultContainerMemory:\n            return String.self\n        case .defaultDNSDomain:\n            return String.self\n        case .defaultBuilderImage:\n            return String.self\n        case .defaultInitImage:\n            return String.self\n        case .defaultKernelBinaryPath:\n            return String.self\n        case .defaultKernelURL:\n            return String.self\n        case .defaultSubnet:\n            return String.self\n        case .defaultIPv6Subnet:\n            return String.self\n        case .defaultRegistryDomain:\n            return String.self\n        }\n    }\n\n    fileprivate var defaultValue: String {\n        switch self {\n        case .buildRosetta:\n            // This is a boolean key, not used with the string get() method\n            return \"true\"\n        case .defaultBuildCPUs:\n            // This key is read with getOptional(), not get(); this value is never used\n            return \"2\"\n        case .defaultBuildMemory:\n            // This key is read with getOptional(), not get(); this value is never used\n            return \"2048MB\"\n        case .defaultContainerCPUs:\n            // This key is read with getOptional(), not get(); this value is never used\n            return \"4\"\n        case .defaultContainerMemory:\n            // This key is read with getOptional(), not get(); this value is never used\n            return \"1g\"\n        case .defaultDNSDomain:\n            return \"test\"\n        case .defaultBuilderImage:\n            let tag = String(cString: get_container_builder_shim_version())\n            return \"ghcr.io/apple/container-builder-shim/builder:\\(tag)\"\n        case .defaultInitImage:\n            let tag = String(cString: get_swift_containerization_version())\n            guard tag != \"latest\" else {\n                return \"vminit:latest\"\n            }\n            return \"ghcr.io/apple/containerization/vminit:\\(tag)\"\n        case .defaultKernelBinaryPath:\n            return \"opt/kata/share/kata-containers/vmlinux-6.18.5-177\"\n        case .defaultKernelURL:\n            return \"https://github.com/kata-containers/kata-containers/releases/download/3.26.0/kata-static-3.26.0-arm64.tar.zst\"\n        case .defaultSubnet:\n            return \"192.168.64.1/24\"\n        case .defaultIPv6Subnet:\n            return \"fd00::/64\"\n        case .defaultRegistryDomain:\n            return \"docker.io\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPersistence/EntityStore.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport Foundation\nimport Logging\n\nlet metadataFilename: String = \"entity.json\"\n\npublic protocol EntityStore<T> {\n    associatedtype T: Codable & Identifiable<String> & Sendable\n\n    func list() async throws -> [T]\n    func create(_ entity: T) async throws\n    func retrieve(_ id: String) async throws -> T?\n    func update(_ entity: T) async throws\n    func upsert(_ entity: T) async throws\n    func delete(_ id: String) async throws\n}\n\npublic actor FilesystemEntityStore<T>: EntityStore where T: Codable & Identifiable<String> & Sendable {\n    typealias Index = [String: T]\n\n    private let path: URL\n    private let type: String\n    private var index: Index\n    private let log: Logger\n    private let encoder = JSONEncoder()\n\n    public init(path: URL, type: String, log: Logger) throws {\n        self.path = path\n        self.type = type\n        self.log = log\n        self.index = try Self.load(path: path, log: log)\n    }\n\n    public func list() async throws -> [T] {\n        Array(index.values)\n    }\n\n    public func create(_ entity: T) async throws {\n        let metadataUrl = metadataUrl(entity.id)\n        guard !FileManager.default.fileExists(atPath: metadataUrl.path) else {\n            throw ContainerizationError(.exists, message: \"entity \\(entity.id) already exist\")\n        }\n\n        try FileManager.default.createDirectory(at: entityUrl(entity.id), withIntermediateDirectories: true)\n        let data = try encoder.encode(entity)\n        try data.write(to: metadataUrl)\n        index[entity.id] = entity\n    }\n\n    public func retrieve(_ id: String) throws -> T? {\n        index[id]\n    }\n\n    public func update(_ entity: T) async throws {\n        let metadataUrl: URL = metadataUrl(entity.id)\n        guard FileManager.default.fileExists(atPath: metadataUrl.path) else {\n            throw ContainerizationError(.notFound, message: \"entity \\(entity.id) not found\")\n        }\n\n        let data = try encoder.encode(entity)\n        try data.write(to: metadataUrl)\n        index[entity.id] = entity\n    }\n\n    public func upsert(_ entity: T) async throws {\n        let metadataUrl: URL = metadataUrl(entity.id)\n        let data = try encoder.encode(entity)\n        try data.write(to: metadataUrl)\n        index[entity.id] = entity\n    }\n\n    public func delete(_ id: String) async throws {\n        let metadataUrl = entityUrl(id)\n        guard FileManager.default.fileExists(atPath: metadataUrl.path) else {\n            throw ContainerizationError(.notFound, message: \"entity \\(id) not found\")\n        }\n        try FileManager.default.removeItem(at: metadataUrl)\n        index.removeValue(forKey: id)\n    }\n\n    public func entityUrl(_ id: String) -> URL {\n        path.appendingPathComponent(id)\n    }\n\n    private static func load(path: URL, log: Logger) throws -> Index {\n        let directories = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil)\n        var index: FilesystemEntityStore<T>.Index = Index()\n        let decoder = JSONDecoder()\n\n        for entityUrl in directories {\n            do {\n                let metadataUrl = entityUrl.appendingPathComponent(metadataFilename)\n                let data = try Data(contentsOf: metadataUrl)\n                let entity = try decoder.decode(T.self, from: data)\n                index[entity.id] = entity\n            } catch {\n                log.warning(\n                    \"failed to load entity, ignoring\",\n                    metadata: [\n                        \"path\": \"\\(entityUrl)\"\n                    ])\n            }\n        }\n\n        return index\n    }\n\n    private func metadataUrl(_ id: String) -> URL {\n        entityUrl(id).appendingPathComponent(metadataFilename)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/ApplicationRoot.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Provides the application data root path.\npublic struct ApplicationRoot {\n    public static let environmentName = \"CONTAINER_APP_ROOT\"\n\n    public static let defaultURL = FileManager.default.urls(\n        for: .applicationSupportDirectory,\n        in: .userDomainMask\n    ).first!.appendingPathComponent(\"com.apple.container\")\n\n    private static let envPath = ProcessInfo.processInfo.environment[Self.environmentName]\n\n    public static let url = envPath.map { URL(fileURLWithPath: $0) } ?? defaultURL\n\n    public static let path = url.path(percentEncoded: false)\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/InstallRoot.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerVersion\nimport Foundation\n\n/// Provides the application installation root path.\npublic struct InstallRoot {\n    public static let environmentName = \"CONTAINER_INSTALL_ROOT\"\n\n    public static let defaultURL = CommandLine.executablePathUrl\n        .deletingLastPathComponent()\n        .appendingPathComponent(\"..\")\n        .standardized\n\n    private static let envPath = ProcessInfo.processInfo.environment[Self.environmentName]\n\n    public static let url = envPath.map { URL(fileURLWithPath: $0) } ?? defaultURL\n\n    public static let path = url.path(percentEncoded: false)\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/LaunchPlist.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Foundation\n\npublic struct LaunchPlist: Encodable {\n    static let debugTarget = \"CONTAINER_DEBUG_LAUNCHD_LABEL\"\n\n    public enum Domain: String, Codable {\n        case Aqua\n        case Background\n        case System\n    }\n\n    public let label: String\n    public let arguments: [String]\n\n    public let environment: [String: String]?\n    public let cwd: String?\n    public let username: String?\n    public let groupname: String?\n    public let limitLoadToSessionType: [Domain]?\n    public let runAtLoad: Bool?\n    public let stdin: String?\n    public let stdout: String?\n    public let stderr: String?\n    public let disabled: Bool?\n    public let program: String?\n    public let keepAlive: Bool?\n    public let machServices: [String: Bool]?\n    public let waitForDebugger: Bool?\n\n    enum CodingKeys: String, CodingKey {\n        case label = \"Label\"\n        case arguments = \"ProgramArguments\"\n        case environment = \"EnvironmentVariables\"\n        case cwd = \"WorkingDirectory\"\n        case username = \"UserName\"\n        case groupname = \"GroupName\"\n        case limitLoadToSessionType = \"LimitLoadToSessionType\"\n        case runAtLoad = \"RunAtLoad\"\n        case stdin = \"StandardInPath\"\n        case stdout = \"StandardOutPath\"\n        case stderr = \"StandardErrorPath\"\n        case disabled = \"Disabled\"\n        case program = \"Program\"\n        case keepAlive = \"KeepAlive\"\n        case machServices = \"MachServices\"\n        case waitForDebugger = \"WaitForDebugger\"\n    }\n\n    static private func getWaitForDebugger(label: String, fromArg: Bool?) -> Bool? {\n        if let fromArg {\n            return fromArg\n        }\n\n        let env = ProcessInfo.processInfo.environment\n        if let debugTarget = env[Self.debugTarget],\n            label == debugTarget || label.starts(with: debugTarget + \".\")\n        {\n            return true\n        }\n\n        return nil\n    }\n\n    public init(\n        label: String,\n        arguments: [String],\n        environment: [String: String]? = nil,\n        cwd: String? = nil,\n        username: String? = nil,\n        groupname: String? = nil,\n        limitLoadToSessionType: [Domain]? = nil,\n        runAtLoad: Bool? = nil,\n        stdin: String? = nil,\n        stdout: String? = nil,\n        stderr: String? = nil,\n        disabled: Bool? = nil,\n        program: String? = nil,\n        keepAlive: Bool? = nil,\n        machServices: [String]? = nil,\n        waitForDebugger: Bool? = nil\n    ) {\n        self.label = label\n        self.arguments = arguments\n        self.environment = environment\n        self.cwd = cwd\n        self.username = username\n        self.groupname = groupname\n        self.limitLoadToSessionType = limitLoadToSessionType\n        self.runAtLoad = runAtLoad\n        self.stdin = stdin\n        self.stdout = stdout\n        self.stderr = stderr\n        self.disabled = disabled\n        self.program = program\n        self.keepAlive = keepAlive\n        self.waitForDebugger = Self.getWaitForDebugger(label: label, fromArg: waitForDebugger)\n        if let services = machServices {\n            var machServices: [String: Bool] = [:]\n            for service in services {\n                machServices[service] = true\n            }\n            self.machServices = machServices\n        } else {\n            self.machServices = nil\n        }\n    }\n}\n\nextension LaunchPlist {\n    public func encode() throws -> Data {\n        let enc = PropertyListEncoder()\n        enc.outputFormat = .xml\n        return try enc.encode(self)\n    }\n}\n#endif\n"
  },
  {
    "path": "Sources/ContainerPlugin/LogRoot.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport SystemPackage\n\n/// Provides the application data root path.\npublic struct LogRoot {\n\n    private static let envPath = ProcessInfo.processInfo.environment[Self.environmentName].flatMap {\n        $0.isEmpty ? nil : FilePath($0)\n    }\n\n    /// The environment variable that if set, determines the root directory for log files.\n    /// Otherwise, the application uses the macOS log facility.\n    public static let environmentName = \"CONTAINER_LOG_ROOT\"\n\n    /// The path object for the log file root directory\n    public static let path = envPath.map {\n        guard !$0.isAbsolute else { return $0 }\n        return FilePath(FileManager.default.currentDirectoryPath).appending($0.components)\n    }\n\n    /// The pathname to the log file root directory\n    public static let pathname = path?.string\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/Plugin.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Value type that contains the plugin configuration, the parsed name of the\n/// plugin and whether a CLI surface for the plugin was found.\npublic struct Plugin: Sendable, Codable {\n    private static let machServicePrefix = \"com.apple.container.\"\n\n    /// Pathname to installation directory for plugins.\n    public let binaryURL: URL\n\n    /// Configuration for the plugin.\n    public let config: PluginConfig\n\n    public init(binaryURL: URL, config: PluginConfig) {\n        self.binaryURL = binaryURL\n        self.config = config\n    }\n}\n\nextension Plugin {\n    public var name: String { binaryURL.lastPathComponent }\n\n    public var shouldBoot: Bool {\n        guard let config = self.config.servicesConfig else {\n            return false\n        }\n\n        return config.loadAtBoot\n    }\n\n    public func getLaunchdLabel(instanceId: String? = nil) -> String {\n        // Use the plugin name for the launchd label.\n        guard let instanceId else {\n            return \"\\(Self.machServicePrefix)\\(self.name)\"\n        }\n        return \"\\(Self.machServicePrefix)\\(self.name).\\(instanceId)\"\n    }\n\n    public func getMachServices(instanceId: String? = nil) -> [String] {\n        // Use the service type for the mach service.\n        guard let config = self.config.servicesConfig else {\n            return []\n        }\n        var services = [String]()\n        for service in config.services {\n            let serviceName: String\n            if let instanceId {\n                serviceName = \"\\(Self.machServicePrefix)\\(service.type.rawValue).\\(name).\\(instanceId)\"\n            } else {\n                serviceName = \"\\(Self.machServicePrefix)\\(service.type.rawValue).\\(name)\"\n            }\n            services.append(serviceName)\n        }\n        return services\n    }\n\n    public func getMachService(instanceId: String? = nil, type: PluginConfig.DaemonPluginType) -> String? {\n        guard hasType(type) else {\n            return nil\n        }\n\n        guard let instanceId else {\n            return \"\\(Self.machServicePrefix)\\(type.rawValue).\\(name)\"\n        }\n        return \"\\(Self.machServicePrefix)\\(type.rawValue).\\(name).\\(instanceId)\"\n    }\n\n    public func hasType(_ type: PluginConfig.DaemonPluginType) -> Bool {\n        guard let config = self.config.servicesConfig else {\n            return false\n        }\n\n        guard !(config.services.filter { $0.type == type }.isEmpty) else {\n            return false\n        }\n\n        return true\n    }\n}\n\nextension Plugin {\n    public func exec(args: [String]) throws {\n        var args = args\n        let executable = self.binaryURL.path\n        args[0] = executable\n        let argv = args.map { strdup($0) } + [nil]\n        guard execvp(executable, argv) != -1 else {\n            throw POSIXError.fromErrno()\n        }\n        fatalError(\"unreachable\")\n    }\n\n    func helpText(padding: Int) -> String {\n        guard !self.name.isEmpty else {\n            return \"\"\n        }\n        let namePadded = name.padding(toLength: padding, withPad: \" \", startingAt: 0)\n        return \"  \" + namePadded + self.config.abstract\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/PluginConfig.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n//\nimport Foundation\n\n/// PluginConfig details all of the fields to describe and register a plugin.\n/// A plugin is registered by creating a subdirectory  `<application-root>/user-plugins`,\n/// where the name of the subdirectory is the name of the plugin, and then placing a\n/// file named `config.json` inside with the schema below.\n/// If `services` is filled in then there MUST be a binary named  matching the plugin name\n/// in a `bin` subdirectory inside the same directory as the `config.json`.\n/// An example of a valid plugin directory structure would be\n/// $ tree foobar\n/// foobar\n/// ├── bin\n/// │   └── foobar\n/// └── config.json\npublic struct PluginConfig: Sendable, Codable {\n    /// Categories of services that can be offered through plugins.\n    public enum DaemonPluginType: String, Sendable, Codable {\n        /// A runtime plugin provides an XPC API through which the lifecycle\n        /// of a **single** container can be managed.\n        /// A runtime daemon plugin would typically also have a counterpart\n        /// CLI plugin which knows how to talk to the API exposed by the runtime plugin.\n        /// The API server ensures that a single instance of the plugin is configured\n        /// for a given container such that the client can communicate with it given an instance id.\n        case runtime\n        /// A network plugin provides an XPC API through which IP address allocations on a given\n        /// network can be managed. The API server ensures that a single instance\n        /// of this plugin is configured for a given network. Similar to the runtime plugin, it typically\n        /// would be accompanied by a CLI plugin that knows how to communicate with the XPC API\n        /// given an instance id.\n        case network\n        /// A core plugin provides an XPC API to manage a given type of resource.\n        /// The API server ensures that there exist only a single running instance\n        /// of this plugin type. A core plugin can be thought of a singleton whose lifecycle\n        /// is tied to that of the API server. Core plugins can be used to expand the base functionality\n        /// provided by the API server. As with the other plugin types, it maybe associated with a client\n        /// side plugin that communicates with the XPC service exposed by the daemon plugin.\n        case core\n        /// Reserved for future use. Currently there is no difference between a core and auxiliary daemon plugin.\n        case auxiliary\n    }\n\n    // An XPC service that the plugin publishes.\n    public struct Service: Sendable, Codable {\n        /// The type of the service the daemon is exposing.\n        /// One plugin can expose multiple services of different types.\n        ///\n        /// The plugin MUST expose a MachService at\n        /// `com.apple.container.{type}.{name}.[{id}]` for\n        /// each service that it exposes.\n        public let type: DaemonPluginType\n        /// Optional description of this service.\n        public let description: String?\n    }\n\n    /// Descriptor for the services that the plugin offers.\n    public struct ServicesConfig: Sendable, Codable {\n        /// Load the plugin into launchd when the API server starts.\n        public let loadAtBoot: Bool\n        /// Launch the plugin binary as soon as it loads into launchd.\n        public let runAtLoad: Bool\n        /// The service types that the plugin provides.\n        public let services: [Service]\n        /// An optional parameter that include any command line arguments\n        /// that must be passed to the plugin binary when it is loaded.\n        public let defaultArguments: [String]\n    }\n\n    /// Short description of the plugin surface. This will be displayed as the\n    /// help-text for CLI plugins, and will be returned in API calls to view loaded\n    /// plugins from the daemon.\n    public let abstract: String\n\n    /// Author of the plugin. This is solely metadata.\n    public let author: String?\n\n    /// Services configuration. Specify nil for a CLI plugin, and an empty array for\n    /// that does not publish any XPC services.\n    public let servicesConfig: ServicesConfig?\n}\n\nextension PluginConfig {\n    public var isCLI: Bool { self.servicesConfig == nil }\n}\n\nextension PluginConfig {\n    public init?(configURL: URL) throws {\n        let fm = FileManager.default\n        if !fm.fileExists(atPath: configURL.path) {\n            return nil\n        }\n\n        guard let data = fm.contents(atPath: configURL.path) else {\n            return nil\n        }\n\n        let decoder: JSONDecoder = JSONDecoder()\n        self = try decoder.decode(PluginConfig.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/PluginFactory.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nprivate let configFilename: String = \"config.json\"\n\n/// Describes the configuration and binary file locations for a plugin.\npublic protocol PluginFactory: Sendable {\n    /// Create a plugin from the plugin path, if it conforms to the layout.\n    func create(installURL: URL) throws -> Plugin?\n    /// Create a plugin from the plugin parent path and name, if it conforms to the layout.\n    func create(parentURL: URL, name: String) throws -> Plugin?\n}\n\n/// Default layout which uses a Unix-like structure.\npublic struct DefaultPluginFactory: PluginFactory {\n    public init() {}\n\n    public func create(installURL: URL) throws -> Plugin? {\n        let fm = FileManager.default\n\n        let configURL = installURL.appending(path: configFilename)\n        guard fm.fileExists(atPath: configURL.path) else {\n            return nil\n        }\n\n        guard let config = try PluginConfig(configURL: configURL) else {\n            return nil\n        }\n\n        let name = installURL.lastPathComponent\n        let binaryURL = installURL.appending(path: \"bin\").appending(path: name)\n        guard fm.fileExists(atPath: binaryURL.path) else {\n            return nil\n        }\n\n        return Plugin(binaryURL: binaryURL, config: config)\n    }\n\n    public func create(parentURL: URL, name: String) throws -> Plugin? {\n        try create(installURL: parentURL.appendingPathComponent(name))\n    }\n}\n\n/// Layout which uses a macOS application bundle structure.\npublic struct AppBundlePluginFactory: PluginFactory {\n    private static let appSuffix = \".app\"\n\n    public init() {}\n\n    public func create(installURL: URL) throws -> Plugin? {\n        let fm = FileManager.default\n\n        let configURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"Resources\")\n            .appending(path: configFilename)\n        guard fm.fileExists(atPath: configURL.path) else {\n            return nil\n        }\n\n        guard let config = try PluginConfig(configURL: configURL) else {\n            return nil\n        }\n\n        let appName = installURL.lastPathComponent\n        guard appName.hasSuffix(Self.appSuffix) else {\n            return nil\n        }\n        let name = String(appName.dropLast(Self.appSuffix.count))\n        let binaryURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"MacOS\")\n            .appending(path: name)\n        guard fm.fileExists(atPath: binaryURL.path) else {\n            return nil\n        }\n\n        return Plugin(binaryURL: binaryURL, config: config)\n    }\n\n    public func create(parentURL: URL, name: String) throws -> Plugin? {\n        try create(installURL: parentURL.appendingPathComponent(\"\\(name)\\(Self.appSuffix)\"))\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/PluginLoader.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport SystemPackage\n\npublic struct PluginLoader: Sendable {\n    private let appRoot: URL\n\n    private let installRoot: URL\n\n    private let logRoot: FilePath?\n\n    private let pluginDirectories: [URL]\n\n    private let pluginFactories: [PluginFactory]\n\n    private let log: Logger?\n\n    public typealias PluginQualifier = ((Plugin) -> Bool)\n\n    // A path on disk managed by the PluginLoader, where it stores\n    // runtime data for loaded plugins. This includes the launchd plists\n    // and logs files.\n    private let pluginResourceRoot: URL\n\n    public init(\n        appRoot: URL,\n        installRoot: URL,\n        logRoot: FilePath?,\n        pluginDirectories: [URL],\n        pluginFactories: [PluginFactory],\n        log: Logger? = nil\n    ) throws {\n        let pluginResourceRoot = appRoot.appendingPathComponent(\"plugin-state\")\n        try FileManager.default.createDirectory(at: pluginResourceRoot, withIntermediateDirectories: true)\n        self.pluginResourceRoot = pluginResourceRoot\n        self.appRoot = appRoot\n        self.installRoot = installRoot\n        self.logRoot = logRoot\n        self.pluginDirectories = pluginDirectories\n        self.pluginFactories = pluginFactories\n        self.log = log\n    }\n\n    static public func userPluginsDir(installRoot: URL) -> URL {\n        installRoot\n            .appending(path: \"libexec\")\n            .appending(path: \"container-plugins\")\n            .resolvingSymlinksInPath()\n    }\n}\n\nextension PluginLoader {\n    public func alterCLIHelpText(original: String) -> String {\n        var plugins = findPlugins()\n        plugins = plugins.filter { $0.config.isCLI }\n        guard !plugins.isEmpty else {\n            return original\n        }\n\n        var lines = original.split(separator: \"\\n\").map { String($0) }\n\n        let sectionHeader = \"PLUGINS:\"\n        lines.append(sectionHeader)\n\n        for plugin in plugins {\n            let helpText = plugin.helpText(padding: 24)\n            lines.append(helpText)\n        }\n\n        return lines.joined(separator: \"\\n\")\n    }\n\n    /// Scan all plugin directories and detect plugins.\n    public func findPlugins() -> [Plugin] {\n        let fm = FileManager.default\n\n        // Maintain a set for tracking shadowed plugins\n        var pluginNames = Set<String>()\n        var plugins: [Plugin] = []\n\n        for pluginDir in pluginDirectories {\n            // Skip nonexistent plugin parent directories\n            if !fm.fileExists(atPath: pluginDir.path) {\n                continue\n            }\n\n            // Get all entries under the parent directory\n            let resolvedPluginDir = pluginDir.resolvingSymlinksInPath()\n            guard\n                let urls = try? fm.contentsOfDirectory(\n                    at: resolvedPluginDir,\n                    includingPropertiesForKeys: [.isDirectoryKey, .isSymbolicLinkKey],\n                    options: .skipsHiddenFiles\n                )\n            else {\n                continue\n            }\n\n            // Filter out all but plugin installation directories\n            let installURLs = urls.filter { url in\n                if url.isDirectory {\n                    return true\n                }\n\n                if url.isSymlink {\n                    var isDirectory: ObjCBool = false\n                    _ = fm.fileExists(atPath: url.resolvingSymlinksInPath().path(percentEncoded: false), isDirectory: &isDirectory)\n                    return isDirectory.boolValue\n                }\n\n                return false\n            }\n\n            for installURL in installURLs {\n                do {\n                    // Create a plugin with the first factory that can grok the layout under the install URL\n                    guard\n                        let plugin = try\n                            (pluginFactories.compactMap {\n                                try $0.create(installURL: installURL)\n                            }.first)\n                    else {\n                        log?.warning(\n                            \"not installing plugin with missing configuration\",\n                            metadata: [\n                                \"path\": \"\\(installURL.path)\"\n                            ]\n                        )\n                        continue\n                    }\n\n                    // Warn and skip if this plugin name has been encountered already\n                    guard !pluginNames.contains(plugin.name) else {\n                        log?.warning(\n                            \"not installing shadowed plugin\",\n                            metadata: [\n                                \"path\": \"\\(installURL.path)\",\n                                \"name\": \"\\(plugin.name)\",\n                            ])\n                        continue\n                    }\n\n                    // Add the plugin to the list\n                    plugins.append(plugin)\n                    pluginNames.insert(plugin.name)\n                } catch {\n                    log?.warning(\n                        \"not installing plugin with invalid configuration\",\n                        metadata: [\n                            \"path\": \"\\(installURL.path)\",\n                            \"error\": \"\\(error)\",\n                        ]\n                    )\n                }\n            }\n        }\n\n        return plugins\n    }\n\n    /// Locate a plugin with a specific name.\n    public func findPlugin(name: String, log: Logger? = nil) -> Plugin? {\n        do {\n            for pluginDirectory in pluginDirectories {\n                for PluginFactory in pluginFactories {\n                    // throw means that the factory is correct but the plugin is broken\n                    if let plugin = try PluginFactory.create(parentURL: pluginDirectory, name: name) {\n                        return plugin\n                    }\n                }\n            }\n        } catch {\n            log?.warning(\n                \"not installing plugin with invalid configuration\",\n                metadata: [\n                    \"name\": \"\\(name)\",\n                    \"error\": \"\\(error)\",\n                ]\n            )\n        }\n\n        return nil\n    }\n}\n\nextension PluginLoader {\n    public static let proxyKeys = Set([\n        \"http_proxy\", \"HTTP_PROXY\",\n        \"https_proxy\", \"HTTPS_PROXY\",\n        \"no_proxy\", \"NO_PROXY\",\n    ])\n\n    public func registerWithLaunchd(\n        plugin: Plugin,\n        pluginStateRoot: URL? = nil,\n        args: [String]? = nil,\n        instanceId: String? = nil,\n        debug: Bool = false,\n    ) throws {\n        // We only care about loading plugins that have a service\n        // to expose; otherwise, they may just be CLI commands.\n        guard let serviceConfig = plugin.config.servicesConfig else {\n            return\n        }\n\n        let id = plugin.getLaunchdLabel(instanceId: instanceId)\n        log?.info(\"Registering plugin\", metadata: [\"id\": \"\\(id)\"])\n        let rootURL = pluginStateRoot ?? self.pluginResourceRoot.appending(path: plugin.name)\n        try FileManager.default.createDirectory(at: rootURL, withIntermediateDirectories: true)\n\n        var env = Self.filterEnvironment()\n        env[ApplicationRoot.environmentName] = appRoot.path(percentEncoded: false)\n        env[InstallRoot.environmentName] = installRoot.path(percentEncoded: false)\n        if let logRoot {\n            env[LogRoot.environmentName] =\n                logRoot.isAbsolute\n                ? logRoot.string\n                : FilePath(FileManager.default.currentDirectoryPath).appending(logRoot.components).string\n        }\n\n        let processedArgs = (args ?? [\"start\"]) + (debug ? [\"--debug\"] : [])\n        let plist = LaunchPlist(\n            label: id,\n            arguments: [plugin.binaryURL.path] + processedArgs + serviceConfig.defaultArguments,\n            environment: env,\n            limitLoadToSessionType: [.Aqua, .Background, .System],\n            runAtLoad: serviceConfig.runAtLoad,\n            machServices: plugin.getMachServices(instanceId: instanceId)\n        )\n\n        let plistUrl = rootURL.appendingPathComponent(\"service.plist\")\n        let data = try plist.encode()\n        try data.write(to: plistUrl)\n        try ServiceManager.register(plistPath: plistUrl.path)\n    }\n\n    public func deregisterWithLaunchd(plugin: Plugin, instanceId: String? = nil) throws {\n        // We only care about loading plugins that have a service\n        // to expose; otherwise, they may just be CLI commands.\n        guard plugin.config.servicesConfig != nil else {\n            return\n        }\n        let domain = try ServiceManager.getDomainString()\n        let label = \"\\(domain)/\\(plugin.getLaunchdLabel(instanceId: instanceId))\"\n        log?.info(\"Deregistering plugin\", metadata: [\"id\": \"\\(plugin.getLaunchdLabel())\"])\n        try ServiceManager.deregister(fullServiceLabel: label)\n    }\n\n    public static func filterEnvironment(\n        env: [String: String] = ProcessInfo.processInfo.environment,\n        additionalAllowKeys: Set<String> = Self.proxyKeys\n    ) -> [String: String] {\n        env.filter { key, _ in\n            key.hasPrefix(\"CONTAINER_\") || additionalAllowKeys.contains(key)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerPlugin/ServiceManager.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport Foundation\n\npublic struct ServiceManager {\n    private static func runLaunchctlCommand(args: [String]) throws -> Int32 {\n        let launchctl = Foundation.Process()\n        launchctl.executableURL = URL(fileURLWithPath: \"/bin/launchctl\")\n        launchctl.arguments = args\n\n        let null = FileHandle.nullDevice\n        launchctl.standardOutput = null\n        launchctl.standardError = null\n\n        try launchctl.run()\n        launchctl.waitUntilExit()\n\n        return launchctl.terminationStatus\n    }\n\n    /// Register a service by providing the path to a plist.\n    public static func register(plistPath: String) throws {\n        let domain = try Self.getDomainString()\n        _ = try runLaunchctlCommand(args: [\"bootstrap\", domain, plistPath])\n    }\n\n    /// Deregister a service by a launchd label.\n    public static func deregister(fullServiceLabel label: String) throws {\n        _ = try runLaunchctlCommand(args: [\"bootout\", label])\n    }\n\n    /// Deregister a service and pass return status\n    public static func deregister(fullServiceLabel label: String, status: inout Int32) throws {\n        status = try runLaunchctlCommand(args: [\"bootout\", label])\n    }\n\n    /// Restart a service by a launchd label.\n    public static func kickstart(fullServiceLabel label: String) throws {\n        _ = try runLaunchctlCommand(args: [\"kickstart\", \"-k\", label])\n    }\n\n    /// Send a signal to a service by a launchd label.\n    public static func kill(fullServiceLabel label: String, signal: Int32 = 15) throws {\n        _ = try runLaunchctlCommand(args: [\"kill\", \"\\(signal)\", label])\n    }\n\n    /// Retrieve labels for all loaded launch units.\n    public static func enumerate() throws -> [String] {\n        let launchctl = Foundation.Process()\n        launchctl.executableURL = URL(fileURLWithPath: \"/bin/launchctl\")\n        launchctl.arguments = [\"list\"]\n\n        let stdoutPipe = Pipe()\n        let stderrPipe = Pipe()\n        launchctl.standardOutput = stdoutPipe\n        launchctl.standardError = stderrPipe\n\n        try launchctl.run()\n        let outputData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()\n        let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()\n        launchctl.waitUntilExit()\n        let status = launchctl.terminationStatus\n        guard status == 0 else {\n            throw ContainerizationError(\n                .internalError, message: \"command `launchctl list` failed with status \\(status), message: \\(String(data: stderrData, encoding: .utf8) ?? \"no error message\")\")\n        }\n\n        guard let outputText = String(data: outputData, encoding: .utf8) else {\n            throw ContainerizationError(\n                .internalError, message: \"could not decode output of command `launchctl list`, message: \\(String(data: stderrData, encoding: .utf8) ?? \"no error message\")\")\n        }\n\n        // The third field of each line of launchctl list output is the label\n        return outputText.split { $0.isNewline }\n            .map { String($0).split { $0.isWhitespace } }\n            .filter { $0.count >= 3 }\n            .map { String($0[2]) }\n    }\n\n    /// Check if a service has been registered or not.\n    public static func isRegistered(fullServiceLabel label: String) throws -> Bool {\n        let exitStatus = try runLaunchctlCommand(args: [\"list\", label])\n        return exitStatus == 0\n    }\n\n    private static func getLaunchdSessionType() throws -> String {\n        let launchctl = Foundation.Process()\n        launchctl.executableURL = URL(fileURLWithPath: \"/bin/launchctl\")\n        launchctl.arguments = [\"managername\"]\n\n        let null = FileHandle.nullDevice\n        let stdoutPipe = Pipe()\n        launchctl.standardOutput = stdoutPipe\n        launchctl.standardError = null\n\n        try launchctl.run()\n        let outputData = stdoutPipe.fileHandleForReading.readDataToEndOfFile()\n        launchctl.waitUntilExit()\n        let status = launchctl.terminationStatus\n        guard status == 0 else {\n            throw ContainerizationError(.internalError, message: \"command `launchctl managername` failed with status \\(status)\")\n        }\n        guard let outputText = String(data: outputData, encoding: .utf8) else {\n            throw ContainerizationError(.internalError, message: \"could not decode output of command `launchctl managername`\")\n        }\n        return outputText.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    public static func getDomainString() throws -> String {\n        let currentSessionType = try getLaunchdSessionType()\n        switch currentSessionType {\n        case LaunchPlist.Domain.System.rawValue:\n            return LaunchPlist.Domain.System.rawValue.lowercased()\n        case LaunchPlist.Domain.Background.rawValue:\n            return \"user/\\(getuid())\"\n        case LaunchPlist.Domain.Aqua.rawValue:\n            return \"gui/\\(getuid())\"\n        default:\n            throw ContainerizationError(.internalError, message: \"unsupported session type \\(currentSessionType)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Common/ManagedResource.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Common properties for all managed resources.\npublic protocol ManagedResource: Identifiable, Sendable, Codable {\n    /// A 64 byte hexadecimal string, assigned by the system, that uniquely\n    /// identifies the resource.\n    var id: String { get }\n\n    /// A user assigned name that shall be unique within the namespace of\n    /// the resource category. If the user does not assign a name, this value\n    /// shall be the same as the system-assigned identifier.\n    var name: String { get }\n\n    /// The time at which the system created the resource.\n    var creationDate: Date { get }\n\n    /// Key-value properties for the resource. The user and system may both\n    /// make use of labels to read and write annotations or other metadata.\n    /// A good practice is to use\n    var labels: [String: String] { get }\n\n    /// Generates a unique resource ID value.\n    static func generateId() -> String\n\n    /// Returns true only if the specified resource name is syntactically valid.\n    static func nameValid(_ name: String) -> Bool\n}\n\nextension ManagedResource {\n    /// Generate a random identifier that has the format of an ASCII SHA-256 hash.\n    public static func generateId() -> String {\n        (0..<2)\n            .map { _ in UInt128.random(in: 0...UInt128.max) }\n            .map { String($0, radix: 16).padding(toLength: 32, withPad: \"0\", startingAt: 0) }\n            .joined()\n    }\n}\n\n// FIXME: This moves to ManagedResource and/or a ResourceLabels typealias eventually.\nextension [String: String] {\n    public var isBuiltin: Bool { self.contains { $0 == ResourceLabelKeys.role && $1 == ResourceRoleValues.builtin } }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Common/ResourceLabels.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// System-defined keys for resource labels.\npublic struct ResourceLabelKeys {\n    /// Indicates a owner of a resource managed by a plugin.\n    public static let plugin = \"com.apple.container.plugin\"\n\n    /// Indicates a resource with a reserved or dedicated purpose.\n    public static let role = \"com.apple.container.resource.role\"\n}\n\n/// System-defined values for resource the resource role label.\npublic struct ResourceRoleValues {\n    /// Indicates a container that can build images.\n    public static let builder = \"builder\"\n\n    /// Indicates a system-created resource that cannot be deleted by the user.\n    public static let builtin = \"builtin\"\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/Bundle.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Containerization\nimport ContainerizationError\nimport Foundation\n\npublic struct Bundle: Sendable {\n    private static let initfsFilename = \"initfs.ext4\"\n    private static let kernelFilename = \"kernel.json\"\n    private static let kernelBinaryFilename = \"kernel.bin\"\n    private static let containerRootFsBlockFilename = \"rootfs.ext4\"\n    private static let containerRootFsFilename = \"rootfs.json\"\n\n    static let containerConfigFilename = \"config.json\"\n\n    /// The path to the bundle.\n    public let path: URL\n\n    public init(path: URL) {\n        self.path = path\n    }\n\n    public var bootlog: URL {\n        self.path.appendingPathComponent(\"vminitd.log\")\n    }\n\n    public var containerRootfsBlock: URL {\n        self.path.appendingPathComponent(Self.containerRootFsBlockFilename)\n    }\n\n    private var containerRootfsConfig: URL {\n        self.path.appendingPathComponent(Self.containerRootFsFilename)\n    }\n\n    public var containerRootfs: Filesystem {\n        get throws {\n            let data = try Data(contentsOf: containerRootfsConfig)\n            let fs = try JSONDecoder().decode(Filesystem.self, from: data)\n            return fs\n        }\n    }\n\n    /// Return the initial filesystem for a sandbox.\n    public var initialFilesystem: Filesystem {\n        .block(\n            format: \"ext4\",\n            source: self.path.appendingPathComponent(Self.initfsFilename).path,\n            destination: \"/\",\n            options: [\"ro\"]\n        )\n    }\n\n    public var kernel: Kernel {\n        get throws {\n            try load(path: self.path.appendingPathComponent(Self.kernelFilename))\n        }\n    }\n\n    public var configuration: ContainerConfiguration {\n        get throws {\n            try load(path: self.path.appendingPathComponent(Self.containerConfigFilename))\n        }\n    }\n}\n\nextension Bundle {\n    public static func create(\n        path: URL,\n        initialFilesystem: Filesystem,\n        kernel: Kernel,\n        containerConfiguration: ContainerConfiguration? = nil,\n        containerRootFilesystem: Filesystem? = nil,\n        options: ContainerCreateOptions? = nil\n    ) throws -> Bundle {\n        try FileManager.default.createDirectory(at: path, withIntermediateDirectories: true)\n        let kbin = path.appendingPathComponent(Self.kernelBinaryFilename)\n        try FileManager.default.copyItem(at: kernel.path, to: kbin)\n        var k = kernel\n        k.path = kbin\n        try write(path.appendingPathComponent(Self.kernelFilename), value: k)\n\n        switch initialFilesystem.type {\n        case .block(let fmt, _, _):\n            guard fmt == \"ext4\" else {\n                fatalError(\"ext4 is the only supported format for initial filesystem\")\n            }\n            // when saving the Initial Filesystem to the bundle\n            // discard any filesystem information and just persist\n            // the block into the Bundle.\n            _ = try initialFilesystem.clone(to: path.appendingPathComponent(Self.initfsFilename).path)\n        default:\n            fatalError(\"invalid filesystem type for initial filesystem\")\n        }\n        let bundle = Bundle(path: path)\n        if let containerConfiguration {\n            try bundle.write(filename: Self.containerConfigFilename, value: containerConfiguration)\n        }\n\n        if let rootFsOverride = options?.rootFsOverride {\n            try bundle.setContainerRootFs(fs: rootFsOverride)\n        } else if let containerRootFilesystem {\n            let readonly = containerConfiguration?.readOnly ?? false\n            try bundle.cloneContainerRootFs(cloning: containerRootFilesystem, readonly: readonly)\n        }\n\n        if let options {\n            try bundle.write(filename: \"options.json\", value: options)\n        }\n        return bundle\n    }\n}\n\nextension Bundle {\n    /// Set the value of the configuration for the Bundle.\n    public func set(configuration: ContainerConfiguration) throws {\n        try write(filename: Self.containerConfigFilename, value: configuration)\n    }\n\n    /// Return the full filepath for a named resource in the Bundle.\n    public func filePath(for name: String) -> URL {\n        path.appendingPathComponent(name)\n    }\n\n    public func setContainerRootFs(fs: Filesystem) throws {\n        let fsData = try JSONEncoder().encode(fs)\n        try fsData.write(to: self.containerRootfsConfig)\n    }\n\n    public func cloneContainerRootFs(cloning fs: Filesystem, readonly: Bool = false) throws {\n        var mutableFs = fs\n        if readonly && !mutableFs.options.contains(\"ro\") {\n            mutableFs.options.append(\"ro\")\n        }\n        let cloned = try mutableFs.clone(to: self.containerRootfsBlock.absolutePath())\n        try setContainerRootFs(fs: cloned)\n    }\n\n    /// Delete the bundle and all of the resources contained inside.\n    public func delete() throws {\n        try FileManager.default.removeItem(at: self.path)\n    }\n\n    public func write(filename: String, value: Encodable) throws {\n        try Self.write(self.path.appendingPathComponent(filename), value: value)\n    }\n\n    private static func write(_ path: URL, value: Encodable) throws {\n        let data = try JSONEncoder().encode(value)\n        try data.write(to: path)\n    }\n\n    public func load<T>(filename: String) throws -> T where T: Decodable {\n        try load(path: self.path.appendingPathComponent(filename))\n    }\n\n    private func load<T>(path: URL) throws -> T where T: Decodable {\n        let data = try Data(contentsOf: path)\n        return try JSONDecoder().decode(T.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerConfiguration.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOCI\n\npublic struct ContainerConfiguration: Sendable, Codable {\n    /// Identifier for the container.\n    public var id: String\n    /// Image used to create the container.\n    public var image: ImageDescription\n    /// External mounts to add to the container.\n    public var mounts: [Filesystem] = []\n    /// Ports to publish from container to host.\n    public var publishedPorts: [PublishPort] = []\n    /// Sockets to publish from container to host.\n    public var publishedSockets: [PublishSocket] = []\n    /// Key/Value labels for the container.\n    public var labels: [String: String] = [:]\n    /// System controls for the container.\n    public var sysctls: [String: String] = [:]\n    /// The networks the container will be added to.\n    public var networks: [AttachmentConfiguration] = []\n    /// The DNS configuration for the container.\n    public var dns: DNSConfiguration? = nil\n    /// Whether to enable rosetta x86-64 translation for the container.\n    public var rosetta: Bool = false\n    /// Initial or main process of the container.\n    public var initProcess: ProcessConfiguration\n    /// Platform for the container.\n    public var platform: ContainerizationOCI.Platform = .current\n    /// Resource values for the container.\n    public var resources: Resources = .init()\n    /// Name of the runtime that supports the container.\n    public var runtimeHandler: String = \"container-runtime-linux\"\n    /// Configure exposing virtualization support in the container.\n    public var virtualization: Bool = false\n    /// Enable SSH agent socket forwarding from host to container.\n    public var ssh: Bool = false\n    /// Whether to mount the rootfs as read-only.\n    public var readOnly: Bool = false\n    /// Whether to use a minimal init process inside the container.\n    public var useInit: Bool = false\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case image\n        case mounts\n        case publishedPorts\n        case publishedSockets\n        case labels\n        case sysctls\n        case networks\n        case dns\n        case rosetta\n        case initProcess\n        case platform\n        case resources\n        case runtimeHandler\n        case virtualization\n        case ssh\n        case readOnly\n        case useInit\n    }\n\n    /// Create a configuration from the supplied Decoder, initializing missing\n    /// values where possible to reasonable defaults.\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        id = try container.decode(String.self, forKey: .id)\n        image = try container.decode(ImageDescription.self, forKey: .image)\n        mounts = try container.decodeIfPresent([Filesystem].self, forKey: .mounts) ?? []\n        publishedPorts = try container.decodeIfPresent([PublishPort].self, forKey: .publishedPorts) ?? []\n        publishedSockets = try container.decodeIfPresent([PublishSocket].self, forKey: .publishedSockets) ?? []\n        labels = try container.decodeIfPresent([String: String].self, forKey: .labels) ?? [:]\n        sysctls = try container.decodeIfPresent([String: String].self, forKey: .sysctls) ?? [:]\n\n        if container.contains(.networks) {\n            networks = try container.decode([AttachmentConfiguration].self, forKey: .networks)\n        } else {\n            networks = []\n        }\n\n        dns = try container.decodeIfPresent(DNSConfiguration.self, forKey: .dns)\n        rosetta = try container.decodeIfPresent(Bool.self, forKey: .rosetta) ?? false\n        initProcess = try container.decode(ProcessConfiguration.self, forKey: .initProcess)\n        platform = try container.decodeIfPresent(ContainerizationOCI.Platform.self, forKey: .platform) ?? .current\n        resources = try container.decodeIfPresent(Resources.self, forKey: .resources) ?? .init()\n        runtimeHandler = try container.decodeIfPresent(String.self, forKey: .runtimeHandler) ?? \"container-runtime-linux\"\n        virtualization = try container.decodeIfPresent(Bool.self, forKey: .virtualization) ?? false\n        ssh = try container.decodeIfPresent(Bool.self, forKey: .ssh) ?? false\n        readOnly = try container.decodeIfPresent(Bool.self, forKey: .readOnly) ?? false\n        useInit = try container.decodeIfPresent(Bool.self, forKey: .useInit) ?? false\n    }\n\n    public struct DNSConfiguration: Sendable, Codable {\n        public static let defaultNameservers = [\"1.1.1.1\"]\n\n        public let nameservers: [String]\n        public let domain: String?\n        public let searchDomains: [String]\n        public let options: [String]\n\n        public init(\n            nameservers: [String] = defaultNameservers,\n            domain: String? = nil,\n            searchDomains: [String] = [],\n            options: [String] = []\n        ) {\n            self.nameservers = nameservers\n            self.domain = domain\n            self.searchDomains = searchDomains\n            self.options = options\n        }\n    }\n\n    /// Resources like cpu, memory, and storage quota.\n    public struct Resources: Sendable, Codable {\n        /// Number of CPU cores allocated.\n        public var cpus: Int = 4\n        /// Memory in bytes allocated.\n        public var memoryInBytes: UInt64 = 1024.mib()\n        /// Storage quota/size in bytes.\n        public var storage: UInt64?\n\n        public init() {}\n    }\n\n    public init(\n        id: String,\n        image: ImageDescription,\n        process: ProcessConfiguration\n    ) {\n        self.id = id\n        self.image = image\n        self.initProcess = process\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerCreateOptions.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic struct ContainerCreateOptions: Codable, Sendable {\n    /// Remove the container and wipe out its data on container stop\n    public let autoRemove: Bool\n    /// Override the rootFs with this one other than the image-cloned version\n    public let rootFsOverride: Filesystem?\n\n    public init(autoRemove: Bool, rootFsOverride: Filesystem? = nil) {\n        self.autoRemove = autoRemove\n        self.rootFsOverride = rootFsOverride\n    }\n\n    public static let `default` = ContainerCreateOptions(autoRemove: false)\n\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerListFilters.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Filters for listing containers.\npublic struct ContainerListFilters: Sendable, Codable {\n    /// Filter by container IDs. If non-empty, only containers with matching IDs are returned.\n    public var ids: [String]\n    /// Filter by container status.\n    public var status: RuntimeStatus?\n    /// Filter by labels. All specified labels must match.\n    public var labels: [String: String]\n\n    /// No filters applied. Will return all containers.\n    public static let all = ContainerListFilters()\n\n    public init(\n        ids: [String] = [],\n        status: RuntimeStatus? = nil,\n        labels: [String: String] = [:]\n    ) {\n        self.ids = ids\n        self.status = status\n        self.labels = labels\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerSnapshot.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOCI\nimport Foundation\n\n/// A snapshot of a container along with its configuration\n/// and any runtime state information.\npublic struct ContainerSnapshot: Codable, Sendable {\n    /// The configuration of the container.\n    public var configuration: ContainerConfiguration\n\n    /// Identifier of the container.\n    public var id: String {\n        configuration.id\n    }\n\n    /// Configured platform for the container.\n    public var platform: ContainerizationOCI.Platform {\n        configuration.platform\n    }\n\n    /// The runtime status of the container.\n    public var status: RuntimeStatus\n    /// Network interfaces attached to the sandbox that are provided to the container.\n    public var networks: [Attachment]\n    /// When the container was started.\n    public var startedDate: Date?\n\n    public init(\n        configuration: ContainerConfiguration,\n        status: RuntimeStatus,\n        networks: [Attachment],\n        startedDate: Date? = nil\n    ) {\n        self.configuration = configuration\n        self.status = status\n        self.networks = networks\n        self.startedDate = startedDate\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerStats.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Statistics for a container suitable for CLI display.\npublic struct ContainerStats: Sendable, Codable {\n    /// Container ID\n    public var id: String\n    /// Physical memory usage in bytes\n    public var memoryUsageBytes: UInt64?\n    /// Memory limit in bytes\n    public var memoryLimitBytes: UInt64?\n    /// CPU usage in microseconds\n    public var cpuUsageUsec: UInt64?\n    /// Network received bytes (sum of all interfaces)\n    public var networkRxBytes: UInt64?\n    /// Network transmitted bytes (sum of all interfaces)\n    public var networkTxBytes: UInt64?\n    /// Block I/O read bytes (sum of all devices)\n    public var blockReadBytes: UInt64?\n    /// Block I/O write bytes (sum of all devices)\n    public var blockWriteBytes: UInt64?\n    /// Number of processes in the container\n    public var numProcesses: UInt64?\n\n    public init(\n        id: String,\n        memoryUsageBytes: UInt64?,\n        memoryLimitBytes: UInt64?,\n        cpuUsageUsec: UInt64?,\n        networkRxBytes: UInt64?,\n        networkTxBytes: UInt64?,\n        blockReadBytes: UInt64?,\n        blockWriteBytes: UInt64?,\n        numProcesses: UInt64?\n    ) {\n        self.id = id\n        self.memoryUsageBytes = memoryUsageBytes\n        self.memoryLimitBytes = memoryLimitBytes\n        self.cpuUsageUsec = cpuUsageUsec\n        self.networkRxBytes = networkRxBytes\n        self.networkTxBytes = networkTxBytes\n        self.blockReadBytes = blockReadBytes\n        self.blockWriteBytes = blockWriteBytes\n        self.numProcesses = numProcesses\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ContainerStopOptions.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\npublic struct ContainerStopOptions: Sendable, Codable {\n    public let timeoutInSeconds: Int32\n    public let signal: Int32\n\n    public static let `default` = ContainerStopOptions(\n        timeoutInSeconds: 5,\n        signal: SIGTERM\n    )\n\n    public init(timeoutInSeconds: Int32, signal: Int32) {\n        self.timeoutInSeconds = timeoutInSeconds\n        self.signal = signal\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/Filesystem.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Options to pass to a mount call.\npublic typealias MountOptions = [String]\n\nextension MountOptions {\n    /// Returns true if the Filesystem should be consumed as read-only.\n    public var readonly: Bool {\n        self.contains(\"ro\")\n    }\n}\n\n/// A host filesystem that will be attached to the sandbox for use.\n///\n/// A filesystem will be mounted automatically when starting the sandbox\n/// or container.\npublic struct Filesystem: Sendable, Codable {\n    /// Type of caching to perform at the host level.\n    public enum CacheMode: Sendable, Codable {\n        case on\n        case off\n        case auto\n    }\n\n    /// Sync mode to perform at the host level.\n    public enum SyncMode: Sendable, Codable {\n        case full\n        case fsync\n        case nosync\n    }\n\n    /// The type of filesystem attachment for the sandbox.\n    public enum FSType: Sendable, Codable, Equatable {\n        package enum VirtiofsType: String, Sendable, Codable, Equatable {\n            // This is a virtiofs share for the rootfs of a sandbox.\n            case rootfs\n            // Data share. This is what all virtiofs shares for anything besides\n            // the rootfs for a sandbox will be.\n            case data\n        }\n\n        case block(format: String, cache: CacheMode, sync: SyncMode)\n        case volume(name: String, format: String, cache: CacheMode, sync: SyncMode)\n        case virtiofs\n        case tmpfs\n    }\n\n    /// Type of the filesystem.\n    public var type: FSType\n    /// Source of the filesystem.\n    public var source: String\n    /// Destination where the filesystem should be mounted.\n    public var destination: String\n    /// Mount options applied when mounting the filesystem.\n    public var options: MountOptions\n\n    public init() {\n        self.type = .tmpfs\n        self.source = \"\"\n        self.destination = \"\"\n        self.options = []\n    }\n\n    public init(type: FSType, source: String, destination: String, options: MountOptions) {\n        self.type = type\n        self.source = source\n        self.destination = destination\n        self.options = options\n    }\n\n    // Defaulting to CachedMode = .on (i.e., cached mode) to fix Linux FS issue when using Virtualization\n    // * https://github.com/apple/container/issues/614\n    // * https://github.com/utmapp/UTM/pull/5919\n\n    /// A block based filesystem.\n    public static func block(\n        format: String, source: String, destination: String, options: MountOptions, cache: CacheMode = .on,\n        sync: SyncMode = .fsync\n    ) -> Filesystem {\n        .init(\n            type: .block(format: format, cache: cache, sync: sync),\n            source: URL(fileURLWithPath: source).absolutePath(),\n            destination: destination,\n            options: options\n        )\n    }\n\n    /// A named volume filesystem.\n    public static func volume(\n        name: String, format: String, source: String, destination: String, options: MountOptions,\n        cache: CacheMode = .on, sync: SyncMode = .fsync\n    ) -> Filesystem {\n        .init(\n            type: .volume(name: name, format: format, cache: cache, sync: sync),\n            source: URL(fileURLWithPath: source).absolutePath(),\n            destination: destination,\n            options: options\n        )\n    }\n\n    /// A vritiofs backed filesystem providing a directory.\n    public static func virtiofs(source: String, destination: String, options: MountOptions) -> Filesystem {\n        .init(\n            type: .virtiofs,\n            source: URL(fileURLWithPath: source).absolutePath(),\n            destination: destination,\n            options: options\n        )\n    }\n\n    public static func tmpfs(destination: String, options: MountOptions) -> Filesystem {\n        .init(\n            type: .tmpfs,\n            source: \"tmpfs\",\n            destination: destination,\n            options: options\n        )\n    }\n\n    /// Returns true if the Filesystem is backed by a block device.\n    public var isBlock: Bool {\n        switch type {\n        case .block(_, _, _): true\n        case .volume(_, _, _, _): true\n        default: false\n        }\n    }\n\n    /// Returns true if the Filesystem is a named volume.\n    public var isVolume: Bool {\n        switch type {\n        case .volume(_, _, _, _): true\n        default: false\n        }\n    }\n\n    /// Returns the volume name if this is a volume filesystem, nil otherwise.\n    public var volumeName: String? {\n        switch type {\n        case .volume(let name, _, _, _): name\n        default: nil\n        }\n    }\n\n    /// Returns true if the Filesystem is backed by a in-memory mount type.\n    public var isTmpfs: Bool {\n        switch type {\n        case .tmpfs: true\n        default: false\n        }\n    }\n\n    /// Returns true if the Filesystem is backed by virtioFS.\n    public var isVirtiofs: Bool {\n        switch type {\n        case .virtiofs: true\n        default: false\n        }\n    }\n\n    /// Clone the Filesystem to the provided path.\n    ///\n    /// This uses `clonefile` to provide a copy-on-write copy of the Filesystem.\n    public func clone(to: String) throws -> Self {\n        let fm = FileManager.default\n        let src = self.source\n        try fm.copyItem(atPath: src, toPath: to)\n        return .init(type: self.type, source: to, destination: self.destination, options: self.options)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/ProcessConfiguration.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Configuration data for an executable Process.\npublic struct ProcessConfiguration: Sendable, Codable {\n    /// The on disk path to the executable binary.\n    public var executable: String\n    /// Arguments passed to the Process.\n    public var arguments: [String]\n    /// Environment variables for the Process.\n    public var environment: [String]\n    /// The current working directory (cwd) for the Process.\n    public var workingDirectory: String\n    /// A boolean value indicating if a Terminal or PTY device should\n    /// be attached to the Process's Standard I/O.\n    public var terminal: Bool\n    /// The User a Process should execute under.\n    public var user: User\n    /// Supplemental groups for the Process.\n    public var supplementalGroups: [UInt32]\n    /// Rlimits for the Process.\n    public var rlimits: [Rlimit]\n\n    /// Rlimits for Processes.\n    public struct Rlimit: Sendable, Codable {\n        /// The Rlimit type of the Process.\n        ///\n        /// Values include standard Rlimit resource types, i.e. RLIMIT_NPROC, RLIMIT_NOFILE, ...\n        public let limit: String\n        /// The soft limit of the Process\n        public let soft: UInt64\n        /// The hard or max limit of the Process.\n        public let hard: UInt64\n\n        public init(limit: String, soft: UInt64, hard: UInt64) {\n            self.limit = limit\n            self.soft = soft\n            self.hard = hard\n        }\n    }\n\n    /// The User information for a Process.\n    public enum User: Sendable, Codable, CustomStringConvertible {\n        /// Given the raw user string  of the form <uid:gid> or <user:group> or <user> lookup the uid/gid within\n        /// the container before setting it for the Process.\n        case raw(userString: String)\n        /// Set the provided uid/gid for the Process.\n        case id(uid: UInt32, gid: UInt32)\n\n        public var description: String {\n            switch self {\n            case .id(let uid, let gid):\n                return \"\\(uid):\\(gid)\"\n            case .raw(let name):\n                return name\n            }\n        }\n    }\n\n    public init(\n        executable: String,\n        arguments: [String],\n        environment: [String],\n        workingDirectory: String = \"/\",\n        terminal: Bool = false,\n        user: User = .id(uid: 0, gid: 0),\n        supplementalGroups: [UInt32] = [],\n        rlimits: [Rlimit] = []\n    ) {\n        self.executable = executable\n        self.arguments = arguments\n        self.environment = environment\n        self.workingDirectory = workingDirectory\n        self.terminal = terminal\n        self.user = user\n        self.supplementalGroups = supplementalGroups\n        self.rlimits = rlimits\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/PublishPort.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\n\n/// The network protocols available for port forwarding.\npublic enum PublishProtocol: String, Sendable, Codable {\n    case tcp = \"tcp\"\n    case udp = \"udp\"\n\n    /// Initialize a protocol with to default value, `.tcp`.\n    public init() {\n        self = .tcp\n    }\n\n    /// Initialize a protocol value from the provided string.\n    public init?(_ value: String) {\n        switch value.lowercased() {\n        case \"tcp\": self = .tcp\n        case \"udp\": self = .udp\n        default: return nil\n        }\n    }\n}\n\n/// Specifies internet port forwarding from host to container.\npublic struct PublishPort: Sendable, Codable {\n    /// The IP address of the proxy listener on the host\n    public let hostAddress: IPAddress\n\n    /// The port number of the proxy listener on the host\n    public let hostPort: UInt16\n\n    /// The port number of the container listener\n    public let containerPort: UInt16\n\n    /// The network protocol for the proxy\n    public let proto: PublishProtocol\n\n    /// The number of ports to publish\n    public let count: UInt16\n\n    /// Creates a new port forwarding specification.\n    public init(hostAddress: IPAddress, hostPort: UInt16, containerPort: UInt16, proto: PublishProtocol, count: UInt16) {\n        self.hostAddress = hostAddress\n        self.hostPort = hostPort\n        self.containerPort = containerPort\n        self.proto = proto\n        self.count = count\n    }\n\n    /// Create a configuration from the supplied Decoder, initializing missing\n    /// values where possible to reasonable defaults.\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        hostAddress = try container.decode(IPAddress.self, forKey: .hostAddress)\n        hostPort = try container.decode(UInt16.self, forKey: .hostPort)\n        containerPort = try container.decode(UInt16.self, forKey: .containerPort)\n        proto = try container.decode(PublishProtocol.self, forKey: .proto)\n        count = try container.decodeIfPresent(UInt16.self, forKey: .count) ?? 1\n    }\n}\n\nextension [PublishPort] {\n    public func hasOverlaps() -> Bool {\n        var hostPorts = Set<String>()\n        for publishPort in self {\n            for index in publishPort.hostPort..<(publishPort.hostPort + publishPort.count) {\n                let hostPortKey = \"\\(index)/\\(publishPort.proto.rawValue)\"\n                guard !hostPorts.contains(hostPortKey) else {\n                    return true\n                }\n                hostPorts.insert(hostPortKey)\n            }\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/PublishSocket.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport SystemPackage\n\n/// Represents a socket that should be published from container to host.\npublic struct PublishSocket: Sendable, Codable {\n    /// The path to the socket in the container.\n    public var containerPath: URL\n\n    /// The path where the socket should appear on the host.\n    public var hostPath: URL\n\n    /// File permissions for the socket on the host.\n    public var permissions: FilePermissions?\n\n    public init(\n        containerPath: URL,\n        hostPath: URL,\n        permissions: FilePermissions? = nil\n    ) {\n        self.containerPath = containerPath\n        self.hostPath = hostPath\n        self.permissions = permissions\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Container/RuntimeStatus.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Runtime status for a sandbox or container.\npublic enum RuntimeStatus: String, CaseIterable, Sendable, Codable {\n    /// The object is in an unknown status.\n    case unknown\n    /// The object is currently stopped.\n    case stopped\n    /// The object is currently running.\n    case running\n    /// The object is currently stopping.\n    case stopping\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Image/ImageDescription.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationOCI\n\n/// A type that represents an OCI image that can be used with sandboxes or containers.\npublic struct ImageDescription: Sendable, Codable {\n    /// The public reference/name of the image.\n    public let reference: String\n    /// The descriptor of the image.\n    public let descriptor: Descriptor\n\n    public var digest: String { descriptor.digest }\n    public var mediaType: String { descriptor.mediaType }\n\n    public init(reference: String, descriptor: Descriptor) {\n        self.reference = reference\n        self.descriptor = descriptor\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Image/ImageDetail.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Containerization\nimport ContainerizationOCI\n\npublic struct ImageDetail: Codable {\n    public let name: String\n    public let index: Descriptor\n    public let variants: [Variants]\n\n    public struct Variants: Codable {\n        public let platform: Platform\n        public let config: ContainerizationOCI.Image\n        public let size: Int64\n\n        public init(platform: Platform, size: Int64, config: ContainerizationOCI.Image) {\n            self.platform = platform\n            self.config = config\n            self.size = size\n        }\n    }\n\n    public init(name: String, index: Descriptor, variants: [Variants]) {\n        self.name = name\n        self.index = index\n        self.variants = variants\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/AllocatedAttachment.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\n\n/// AllocatedAttachment represents a network attachment that has been allocated for use\n/// by a container and any additional relevant data needed for a sandbox to properly\n/// configure networking on container bootstrap.\npublic struct AllocatedAttachment: Sendable {\n    public let attachment: Attachment\n    public let additionalData: XPCMessage?\n    public let pluginInfo: NetworkPluginInfo\n\n    public init(attachment: Attachment, additionalData: XPCMessage?, pluginInfo: NetworkPluginInfo) {\n        self.attachment = attachment\n        self.additionalData = additionalData\n        self.pluginInfo = pluginInfo\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/Attachment.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\n\n/// A snapshot of a network interface for a sandbox.\npublic struct Attachment: Codable, Sendable {\n    /// The network ID associated with the attachment.\n    public let network: String\n    /// The hostname associated with the attachment.\n    public let hostname: String\n    /// The CIDR address describing the interface IPv4 address, with the prefix length of the subnet.\n    public let ipv4Address: CIDRv4\n    /// The IPv4 gateway address.\n    public let ipv4Gateway: IPv4Address\n    /// The CIDR address describing the interface IPv6 address, with the prefix length of the subnet.\n    /// The address is nil if the IPv6 subnet could not be determined at network creation time.\n    public let ipv6Address: CIDRv6?\n    /// The MAC address associated with the attachment (optional).\n    public let macAddress: MACAddress?\n    /// The MTU for the network interface.\n    public let mtu: UInt32?\n\n    public init(\n        network: String,\n        hostname: String,\n        ipv4Address: CIDRv4,\n        ipv4Gateway: IPv4Address,\n        ipv6Address: CIDRv6?,\n        macAddress: MACAddress?,\n        mtu: UInt32? = nil\n    ) {\n        self.network = network\n        self.hostname = hostname\n        self.ipv4Address = ipv4Address\n        self.ipv4Gateway = ipv4Gateway\n        self.ipv6Address = ipv6Address\n        self.macAddress = macAddress\n        self.mtu = mtu\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case network\n        case hostname\n        case ipv4Address\n        case ipv4Gateway\n        case ipv6Address\n        case macAddress\n        case mtu\n        // TODO: retain for deserialization compatibility for now, remove later\n        case address\n        case gateway\n    }\n\n    /// Create a configuration from the supplied Decoder, initializing missing\n    /// values where possible to reasonable defaults.\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        network = try container.decode(String.self, forKey: .network)\n        hostname = try container.decode(String.self, forKey: .hostname)\n        if let address = try? container.decode(CIDRv4.self, forKey: .ipv4Address) {\n            ipv4Address = address\n        } else {\n            ipv4Address = try container.decode(CIDRv4.self, forKey: .address)\n        }\n        if let gateway = try? container.decode(IPv4Address.self, forKey: .ipv4Gateway) {\n            ipv4Gateway = gateway\n        } else {\n            ipv4Gateway = try container.decode(IPv4Address.self, forKey: .gateway)\n        }\n        ipv6Address = try container.decodeIfPresent(CIDRv6.self, forKey: .ipv6Address)\n        macAddress = try container.decodeIfPresent(MACAddress.self, forKey: .macAddress)\n        mtu = try container.decodeIfPresent(UInt32.self, forKey: .mtu)\n    }\n\n    /// Encode the configuration to the supplied Encoder.\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        try container.encode(network, forKey: .network)\n        try container.encode(hostname, forKey: .hostname)\n        try container.encode(ipv4Address, forKey: .ipv4Address)\n        try container.encode(ipv4Gateway, forKey: .ipv4Gateway)\n        try container.encodeIfPresent(ipv6Address, forKey: .ipv6Address)\n        try container.encodeIfPresent(macAddress, forKey: .macAddress)\n        try container.encodeIfPresent(mtu, forKey: .mtu)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/AttachmentConfiguration.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\n\n/// Configuration information for attaching a container network interface to a network.\npublic struct AttachmentConfiguration: Codable, Sendable {\n    /// The network ID associated with the attachment.\n    public let network: String\n\n    /// The option information for the attachment\n    public let options: AttachmentOptions\n\n    public init(network: String, options: AttachmentOptions) {\n        self.network = network\n        self.options = options\n    }\n}\n\n// Option information for a network attachment.\npublic struct AttachmentOptions: Codable, Sendable {\n    /// The hostname associated with the attachment.\n    public let hostname: String\n\n    /// The MAC address associated with the attachment (optional).\n    public let macAddress: MACAddress?\n\n    /// The MTU for the network interface.\n    public let mtu: UInt32?\n\n    public init(hostname: String, macAddress: MACAddress? = nil, mtu: UInt32? = nil) {\n        self.hostname = hostname\n        self.macAddress = macAddress\n        self.mtu = mtu\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/NetworkConfiguration.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\npublic struct NetworkPluginInfo: Codable, Sendable, Hashable {\n    public let plugin: String\n    public let variant: String?\n\n    public init(plugin: String, variant: String? = nil) {\n        self.plugin = plugin\n        self.variant = variant\n    }\n}\n\n/// Configuration parameters for network creation.\npublic struct NetworkConfiguration: Codable, Sendable, Identifiable {\n    /// A unique identifier for the network\n    public let id: String\n\n    /// The network type\n    public let mode: NetworkMode\n\n    /// When the network was created.\n    public let creationDate: Date\n\n    /// The preferred CIDR address for the IPv4 subnet, if specified\n    public let ipv4Subnet: CIDRv4?\n\n    /// The preferred CIDR address for the IPv6 subnet, if specified\n    public let ipv6Subnet: CIDRv6?\n\n    /// Key-value labels for the network.\n    public var labels: [String: String] = [:]\n\n    /// Details about the network plugin that manages this network.\n    /// FIXME: This field only needs to be optional while we wait for the field\n    /// to be proliferated to most users when they update container.\n    public var pluginInfo: NetworkPluginInfo?\n\n    /// Creates a network configuration\n    public init(\n        id: String,\n        mode: NetworkMode,\n        ipv4Subnet: CIDRv4? = nil,\n        ipv6Subnet: CIDRv6? = nil,\n        labels: [String: String] = [:],\n        pluginInfo: NetworkPluginInfo\n    ) throws {\n        self.id = id\n        self.creationDate = Date()\n        self.mode = mode\n        self.ipv4Subnet = ipv4Subnet\n        self.ipv6Subnet = ipv6Subnet\n        self.labels = labels\n        self.pluginInfo = pluginInfo\n        try validate()\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case id\n        case creationDate\n        case mode\n        case ipv4Subnet\n        case ipv6Subnet\n        case labels\n        case pluginInfo\n        // TODO: retain for deserialization compatibility for now, remove later\n        case subnet\n    }\n\n    /// Create a configuration from the supplied Decoder, initializing missing\n    /// values where possible to reasonable defaults.\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        id = try container.decode(String.self, forKey: .id)\n        creationDate = try container.decodeIfPresent(Date.self, forKey: .creationDate) ?? Date(timeIntervalSince1970: 0)\n        mode = try container.decode(NetworkMode.self, forKey: .mode)\n        let subnetText =\n            try container.decodeIfPresent(String.self, forKey: .ipv4Subnet)\n            ?? container.decodeIfPresent(String.self, forKey: .subnet)\n        ipv4Subnet = try subnetText.map { try CIDRv4($0) }\n        ipv6Subnet = try container.decodeIfPresent(String.self, forKey: .ipv6Subnet)\n            .map { try CIDRv6($0) }\n        labels = try container.decodeIfPresent([String: String].self, forKey: .labels) ?? [:]\n        pluginInfo = try container.decodeIfPresent(NetworkPluginInfo.self, forKey: .pluginInfo)\n        try validate()\n    }\n\n    /// Encode the configuration to the supplied Encoder.\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        try container.encode(id, forKey: .id)\n        try container.encode(creationDate, forKey: .creationDate)\n        try container.encode(mode, forKey: .mode)\n        try container.encodeIfPresent(ipv4Subnet, forKey: .ipv4Subnet)\n        try container.encodeIfPresent(ipv6Subnet, forKey: .ipv6Subnet)\n        try container.encode(labels, forKey: .labels)\n        try container.encodeIfPresent(pluginInfo, forKey: .pluginInfo)\n    }\n\n    private func validate() throws {\n        guard id.isValidNetworkID() else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid network ID: \\(id)\")\n        }\n\n        for (key, value) in labels {\n            try validateLabel(key: key, value: value)\n        }\n    }\n\n    /// TODO: Extract when we clean up client dependencies.\n    private func validateLabel(key: String, value: String) throws {\n        let keyLengthMax = 128\n        let labelLengthMax = 4096\n        guard key.count <= keyLengthMax else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid label, key length is greater than \\(keyLengthMax): \\(key)\")\n        }\n\n        guard key.isValidLabelKey() else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid label key: \\(key)\")\n        }\n\n        let fullLabel = \"\\(key)=\\(value)\"\n        guard fullLabel.count <= labelLengthMax else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid label, key length is greater than \\(labelLengthMax): \\(fullLabel)\")\n        }\n    }\n}\n\nextension String {\n    /// Ensure that the network ID has the correct syntax.\n    fileprivate func isValidNetworkID() -> Bool {\n        let pattern = #\"^[a-z0-9](?:[a-z0-9._-]{0,61}[a-z0-9])?$\"#\n        return self.range(of: pattern, options: .regularExpression) != nil\n    }\n\n    /// Ensure label key conforms to OCI or Docker label guidelines.\n    /// TODO: Extract when we clean up client dependencies.\n    fileprivate func isValidLabelKey() -> Bool {\n        let dockerPattern = #/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*$/#\n        let ociPattern = #/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*(?:/(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*))*$/#\n        let dockerMatch = !self.ranges(of: dockerPattern).isEmpty\n        let ociMatch = !self.ranges(of: ociPattern).isEmpty\n        return dockerMatch || ociMatch\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/NetworkMode.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Networking mode that applies to client containers.\npublic enum NetworkMode: String, Codable, Sendable {\n    /// NAT networking mode.\n    /// Containers do not have routable IPs, and the host performs network\n    /// address translation to allow containers to reach external services.\n    case nat = \"nat\"\n\n    /// Host only networking mode\n    /// Containers can talk with each other in the same subnet only.\n    case hostOnly = \"hostOnly\"\n}\n\nextension NetworkMode {\n    public init() {\n        self = .nat\n    }\n\n    public init?(_ value: String) {\n        switch value.lowercased() {\n        case \"nat\": self = .nat\n        case \"hostOnly\": self = .hostOnly\n        default: return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Network/NetworkState.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Foundation\n\npublic struct NetworkStatus: Codable, Sendable {\n    /// The address allocated for the network if no subnet was specified at\n    /// creation time; otherwise, the subnet from the configuration.\n    public let ipv4Subnet: CIDRv4\n\n    /// The gateway IPv4 address.\n    public let ipv4Gateway: IPv4Address\n\n    /// The address allocated for the IPv6 network if no subnet was specified at\n    /// creation time; otherwise, the IPv6 subnet from the configuration.\n    /// The value is nil if the IPv6 subnet cannot be determined at creation time.\n    public let ipv6Subnet: CIDRv6?\n\n    public init(\n        ipv4Subnet: CIDRv4,\n        ipv4Gateway: IPv4Address,\n        ipv6Subnet: CIDRv6?,\n    ) {\n        self.ipv4Subnet = ipv4Subnet\n        self.ipv4Gateway = ipv4Gateway\n        self.ipv6Subnet = ipv6Subnet\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case ipv4Subnet\n        case ipv4Gateway\n        case ipv6Subnet\n        // TODO: retain for deserialization compatibility for now, remove later\n        case address\n        case gateway\n    }\n\n    /// Create a configuration from the supplied Decoder, initializing missing\n    /// values where possible to reasonable defaults.\n    public init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        if let address = try? container.decode(CIDRv4.self, forKey: .ipv4Subnet) {\n            ipv4Subnet = address\n        } else {\n            ipv4Subnet = try container.decode(CIDRv4.self, forKey: .address)\n        }\n        if let gateway = try? container.decode(IPv4Address.self, forKey: .ipv4Gateway) {\n            ipv4Gateway = gateway\n        } else {\n            ipv4Gateway = try container.decode(IPv4Address.self, forKey: .gateway)\n        }\n        ipv6Subnet = try container.decodeIfPresent(String.self, forKey: .ipv6Subnet)\n            .map { try CIDRv6($0) }\n    }\n\n    /// Encode the configuration to the supplied Encoder.\n    public func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        try container.encode(ipv4Subnet, forKey: .ipv4Subnet)\n        try container.encode(ipv4Gateway, forKey: .ipv4Gateway)\n        try container.encodeIfPresent(ipv6Subnet, forKey: .ipv6Subnet)\n    }\n}\n\n/// The configuration and runtime attributes for a network.\npublic enum NetworkState: Codable, Sendable {\n    // The network has been configured.\n    case created(NetworkConfiguration)\n    // The network is running.\n    case running(NetworkConfiguration, NetworkStatus)\n\n    public var state: String {\n        switch self {\n        case .created: \"created\"\n        case .running: \"running\"\n        }\n    }\n\n    public var id: String {\n        switch self {\n        case .created(let config), .running(let config, _): config.id\n        }\n    }\n\n    public var creationDate: Date {\n        switch self {\n        case .created(let config), .running(let config, _): config.creationDate\n        }\n    }\n\n    public var isBuiltin: Bool {\n        switch self {\n        case .created(let config), .running(let config, _): config.labels.isBuiltin\n        }\n    }\n\n    public var pluginInfo: NetworkPluginInfo? {\n        switch self {\n        case .created(let configuration), .running(let configuration, _): configuration.pluginInfo\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Registry/RegistryResource.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\n\n/// A container registry resource representing a configured registry endpoint.\n///\n/// Registry resources store authentication and configuration information for\n/// container registries such as Docker Hub, GitHub Container Registry, or\n/// private registries.\npublic struct RegistryResource: ManagedResource {\n    /// The registry hostname that uniquely identifies this resource.\n    ///\n    /// For registry resources, the identifier is the same as the hostname.\n    public let id: String\n\n    /// The hostname of the registry.\n    ///\n    /// This value must be a valid DNS hostname or IPv6 address, optionally\n    /// followed by a port number (e.g., \"docker.io\", \"localhost:5000\", \"[::1]:5000\").\n    public var name: String\n\n    /// The username used for authentication with this registry.\n    public let username: String\n\n    /// The time at which the system created this registry resource.\n    public var creationDate: Date\n\n    /// The time at which the registry resource was last modified.\n    public var modificationDate: Date\n\n    /// Key-value properties for the resource.\n    ///\n    /// The user and system may both make use of labels to read and write\n    /// annotations or other metadata.\n    public var labels: [String: String]\n\n    /// Validates a registry hostname according to OCI distribution specification.\n    ///\n    /// This method validates that a registry hostname conforms to the domain pattern\n    /// used by OCI image references. It supports DNS hostnames, IPv6 addresses, and\n    /// optional port numbers.\n    ///\n    /// - Parameter name: The registry hostname to validate\n    /// - Returns: `true` if the hostname is syntactically valid, `false` otherwise\n    ///\n    /// ## Valid Examples\n    /// - `docker.io`\n    /// - `registry.example.com`\n    /// - `localhost:5000`\n    /// - `[::1]:5000`\n    ///\n    /// ## Implementation Notes\n    /// The validation logic is based on ContainerizationOCI's `Reference.domainPattern`.\n    /// See <https://github.com/apple/containerization/blob/main/Sources/ContainerizationOCI/Reference.swift>\n    public static func nameValid(_ name: String) -> Bool {\n        // Domain validation logic based on ContainerizationOCI Reference.domainPattern\n        // See: https://github.com/apple/containerization/blob/main/Sources/ContainerizationOCI/Reference.swift\n        // TODO: if we have domain IP validation API, use that instead\n        let domainNameComponent = \"(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\"\n        let optionalPort = \"(?::[0-9]+)?\"\n        let ipv6address = \"\\\\[(?:[a-fA-F0-9:]+)\\\\]\"\n        let domainName = \"\\(domainNameComponent)(?:\\\\.\\(domainNameComponent))*\"\n        let host = \"(?:\\(domainName)|\\(ipv6address))\"\n        let pattern = \"^\\(host)\\(optionalPort)$\"\n\n        return name.range(of: pattern, options: .regularExpression) != nil\n    }\n\n    /// Creates a new registry resource.\n    ///\n    /// - Parameters:\n    ///   - hostname: The registry hostname (also used as the resource ID)\n    ///   - username: The username for authentication\n    ///   - creationDate: The time the resource was created\n    ///   - modificationDate: The time the resource was last modified\n    ///   - labels: Optional key-value labels for metadata (default: empty dictionary)\n    public init(\n        hostname: String,\n        username: String,\n        creationDate: Date,\n        modificationDate: Date,\n        labels: [String: String] = [:]\n    ) {\n        self.id = hostname\n        self.name = hostname\n        self.username = username\n        self.creationDate = creationDate\n        self.modificationDate = modificationDate\n        self.labels = labels\n    }\n}\n\nextension RegistryResource {\n    /// Creates a registry resource from registry information.\n    ///\n    /// - Parameter registryInfo: The registry information to convert\n    public init(from registryInfo: RegistryInfo) {\n        self.init(\n            hostname: registryInfo.hostname,\n            username: registryInfo.username,\n            creationDate: registryInfo.createdDate,\n            modificationDate: registryInfo.modifiedDate\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerResource/Volume/Volume.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A named or anonymous volume that can be mounted in containers.\npublic struct Volume: Sendable, Codable, Equatable, Identifiable {\n    // id of the volume.\n    public var id: String { name }\n    // Name of the volume.\n    public var name: String\n    // Driver used to create the volume.\n    public var driver: String\n    // Filesystem format of the volume.\n    public var format: String\n    // The mount point of the volume on the host.\n    public var source: String\n    // Timestamp when the volume was created.\n    public var createdAt: Date\n    // User-defined key/value metadata.\n    public var labels: [String: String]\n    // Driver-specific options.\n    public var options: [String: String]\n    // Size of the volume in bytes (optional).\n    public var sizeInBytes: UInt64?\n\n    public init(\n        name: String,\n        driver: String = \"local\",\n        format: String = \"ext4\",\n        source: String,\n        createdAt: Date = Date(),\n        labels: [String: String] = [:],\n        options: [String: String] = [:],\n        sizeInBytes: UInt64? = nil\n    ) {\n        self.name = name\n        self.driver = driver\n        self.format = format\n        self.source = source\n        self.createdAt = createdAt\n        self.labels = labels\n        self.options = options\n        self.sizeInBytes = sizeInBytes\n    }\n}\n\nextension Volume {\n    /// Reserved label key for marking anonymous volumes\n    public static let anonymousLabel = \"com.apple.container.resource.anonymous\"\n\n    /// Whether this is an anonymous volume (detected via label)\n    public var isAnonymous: Bool {\n        labels[Self.anonymousLabel] != nil\n    }\n}\n\n/// Error types for volume operations.\npublic enum VolumeError: Error, LocalizedError {\n    case volumeNotFound(String)\n    case volumeAlreadyExists(String)\n    case volumeInUse(String)\n    case invalidVolumeName(String)\n    case driverNotSupported(String)\n    case storageError(String)\n\n    public var errorDescription: String? {\n        switch self {\n        case .volumeNotFound(let name):\n            return \"volume '\\(name)' not found\"\n        case .volumeAlreadyExists(let name):\n            return \"volume '\\(name)' already exists\"\n        case .volumeInUse(let name):\n            return \"volume '\\(name)' is currently in use and cannot be accessed by another container, or deleted\"\n        case .invalidVolumeName(let name):\n            return \"invalid volume name '\\(name)'\"\n        case .driverNotSupported(let driver):\n            return \"volume driver '\\(driver)' is not supported\"\n        case .storageError(let message):\n            return \"storage error: \\(message)\"\n        }\n    }\n}\n\n/// Volume storage management utilities.\npublic struct VolumeStorage {\n    public static let volumeNamePattern = \"^[A-Za-z0-9][A-Za-z0-9_.-]*$\"\n    public static let defaultVolumeSizeBytes: UInt64 = 512 * 1024 * 1024 * 1024  // 512GB\n\n    public static func isValidVolumeName(_ name: String) -> Bool {\n        guard name.count <= 255 else { return false }\n\n        do {\n            let regex = try Regex(volumeNamePattern)\n            return (try? regex.wholeMatch(in: name)) != nil\n        } catch {\n            return false\n        }\n    }\n\n    /// Generates an anonymous volume name with UUID format\n    public static func generateAnonymousVolumeName() -> String {\n        UUID().uuidString.lowercased()\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerVersion/Bundle+AppBundle.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Retrieve the application bundle for a path that refers to a macOS executable.\nextension Bundle {\n    public static func appBundle(executableURL: URL) -> Bundle? {\n        let resolvedURL = executableURL.resolvingSymlinksInPath()\n        let macOSURL = resolvedURL.deletingLastPathComponent()\n        let contentsURL = macOSURL.deletingLastPathComponent()\n        let bundleURL = contentsURL.deletingLastPathComponent()\n        if bundleURL.pathExtension == \"app\" {\n            return Bundle(url: bundleURL)\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerVersion/CommandLine+Executable.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension CommandLine {\n    public static var executablePathUrl: URL {\n        /// _NSGetExecutablePath with a zero-length buffer returns the needed buffer length\n        var bufferSize: Int32 = 0\n        var buffer = [CChar](repeating: 0, count: Int(bufferSize))\n        _ = _NSGetExecutablePath(&buffer, &bufferSize)\n\n        /// Create the buffer and get the path\n        buffer = [CChar](repeating: 0, count: Int(bufferSize))\n        guard _NSGetExecutablePath(&buffer, &bufferSize) == 0 else {\n            fatalError(\"unexpected: failed to get executable path\")\n        }\n\n        /// Return the path with the executable file component removed the last component and\n        let executablePath = String(cString: &buffer)\n        return URL(filePath: executablePath)\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerVersion/ReleaseVersion.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport CVersion\nimport Foundation\n\npublic struct ReleaseVersion {\n    public static func singleLine(appName: String) -> String {\n        var versionDetails: [String: String] = [\"build\": buildType()]\n        versionDetails[\"commit\"] = gitCommit().map { String($0.prefix(7)) } ?? \"unspecified\"\n        let extras: String = versionDetails.map { \"\\($0): \\($1)\" }.sorted().joined(separator: \", \")\n\n        return \"\\(appName) version \\(version()) (\\(extras))\"\n    }\n\n    public static func buildType() -> String {\n        #if DEBUG\n        return \"debug\"\n        #else\n        return \"release\"\n        #endif\n    }\n\n    public static func version() -> String {\n        let appBundle = Bundle.appBundle(executableURL: CommandLine.executablePathUrl)\n        let bundleVersion = appBundle?.infoDictionary?[\"CFBundleShortVersionString\"] as? String\n        return bundleVersion ?? get_release_version().map { String(cString: $0) } ?? \"0.0.0\"\n    }\n\n    public static func gitCommit() -> String? {\n        get_git_commit().map { String(cString: $0) }\n    }\n}\n"
  },
  {
    "path": "Sources/ContainerXPC/XPCClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport ContainerizationError\nimport Foundation\n\npublic final class XPCClient: Sendable {\n    /// The maximum amount of time to wait for a request to a recently\n    /// registered XPC service. Once a service has launched, XPC\n    /// requests only have milliseconds of overhead, but in some instances,\n    /// macOS can take 5 seconds (or considerably longer) to launch a\n    /// service after it has been registered.\n    public static let xpcRegistrationTimeout: Duration = .seconds(60)\n\n    private nonisolated(unsafe) let connection: xpc_connection_t\n    private let q: DispatchQueue?\n    private let service: String\n\n    public init(service: String, queue: DispatchQueue? = nil) {\n        let connection = xpc_connection_create_mach_service(service, queue, 0)\n        self.connection = connection\n        self.q = queue\n        self.service = service\n\n        xpc_connection_set_event_handler(connection) { _ in }\n        xpc_connection_set_target_queue(connection, self.q)\n        xpc_connection_activate(connection)\n    }\n\n    public init(connection: xpc_connection_t, label: String, queue: DispatchQueue? = nil) {\n        self.connection = connection\n        self.q = queue\n        self.service = label\n\n        xpc_connection_set_event_handler(connection) { _ in }\n        xpc_connection_set_target_queue(connection, self.q)\n        xpc_connection_activate(connection)\n    }\n\n    deinit {\n        self.close()\n    }\n}\n\nextension XPCClient {\n    /// Close the underlying XPC connection.\n    public func close() {\n        xpc_connection_cancel(connection)\n    }\n\n    /// Returns the pid of process to which we have a connection.\n    /// Note: `xpc_connection_get_pid` returns 0 if no activity\n    /// has taken place on the connection prior to it being called.\n    public func remotePid() -> pid_t {\n        xpc_connection_get_pid(self.connection)\n    }\n\n    /// Send the provided message to the service.\n    @discardableResult\n    public func send(_ message: XPCMessage, responseTimeout: Duration? = nil) async throws -> XPCMessage {\n        try await withThrowingTaskGroup(of: XPCMessage.self, returning: XPCMessage.self) { group in\n            if let responseTimeout {\n                group.addTask {\n                    try await Task.sleep(for: responseTimeout)\n                    let route = message.string(key: XPCMessage.routeKey) ?? \"nil\"\n                    throw ContainerizationError(\n                        .internalError,\n                        message: \"XPC timeout for request to \\(self.service)/\\(route)\"\n                    )\n                }\n            }\n\n            group.addTask {\n                try await withCheckedThrowingContinuation { cont in\n                    xpc_connection_send_message_with_reply(self.connection, message.underlying, nil) { reply in\n                        do {\n                            let message = try self.parseReply(reply)\n                            cont.resume(returning: message)\n                        } catch {\n                            cont.resume(throwing: error)\n                        }\n                    }\n                }\n            }\n\n            let response = try await group.next()\n            // once one task has finished, cancel the rest.\n            group.cancelAll()\n            // we don't really care about the second error here\n            // as it's most likely a `CancellationError`.\n            try? await group.waitForAll()\n\n            guard let response else {\n                throw ContainerizationError(.invalidState, message: \"failed to receive XPC response\")\n            }\n            return response\n        }\n    }\n\n    private func parseReply(_ reply: xpc_object_t) throws -> XPCMessage {\n        switch xpc_get_type(reply) {\n        case XPC_TYPE_ERROR:\n            var code = ContainerizationError.Code.invalidState\n            if reply.connectionError {\n                code = .interrupted\n            }\n            throw ContainerizationError(\n                code,\n                message: \"XPC connection error: \\(reply.errorDescription ?? \"unknown\")\"\n            )\n        case XPC_TYPE_DICTIONARY:\n            let message = XPCMessage(object: reply)\n            // check errors from our protocol\n            try message.error()\n            return message\n        default:\n            fatalError(\"unhandled xpc object type: \\(xpc_get_type(reply))\")\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/ContainerXPC/XPCMessage.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport ContainerizationError\nimport Foundation\n\n/// A message that can be pass across application boundaries via XPC.\npublic struct XPCMessage: Sendable {\n    /// Defined message key storing the route value.\n    public static let routeKey = \"com.apple.container.xpc.route\"\n    /// Defined message key storing the error value.\n    public static let errorKey = \"com.apple.container.xpc.error\"\n\n    // Access to `object` is protected by a lock\n    private nonisolated(unsafe) let object: xpc_object_t\n    private let lock = NSLock()\n    private let isErr: Bool\n\n    /// The underlying xpc object that the message wraps.\n    public var underlying: xpc_object_t {\n        lock.withLock {\n            object\n        }\n    }\n    public var isErrorType: Bool { isErr }\n\n    public init(object: xpc_object_t) {\n        self.object = object\n        self.isErr = xpc_get_type(self.object) == XPC_TYPE_ERROR\n    }\n\n    public init(route: String) {\n        self.object = xpc_dictionary_create_empty()\n        self.isErr = false\n        xpc_dictionary_set_string(self.object, Self.routeKey, route)\n    }\n}\n\nextension XPCMessage {\n    public static func == (lhs: XPCMessage, rhs: xpc_object_t) -> Bool {\n        xpc_equal(lhs.underlying, rhs)\n    }\n\n    public func reply() -> XPCMessage {\n        lock.withLock {\n            XPCMessage(object: xpc_dictionary_create_reply(object)!)\n        }\n    }\n\n    public func errorKeyDescription() -> String? {\n        guard self.isErr,\n            let xpcErr = lock.withLock({\n                xpc_dictionary_get_string(\n                    self.object,\n                    XPC_ERROR_KEY_DESCRIPTION\n                )\n            })\n        else {\n            return nil\n        }\n        return String(cString: xpcErr)\n    }\n\n    public func error() throws {\n        let data = data(key: Self.errorKey)\n        if let data {\n            let item = try? JSONDecoder().decode(ContainerXPCError.self, from: data)\n            precondition(item != nil, \"expected to receive a ContainerXPCXPCError\")\n\n            throw ContainerizationError(item!.code, message: item!.message)\n        }\n    }\n\n    public func set(error: ContainerizationError) {\n        var message = error.message\n        if let cause = error.cause {\n            message += \" (cause: \\\"\\(cause)\\\")\"\n        }\n        let serializableError = ContainerXPCError(code: error.code.description, message: message)\n        let data = try? JSONEncoder().encode(serializableError)\n        precondition(data != nil)\n\n        set(key: Self.errorKey, value: data!)\n    }\n}\n\nstruct ContainerXPCError: Codable {\n    let code: String\n    let message: String\n}\n\nextension XPCMessage {\n    public func data(key: String) -> Data? {\n        var length: Int = 0\n        let bytes = lock.withLock {\n            xpc_dictionary_get_data(self.object, key, &length)\n        }\n\n        guard let bytes else {\n            return nil\n        }\n\n        return Data(bytes: bytes, count: length)\n    }\n\n    /// dataNoCopy is similar to data, except the data is not copied\n    /// to a new buffer. What this means in practice is the second the\n    /// underlying xpc_object_t gets released by ARC the data will be\n    /// released as well. This variant should be used when you know the\n    /// data will be used before the object has no more references.\n    public func dataNoCopy(key: String) -> Data? {\n        var length: Int = 0\n        let bytes = lock.withLock {\n            xpc_dictionary_get_data(self.object, key, &length)\n        }\n\n        guard let bytes else {\n            return nil\n        }\n\n        return Data(\n            bytesNoCopy: UnsafeMutableRawPointer(mutating: bytes),\n            count: length,\n            deallocator: .none\n        )\n    }\n\n    public func set(key: String, value: Data) {\n        value.withUnsafeBytes { ptr in\n            if let addr = ptr.baseAddress {\n                lock.withLock {\n                    xpc_dictionary_set_data(self.object, key, addr, value.count)\n                }\n            }\n        }\n    }\n\n    public func string(key: String) -> String? {\n        let _id = lock.withLock {\n            xpc_dictionary_get_string(self.object, key)\n        }\n        if let _id {\n            return String(cString: _id)\n        }\n        return nil\n    }\n\n    public func set(key: String, value: String) {\n        lock.withLock {\n            xpc_dictionary_set_string(self.object, key, value)\n        }\n    }\n\n    public func bool(key: String) -> Bool {\n        lock.withLock {\n            xpc_dictionary_get_bool(self.object, key)\n        }\n    }\n\n    public func set(key: String, value: Bool) {\n        lock.withLock {\n            xpc_dictionary_set_bool(self.object, key, value)\n        }\n    }\n\n    public func uint64(key: String) -> UInt64 {\n        lock.withLock {\n            xpc_dictionary_get_uint64(self.object, key)\n        }\n    }\n\n    public func set(key: String, value: UInt64) {\n        lock.withLock {\n            xpc_dictionary_set_uint64(self.object, key, value)\n        }\n    }\n\n    public func int64(key: String) -> Int64 {\n        lock.withLock {\n            xpc_dictionary_get_int64(self.object, key)\n        }\n    }\n\n    public func set(key: String, value: Int64) {\n        lock.withLock {\n            xpc_dictionary_set_int64(self.object, key, value)\n        }\n    }\n\n    public func date(key: String) -> Date {\n        lock.withLock {\n            let nsSinceEpoch = xpc_dictionary_get_date(self.object, key)\n            return Date(timeIntervalSince1970: TimeInterval(nsSinceEpoch) / 1_000_000_000)\n        }\n    }\n\n    public func set(key: String, value: Date) {\n        lock.withLock {\n            let nsSinceEpoch = Int64(value.timeIntervalSince1970 * 1_000_000_000)\n            xpc_dictionary_set_date(self.object, key, nsSinceEpoch)\n        }\n    }\n\n    public func fileHandle(key: String) -> FileHandle? {\n        let fd = lock.withLock {\n            xpc_dictionary_get_value(self.object, key)\n        }\n        if let fd {\n            let fd2 = xpc_fd_dup(fd)\n            return FileHandle(fileDescriptor: fd2, closeOnDealloc: false)\n        }\n        return nil\n    }\n\n    public func set(key: String, value: FileHandle) {\n        let fd = xpc_fd_create(value.fileDescriptor)\n        close(value.fileDescriptor)\n        lock.withLock {\n            xpc_dictionary_set_value(self.object, key, fd)\n        }\n    }\n\n    public func fileHandles(key: String) -> [FileHandle]? {\n        let fds = lock.withLock {\n            xpc_dictionary_get_value(self.object, key)\n        }\n        if let fds {\n            let fd1 = xpc_array_dup_fd(fds, 0)\n            let fd2 = xpc_array_dup_fd(fds, 1)\n            if fd1 == -1 || fd2 == -1 {\n                return nil\n            }\n            return [\n                FileHandle(fileDescriptor: fd1, closeOnDealloc: false),\n                FileHandle(fileDescriptor: fd2, closeOnDealloc: false),\n            ]\n        }\n        return nil\n    }\n\n    public func set(key: String, value: [FileHandle]) throws {\n        let fdArray = xpc_array_create(nil, 0)\n        for fh in value {\n            guard let xpcFd = xpc_fd_create(fh.fileDescriptor) else {\n                throw ContainerizationError(\n                    .internalError,\n                    message: \"failed to create xpc fd for \\(fh.fileDescriptor)\"\n                )\n            }\n            xpc_array_append_value(fdArray, xpcFd)\n            close(fh.fileDescriptor)\n        }\n        lock.withLock {\n            xpc_dictionary_set_value(self.object, key, fdArray)\n        }\n    }\n\n    public func set(key: String, xpcDictionary: xpc_object_t) {\n        lock.withLock {\n            xpc_dictionary_set_value(self.object, key, xpcDictionary)\n        }\n    }\n\n    public func endpoint(key: String) -> xpc_endpoint_t? {\n        lock.withLock {\n            xpc_dictionary_get_value(self.object, key)\n        }\n    }\n\n    public func set(key: String, value: xpc_endpoint_t) {\n        lock.withLock {\n            xpc_dictionary_set_value(self.object, key, value)\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/ContainerXPC/XPCServer.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport CAuditToken\nimport ContainerizationError\nimport Foundation\nimport Logging\nimport os\nimport Synchronization\n\npublic struct XPCServer: Sendable {\n    public typealias RouteHandler = @Sendable (XPCMessage) async throws -> XPCMessage\n\n    private let routes: [String: RouteHandler]\n    // Access to `connection` is protected by a lock.\n    private nonisolated(unsafe) let connection: xpc_connection_t\n    private let lock = NSLock()\n\n    let log: Logging.Logger\n\n    public init(identifier: String, routes: [String: RouteHandler], log: Logging.Logger) {\n        let connection = xpc_connection_create_mach_service(\n            identifier,\n            nil,\n            UInt64(XPC_CONNECTION_MACH_SERVICE_LISTENER)\n        )\n\n        self.routes = routes\n        self.connection = connection\n        self.log = log\n    }\n\n    public init(connection: xpc_connection_t, routes: [String: RouteHandler], log: Logging.Logger) {\n        self.routes = routes\n        self.connection = connection\n        self.log = log\n    }\n\n    public func listen() async throws {\n        let connections = AsyncStream<xpc_connection_t> { cont in\n            lock.withLock {\n                xpc_connection_set_event_handler(self.connection) { object in\n                    switch xpc_get_type(object) {\n                    case XPC_TYPE_CONNECTION:\n                        // `object` isn't used concurrently.\n                        nonisolated(unsafe) let object = object\n                        cont.yield(object)\n                    case XPC_TYPE_ERROR:\n                        if object.connectionError {\n                            cont.finish()\n                        }\n                    default:\n                        fatalError(\"unhandled xpc object type: \\(xpc_get_type(object))\")\n                    }\n                }\n            }\n        }\n\n        defer {\n            lock.withLock {\n                xpc_connection_cancel(self.connection)\n            }\n        }\n\n        lock.withLock {\n            xpc_connection_activate(self.connection)\n        }\n\n        try await withThrowingDiscardingTaskGroup { group in\n            for await conn in connections {\n                // `conn` isn't used concurrently.\n                nonisolated(unsafe) let conn = conn\n                let added = group.addTaskUnlessCancelled { @Sendable in\n                    try await self.handleClientConnection(connection: conn)\n                    xpc_connection_cancel(conn)\n                }\n\n                if !added {\n                    break\n                }\n            }\n\n            group.cancelAll()\n        }\n    }\n\n    func handleClientConnection(connection: xpc_connection_t) async throws {\n        let replySent = Mutex(false)\n\n        let objects = AsyncStream<xpc_object_t> { cont in\n            xpc_connection_set_event_handler(connection) { object in\n                switch xpc_get_type(object) {\n                case XPC_TYPE_DICTIONARY:\n                    // `object` isn't used concurrently.\n                    nonisolated(unsafe) let object = object\n                    cont.yield(object)\n                case XPC_TYPE_ERROR:\n                    if object.connectionError {\n                        cont.finish()\n                    }\n                    if !(replySent.withLock({ $0 }) && object.connectionClosed) {\n                        // When a xpc connection is closed, the framework sends\n                        // a final XPC_ERROR_CONNECTION_INVALID message.\n                        // We can ignore this if we know we have already handled\n                        // the request.\n                        self.log.error(\n                            \"xpc client handler connection error\",\n                            metadata: [\n                                \"error\": \"\\(object.errorDescription ?? \"no description\")\"\n                            ])\n                    }\n                default:\n                    fatalError(\"unhandled xpc object type: \\(xpc_get_type(object))\")\n                }\n            }\n        }\n        defer {\n            xpc_connection_cancel(connection)\n        }\n\n        xpc_connection_activate(connection)\n        try await withThrowingDiscardingTaskGroup { group in\n            // `connection` isn't used concurrently.\n            nonisolated(unsafe) let connection = connection\n            for await object in objects {\n                // `object` isn't used concurrently.\n                nonisolated(unsafe) let object = object\n                let added = group.addTaskUnlessCancelled { @Sendable in\n                    try await self.handleMessage(connection: connection, object: object)\n                    replySent.withLock { $0 = true }\n                }\n                if !added {\n                    break\n                }\n            }\n            group.cancelAll()\n        }\n    }\n\n    func handleMessage(connection: xpc_connection_t, object: xpc_object_t) async throws {\n        // All requests are dictionary-valued.\n        guard xpc_get_type(object) == XPC_TYPE_DICTIONARY else {\n            log.error(\"invalid request - not a dictionary\")\n            Self.replyWithError(\n                connection: connection,\n                object: object,\n                err: ContainerizationError(.invalidArgument, message: \"invalid request\")\n            )\n            return\n        }\n\n        // Ensure that the client has our EUID\n        var token = audit_token_t()\n        xpc_dictionary_get_audit_token(object, &token)\n        let serverEuid = geteuid()\n        let clientEuid = audit_token_to_euid(token)\n        guard clientEuid == serverEuid else {\n            log.error(\n                \"unauthorized request - uid mismatch\",\n                metadata: [\n                    \"server_euid\": \"\\(serverEuid)\",\n                    \"client_euid\": \"\\(clientEuid)\",\n                ])\n            Self.replyWithError(\n                connection: connection,\n                object: object,\n                err: ContainerizationError(.invalidState, message: \"unauthorized request\")\n            )\n            return\n        }\n\n        guard let route = object.route else {\n            log.error(\"invalid request - empty route\")\n            Self.replyWithError(\n                connection: connection,\n                object: object,\n                err: ContainerizationError(.invalidArgument, message: \"invalid request\")\n            )\n            return\n        }\n\n        if let handler = routes[route] {\n            do {\n                let message = XPCMessage(object: object)\n                let response = try await handler(message)\n                xpc_connection_send_message(connection, response.underlying)\n            } catch let error as ContainerizationError {\n                log.error(\n                    \"route handler threw an error\",\n                    metadata: [\n                        \"route\": \"\\(route)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                Self.replyWithError(\n                    connection: connection,\n                    object: object,\n                    err: error\n                )\n            } catch {\n                log.error(\n                    \"route handler threw an error\",\n                    metadata: [\n                        \"route\": \"\\(route)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                let message = XPCMessage(object: object)\n                let reply = message.reply()\n\n                // Check if this is a VolumeError by looking at the error description\n                let errorMessage = error.localizedDescription\n                let errorTypeString = String(describing: type(of: error))\n                if errorTypeString.contains(\"VolumeError\") || errorMessage.contains(\"Volume\") {\n                    let err = ContainerizationError(.invalidArgument, message: errorMessage)\n                    reply.set(error: err)\n                } else {\n                    let err = ContainerizationError(.unknown, message: String(describing: error))\n                    reply.set(error: err)\n                }\n                xpc_connection_send_message(connection, reply.underlying)\n            }\n        }\n    }\n\n    private static func replyWithError(connection: xpc_connection_t, object: xpc_object_t, err: ContainerizationError) {\n        let message = XPCMessage(object: object)\n        let reply = message.reply()\n        reply.set(error: err)\n        xpc_connection_send_message(connection, reply.underlying)\n    }\n}\n\nextension xpc_object_t {\n    var route: String? {\n        let croute = xpc_dictionary_get_string(self, XPCMessage.routeKey)\n        guard let croute else {\n            return nil\n        }\n        return String(cString: croute)\n    }\n\n    var connectionError: Bool {\n        precondition(isError, \"not an error\")\n        return xpc_equal(self, XPC_ERROR_CONNECTION_INVALID) || xpc_equal(self, XPC_ERROR_CONNECTION_INTERRUPTED)\n    }\n\n    var connectionClosed: Bool {\n        precondition(isError, \"not an error\")\n        return xpc_equal(self, XPC_ERROR_CONNECTION_INVALID)\n    }\n\n    var isError: Bool {\n        xpc_get_type(self) == XPC_TYPE_ERROR\n    }\n\n    var errorDescription: String? {\n        precondition(isError, \"not an error\")\n        let cstring = xpc_dictionary_get_string(self, XPC_ERROR_KEY_DESCRIPTION)\n        guard let cstring else {\n            return nil\n        }\n        return String(cString: cstring)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/DNSServer/DNSHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Protocol for implementing custom DNS handlers.\npublic protocol DNSHandler {\n    /// Attempt to answer a DNS query\n    /// - Parameter query: the query message\n    /// - Throws: a server failure occurred during the query\n    /// - Returns: The response message for the query, or nil if the request\n    ///   is not within the scope of the handler.\n    func answer(query: Message) async throws -> Message?\n}\n"
  },
  {
    "path": "Sources/DNSServer/DNSServer+Handle.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport NIOCore\nimport NIOPosix\n\nextension DNSServer {\n    /// Handles the DNS request.\n    /// - Parameters:\n    ///   - outbound: The NIOAsyncChannelOutboundWriter for which to respond.\n    ///   - packet: The request packet.\n    func handle(\n        outbound: NIOAsyncChannelOutboundWriter<AddressedEnvelope<ByteBuffer>>,\n        packet: inout AddressedEnvelope<ByteBuffer>\n    ) async throws {\n        // RFC 1035 §2.3.4 limits UDP DNS messages to 512 bytes. We don't implement\n        // EDNS0 (RFC 6891), and this server only resolves host A/AAAA queries, so a\n        // legitimate query will never approach this limit. Reject oversized packets\n        // before reading to avoid allocating memory for malformed or malicious datagrams.\n        let maxPacketSize = 512\n        guard packet.data.readableBytes <= maxPacketSize else {\n            self.log?.error(\"dropping oversized DNS packet: \\(packet.data.readableBytes) bytes\")\n            return\n        }\n\n        var data = Data()\n        self.log?.debug(\"reading data\")\n        while packet.data.readableBytes > 0 {\n            if let chunk = packet.data.readBytes(length: packet.data.readableBytes) {\n                data.append(contentsOf: chunk)\n            }\n        }\n\n        self.log?.debug(\"deserializing message\")\n\n        // always send response\n        let responseData: Data\n        do {\n            let query = try Message(deserialize: data)\n            self.log?.debug(\"processing query: \\(query.questions)\")\n\n            self.log?.debug(\"awaiting processing\")\n            var response =\n                try await handler.answer(query: query)\n                ?? Message(\n                    id: query.id,\n                    type: .response,\n                    returnCode: .notImplemented,\n                    questions: query.questions,\n                    answers: []\n                )\n\n            // Only set NXDOMAIN if handler didn't explicitly set noError (NODATA response).\n            // This preserves NODATA responses for AAAA queries when A record exists,\n            // which prevents musl libc from treating empty AAAA as \"domain doesn't exist\".\n            if response.answers.isEmpty && response.returnCode != .noError {\n                response.returnCode = .nonExistentDomain\n            }\n\n            self.log?.debug(\"serializing response\")\n            responseData = try response.serialize()\n        } catch let error as DNSBindError {\n            // Best-effort: echo the transaction ID from the first two bytes of the raw packet.\n            let rawId = data.count >= 2 ? data[0..<2].withUnsafeBytes { $0.load(as: UInt16.self) } : 0\n            let id = UInt16(bigEndian: rawId)\n            let returnCode: ReturnCode\n            switch error {\n            case .unsupportedValue:\n                self.log?.error(\"not implemented processing DNS message: \\(error)\")\n                returnCode = .notImplemented\n            default:\n                self.log?.error(\"format error processing DNS message: \\(error)\")\n                returnCode = .formatError\n            }\n            let response = Message(\n                id: id,\n                type: .response,\n                returnCode: returnCode,\n                questions: [],\n                answers: []\n            )\n            responseData = try response.serialize()\n        } catch {\n            let rawId = data.count >= 2 ? data[0..<2].withUnsafeBytes { $0.load(as: UInt16.self) } : 0\n            let id = UInt16(bigEndian: rawId)\n            self.log?.error(\"error processing DNS message: \\(error)\")\n            let response = Message(\n                id: id,\n                type: .response,\n                returnCode: .serverFailure,\n                questions: [],\n                answers: []\n            )\n            responseData = try response.serialize()\n        }\n\n        self.log?.debug(\"sending response\")\n        let rData = ByteBuffer(bytes: responseData)\n        do {\n            try await outbound.write(AddressedEnvelope(remoteAddress: packet.remoteAddress, data: rData))\n        } catch {\n            self.log?.error(\"failed to send DNS response: \\(error)\")\n        }\n\n        self.log?.debug(\"processing done\")\n\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/DNSServer.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\nimport NIOCore\nimport NIOPosix\n\n/// Provides a DNS server.\n/// - Parameters:\n///   - host: The host address on which to listen.\n///   - port: The port for the server to listen.\npublic struct DNSServer {\n    public var handler: DNSHandler\n    let log: Logger?\n\n    public init(\n        handler: DNSHandler,\n        log: Logger? = nil\n    ) {\n        self.handler = handler\n        self.log = log\n    }\n\n    public func run(host: String, port: Int) async throws {\n        // TODO: TCP server\n        let srv = try await DatagramBootstrap(group: NIOSingletons.posixEventLoopGroup)\n            .channelOption(.socketOption(.so_reuseaddr), value: 1)\n            .bind(host: host, port: port)\n            .flatMapThrowing { channel in\n                try NIOAsyncChannel(\n                    wrappingChannelSynchronously: channel,\n                    configuration: NIOAsyncChannel.Configuration(\n                        inboundType: AddressedEnvelope<ByteBuffer>.self,\n                        outboundType: AddressedEnvelope<ByteBuffer>.self\n                    )\n                )\n            }\n            .get()\n\n        try await srv.executeThenClose { inbound, outbound in\n            for try await var packet in inbound {\n                try await self.handle(outbound: outbound, packet: &packet)\n            }\n        }\n    }\n\n    public func run(socketPath: String) async throws {\n        // TODO: TCP server\n        let srv = try await DatagramBootstrap(group: NIOSingletons.posixEventLoopGroup)\n            .bind(unixDomainSocketPath: socketPath, cleanupExistingSocketFile: true)\n            .flatMapThrowing { channel in\n                try NIOAsyncChannel(\n                    wrappingChannelSynchronously: channel,\n                    configuration: NIOAsyncChannel.Configuration(\n                        inboundType: AddressedEnvelope<ByteBuffer>.self,\n                        outboundType: AddressedEnvelope<ByteBuffer>.self\n                    )\n                )\n            }\n            .get()\n\n        try await srv.executeThenClose { inbound, outbound in\n            for try await var packet in inbound {\n                log?.debug(\"received packet from \\(packet.remoteAddress)\")\n                try await self.handle(outbound: outbound, packet: &packet)\n                log?.debug(\"sent packet\")\n            }\n        }\n    }\n\n    public func stop() async throws {}\n}\n"
  },
  {
    "path": "Sources/DNSServer/Handlers/CompositeResolver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Delegates a query sequentially to handlers until one provides a response.\npublic struct CompositeResolver: DNSHandler {\n    private let handlers: [DNSHandler]\n\n    public init(handlers: [DNSHandler]) {\n        self.handlers = handlers\n    }\n\n    public func answer(query: Message) async throws -> Message? {\n        for handler in self.handlers {\n            if let response = try await handler.answer(query: query) {\n                return response\n            }\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Handlers/HostTableResolver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\n\n/// Handler that uses table lookup to resolve hostnames.\n///\n/// Keys in `hosts4` are normalized to `DNSName` on construction, so lookups\n/// are case-insensitive and trailing dots are optional.\npublic struct HostTableResolver: DNSHandler {\n    public let hosts4: [DNSName: IPv4Address]\n    private let ttl: UInt32\n\n    /// Creates a resolver backed by a static IPv4 host table.\n    ///\n    /// - Parameter hosts4: A dictionary mapping domain names to IPv4 addresses.\n    ///   Keys are normalized to `DNSName` (lowercased, trailing dot stripped), so\n    ///   `\"FOO.\"`, `\"foo.\"`, and `\"foo\"` all refer to the same entry.\n    /// - Parameter ttl: The TTL in seconds to set on answer records (default is 300).\n    /// - Throws: `DNSBindError.invalidName` if any key is not a valid DNS name.\n    public init(hosts4: [String: IPv4Address], ttl: UInt32 = 300) throws {\n        self.hosts4 = try Dictionary(uniqueKeysWithValues: hosts4.map { (try DNSName($0.key), $0.value) })\n        self.ttl = ttl\n    }\n\n    public func answer(query: Message) async throws -> Message? {\n        guard let question = query.questions.first else {\n            return nil\n        }\n        let n = question.name.hasSuffix(\".\") ? String(question.name.dropLast()) : question.name\n        let key = try DNSName(labels: n.isEmpty ? [] : n.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))\n        let record: ResourceRecord?\n        switch question.type {\n        case ResourceRecordType.host:\n            record = answerHost(question: question, key: key)\n        case ResourceRecordType.host6:\n            // Return NODATA (noError with empty answers) for AAAA queries ONLY if A record exists.\n            // This is required because musl libc has issues when A record exists but AAAA returns NXDOMAIN.\n            // musl treats NXDOMAIN on AAAA as \"domain doesn't exist\" and fails DNS resolution entirely.\n            // NODATA correctly indicates \"no IPv6 address available, but domain exists\".\n            if hosts4[key] != nil {\n                return Message(\n                    id: query.id,\n                    type: .response,\n                    returnCode: .noError,\n                    questions: query.questions,\n                    answers: []\n                )\n            }\n            // If hostname doesn't exist, return nil which will become NXDOMAIN\n            return nil\n        default:\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .notImplemented,\n                questions: query.questions,\n                answers: []\n            )\n        }\n\n        guard let record else {\n            return nil\n        }\n\n        return Message(\n            id: query.id,\n            type: .response,\n            returnCode: .noError,\n            questions: query.questions,\n            answers: [record]\n        )\n    }\n\n    private func answerHost(question: Question, key: DNSName) -> ResourceRecord? {\n        guard let ip = hosts4[key] else {\n            return nil\n        }\n\n        return HostRecord<IPv4Address>(name: question.name, ttl: ttl, ip: ip)\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Handlers/NxDomainResolver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Handler that returns NXDOMAIN for all hostnames.\npublic struct NxDomainResolver: DNSHandler {\n    private let ttl: UInt32\n\n    public init(ttl: UInt32 = 300) {\n        self.ttl = ttl\n    }\n\n    public func answer(query: Message) async throws -> Message? {\n        let question = query.questions[0]\n        switch question.type {\n        case ResourceRecordType.host:\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .nonExistentDomain,\n                questions: query.questions,\n                answers: []\n            )\n        default:\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .notImplemented,\n                questions: query.questions,\n                answers: []\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Handlers/StandardQueryValidator.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Pass standard queries to a delegate handler.\npublic struct StandardQueryValidator: DNSHandler {\n    private let handler: DNSHandler\n\n    /// Create the handler.\n    /// - Parameter delegate: the handler that receives valid queries\n    public init(handler: DNSHandler) {\n        self.handler = handler\n    }\n\n    /// Ensures the query is valid before forwarding it to the delegate.\n    /// - Parameter msg: the query message\n    /// - Returns: the delegate response if the query is valid, and an\n    ///   error response otherwise\n    public func answer(query: Message) async throws -> Message? {\n        // Reject response messages.\n        guard query.type == .query else {\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .formatError,\n                questions: query.questions\n            )\n        }\n\n        // Standard DNS servers handle only query operations.\n        guard query.operationCode == .query else {\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .notImplemented,\n                questions: query.questions\n            )\n        }\n\n        // Standard DNS servers only handle messages with exactly one question.\n        guard query.questions.count == 1 else {\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .formatError,\n                questions: query.questions\n            )\n        }\n\n        return try await handler.answer(query: query)\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/DNSBindError.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Errors that can occur during DNS message serialization/deserialization.\npublic enum DNSBindError: Error, CustomStringConvertible {\n    case marshalFailure(type: String, field: String)\n    case unmarshalFailure(type: String, field: String)\n    case unsupportedValue(type: String, field: String)\n    case invalidName(String)\n    case unexpectedOffset(type: String, expected: Int, actual: Int)\n\n    public var description: String {\n        switch self {\n        case .marshalFailure(let type, let field):\n            return \"failed to marshal \\(type).\\(field)\"\n        case .unmarshalFailure(let type, let field):\n            return \"failed to unmarshal \\(type).\\(field)\"\n        case .unsupportedValue(let type, let field):\n            return \"unsupported value for \\(type).\\(field)\"\n        case .invalidName(let reason):\n            return \"invalid DNS name: \\(reason)\"\n        case .unexpectedOffset(let type, let expected, let actual):\n            return \"unexpected offset serializing \\(type): expected \\(expected), got \\(actual)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/DNSEnums.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// DNS message type (query or response).\npublic enum MessageType: UInt16, Sendable {\n    case query = 0\n    case response = 1\n}\n\n/// DNS operation code (RFC 1035, 1996, 2136).\npublic enum OperationCode: UInt8, Sendable {\n    case query = 0  // Standard query (RFC 1035)\n    case inverseQuery = 1  // Inverse query (obsolete, RFC 3425)\n    case status = 2  // Server status request (RFC 1035)\n    // 3 is reserved\n    case notify = 4  // Zone change notification (RFC 1996)\n    case update = 5  // Dynamic update (RFC 2136)\n    case dso = 6  // DNS Stateful Operations (RFC 8490)\n    // 7-15 reserved\n}\n\n/// DNS response return codes (RFC 1035, 2136, 2845, 6895).\npublic enum ReturnCode: UInt8, Sendable {\n    case noError = 0  // No error\n    case formatError = 1  // Format error - unable to interpret query\n    case serverFailure = 2  // Server failure\n    case nonExistentDomain = 3  // Name error - domain does not exist (NXDOMAIN)\n    case notImplemented = 4  // Not implemented - query type not supported\n    case refused = 5  // Refused - policy restriction\n    case yxDomain = 6  // Name exists when it should not (RFC 2136)\n    case yxRRSet = 7  // RR set exists when it should not (RFC 2136)\n    case nxRRSet = 8  // RR set does not exist when it should (RFC 2136)\n    case notAuthoritative = 9  // Server not authoritative (RFC 2136) / Not authorized (RFC 2845)\n    case notZone = 10  // Name not in zone (RFC 2136)\n    case dsoTypeNotImplemented = 11  // DSO-TYPE not implemented (RFC 8490)\n    // 12-15 reserved\n    case badSignature = 16  // TSIG signature failure (RFC 2845)\n    case badKey = 17  // Key not recognized (RFC 2845)\n    case badTime = 18  // Signature out of time window (RFC 2845)\n    case badMode = 19  // Bad TKEY mode (RFC 2930)\n    case badName = 20  // Duplicate key name (RFC 2930)\n    case badAlgorithm = 21  // Algorithm not supported (RFC 2930)\n    case badTruncation = 22  // Bad truncation (RFC 4635)\n    case badCookie = 23  // Bad/missing server cookie (RFC 7873)\n}\n\n/// DNS resource record types (RFC 1035, 3596, 2782, and others).\npublic enum ResourceRecordType: UInt16, Sendable {\n    case host = 1  // A - IPv4 address (RFC 1035)\n    case nameServer = 2  // NS - Authoritative name server (RFC 1035)\n    case mailDestination = 3  // MD - Mail destination (obsolete, RFC 1035)\n    case mailForwarder = 4  // MF - Mail forwarder (obsolete, RFC 1035)\n    case alias = 5  // CNAME - Canonical name (RFC 1035)\n    case startOfAuthority = 6  // SOA - Start of authority (RFC 1035)\n    case mailbox = 7  // MB - Mailbox domain name (experimental, RFC 1035)\n    case mailGroup = 8  // MG - Mail group member (experimental, RFC 1035)\n    case mailRename = 9  // MR - Mail rename domain name (experimental, RFC 1035)\n    case null = 10  // NULL - Null RR (experimental, RFC 1035)\n    case wellKnownService = 11  // WKS - Well known service (RFC 1035)\n    case pointer = 12  // PTR - Domain name pointer (RFC 1035)\n    case hostInfo = 13  // HINFO - Host information (RFC 1035)\n    case mailInfo = 14  // MINFO - Mailbox information (RFC 1035)\n    case mailExchange = 15  // MX - Mail exchange (RFC 1035)\n    case text = 16  // TXT - Text strings (RFC 1035)\n    case responsiblePerson = 17  // RP - Responsible person (RFC 1183)\n    case afsDatabase = 18  // AFSDB - AFS database location (RFC 1183)\n    case x25 = 19  // X25 - X.25 PSDN address (RFC 1183)\n    case isdn = 20  // ISDN - ISDN address (RFC 1183)\n    case routeThrough = 21  // RT - Route through (RFC 1183)\n    case nsapAddress = 22  // NSAP - NSAP address (RFC 1706)\n    case nsapPointer = 23  // NSAP-PTR - NSAP pointer (RFC 1706)\n    case signature = 24  // SIG - Security signature (RFC 2535)\n    case key = 25  // KEY - Security key (RFC 2535)\n    case pxRecord = 26  // PX - X.400 mail mapping (RFC 2163)\n    case gpos = 27  // GPOS - Geographical position (RFC 1712)\n    case host6 = 28  // AAAA - IPv6 address (RFC 3596)\n    case location = 29  // LOC - Location information (RFC 1876)\n    case nextDomain = 30  // NXT - Next domain (obsolete, RFC 2535)\n    case endpointId = 31  // EID - Endpoint identifier\n    case nimrodLocator = 32  // NIMLOC - Nimrod locator\n    case service = 33  // SRV - Service locator (RFC 2782)\n    case atma = 34  // ATMA - ATM address\n    case namingPointer = 35  // NAPTR - Naming authority pointer (RFC 3403)\n    case keyExchange = 36  // KX - Key exchange (RFC 2230)\n    case cert = 37  // CERT - Certificate (RFC 4398)\n    case a6Record = 38  // A6 - IPv6 address (obsolete, RFC 2874)\n    case dname = 39  // DNAME - Delegation name (RFC 6672)\n    case sink = 40  // SINK - Kitchen sink\n    case opt = 41  // OPT - EDNS option (RFC 6891)\n    case apl = 42  // APL - Address prefix list (RFC 3123)\n    case delegationSigner = 43  // DS - Delegation signer (RFC 4034)\n    case sshFingerprint = 44  // SSHFP - SSH key fingerprint (RFC 4255)\n    case ipsecKey = 45  // IPSECKEY - IPsec key (RFC 4025)\n    case resourceSignature = 46  // RRSIG - Resource record signature (RFC 4034)\n    case nsec = 47  // NSEC - Next secure record (RFC 4034)\n    case dnsKey = 48  // DNSKEY - DNS key (RFC 4034)\n    case dhcid = 49  // DHCID - DHCP identifier (RFC 4701)\n    case nsec3 = 50  // NSEC3 - NSEC3 (RFC 5155)\n    case nsec3Param = 51  // NSEC3PARAM - NSEC3 parameters (RFC 5155)\n    case tlsa = 52  // TLSA - TLSA certificate (RFC 6698)\n    case smimea = 53  // SMIMEA - S/MIME cert association (RFC 8162)\n    // 54 unassigned\n    case hip = 55  // HIP - Host identity protocol (RFC 8005)\n    case ninfo = 56  // NINFO\n    case rkey = 57  // RKEY\n    case taLink = 58  // TALINK - Trust anchor link\n    case cds = 59  // CDS - Child DS (RFC 7344)\n    case cdnsKey = 60  // CDNSKEY - Child DNSKEY (RFC 7344)\n    case openPGPKey = 61  // OPENPGPKEY - OpenPGP key (RFC 7929)\n    case csync = 62  // CSYNC - Child-to-parent sync (RFC 7477)\n    case zoneDigest = 63  // ZONEMD - Zone message digest (RFC 8976)\n    case svcBinding = 64  // SVCB - Service binding (RFC 9460)\n    case httpsBinding = 65  // HTTPS - HTTPS binding (RFC 9460)\n    // 66-98 unassigned\n    case spf = 99  // SPF - Sender policy framework (RFC 7208)\n    case uinfo = 100  // UINFO\n    case uid = 101  // UID\n    case gid = 102  // GID\n    case unspec = 103  // UNSPEC\n    case nid = 104  // NID - Node identifier (RFC 6742)\n    case l32 = 105  // L32 - Locator32 (RFC 6742)\n    case l64 = 106  // L64 - Locator64 (RFC 6742)\n    case lp = 107  // LP - Locator FQDN (RFC 6742)\n    case eui48 = 108  // EUI48 - 48-bit MAC (RFC 7043)\n    case eui64 = 109  // EUI64 - 64-bit MAC (RFC 7043)\n    // 110-248 unassigned\n    case tkey = 249  // TKEY - Transaction key (RFC 2930)\n    case tsig = 250  // TSIG - Transaction signature (RFC 2845)\n    case incrementalZoneTransfer = 251  // IXFR - Incremental zone transfer (RFC 1995)\n    case standardZoneTransfer = 252  // AXFR - Full zone transfer (RFC 1035)\n    case mailboxRecords = 253  // MAILB - Mailbox-related records (RFC 1035)\n    case mailAgentRecords = 254  // MAILA - Mail agent RRs (obsolete, RFC 1035)\n    case all = 255  // * - All records (RFC 1035)\n    case uri = 256  // URI - Uniform resource identifier (RFC 7553)\n    case caa = 257  // CAA - Certification authority authorization (RFC 8659)\n    case avc = 258  // AVC - Application visibility and control\n    case doa = 259  // DOA - Digital object architecture\n    case amtRelay = 260  // AMTRELAY - Automatic multicast tunneling relay (RFC 8777)\n    case resInfo = 261  // RESINFO - Resolver information\n    // ...\n    case ta = 32768  // TA - DNSSEC trust authorities\n    case dlv = 32769  // DLV - DNSSEC lookaside validation (RFC 4431)\n}\n\n/// DNS resource record class (RFC 1035).\npublic enum ResourceRecordClass: UInt16, Sendable {\n    case internet = 1  // IN - Internet (RFC 1035)\n    // 2 unassigned\n    case chaos = 3  // CH - Chaos (RFC 1035)\n    case hesiod = 4  // HS - Hesiod (RFC 1035)\n    // 5-253 unassigned\n    case none = 254  // NONE - None (RFC 2136)\n    case any = 255  // * - Any class (RFC 1035)\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/DNSName.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A DNS name encoded as a sequence of labels.\n///\n/// DNS names are encoded as: `[length][label][length][label]...[0]`\n/// For example, \"example.com\" becomes: `[7]example[3]com[0]`\npublic struct DNSName: Sendable, Hashable, CustomStringConvertible {\n    /// The labels that make up this name (e.g., [\"example\", \"com\"]).\n    public private(set) var labels: [String]\n\n    /// Creates a DNS name representing the root (empty label list).\n    public init() {\n        self.labels = []\n    }\n\n    /// Creates a validated DNS name from an array of labels.\n    ///\n    /// Validates structural RFC 1035 constraints only: no empty labels, each label ≤ 63\n    /// bytes, total wire length ≤ 255 bytes. Does not enforce hostname character rules.\n    /// Labels are lowercased to normalize for case-insensitive DNS comparison.\n    ///\n    /// - Throws: `DNSBindError.invalidName` if any label is empty, exceeds 63 bytes,\n    ///   or if the total wire representation exceeds 255 bytes.\n    public init(labels: [String]) throws {\n        for label in labels {\n            guard !label.isEmpty else {\n                throw DNSBindError.invalidName(\"empty label\")\n            }\n            guard label.utf8.count <= 63 else {\n                throw DNSBindError.invalidName(\"label too long: \\\"\\(label)\\\"\")\n            }\n        }\n        let wireLength = labels.reduce(1) { $0 + 1 + $1.utf8.count }\n        guard wireLength <= 255 else {\n            throw DNSBindError.invalidName(\"name too long\")\n        }\n        self.labels = labels.map { $0.lowercased() }\n    }\n\n    /// Creates a validated DNS name from a dot-separated hostname string\n    /// (e.g., `\"example.com.\"` or `\"example.com\"`).\n    ///\n    /// A trailing dot is accepted but not required.\n    /// An empty string produces the root name without error.\n    ///\n    /// Labels must start and end with a letter or digit (LDH hostname rule).\n    /// Use `init(labels:)` directly when working with wire-decoded names that\n    /// may contain non-hostname labels (e.g. service-discovery labels like `\"_dns\"`).\n    ///\n    /// - Throws: `DNSBindError.invalidName` if any label violates the character rules,\n    ///   or if structural limits are exceeded (see `init(labels:)`).\n    public init(_ hostname: String) throws {\n        let normalized = hostname.hasSuffix(\".\") ? String(hostname.dropLast()) : hostname\n        guard !normalized.isEmpty else {\n            self.init()\n            return\n        }\n        let parts = normalized.split(separator: \".\", omittingEmptySubsequences: false).map { String($0) }\n        let hostnameRegex = /[a-zA-Z0-9](?:[a-zA-Z0-9\\-_]*[a-zA-Z0-9])?/\n        for part in parts {\n            guard part.wholeMatch(of: hostnameRegex) != nil else {\n                throw DNSBindError.invalidName(\n                    \"label must start and end with a letter or digit: \\\"\\(part)\\\"\"\n                )\n            }\n        }\n        try self.init(labels: parts)\n    }\n\n    /// The wire format size of this name in bytes.\n    public var size: Int {\n        // Each label: 1 byte length + label bytes, plus 1 byte for null terminator\n        labels.reduce(1) { $0 + 1 + $1.utf8.count }\n    }\n\n    /// The fully-qualified domain name with trailing dot.\n    public var description: String {\n        labels.isEmpty ? \".\" : labels.joined(separator: \".\") + \".\"\n    }\n\n    /// Serialize this name into the buffer at the given offset.\n    public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {\n        let startOffset = offset\n        var offset = offset\n\n        for label in labels {\n            let bytes = Array(label.utf8)\n            guard bytes.count <= 63 else {\n                throw DNSBindError.marshalFailure(type: \"DNSName\", field: \"label\")\n            }\n\n            guard let newOffset = buffer.copyIn(as: UInt8.self, value: UInt8(bytes.count), offset: offset) else {\n                throw DNSBindError.marshalFailure(type: \"DNSName\", field: \"label\")\n            }\n            offset = newOffset\n\n            guard let newOffset = buffer.copyIn(buffer: bytes, offset: offset) else {\n                throw DNSBindError.marshalFailure(type: \"DNSName\", field: \"label\")\n            }\n            offset = newOffset\n        }\n\n        // Null terminator\n        guard let newOffset = buffer.copyIn(as: UInt8.self, value: 0, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"DNSName\", field: \"terminator\")\n        }\n\n        guard newOffset == startOffset + size else {\n            throw DNSBindError.unexpectedOffset(type: \"DNSName\", expected: startOffset + size, actual: newOffset)\n        }\n        return newOffset\n    }\n\n    /// Deserialize a name from the buffer at the given offset.\n    ///\n    /// - Parameters:\n    ///   - buffer: The buffer to read from.\n    ///   - offset: The offset to start reading.\n    ///   - messageStart: The start of the DNS message (for compression pointer resolution).\n    /// - Returns: The new offset after reading.\n    public mutating func bindBuffer(\n        _ buffer: inout [UInt8],\n        offset: Int,\n        messageStart: Int = 0\n    ) throws -> Int {\n        var offset = offset\n        var collectedLabels: [String] = []\n        var jumped = false\n        var returnOffset = offset\n        var pointerHops = 0\n\n        while true {\n            guard offset < buffer.count else {\n                throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"name\")\n            }\n\n            let length = buffer[offset]\n\n            // Check for compression pointer (top 2 bits set)\n            if (length & 0xC0) == 0xC0 {\n                guard offset + 1 < buffer.count else {\n                    throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"pointer\")\n                }\n\n                pointerHops += 1\n                guard pointerHops <= 10 else {\n                    throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"pointer\")\n                }\n\n                if !jumped {\n                    returnOffset = offset + 2\n                }\n\n                // Calculate pointer offset from message start\n                let pointer = Int(length & 0x3F) << 8 | Int(buffer[offset + 1])\n                let pointerTarget = messageStart + pointer\n                guard pointerTarget >= 0 && pointerTarget < offset && pointerTarget < buffer.count else {\n                    throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"pointer\")\n                }\n                offset = pointerTarget\n                jumped = true\n                continue\n            }\n\n            offset += 1\n\n            // Null terminator - end of name\n            if length == 0 {\n                break\n            }\n\n            guard offset + Int(length) <= buffer.count else {\n                throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"label\")\n            }\n\n            let labelBytes = Array(buffer[offset..<offset + Int(length)])\n            guard let label = String(bytes: labelBytes, encoding: .utf8) else {\n                throw DNSBindError.unmarshalFailure(type: \"DNSName\", field: \"label\")\n            }\n\n            collectedLabels.append(label)\n            offset += Int(length)\n        }\n\n        self = try DNSName(labels: collectedLabels)\n        return jumped ? returnOffset : offset\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/IPAddressProtocol.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\n\n/// Protocol for IP address types that can be used in DNS records.\npublic protocol IPAddressProtocol: Sendable, Hashable {\n    static var size: Int { get }\n    static var recordType: ResourceRecordType { get }\n    var bytes: [UInt8] { get }\n}\n\nextension IPv4Address: IPAddressProtocol {\n    public static let size = 4\n    public static let recordType: ResourceRecordType = .host\n}\n\nextension IPv6Address: IPAddressProtocol {\n    public static let size = 16\n    public static let recordType: ResourceRecordType = .host6\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/Message.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A DNS message (query or response).\n///\n/// Wire format (RFC 1035):\n/// ```\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |                      ID                       |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |                    QDCOUNT                    |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |                    ANCOUNT                    |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |                    NSCOUNT                    |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// |                    ARCOUNT                    |\n/// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+\n/// ```\npublic struct Message: Sendable {\n    /// Header size in bytes.\n    public static let headerSize = 12\n\n    /// Transaction ID.\n    public var id: UInt16\n\n    /// Message type (query or response).\n    public var type: MessageType\n\n    /// Operation code.\n    public var operationCode: OperationCode\n\n    /// Authoritative answer flag.\n    public var authoritativeAnswer: Bool\n\n    /// Truncation flag.\n    public var truncation: Bool\n\n    /// Recursion desired flag.\n    public var recursionDesired: Bool\n\n    /// Recursion available flag.\n    public var recursionAvailable: Bool\n\n    /// Response code.\n    public var returnCode: ReturnCode\n\n    /// Questions in this message.\n    public var questions: [Question]\n\n    /// Answer resource records.\n    public var answers: [ResourceRecord]\n\n    /// Authority resource records.\n    public var authorities: [ResourceRecord]\n\n    /// Additional resource records.\n    public var additional: [ResourceRecord]\n\n    /// Creates a new DNS message.\n    public init(\n        id: UInt16 = 0,\n        type: MessageType = .query,\n        operationCode: OperationCode = .query,\n        authoritativeAnswer: Bool = false,\n        truncation: Bool = false,\n        recursionDesired: Bool = false,\n        recursionAvailable: Bool = false,\n        returnCode: ReturnCode = .noError,\n        questions: [Question] = [],\n        answers: [ResourceRecord] = [],\n        authorities: [ResourceRecord] = [],\n        additional: [ResourceRecord] = []\n    ) {\n        self.id = id\n        self.type = type\n        self.operationCode = operationCode\n        self.authoritativeAnswer = authoritativeAnswer\n        self.truncation = truncation\n        self.recursionDesired = recursionDesired\n        self.recursionAvailable = recursionAvailable\n        self.returnCode = returnCode\n        self.questions = questions\n        self.answers = answers\n        self.authorities = authorities\n        self.additional = additional\n    }\n\n    /// Deserialize a DNS message from raw data.\n    public init(deserialize data: Data) throws {\n        var buffer = Array(data)\n        var offset = 0\n\n        // Read ID\n        guard let (newOffset, rawId) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"id\")\n        }\n        self.id = UInt16(bigEndian: rawId)\n        offset = newOffset\n\n        // Read flags\n        guard let (newOffset, rawFlags) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"flags\")\n        }\n        let flags = UInt16(bigEndian: rawFlags)\n        offset = newOffset\n\n        // Parse flags\n        self.type = (flags & 0x8000) != 0 ? .response : .query\n        guard let opCode = OperationCode(rawValue: UInt8((flags >> 11) & 0x0F)) else {\n            throw DNSBindError.unsupportedValue(type: \"Message\", field: \"opcode\")\n        }\n        self.operationCode = opCode\n        self.authoritativeAnswer = (flags & 0x0400) != 0\n        self.truncation = (flags & 0x0200) != 0\n        self.recursionDesired = (flags & 0x0100) != 0\n        self.recursionAvailable = (flags & 0x0080) != 0\n        guard let returnCode = ReturnCode(rawValue: UInt8(flags & 0x000F)) else {\n            throw DNSBindError.unsupportedValue(type: \"Message\", field: \"rcode\")\n        }\n        self.returnCode = returnCode\n\n        // Read counts\n        guard let (newOffset, rawQdCount) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"qdcount\")\n        }\n        let qdCount = UInt16(bigEndian: rawQdCount)\n        offset = newOffset\n\n        guard let (newOffset, rawAnCount) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"ancount\")\n        }\n        let anCount = UInt16(bigEndian: rawAnCount)\n        offset = newOffset\n\n        guard let (newOffset, rawNsCount) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"nscount\")\n        }\n        // nsCount not used for now, but we need to read past it\n        _ = UInt16(bigEndian: rawNsCount)\n        offset = newOffset\n\n        guard let (newOffset, rawArCount) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Message\", field: \"arcount\")\n        }\n        // arCount not used for now, but we need to read past it\n        _ = UInt16(bigEndian: rawArCount)\n        offset = newOffset\n\n        // Read questions\n        self.questions = []\n        for _ in 0..<qdCount {\n            var question = Question(name: \"\")\n            offset = try question.bindBuffer(&buffer, offset: offset, messageStart: 0)\n            self.questions.append(question)\n        }\n\n        // Read answers (simplified - skip for now as we only need to parse queries)\n        self.answers = []\n        self.authorities = []\n        self.additional = []\n\n        // Skip answer parsing for now - we primarily receive queries and send responses\n        _ = anCount\n    }\n\n    /// Serialize this message to raw data.\n    public func serialize() throws -> Data {\n        // Calculate exact buffer size.\n        var bufferSize = Self.headerSize\n        for question in questions {\n            // name + type + class\n            let n = question.name.hasSuffix(\".\") ? String(question.name.dropLast()) : question.name\n            bufferSize += (try DNSName(labels: n.isEmpty ? [] : n.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))).size + 4\n        }\n        for answer in answers {\n            // name + type + class + ttl + rdlen + rdata\n            let n = answer.name.hasSuffix(\".\") ? String(answer.name.dropLast()) : answer.name\n            let rdataSize = answer.type == .host ? 4 : 16\n            bufferSize += (try DNSName(labels: n.isEmpty ? [] : n.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))).size + 10 + rdataSize\n        }\n\n        var buffer = [UInt8](repeating: 0, count: bufferSize)\n        var offset = 0\n\n        // Write ID\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: id.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"id\")\n        }\n        offset = newOffset\n\n        // Build and write flags\n        var flags: UInt16 = 0\n        flags |= type == .response ? 0x8000 : 0\n        flags |= UInt16(operationCode.rawValue) << 11\n        flags |= authoritativeAnswer ? 0x0400 : 0\n        flags |= truncation ? 0x0200 : 0\n        flags |= recursionDesired ? 0x0100 : 0\n        flags |= recursionAvailable ? 0x0080 : 0\n        flags |= UInt16(returnCode.rawValue)\n\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: flags.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"flags\")\n        }\n        offset = newOffset\n\n        // Write counts\n        guard questions.count <= UInt16.max else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"qdcount\")\n        }\n        guard answers.count <= UInt16.max else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"ancount\")\n        }\n        guard authorities.count <= UInt16.max else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"nscount\")\n        }\n        guard additional.count <= UInt16.max else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"arcount\")\n        }\n\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: UInt16(questions.count).bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"qdcount\")\n        }\n        offset = newOffset\n\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: UInt16(answers.count).bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"ancount\")\n        }\n        offset = newOffset\n\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: UInt16(authorities.count).bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"nscount\")\n        }\n        offset = newOffset\n\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: UInt16(additional.count).bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Message\", field: \"arcount\")\n        }\n        offset = newOffset\n\n        // Write questions\n        for question in questions {\n            offset = try question.appendBuffer(&buffer, offset: offset)\n        }\n\n        // Write answers\n        for answer in answers {\n            offset = try answer.appendBuffer(&buffer, offset: offset)\n        }\n\n        // Write authorities\n        for authority in authorities {\n            offset = try authority.appendBuffer(&buffer, offset: offset)\n        }\n\n        // Write additional\n        for record in additional {\n            offset = try record.appendBuffer(&buffer, offset: offset)\n        }\n\n        guard offset == bufferSize else {\n            throw DNSBindError.unexpectedOffset(type: \"Message\", expected: bufferSize, actual: offset)\n        }\n        return Data(buffer[0..<offset])\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/Question.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A DNS question (query).\n///\n/// All domain names in a `Question` are canonical DNS names — fully-qualified\n/// with a trailing dot (e.g. `\"example.com.\"`). This invariant is not enforced\n/// automatically; callers are responsible for supplying canonical names.\npublic struct Question: Sendable, CustomStringConvertible {\n    /// The fully-qualified domain name being queried, with a trailing dot (e.g. `\"example.com.\"`).\n    public var name: String\n\n    /// The record type being requested.\n    public var type: ResourceRecordType\n\n    /// The record class (usually .internet).\n    public var recordClass: ResourceRecordClass\n\n    /// Creates a DNS question.\n    ///\n    /// - Parameter name: The fully-qualified domain name to query, with a trailing dot\n    ///   (e.g. `\"example.com.\"`). Supplying a name without a trailing dot will produce\n    ///   lookup mismatches against canonically-stored records.\n    /// - Parameter type: The record type being requested.\n    /// - Parameter recordClass: The record class (usually `.internet`).\n    public init(\n        name: String,\n        type: ResourceRecordType = .host,\n        recordClass: ResourceRecordClass = .internet\n    ) {\n        self.name = name\n        self.type = type\n        self.recordClass = recordClass\n    }\n\n    public var description: String {\n        \"\\(name) \\(type) \\(recordClass)\"\n    }\n\n    /// Serialize this question into the buffer.\n    public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {\n        let startOffset = offset\n        var offset = offset\n\n        // Write name\n        let normalized = name.hasSuffix(\".\") ? String(name.dropLast()) : name\n        let dnsName = try DNSName(labels: normalized.isEmpty ? [] : normalized.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))\n        offset = try dnsName.appendBuffer(&buffer, offset: offset)\n\n        // Write type (big-endian)\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: type.rawValue.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Question\", field: \"type\")\n        }\n        offset = newOffset\n\n        // Write class (big-endian)\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: recordClass.rawValue.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"Question\", field: \"class\")\n        }\n\n        let expectedOffset = startOffset + dnsName.size + 4\n        guard newOffset == expectedOffset else {\n            throw DNSBindError.unexpectedOffset(type: \"Question\", expected: expectedOffset, actual: newOffset)\n        }\n        return newOffset\n    }\n\n    /// Deserialize a question from the buffer.\n    public mutating func bindBuffer(_ buffer: inout [UInt8], offset: Int, messageStart: Int = 0) throws -> Int {\n        var offset = offset\n\n        // Read name\n        var dnsName = DNSName()\n        offset = try dnsName.bindBuffer(&buffer, offset: offset, messageStart: messageStart)\n        self.name = dnsName.description\n\n        // Read type (big-endian)\n        guard let (newOffset, rawType) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Question\", field: \"type\")\n        }\n        guard let qtype = ResourceRecordType(rawValue: UInt16(bigEndian: rawType)) else {\n            throw DNSBindError.unsupportedValue(type: \"Question\", field: \"type\")\n        }\n        self.type = qtype\n        offset = newOffset\n\n        // Read class (big-endian)\n        guard let (newOffset, rawClass) = buffer.copyOut(as: UInt16.self, offset: offset) else {\n            throw DNSBindError.unmarshalFailure(type: \"Question\", field: \"class\")\n        }\n        guard let qclass = ResourceRecordClass(rawValue: UInt16(bigEndian: rawClass)) else {\n            throw DNSBindError.unsupportedValue(type: \"Question\", field: \"class\")\n        }\n        self.recordClass = qclass\n\n        return newOffset\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/ResourceRecord.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Protocol for DNS resource records.\npublic protocol ResourceRecord: Sendable {\n    /// The domain name this record applies to.\n    var name: String { get }\n\n    /// The record type.\n    var type: ResourceRecordType { get }\n\n    /// The record class.\n    var recordClass: ResourceRecordClass { get }\n\n    /// Time to live in seconds.\n    var ttl: UInt32 { get }\n\n    /// Serialize this record into the buffer.\n    func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int\n}\n\n/// A host record (A or AAAA) containing an IP address.\npublic struct HostRecord<T: IPAddressProtocol>: ResourceRecord {\n    public let name: String\n    public let type: ResourceRecordType\n    public let recordClass: ResourceRecordClass\n    public let ttl: UInt32\n    public let ip: T\n\n    public init(\n        name: String,\n        ttl: UInt32 = 300,\n        ip: T,\n        recordClass: ResourceRecordClass = .internet\n    ) {\n        self.name = name\n        self.type = T.recordType\n        self.recordClass = recordClass\n        self.ttl = ttl\n        self.ip = ip\n    }\n\n    public func appendBuffer(_ buffer: inout [UInt8], offset: Int) throws -> Int {\n        let startOffset = offset\n        var offset = offset\n\n        // Write name\n        let normalized = name.hasSuffix(\".\") ? String(name.dropLast()) : name\n        let dnsName = try DNSName(labels: normalized.isEmpty ? [] : normalized.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))\n        offset = try dnsName.appendBuffer(&buffer, offset: offset)\n\n        // Write type (big-endian)\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: type.rawValue.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"HostRecord\", field: \"type\")\n        }\n        offset = newOffset\n\n        // Write class (big-endian)\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: recordClass.rawValue.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"HostRecord\", field: \"class\")\n        }\n        offset = newOffset\n\n        // Write TTL (big-endian)\n        guard let newOffset = buffer.copyIn(as: UInt32.self, value: ttl.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"HostRecord\", field: \"ttl\")\n        }\n        offset = newOffset\n\n        // Write rdlength (big-endian)\n        let rdlength = UInt16(T.size)\n        guard let newOffset = buffer.copyIn(as: UInt16.self, value: rdlength.bigEndian, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"HostRecord\", field: \"rdlength\")\n        }\n        offset = newOffset\n\n        // Write IP address bytes\n        guard let newOffset = buffer.copyIn(buffer: ip.bytes, offset: offset) else {\n            throw DNSBindError.marshalFailure(type: \"HostRecord\", field: \"rdata\")\n        }\n\n        let expectedOffset = startOffset + dnsName.size + 10 + T.size\n        guard newOffset == expectedOffset else {\n            throw DNSBindError.unexpectedOffset(type: \"HostRecord\", expected: expectedOffset, actual: newOffset)\n        }\n        return newOffset\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Records/UInt8+Binding.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n// TODO: This copies some of the Bindable code from Containerization,\n// but we can't use Bindable as it presumes a fixed length record.\n// We can look at refining this later to see if we can use some common\n// bit fiddling code everywhere.\n\nextension [UInt8] {\n    /// Copy a value into the buffer at the given offset.\n    /// - Returns: The new offset after writing, or nil if the buffer is too small.\n    package mutating func copyIn<T>(as type: T.Type, value: T, offset: Int = 0) -> Int? {\n        let size = MemoryLayout<T>.size\n        guard self.count >= size + offset else {\n            return nil\n        }\n        return self.withUnsafeMutableBytes {\n            $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee = value\n            return offset + size\n        }\n    }\n\n    /// Copy a value out of the buffer at the given offset.\n    /// - Returns: A tuple of (new offset, value), or nil if the buffer is too small.\n    package func copyOut<T>(as type: T.Type, offset: Int = 0) -> (Int, T)? {\n        let size = MemoryLayout<T>.size\n        guard self.count >= size + offset else {\n            return nil\n        }\n        return self.withUnsafeBytes {\n            guard let value = $0.baseAddress?.advanced(by: offset).assumingMemoryBound(to: T.self).pointee else {\n                return nil\n            }\n            return (offset + size, value)\n        }\n    }\n\n    /// Copy a byte array into the buffer at the given offset.\n    /// - Returns: The new offset after writing, or nil if the buffer is too small.\n    package mutating func copyIn(buffer: [UInt8], offset: Int = 0) -> Int? {\n        guard offset + buffer.count <= self.count else {\n            return nil\n        }\n        self[offset..<offset + buffer.count] = buffer[0..<buffer.count]\n        return offset + buffer.count\n    }\n\n    /// Copy bytes out of the buffer into another buffer.\n    /// - Returns: The new offset after reading, or nil if the buffer is too small.\n    package func copyOut(buffer: inout [UInt8], offset: Int = 0) -> Int? {\n        guard offset + buffer.count <= self.count else {\n            return nil\n        }\n        buffer[0..<buffer.count] = self[offset..<offset + buffer.count]\n        return offset + buffer.count\n    }\n}\n"
  },
  {
    "path": "Sources/DNSServer/Types.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\npublic enum DNSResolverError: Swift.Error, CustomStringConvertible {\n    case serverError(_ msg: String)\n    case invalidHandlerSpec(_ spec: String)\n    case unsupportedHandlerType(_ t: String)\n    case invalidIP(_ v: String)\n    case invalidHandlerOption(_ v: String)\n    case handlerConfigError(_ msg: String)\n\n    public var description: String {\n        switch self {\n        case .serverError(let msg):\n            return \"server error: \\(msg)\"\n        case .invalidHandlerSpec(let msg):\n            return \"invalid handler spec: \\(msg)\"\n        case .unsupportedHandlerType(let t):\n            return \"unsupported handler type specified: \\(t)\"\n        case .invalidIP(let ip):\n            return \"invalid IP specified: \\(ip)\"\n        case .invalidHandlerOption(let v):\n            return \"invalid handler option specified: \\(v)\"\n        case .handlerConfigError(let msg):\n            return \"error configuring handler: \\(msg)\"\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/APIServer/APIServer+Start.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerAPIClient\nimport ContainerAPIService\nimport ContainerLog\nimport ContainerNetworkService\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerXPC\nimport DNSServer\nimport Foundation\nimport Logging\nimport SystemPackage\n\nextension APIServer {\n    struct Start: AsyncParsableCommand {\n        static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Start helper for the API server\"\n        )\n\n        static let listenAddress = \"127.0.0.1\"\n        static let localhostDNSPort = 1053\n        static let dnsPort = 2053\n\n        @Flag(name: .long, help: \"Enable debug logging\")\n        var debug = false\n\n        var appRoot = ApplicationRoot.url\n\n        var installRoot = InstallRoot.url\n\n        var logRoot = LogRoot.path\n\n        func run() async throws {\n            let commandName = APIServer._commandName\n            let logPath = logRoot.map { $0.appending(\"\\(commandName).log\") }\n            let log = ServiceLogger.bootstrap(category: \"APIServer\", debug: debug, logPath: logPath)\n            log.info(\"starting helper\", metadata: [\"name\": \"\\(commandName)\"])\n            defer {\n                log.info(\"stopping helper\", metadata: [\"name\": \"\\(commandName)\"])\n            }\n\n            do {\n                log.info(\"configuring XPC server\")\n                var routes = [XPCRoute: XPCServer.RouteHandler]()\n                let pluginLoader = try initializePluginLoader(log: log)\n                try await initializePlugins(pluginLoader: pluginLoader, log: log, routes: &routes)\n                let containersService = try initializeContainersService(\n                    pluginLoader: pluginLoader,\n                    log: log,\n                    routes: &routes\n                )\n                let networkService = try await initializeNetworksService(\n                    pluginLoader: pluginLoader,\n                    containersService: containersService,\n                    log: log,\n                    routes: &routes\n                )\n                await containersService.setNetworksService(networkService)\n                initializeHealthCheckService(log: log, routes: &routes)\n                try initializeKernelService(log: log, routes: &routes)\n                let volumesService = try initializeVolumeService(containersService: containersService, log: log, routes: &routes)\n                try initializeDiskUsageService(\n                    containersService: containersService,\n                    volumesService: volumesService,\n                    log: log,\n                    routes: &routes\n                )\n\n                let server = XPCServer(\n                    identifier: \"com.apple.container.apiserver\",\n                    routes: routes.reduce(\n                        into: [String: XPCServer.RouteHandler](),\n                        {\n                            $0[$1.key.rawValue] = $1.value\n                        }), log: log)\n\n                await withTaskGroup(of: Result<Void, Error>.self) { group in\n                    group.addTask {\n                        log.info(\"starting XPC server\")\n                        do {\n                            try await server.listen()\n                            return .success(())\n                        } catch {\n                            return .failure(error)\n                        }\n                    }\n\n                    // start up host table DNS\n                    group.addTask {\n                        let hostsResolver = ContainerDNSHandler(networkService: networkService)\n                        let nxDomainResolver = NxDomainResolver()\n                        let compositeResolver = CompositeResolver(handlers: [hostsResolver, nxDomainResolver])\n                        let hostsQueryValidator = StandardQueryValidator(handler: compositeResolver)\n                        let dnsServer: DNSServer = DNSServer(handler: hostsQueryValidator, log: log)\n                        log.info(\n                            \"starting DNS resolver for container hostnames\",\n                            metadata: [\n                                \"host\": \"\\(Self.listenAddress)\",\n                                \"port\": \"\\(Self.dnsPort)\",\n                            ]\n                        )\n                        do {\n                            try await dnsServer.run(host: Self.listenAddress, port: Self.dnsPort)\n                            return .success(())\n                        } catch {\n                            return .failure(error)\n                        }\n\n                    }\n\n                    // start up realhost DNS\n                    group.addTask {\n                        do {\n                            let localhostResolver = LocalhostDNSHandler(log: log)\n                            await localhostResolver.monitorResolvers()\n\n                            let nxDomainResolver = NxDomainResolver()\n                            let compositeResolver = CompositeResolver(handlers: [localhostResolver, nxDomainResolver])\n                            let hostsQueryValidator = StandardQueryValidator(handler: compositeResolver)\n                            let dnsServer: DNSServer = DNSServer(handler: hostsQueryValidator, log: log)\n                            log.info(\n                                \"starting DNS resolver for localhost\",\n                                metadata: [\n                                    \"host\": \"\\(Self.listenAddress)\",\n                                    \"port\": \"\\(Self.localhostDNSPort)\",\n                                ]\n                            )\n                            try await dnsServer.run(host: Self.listenAddress, port: Self.localhostDNSPort)\n                            return .success(())\n                        } catch {\n                            return .failure(error)\n                        }\n                    }\n\n                    for await result in group {\n                        switch result {\n                        case .success():\n                            continue\n                        case .failure(let error):\n                            log.error(\"API server task failed: \\(error)\")\n                        }\n                    }\n                }\n            } catch {\n                log.error(\n                    \"helper failed\",\n                    metadata: [\n                        \"name\": \"\\(commandName)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                APIServer.exit(withError: error)\n            }\n        }\n\n        private func initializePluginLoader(log: Logger) throws -> PluginLoader {\n            log.info(\n                \"initializing plugin loader\",\n                metadata: [\n                    \"installRoot\": \"\\(installRoot.path(percentEncoded: false))\"\n                ])\n\n            let pluginsURL = PluginLoader.userPluginsDir(installRoot: installRoot)\n            log.info(\"detecting user plugins directory\", metadata: [\"path\": \"\\(pluginsURL.path(percentEncoded: false))\"])\n            var directoryExists: ObjCBool = false\n            _ = FileManager.default.fileExists(atPath: pluginsURL.path, isDirectory: &directoryExists)\n            let userPluginsURL = directoryExists.boolValue ? pluginsURL : nil\n\n            // plugins built into the application installed as a macOS app bundle\n            let appBundlePluginsURL = Bundle.main.resourceURL?.appending(path: \"plugins\")\n\n            // plugins built into the application installed as a Unix-like application\n            let installRootPluginsURL =\n                installRoot\n                .appendingPathComponent(\"libexec\")\n                .appendingPathComponent(\"container\")\n                .appendingPathComponent(\"plugins\")\n                .standardized\n\n            let pluginDirectories = [\n                userPluginsURL,\n                appBundlePluginsURL,\n                installRootPluginsURL,\n            ].compactMap { $0 }\n\n            let pluginFactories: [PluginFactory] = [\n                DefaultPluginFactory(),\n                AppBundlePluginFactory(),\n            ]\n\n            for pluginDirectory in pluginDirectories {\n                log.info(\"discovered plugin directory\", metadata: [\"path\": \"\\(pluginDirectory.path(percentEncoded: false))\"])\n            }\n\n            return try PluginLoader(\n                appRoot: appRoot,\n                installRoot: installRoot,\n                logRoot: logRoot,\n                pluginDirectories: pluginDirectories,\n                pluginFactories: pluginFactories,\n                log: log\n            )\n        }\n\n        // First load all of the plugins we can find. Then just expose\n        // the handlers for clients to do whatever they want.\n        private func initializePlugins(\n            pluginLoader: PluginLoader,\n            log: Logger,\n            routes: inout [XPCRoute: XPCServer.RouteHandler]\n        ) async throws {\n            log.info(\"initializing plugins\")\n\n            let bootPlugins = pluginLoader.findPlugins().filter { $0.shouldBoot }\n\n            let service = PluginsService(pluginLoader: pluginLoader, log: log)\n            try await service.loadAll(bootPlugins)\n\n            let harness = PluginsHarness(service: service, log: log)\n            routes[XPCRoute.pluginGet] = harness.get\n            routes[XPCRoute.pluginList] = harness.list\n            routes[XPCRoute.pluginLoad] = harness.load\n            routes[XPCRoute.pluginUnload] = harness.unload\n            routes[XPCRoute.pluginRestart] = harness.restart\n        }\n\n        private func initializeHealthCheckService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) {\n            log.info(\"initializing health check service\")\n\n            let svc = HealthCheckHarness(\n                appRoot: appRoot,\n                installRoot: installRoot,\n                logRoot: logRoot,\n                log: log\n            )\n            routes[XPCRoute.ping] = svc.ping\n        }\n\n        private func initializeKernelService(log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws {\n            log.info(\"initializing kernel service\")\n\n            let svc = try KernelService(log: log, appRoot: appRoot)\n            let harness = KernelHarness(service: svc, log: log)\n            routes[XPCRoute.installKernel] = harness.install\n            routes[XPCRoute.getDefaultKernel] = harness.getDefaultKernel\n        }\n\n        private func initializeContainersService(pluginLoader: PluginLoader, log: Logger, routes: inout [XPCRoute: XPCServer.RouteHandler]) throws -> ContainersService {\n            log.info(\"initializing containers service\")\n\n            let service = try ContainersService(\n                appRoot: appRoot,\n                pluginLoader: pluginLoader,\n                log: log,\n                debugHelpers: debug\n            )\n            let harness = ContainersHarness(service: service, log: log)\n\n            routes[XPCRoute.containerList] = harness.list\n            routes[XPCRoute.containerCreate] = harness.create\n            routes[XPCRoute.containerDelete] = harness.delete\n            routes[XPCRoute.containerLogs] = harness.logs\n            routes[XPCRoute.containerBootstrap] = harness.bootstrap\n            routes[XPCRoute.containerDial] = harness.dial\n            routes[XPCRoute.containerStop] = harness.stop\n            routes[XPCRoute.containerStartProcess] = harness.startProcess\n            routes[XPCRoute.containerCreateProcess] = harness.createProcess\n            routes[XPCRoute.containerResize] = harness.resize\n            routes[XPCRoute.containerWait] = harness.wait\n            routes[XPCRoute.containerKill] = harness.kill\n            routes[XPCRoute.containerStats] = harness.stats\n            routes[XPCRoute.containerDiskUsage] = harness.diskUsage\n            routes[XPCRoute.containerExport] = harness.export\n\n            return service\n        }\n\n        private func initializeNetworksService(\n            pluginLoader: PluginLoader,\n            containersService: ContainersService,\n            log: Logger,\n            routes: inout [XPCRoute: XPCServer.RouteHandler]\n        ) async throws -> NetworksService {\n            log.info(\"initializing networks service\")\n\n            let resourceRoot = appRoot.appendingPathComponent(\"networks\")\n            let service = try await NetworksService(\n                pluginLoader: pluginLoader,\n                resourceRoot: resourceRoot,\n                containersService: containersService,\n                log: log,\n                debugHelpers: debug\n            )\n\n            let defaultNetwork = try await service.list()\n                .filter { $0.isBuiltin }\n                .first\n            if defaultNetwork == nil {\n                // FIXME: default network should be configurable elsewhere\n                let config = try NetworkConfiguration(\n                    id: ClientNetwork.defaultNetworkName,\n                    mode: .nat,\n                    labels: [ResourceLabelKeys.role: ResourceRoleValues.builtin],\n                    pluginInfo: NetworkPluginInfo(plugin: \"container-network-vmnet\")\n                )\n                _ = try await service.create(configuration: config)\n            }\n\n            let harness = NetworksHarness(service: service, log: log)\n\n            routes[XPCRoute.networkCreate] = harness.create\n            routes[XPCRoute.networkDelete] = harness.delete\n            routes[XPCRoute.networkList] = harness.list\n            return service\n        }\n\n        private func initializeVolumeService(\n            containersService: ContainersService,\n            log: Logger,\n            routes: inout [XPCRoute: XPCServer.RouteHandler]\n        ) throws -> VolumesService {\n            log.info(\"initializing volume service\")\n\n            let resourceRoot = appRoot.appendingPathComponent(\"volumes\")\n            let service = try VolumesService(resourceRoot: resourceRoot, containersService: containersService, log: log)\n            let harness = VolumesHarness(service: service, log: log)\n\n            routes[XPCRoute.volumeCreate] = harness.create\n            routes[XPCRoute.volumeDelete] = harness.delete\n            routes[XPCRoute.volumeList] = harness.list\n            routes[XPCRoute.volumeInspect] = harness.inspect\n            routes[XPCRoute.volumeDiskUsage] = harness.diskUsage\n\n            return service\n        }\n\n        private func initializeDiskUsageService(\n            containersService: ContainersService,\n            volumesService: VolumesService,\n            log: Logger,\n            routes: inout [XPCRoute: XPCServer.RouteHandler]\n        ) throws {\n            log.info(\"initializing disk usage service\")\n\n            let service = DiskUsageService(\n                containersService: containersService,\n                volumesService: volumesService,\n                log: log\n            )\n            let harness = DiskUsageHarness(service: service, log: log)\n\n            routes[XPCRoute.systemDiskUsage] = harness.get\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/APIServer/APIServer.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerVersion\n\n@main\nstruct APIServer: AsyncParsableCommand {\n    static let configuration = CommandConfiguration(\n        commandName: \"container-apiserver\",\n        abstract: \"Container management API server\",\n        version: ReleaseVersion.singleLine(appName: \"container-apiserver\"),\n        subcommands: [Start.self],\n    )\n}\n"
  },
  {
    "path": "Sources/Helpers/APIServer/ContainerDNSHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIService\nimport ContainerizationExtras\nimport DNSServer\n\n/// Handler that uses table lookup to resolve hostnames.\nstruct ContainerDNSHandler: DNSHandler {\n    private let networkService: NetworksService\n    private let ttl: UInt32\n\n    public init(networkService: NetworksService, ttl: UInt32 = 5) {\n        self.networkService = networkService\n        self.ttl = ttl\n    }\n\n    public func answer(query: Message) async throws -> Message? {\n        guard let question = query.questions.first else {\n            return nil\n        }\n        let record: ResourceRecord?\n        switch question.type {\n        case ResourceRecordType.host:\n            record = try await answerHost(question: question)\n        case ResourceRecordType.host6:\n            let result = try await answerHost6(question: question)\n            if result.record == nil && result.hostnameExists {\n                // Return NODATA (noError with empty answers) when hostname exists but has no IPv6.\n                // This is required because musl libc has issues when A record exists but AAAA returns NXDOMAIN.\n                // musl treats NXDOMAIN on AAAA as \"domain doesn't exist\" and fails DNS resolution entirely.\n                // NODATA correctly indicates \"no IPv6 address available, but domain exists\".\n                return Message(\n                    id: query.id,\n                    type: .response,\n                    returnCode: .noError,\n                    questions: query.questions,\n                    answers: []\n                )\n            }\n            record = result.record\n        default:\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .notImplemented,\n                questions: query.questions,\n                answers: []\n            )\n        }\n\n        guard let record else {\n            return nil\n        }\n\n        return Message(\n            id: query.id,\n            type: .response,\n            returnCode: .noError,\n            questions: query.questions,\n            answers: [record]\n        )\n    }\n\n    private func answerHost(question: Question) async throws -> ResourceRecord? {\n        guard let ipAllocation = try await networkService.lookup(hostname: question.name) else {\n            return nil\n        }\n        let ipv4 = ipAllocation.ipv4Address.address.description\n        guard let ip = try? IPv4Address(ipv4) else {\n            throw DNSResolverError.serverError(\"failed to parse IP address: \\(ipv4)\")\n        }\n\n        return HostRecord<IPv4Address>(name: question.name, ttl: ttl, ip: ip)\n    }\n\n    private func answerHost6(question: Question) async throws -> (record: ResourceRecord?, hostnameExists: Bool) {\n        guard let ipAllocation = try await networkService.lookup(hostname: question.name) else {\n            return (nil, false)\n        }\n        guard let ipv6Address = ipAllocation.ipv6Address else {\n            return (nil, true)\n        }\n        let ipv6 = ipv6Address.address.description\n        guard let ip = try? IPv6Address(ipv6) else {\n            throw DNSResolverError.serverError(\"failed to parse IPv6 address: \\(ipv6)\")\n        }\n\n        return (HostRecord<IPv6Address>(name: question.name, ttl: ttl, ip: ip), true)\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/APIServer/LocalhostDNSHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerOS\nimport ContainerPersistence\nimport ContainerizationError\nimport ContainerizationExtras\nimport DNSServer\nimport Foundation\nimport Logging\nimport Synchronization\n\nactor LocalhostDNSHandler: DNSHandler {\n    private let ttl: UInt32\n    private let watcher: DirectoryWatcher\n\n    private var dns: [DNSName: IPv4Address]\n\n    public init(resolversURL: URL = HostDNSResolver.defaultConfigPath, ttl: UInt32 = 5, log: Logger) {\n        self.ttl = ttl\n\n        self.watcher = DirectoryWatcher(directoryURL: resolversURL, log: log)\n        self.dns = [DNSName: IPv4Address]()\n    }\n\n    public func monitorResolvers() async {\n        await self.watcher.startWatching { [weak self] fileURLs in\n            var dns: [DNSName: IPv4Address] = [:]\n            let regex = try Regex(HostDNSResolver.localhostOptionsRegex)\n\n            for file in fileURLs.filter({ $0.lastPathComponent.starts(with: HostDNSResolver.containerizationPrefix) }) {\n                let content = try String(contentsOf: file, encoding: .utf8)\n\n                if let match = content.firstMatch(of: regex),\n                    let ipv4 = (match[1].substring.flatMap { try? IPv4Address(String($0)) })\n                {\n                    let name = String(file.lastPathComponent.dropFirst(HostDNSResolver.containerizationPrefix.count))\n                    guard let dnsName = try? DNSName(name) else {\n                        continue\n                    }\n                    dns[dnsName] = ipv4\n                }\n            }\n            Task { await self?.updateDNS(dns) }\n        }\n    }\n\n    public func answer(query: Message) async throws -> Message? {\n        guard let question = query.questions.first else {\n            return nil\n        }\n        let n = question.name.hasSuffix(\".\") ? String(question.name.dropLast()) : question.name\n        let key = try DNSName(labels: n.isEmpty ? [] : n.split(separator: \".\", omittingEmptySubsequences: false).map(String.init))\n        var record: ResourceRecord?\n        switch question.type {\n        case ResourceRecordType.host:\n            if let ip = dns[key] {\n                record = HostRecord<IPv4Address>(name: question.name, ttl: ttl, ip: ip)\n            }\n        case ResourceRecordType.host6:\n            guard dns[key] != nil else {\n                return nil\n            }\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .noError,\n                questions: query.questions,\n                answers: []\n            )\n        default:\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .notImplemented,\n                questions: query.questions,\n                answers: []\n            )\n        }\n\n        guard let record else {\n            return nil\n        }\n\n        return Message(\n            id: query.id,\n            type: .response,\n            returnCode: .noError,\n            questions: query.questions,\n            answers: [record]\n        )\n    }\n\n    private func updateDNS(_ dns: [DNSName: IPv4Address]) {\n        self.dns = dns\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/Images/ImagesHelper.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerImagesService\nimport ContainerImagesServiceClient\nimport ContainerLog\nimport ContainerPlugin\nimport ContainerVersion\nimport ContainerXPC\nimport Containerization\nimport Foundation\nimport Logging\n\n@main\nstruct ImagesHelper: AsyncParsableCommand {\n    static let configuration = CommandConfiguration(\n        commandName: \"container-core-images\",\n        abstract: \"XPC service for managing OCI images\",\n        version: ReleaseVersion.singleLine(appName: \"container-core-images\"),\n        subcommands: [\n            Start.self\n        ]\n    )\n}\n\nextension ImagesHelper {\n    struct Start: AsyncParsableCommand {\n        static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Starts the image plugin\"\n        )\n\n        @Flag(name: .long, help: \"Enable debug logging\")\n        var debug = false\n\n        @Option(name: .long, help: \"XPC service prefix\")\n        var serviceIdentifier: String = \"com.apple.container.core.container-core-images\"\n\n        var appRoot = ApplicationRoot.url\n\n        var installRoot = InstallRoot.url\n\n        var logRoot = LogRoot.path\n\n        private static let unpackStrategy = SnapshotStore.defaultUnpackStrategy\n\n        func run() async throws {\n            let commandName = ImagesHelper._commandName\n            let logPath = logRoot.map { $0.appending(\"\\(commandName).log\") }\n            let log = ServiceLogger.bootstrap(category: \"ImagesHelper\", debug: debug, logPath: logPath)\n            log.info(\"starting helper\", metadata: [\"name\": \"\\(commandName)\"])\n            defer {\n                log.info(\"stopping helper\", metadata: [\"name\": \"\\(commandName)\"])\n            }\n\n            do {\n                log.info(\"configuring XPC server\")\n                var routes = [String: XPCServer.RouteHandler]()\n                try self.initializeContentService(root: appRoot, log: log, routes: &routes)\n                try self.initializeImagesService(root: appRoot, log: log, routes: &routes)\n                let xpc = XPCServer(\n                    identifier: serviceIdentifier,\n                    routes: routes,\n                    log: log\n                )\n                log.info(\"starting XPC server\")\n                try await xpc.listen()\n            } catch {\n                log.error(\n                    \"helper failed\",\n                    metadata: [\n                        \"name\": \"\\(commandName)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                ImagesHelper.exit(withError: error)\n            }\n        }\n\n        private func initializeImagesService(root: URL, log: Logger, routes: inout [String: XPCServer.RouteHandler]) throws {\n            let contentStore = RemoteContentStoreClient()\n            let imageStore = try ImageStore(path: root, contentStore: contentStore)\n            let snapshotStore = try SnapshotStore(path: root, unpackStrategy: Self.unpackStrategy, log: log)\n            let service = try ImagesService(contentStore: contentStore, imageStore: imageStore, snapshotStore: snapshotStore, log: log)\n            let harness = ImagesServiceHarness(service: service, log: log)\n\n            routes[ImagesServiceXPCRoute.imagePull.rawValue] = harness.pull\n            routes[ImagesServiceXPCRoute.imageList.rawValue] = harness.list\n            routes[ImagesServiceXPCRoute.imageDelete.rawValue] = harness.delete\n            routes[ImagesServiceXPCRoute.imageTag.rawValue] = harness.tag\n            routes[ImagesServiceXPCRoute.imagePush.rawValue] = harness.push\n            routes[ImagesServiceXPCRoute.imageSave.rawValue] = harness.save\n            routes[ImagesServiceXPCRoute.imageLoad.rawValue] = harness.load\n            routes[ImagesServiceXPCRoute.imageUnpack.rawValue] = harness.unpack\n            routes[ImagesServiceXPCRoute.imageCleanupOrphanedBlobs.rawValue] = harness.cleanUpOrphanedBlobs\n            routes[ImagesServiceXPCRoute.imageDiskUsage.rawValue] = harness.calculateDiskUsage\n            routes[ImagesServiceXPCRoute.snapshotDelete.rawValue] = harness.deleteSnapshot\n            routes[ImagesServiceXPCRoute.snapshotGet.rawValue] = harness.getSnapshot\n        }\n\n        private func initializeContentService(root: URL, log: Logger, routes: inout [String: XPCServer.RouteHandler]) throws {\n            let service = try ContentStoreService(root: root, log: log)\n            let harness = ContentServiceHarness(service: service, log: log)\n\n            routes[ImagesServiceXPCRoute.contentClean.rawValue] = harness.clean\n            routes[ImagesServiceXPCRoute.contentGet.rawValue] = harness.get\n            routes[ImagesServiceXPCRoute.contentDelete.rawValue] = harness.delete\n            routes[ImagesServiceXPCRoute.contentIngestStart.rawValue] = harness.newIngestSession\n            routes[ImagesServiceXPCRoute.contentIngestCancel.rawValue] = harness.cancelIngestSession\n            routes[ImagesServiceXPCRoute.contentIngestComplete.rawValue] = harness.completeIngestSession\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/NetworkVmnet/NetworkVmnetHelper+Start.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerLog\nimport ContainerNetworkService\nimport ContainerNetworkServiceClient\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Logging\n\nenum Variant: String, ExpressibleByArgument {\n    case reserved\n    case allocationOnly\n}\n\nextension NetworkMode: ExpressibleByArgument {}\n\nextension NetworkVmnetHelper {\n    struct Start: AsyncParsableCommand {\n        static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Starts the network plugin\"\n        )\n\n        @Flag(name: .long, help: \"Enable debug logging\")\n        var debug = false\n\n        @Option(name: .long, help: \"XPC service identifier\")\n        var serviceIdentifier: String\n\n        @Option(name: .shortAndLong, help: \"Network identifier\")\n        var id: String\n\n        @Option(name: .long, help: \"Network mode\")\n        var mode: NetworkMode = .nat\n\n        @Option(name: .customLong(\"subnet\"), help: \"CIDR address for the IPv4 subnet\")\n        var ipv4Subnet: String?\n\n        @Option(name: .customLong(\"subnet-v6\"), help: \"CIDR address for the IPv6 prefix\")\n        var ipv6Subnet: String?\n\n        @Option(name: .long, help: \"Variant of the network helper to use.\")\n        var variant: Variant = {\n            guard #available(macOS 26, *) else {\n                return .allocationOnly\n            }\n            return .reserved\n        }()\n\n        var logRoot = LogRoot.path\n\n        func run() async throws {\n            let commandName = NetworkVmnetHelper._commandName\n            let logPath = logRoot.map { $0.appending(\"\\(commandName)-\\(id).log\") }\n            let log = ServiceLogger.bootstrap(category: \"NetworkVmnetHelper\", metadata: [\"id\": \"\\(id)\"], debug: debug, logPath: logPath)\n            log.info(\"starting helper\", metadata: [\"name\": \"\\(commandName)\"])\n            defer {\n                log.info(\"stopping helper\", metadata: [\"name\": \"\\(commandName)\"])\n            }\n\n            do {\n                log.info(\"configuring XPC server\")\n                let ipv4Subnet = try self.ipv4Subnet.map { try CIDRv4($0) }\n                let ipv6Subnet = try self.ipv6Subnet.map { try CIDRv6($0) }\n                let pluginInfo = NetworkPluginInfo(\n                    plugin: NetworkVmnetHelper._commandName,\n                    variant: self.variant.rawValue\n                )\n\n                let configuration = try NetworkConfiguration(\n                    id: id,\n                    mode: mode,\n                    ipv4Subnet: ipv4Subnet,\n                    ipv6Subnet: ipv6Subnet,\n                    pluginInfo: pluginInfo\n                )\n                let network = try Self.createNetwork(\n                    configuration: configuration,\n                    variant: self.variant,\n                    log: log\n                )\n                try await network.start()\n                let server = try await NetworkService(network: network, log: log)\n                let xpc = XPCServer(\n                    identifier: serviceIdentifier,\n                    routes: [\n                        NetworkRoutes.state.rawValue: server.state,\n                        NetworkRoutes.allocate.rawValue: server.allocate,\n                        NetworkRoutes.deallocate.rawValue: server.deallocate,\n                        NetworkRoutes.lookup.rawValue: server.lookup,\n                        NetworkRoutes.disableAllocator.rawValue: server.disableAllocator,\n                    ],\n                    log: log\n                )\n\n                log.info(\"starting XPC server\")\n                try await xpc.listen()\n            } catch {\n                log.error(\n                    \"helper failed\",\n                    metadata: [\n                        \"name\": \"\\(commandName)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                NetworkVmnetHelper.exit(withError: error)\n            }\n        }\n\n        private static func createNetwork(configuration: NetworkConfiguration, variant: Variant, log: Logger) throws -> Network {\n            switch variant {\n            case .allocationOnly:\n                return try AllocationOnlyVmnetNetwork(configuration: configuration, log: log)\n            case .reserved:\n                guard #available(macOS 26, *) else {\n                    throw ContainerizationError(\n                        .invalidArgument,\n                        message: \"variant ReservedVmnetNetwork is only available on macOS 26+\"\n                    )\n                }\n                return try ReservedVmnetNetwork(configuration: configuration, log: log)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/NetworkVmnet/NetworkVmnetHelper.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerVersion\n\n@main\nstruct NetworkVmnetHelper: AsyncParsableCommand {\n    static let configuration = CommandConfiguration(\n        commandName: \"container-network-vmnet\",\n        abstract: \"XPC service for managing a vmnet network\",\n        version: ReleaseVersion.singleLine(appName: \"container-network-vmnet\"),\n        subcommands: [\n            Start.self\n        ]\n    )\n}\n"
  },
  {
    "path": "Sources/Helpers/RuntimeLinux/IsolatedInterfaceStrategy.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerSandboxService\nimport ContainerXPC\nimport Containerization\n\n/// Isolated container network interface strategy. This strategy prohibits\n/// container to container networking, but it is the only approach that\n/// works for macOS Sequoia.\nstruct IsolatedInterfaceStrategy: InterfaceStrategy {\n    public func toInterface(attachment: Attachment, interfaceIndex: Int, additionalData: XPCMessage?) -> Interface {\n        let ipv4Gateway = interfaceIndex == 0 ? attachment.ipv4Gateway : nil\n        return NATInterface(\n            ipv4Address: attachment.ipv4Address,\n            ipv4Gateway: ipv4Gateway,\n            macAddress: attachment.macAddress,\n            // https://github.com/apple/containerization/pull/38\n            mtu: attachment.mtu ?? 1280\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/RuntimeLinux/NonisolatedInterfaceStrategy.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerSandboxService\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport Logging\nimport Virtualization\nimport vmnet\n\n/// Interface strategy for containers that use macOS's custom network feature.\n@available(macOS 26, *)\nstruct NonisolatedInterfaceStrategy: InterfaceStrategy {\n    private let log: Logger\n\n    public init(log: Logger) {\n        self.log = log\n    }\n\n    public func toInterface(attachment: Attachment, interfaceIndex: Int, additionalData: XPCMessage?) throws -> Interface {\n        guard let additionalData else {\n            throw ContainerizationError(.invalidState, message: \"network state does not contain custom network reference\")\n        }\n\n        var status: vmnet_return_t = .VMNET_SUCCESS\n        guard let networkRef = vmnet_network_create_with_serialization(additionalData.underlying, &status) else {\n            throw ContainerizationError(.invalidState, message: \"cannot deserialize custom network reference, status \\(status)\")\n        }\n\n        log.info(\"creating NATNetworkInterface with network reference\")\n        let ipv4Gateway = interfaceIndex == 0 ? attachment.ipv4Gateway : nil\n        return NATNetworkInterface(\n            ipv4Address: attachment.ipv4Address,\n            ipv4Gateway: ipv4Gateway,\n            reference: networkRef,\n            macAddress: attachment.macAddress,\n            // https://github.com/apple/containerization/pull/38\n            mtu: attachment.mtu ?? 1280\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/RuntimeLinux/RuntimeLinuxHelper+Start.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerLog\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerSandboxService\nimport ContainerSandboxServiceClient\nimport ContainerXPC\nimport Foundation\nimport Logging\nimport NIO\n\nextension RuntimeLinuxHelper {\n    struct Start: AsyncParsableCommand {\n        static let label = \"com.apple.container.runtime.container-runtime-linux\"\n\n        static let configuration = CommandConfiguration(\n            commandName: \"start\",\n            abstract: \"Start helper for a Linux container\"\n        )\n\n        @Flag(name: .long, help: \"Enable debug logging\")\n        var debug = false\n\n        @Option(name: .shortAndLong, help: \"Sandbox UUID\")\n        var uuid: String\n\n        @Option(name: .shortAndLong, help: \"Root directory for the sandbox\")\n        var root: String\n\n        var logRoot = LogRoot.path\n\n        var machServiceLabel: String {\n            \"\\(Self.label).\\(uuid)\"\n        }\n\n        func run() async throws {\n            let commandName = RuntimeLinuxHelper._commandName\n            let logPath = logRoot.map { $0.appending(\"\\(commandName)-\\(uuid).log\") }\n            let log = ServiceLogger.bootstrap(category: \"RuntimeLinuxHelper\", metadata: [\"uuid\": \"\\(uuid)\"], debug: debug, logPath: logPath)\n            log.info(\"starting helper\", metadata: [\"name\": \"\\(commandName)\"])\n            defer {\n                log.info(\"stopping helper\", metadata: [\"name\": \"\\(commandName)\"])\n            }\n\n            let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)\n            do {\n                try adjustLimits()\n                signal(SIGPIPE, SIG_IGN)\n\n                // FIXME: The network plugins that the runtime supports should be configurable elsewhere\n                var interfaceStrategies: [NetworkPluginInfo: InterfaceStrategy] = [\n                    NetworkPluginInfo(plugin: \"container-network-vmnet\", variant: \"allocationOnly\"): IsolatedInterfaceStrategy()\n                ]\n                if #available(macOS 26, *) {\n                    interfaceStrategies[NetworkPluginInfo(plugin: \"container-network-vmnet\", variant: \"reserved\")] = NonisolatedInterfaceStrategy(log: log)\n                }\n\n                log.info(\"configuring XPC server\")\n                nonisolated(unsafe) let anonymousConnection = xpc_connection_create(nil, nil)\n\n                let server = SandboxService(\n                    root: .init(fileURLWithPath: root),\n                    interfaceStrategies: interfaceStrategies,\n                    eventLoopGroup: eventLoopGroup,\n                    connection: anonymousConnection,\n                    log: log\n                )\n\n                let endpointServer = XPCServer(\n                    identifier: machServiceLabel,\n                    routes: [\n                        SandboxRoutes.createEndpoint.rawValue: server.createEndpoint\n                    ],\n                    log: log\n                )\n\n                let mainServer = XPCServer(\n                    connection: anonymousConnection,\n                    routes: [\n                        SandboxRoutes.bootstrap.rawValue: server.bootstrap,\n                        SandboxRoutes.createProcess.rawValue: server.createProcess,\n                        SandboxRoutes.state.rawValue: server.state,\n                        SandboxRoutes.stop.rawValue: server.stop,\n                        SandboxRoutes.kill.rawValue: server.kill,\n                        SandboxRoutes.resize.rawValue: server.resize,\n                        SandboxRoutes.wait.rawValue: server.wait,\n                        SandboxRoutes.start.rawValue: server.startProcess,\n                        SandboxRoutes.dial.rawValue: server.dial,\n                        SandboxRoutes.shutdown.rawValue: server.shutdown,\n                        SandboxRoutes.statistics.rawValue: server.statistics,\n                    ],\n                    log: log\n                )\n\n                log.info(\"starting XPC server\")\n                try await withThrowingTaskGroup(of: Void.self) { group in\n                    group.addTask {\n                        try await endpointServer.listen()\n                    }\n                    group.addTask {\n                        try await mainServer.listen()\n                    }\n                    defer { group.cancelAll() }\n\n                    _ = try await group.next()\n                }\n            } catch {\n                log.error(\n                    \"helper failed\",\n                    metadata: [\n                        \"name\": \"\\(commandName)\",\n                        \"error\": \"\\(error)\",\n                    ])\n                try? await eventLoopGroup.shutdownGracefully()\n                RuntimeLinuxHelper.Start.exit(withError: error)\n            }\n        }\n\n        private func adjustLimits() throws {\n            var limits = rlimit()\n            guard getrlimit(RLIMIT_NOFILE, &limits) == 0 else {\n                throw POSIXError(.init(rawValue: errno)!)\n            }\n            limits.rlim_cur = 65536\n            limits.rlim_max = 65536\n            guard setrlimit(RLIMIT_NOFILE, &limits) == 0 else {\n                throw POSIXError(.init(rawValue: errno)!)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Helpers/RuntimeLinux/RuntimeLinuxHelper.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerVersion\n\n@main\nstruct RuntimeLinuxHelper: AsyncParsableCommand {\n    static let configuration = CommandConfiguration(\n        commandName: \"container-runtime-linux\",\n        abstract: \"XPC Service for managing a Linux sandbox\",\n        version: ReleaseVersion.singleLine(appName: \"container-runtime-linux\"),\n        subcommands: [\n            Start.self\n        ]\n    )\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Arch.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum Arch: String {\n    case arm64, amd64\n\n    public init?(rawValue: String) {\n        switch rawValue.lowercased() {\n        case \"arm64\", \"aarch64\":\n            self = .arm64\n        case \"amd64\", \"x86_64\", \"x86-64\":\n            self = .amd64\n        default:\n            return nil\n        }\n    }\n\n    public static func hostArchitecture() -> Arch {\n        #if arch(arm64)\n        return .arm64\n        #elseif arch(x86_64)\n        return .amd64\n        #endif\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Archiver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationArchive\nimport ContainerizationOS\nimport CryptoKit\nimport Foundation\n\npublic final class Archiver: Sendable {\n    public struct ArchiveEntryInfo: Sendable, Codable {\n        public let pathOnHost: URL\n        public let pathInArchive: URL\n\n        public let owner: UInt32?\n        public let group: UInt32?\n        public let permissions: UInt16?\n\n        public init(\n            pathOnHost: URL,\n            pathInArchive: URL,\n            owner: UInt32? = nil,\n            group: UInt32? = nil,\n            permissions: UInt16? = nil\n        ) {\n            self.pathOnHost = pathOnHost\n            self.pathInArchive = pathInArchive\n            self.owner = owner\n            self.group = group\n            self.permissions = permissions\n        }\n    }\n\n    public static func compress(\n        source: URL,\n        destination: URL,\n        followSymlinks: Bool = false,\n        writerConfiguration: ArchiveWriterConfiguration = ArchiveWriterConfiguration(format: .paxRestricted, filter: .gzip),\n        closure: (URL) -> ArchiveEntryInfo?\n    ) throws -> SHA256.Digest {\n        let source = source.standardizedFileURL\n        let destination = destination.standardizedFileURL\n\n        let fileManager = FileManager.default\n        try? fileManager.removeItem(at: destination)\n\n        var hasher = SHA256()\n\n        do {\n            let directory = destination.deletingLastPathComponent()\n            try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)\n\n            guard\n                let enumerator = FileManager.default.enumerator(atPath: source.path)\n            else {\n                throw Error.fileDoesNotExist(source)\n            }\n\n            var entryInfo = [ArchiveEntryInfo]()\n            if !source.isDirectory {\n                if let info = closure(source) {\n                    entryInfo.append(info)\n                }\n            } else {\n                let relPaths = enumerator.compactMap { $0 as? String }\n                for relPath in relPaths.sorted(by: { $0 < $1 }) {\n                    let url = source.appending(path: relPath).standardizedFileURL\n                    guard let info = closure(url) else {\n                        continue\n                    }\n                    entryInfo.append(info)\n                }\n            }\n\n            let archiver = try ArchiveWriter(\n                configuration: writerConfiguration\n            )\n            try archiver.open(file: destination)\n\n            let encoder = JSONEncoder()\n            encoder.outputFormatting = .sortedKeys\n\n            for info in entryInfo {\n                guard let entry = try Self._createEntry(entryInfo: info) else {\n                    throw Error.failedToCreateEntry\n                }\n                hasher.update(data: try encoder.encode(info))\n                try Self._compressFile(item: info.pathOnHost, entry: entry, archiver: archiver, hasher: &hasher)\n            }\n            try archiver.finishEncoding()\n        } catch {\n            try? fileManager.removeItem(at: destination)\n            throw error\n        }\n\n        return hasher.finalize()\n    }\n\n    public static func uncompress(source: URL, destination: URL) throws {\n        let source = source.standardizedFileURL\n        let destination = destination.standardizedFileURL\n\n        // TODO: ArchiveReader needs some enhancement to support buffered uncompression\n        let reader = try ArchiveReader(\n            format: .paxRestricted,\n            filter: .gzip,\n            file: source\n        )\n\n        for (entry, data) in reader {\n            guard let path = entry.path else {\n                continue\n            }\n            let uncompressPath = destination.appendingPathComponent(path)\n\n            let fileManager = FileManager.default\n            switch entry.fileType {\n            case .blockSpecial, .characterSpecial, .socket:\n                continue\n            case .directory:\n                try fileManager.createDirectory(\n                    at: uncompressPath,\n                    withIntermediateDirectories: true,\n                    attributes: [\n                        FileAttributeKey.posixPermissions: entry.permissions\n                    ]\n                )\n            case .regular:\n                try fileManager.createDirectory(\n                    at: uncompressPath.deletingLastPathComponent(),\n                    withIntermediateDirectories: true,\n                    attributes: [\n                        FileAttributeKey.posixPermissions: 0o755\n                    ]\n                )\n                let success = fileManager.createFile(\n                    atPath: uncompressPath.path,\n                    contents: data,\n                    attributes: [\n                        FileAttributeKey.posixPermissions: entry.permissions\n                    ]\n                )\n                if !success {\n                    throw POSIXError.fromErrno()\n                }\n                try data.write(to: uncompressPath)\n            case .symbolicLink:\n                guard let target = entry.symlinkTarget else {\n                    continue\n                }\n                try fileManager.createDirectory(\n                    at: uncompressPath.deletingLastPathComponent(),\n                    withIntermediateDirectories: true,\n                    attributes: [\n                        FileAttributeKey.posixPermissions: 0o755\n                    ]\n                )\n                try fileManager.createSymbolicLink(atPath: uncompressPath.path, withDestinationPath: target)\n                continue\n            default:\n                continue\n            }\n\n            // FIXME: uid/gid for compress.\n            try fileManager.setAttributes(\n                [.posixPermissions: NSNumber(value: entry.permissions)],\n                ofItemAtPath: uncompressPath.path\n            )\n\n            if let creationDate = entry.creationDate {\n                try fileManager.setAttributes(\n                    [.creationDate: creationDate],\n                    ofItemAtPath: uncompressPath.path\n                )\n            }\n\n            if let modificationDate = entry.modificationDate {\n                try fileManager.setAttributes(\n                    [.modificationDate: modificationDate],\n                    ofItemAtPath: uncompressPath.path\n                )\n            }\n        }\n    }\n\n    // MARK: private functions\n    private static func _compressFile(item: URL, entry: WriteEntry, archiver: ArchiveWriter, hasher: inout SHA256) throws {\n        guard let stream = InputStream(url: item) else {\n            return\n        }\n\n        let writer = archiver.makeTransactionWriter()\n\n        let bufferSize = Int(1.mib())\n        let readBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)\n\n        stream.open()\n        try writer.writeHeader(entry: entry)\n        while true {\n            let byteRead = stream.read(readBuffer, maxLength: bufferSize)\n            if byteRead <= 0 {\n                break\n            } else {\n                let data = Data(bytes: readBuffer, count: byteRead)\n                hasher.update(data: data)\n                try data.withUnsafeBytes { pointer in\n                    try writer.writeChunk(data: pointer)\n                }\n            }\n        }\n        stream.close()\n        try writer.finish()\n    }\n\n    private static func _createEntry(entryInfo: ArchiveEntryInfo, pathPrefix: String = \"\") throws -> WriteEntry? {\n        let entry = WriteEntry()\n        let fileManager = FileManager.default\n        let attributes = try fileManager.attributesOfItem(atPath: entryInfo.pathOnHost.path)\n\n        if let fileType = attributes[.type] as? FileAttributeType {\n            switch fileType {\n            case .typeBlockSpecial, .typeCharacterSpecial, .typeSocket:\n                return nil\n            case .typeDirectory:\n                entry.fileType = .directory\n            case .typeRegular:\n                entry.fileType = .regular\n            case .typeSymbolicLink:\n                entry.fileType = .symbolicLink\n                let symlinkTarget = try fileManager.destinationOfSymbolicLink(atPath: entryInfo.pathOnHost.path)\n                entry.symlinkTarget = symlinkTarget\n            default:\n                return nil\n            }\n        }\n        if let posixPermissions = attributes[.posixPermissions] as? NSNumber {\n            #if os(macOS)\n            entry.permissions = posixPermissions.uint16Value\n            #else\n            entry.permissions = posixPermissions.uint32Value\n            #endif\n        }\n        if let fileSize = attributes[.size] as? UInt64 {\n            entry.size = Int64(fileSize)\n        }\n        if let uid = attributes[.ownerAccountID] as? NSNumber {\n            entry.owner = uid.uint32Value\n        }\n        if let gid = attributes[.groupOwnerAccountID] as? NSNumber {\n            entry.group = gid.uint32Value\n        }\n        if let creationDate = attributes[.creationDate] as? Date {\n            entry.creationDate = creationDate\n        }\n        if let modificationDate = attributes[.modificationDate] as? Date {\n            entry.modificationDate = modificationDate\n        }\n\n        // Apply explicit overrides from ArchiveEntryInfo when provided\n        if let overrideOwner = entryInfo.owner {\n            entry.owner = overrideOwner\n        }\n        if let overrideGroup = entryInfo.group {\n            entry.group = overrideGroup\n        }\n        if let overridePerm = entryInfo.permissions {\n            #if os(macOS)\n            entry.permissions = overridePerm\n            #else\n            entry.permissions = UInt32(overridePerm)\n            #endif\n        }\n\n        let pathTrimmed = Self._trimPathPrefix(entryInfo.pathInArchive.relativePath, pathPrefix: pathPrefix)\n        entry.path = pathTrimmed\n        return entry\n    }\n\n    private static func _trimPathPrefix(_ path: String, pathPrefix: String) -> String {\n        guard !path.isEmpty && !pathPrefix.isEmpty else {\n            return path\n        }\n\n        let decodedPath = path.removingPercentEncoding ?? path\n\n        guard decodedPath.hasPrefix(pathPrefix) else {\n            return decodedPath\n        }\n        let trimmedPath = String(decodedPath.suffix(from: pathPrefix.endIndex))\n        return trimmedPath\n    }\n\n    private static func _isSymbolicLink(_ path: URL) throws -> Bool {\n        let resourceValues = try path.resourceValues(forKeys: [.isSymbolicLinkKey])\n        if let isSymbolicLink = resourceValues.isSymbolicLink {\n            if isSymbolicLink {\n                return true\n            }\n        }\n        return false\n    }\n}\n\nextension Archiver {\n    public enum Error: Swift.Error, CustomStringConvertible {\n        case failedToCreateEntry\n        case fileDoesNotExist(_ url: URL)\n\n        public var description: String {\n            switch self {\n            case .failedToCreateEntry:\n                return \"failed to create entry\"\n            case .fileDoesNotExist(let url):\n                return \"file \\(url.path) does not exist\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Array+Dedupe.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nextension Array where Element: Hashable {\n    func dedupe() -> [Element] {\n        var elems = Set<Element>()\n        return filter { elems.insert($0).inserted }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientDiskUsage.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\n\n/// Client API for disk usage operations\npublic struct ClientDiskUsage {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n\n    /// Get disk usage statistics for all resource types\n    public static func get() async throws -> DiskUsageStats {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .systemDiskUsage)\n        let reply = try await client.send(message)\n\n        guard let responseData = reply.dataNoCopy(key: .diskUsageStats) else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"invalid response from server: missing disk usage data\"\n            )\n        }\n\n        return try JSONDecoder().decode(DiskUsageStats.self, from: responseData)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientHealthCheck.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\nimport SystemPackage\n\npublic enum ClientHealthCheck {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n}\n\nextension ClientHealthCheck {\n    private static func newClient() -> XPCClient {\n        XPCClient(service: serviceIdentifier)\n    }\n\n    public static func ping(timeout: Duration? = XPCClient.xpcRegistrationTimeout) async throws -> SystemHealth {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .ping)\n        let reply = try await client.send(request, responseTimeout: timeout)\n        guard let appRootValue = reply.string(key: .appRoot), let appRoot = URL(string: appRootValue) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode appRoot in health check\")\n        }\n        guard let installRootValue = reply.string(key: .installRoot), let installRoot = URL(string: installRootValue) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode installRoot in health check\")\n        }\n        let logRoot = reply.string(key: .logRoot).map { FilePath($0) }\n        guard let apiServerVersion = reply.string(key: .apiServerVersion) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode apiServerVersion in health check\")\n        }\n        guard let apiServerCommit = reply.string(key: .apiServerCommit) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode apiServerCommit in health check\")\n        }\n        guard let apiServerBuild = reply.string(key: .apiServerBuild) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode apiServerBuild in health check\")\n        }\n        guard let apiServerAppName = reply.string(key: .apiServerAppName) else {\n            throw ContainerizationError(.internalError, message: \"failed to decode apiServerAppName in health check\")\n        }\n        return .init(\n            appRoot: appRoot,\n            installRoot: installRoot,\n            logRoot: logRoot,\n            apiServerVersion: apiServerVersion,\n            apiServerCommit: apiServerCommit,\n            apiServerBuild: apiServerBuild,\n            apiServerAppName: apiServerAppName\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientImage.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerImagesServiceClient\nimport ContainerPersistence\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport TerminalProgress\n\n// MARK: ClientImage structure\n\npublic struct ClientImage: Sendable {\n    private let contentStore: ContentStore = RemoteContentStoreClient()\n    public let description: ImageDescription\n\n    public var digest: String { description.digest }\n    public var descriptor: Descriptor { description.descriptor }\n    public var reference: String { description.reference }\n\n    public init(description: ImageDescription) {\n        self.description = description\n    }\n\n    /// Returns the underlying OCI index for the image.\n    public func index() async throws -> Index {\n        guard let content: Content = try await contentStore.get(digest: description.digest) else {\n            throw ContainerizationError(.notFound, message: \"content with digest \\(description.digest)\")\n        }\n        return try content.decode()\n    }\n\n    /// Returns the manifest for the specified platform.\n    public func manifest(for platform: Platform) async throws -> Manifest {\n        let index = try await self.index()\n        let desc = index.manifests.first { desc in\n            desc.platform == platform\n        }\n        guard let desc else {\n            throw ContainerizationError(.unsupported, message: \"platform \\(platform.description)\")\n        }\n        guard let content: Content = try await contentStore.get(digest: desc.digest) else {\n            throw ContainerizationError(.notFound, message: \"content with digest \\(desc.digest)\")\n        }\n        return try content.decode()\n    }\n\n    /// Returns the OCI config for the specified platform.\n    public func config(for platform: Platform) async throws -> ContainerizationOCI.Image {\n        let manifest = try await self.manifest(for: platform)\n        let desc = manifest.config\n        guard let content: Content = try await contentStore.get(digest: desc.digest) else {\n            throw ContainerizationError(.notFound, message: \"content with digest \\(desc.digest)\")\n        }\n        return try content.decode()\n    }\n\n    /// Returns the resolved OCI descriptor for the image.\n    package func resolved() async throws -> Descriptor {\n        let index = try await self.index()\n        let indirect = index.annotations?[AnnotationKeys.containerizationIndexIndirect]\n        // If this is not an indirect index, return its own descriptor\n        guard let indirect, [\"1\", \"true\"].contains(indirect.lowercased()) else {\n            return self.descriptor\n        }\n        // For indirect indices, return the first (and only) manifest\n        guard let manifest = index.manifests.first else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to resolve indirect index \\(self.digest): no manifests found\"\n            )\n        }\n        return manifest\n    }\n}\n\n// MARK: ClientImage constants\n\nextension ClientImage {\n    private static let serviceIdentifier = \"com.apple.container.core.container-core-images\"\n    public static let initImageRef = DefaultsStore.get(key: .defaultInitImage)\n\n    private static func newXPCClient() -> XPCClient {\n        XPCClient(service: Self.serviceIdentifier)\n    }\n\n    private static func newRequest(_ route: ImagesServiceXPCRoute) -> XPCMessage {\n        XPCMessage(route: route)\n    }\n\n    private static var defaultRegistryDomain: String {\n        DefaultsStore.get(key: .defaultRegistryDomain)\n    }\n}\n\n// MARK: Static methods\n\nextension ClientImage {\n    private static let legacyDockerRegistryHost = \"docker.io\"\n    private static let dockerRegistryHost = \"registry-1.docker.io\"\n    private static let defaultDockerRegistryRepo = \"library\"\n\n    public static func normalizeReference(_ ref: String) throws -> String {\n        guard ref != Self.initImageRef else {\n            // Don't modify the default init image reference.\n            // This is to allow for easier local development against\n            // an updated containerization.\n            return ref\n        }\n        // Check if the input reference has a domain specified\n        var updatedRawReference: String = ref\n        let r = try Reference.parse(ref)\n        if r.domain == nil {\n            updatedRawReference = \"\\(Self.defaultRegistryDomain)/\\(ref)\"\n        }\n\n        let updatedReference = try Reference.parse(updatedRawReference)\n\n        // Handle adding the :latest tag if it isn't specified,\n        // as well as adding the \"library/\" repository if it isn't set only if the host is docker.io\n        updatedReference.normalize()\n        return updatedReference.description\n    }\n\n    public static func denormalizeReference(_ ref: String) throws -> String {\n        var updatedRawReference: String = ref\n        let r = try Reference.parse(ref)\n        let defaultRegistry = Self.defaultRegistryDomain\n        if r.domain == defaultRegistry {\n            updatedRawReference = \"\\(r.path)\"\n            if let tag = r.tag {\n                updatedRawReference += \":\\(tag)\"\n            } else if let digest = r.digest {\n                updatedRawReference += \"@\\(digest)\"\n            }\n            if defaultRegistry == dockerRegistryHost || defaultRegistry == legacyDockerRegistryHost {\n                updatedRawReference.trimPrefix(\"\\(defaultDockerRegistryRepo)/\")\n            }\n        }\n        return updatedRawReference\n    }\n\n    public static func list() async throws -> [ClientImage] {\n        let client = newXPCClient()\n        let request = newRequest(.imageList)\n        let response = try await client.send(request)\n\n        let imageDescriptions = try response.imageDescriptions()\n        return imageDescriptions.map { desc in\n            ClientImage(description: desc)\n        }\n    }\n\n    public static func get(names: [String]) async throws -> (images: [ClientImage], error: [String]) {\n        let all = try await self.list()\n        var errors: [String] = []\n        var found: [ClientImage] = []\n        for name in names {\n            do {\n                guard let img = try Self._search(reference: name, in: all) else {\n                    errors.append(name)\n                    continue\n                }\n                found.append(img)\n            } catch {\n                errors.append(name)\n            }\n        }\n        return (found, errors)\n    }\n\n    public static func get(reference: String) async throws -> ClientImage {\n        let all = try await self.list()\n        guard let found = try self._search(reference: reference, in: all) else {\n            throw ContainerizationError(.notFound, message: \"image with reference \\(reference)\")\n        }\n        return found\n    }\n\n    /// Returns the total size of an image in bytes.\n    /// - Parameter image: The image to get the size for.\n    /// - Returns: The full image size in bytes.\n    /// - Throws: An error if the image cannot be retrieved.\n    public static func getFullImageSize(image: ClientImage) async throws -> Int64 {\n        for descriptor in try await image.index().manifests {\n            if let referenceType = descriptor.annotations?[\"vnd.docker.reference.type\"],\n                referenceType == \"attestation-manifest\"\n            {\n                continue\n            }\n\n            guard let platform = descriptor.platform else {\n                continue\n            }\n\n            do {\n                let manifest = try await image.manifest(for: platform)\n                return\n                    descriptor.size + manifest.config.size + manifest.layers.reduce(0) { $0 + $1.size }\n            } catch {\n                continue\n            }\n        }\n        return 0\n    }\n\n    private static func _search(reference: String, in all: [ClientImage]) throws -> ClientImage? {\n        let locallyBuiltImage = try {\n            // Check if we have an image whose index descriptor contains the image name\n            // as an annotation. Prefer this in all cases, since these are locally built images.\n            let r = try Reference.parse(reference)\n            r.normalize()\n            let withDefaultTag = r.description\n\n            let localImageMatches = all.filter { $0.description.nameFromAnnotation() == withDefaultTag }\n            guard localImageMatches.count > 1 else {\n                return localImageMatches.first\n            }\n            // More than one image matched. Check against the tagged reference\n            return localImageMatches.first { $0.reference == withDefaultTag }\n        }()\n        if let locallyBuiltImage {\n            return locallyBuiltImage\n        }\n        // If we don't find a match, try matching `ImageDescription.name` against the given\n        // input string, while also checking against its normalized form.\n        // Return the first match.\n        let normalizedReference = try Self.normalizeReference(reference)\n        return all.first(where: { image in\n            image.reference == reference || image.reference == normalizedReference\n        })\n    }\n\n    public static func pull(\n        reference: String, platform: Platform? = nil, scheme: RequestScheme = .auto, progressUpdate: ProgressUpdateHandler? = nil, maxConcurrentDownloads: Int = 3\n    ) async throws -> ClientImage {\n        guard maxConcurrentDownloads > 0 else {\n            throw ContainerizationError(.invalidArgument, message: \"maximum number of concurrent downloads must be greater than 0, got \\(maxConcurrentDownloads)\")\n        }\n\n        let client = newXPCClient()\n        let request = newRequest(.imagePull)\n\n        let reference = try self.normalizeReference(reference)\n        guard let host = try Reference.parse(reference).domain else {\n            throw ContainerizationError(.invalidArgument, message: \"could not extract host from reference \\(reference)\")\n        }\n\n        request.set(key: .imageReference, value: reference)\n        try request.set(platform: platform)\n\n        let insecure = try scheme.schemeFor(host: host) == .http\n        request.set(key: .insecureFlag, value: insecure)\n        request.set(key: .maxConcurrentDownloads, value: Int64(maxConcurrentDownloads))\n\n        var progressUpdateClient: ProgressUpdateClient?\n        if let progressUpdate {\n            progressUpdateClient = await ProgressUpdateClient(for: progressUpdate, request: request)\n        }\n\n        let response = try await client.send(request)\n        let description = try response.imageDescription()\n        let image = ClientImage(description: description)\n\n        await progressUpdateClient?.finish()\n        return image\n    }\n\n    public static func delete(reference: String, garbageCollect: Bool = false) async throws {\n        let client = newXPCClient()\n        let request = newRequest(.imageDelete)\n        request.set(key: .imageReference, value: reference)\n        request.set(key: .garbageCollect, value: garbageCollect)\n        let _ = try await client.send(request)\n    }\n\n    public static func save(references: [String], out: String, platform: Platform? = nil) async throws {\n        let (clientImages, errors) = try await get(names: references)\n        guard errors.isEmpty else {\n            // TODO: Improve error handling here\n            throw ContainerizationError(.invalidArgument, message: \"one or more image references are invalid: \\(errors.joined(separator: \", \"))\")\n        }\n\n        let descriptions = clientImages.map { $0.description }\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.imageSave)\n        try request.set(descriptions: descriptions)\n        request.set(key: .filePath, value: out)\n        try request.set(platform: platform)\n        let _ = try await client.send(request)\n    }\n\n    public static func load(from tarFile: String, force: Bool = false) async throws -> ImageLoadResult {\n        let client = newXPCClient()\n        let request = newRequest(.imageLoad)\n        request.set(key: .filePath, value: tarFile)\n        request.set(key: .forceLoad, value: force)\n        let reply = try await client.send(request)\n\n        let (descriptions, rejectedMembers) = try reply.loadResults()\n        let images = descriptions.map { desc in\n            ClientImage(description: desc)\n        }\n        return ImageLoadResult(images: images, rejectedMembers: rejectedMembers)\n    }\n\n    public static func cleanUpOrphanedBlobs() async throws -> ([String], UInt64) {\n        let client = newXPCClient()\n        let request = newRequest(.imageCleanupOrphanedBlobs)\n        let response = try await client.send(request)\n        let digests = try response.digests()\n        let size = response.uint64(key: .imageSize)\n        return (digests, size)\n    }\n\n    /// Calculate disk usage for images\n    /// - Parameter activeReferences: Set of image references currently in use by containers\n    /// - Returns: Tuple of (total count, active count, total size, reclaimable size)\n    public static func calculateDiskUsage(activeReferences: Set<String>) async throws -> (totalCount: Int, activeCount: Int, totalSize: UInt64, reclaimableSize: UInt64) {\n        let client = newXPCClient()\n        let request = newRequest(.imageDiskUsage)\n\n        // Encode active references\n        let activeRefsData = try JSONEncoder().encode(activeReferences)\n        request.set(key: .activeImageReferences, value: activeRefsData)\n\n        let response = try await client.send(request)\n        let total = Int(response.int64(key: .totalCount))\n        let active = Int(response.int64(key: .activeCount))\n        let size = response.uint64(key: .imageSize)\n        let reclaimable = response.uint64(key: .reclaimableSize)\n\n        return (totalCount: total, activeCount: active, totalSize: size, reclaimableSize: reclaimable)\n    }\n\n    public static func fetch(\n        reference: String, platform: Platform? = nil, scheme: RequestScheme = .auto, progressUpdate: ProgressUpdateHandler? = nil, maxConcurrentDownloads: Int = 3\n    ) async throws -> ClientImage {\n        do {\n            let match = try await self.get(reference: reference)\n            if let platform {\n                // The image exists, but we dont know if we have the right platform pulled\n                // Check if we do, if not pull the requested platform\n                _ = try await match.config(for: platform)\n            }\n            return match\n        } catch let err as ContainerizationError {\n            guard err.isCode(.notFound) else {\n                throw err\n            }\n            return try await Self.pull(reference: reference, platform: platform, scheme: scheme, progressUpdate: progressUpdate, maxConcurrentDownloads: maxConcurrentDownloads)\n        }\n    }\n}\n\n// MARK: Instance methods\n\nextension ClientImage {\n    public func push(platform: Platform? = nil, scheme: RequestScheme, progressUpdate: ProgressUpdateHandler?) async throws {\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.imagePush)\n\n        guard let host = try Reference.parse(reference).domain else {\n            throw ContainerizationError(.invalidArgument, message: \"could not extract host from reference \\(reference)\")\n        }\n        request.set(key: .imageReference, value: reference)\n\n        let insecure = try scheme.schemeFor(host: host) == .http\n        request.set(key: .insecureFlag, value: insecure)\n\n        try request.set(platform: platform)\n\n        var progressUpdateClient: ProgressUpdateClient?\n        if let progressUpdate {\n            progressUpdateClient = await ProgressUpdateClient(for: progressUpdate, request: request)\n        }\n        _ = try await client.send(request)\n        await progressUpdateClient?.finish()\n    }\n\n    @discardableResult\n    public func tag(new: String) async throws -> ClientImage {\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.imageTag)\n        request.set(key: .imageReference, value: self.description.reference)\n        request.set(key: .imageNewReference, value: new)\n        let reply = try await client.send(request)\n        let description = try reply.imageDescription()\n        return ClientImage(description: description)\n    }\n\n    // MARK: Snapshot Methods\n\n    public func unpack(platform: Platform?, progressUpdate: ProgressUpdateHandler? = nil) async throws {\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.imageUnpack)\n\n        try request.set(description: description)\n        try request.set(platform: platform)\n\n        var progressUpdateClient: ProgressUpdateClient?\n        if let progressUpdate {\n            progressUpdateClient = await ProgressUpdateClient(for: progressUpdate, request: request)\n        }\n\n        try await client.send(request)\n\n        await progressUpdateClient?.finish()\n    }\n\n    public func deleteSnapshot(platform: Platform?) async throws {\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.snapshotDelete)\n\n        try request.set(description: description)\n        try request.set(platform: platform)\n\n        try await client.send(request)\n    }\n\n    public func getSnapshot(platform: Platform) async throws -> Filesystem {\n        let client = Self.newXPCClient()\n        let request = Self.newRequest(.snapshotGet)\n\n        try request.set(description: description)\n        try request.set(platform: platform)\n\n        let response = try await client.send(request)\n        let fs = try response.filesystem()\n        return fs\n    }\n\n    @discardableResult\n    public func getCreateSnapshot(platform: Platform, progressUpdate: ProgressUpdateHandler? = nil) async throws -> Filesystem {\n        do {\n            return try await self.getSnapshot(platform: platform)\n        } catch let err as ContainerizationError {\n            guard err.code == .notFound else {\n                throw err\n            }\n            try await self.unpack(platform: platform, progressUpdate: progressUpdate)\n            return try await self.getSnapshot(platform: platform)\n        }\n    }\n}\n\nextension XPCMessage {\n    fileprivate func set(description: ImageDescription) throws {\n        let descData = try JSONEncoder().encode(description)\n        self.set(key: .imageDescription, value: descData)\n    }\n\n    fileprivate func set(descriptions: [ImageDescription]) throws {\n        let descData = try JSONEncoder().encode(descriptions)\n        self.set(key: .imageDescriptions, value: descData)\n    }\n\n    fileprivate func set(platform: Platform?) throws {\n        guard let platform else {\n            return\n        }\n        let platformData = try JSONEncoder().encode(platform)\n        self.set(key: .ociPlatform, value: platformData)\n    }\n\n    fileprivate func imageDescription() throws -> ImageDescription {\n        let responseData = self.dataNoCopy(key: .imageDescription)\n        guard let responseData else {\n            throw ContainerizationError(.empty, message: \"imageDescription not received\")\n        }\n        let description = try JSONDecoder().decode(ImageDescription.self, from: responseData)\n        return description\n    }\n\n    fileprivate func imageDescriptions() throws -> [ImageDescription] {\n        let imagesData = self.dataNoCopy(key: .imageDescriptions)\n        guard let imagesData else {\n            throw ContainerizationError(.empty, message: \"imageDescriptions not received\")\n        }\n        let descriptions = try JSONDecoder().decode([ImageDescription].self, from: imagesData)\n        return descriptions\n    }\n\n    fileprivate func loadResults() throws -> ([ImageDescription], [String]) {\n        let imagesData = self.dataNoCopy(key: .imageDescriptions)\n        guard let imagesData else {\n            throw ContainerizationError(.empty, message: \"imageDescriptions not received\")\n        }\n        let descriptions = try JSONDecoder().decode([ImageDescription].self, from: imagesData)\n        let rejectedMembersData = self.dataNoCopy(key: .rejectedMembers)\n        guard let rejectedMembersData else {\n            throw ContainerizationError(.empty, message: \"rejectedMembers not received\")\n        }\n        let rejectedMembers = try JSONDecoder().decode([String].self, from: rejectedMembersData)\n        return (descriptions, rejectedMembers)\n    }\n\n    fileprivate func filesystem() throws -> Filesystem {\n        let responseData = self.dataNoCopy(key: .filesystem)\n        guard let responseData else {\n            throw ContainerizationError(.empty, message: \"filesystem not received\")\n        }\n        let fs = try JSONDecoder().decode(Filesystem.self, from: responseData)\n        return fs\n    }\n\n    fileprivate func digests() throws -> [String] {\n        let responseData = self.dataNoCopy(key: .digests)\n        guard let responseData else {\n            throw ContainerizationError(.empty, message: \"digests not received\")\n        }\n        let digests = try JSONDecoder().decode([String].self, from: responseData)\n        return digests\n    }\n}\n\nextension ImageDescription {\n    fileprivate func nameFromAnnotation() -> String? {\n        guard let annotations = self.descriptor.annotations else {\n            return nil\n        }\n        guard let name = annotations[AnnotationKeys.containerizationImageName] else {\n            return nil\n        }\n        return name\n    }\n}\n\nextension ClientImage {\n    public func details() async throws -> ImageDetail {\n        let descriptor = try await self.resolved()\n        let reference = self.reference\n        var variants: [ImageDetail.Variants] = []\n        for desc in try await self.index().manifests {\n            guard let platform = desc.platform else {\n                continue\n            }\n            let config: ContainerizationOCI.Image\n            let manifest: ContainerizationOCI.Manifest\n            do {\n                config = try await self.config(for: platform)\n                manifest = try await self.manifest(for: platform)\n            } catch {\n                continue\n            }\n            let size = desc.size + manifest.config.size + manifest.layers.reduce(0, { (l, r) in l + r.size })\n            variants.append(.init(platform: platform, size: size, config: config))\n        }\n        return ImageDetail(name: reference, index: descriptor, variants: variants)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientKernel.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\nimport TerminalProgress\n\npublic struct ClientKernel {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n}\n\nextension ClientKernel {\n    private static func newClient() -> XPCClient {\n        XPCClient(service: serviceIdentifier)\n    }\n\n    public static func installKernel(kernelFilePath: String, platform: SystemPlatform, force: Bool) async throws {\n        let client = newClient()\n        let message = XPCMessage(route: .installKernel)\n\n        message.set(key: .kernelFilePath, value: kernelFilePath)\n        message.set(key: .kernelForce, value: force)\n\n        let platformData = try JSONEncoder().encode(platform)\n        message.set(key: .systemPlatform, value: platformData)\n        try await client.send(message)\n    }\n\n    public static func installKernelFromTar(tarFile: String, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler? = nil, force: Bool)\n        async throws\n    {\n        let client = newClient()\n        let message = XPCMessage(route: .installKernel)\n\n        message.set(key: .kernelTarURL, value: tarFile)\n        message.set(key: .kernelFilePath, value: kernelFilePath)\n        message.set(key: .kernelForce, value: force)\n\n        let platformData = try JSONEncoder().encode(platform)\n        message.set(key: .systemPlatform, value: platformData)\n\n        var progressUpdateClient: ProgressUpdateClient?\n        if let progressUpdate {\n            progressUpdateClient = await ProgressUpdateClient(for: progressUpdate, request: message)\n        }\n\n        try await client.send(message)\n        await progressUpdateClient?.finish()\n    }\n\n    @discardableResult\n    public static func getDefaultKernel(for platform: SystemPlatform) async throws -> Kernel {\n        let client = newClient()\n        let message = XPCMessage(route: .getDefaultKernel)\n\n        let platformData = try JSONEncoder().encode(platform)\n        message.set(key: .systemPlatform, value: platformData)\n        do {\n            let reply = try await client.send(message)\n            guard let kData = reply.dataNoCopy(key: .kernel) else {\n                throw ContainerizationError(.internalError, message: \"missing kernel data from XPC response\")\n            }\n\n            let kernel = try JSONDecoder().decode(Kernel.self, from: kData)\n            return kernel\n        } catch let err as ContainerizationError {\n            guard err.isCode(.notFound) else {\n                throw err\n            }\n            throw ContainerizationError(\n                .notFound, message: \"default kernel not configured for architecture \\(platform.architecture), please use the `container system kernel set` command to configure it\")\n        }\n    }\n}\n\nextension SystemPlatform {\n    public static var current: SystemPlatform {\n        switch Platform.current.architecture {\n        case \"arm64\":\n            return .linuxArm\n        case \"amd64\":\n            return .linuxAmd\n        default:\n            fatalError(\"unknown architecture\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientNetwork.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\n\npublic struct ClientNetwork {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n\n    public static let defaultNetworkName = \"default\"\n    public static let noNetworkName = \"none\"\n}\n\nextension ClientNetwork {\n    private static func newClient() -> XPCClient {\n        XPCClient(service: serviceIdentifier)\n    }\n\n    private static func xpcSend(\n        client: XPCClient,\n        message: XPCMessage,\n        timeout: Duration? = XPCClient.xpcRegistrationTimeout\n    ) async throws -> XPCMessage {\n        try await client.send(message, responseTimeout: timeout)\n    }\n\n    public static func create(configuration: NetworkConfiguration) async throws -> NetworkState {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .networkCreate)\n        request.set(key: .networkId, value: configuration.id)\n\n        let data = try JSONEncoder().encode(configuration)\n        request.set(key: .networkConfig, value: data)\n\n        let response = try await xpcSend(client: client, message: request)\n        let responseData = response.dataNoCopy(key: .networkState)\n        guard let responseData else {\n            throw ContainerizationError(.invalidArgument, message: \"network configuration not received\")\n        }\n        let state = try JSONDecoder().decode(NetworkState.self, from: responseData)\n        return state\n    }\n\n    public static func list() async throws -> [NetworkState] {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .networkList)\n\n        let response = try await xpcSend(client: client, message: request, timeout: .seconds(1))\n        let responseData = response.dataNoCopy(key: .networkStates)\n        guard let responseData else {\n            return []\n        }\n        let states = try JSONDecoder().decode([NetworkState].self, from: responseData)\n        return states\n    }\n\n    /// Get the network for the provided id.\n    public static func get(id: String) async throws -> NetworkState {\n        let networks = try await list()\n        guard let network = networks.first(where: { $0.id == id }) else {\n            throw ContainerizationError(.notFound, message: \"network \\(id) not found\")\n        }\n        return network\n    }\n\n    /// Delete the network with the given id.\n    public static func delete(id: String) async throws {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .networkDelete)\n        request.set(key: .networkId, value: id)\n        let _ = try await xpcSend(client: client, message: request)\n    }\n\n    /// Retrieve the builtin network.\n    public static var builtin: NetworkState? {\n        get async throws {\n            try await list().first { $0.isBuiltin }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientProcess.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport NIOCore\nimport NIOPosix\nimport TerminalProgress\n\n/// A protocol that defines the methods and data members available to a process\n/// started inside of a container.\npublic protocol ClientProcess: Sendable {\n    /// Identifier for the process.\n    var id: String { get }\n\n    /// Start the underlying process inside of the container.\n    func start() async throws\n    /// Send a terminal resize request to the process `id`.\n    func resize(_ size: Terminal.Size) async throws\n    /// Send a signal to the process `id`.\n    /// Kill does not wait for the process to exit, it only delivers the signal.\n    func kill(_ signal: Int32) async throws\n    ///  Wait for the process `id` to complete and return its exit code.\n    /// This method blocks until the process exits and the code is obtained.\n    func wait() async throws -> Int32\n}\n\nstruct ClientProcessImpl: ClientProcess, Sendable {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n\n    /// ID of the process.\n    public var id: String {\n        processId ?? containerId\n    }\n\n    /// Identifier of the container.\n    public let containerId: String\n\n    /// Identifier of a process. That is running inside of a container.\n    /// This field is nil if the process this objects refers to is the\n    /// init process of the container.\n    public let processId: String?\n\n    private let xpcClient: XPCClient\n\n    init(containerId: String, processId: String? = nil, xpcClient: XPCClient) {\n        self.containerId = containerId\n        self.processId = processId\n        self.xpcClient = xpcClient\n    }\n\n    /// Start the process.\n    public func start() async throws {\n        let request = XPCMessage(route: .containerStartProcess)\n        request.set(key: .id, value: containerId)\n        request.set(key: .processIdentifier, value: id)\n\n        try await xpcClient.send(request)\n    }\n\n    /// Send a signal to the process.\n    public func kill(_ signal: Int32) async throws {\n        let request = XPCMessage(route: .containerKill)\n        request.set(key: .id, value: containerId)\n        request.set(key: .processIdentifier, value: id)\n        request.set(key: .signal, value: Int64(signal))\n\n        try await xpcClient.send(request)\n    }\n\n    /// Resize the processes PTY if it has one.\n    public func resize(_ size: Terminal.Size) async throws {\n        let request = XPCMessage(route: .containerResize)\n        request.set(key: .id, value: containerId)\n        request.set(key: .processIdentifier, value: id)\n        request.set(key: .width, value: UInt64(size.width))\n        request.set(key: .height, value: UInt64(size.height))\n\n        try await xpcClient.send(request)\n    }\n\n    /// Wait for the process to exit.\n    public func wait() async throws -> Int32 {\n        let request = XPCMessage(route: .containerWait)\n        request.set(key: .id, value: containerId)\n        request.set(key: .processIdentifier, value: id)\n\n        let response = try await xpcClient.send(request)\n        let code = response.int64(key: .exitCode)\n        return Int32(code)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ClientVolume.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport Foundation\n\npublic struct ClientVolume {\n    static let serviceIdentifier = \"com.apple.container.apiserver\"\n\n    public static func create(\n        name: String,\n        driver: String = \"local\",\n        driverOpts: [String: String] = [:],\n        labels: [String: String] = [:]\n    ) async throws -> Volume {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .volumeCreate)\n        message.set(key: .volumeName, value: name)\n        message.set(key: .volumeDriver, value: driver)\n\n        let driverOptsData = try JSONEncoder().encode(driverOpts)\n        message.set(key: .volumeDriverOpts, value: driverOptsData)\n\n        let labelsData = try JSONEncoder().encode(labels)\n        message.set(key: .volumeLabels, value: labelsData)\n\n        let reply = try await client.send(message)\n\n        guard let responseData = reply.dataNoCopy(key: .volume) else {\n            throw VolumeError.storageError(\"invalid response from server\")\n        }\n\n        return try JSONDecoder().decode(Volume.self, from: responseData)\n    }\n\n    public static func delete(name: String) async throws {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .volumeDelete)\n        message.set(key: .volumeName, value: name)\n\n        _ = try await client.send(message)\n    }\n\n    public static func list() async throws -> [Volume] {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .volumeList)\n        let reply = try await client.send(message)\n\n        guard let responseData = reply.dataNoCopy(key: .volumes) else {\n            return []\n        }\n\n        return try JSONDecoder().decode([Volume].self, from: responseData)\n    }\n\n    public static func inspect(_ name: String) async throws -> Volume {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .volumeInspect)\n        message.set(key: .volumeName, value: name)\n\n        let reply = try await client.send(message)\n\n        guard let responseData = reply.dataNoCopy(key: .volume) else {\n            throw VolumeError.volumeNotFound(name)\n        }\n\n        return try JSONDecoder().decode(Volume.self, from: responseData)\n    }\n\n    public static func volumeDiskUsage(name: String) async throws -> UInt64 {\n        let client = XPCClient(service: serviceIdentifier)\n        let message = XPCMessage(route: .volumeDiskUsage)\n        message.set(key: .volumeName, value: name)\n        let reply = try await client.send(message)\n\n        let size = reply.uint64(key: .volumeSize)\n        return size\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Constants.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// Global constants for the container API clients.\npublic enum Constants {\n    /// The keychain ID to use for registry credentials.\n    public static let keychainID = \"com.apple.container.registry\"\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ContainerClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\n\n/// A client for interacting with the container API server.\n///\n/// This client holds a reusable XPC connection and provides methods for\n/// container lifecycle operations. All methods that operate on a specific\n/// container take an `id` parameter.\npublic struct ContainerClient: Sendable {\n    private static let serviceIdentifier = \"com.apple.container.apiserver\"\n\n    private let xpcClient: XPCClient\n\n    /// Creates a new container client with a connection to the API server.\n    public init() {\n        self.xpcClient = XPCClient(service: Self.serviceIdentifier)\n    }\n\n    @discardableResult\n    private func xpcSend(\n        message: XPCMessage,\n        timeout: Duration? = XPCClient.xpcRegistrationTimeout\n    ) async throws -> XPCMessage {\n        try await xpcClient.send(message, responseTimeout: timeout)\n    }\n\n    /// Create a new container with the given configuration.\n    public func create(\n        configuration: ContainerConfiguration,\n        options: ContainerCreateOptions = .default,\n        kernel: Kernel,\n        initImage: String? = nil\n    ) async throws {\n        do {\n            let request = XPCMessage(route: .containerCreate)\n\n            let data = try JSONEncoder().encode(configuration)\n            let kdata = try JSONEncoder().encode(kernel)\n            let odata = try JSONEncoder().encode(options)\n            request.set(key: .containerConfig, value: data)\n            request.set(key: .kernel, value: kdata)\n            request.set(key: .containerOptions, value: odata)\n\n            if let initImage {\n                request.set(key: .initImage, value: initImage)\n            }\n\n            try await xpcSend(message: request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to create container\",\n                cause: error\n            )\n        }\n    }\n\n    /// List containers matching the given filters.\n    public func list(filters: ContainerListFilters = .all) async throws -> [ContainerSnapshot] {\n        do {\n            let request = XPCMessage(route: .containerList)\n            let filterData = try JSONEncoder().encode(filters)\n            request.set(key: .listFilters, value: filterData)\n\n            let response = try await xpcSend(\n                message: request,\n                timeout: .seconds(10)\n            )\n            let data = response.dataNoCopy(key: .containers)\n            guard let data else {\n                return []\n            }\n            return try JSONDecoder().decode([ContainerSnapshot].self, from: data)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to list containers\",\n                cause: error\n            )\n        }\n    }\n\n    /// Get the container for the provided id.\n    public func get(id: String) async throws -> ContainerSnapshot {\n        let containers = try await list(filters: ContainerListFilters(ids: [id]))\n        guard let container = containers.first else {\n            throw ContainerizationError(\n                .notFound,\n                message: \"get failed: container \\(id) not found\"\n            )\n        }\n        return container\n    }\n\n    /// Bootstrap the container's init process.\n    public func bootstrap(id: String, stdio: [FileHandle?]) async throws -> ClientProcess {\n        let request = XPCMessage(route: .containerBootstrap)\n\n        for (i, h) in stdio.enumerated() {\n            let key: XPCKeys = try {\n                switch i {\n                case 0: .stdin\n                case 1: .stdout\n                case 2: .stderr\n                default:\n                    throw ContainerizationError(.invalidArgument, message: \"invalid fd \\(i)\")\n                }\n            }()\n\n            if let h {\n                request.set(key: key, value: h)\n            }\n        }\n\n        do {\n            request.set(key: .id, value: id)\n            try await xpcClient.send(request)\n            return ClientProcessImpl(containerId: id, xpcClient: xpcClient)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to bootstrap container\",\n                cause: error\n            )\n        }\n    }\n\n    /// Send a signal to the container.\n    public func kill(id: String, signal: Int32) async throws {\n        do {\n            let request = XPCMessage(route: .containerKill)\n            request.set(key: .id, value: id)\n            request.set(key: .processIdentifier, value: id)\n            request.set(key: .signal, value: Int64(signal))\n\n            try await xpcClient.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to kill container\",\n                cause: error\n            )\n        }\n    }\n\n    /// Stop the container and all processes currently executing inside.\n    public func stop(id: String, opts: ContainerStopOptions = ContainerStopOptions.default) async throws {\n        do {\n            let request = XPCMessage(route: .containerStop)\n            let data = try JSONEncoder().encode(opts)\n            request.set(key: .id, value: id)\n            request.set(key: .stopOptions, value: data)\n\n            try await xpcClient.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to stop container\",\n                cause: error\n            )\n        }\n    }\n\n    /// Delete the container along with any resources.\n    public func delete(id: String, force: Bool = false) async throws {\n        do {\n            let request = XPCMessage(route: .containerDelete)\n            request.set(key: .id, value: id)\n            request.set(key: .forceDelete, value: force)\n            try await xpcClient.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to delete container\",\n                cause: error\n            )\n        }\n    }\n\n    /// Get the disk usage for a container.\n    public func diskUsage(id: String) async throws -> UInt64 {\n        let request = XPCMessage(route: .containerDiskUsage)\n        request.set(key: .id, value: id)\n        let reply = try await xpcClient.send(request)\n\n        let size = reply.uint64(key: .containerSize)\n        return size\n    }\n\n    /// Create a new process inside a running container.\n    /// The process is in a created state and must still be started.\n    public func createProcess(\n        containerId: String,\n        processId: String,\n        configuration: ProcessConfiguration,\n        stdio: [FileHandle?]\n    ) async throws -> ClientProcess {\n        do {\n            let request = XPCMessage(route: .containerCreateProcess)\n            request.set(key: .id, value: containerId)\n            request.set(key: .processIdentifier, value: processId)\n\n            let data = try JSONEncoder().encode(configuration)\n            request.set(key: .processConfig, value: data)\n\n            for (i, h) in stdio.enumerated() {\n                let key: XPCKeys = try {\n                    switch i {\n                    case 0: .stdin\n                    case 1: .stdout\n                    case 2: .stderr\n                    default:\n                        throw ContainerizationError(.invalidArgument, message: \"invalid fd \\(i)\")\n                    }\n                }()\n\n                if let h {\n                    request.set(key: key, value: h)\n                }\n            }\n\n            try await xpcClient.send(request)\n            return ClientProcessImpl(containerId: containerId, processId: processId, xpcClient: xpcClient)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to create process in container\",\n                cause: error\n            )\n        }\n    }\n\n    /// Get the log file handles for a container.\n    public func logs(id: String) async throws -> [FileHandle] {\n        do {\n            let request = XPCMessage(route: .containerLogs)\n            request.set(key: .id, value: id)\n\n            let response = try await xpcClient.send(request)\n            let fds = response.fileHandles(key: .logs)\n            guard let fds else {\n                throw ContainerizationError(\n                    .internalError,\n                    message: \"no log fds returned\"\n                )\n            }\n            return fds\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get logs for container \\(id)\",\n                cause: error\n            )\n        }\n    }\n\n    /// Dial a port on the container via vsock.\n    public func dial(id: String, port: UInt32) async throws -> FileHandle {\n        let request = XPCMessage(route: .containerDial)\n        request.set(key: .id, value: id)\n        request.set(key: .port, value: UInt64(port))\n\n        let response: XPCMessage\n        do {\n            response = try await xpcClient.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to dial port \\(port) on container\",\n                cause: error\n            )\n        }\n        guard let fh = response.fileHandle(key: .fd) else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get fd for vsock port \\(port)\"\n            )\n        }\n        return fh\n    }\n\n    /// Get resource usage statistics for a container.\n    public func stats(id: String) async throws -> ContainerStats {\n        let request = XPCMessage(route: .containerStats)\n        request.set(key: .id, value: id)\n\n        do {\n            let response = try await xpcClient.send(request)\n            guard let data = response.dataNoCopy(key: .statistics) else {\n                throw ContainerizationError(\n                    .internalError,\n                    message: \"no statistics data returned\"\n                )\n            }\n            return try JSONDecoder().decode(ContainerStats.self, from: data)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get statistics for container \\(id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func export(id: String, archive: URL) async throws {\n        let request = XPCMessage(route: .containerExport)\n        request.set(key: .id, value: id)\n        request.set(key: .archive, value: archive.absolutePath())\n\n        do {\n            try await xpcClient.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to export container\",\n                cause: error\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ContainerizationProgressAdapter.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport TerminalProgress\n\npublic enum ContainerizationProgressAdapter: ProgressAdapter {\n    public static func handler(from progressUpdate: ProgressUpdateHandler?) -> ProgressHandler? {\n        guard let progressUpdate else {\n            return nil\n        }\n        return { events in\n            var updateEvents = [ProgressUpdateEvent]()\n            for event in events {\n                if event.event == \"add-items\" {\n                    if let items = event.value as? Int {\n                        updateEvents.append(.addItems(items))\n                    }\n                } else if event.event == \"add-total-items\" {\n                    if let totalItems = event.value as? Int {\n                        updateEvents.append(.addTotalItems(totalItems))\n                    }\n                } else if event.event == \"add-size\" {\n                    if let size = event.value as? Int64 {\n                        updateEvents.append(.addSize(size))\n                    }\n                } else if event.event == \"add-total-size\" {\n                    if let totalSize = event.value as? Int64 {\n                        updateEvents.append(.addTotalSize(totalSize))\n                    }\n                }\n            }\n            await progressUpdate(updateEvents)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/DefaultPlatform.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\nimport Logging\n\n/// Resolves the default platform from the `CONTAINER_DEFAULT_PLATFORM` environment variable.\n///\n/// When set, this variable overrides the native platform as the default for commands\n/// that support `--platform`. Explicit `--platform` flags always take precedence.\npublic enum DefaultPlatform {\n    /// The name of the environment variable checked for a default platform.\n    public static let environmentVariable = \"CONTAINER_DEFAULT_PLATFORM\"\n\n    /// Reads and parses the `CONTAINER_DEFAULT_PLATFORM` environment variable.\n    ///\n    /// When a valid platform is found and a logger is provided, a warning is emitted\n    /// to inform the user that the environment variable is being used.\n    ///\n    /// - Parameters:\n    ///   - environment: The environment dictionary to read from. Defaults to the current process environment.\n    ///   - log: An optional logger. When provided, a warning is logged if the environment variable is active.\n    /// - Returns: The parsed platform, or `nil` if the variable is not set or empty.\n    /// - Throws: ContainerizationError if the variable is set but contains an invalid platform string.\n    public static func fromEnvironment(\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        log: Logger? = nil\n    ) throws -> ContainerizationOCI.Platform? {\n        guard let value = environment[environmentVariable],\n            !value.isEmpty\n        else {\n            return nil\n        }\n        let platform: ContainerizationOCI.Platform\n        do {\n            platform = try ContainerizationOCI.Platform(from: value)\n        } catch {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"invalid platform \\\"\\(value)\\\" in \\(environmentVariable) environment variable\",\n                cause: error\n            )\n        }\n        logNotice(platform, log: log)\n        return platform\n    }\n\n    /// Resolves the platform for commands where `--os` and `--arch` are optional (image pull, push, save).\n    ///\n    /// Precedence: `--platform` > `--os`/`--arch` > `CONTAINER_DEFAULT_PLATFORM` > `nil`.\n    ///\n    /// - Parameters:\n    ///   - platform: The value of the `--platform` flag, if provided.\n    ///   - os: The value of the `--os` flag, if provided.\n    ///   - arch: The value of the `--arch` flag, if provided.\n    ///   - environment: The environment dictionary to read from. Defaults to the current process environment.\n    ///   - log: An optional logger for environment variable notices.\n    /// - Returns: The resolved platform, or `nil` if no platform information is available.\n    /// - Throws: ContainerizationError if a platform string (from flags or environment) is invalid.\n    public static func resolve(\n        platform: String?,\n        os: String?,\n        arch: String?,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        log: Logger? = nil\n    ) throws -> ContainerizationOCI.Platform? {\n        if let platform {\n            return try ContainerizationOCI.Platform(from: platform)\n        }\n        if let arch {\n            return try ContainerizationOCI.Platform(from: \"\\(os ?? \"linux\")/\\(arch)\")\n        }\n        if let os {\n            return try ContainerizationOCI.Platform(from: \"\\(os)/\\(arch ?? Arch.hostArchitecture().rawValue)\")\n        }\n        return try fromEnvironment(environment: environment, log: log)\n    }\n\n    /// Resolves the platform for commands where `--os` and `--arch` have defaults (run, create).\n    ///\n    /// Precedence: `--platform` > `CONTAINER_DEFAULT_PLATFORM` > `--os`/`--arch` defaults.\n    ///\n    /// - Parameters:\n    ///   - platform: The value of the `--platform` flag, if provided.\n    ///   - os: The default OS value (always present).\n    ///   - arch: The default architecture value (always present).\n    ///   - environment: The environment dictionary to read from. Defaults to the current process environment.\n    ///   - log: An optional logger for environment variable notices.\n    /// - Returns: The resolved platform. Always returns a value since os/arch defaults are provided.\n    /// - Throws: ContainerizationError if a platform string (from flags or environment) is invalid.\n    public static func resolveWithDefaults(\n        platform: String?,\n        os: String,\n        arch: String,\n        environment: [String: String] = ProcessInfo.processInfo.environment,\n        log: Logger? = nil\n    ) throws -> ContainerizationOCI.Platform {\n        if let platform {\n            return try Parser.platform(from: platform)\n        }\n        if let envPlatform = try fromEnvironment(environment: environment, log: log) {\n            return envPlatform\n        }\n        return Parser.platform(os: os, arch: arch)\n    }\n\n    private static func logNotice(_ platform: ContainerizationOCI.Platform, log: Logger?) {\n        guard let log else { return }\n        log.warning(\n            \"using platform from environment variable\",\n            metadata: [\n                \"platform\": \"\\(platform.description)\",\n                \"variable\": \"\\(environmentVariable)\",\n            ]\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/DiskUsage.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// Disk usage statistics for all resource types\npublic struct DiskUsageStats: Sendable, Codable {\n    /// Disk usage for images\n    public var images: ResourceUsage\n\n    /// Disk usage for containers\n    public var containers: ResourceUsage\n\n    /// Disk usage for volumes\n    public var volumes: ResourceUsage\n\n    public init(images: ResourceUsage, containers: ResourceUsage, volumes: ResourceUsage) {\n        self.images = images\n        self.containers = containers\n        self.volumes = volumes\n    }\n}\n\n/// Disk usage statistics for a specific resource type\npublic struct ResourceUsage: Sendable, Codable {\n    /// Total number of resources\n    public var total: Int\n\n    /// Number of active/running resources\n    public var active: Int\n\n    /// Total size in bytes\n    public var sizeInBytes: UInt64\n\n    /// Reclaimable size in bytes (from unused/inactive resources)\n    public var reclaimable: UInt64\n\n    public init(total: Int, active: Int, sizeInBytes: UInt64, reclaimable: UInt64) {\n        self.total = total\n        self.active = active\n        self.sizeInBytes = sizeInBytes\n        self.reclaimable = reclaimable\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/FileDownloader.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport AsyncHTTPClient\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport TerminalProgress\n\npublic struct FileDownloader {\n    public static func downloadFile(url: URL, to destination: URL, progressUpdate: ProgressUpdateHandler? = nil) async throws {\n        let request = try HTTPClient.Request(url: url)\n\n        let delegate = try FileDownloadDelegate(\n            path: destination.path(),\n            reportHead: {\n                let expectedSizeString = $0.headers[\"Content-Length\"].first ?? \"\"\n                if let expectedSize = Int64(expectedSizeString) {\n                    if let progressUpdate {\n                        Task {\n                            await progressUpdate([\n                                .addTotalSize(expectedSize)\n                            ])\n                        }\n                    }\n                }\n            },\n            reportProgress: {\n                let receivedBytes = Int64($0.receivedBytes)\n                if let progressUpdate {\n                    Task {\n                        await progressUpdate([\n                            .setSize(receivedBytes)\n                        ])\n                    }\n                }\n            })\n\n        let client = FileDownloader.createClient(url: url)\n        do {\n            _ = try await client.execute(request: request, delegate: delegate).get()\n        } catch {\n            try? await client.shutdown()\n            throw error\n        }\n        try await client.shutdown()\n    }\n\n    private static func createClient(url: URL) -> HTTPClient {\n        var httpConfiguration = HTTPClient.Configuration()\n        // for large file downloads we keep a generous connect timeout, and\n        // no read timeout since download durations can vary\n        httpConfiguration.timeout = HTTPClient.Configuration.Timeout(\n            connect: .seconds(30),\n            read: .none\n        )\n        if let host = url.host {\n            let proxyURL = ProxyUtils.proxyFromEnvironment(scheme: url.scheme, host: host)\n            if let proxyURL, let proxyHost = proxyURL.host {\n                httpConfiguration.proxy = HTTPClient.Configuration.Proxy.server(host: proxyHost, port: proxyURL.port ?? 8080)\n            }\n        }\n\n        return HTTPClient(eventLoopGroupProvider: .singleton, configuration: httpConfiguration)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Flags.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ArgumentParser\nimport ContainerizationError\nimport Foundation\n\npublic struct Flags {\n    public struct Logging: ParsableArguments {\n        public init() {}\n\n        public init(debug: Bool) {\n            self.debug = debug\n        }\n\n        @Flag(name: .long, help: \"Enable debug output [environment: CONTAINER_DEBUG]\")\n        public var debug = false\n    }\n\n    public struct Process: ParsableArguments {\n        public init() {}\n\n        public init(\n            cwd: String?,\n            env: [String],\n            envFile: [String],\n            gid: UInt32?,\n            interactive: Bool,\n            tty: Bool,\n            uid: UInt32?,\n            ulimits: [String],\n            user: String?\n        ) {\n            self.cwd = cwd\n            self.env = env\n            self.envFile = envFile\n            self.gid = gid\n            self.interactive = interactive\n            self.tty = tty\n            self.uid = uid\n            self.ulimits = ulimits\n            self.user = user\n        }\n\n        @Option(name: .shortAndLong, help: \"Set environment variables (key=value, or just key to inherit from host)\")\n        public var env: [String] = []\n\n        @Option(\n            name: .long,\n            help: \"Read in a file of environment variables (key=value format, ignores # comments and blank lines)\"\n        )\n        public var envFile: [String] = []\n\n        @Option(name: .long, help: \"Set the group ID for the process\")\n        public var gid: UInt32?\n\n        @Flag(name: .shortAndLong, help: \"Keep the standard input open even if not attached\")\n        public var interactive = false\n\n        @Flag(name: .shortAndLong, help: \"Open a TTY with the process\")\n        public var tty = false\n\n        @Option(name: .shortAndLong, help: \"Set the user for the process (format: name|uid[:gid])\")\n        public var user: String?\n\n        @Option(name: .long, help: \"Set the user ID for the process\")\n        public var uid: UInt32?\n\n        @Option(\n            name: [.customShort(\"w\"), .customLong(\"workdir\"), .long],\n            help: .init(\n                \"Set the initial working directory inside the container\",\n                valueName: \"dir\"\n            )\n        )\n        public var cwd: String?\n\n        @Option(\n            name: .customLong(\"ulimit\"),\n            help: .init(\n                \"Set resource limits (format: <type>=<soft>[:<hard>])\",\n                valueName: \"limit\"\n            )\n        )\n        public var ulimits: [String] = []\n    }\n\n    public struct Resource: ParsableArguments {\n        public init() {}\n\n        public init(cpus: Int64?, memory: String?) {\n            self.cpus = cpus\n            self.memory = memory\n        }\n\n        @Option(name: .shortAndLong, help: \"Number of CPUs to allocate to the container\")\n        public var cpus: Int64?\n\n        @Option(\n            name: .shortAndLong,\n            help: \"Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix\"\n        )\n        public var memory: String?\n    }\n\n    public struct DNS: ParsableArguments {\n        public init() {}\n\n        public init(domain: String?, nameservers: [String], options: [String], searchDomains: [String]) {\n            self.domain = domain\n            self.nameservers = nameservers\n            self.options = options\n            self.searchDomains = searchDomains\n        }\n\n        @Option(\n            name: .customLong(\"dns\"),\n            help: .init(\"DNS nameserver IP address\", valueName: \"ip\")\n        )\n        public var nameservers: [String] = []\n\n        @Option(\n            name: .customLong(\"dns-domain\"),\n            help: .init(\"Default DNS domain\", valueName: \"domain\")\n        )\n        public var domain: String? = nil\n\n        @Option(\n            name: .customLong(\"dns-option\"),\n            help: .init(\"DNS options\", valueName: \"option\")\n        )\n        public var options: [String] = []\n\n        @Option(\n            name: .customLong(\"dns-search\"),\n            help: .init(\"DNS search domains\", valueName: \"domain\")\n        )\n        public var searchDomains: [String] = []\n    }\n\n    public struct Registry: ParsableArguments {\n        public init() {}\n\n        public init(scheme: String) {\n            self.scheme = scheme\n        }\n\n        @Option(help: \"Scheme to use when connecting to the container registry. One of (http, https, auto)\")\n        public var scheme: String = \"auto\"\n    }\n\n    public struct Management: ParsableArguments {\n        public init() {}\n\n        public init(\n            arch: String,\n            cidfile: String,\n            detach: Bool,\n            dns: Flags.DNS,\n            dnsDisabled: Bool,\n            entrypoint: String?,\n            initImage: String?,\n            kernel: String?,\n            labels: [String],\n            mounts: [String],\n            name: String?,\n            networks: [String],\n            os: String,\n            platform: String?,\n            publishPorts: [String],\n            publishSockets: [String],\n            readOnly: Bool,\n            remove: Bool,\n            rosetta: Bool,\n            runtime: String?,\n            ssh: Bool,\n            tmpFs: [String],\n            useInit: Bool,\n            virtualization: Bool,\n            volumes: [String]\n        ) {\n            self.arch = arch\n            self.cidfile = cidfile\n            self.detach = detach\n            self.dns = dns\n            self.dnsDisabled = dnsDisabled\n            self.entrypoint = entrypoint\n            self.initImage = initImage\n            self.kernel = kernel\n            self.labels = labels\n            self.mounts = mounts\n            self.name = name\n            self.networks = networks\n            self.os = os\n            self.platform = platform\n            self.publishPorts = publishPorts\n            self.publishSockets = publishSockets\n            self.readOnly = readOnly\n            self.remove = remove\n            self.rosetta = rosetta\n            self.runtime = runtime\n            self.ssh = ssh\n            self.tmpFs = tmpFs\n            self.useInit = useInit\n            self.virtualization = virtualization\n            self.volumes = volumes\n        }\n\n        @Option(name: .shortAndLong, help: \"Set arch if image can target multiple architectures\")\n        public var arch: String = Arch.hostArchitecture().rawValue\n\n        @Option(name: .long, help: \"Write the container ID to the path provided\")\n        public var cidfile = \"\"\n\n        @Flag(name: .shortAndLong, help: \"Run the container and detach from the process\")\n        public var detach = false\n\n        @OptionGroup\n        public var dns: Flags.DNS\n\n        @Option(\n            name: .long,\n            help: .init(\n                \"Override the entrypoint of the image\",\n                valueName: \"cmd\"\n            )\n        )\n        public var entrypoint: String?\n\n        @Flag(name: .customLong(\"init\"), help: \"Run an init process inside the container that forwards signals and reaps processes\")\n        public var useInit = false\n\n        @Option(\n            name: .long,\n            help: .init(\"Use a custom init image instead of the default\", valueName: \"image\")\n        )\n        public var initImage: String?\n\n        @Option(\n            name: .shortAndLong,\n            help: .init(\"Set a custom kernel path\", valueName: \"path\"),\n            completion: .file(),\n            transform: { str in\n                URL(fileURLWithPath: str, relativeTo: .currentDirectory()).absoluteURL.path(percentEncoded: false)\n            }\n        )\n        public var kernel: String?\n\n        @Option(name: [.short, .customLong(\"label\")], help: \"Add a key=value label to the container\")\n        public var labels: [String] = []\n\n        @Option(name: .customLong(\"mount\"), help: \"Add a mount to the container (format: type=<>,source=<>,target=<>,readonly)\")\n        public var mounts: [String] = []\n\n        @Option(name: .long, help: \"Use the specified name as the container ID\")\n        public var name: String?\n\n        @Option(name: [.customLong(\"network\")], help: \"Attach the container to a network (format: <name>[,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE])\")\n        public var networks: [String] = []\n\n        @Flag(name: [.customLong(\"no-dns\")], help: \"Do not configure DNS in the container\")\n        public var dnsDisabled = false\n\n        @Option(name: .long, help: \"Set OS if image can target multiple operating systems\")\n        public var os = \"linux\"\n\n        @Option(\n            name: [.customShort(\"p\"), .customLong(\"publish\")],\n            help: .init(\n                \"Publish a port from container to host (format: [host-ip:]host-port:container-port[/protocol])\",\n                valueName: \"spec\"\n            )\n        )\n        public var publishPorts: [String] = []\n\n        @Option(name: .long, help: \"Platform for the image if it's multi-platform. This takes precedence over --os and --arch [environment: CONTAINER_DEFAULT_PLATFORM]\")\n        public var platform: String?\n\n        @Option(\n            name: .customLong(\"publish-socket\"),\n            help: .init(\n                \"Publish a socket from container to host (format: host_path:container_path)\",\n                valueName: \"spec\"\n            )\n        )\n        public var publishSockets: [String] = []\n\n        @Flag(name: .long, help: \"Mount the container's root filesystem as read-only\")\n        public var readOnly = false\n\n        @Flag(name: [.customLong(\"rm\"), .long], help: \"Remove the container after it stops\")\n        public var remove = false\n\n        @Flag(name: .long, help: \"Enable Rosetta in the container\")\n        public var rosetta = false\n\n        @Option(name: .long, help: \"Set the runtime handler for the container (default: container-runtime-linux)\")\n        public var runtime: String?\n\n        @Flag(name: .long, help: \"Forward SSH agent socket to container\")\n        public var ssh = false\n\n        @Option(name: .customLong(\"tmpfs\"), help: \"Add a tmpfs mount to the container at the given path\")\n        public var tmpFs: [String] = []\n\n        @Flag(\n            name: .long,\n            help:\n                \"Expose virtualization capabilities to the container (requires host and guest support)\"\n        )\n        public var virtualization: Bool = false\n\n        @Option(name: [.customLong(\"volume\"), .short], help: \"Bind mount a volume into the container\")\n        public var volumes: [String] = []\n    }\n\n    public struct Progress: ParsableArguments {\n        public init() {}\n\n        public init(progress: ProgressType) {\n            self.progress = progress\n        }\n\n        public enum ProgressType: String, ExpressibleByArgument {\n            case none\n            case ansi\n        }\n\n        @Option(name: .long, help: ArgumentHelp(\"Progress type (format: none|ansi)\", valueName: \"type\"))\n        public var progress: ProgressType = .ansi\n    }\n\n    public struct ImageFetch: ParsableArguments {\n        public init() {}\n\n        public init(maxConcurrentDownloads: Int) {\n            self.maxConcurrentDownloads = maxConcurrentDownloads\n        }\n\n        @Option(name: .long, help: \"Maximum number of concurrent downloads (default: 3)\")\n        public var maxConcurrentDownloads: Int = 3\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/HostDNSResolver.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\n/// Functions for managing local DNS domains for containers.\npublic struct HostDNSResolver {\n    public static let defaultConfigPath = URL(filePath: \"/etc/resolver\")\n\n    // prefix used to mark our files as /etc/resolver/{prefix}{domainName}\n    public static let containerizationPrefix = \"containerization.\"\n    public static let localhostOptionsRegex = #\"options localhost:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})\"#\n\n    private let configURL: URL\n\n    public init(configURL: URL = Self.defaultConfigPath) {\n        self.configURL = configURL\n    }\n\n    /// Creates a DNS resolver configuration file for domain resolved by the application.\n    public func createDomain(name: String, localhost: IPAddress? = nil) throws {\n        let path = self.configURL.appending(path: \"\\(Self.containerizationPrefix)\\(name)\").path\n        let fm: FileManager = FileManager.default\n\n        if fm.fileExists(atPath: self.configURL.path) {\n            guard let isDir = try self.configURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory, isDir else {\n                throw ContainerizationError(.invalidState, message: \"expected \\(self.configURL.path) to be a directory, but found a file\")\n            }\n        } else {\n            try fm.createDirectory(at: self.configURL, withIntermediateDirectories: true)\n        }\n\n        guard !fm.fileExists(atPath: path) else {\n            throw ContainerizationError(.exists, message: \"domain \\(name) already exists\")\n        }\n\n        let dnsPort = localhost == nil ? \"2053\" : \"1053\"\n        let options =\n            localhost.map {\n                HostDNSResolver.localhostOptionsRegex.replacingOccurrences(\n                    of: #\"\\((.*?)\\)\"#, with: $0.description, options: .regularExpression)\n            } ?? \"\"\n        let resolverText = \"\"\"\n            domain \\(name)\n            search \\(name)\n            nameserver 127.0.0.1\n            port \\(dnsPort)\n            \\(options)\n            \"\"\"\n\n        try resolverText.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    /// Removes a DNS resolver configuration file for domain resolved by the application.\n    public func deleteDomain(name: String) throws -> IPAddress? {\n        let path = self.configURL.appending(path: \"\\(Self.containerizationPrefix)\\(name)\").path\n        let fm = FileManager.default\n        guard fm.fileExists(atPath: path) else {\n            throw ContainerizationError(.notFound, message: \"domain \\(name) at \\(path) not found\")\n        }\n\n        var localhost: IPAddress?\n        let content = try String(contentsOfFile: path, encoding: .utf8)\n        if let match = content.firstMatch(of: try Regex(HostDNSResolver.localhostOptionsRegex)) {\n            localhost = try? IPAddress(String(match[1].substring ?? \"\"))\n        }\n\n        do {\n            try fm.removeItem(atPath: path)\n        } catch {\n            throw ContainerizationError(.invalidState, message: \"cannot delete domain (try sudo?)\")\n        }\n\n        return localhost\n    }\n\n    /// Lists application-created local DNS domains.\n    public func listDomains() -> [String] {\n        let fm: FileManager = FileManager.default\n        guard\n            let resolverPaths = try? fm.contentsOfDirectory(\n                at: self.configURL,\n                includingPropertiesForKeys: [.isDirectoryKey]\n            )\n        else {\n            return []\n        }\n\n        return\n            resolverPaths\n            .filter { $0.lastPathComponent.starts(with: Self.containerizationPrefix) }\n            .compactMap { try? getDomainFromResolver(url: $0) }\n            .sorted()\n    }\n\n    /// Reinitializes the macOS DNS daemon.\n    public static func reinitialize() throws {\n        do {\n            let kill = Foundation.Process()\n            kill.executableURL = URL(fileURLWithPath: \"/usr/bin/killall\")\n            kill.arguments = [\"-HUP\", \"mDNSResponder\"]\n\n            let null = FileHandle.nullDevice\n            kill.standardOutput = null\n            kill.standardError = null\n\n            try kill.run()\n            kill.waitUntilExit()\n            let status = kill.terminationStatus\n            guard status == 0 else {\n                throw ContainerizationError(.internalError, message: \"mDNSResponder restart failed with status \\(status)\")\n            }\n        }\n    }\n\n    private func getDomainFromResolver(url: URL) throws -> String? {\n        let text = try String(contentsOf: url, encoding: .utf8)\n        for line in text.components(separatedBy: .newlines) {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            let components = trimmed.split(whereSeparator: { $0.isWhitespace })\n            guard components.count == 2 else {\n                continue\n            }\n            guard components[0] == \"domain\" else {\n                continue\n            }\n\n            return String(components[1])\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ImageLoadResult.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// The result of loading an archive file into the image store.\npublic struct ImageLoadResult {\n    /// The successfully loaded images\n    public let images: [ClientImage]\n\n    /// The archive member files that were not extracted due\n    /// to invalid paths or attempted symlink traversal.\n    public let rejectedMembers: [String]\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Measurement+Parse.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nprivate let binaryUnits: [Character: UnitInformationStorage] = [\n    \"b\": .bytes,\n    \"k\": .kibibytes,\n    \"m\": .mebibytes,\n    \"g\": .gibibytes,\n    \"t\": .tebibytes,\n    \"p\": .pebibytes,\n]\n\nextension Measurement {\n    public enum ParseError: Swift.Error, CustomStringConvertible {\n        case invalidSize\n        case invalidSymbol(String)\n\n        public var description: String {\n            switch self {\n            case .invalidSize:\n                return \"invalid size\"\n            case .invalidSymbol(let symbol):\n                return \"invalid symbol: \\(symbol)\"\n            }\n        }\n    }\n\n    /// parseMemory the provided string into a measurement that is able to be converted to various byte sizes using binary exponents\n    public static func parse(parsing: String) throws -> Measurement<UnitInformationStorage> {\n        let check = \"01234567890.\"\n        let trimmed = parsing.trimmingCharacters(in: .whitespaces).lowercased()\n        guard !trimmed.isEmpty else {\n            throw ParseError.invalidSize\n        }\n\n        let i = trimmed.firstIndex {\n            !check.contains($0)\n        }\n        let rawValue =\n            i\n            .map { trimmed[..<$0].trimmingCharacters(in: .whitespaces) }\n            ?? trimmed\n        let rawUnit = i.map { trimmed[$0...].trimmingCharacters(in: .whitespaces) } ?? \"\"\n\n        let value = Double(rawValue)\n        guard let value else {\n            throw ParseError.invalidSize\n        }\n        let unitSymbol = try Self.parseUnit(rawUnit)\n\n        let unit = binaryUnits[unitSymbol]\n        guard let unit else {\n            throw ParseError.invalidSymbol(rawUnit)\n        }\n        return Measurement<UnitInformationStorage>(value: value, unit: unit)\n    }\n\n    static func parseUnit(_ unit: String) throws -> Character {\n        let s = unit.dropFirst()\n        let unitSymbol = unit.first ?? \"b\"\n\n        switch s {\n        case \"\", \"ib\", \"b\":\n            return unitSymbol\n        default:\n            throw ParseError.invalidSymbol(unit)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/PacketFilter.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\npublic struct PacketFilter {\n    public static let anchor = \"com.apple.container\"\n    public static let defaultConfigPath = URL(filePath: \"/etc/pf.conf\")\n    public static let defaultAnchorsPath = URL(filePath: \"/etc/pf.anchors\")\n\n    private let configURL: URL\n    private let anchorsURL: URL\n\n    public init(configURL: URL = Self.defaultConfigPath, anchorsURL: URL = Self.defaultAnchorsPath) {\n        self.configURL = configURL\n        self.anchorsURL = anchorsURL\n    }\n\n    public func createRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {\n        guard type(of: from) == type(of: to) else {\n            throw ContainerizationError(.invalidArgument, message: \"protocol does not match: \\(from) vs. \\(to)\")\n        }\n\n        let fm: FileManager = FileManager.default\n\n        let anchorURL = self.anchorsURL.appending(path: Self.anchor)\n\n        let inet: String\n        switch from {\n        case .v4: inet = \"inet\"\n        case .v6: inet = \"inet6\"\n        }\n        let redirectRule = \"rdr \\(inet) from any to \\(from.description) -> \\(to.description) # \\(domain)\"\n\n        var content = \"\"\n        if fm.fileExists(atPath: anchorURL.path) {\n            content = try String(contentsOfFile: anchorURL.path, encoding: .utf8)\n        } else {\n            try addAnchorToConfig()\n        }\n\n        var lines = content.components(separatedBy: .newlines)\n        if !content.contains(redirectRule) {\n            lines.insert(redirectRule, at: lines.endIndex - 1)\n        }\n\n        try lines.joined(separator: \"\\n\").write(toFile: anchorURL.path, atomically: true, encoding: .utf8)\n    }\n\n    public func removeRedirectRule(from: IPAddress, to: IPAddress, domain: String) throws {\n        guard type(of: from) == type(of: to) else {\n            throw ContainerizationError(.invalidArgument, message: \"protocol does not match: \\(from) vs. \\(to)\")\n        }\n\n        let fm: FileManager = FileManager.default\n\n        let anchorURL = self.anchorsURL.appending(path: Self.anchor)\n\n        let inet: String\n        switch from {\n        case .v4: inet = \"inet\"\n        case .v6: inet = \"inet6\"\n        }\n        let redirectRule = \"rdr \\(inet) from any to \\(from.description) -> \\(to.description) # \\(domain)\"\n\n        guard fm.fileExists(atPath: anchorURL.path) else {\n            return\n        }\n\n        let content = try String(contentsOfFile: anchorURL.path, encoding: .utf8)\n        let lines = content.components(separatedBy: .newlines)\n\n        let removedLines = lines.filter { l in\n            l != redirectRule\n        }\n\n        if removedLines == [\"\"] {\n            try fm.removeItem(atPath: anchorURL.path)\n            try removeAnchorFromConfig()\n        } else {\n            try removedLines.joined(separator: \"\\n\").write(toFile: anchorURL.path, atomically: true, encoding: .utf8)\n        }\n    }\n\n    private func addAnchorToConfig() throws {\n        let fm: FileManager = FileManager.default\n\n        let anchorURL = self.anchorsURL.appending(path: Self.anchor)\n\n        /* PF requires strict ordering of anchors:\n           scrub-anchor, nat-anchor, rdr-anchor, dummynet-anchor, anchor, load anchor\n         */\n        let anchorKeywords = [\"scrub-anchor\", \"nat-anchor\", \"rdr-anchor\", \"dummynet-anchor\", \"anchor\", \"load anchor\"]\n        let loadAnchorText = \"load anchor \\\"\\(Self.anchor)\\\" from \\\"\\(anchorURL.path)\\\"\"\n\n        var content: String = \"\"\n        var lines: [String] = []\n        if fm.fileExists(atPath: self.configURL.path) {\n            content = try String(contentsOfFile: self.configURL.path, encoding: .utf8)\n        }\n        lines = content.components(separatedBy: .newlines)\n\n        for (i, keyword) in anchorKeywords[..<(anchorKeywords.endIndex - 1)].enumerated() {\n            let anchorText = \"\\(keyword) \\\"\\(Self.anchor)\\\"\"\n\n            if content.contains(anchorText) {\n                continue\n            }\n\n            let idx = lines.firstIndex { l in\n                anchorKeywords[i...].map { k in l.starts(with: k) }.contains(true)\n            }\n            lines.insert(anchorText, at: idx ?? lines.endIndex - 1)\n        }\n\n        if !content.contains(loadAnchorText) {\n            lines.insert(loadAnchorText, at: lines.endIndex - 1)\n        }\n\n        do {\n            try lines.joined(separator: \"\\n\").write(toFile: self.configURL.path, atomically: true, encoding: .utf8)\n        } catch {\n            throw ContainerizationError(.invalidState, message: \"failed to write \\\"\\(self.configURL.path)\\\"\")\n        }\n    }\n\n    private func removeAnchorFromConfig() throws {\n        let fm: FileManager = FileManager.default\n\n        guard fm.fileExists(atPath: configURL.path) else {\n            return\n        }\n\n        let content = try String(contentsOfFile: configURL.path, encoding: .utf8)\n        let lines = content.components(separatedBy: .newlines)\n\n        let removedLines = lines.filter { l in !l.contains(Self.anchor) }\n\n        do {\n            try removedLines.joined(separator: \"\\n\").write(toFile: configURL.path, atomically: true, encoding: .utf8)\n        } catch {\n            throw ContainerizationError(.invalidState, message: \"failed to write \\\"\\(configURL.path)\\\"\")\n        }\n    }\n\n    public func reinitialize() throws {\n        let null = FileHandle.nullDevice\n\n        let checkProcess = Foundation.Process()\n        var checkStatus: Int32\n        checkProcess.executableURL = URL(fileURLWithPath: \"/sbin/pfctl\")\n        checkProcess.arguments = [\"-n\", \"-f\", configURL.path]\n        checkProcess.standardOutput = null\n        checkProcess.standardError = null\n\n        do {\n            try checkProcess.run()\n        } catch {\n            throw ContainerizationError(.internalError, message: \"pfctl rule check exec failed: \\\"\\(error)\\\"\")\n        }\n\n        checkProcess.waitUntilExit()\n        checkStatus = checkProcess.terminationStatus\n        guard checkStatus == 0 else {\n            throw ContainerizationError(.internalError, message: \"invalid pf config \\\"\\(configURL.path)\\\"\")\n        }\n\n        let reloadProcess = Foundation.Process()\n        var reloadStatus: Int32\n\n        reloadProcess.executableURL = URL(fileURLWithPath: \"/sbin/pfctl\")\n        reloadProcess.arguments = [\"-f\", configURL.path]\n        reloadProcess.standardOutput = null\n        reloadProcess.standardError = null\n\n        do {\n            try reloadProcess.run()\n        } catch {\n            throw ContainerizationError(.internalError, message: \"pfctl reload exec failed: \\\"\\(error)\\\"\")\n        }\n        reloadProcess.waitUntilExit()\n        reloadStatus = reloadProcess.terminationStatus\n        guard reloadStatus == 0 else {\n            throw ContainerizationError(.invalidState, message: \"pfctl -f \\\"\\(configURL.path)\\\" failed with status \\(reloadStatus)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Parser.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\n\n/// A parsed volume specification from user input\npublic struct ParsedVolume {\n    public let name: String\n    public let destination: String\n    public let options: [String]\n    public let isAnonymous: Bool\n\n    public init(name: String, destination: String, options: [String] = [], isAnonymous: Bool = false) {\n        self.name = name\n        self.destination = destination\n        self.options = options\n        self.isAnonymous = isAnonymous\n    }\n}\n\n/// Union type for parsed mount specifications\npublic enum VolumeOrFilesystem {\n    case filesystem(Filesystem)\n    case volume(ParsedVolume)\n}\n\npublic struct Parser {\n    public static func memoryStringAsMiB(_ memory: String) throws -> Int64 {\n        let ram = try Measurement.parse(parsing: memory)\n        let mb = ram.converted(to: .mebibytes)\n        return Int64(mb.value)\n    }\n\n    public static func user(\n        user: String?, uid: UInt32?, gid: UInt32?,\n        defaultUser: ProcessConfiguration.User = .id(uid: 0, gid: 0)\n    ) -> (user: ProcessConfiguration.User, groups: [UInt32]) {\n        var supplementalGroups: [UInt32] = []\n        let user: ProcessConfiguration.User = {\n            if let user = user, !user.isEmpty {\n                return .raw(userString: user)\n            }\n            if let uid, let gid {\n                return .id(uid: uid, gid: gid)\n            }\n            if uid == nil, gid == nil {\n                // Neither uid nor gid is set. return the default user\n                return defaultUser\n            }\n            // One of uid / gid is left unspecified. Set the user accordingly\n            if let uid {\n                return .raw(userString: \"\\(uid)\")\n            }\n            if let gid {\n                supplementalGroups.append(gid)\n            }\n            return defaultUser\n        }()\n        return (user, supplementalGroups)\n    }\n\n    public static func platform(os: String, arch: String) -> ContainerizationOCI.Platform {\n        .init(arch: arch, os: os)\n    }\n\n    public static func platform(from platform: String) throws -> ContainerizationOCI.Platform {\n        try .init(from: platform)\n    }\n\n    public static func resources(\n        cpus: Int64?,\n        memory: String?,\n        cpuPropertyKey: DefaultsStore.Keys = .defaultContainerCPUs,\n        memoryPropertyKey: DefaultsStore.Keys = .defaultContainerMemory,\n        defaultCPUs: Int = 4,\n        defaultMemoryInBytes: UInt64 = 1024.mib()\n    ) throws -> ContainerConfiguration.Resources {\n        var resource = ContainerConfiguration.Resources()\n        resource.cpus = defaultCPUs\n        resource.memoryInBytes = defaultMemoryInBytes\n\n        if let cpus {\n            resource.cpus = Int(cpus)\n        } else if let cpuStr = DefaultsStore.getOptional(key: cpuPropertyKey),\n            let cpuVal = Int(cpuStr), cpuVal > 0\n        {\n            resource.cpus = cpuVal\n        }\n        if let memory {\n            resource.memoryInBytes = try Parser.memoryStringAsMiB(memory).mib()\n        } else if let memStr = DefaultsStore.getOptional(key: memoryPropertyKey) {\n            resource.memoryInBytes = try Parser.memoryStringAsMiB(memStr).mib()\n        }\n        return resource\n    }\n\n    public static func allEnv(imageEnvs: [String], envFiles: [String], envs: [String]) throws -> [String] {\n        var combined: [String] = []\n        combined.append(contentsOf: Parser.env(envList: imageEnvs))\n        for envFile in envFiles {\n            let content = try Parser.envFile(path: envFile)\n            combined.append(contentsOf: content)\n        }\n        combined.append(contentsOf: Parser.env(envList: envs))\n\n        let deduped = combined.reduce(into: [String: String]()) { map, entry in\n            let key = String(entry.split(separator: \"=\", maxSplits: 1).first ?? Substring(entry))\n            map[key] = entry\n        }\n\n        return deduped.map { $0.value }\n    }\n\n    public static func envFile(path: String) throws -> [String] {\n        // This is a somewhat faithful Go->Swift port of Moby's envfile\n        // parsing in the cli:\n        // https://github.com/docker/cli/blob/f5a7a3c72eb35fc5ba9c4d65a2a0e2e1bd216bf2/pkg/kvfile/kvfile.go#L81\n\n        let data: Data\n        do {\n            // Use FileHandle to support named pipes (FIFOs) and process substitutions\n            // like --env-file <(echo \"KEY=value\")\n            let fileHandle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))\n            defer { try? fileHandle.close() }\n            data = try fileHandle.readToEnd() ?? Data()\n        } catch {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"failed to read envfile at \\(path)\",\n                cause: error\n            )\n        }\n\n        guard let content = String(data: data, encoding: .utf8) else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"env file \\(path) contains invalid utf8 bytes\"\n            )\n        }\n\n        let whiteSpaces = \" \\t\"\n\n        var lines: [String] = []\n        let fileLines = content.components(separatedBy: .newlines)\n\n        for line in fileLines {\n            let trimmedLine = line.drop(while: { $0.isWhitespace })\n\n            // Skip empty lines and comments\n            if trimmedLine.isEmpty || trimmedLine.hasPrefix(\"#\") {\n                continue\n            }\n\n            let hasValue: Bool\n            let variable: String\n            let value: String\n\n            if let equalIndex = trimmedLine.firstIndex(of: \"=\") {\n                variable = String(trimmedLine[..<equalIndex])\n                value = String(trimmedLine[trimmedLine.index(after: equalIndex)...])\n                hasValue = true\n            } else {\n                variable = String(trimmedLine)\n                value = \"\"\n                hasValue = false\n            }\n\n            let trimmedVariable = variable.drop(while: { whiteSpaces.contains($0) })\n            if trimmedVariable.contains(where: { whiteSpaces.contains($0) }) {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"variable '\\(trimmedVariable)' contains whitespaces\"\n                )\n            }\n\n            if trimmedVariable.isEmpty {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"no variable name on line '\\(trimmedLine)'\"\n                )\n            }\n\n            if hasValue {\n                lines.append(\"\\(trimmedVariable)=\\(value)\")\n            } else {\n                // We got just a variable name, try and see if it exists on the host.\n                if let envValue = ProcessInfo.processInfo.environment[String(trimmedVariable)] {\n                    lines.append(\"\\(trimmedVariable)=\\(envValue)\")\n                }\n            }\n        }\n\n        return lines\n    }\n\n    public static func env(envList: [String]) -> [String] {\n        var envVar: [String] = []\n        for env in envList {\n            var env = env\n            // Only inherit from host if no \"=\" is present (e.g., \"--env VAR\")\n            // \"VAR=\" should set an explicit empty value, not inherit.\n            if !env.contains(\"=\") {\n                guard let val = ProcessInfo.processInfo.environment[env] else {\n                    continue\n                }\n                env = \"\\(env)=\\(val)\"\n            }\n            envVar.append(env)\n        }\n        return envVar\n    }\n\n    public static func labels(_ rawLabels: [String]) throws -> [String: String] {\n        var result: [String: String] = [:]\n        for label in rawLabels {\n            if label.isEmpty {\n                throw ContainerizationError(.invalidArgument, message: \"label cannot be an empty string\")\n            }\n            let parts = label.split(separator: \"=\", maxSplits: 2)\n            switch parts.count {\n            case 1:\n                result[String(parts[0])] = \"\"\n            case 2:\n                result[String(parts[0])] = String(parts[1])\n            default:\n                throw ContainerizationError(.invalidArgument, message: \"invalid label format \\(label)\")\n            }\n        }\n        return result\n    }\n\n    public static func process(\n        arguments: [String],\n        processFlags: Flags.Process,\n        managementFlags: Flags.Management,\n        config: ContainerizationOCI.ImageConfig?\n    ) throws -> ProcessConfiguration {\n\n        let imageEnvVars = config?.env ?? []\n        let envvars = try Parser.allEnv(imageEnvs: imageEnvVars, envFiles: processFlags.envFile, envs: processFlags.env)\n\n        let workingDir: String = {\n            if let cwd = processFlags.cwd {\n                return cwd\n            }\n            if let cwd = config?.workingDir {\n                return cwd\n            }\n            return \"/\"\n        }()\n\n        let processArguments: [String]? = {\n            var result: [String] = []\n            var hasEntrypointOverride: Bool = false\n            // ensure the entrypoint is honored if it has been explicitly set by the user\n            if let entrypoint = managementFlags.entrypoint, !entrypoint.isEmpty {\n                result = [entrypoint]\n                hasEntrypointOverride = true\n            } else if let entrypoint = config?.entrypoint, !entrypoint.isEmpty {\n                result = entrypoint\n            }\n            if !arguments.isEmpty {\n                result.append(contentsOf: arguments)\n            } else {\n                if let cmd = config?.cmd, !hasEntrypointOverride, !cmd.isEmpty {\n                    result.append(contentsOf: cmd)\n                }\n            }\n            return result.count > 0 ? result : nil\n        }()\n\n        guard let commandToRun = processArguments, commandToRun.count > 0 else {\n            throw ContainerizationError(.invalidArgument, message: \"command/entrypoint not specified for container process\")\n        }\n\n        let defaultUser: ProcessConfiguration.User = {\n            if let u = config?.user {\n                return .raw(userString: u)\n            }\n            return .id(uid: 0, gid: 0)\n        }()\n\n        let (user, additionalGroups) = Parser.user(\n            user: processFlags.user, uid: processFlags.uid,\n            gid: processFlags.gid, defaultUser: defaultUser)\n\n        let rlimits = try Parser.rlimits(processFlags.ulimits)\n\n        return .init(\n            executable: commandToRun.first!,\n            arguments: [String](commandToRun.dropFirst()),\n            environment: envvars,\n            workingDirectory: workingDir,\n            terminal: processFlags.tty,\n            user: user,\n            supplementalGroups: additionalGroups,\n            rlimits: rlimits\n        )\n    }\n\n    // MARK: Mounts\n\n    public static let mountTypes = [\n        \"virtiofs\",\n        \"bind\",\n        \"tmpfs\",\n    ]\n\n    public static let defaultDirectives = [\"type\": \"virtiofs\"]\n\n    public static func tmpfsMounts(_ mounts: [String]) throws -> [Filesystem] {\n        var result: [Filesystem] = []\n        let mounts = mounts.dedupe()\n        for tmpfs in mounts {\n            let fs = Filesystem.tmpfs(destination: tmpfs, options: [])\n            try validateMount(.filesystem(fs))\n            result.append(fs)\n        }\n        return result\n    }\n\n    public static func mounts(_ rawMounts: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] {\n        var mounts: [VolumeOrFilesystem] = []\n        let rawMounts = rawMounts.dedupe()\n        for mount in rawMounts {\n            let m = try Parser.mount(mount, relativeTo: basePath)\n            try validateMount(m)\n            mounts.append(m)\n        }\n        return mounts\n    }\n\n    public static func mount(_ mount: String, relativeTo basePath: URL? = nil) throws -> VolumeOrFilesystem {\n        let parts = mount.split(separator: \",\")\n        if parts.count == 0 {\n            throw ContainerizationError(.invalidArgument, message: \"invalid mount format: \\(mount)\")\n        }\n        var directives = defaultDirectives\n        for part in parts {\n            let keyVal = part.split(separator: \"=\", maxSplits: 2)\n            var key = String(keyVal[0])\n            var skipValue = false\n            switch key {\n            case \"type\", \"size\", \"mode\":\n                break\n            case \"source\", \"src\":\n                key = \"source\"\n            case \"destination\", \"dst\", \"target\":\n                key = \"destination\"\n            case \"readonly\", \"ro\":\n                key = \"ro\"\n                skipValue = true\n            default:\n                throw ContainerizationError(.invalidArgument, message: \"unknown directive \\(key) when parsing mount \\(mount)\")\n            }\n            var value = \"\"\n            if !skipValue {\n                if keyVal.count != 2 {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid directive format missing value \\(part) in \\(mount)\")\n                }\n                value = String(keyVal[1])\n            }\n            directives[key] = value\n        }\n\n        var fs = Filesystem()\n        var isVolume = false\n        var volumeName = \"\"\n        for (key, val) in directives {\n            var val = val\n            let type = directives[\"type\"] ?? \"\"\n\n            switch key {\n            case \"type\":\n                if val == \"bind\" {\n                    val = \"virtiofs\"\n                }\n                switch val {\n                case \"virtiofs\":\n                    fs.type = Filesystem.FSType.virtiofs\n                case \"tmpfs\":\n                    fs.type = Filesystem.FSType.tmpfs\n                case \"volume\":\n                    isVolume = true\n                default:\n                    throw ContainerizationError(.invalidArgument, message: \"unsupported mount type \\(val)\")\n                }\n\n            case \"ro\":\n                fs.options.append(\"ro\")\n            case \"size\":\n                if type != \"tmpfs\" {\n                    throw ContainerizationError(.invalidArgument, message: \"unsupported option size for \\(type) mount\")\n                }\n                var overflow: Bool\n                var memory = try Parser.memoryStringAsMiB(val)\n                (memory, overflow) = memory.multipliedReportingOverflow(by: 1024 * 1024)\n                if overflow {\n                    throw ContainerizationError(.invalidArgument, message: \"overflow encountered when parsing memory string: \\(val)\")\n                }\n                let s = \"size=\\(memory)\"\n                fs.options.append(s)\n            case \"mode\":\n                if type != \"tmpfs\" {\n                    throw ContainerizationError(.invalidArgument, message: \"unsupported option mode for \\(type) mount\")\n                }\n                let s = \"mode=\\(val)\"\n                fs.options.append(s)\n            case \"source\":\n                switch type {\n                case \"virtiofs\", \"bind\":\n                    // For bind mounts, resolve both absolute and relative paths\n                    let url = basePath?.appending(path: val).standardizedFileURL ?? URL(filePath: val)\n                    let absolutePath = url.absoluteURL.path\n\n                    var isDirectory: ObjCBool = false\n                    guard FileManager.default.fileExists(atPath: absolutePath, isDirectory: &isDirectory) else {\n                        throw ContainerizationError(.invalidArgument, message: \"path '\\(val)' does not exist\")\n                    }\n                    guard isDirectory.boolValue else {\n                        throw ContainerizationError(.invalidArgument, message: \"path '\\(val)' is not a directory\")\n                    }\n                    fs.source = absolutePath\n                case \"volume\":\n                    // For volume mounts, validate as volume name\n                    guard VolumeStorage.isValidVolumeName(val) else {\n                        throw ContainerizationError(.invalidArgument, message: \"invalid volume name '\\(val)': must match \\(VolumeStorage.volumeNamePattern)\")\n                    }\n\n                    // This is a named volume\n                    volumeName = val\n                    fs.source = val\n                case \"tmpfs\":\n                    throw ContainerizationError(.invalidArgument, message: \"cannot specify source for tmpfs mount\")\n                default:\n                    throw ContainerizationError(.invalidArgument, message: \"unknown mount type \\(type)\")\n                }\n            case \"destination\":\n                fs.destination = val\n            default:\n                throw ContainerizationError(.invalidArgument, message: \"unknown mount directive \\(key)\")\n            }\n        }\n\n        guard isVolume else {\n            return .filesystem(fs)\n        }\n\n        // If it's a volume type but no source was provided, create an anonymous volume\n        let isAnonymous = volumeName.isEmpty\n        if isAnonymous {\n            volumeName = VolumeStorage.generateAnonymousVolumeName()\n        }\n\n        return .volume(\n            ParsedVolume(\n                name: volumeName,\n                destination: fs.destination,\n                options: fs.options,\n                isAnonymous: isAnonymous\n            ))\n    }\n\n    public static func volumes(_ rawVolumes: [String], relativeTo basePath: URL? = nil) throws -> [VolumeOrFilesystem] {\n        var mounts: [VolumeOrFilesystem] = []\n        for volume in rawVolumes {\n            let m = try Parser.volume(volume, relativeTo: basePath)\n            try Parser.validateMount(m)\n            mounts.append(m)\n        }\n        return mounts\n    }\n\n    public static func volume(_ volume: String, relativeTo basePath: URL? = nil) throws -> VolumeOrFilesystem {\n        var vol = volume\n        vol.trimLeft(char: \":\")\n\n        let parts = vol.split(separator: \":\")\n        switch parts.count {\n        case 1:\n            // Anonymous volume: -v /path\n            // Generate a random name for the anonymous volume\n            let anonymousName = VolumeStorage.generateAnonymousVolumeName()\n            let destination = String(parts[0])\n            let options: [String] = []\n\n            return .volume(\n                ParsedVolume(\n                    name: anonymousName,\n                    destination: destination,\n                    options: options,\n                    isAnonymous: true\n                ))\n        case 2, 3:\n            let src = String(parts[0])\n            let dst = String(parts[1])\n\n            // Check if it's a filesystem path (absolute, or relative like \".\", \"..\", \"./foo\", \"../foo\")\n            guard src.contains(\"/\") || src == \".\" || src == \"..\" else {\n                // Named volume - validate name syntax only\n                guard VolumeStorage.isValidVolumeName(src) else {\n                    throw ContainerizationError(.invalidArgument, message: \"invalid volume name '\\(src)': must match \\(VolumeStorage.volumeNamePattern)\")\n                }\n\n                // This is a named volume\n                let options = parts.count == 3 ? parts[2].split(separator: \",\").map { String($0) } : []\n                return .volume(\n                    ParsedVolume(\n                        name: src,\n                        destination: dst,\n                        options: options\n                    ))\n            }\n            let url = basePath?.appending(path: src).standardizedFileURL ?? URL(filePath: src)\n            let absolutePath = url.absoluteURL.path\n\n            var isDirectory: ObjCBool = false\n            guard FileManager.default.fileExists(atPath: absolutePath, isDirectory: &isDirectory) else {\n                throw ContainerizationError(.invalidArgument, message: \"path '\\(src)' does not exist\")\n            }\n\n            // This is a filesystem mount\n            var fs = Filesystem.virtiofs(\n                source: URL(fileURLWithPath: absolutePath).absolutePath(),\n                destination: dst,\n                options: []\n            )\n            if parts.count == 3 {\n                fs.options = parts[2].split(separator: \",\").map { String($0) }\n            }\n            return .filesystem(fs)\n        default:\n            throw ContainerizationError(.invalidArgument, message: \"invalid volume format \\(volume)\")\n        }\n    }\n\n    public static func validMountType(_ type: String) -> Bool {\n        mountTypes.contains(type)\n    }\n\n    public static func validateMount(_ mount: VolumeOrFilesystem) throws {\n        switch mount {\n        case .filesystem(let fs):\n            if !fs.isTmpfs {\n                if !fs.source.isAbsolutePath() {\n                    throw ContainerizationError(\n                        .invalidArgument, message: \"\\(fs.source) is not an absolute path on the host\")\n                }\n                if !FileManager.default.fileExists(atPath: fs.source) {\n                    throw ContainerizationError(.invalidArgument, message: \"file path '\\(fs.source)' does not exist\")\n                }\n            }\n\n            if fs.destination.isEmpty {\n                throw ContainerizationError(.invalidArgument, message: \"mount destination cannot be empty\")\n            }\n        case .volume(let vol):\n            if vol.destination.isEmpty {\n                throw ContainerizationError(.invalidArgument, message: \"volume destination cannot be empty\")\n            }\n        // Volume name validation already done during parsing\n        }\n    }\n\n    /// Parse --publish-port arguments into PublishPort objects\n    /// The format of each argument is `[host-ip:]host-port:container-port[/protocol]`\n    /// (e.g., \"127.0.0.1:8080:80/tcp\")\n    /// host-port and container-port can be ranges (e.g., \"127.0.0.1:3456-4567:3456-4567/tcp`\n    ///\n    /// - Parameter rawPublishPorts: Array of port arguments\n    /// - Returns: Array of PublishPort objects\n    /// - Throws: ContainerizationError if parsing fails\n    public static func publishPorts(_ rawPublishPorts: [String]) throws -> [PublishPort] {\n        var publishPorts: [PublishPort] = []\n\n        // Process each raw port string\n        for socket in rawPublishPorts {\n            let publishPort = try Parser.publishPort(socket)\n            publishPorts.append(publishPort)\n        }\n        return publishPorts\n    }\n\n    // Parse a single `--publish-port` argument into a `PublishPort`.\n    public static func publishPort(_ portText: String) throws -> PublishPort {\n        let publishPortRegex = #/((\\[(?<ipv6>[^\\]]*)\\]|(?<ipv4>[^:].*)):)?(?<hostPort>[^:].*):(?<containerPort>[^:/]*)(/(?<proto>.*))?/#\n        guard let match = try publishPortRegex.wholeMatch(in: portText) else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish value: \\(portText)\")\n        }\n\n        let proto: PublishProtocol\n        let protoText = match.proto?.lowercased() ?? \"tcp\"\n        switch protoText {\n        case \"tcp\":\n            proto = .tcp\n        case \"udp\":\n            proto = .udp\n        default:\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish protocol: \\(protoText)\")\n        }\n\n        let hostAddress: IPAddress\n        if let ipv6 = match.ipv6, !ipv6.isEmpty {\n            guard let address = try? IPAddress(String(ipv6)), case .v6 = address else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish IPv6 address: \\(portText)\")\n            }\n            hostAddress = address\n        } else if let ipv4 = match.ipv4, !ipv4.isEmpty {\n            guard let address = try? IPAddress(String(ipv4)), case .v4 = address else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish IPv4 address: \\(portText)\")\n            }\n            hostAddress = address\n        } else {\n            hostAddress = try IPAddress(\"0.0.0.0\")\n        }\n\n        let hostPortText = match.hostPort\n        let containerPortText = match.containerPort\n        let hostPortRangeStart: UInt16\n        let hostPortRangeEnd: UInt16\n        let containerPortRangeStart: UInt16\n        let containerPortRangeEnd: UInt16\n\n        let hostPortParts = hostPortText.split(separator: \"-\")\n        switch hostPortParts.count {\n        case 1:\n            guard let start = UInt16(hostPortParts[0]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish host port: \\(hostPortText)\")\n            }\n            hostPortRangeStart = start\n            hostPortRangeEnd = start\n        case 2:\n            guard let start = UInt16(hostPortParts[0]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish host port: \\(hostPortText)\")\n            }\n\n            guard let end = UInt16(hostPortParts[1]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish host port: \\(hostPortText)\")\n            }\n\n            hostPortRangeStart = start\n            hostPortRangeEnd = end\n        default:\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish host port: \\(hostPortText)\")\n        }\n\n        let containerPortParts = containerPortText.split(separator: \"-\")\n        switch containerPortParts.count {\n        case 1:\n            guard let start = UInt16(containerPortParts[0]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish container port: \\(containerPortText)\")\n            }\n\n            containerPortRangeStart = start\n            containerPortRangeEnd = start\n        case 2:\n            guard let start = UInt16(containerPortParts[0]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish container port: \\(containerPortText)\")\n            }\n\n            guard let end = UInt16(containerPortParts[1]) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid publish container port: \\(containerPortText)\")\n            }\n\n            containerPortRangeStart = start\n            containerPortRangeEnd = end\n        default:\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish container port: \\(containerPortText)\")\n        }\n\n        guard hostPortRangeStart > 1,\n            hostPortRangeStart <= hostPortRangeEnd\n        else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish host port range: \\(hostPortText)\")\n        }\n\n        guard containerPortRangeStart > 1,\n            containerPortRangeStart <= containerPortRangeEnd\n        else {\n            throw ContainerizationError(.invalidArgument, message: \"invalid publish container port range: \\(containerPortText)\")\n        }\n\n        let hostCount = hostPortRangeEnd - hostPortRangeStart + 1\n        let containerCount = containerPortRangeEnd - containerPortRangeStart + 1\n\n        guard hostCount == containerCount else {\n            throw ContainerizationError(.invalidArgument, message: \"publish host and container port counts are not equal: \\(hostPortText):\\(containerPortText)\")\n        }\n\n        return PublishPort(\n            hostAddress: hostAddress,\n            hostPort: hostPortRangeStart,\n            containerPort: containerPortRangeStart,\n            proto: proto,\n            count: hostCount\n        )\n    }\n\n    /// Parse --publish-socket arguments into PublishSocket objects\n    /// The format of each argument is `host_path:container_path`\n    /// (e.g., \"/tmp/docker.sock:/var/run/docker.sock\")\n    ///\n    /// - Parameter rawPublishSockets: Array of socket arguments\n    /// - Returns: Array of PublishSocket objects\n    /// - Throws: ContainerizationError if parsing fails or a path is invalid\n    public static func publishSockets(_ rawPublishSockets: [String]) throws -> [PublishSocket] {\n        var sockets: [PublishSocket] = []\n\n        // Process each raw socket string\n        for socket in rawPublishSockets {\n            let parsedSocket = try Parser.publishSocket(socket)\n            sockets.append(parsedSocket)\n        }\n        return sockets\n    }\n\n    // Parse a single `--publish-socket`` argument into a `PublishSocket`.\n    public static func publishSocket(_ socketText: String) throws -> PublishSocket {\n        // Split by colon to two parts: [host_path, container_path]\n        let parts = socketText.split(separator: \":\")\n\n        switch parts.count {\n        case 2:\n            // Extract host and container paths\n            let hostPath = String(parts[0])\n            let containerPath = String(parts[1])\n\n            // Validate paths are not empty\n            if hostPath.isEmpty {\n                throw ContainerizationError(\n                    .invalidArgument, message: \"host socket path cannot be empty\")\n            }\n            if containerPath.isEmpty {\n                throw ContainerizationError(\n                    .invalidArgument, message: \"container socket path cannot be empty\")\n            }\n\n            // Ensure container path must start with /\n            if !containerPath.hasPrefix(\"/\") {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"container socket path must be absolute: \\(containerPath)\")\n            }\n\n            // Convert host path to absolute path for consistency\n            let hostURL = URL(fileURLWithPath: hostPath)\n            let absoluteHostPath = hostURL.absoluteURL.path\n\n            // Check if host socket already exists and might be in use\n            if FileManager.default.fileExists(atPath: absoluteHostPath) {\n                do {\n                    let attrs = try FileManager.default.attributesOfItem(atPath: absoluteHostPath)\n                    if let fileType = attrs[.type] as? FileAttributeType, fileType == .typeSocket {\n                        throw ContainerizationError(\n                            .invalidArgument,\n                            message: \"host socket \\(absoluteHostPath) already exists and may be in use\")\n                    }\n                    // If it exists but is not a socket, we can remove it and create socket\n                    try FileManager.default.removeItem(atPath: absoluteHostPath)\n                } catch let error as ContainerizationError {\n                    throw error\n                } catch {\n                    // For other file system errors, continue with creation\n                }\n            }\n\n            // Create host directory if it doesn't exist\n            let hostDir = hostURL.deletingLastPathComponent()\n            if !FileManager.default.fileExists(atPath: hostDir.path) {\n                try FileManager.default.createDirectory(\n                    at: hostDir, withIntermediateDirectories: true)\n            }\n\n            // Create and return PublishSocket object with validated paths\n            return PublishSocket(\n                containerPath: URL(fileURLWithPath: containerPath),\n                hostPath: URL(fileURLWithPath: absoluteHostPath),\n                permissions: nil\n            )\n\n        default:\n            throw ContainerizationError(\n                .invalidArgument,\n                message:\n                    \"invalid publish-socket format \\(socketText). Expected: host_path:container_path\")\n        }\n    }\n\n    // MARK: Networks\n\n    /// Parsed network attachment with optional properties\n    public struct ParsedNetwork {\n        public let name: String\n        public let macAddress: String?\n        public let mtu: UInt32?\n\n        public init(name: String, macAddress: String? = nil, mtu: UInt32? = nil) {\n            self.name = name\n            self.macAddress = macAddress\n            self.mtu = mtu\n        }\n    }\n\n    /// Parse network attachment with optional properties\n    /// Format: network_name[,mac=XX:XX:XX:XX:XX:XX][,mtu=VALUE]\n    /// Example: \"backend,mac=02:42:ac:11:00:02,mtu=1500\"\n    public static func network(_ networkSpec: String) throws -> ParsedNetwork {\n        guard !networkSpec.isEmpty else {\n            throw ContainerizationError(.invalidArgument, message: \"network specification cannot be empty\")\n        }\n\n        let parts = networkSpec.split(separator: \",\", omittingEmptySubsequences: false)\n\n        guard !parts.isEmpty else {\n            throw ContainerizationError(.invalidArgument, message: \"network specification cannot be empty\")\n        }\n\n        let networkName = String(parts[0])\n        if networkName.isEmpty {\n            throw ContainerizationError(.invalidArgument, message: \"network name cannot be empty\")\n        }\n\n        var macAddress: String?\n        var mtu: UInt32?\n\n        // Parse properties if any\n        for part in parts.dropFirst() {\n            let keyVal = part.split(separator: \"=\", maxSplits: 2, omittingEmptySubsequences: false)\n\n            let key: String\n            let value: String\n\n            guard keyVal.count == 2 else {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"invalid property format '\\(part)' in network specification '\\(networkSpec)'\"\n                )\n            }\n            key = String(keyVal[0])\n            value = String(keyVal[1])\n\n            switch key {\n            case \"mac\":\n                if value.isEmpty {\n                    throw ContainerizationError(\n                        .invalidArgument,\n                        message: \"mac address value cannot be empty\"\n                    )\n                }\n                macAddress = value\n            case \"mtu\":\n                guard let mtuValue = UInt32(value), mtuValue >= 1280, mtuValue <= 65535 else {\n                    throw ContainerizationError(\n                        .invalidArgument,\n                        message: \"invalid mtu value '\\(value)': must be between 1280 and 65535\"\n                    )\n                }\n                mtu = mtuValue\n            default:\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"unknown network property '\\(key)'. Available properties: mac, mtu\"\n                )\n            }\n        }\n\n        return ParsedNetwork(name: networkName, macAddress: macAddress, mtu: mtu)\n    }\n\n    // MARK: DNS\n\n    public static func isValidDomainName(_ name: String) -> Bool {\n        guard !name.isEmpty && name.count <= 255 else {\n            return false\n        }\n        return name.components(separatedBy: \".\").allSatisfy { Self.isValidDomainNameLabel($0) }\n    }\n\n    public static func isValidDomainNameLabel(_ label: String) -> Bool {\n        guard !label.isEmpty && label.count <= 63 else {\n            return false\n        }\n        let pattern = #/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/#\n        return !label.ranges(of: pattern).isEmpty\n    }\n\n    private static let ulimitNameToRlimit: [String: String] = [\n        \"core\": \"RLIMIT_CORE\",\n        \"cpu\": \"RLIMIT_CPU\",\n        \"data\": \"RLIMIT_DATA\",\n        \"fsize\": \"RLIMIT_FSIZE\",\n        \"locks\": \"RLIMIT_LOCKS\",\n        \"memlock\": \"RLIMIT_MEMLOCK\",\n        \"msgqueue\": \"RLIMIT_MSGQUEUE\",\n        \"nice\": \"RLIMIT_NICE\",\n        \"nofile\": \"RLIMIT_NOFILE\",\n        \"nproc\": \"RLIMIT_NPROC\",\n        \"rss\": \"RLIMIT_RSS\",\n        \"rtprio\": \"RLIMIT_RTPRIO\",\n        \"rttime\": \"RLIMIT_RTTIME\",\n        \"sigpending\": \"RLIMIT_SIGPENDING\",\n        \"stack\": \"RLIMIT_STACK\",\n    ]\n\n    /// Parse ulimit specifications into Rlimit objects\n    /// Format: <type>=<soft>[:<hard>]\n    /// Examples:\n    ///   - nofile=1024:2048  (soft=1024, hard=2048)\n    ///   - nofile=1024       (soft=hard=1024)\n    ///   - nofile=unlimited  (soft=hard=UINT64_MAX)\n    ///   - nofile=1024:unlimited (soft=1024, hard=UINT64_MAX)\n    public static func rlimits(_ rawUlimits: [String]) throws -> [ProcessConfiguration.Rlimit] {\n        var rlimits: [ProcessConfiguration.Rlimit] = []\n        var seenTypes: Set<String> = []\n\n        for ulimit in rawUlimits {\n            let rlimit = try Parser.rlimit(ulimit)\n            if seenTypes.contains(rlimit.limit) {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"duplicate ulimit type: \\(ulimit.split(separator: \"=\").first ?? \"\")\"\n                )\n            }\n            seenTypes.insert(rlimit.limit)\n            rlimits.append(rlimit)\n        }\n\n        return rlimits\n    }\n\n    /// Parse a single ulimit specification\n    public static func rlimit(_ ulimit: String) throws -> ProcessConfiguration.Rlimit {\n        let parts = ulimit.split(separator: \"=\", maxSplits: 1)\n        guard parts.count == 2 else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"invalid ulimit format '\\(ulimit)': expected <type>=<soft>[:<hard>]\"\n            )\n        }\n\n        let typeName = String(parts[0]).lowercased()\n        let valuesPart = String(parts[1])\n\n        guard let rlimitType = ulimitNameToRlimit[typeName] else {\n            let validTypes = ulimitNameToRlimit.keys.sorted().joined(separator: \", \")\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"unsupported ulimit type '\\(typeName)': valid types are \\(validTypes)\"\n            )\n        }\n\n        let valueParts = valuesPart.split(separator: \":\", maxSplits: 1)\n        let soft: UInt64\n        let hard: UInt64\n\n        switch valueParts.count {\n        case 1:\n            // Single value: use for both soft and hard\n            soft = try parseRlimitValue(String(valueParts[0]), typeName: typeName)\n            hard = soft\n        case 2:\n            // Two values: soft:hard\n            soft = try parseRlimitValue(String(valueParts[0]), typeName: typeName)\n            hard = try parseRlimitValue(String(valueParts[1]), typeName: typeName)\n        default:\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"invalid ulimit format '\\(ulimit)': expected <type>=<soft>[:<hard>]\"\n            )\n        }\n\n        if soft > hard {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"ulimit '\\(typeName)' soft limit (\\(soft)) cannot exceed hard limit (\\(hard))\"\n            )\n        }\n\n        return ProcessConfiguration.Rlimit(limit: rlimitType, soft: soft, hard: hard)\n    }\n\n    private static func parseRlimitValue(_ value: String, typeName: String) throws -> UInt64 {\n        let trimmed = value.trimmingCharacters(in: .whitespaces).lowercased()\n\n        if trimmed == \"unlimited\" || trimmed == \"-1\" {\n            return UInt64.max\n        }\n\n        guard let parsed = UInt64(trimmed) else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"invalid ulimit value '\\(value)' for '\\(typeName)': must be a non-negative integer or 'unlimited'\"\n            )\n        }\n\n        return parsed\n    }\n\n    // MARK: Miscellaneous\n\n    public static func parseBool(string: String) -> Bool? {\n        let lower = string.lowercased()\n        switch lower {\n        case \"true\", \"t\": return true\n        case \"false\", \"f\": return false\n        default: return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ProcessIO.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\npublic struct ProcessIO: Sendable {\n    let stdin: Pipe?\n    let stdout: Pipe?\n    let stderr: Pipe?\n    var ioTracker: IoTracker?\n\n    static let signalSet: [Int32] = [\n        SIGTERM,\n        SIGINT,\n        SIGUSR1,\n        SIGUSR2,\n        SIGWINCH,\n    ]\n\n    public struct IoTracker: Sendable {\n        let stream: AsyncStream<Void>\n        let cont: AsyncStream<Void>.Continuation\n        let configuredStreams: Int\n    }\n\n    public let stdio: [FileHandle?]\n\n    public let console: Terminal?\n\n    public static func create(tty: Bool, interactive: Bool, detach: Bool) throws -> ProcessIO {\n        let current: Terminal? = try {\n            if !tty || !interactive {\n                return nil\n            }\n            let current = try Terminal(descriptor: STDIN_FILENO)\n            try current.setraw()\n            return current\n        }()\n\n        var stdio = [FileHandle?](repeating: nil, count: 3)\n\n        let stdin: Pipe? = {\n            if !interactive {\n                return nil\n            }\n            return Pipe()\n        }()\n\n        if let stdin {\n            let pin = FileHandle.standardInput\n            let stdinOSFile = OSFile(fd: pin.fileDescriptor)\n            let pipeOSFile = OSFile(fd: stdin.fileHandleForWriting.fileDescriptor)\n            try stdinOSFile.makeNonBlocking()\n            nonisolated(unsafe) let buf = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: Int(getpagesize()))\n\n            pin.readabilityHandler = { _ in\n                Self.streamStdin(\n                    from: stdinOSFile,\n                    to: pipeOSFile,\n                    buffer: buf,\n                ) {\n                    pin.readabilityHandler = nil\n                    buf.deallocate()\n                    try? stdin.fileHandleForWriting.close()\n                }\n            }\n            stdio[0] = stdin.fileHandleForReading\n        }\n\n        let stdout: Pipe? = {\n            if detach {\n                return nil\n            }\n            return Pipe()\n        }()\n\n        var configuredStreams = 0\n        let (stream, cc) = AsyncStream<Void>.makeStream()\n        if let stdout {\n            configuredStreams += 1\n\n            stdio[1] = stdout.fileHandleForWriting\n            let pout = FileHandle.standardOutput\n            let rout = stdout.fileHandleForReading\n            rout.readabilityHandler = { handle in\n                let data = handle.availableData\n                if data.isEmpty {\n                    rout.readabilityHandler = nil\n                    cc.yield()\n                    return\n                }\n                try! pout.write(contentsOf: data)\n            }\n        }\n\n        let stderr: Pipe? = {\n            if detach || tty {\n                return nil\n            }\n            return Pipe()\n        }()\n        if let stderr {\n            configuredStreams += 1\n            let perr: FileHandle = .standardError\n            let rerr = stderr.fileHandleForReading\n            rerr.readabilityHandler = { handle in\n                let data = handle.availableData\n                if data.isEmpty {\n                    rerr.readabilityHandler = nil\n                    cc.yield()\n                    return\n                }\n                try! perr.write(contentsOf: data)\n            }\n            stdio[2] = stderr.fileHandleForWriting\n        }\n\n        var ioTracker: IoTracker? = nil\n        if configuredStreams > 0 {\n            ioTracker = .init(stream: stream, cont: cc, configuredStreams: configuredStreams)\n        }\n\n        return .init(\n            stdin: stdin,\n            stdout: stdout,\n            stderr: stderr,\n            ioTracker: ioTracker,\n            stdio: stdio,\n            console: current\n        )\n    }\n\n    public func handleProcess(process: ClientProcess, log: Logger) async throws -> Int32 {\n        let signals = AsyncSignalHandler.create(notify: Self.signalSet)\n        return try await withThrowingTaskGroup(of: Int32?.self, returning: Int32.self) { group in\n            try await process.start()\n            try closeAfterStart()\n\n            let waitAdded = group.addTaskUnlessCancelled {\n                let code = try await process.wait()\n                try await wait()\n                return code\n            }\n\n            guard waitAdded else {\n                group.cancelAll()\n                return -1\n            }\n\n            if let current = console {\n                let size = try current.size\n                // It's supremely possible the process could've exited already. We shouldn't treat\n                // this as fatal.\n                try? await process.resize(size)\n                _ = group.addTaskUnlessCancelled {\n                    let winchHandler = AsyncSignalHandler.create(notify: [SIGWINCH])\n                    for await _ in winchHandler.signals {\n                        do {\n                            try await process.resize(try current.size)\n                        } catch {\n                            log.error(\n                                \"failed to send terminal resize event\",\n                                metadata: [\n                                    \"error\": \"\\(error)\"\n                                ]\n                            )\n                        }\n                    }\n                    return nil\n                }\n            } else {\n                _ = group.addTaskUnlessCancelled {\n                    for await sig in signals.signals {\n                        do {\n                            try await process.kill(sig)\n                        } catch {\n                            log.error(\n                                \"failed to send signal\",\n                                metadata: [\n                                    \"signal\": \"\\(sig)\",\n                                    \"error\": \"\\(error)\",\n                                ]\n                            )\n                        }\n                    }\n                    return nil\n                }\n            }\n\n            while true {\n                let result = try await group.next()\n                if result == nil {\n                    return -1\n                }\n                let status = result!\n                if let status {\n                    group.cancelAll()\n                    return status\n                }\n            }\n            return -1\n        }\n    }\n\n    public func closeAfterStart() throws {\n        try stdin?.fileHandleForReading.close()\n        try stdout?.fileHandleForWriting.close()\n        try stderr?.fileHandleForWriting.close()\n    }\n\n    public func close() throws {\n        try console?.reset()\n    }\n\n    public func wait() async throws {\n        guard let ioTracker = self.ioTracker else {\n            return\n        }\n        do {\n            try await Timeout.run(seconds: 3) {\n                var counter = ioTracker.configuredStreams\n                for await _ in ioTracker.stream {\n                    counter -= 1\n                    if counter == 0 {\n                        ioTracker.cont.finish()\n                        break\n                    }\n                }\n            }\n        } catch {\n            throw error\n        }\n    }\n\n    static func streamStdin(\n        from: OSFile,\n        to: OSFile,\n        buffer: UnsafeMutableBufferPointer<UInt8>,\n        onErrorOrEOF: () -> Void,\n    ) {\n        while true {\n            let (bytesRead, action) = from.read(buffer)\n            if bytesRead > 0 {\n                let view = UnsafeMutableBufferPointer(\n                    start: buffer.baseAddress,\n                    count: bytesRead\n                )\n\n                let (bytesWritten, _) = to.write(view)\n                if bytesWritten != bytesRead {\n                    onErrorOrEOF()\n                    return\n                }\n            }\n\n            switch action {\n            case .error(_), .eof, .brokenPipe:\n                onErrorOrEOF()\n                return\n            case .again:\n                return\n            case .success:\n                break\n            }\n        }\n    }\n}\n\npublic struct OSFile: Sendable {\n    private let fd: Int32\n\n    public enum IOAction: Equatable {\n        case eof\n        case again\n        case success\n        case brokenPipe\n        case error(_ errno: Int32)\n    }\n\n    public init(fd: Int32) {\n        self.fd = fd\n    }\n\n    public init(handle: FileHandle) {\n        self.fd = handle.fileDescriptor\n    }\n\n    func makeNonBlocking() throws {\n        let flags = fcntl(fd, F_GETFL)\n        guard flags != -1 else {\n            throw POSIXError.fromErrno()\n        }\n\n        if fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1 {\n            throw POSIXError.fromErrno()\n        }\n    }\n\n    func write(_ buffer: UnsafeMutableBufferPointer<UInt8>) -> (wrote: Int, action: IOAction) {\n        if buffer.count == 0 {\n            return (0, .success)\n        }\n\n        var bytesWrote: Int = 0\n        while true {\n            let n = Darwin.write(\n                self.fd,\n                buffer.baseAddress!.advanced(by: bytesWrote),\n                buffer.count - bytesWrote\n            )\n            if n == -1 {\n                if errno == EAGAIN || errno == EIO {\n                    return (bytesWrote, .again)\n                }\n                return (bytesWrote, .error(errno))\n            }\n\n            if n == 0 {\n                return (bytesWrote, .brokenPipe)\n            }\n\n            bytesWrote += n\n            if bytesWrote < buffer.count {\n                continue\n            }\n            return (bytesWrote, .success)\n        }\n    }\n\n    func read(_ buffer: UnsafeMutableBufferPointer<UInt8>) -> (read: Int, action: IOAction) {\n        if buffer.count == 0 {\n            return (0, .success)\n        }\n\n        var bytesRead: Int = 0\n        while true {\n            let n = Darwin.read(\n                self.fd,\n                buffer.baseAddress!.advanced(by: bytesRead),\n                buffer.count - bytesRead\n            )\n            if n == -1 {\n                if errno == EAGAIN || errno == EIO {\n                    return (bytesRead, .again)\n                }\n                return (bytesRead, .error(errno))\n            }\n\n            if n == 0 {\n                return (bytesRead, .eof)\n            }\n\n            bytesRead += n\n            if bytesRead < buffer.count {\n                continue\n            }\n            return (bytesRead, .success)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ProgressUpdateClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport ContainerizationExtras\nimport Foundation\nimport TerminalProgress\n\n/// A client that can be used to receive progress updates from a service.\npublic actor ProgressUpdateClient {\n    private var endpointConnection: xpc_connection_t?\n    private var endpoint: xpc_endpoint_t?\n\n    /// Creates a new client for receiving progress updates from a service.\n    /// - Parameters:\n    ///   - progressUpdate: The handler to invoke when progress updates are received.\n    ///   - request: The XPC message to send the endpoint to connect to.\n    public init(for progressUpdate: @escaping ProgressUpdateHandler, request: XPCMessage) async {\n        createEndpoint(for: progressUpdate)\n        setEndpoint(to: request)\n    }\n\n    /// Performs a connection setup for receiving progress updates.\n    /// - Parameter progressUpdate: The handler to invoke when progress updates are received.\n    private func createEndpoint(for progressUpdate: @escaping ProgressUpdateHandler) {\n        let endpointConnection = xpc_connection_create(nil, nil)\n        // Access to `reversedConnection` is protected by a lock\n        nonisolated(unsafe) var reversedConnection: xpc_connection_t?\n        let reversedConnectionLock = NSLock()\n        xpc_connection_set_event_handler(endpointConnection) { connectionMessage in\n            reversedConnectionLock.withLock {\n                switch xpc_get_type(connectionMessage) {\n                case XPC_TYPE_CONNECTION:\n                    reversedConnection = connectionMessage\n                    xpc_connection_set_event_handler(connectionMessage) { updateMessage in\n                        Self.handleProgressUpdate(updateMessage, progressUpdate: progressUpdate)\n                    }\n                    xpc_connection_activate(connectionMessage)\n                case XPC_TYPE_ERROR:\n                    if let reversedConnectionUnwrapped = reversedConnection {\n                        xpc_connection_cancel(reversedConnectionUnwrapped)\n                        reversedConnection = nil\n                    }\n                default:\n                    fatalError(\"unhandled xpc object type: \\(xpc_get_type(connectionMessage))\")\n                }\n            }\n        }\n        xpc_connection_activate(endpointConnection)\n\n        self.endpointConnection = endpointConnection\n        self.endpoint = xpc_endpoint_create(endpointConnection)\n    }\n\n    /// Performs a setup of the progress update endpoint.\n    /// - Parameter request: The XPC message containing the endpoint to use.\n    private func setEndpoint(to request: XPCMessage) {\n        guard let endpoint else {\n            return\n        }\n        request.set(key: .progressUpdateEndpoint, value: endpoint)\n    }\n\n    /// Performs cleanup of the created connection.\n    public func finish() {\n        if let endpointConnection {\n            xpc_connection_cancel(endpointConnection)\n            self.endpointConnection = nil\n        }\n    }\n\n    private static func handleProgressUpdate(_ message: xpc_object_t, progressUpdate: @escaping ProgressUpdateHandler) {\n        switch xpc_get_type(message) {\n        case XPC_TYPE_DICTIONARY:\n            let message = XPCMessage(object: message)\n            handleProgressUpdate(message, progressUpdate: progressUpdate)\n        case XPC_TYPE_ERROR:\n            break\n        default:\n            fatalError(\"unhandled xpc object type: \\(xpc_get_type(message))\")\n            break\n        }\n    }\n\n    private static func handleProgressUpdate(_ message: XPCMessage, progressUpdate: @escaping ProgressUpdateHandler) {\n        var events = [ProgressUpdateEvent]()\n\n        if let description = message.string(key: .progressUpdateSetDescription) {\n            events.append(.setDescription(description))\n        }\n        if let subDescription = message.string(key: .progressUpdateSetSubDescription) {\n            events.append(.setSubDescription(subDescription))\n        }\n        if let itemsName = message.string(key: .progressUpdateSetItemsName) {\n            events.append(.setItemsName(itemsName))\n        }\n        var tasks = message.int(key: .progressUpdateAddTasks)\n        if tasks != 0 {\n            events.append(.addTasks(tasks))\n        }\n        tasks = message.int(key: .progressUpdateSetTasks)\n        if tasks != 0 {\n            events.append(.setTasks(tasks))\n        }\n        var totalTasks = message.int(key: .progressUpdateAddTotalTasks)\n        if totalTasks != 0 {\n            events.append(.addTotalTasks(totalTasks))\n        }\n        totalTasks = message.int(key: .progressUpdateSetTotalTasks)\n        if totalTasks != 0 {\n            events.append(.setTotalTasks(totalTasks))\n        }\n        var items = message.int(key: .progressUpdateAddItems)\n        if items != 0 {\n            events.append(.addItems(items))\n        }\n        items = message.int(key: .progressUpdateSetItems)\n        if items != 0 {\n            events.append(.setItems(items))\n        }\n        var totalItems = message.int(key: .progressUpdateAddTotalItems)\n        if totalItems != 0 {\n            events.append(.addTotalItems(totalItems))\n        }\n        totalItems = message.int(key: .progressUpdateSetTotalItems)\n        if totalItems != 0 {\n            events.append(.setTotalItems(totalItems))\n        }\n        var size = message.int64(key: .progressUpdateAddSize)\n        if size != 0 {\n            events.append(.addSize(size))\n        }\n        size = message.int64(key: .progressUpdateSetSize)\n        if size != 0 {\n            events.append(.setSize(size))\n        }\n        var totalSize = message.int64(key: .progressUpdateAddTotalSize)\n        if totalSize != 0 {\n            events.append(.addTotalSize(totalSize))\n        }\n        totalSize = message.int64(key: .progressUpdateSetTotalSize)\n        if totalSize != 0 {\n            events.append(.setTotalSize(totalSize))\n        }\n\n        Task {\n            await progressUpdate(events)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/ProgressUpdateService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport ContainerizationExtras\nimport Foundation\nimport TerminalProgress\n\n/// A service that sends progress updates to the client.\npublic actor ProgressUpdateService {\n    private let endpointConnection: xpc_connection_t\n\n    /// Creates a new instance for sending progress updates to the client.\n    /// - Parameter message: The XPC message that contains the endpoint to connect to.\n    public init?(message: XPCMessage) {\n        guard let progressUpdateEndpoint = message.endpoint(key: .progressUpdateEndpoint) else {\n            return nil\n        }\n        endpointConnection = xpc_connection_create_from_endpoint(progressUpdateEndpoint)\n        xpc_connection_set_event_handler(endpointConnection) { _ in }\n        // This connection will be closed by the client.\n        xpc_connection_activate(endpointConnection)\n    }\n\n    /// Performs a progress update.\n    /// - Parameter events: The events that represent the update.\n    public func handler(_ events: [ProgressUpdateEvent]) async {\n        let object = xpc_dictionary_create(nil, nil, 0)\n        let replyMessage = XPCMessage(object: object)\n        for event in events {\n            switch event {\n            case .setDescription(let description):\n                replyMessage.set(key: .progressUpdateSetDescription, value: description)\n            case .setSubDescription(let subDescription):\n                replyMessage.set(key: .progressUpdateSetSubDescription, value: subDescription)\n            case .setItemsName(let itemsName):\n                replyMessage.set(key: .progressUpdateSetItemsName, value: itemsName)\n            case .addTasks(let tasks):\n                replyMessage.set(key: .progressUpdateAddTasks, value: tasks)\n            case .setTasks(let tasks):\n                replyMessage.set(key: .progressUpdateSetTasks, value: tasks)\n            case .addTotalTasks(let totalTasks):\n                replyMessage.set(key: .progressUpdateAddTotalTasks, value: totalTasks)\n            case .setTotalTasks(let totalTasks):\n                replyMessage.set(key: .progressUpdateSetTotalTasks, value: totalTasks)\n            case .addSize(let size):\n                replyMessage.set(key: .progressUpdateAddSize, value: size)\n            case .setSize(let size):\n                replyMessage.set(key: .progressUpdateSetSize, value: size)\n            case .addTotalSize(let totalSize):\n                replyMessage.set(key: .progressUpdateAddTotalSize, value: totalSize)\n            case .setTotalSize(let totalSize):\n                replyMessage.set(key: .progressUpdateSetTotalSize, value: totalSize)\n            case .addItems(let items):\n                replyMessage.set(key: .progressUpdateAddItems, value: items)\n            case .setItems(let items):\n                replyMessage.set(key: .progressUpdateSetItems, value: items)\n            case .addTotalItems(let totalItems):\n                replyMessage.set(key: .progressUpdateAddTotalItems, value: totalItems)\n            case .setTotalItems(let totalItems):\n                replyMessage.set(key: .progressUpdateSetTotalItems, value: totalItems)\n            case .custom(_):\n                // Unsupported progress update event in XPC communication.\n                break\n            }\n        }\n        xpc_connection_send_message(endpointConnection, replyMessage.underlying)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/RequestScheme.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerizationError\n\n/// The URL scheme to be used for a HTTP request.\npublic enum RequestScheme: String, Sendable {\n    case http = \"http\"\n    case https = \"https\"\n\n    case auto = \"auto\"\n\n    public init(_ rawValue: String) throws {\n        switch rawValue {\n        case RequestScheme.http.rawValue:\n            self = .http\n        case RequestScheme.https.rawValue:\n            self = .https\n        case RequestScheme.auto.rawValue:\n            self = .auto\n        default:\n            throw ContainerizationError(.invalidArgument, message: \"unsupported scheme \\(rawValue)\")\n        }\n    }\n\n    /// Returns the prescribed protocol to use while making a HTTP request to a webserver\n    /// - Parameter host: The domain or IP address of the webserver\n    /// - Returns: RequestScheme\n    public func schemeFor(host: String) throws -> Self {\n        guard host.count > 0 else {\n            throw ContainerizationError(.invalidArgument, message: \"host cannot be empty\")\n        }\n        switch self {\n        case .http, .https:\n            return self\n        case .auto:\n            return Self.isInternalHost(host: host) ? .http : .https\n        }\n    }\n\n    /// Checks if the given `host` string is a private IP address\n    /// or a domain typically reachable only on the local system.\n    private static func isInternalHost(host: String) -> Bool {\n        if host.hasPrefix(\"localhost\") || host.hasPrefix(\"127.\") {\n            return true\n        }\n        if host.hasPrefix(\"192.168.\") || host.hasPrefix(\"10.\") {\n            return true\n        }\n        let regex = \"(^172\\\\.1[6-9]\\\\.)|(^172\\\\.2[0-9]\\\\.)|(^172\\\\.3[0-1]\\\\.)\"\n        if host.range(of: regex, options: .regularExpression) != nil {\n            return true\n        }\n        let dnsDomain = DefaultsStore.get(key: .defaultDNSDomain)\n        if host.hasSuffix(\".\\(dnsDomain)\") {\n            return true\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/SignalThreshold.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\n\n// For a lot of programs, they don't install their own signal handlers for\n// SIGINT/SIGTERM which poses a somewhat fun problem for containers. Because\n// they're pid 1 (doesn't matter that it isn't in the \"root\" pid namespace)\n// the default actions for SIGINT and SIGTERM now are nops. So this type gives\n// us an opportunity to set a threshold for a certain number of signals received\n// so we can have an escape hatch for users to escape their horrific mistake\n// of cat'ing /dev/urandom by exit(1)'ing :)\npublic struct SignalThreshold {\n    private let threshold: Int\n    private let signals: [Int32]\n    private var t: Task<(), Never>?\n\n    public init(\n        threshold: Int,\n        signals: [Int32],\n    ) {\n        self.threshold = threshold\n        self.signals = signals\n    }\n\n    // Start kicks off the signal watching. The passed in handler will\n    // run only once upon passing the threshold number passed in the constructor.\n    mutating public func start(handler: @Sendable @escaping () -> Void) {\n        let signals = self.signals\n        let threshold = self.threshold\n        self.t = Task {\n            var received = 0\n            let signalHandler = AsyncSignalHandler.create(notify: signals)\n            for await _ in signalHandler.signals {\n                received += 1\n                if received == threshold {\n                    handler()\n                    signalHandler.cancel()\n                    return\n                }\n            }\n        }\n    }\n\n    public func stop() {\n        self.t?.cancel()\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/String+Extensions.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension String {\n    public func fromISO8601DateString(to: String) -> String? {\n        if let date = fromISO8601Date() {\n            let dateformatTo = DateFormatter()\n            dateformatTo.dateFormat = to\n            return dateformatTo.string(from: date)\n        }\n        return nil\n    }\n\n    public func fromISO8601Date() -> Date? {\n        let iso8601DateFormatter = ISO8601DateFormatter()\n        iso8601DateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n        return iso8601DateFormatter.date(from: self)\n    }\n\n    public func isAbsolutePath() -> Bool {\n        self.starts(with: \"/\")\n    }\n\n    /// Trim all `char` characters from the left side of the string. Stops when encountering a character that\n    /// doesn't match `char`.\n    mutating public func trimLeft(char: Character) {\n        if self.isEmpty {\n            return\n        }\n        var trimTo = 0\n        for c in self {\n            if char != c {\n                break\n            }\n            trimTo += 1\n        }\n        if trimTo != 0 {\n            let index = self.index(self.startIndex, offsetBy: trimTo)\n            self = String(self[index...])\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/SystemHealth.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport SystemPackage\n\n/// Snapshot of the health of container services and resources\npublic struct SystemHealth: Sendable, Codable {\n    /// The full pathname of the application data root.\n    public let appRoot: URL\n\n    /// The full pathname of the application install root.\n    public let installRoot: URL\n\n    /// The full pathname of the application install root.\n    public let logRoot: FilePath?\n\n    /// The release version of the container services.\n    public let apiServerVersion: String\n\n    /// The Git commit ID for the container services.\n    public let apiServerCommit: String\n\n    /// The build type of the API server (debug|release).\n    public let apiServerBuild: String\n\n    /// The app name label returned by the server.\n    public let apiServerAppName: String\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/TableOutput.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\npublic struct TableOutput {\n    private let rows: [[String]]\n    private let spacing: Int\n\n    public init(\n        rows: [[String]],\n        spacing: Int = 2\n    ) {\n        self.rows = rows\n        self.spacing = spacing\n    }\n\n    public func format() -> String {\n        var output = \"\"\n        let maxLengths = self.maxLength()\n\n        for rowIndex in 0..<self.rows.count {\n            let row = self.rows[rowIndex]\n            for columnIndex in 0..<row.count - 1 {\n                let currentLength = (maxLengths[columnIndex] ?? 0) + self.spacing\n                let padded = row[columnIndex].padding(toLength: currentLength, withPad: \" \", startingAt: 0)\n                output += padded\n            }\n            // Skip padding for the last column.\n            output += row.last ?? \"\"\n            output += (rowIndex == self.rows.count - 1) ? \"\" : \"\\n\"\n        }\n        return output\n    }\n\n    /// Returns a mapping of column index and the maximum length of all elements belonging under that column.\n    private func maxLength() -> [Int: Int] {\n        var output: [Int: Int] = [:]\n        for row in self.rows {\n            for (i, column) in row.enumerated() {\n                let currentMax = output[i] ?? 0\n                output[i] = (column.count > currentMax) ? column.count : currentMax\n            }\n        }\n        return output\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/Utility.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport Logging\nimport TerminalProgress\n\npublic struct Utility {\n    static let publishedPortCountLimit = 64\n\n    private static let infraImages = [\n        DefaultsStore.get(key: .defaultBuilderImage),\n        DefaultsStore.get(key: .defaultInitImage),\n    ]\n\n    public static func createContainerID(name: String?) -> String {\n        guard let name else {\n            return UUID().uuidString.lowercased()\n        }\n        return name\n    }\n\n    public static func isInfraImage(name: String) -> Bool {\n        for infraImage in infraImages {\n            if name == infraImage {\n                return true\n            }\n        }\n        return false\n    }\n\n    public static func trimDigest(digest: String) -> String {\n        var digest = digest\n        digest.trimPrefix(\"sha256:\")\n        if digest.count > 24 {\n            digest = String(digest.prefix(24)) + \"...\"\n        }\n        return digest\n    }\n\n    public static func validEntityName(_ name: String) throws {\n        let pattern = #\"^[a-zA-Z0-9][a-zA-Z0-9_.-]+$\"#\n        let regex = try Regex(pattern)\n        if try regex.firstMatch(in: name) == nil {\n            throw ContainerizationError(.invalidArgument, message: \"invalid entity name \\(name)\")\n        }\n    }\n\n    public static func validMACAddress(_ macAddress: String) throws {\n        let pattern = #\"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$\"#\n        let regex = try Regex(pattern)\n        if try regex.firstMatch(in: macAddress) == nil {\n            throw ContainerizationError(.invalidArgument, message: \"invalid MAC address format \\(macAddress), expected format: XX:XX:XX:XX:XX:XX\")\n        }\n    }\n\n    public static func containerConfigFromFlags(\n        id: String,\n        image: String,\n        arguments: [String],\n        process: Flags.Process,\n        management: Flags.Management,\n        resource: Flags.Resource,\n        registry: Flags.Registry,\n        imageFetch: Flags.ImageFetch,\n        progressUpdate: @escaping ProgressUpdateHandler,\n        log: Logger\n    ) async throws -> (ContainerConfiguration, Kernel, String?) {\n        let requestedPlatform = try DefaultPlatform.resolveWithDefaults(\n            platform: management.platform,\n            os: management.os,\n            arch: management.arch,\n            log: log\n        )\n        let scheme = try RequestScheme(registry.scheme)\n\n        await progressUpdate([\n            .setDescription(\"Fetching image\"),\n            .setItemsName(\"blobs\"),\n        ])\n        let taskManager = ProgressTaskCoordinator()\n        let fetchTask = await taskManager.startTask()\n        let img = try await ClientImage.fetch(\n            reference: image,\n            platform: requestedPlatform,\n            scheme: scheme,\n            progressUpdate: ProgressTaskCoordinator.handler(for: fetchTask, from: progressUpdate),\n            maxConcurrentDownloads: imageFetch.maxConcurrentDownloads\n        )\n\n        // Unpack a fetched image before use\n        await progressUpdate([\n            .setDescription(\"Unpacking image\"),\n            .setItemsName(\"entries\"),\n        ])\n        let unpackTask = await taskManager.startTask()\n        try await img.getCreateSnapshot(\n            platform: requestedPlatform,\n            progressUpdate: ProgressTaskCoordinator.handler(for: unpackTask, from: progressUpdate))\n\n        await progressUpdate([\n            .setDescription(\"Fetching kernel\"),\n            .setItemsName(\"binary\"),\n        ])\n\n        let kernel = try await self.getKernel(management: management)\n\n        // Pull and unpack the initial filesystem\n        await progressUpdate([\n            .setDescription(\"Fetching init image\"),\n            .setItemsName(\"blobs\"),\n        ])\n        let fetchInitTask = await taskManager.startTask()\n        let initImageRef = management.initImage ?? ClientImage.initImageRef\n        let initImage = try await ClientImage.fetch(\n            reference: initImageRef, platform: .current, scheme: scheme,\n            progressUpdate: ProgressTaskCoordinator.handler(for: fetchInitTask, from: progressUpdate),\n            maxConcurrentDownloads: imageFetch.maxConcurrentDownloads)\n\n        await progressUpdate([\n            .setDescription(\"Unpacking init image\"),\n            .setItemsName(\"entries\"),\n        ])\n        let unpackInitTask = await taskManager.startTask()\n        _ = try await initImage.getCreateSnapshot(\n            platform: .current,\n            progressUpdate: ProgressTaskCoordinator.handler(for: unpackInitTask, from: progressUpdate))\n\n        await taskManager.finish()\n\n        let imageConfig = try await img.config(for: requestedPlatform).config\n        let description = img.description\n        let pc = try Parser.process(\n            arguments: arguments,\n            processFlags: process,\n            managementFlags: management,\n            config: imageConfig\n        )\n\n        var config = ContainerConfiguration(id: id, image: description, process: pc)\n        config.platform = requestedPlatform\n\n        config.resources = try Parser.resources(\n            cpus: resource.cpus,\n            memory: resource.memory\n        )\n\n        let tmpfs = try Parser.tmpfsMounts(management.tmpFs)\n        let volumesOrFs = try Parser.volumes(management.volumes)\n        let mountsOrFs = try Parser.mounts(management.mounts)\n\n        var resolvedMounts: [Filesystem] = []\n        resolvedMounts.append(contentsOf: tmpfs)\n\n        // Resolve volumes and filesystems\n        for item in (volumesOrFs + mountsOrFs) {\n            switch item {\n            case .filesystem(let fs):\n                resolvedMounts.append(fs)\n            case .volume(let parsed):\n                let volume = try await getOrCreateVolume(parsed: parsed, log: log)\n                let volumeMount = Filesystem.volume(\n                    name: parsed.name,\n                    format: volume.format,\n                    source: volume.source,\n                    destination: parsed.destination,\n                    options: parsed.options\n                )\n                resolvedMounts.append(volumeMount)\n            }\n        }\n\n        config.mounts = resolvedMounts\n\n        config.virtualization = management.virtualization\n\n        // Parse network specifications with properties\n        let parsedNetworks = try management.networks.map { try Parser.network($0) }\n        if management.networks.contains(ClientNetwork.noNetworkName) {\n            guard management.networks.count == 1 else {\n                throw ContainerizationError(.unsupported, message: \"no other networks may be created along with network \\(ClientNetwork.noNetworkName)\")\n            }\n            config.networks = []\n        } else {\n            let builtinNetworkId = try await ClientNetwork.builtin?.id\n            config.networks = try getAttachmentConfigurations(\n                containerId: config.id,\n                builtinNetworkId: builtinNetworkId,\n                networks: parsedNetworks\n            )\n            for attachmentConfiguration in config.networks {\n                let network: NetworkState = try await ClientNetwork.get(id: attachmentConfiguration.network)\n                guard case .running(_, _) = network else {\n                    throw ContainerizationError(.invalidState, message: \"network \\(attachmentConfiguration.network) is not running\")\n                }\n            }\n        }\n\n        if management.dnsDisabled {\n            config.dns = nil\n        } else {\n            let domain = management.dns.domain ?? DefaultsStore.getOptional(key: .defaultDNSDomain)\n            config.dns = .init(\n                nameservers: management.dns.nameservers,\n                domain: domain,\n                searchDomains: management.dns.searchDomains,\n                options: management.dns.options\n            )\n        }\n\n        config.rosetta = management.rosetta || (Platform.current.architecture == \"arm64\" && requestedPlatform.architecture == \"amd64\")\n\n        if management.rosetta && Platform.current.architecture != \"arm64\" {\n            throw ContainerizationError(.unsupported, message: \"--rosetta flag requires an arm64 host\")\n        }\n\n        config.labels = try Parser.labels(management.labels)\n\n        config.publishedPorts = try Parser.publishPorts(management.publishPorts)\n        guard config.publishedPorts.count <= publishedPortCountLimit else {\n            throw ContainerizationError(.invalidArgument, message: \"cannot exceed more than \\(publishedPortCountLimit) port publish descriptors\")\n        }\n        guard !config.publishedPorts.hasOverlaps() else {\n            throw ContainerizationError(.invalidArgument, message: \"host ports for different publish port specs may not overlap\")\n        }\n\n        // Parse --publish-socket arguments and add to container configuration\n        // to enable socket forwarding from container to host.\n        config.publishedSockets = try Parser.publishSockets(management.publishSockets)\n\n        config.ssh = management.ssh\n        config.readOnly = management.readOnly\n        config.useInit = management.useInit\n\n        if let runtime = management.runtime {\n            config.runtimeHandler = runtime\n        }\n\n        return (config, kernel, management.initImage)\n    }\n\n    static func getAttachmentConfigurations(\n        containerId: String,\n        builtinNetworkId: String?,\n        networks: [Parser.ParsedNetwork]\n    ) throws -> [AttachmentConfiguration] {\n        // Validate MAC addresses if provided\n        for network in networks {\n            if let mac = network.macAddress {\n                try validMACAddress(mac)\n            }\n        }\n\n        // make an FQDN for the first interface\n        let fqdn: String?\n        if !containerId.contains(\".\") {\n            // add default domain if it exists, and container ID is unqualified\n            if let dnsDomain = DefaultsStore.getOptional(key: .defaultDNSDomain) {\n                fqdn = \"\\(containerId).\\(dnsDomain).\"\n            } else {\n                fqdn = nil\n            }\n        } else {\n            // use container ID directly if fully qualified\n            fqdn = \"\\(containerId).\"\n        }\n\n        guard networks.isEmpty else {\n            // Check if this is only the default network with properties (e.g., MAC address)\n            let isOnlyDefaultNetwork = networks.count == 1 && networks[0].name == builtinNetworkId\n\n            // networks may only be specified for macOS 26+ (except for default network with properties)\n            if !isOnlyDefaultNetwork {\n                guard #available(macOS 26, *) else {\n                    throw ContainerizationError(.invalidArgument, message: \"non-default network configuration requires macOS 26 or newer\")\n                }\n            }\n\n            // attach the first network using the fqdn, and the rest using just the container ID\n            return try networks.enumerated().map { item in\n                let macAddress = try item.element.macAddress.map { try MACAddress($0) }\n                let mtu = item.element.mtu ?? 1280\n                guard item.offset == 0 else {\n                    return AttachmentConfiguration(\n                        network: item.element.name,\n                        options: AttachmentOptions(hostname: containerId, macAddress: macAddress, mtu: mtu)\n                    )\n                }\n                return AttachmentConfiguration(\n                    network: item.element.name,\n                    options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: macAddress, mtu: mtu)\n                )\n            }\n        }\n\n        // if no networks specified, attach to the default network\n        guard let builtinNetworkId else {\n            throw ContainerizationError(.invalidState, message: \"builtin network is not present\")\n        }\n        return [AttachmentConfiguration(network: builtinNetworkId, options: AttachmentOptions(hostname: fqdn ?? containerId, macAddress: nil, mtu: 1280))]\n    }\n\n    private static func getKernel(management: Flags.Management) async throws -> Kernel {\n        // For the image itself we'll take the user input and try with it as we can do userspace\n        // emulation for x86, but for the kernel we need it to match the hosts architecture.\n        let s: SystemPlatform = .current\n        if let userKernel = management.kernel {\n            guard FileManager.default.fileExists(atPath: userKernel) else {\n                throw ContainerizationError(.notFound, message: \"kernel file not found at path \\(userKernel)\")\n            }\n            let p = URL(filePath: userKernel)\n            return .init(path: p, platform: s)\n        }\n        return try await ClientKernel.getDefaultKernel(for: s)\n    }\n\n    /// Parses key-value pairs from command line arguments.\n    ///\n    /// Supports formats like \"key=value\" and standalone keys (treated as \"key=\").\n    /// - Parameter pairs: Array of strings in \"key=value\" format\n    /// - Returns: Dictionary mapping keys to values\n    public static func parseKeyValuePairs(_ pairs: [String]) -> [String: String] {\n        var result: [String: String] = [:]\n        for pair in pairs {\n            let components = pair.split(separator: \"=\", maxSplits: 1)\n            if components.count == 2 {\n                result[String(components[0])] = String(components[1])\n            } else {\n                result[pair] = \"\"\n            }\n        }\n        return result\n    }\n\n    /// Gets an existing volume or creates it if it doesn't exist.\n    /// Shows a warning for named volumes when auto-creating.\n    private static func getOrCreateVolume(parsed: ParsedVolume, log: Logger) async throws -> Volume {\n        let labels = parsed.isAnonymous ? [Volume.anonymousLabel: \"\"] : [:]\n\n        let volume: Volume\n        var wasCreated = false\n        do {\n            volume = try await ClientVolume.create(\n                name: parsed.name,\n                driver: \"local\",\n                driverOpts: [:],\n                labels: labels\n            )\n            wasCreated = true\n        } catch let error as VolumeError {\n            guard case .volumeAlreadyExists = error else {\n                throw error\n            }\n            // Volume already exists, just inspect it\n            volume = try await ClientVolume.inspect(parsed.name)\n        } catch let error as ContainerizationError {\n            // Handle XPC-wrapped volumeAlreadyExists error\n            guard error.message.contains(\"already exists\") else {\n                throw error\n            }\n            volume = try await ClientVolume.inspect(parsed.name)\n        }\n\n        if wasCreated && !parsed.isAnonymous {\n            log.warning(\"named volume was automatically created\", metadata: [\"volume\": \"\\(parsed.name)\"])\n        }\n\n        return volume\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Client/XPC+.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Foundation\nimport ContainerXPC\n\n/// Keys for XPC fields.\npublic enum XPCKeys: String {\n    /// Route key.\n    case route\n    /// Container array key.\n    case containers\n    /// ID key.\n    case id\n    // ID for a process.\n    case processIdentifier\n    /// Container configuration key.\n    case containerConfig\n    /// Container options key.\n    case containerOptions\n    /// Vsock port number key.\n    case port\n    /// Exit code for a process\n    case exitCode\n    /// Exit timestamp for a process\n    case exitedAt\n    /// An event that occurred in a container\n    case containerEvent\n    /// Error key.\n    case error\n    /// FD to a container resource key.\n    case fd\n    /// FDs pointing to container logs key.\n    case logs\n    /// Options for stopping a container key.\n    case stopOptions\n    /// Whether to force stop a container when deleting.\n    case forceDelete\n    /// Plugins\n    case pluginName\n    case plugins\n    case plugin\n    /// Archive path to export rootfs\n    case archive\n\n    /// Health check request.\n    case ping\n    case appRoot\n    case installRoot\n    case logRoot\n    case apiServerVersion\n    case apiServerCommit\n    case apiServerBuild\n    case apiServerAppName\n\n    /// Process request keys.\n    case signal\n    case snapshot\n    case stdin\n    case stdout\n    case stderr\n    case status\n    case width\n    case height\n    case processConfig\n\n    /// Update progress\n    case progressUpdateEndpoint\n    case progressUpdateSetDescription\n    case progressUpdateSetSubDescription\n    case progressUpdateSetItemsName\n    case progressUpdateAddTasks\n    case progressUpdateSetTasks\n    case progressUpdateAddTotalTasks\n    case progressUpdateSetTotalTasks\n    case progressUpdateAddItems\n    case progressUpdateSetItems\n    case progressUpdateAddTotalItems\n    case progressUpdateSetTotalItems\n    case progressUpdateAddSize\n    case progressUpdateSetSize\n    case progressUpdateAddTotalSize\n    case progressUpdateSetTotalSize\n\n    /// Network\n    case networkId\n    case networkConfig\n    case networkState\n    case networkStates\n\n    /// Kernel\n    case kernel\n    case kernelTarURL\n    case kernelFilePath\n    case systemPlatform\n    case kernelForce\n\n    /// Init image reference\n    case initImage\n\n    /// Volume\n    case volume\n    case volumes\n    case volumeName\n    case volumeSize\n    case volumeDriver\n    case volumeDriverOpts\n    case volumeLabels\n    case volumeReadonly\n    case volumeContainerId\n\n    /// Container statistics\n    case statistics\n    case containerSize\n\n    /// Container list filters\n    case listFilters\n\n    /// Disk usage\n    case diskUsageStats\n}\n\npublic enum XPCRoute: String {\n    case containerList\n    case containerCreate\n    case containerBootstrap\n    case containerCreateProcess\n    case containerStartProcess\n    case containerWait\n    case containerDelete\n    case containerStop\n    case containerDial\n    case containerResize\n    case containerKill\n    case containerState\n    case containerLogs\n    case containerEvent\n    case containerStats\n    case containerDiskUsage\n    case containerExport\n\n    case pluginLoad\n    case pluginGet\n    case pluginRestart\n    case pluginUnload\n    case pluginList\n\n    case networkCreate\n    case networkDelete\n    case networkList\n\n    case volumeCreate\n    case volumeDelete\n    case volumeList\n    case volumeInspect\n\n    case volumeDiskUsage\n    case systemDiskUsage\n\n    case ping\n\n    case installKernel\n    case getDefaultKernel\n}\n\nextension XPCMessage {\n    public init(route: XPCRoute) {\n        self.init(route: route.rawValue)\n    }\n\n    public func data(key: XPCKeys) -> Data? {\n        data(key: key.rawValue)\n    }\n\n    public func dataNoCopy(key: XPCKeys) -> Data? {\n        dataNoCopy(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: Data) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func string(key: XPCKeys) -> String? {\n        string(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: String) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func bool(key: XPCKeys) -> Bool {\n        bool(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: Bool) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func uint64(key: XPCKeys) -> UInt64 {\n        uint64(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: UInt64) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func int64(key: XPCKeys) -> Int64 {\n        int64(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: Int64) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func int(key: XPCKeys) -> Int {\n        Int(int64(key: key.rawValue))\n    }\n\n    public func set(key: XPCKeys, value: Int) {\n        set(key: key.rawValue, value: Int64(value))\n    }\n\n    public func date(key: XPCKeys) -> Date {\n        date(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: Date) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func fileHandle(key: XPCKeys) -> FileHandle? {\n        fileHandle(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: FileHandle) {\n        set(key: key.rawValue, value: value)\n    }\n\n    public func fileHandles(key: XPCKeys) -> [FileHandle]? {\n        fileHandles(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: [FileHandle]) throws {\n        try set(key: key.rawValue, value: value)\n    }\n\n    public func endpoint(key: XPCKeys) -> xpc_endpoint_t? {\n        endpoint(key: key.rawValue)\n    }\n\n    public func set(key: XPCKeys, value: xpc_endpoint_t) {\n        set(key: key.rawValue, value: value)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Containers/ContainersHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\npublic struct ContainersHarness: Sendable {\n    let log: Logging.Logger\n    let service: ContainersService\n\n    public init(service: ContainersService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func list(_ message: XPCMessage) async throws -> XPCMessage {\n        var filters = ContainerListFilters.all\n        if let filterData = message.dataNoCopy(key: .listFilters) {\n            filters = try JSONDecoder().decode(ContainerListFilters.self, from: filterData)\n        }\n        let containers = try await service.list(filters: filters)\n        let data = try JSONEncoder().encode(containers)\n\n        let reply = message.reply()\n        reply.set(key: .containers, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func bootstrap(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let stdio = message.stdio()\n        try await service.bootstrap(id: id, stdio: stdio)\n        return message.reply()\n    }\n\n    @Sendable\n    public func stop(_ message: XPCMessage) async throws -> XPCMessage {\n        let stopOptions = try message.stopOptions()\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        try await service.stop(id: id, options: stopOptions)\n        return message.reply()\n    }\n\n    @Sendable\n    public func dial(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n\n        let port = message.uint64(key: .port)\n        let fh = try await service.dial(id: id, port: UInt32(port))\n        let reply = message.reply()\n        reply.setFileHandle(fh)\n\n        return reply\n    }\n\n    @Sendable\n    public func wait(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let processID = message.string(key: .processIdentifier)\n        guard let processID else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"process ID cannot be empty\"\n            )\n        }\n\n        let exitStatus = try await service.wait(id: id, processID: processID)\n        let reply = message.reply()\n        reply.set(key: .exitCode, value: Int64(exitStatus.exitCode))\n        reply.set(key: .exitedAt, value: exitStatus.exitedAt)\n        return reply\n    }\n\n    @Sendable\n    public func resize(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let processID = message.string(key: .processIdentifier)\n        guard let processID else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"process ID cannot be empty\"\n            )\n        }\n\n        let width = message.uint64(key: .width)\n        let height = message.uint64(key: .height)\n        try await service.resize(\n            id: id,\n            processID: processID,\n            size: Terminal.Size(width: UInt16(width), height: UInt16(height))\n        )\n\n        return message.reply()\n    }\n\n    @Sendable\n    public func kill(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let processID = message.string(key: .processIdentifier)\n        guard let processID else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"process ID cannot be empty\"\n            )\n        }\n        try await service.kill(\n            id: id,\n            processID: processID,\n            signal: try message.signal()\n        )\n        return message.reply()\n    }\n\n    @Sendable\n    public func create(_ message: XPCMessage) async throws -> XPCMessage {\n        let data = message.dataNoCopy(key: .containerConfig)\n        guard let data else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"container configuration cannot be empty\"\n            )\n        }\n        let kdata = message.dataNoCopy(key: .kernel)\n        guard let kdata else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"kernel cannot be empty\"\n            )\n        }\n        let odata = message.dataNoCopy(key: .containerOptions)\n        var options: ContainerCreateOptions = .default\n        if let odata {\n            options = try JSONDecoder().decode(ContainerCreateOptions.self, from: odata)\n        }\n        let config = try JSONDecoder().decode(ContainerConfiguration.self, from: data)\n        let kernel = try JSONDecoder().decode(Kernel.self, from: kdata)\n\n        let initImage = message.string(key: .initImage)\n\n        try await service.create(configuration: config, kernel: kernel, options: options, initImage: initImage)\n        return message.reply()\n    }\n\n    @Sendable\n    public func createProcess(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let processID = message.string(key: .processIdentifier)\n        guard let processID else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"process ID cannot be empty\"\n            )\n        }\n        let config = try message.processConfig()\n        let stdio = message.stdio()\n\n        try await service.createProcess(\n            id: id,\n            processID: processID,\n            config: config,\n            stdio: stdio\n        )\n\n        return message.reply()\n    }\n\n    @Sendable\n    public func startProcess(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let processID = message.string(key: .processIdentifier)\n        guard let processID else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"process ID cannot be empty\"\n            )\n        }\n\n        try await service.startProcess(\n            id: id,\n            processID: processID,\n        )\n\n        return message.reply()\n    }\n\n    @Sendable\n    public func delete(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(.invalidArgument, message: \"id cannot be empty\")\n        }\n        let forceDelete = message.bool(key: .forceDelete)\n        try await service.delete(id: id, force: forceDelete)\n        return message.reply()\n    }\n\n    @Sendable\n    public func diskUsage(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let containerId = message.string(key: .id) else {\n            throw ContainerizationError(.invalidArgument, message: \"id cannot be empty\")\n        }\n\n        let size = try await service.containerDiskUsage(id: containerId)\n\n        let reply = message.reply()\n        reply.set(key: .containerSize, value: size)\n        return reply\n    }\n\n    @Sendable\n    public func logs(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let fds = try await service.logs(id: id)\n        let reply = message.reply()\n        try reply.set(key: .logs, value: fds)\n        return reply\n    }\n\n    @Sendable\n    public func stats(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let stats = try await service.stats(id: id)\n        let data = try JSONEncoder().encode(stats)\n        let reply = message.reply()\n        reply.set(key: .statistics, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func export(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .id)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"id cannot be empty\"\n            )\n        }\n        let archive = message.string(key: .archive)\n        guard let archive else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"archive cannot be empty\"\n            )\n        }\n        let archiveUrl = URL(fileURLWithPath: archive)\n\n        try await service.exportRootfs(id: id, archive: archiveUrl)\n        return message.reply()\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport CVersion\nimport ContainerAPIClient\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerSandboxServiceClient\nimport ContainerXPC\nimport Containerization\nimport ContainerizationEXT4\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport SystemPackage\n\npublic actor ContainersService {\n    struct ContainerState {\n        var snapshot: ContainerSnapshot\n        var client: SandboxClient?\n        var allocatedAttachments: [AllocatedAttachment]\n\n        func getClient() throws -> SandboxClient {\n            guard let client else {\n                var message = \"no sandbox client exists\"\n                if snapshot.status == .stopped {\n                    message += \": container is stopped\"\n                }\n                throw ContainerizationError(.invalidState, message: message)\n            }\n            return client\n        }\n    }\n\n    private static let machServicePrefix = \"com.apple.container\"\n    private static let launchdDomainString = try! ServiceManager.getDomainString()\n\n    private let log: Logger\n    private let debugHelpers: Bool\n    private let containerRoot: URL\n    private let pluginLoader: PluginLoader\n    private let runtimePlugins: [Plugin]\n    private let exitMonitor: ExitMonitor\n\n    private let lock: AsyncLock\n    private var containers: [String: ContainerState]\n\n    // FIXME: Find a better mechanism for services running on the APIServer to work with each other\n    private weak var networksService: NetworksService?\n\n    public init(\n        appRoot: URL,\n        pluginLoader: PluginLoader,\n        log: Logger,\n        debugHelpers: Bool = false\n    ) throws {\n        let containerRoot = appRoot.appendingPathComponent(\"containers\")\n        try FileManager.default.createDirectory(at: containerRoot, withIntermediateDirectories: true)\n        self.exitMonitor = ExitMonitor(log: log)\n        self.lock = AsyncLock(log: log)\n        self.containerRoot = containerRoot\n        self.pluginLoader = pluginLoader\n        self.log = log\n        self.debugHelpers = debugHelpers\n        self.runtimePlugins = pluginLoader.findPlugins().filter { $0.hasType(.runtime) }\n        self.containers = try Self.loadAtBoot(root: containerRoot, loader: pluginLoader, log: log)\n    }\n\n    public func setNetworksService(_ service: NetworksService) async {\n        self.networksService = service\n    }\n\n    static func loadAtBoot(root: URL, loader: PluginLoader, log: Logger) throws -> [String: ContainerState] {\n        var directories = try FileManager.default.contentsOfDirectory(\n            at: root,\n            includingPropertiesForKeys: [.isDirectoryKey]\n        )\n        directories = directories.filter {\n            $0.isDirectory\n        }\n\n        let runtimePlugins = loader.findPlugins().filter { $0.hasType(.runtime) }\n        var results = [String: ContainerState]()\n        for dir in directories {\n            do {\n                let (config, options) = try Self.getContainerConfiguration(at: dir)\n                if options?.autoRemove ?? false {\n                    let label = Self.fullLaunchdServiceLabel(\n                        runtimeName: config.runtimeHandler,\n                        instanceId: config.id)\n\n                    var status: Int32 = -1\n                    try? ServiceManager.deregister(fullServiceLabel: label, status: &status)\n                    if status == 0 {\n                        log.info(\n                            \"reap auto-remove container\",\n                            metadata: [\n                                \"id\": \"\\(config.id)\"\n                            ]\n                        )\n\n                        let bundle = ContainerResource.Bundle(path: dir)\n                        try? bundle.delete()\n                        continue\n                    }\n                }\n\n                let state = ContainerState(\n                    snapshot: .init(\n                        configuration: config,\n                        status: .stopped,\n                        networks: [],\n                        startedDate: nil\n                    ),\n                    allocatedAttachments: []\n                )\n                results[config.id] = state\n                guard runtimePlugins.first(where: { $0.name == config.runtimeHandler }) != nil else {\n                    throw ContainerizationError(\n                        .internalError,\n                        message: \"failed to find runtime plugin \\(config.runtimeHandler)\"\n                    )\n                }\n            } catch {\n                try? FileManager.default.removeItem(at: dir)\n                log.warning(\n                    \"failed to load container\",\n                    metadata: [\n                        \"path\": \"\\(dir.path)\",\n                        \"error\": \"\\(error)\",\n                    ])\n            }\n        }\n        return results\n    }\n\n    /// List containers matching the given filters.\n    public func list(filters: ContainerListFilters = .all) async throws -> [ContainerSnapshot] {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n\n        return self.containers.values.compactMap { state -> ContainerSnapshot? in\n            let snapshot = state.snapshot\n\n            if !filters.ids.isEmpty {\n                guard filters.ids.contains(snapshot.id) else {\n                    return nil\n                }\n            }\n\n            if let status = filters.status {\n                guard snapshot.status == status else {\n                    return nil\n                }\n            }\n\n            for (key, value) in filters.labels {\n                guard snapshot.configuration.labels[key] == value else {\n                    return nil\n                }\n            }\n\n            return snapshot\n        }\n    }\n\n    /// Execute an operation with the current container list while maintaining atomicity\n    /// This prevents race conditions where containers are created during the operation\n    public func withContainerList<T: Sendable>(\n        logMetadata: Logger.Metadata? = nil,\n        _ operation: @Sendable @escaping ([ContainerSnapshot]) async throws -> T\n    ) async throws -> T {\n        try await lock.withLock(logMetadata: logMetadata) { context in\n            let snapshots = await self.containers.values.map { $0.snapshot }\n            return try await operation(snapshots)\n        }\n    }\n\n    /// Calculate disk usage for containers\n    /// - Returns: Tuple of (total count, active count, total size, reclaimable size)\n    public func calculateDiskUsage() async -> (Int, Int, UInt64, UInt64) {\n        await lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\"]) { _ in\n            var totalSize: UInt64 = 0\n            var reclaimableSize: UInt64 = 0\n            var activeCount = 0\n\n            for (id, state) in await self.containers {\n                let bundlePath = self.containerRoot.appendingPathComponent(id)\n                let containerSize = Self.calculateDirectorySize(at: bundlePath.path)\n                totalSize += containerSize\n\n                if state.snapshot.status == .running {\n                    activeCount += 1\n                } else {\n                    // Stopped containers are reclaimable\n                    reclaimableSize += containerSize\n                }\n            }\n\n            return (await self.containers.count, activeCount, totalSize, reclaimableSize)\n        }\n    }\n\n    /// Get set of image references used by containers (for disk usage calculation)\n    /// - Returns: Set of image references currently in use\n    public func getActiveImageReferences() async -> Set<String> {\n        await lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\"]) { _ in\n            var imageRefs = Set<String>()\n            for (_, state) in await self.containers {\n                imageRefs.insert(state.snapshot.configuration.image.reference)\n            }\n            return imageRefs\n        }\n    }\n\n    /// Calculate directory size using APFS-aware resource keys\n    /// - Parameter path: Path to directory\n    /// - Returns: Total allocated size in bytes\n    private static nonisolated func calculateDirectorySize(at path: String) -> UInt64 {\n        let url = URL(fileURLWithPath: path)\n        let fileManager = FileManager.default\n\n        guard\n            let enumerator = fileManager.enumerator(\n                at: url,\n                includingPropertiesForKeys: [.totalFileAllocatedSizeKey],\n                options: [.skipsHiddenFiles]\n            )\n        else {\n            return 0\n        }\n\n        var totalSize: UInt64 = 0\n        for case let fileURL as URL in enumerator {\n            guard\n                let resourceValues = try? fileURL.resourceValues(\n                    forKeys: [.totalFileAllocatedSizeKey]\n                ),\n                let fileSize = resourceValues.totalFileAllocatedSize\n            else {\n                continue\n            }\n            totalSize += UInt64(fileSize)\n        }\n\n        return totalSize\n    }\n\n    /// Create a new container from the provided id and configuration.\n    public func create(configuration: ContainerConfiguration, kernel: Kernel, options: ContainerCreateOptions, initImage: String? = nil) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(configuration.id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(configuration.id)\",\n                ]\n            )\n        }\n\n        try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(configuration.id)\"]) { context in\n            guard await self.containers[configuration.id] == nil else {\n                throw ContainerizationError(\n                    .exists,\n                    message: \"container already exists: \\(configuration.id)\"\n                )\n            }\n\n            var allHostnames = Set<String>()\n            for container in await self.containers.values {\n                for attachmentConfiguration in container.snapshot.configuration.networks {\n                    allHostnames.insert(attachmentConfiguration.options.hostname)\n                }\n            }\n\n            var conflictingHostnames = [String]()\n            for attachmentConfiguration in configuration.networks {\n                if allHostnames.contains(attachmentConfiguration.options.hostname) {\n                    conflictingHostnames.append(attachmentConfiguration.options.hostname)\n                }\n            }\n\n            guard conflictingHostnames.isEmpty else {\n                throw ContainerizationError(\n                    .exists,\n                    message: \"hostname(s) already exist: \\(conflictingHostnames)\"\n                )\n            }\n\n            guard self.runtimePlugins.first(where: { $0.name == configuration.runtimeHandler }) != nil else {\n                throw ContainerizationError(\n                    .notFound,\n                    message: \"unable to locate runtime plugin \\(configuration.runtimeHandler)\"\n                )\n            }\n\n            // Protect against a user providing a memory amount that will cause us to not be able\n            // to boot. We can go lower, but this is a somewhat safe threshold. Containerization\n            // also gives a little bit extra than the user asked for to account for guest agent overhead.\n            //\n            // NOTE: We could potentially leave this validation to the sandbox service(s), as\n            // it's possible there could be an implementation that can get away with a lower\n            // amount and be perfectly safe.\n            let minimumMemory: UInt64 = 200.mib()\n            guard configuration.resources.memoryInBytes >= minimumMemory else {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"minimum memory amount allowed is 200 MiB (got \\(configuration.resources.memoryInBytes) bytes)\"\n                )\n            }\n\n            let path = self.containerRoot.appendingPathComponent(configuration.id)\n            let systemPlatform = kernel.platform\n\n            // Fetch init image (custom or default)\n            self.log.debug(\n                \"ContainersService: get init block\",\n                metadata: [\n                    \"id\": \"\\(configuration.id)\"\n                ]\n            )\n            let initFilesystem = try await self.getInitBlock(for: systemPlatform.ociPlatform(), imageRef: initImage)\n\n            do {\n                self.log.debug(\n                    \"create snapshot\",\n                    metadata: [\n                        \"id\": \"\\(configuration.id)\",\n                        \"ref\": \"\\(configuration.image.reference)\",\n                    ])\n                let containerImage = ClientImage(description: configuration.image)\n                let imageFs = try await options.rootFsOverride == nil ? containerImage.getCreateSnapshot(platform: configuration.platform) : nil\n\n                self.log.debug(\n                    \"configure runtime\",\n                    metadata: [\n                        \"id\": \"\\(configuration.id)\",\n                        \"kernel\": \"\\(kernel.path)\",\n                        \"initfs\": \"\\(initImage ?? ClientImage.initImageRef)\",\n                    ])\n                let runtimeConfig = RuntimeConfiguration(\n                    path: path,\n                    initialFilesystem: initFilesystem,\n                    kernel: kernel,\n                    containerConfiguration: configuration,\n                    containerRootFilesystem: imageFs,\n                    options: options\n                )\n\n                try runtimeConfig.writeRuntimeConfiguration()\n\n                let snapshot = ContainerSnapshot(\n                    configuration: configuration,\n                    status: .stopped,\n                    networks: [],\n                    startedDate: nil\n                )\n                await self.setContainerState(configuration.id, ContainerState(snapshot: snapshot, allocatedAttachments: []), context: context)\n            } catch {\n                throw error\n            }\n        }\n    }\n\n    /// Bootstrap the init process of the container.\n    public func bootstrap(id: String, stdio: [FileHandle?]) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\"]) { context in\n            var state = try await self.getContainerState(id: id, context: context)\n\n            // We've already bootstrapped this container. Ideally we should be able to\n            // return some sort of error code from the sandbox svc to check here, but this\n            // is also a very simple check and faster than doing an rpc to get the same result.\n            if state.client != nil {\n                return\n            }\n\n            let path = self.containerRoot.appendingPathComponent(id)\n            let (config, _) = try Self.getContainerConfiguration(at: path)\n\n            var allocatedAttachments = [AllocatedAttachment]()\n            do {\n                for n in config.networks {\n                    let allocatedAttach = try await self.networksService?.allocate(\n                        id: n.network,\n                        hostname: n.options.hostname,\n                        macAddress: n.options.macAddress\n                    )\n                    guard var allocatedAttach = allocatedAttach else {\n                        throw ContainerizationError(.internalError, message: \"failed to allocate a network\")\n                    }\n\n                    if let mtu = n.options.mtu {\n                        let a = allocatedAttach.attachment\n                        allocatedAttach = AllocatedAttachment(\n                            attachment: Attachment(\n                                network: a.network,\n                                hostname: a.hostname,\n                                ipv4Address: a.ipv4Address,\n                                ipv4Gateway: a.ipv4Gateway,\n                                ipv6Address: a.ipv6Address,\n                                macAddress: a.macAddress,\n                                mtu: mtu\n                            ),\n                            additionalData: allocatedAttach.additionalData,\n                            pluginInfo: allocatedAttach.pluginInfo\n                        )\n                    }\n                    allocatedAttachments.append(allocatedAttach)\n                }\n\n                try Self.registerService(\n                    plugin: self.runtimePlugins.first { $0.name == config.runtimeHandler }!,\n                    loader: self.pluginLoader,\n                    configuration: config,\n                    path: path,\n                    debug: self.debugHelpers\n                )\n\n                let runtime = state.snapshot.configuration.runtimeHandler\n                let sandboxClient = try await SandboxClient.create(\n                    id: id,\n                    runtime: runtime\n                )\n                try await sandboxClient.bootstrap(stdio: stdio, allocatedAttachments: allocatedAttachments)\n\n                try await self.exitMonitor.registerProcess(\n                    id: id,\n                    onExit: self.handleContainerExit\n                )\n\n                state.client = sandboxClient\n                state.allocatedAttachments = allocatedAttachments\n                await self.setContainerState(id, state, context: context)\n            } catch {\n                for allocatedAttach in allocatedAttachments {\n                    do {\n                        try await self.networksService?.deallocate(attachment: allocatedAttach.attachment)\n                    } catch {\n                        self.log.error(\n                            \"failed to deallocate network attachment\",\n                            metadata: [\n                                \"id\": \"\\(id)\",\n                                \"network\": \"\\(allocatedAttach.attachment.network)\",\n                                \"error\": \"\\(error)\",\n                            ])\n                    }\n                }\n\n                let label = Self.fullLaunchdServiceLabel(\n                    runtimeName: config.runtimeHandler,\n                    instanceId: id\n                )\n\n                await self.exitMonitor.stopTracking(id: id)\n                try? ServiceManager.deregister(fullServiceLabel: label)\n                throw error\n            }\n        }\n    }\n\n    /// Create a new process in the container.\n    public func createProcess(\n        id: String,\n        processID: String,\n        config: ProcessConfiguration,\n        stdio: [FileHandle?]\n    ) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"processId\": \"\\(processID)\",\n                \"command\": \"\\(config.arguments.isEmpty ? \"\" : config.arguments[0])\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        try await client.createProcess(\n            processID,\n            config: config,\n            stdio: stdio\n        )\n    }\n\n    /// Start a process in a container. This can either be a process created via\n    /// createProcess, or the init process of the container which requires\n    /// id == processID.\n    public func startProcess(id: String, processID: String) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"processId\": \"\\(processID)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                    \"processId\": \"\\(processID)\",\n                ]\n            )\n        }\n\n        try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\", \"processId\": \"\\(processID)\"]) { context in\n            var state = try await self.getContainerState(id: id, context: context)\n\n            let isInit = Self.isInitProcess(id: id, processID: processID)\n            if state.snapshot.status == .running && isInit {\n                return\n            }\n\n            let client = try state.getClient()\n            try await client.startProcess(processID)\n\n            guard isInit else {\n                return\n            }\n\n            do {\n                let log = self.log\n                let waitFunc: ExitMonitor.WaitHandler = {\n                    log.info(\"registering container with exit monitor\")\n                    let code = try await client.wait(id)\n                    log.info(\n                        \"container finished in exit monitor\",\n                        metadata: [\n                            \"id\": \"\\(id)\",\n                            \"rc\": \"\\(code)\",\n                        ])\n\n                    return code\n                }\n                try await self.exitMonitor.track(id: id, waitingOn: waitFunc)\n\n                let sandboxSnapshot = try await client.state()\n                state.snapshot.status = .running\n                state.snapshot.networks = sandboxSnapshot.networks\n                state.snapshot.startedDate = Date()\n                await self.setContainerState(id, state, context: context)\n            } catch {\n                await self.exitMonitor.stopTracking(id: id)\n                try? await client.stop(options: ContainerStopOptions.default)\n                throw error\n            }\n        }\n    }\n\n    /// Send a signal to the container.\n    public func kill(id: String, processID: String, signal: Int64) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"processId\": \"\\(processID)\",\n                \"signal\": \"\\(signal)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                    \"processId\": \"\\(processID)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        try await client.kill(processID, signal: signal)\n    }\n\n    /// Stop all containers inside the sandbox, aborting any processes currently\n    /// executing inside the container, before stopping the underlying sandbox.\n    public func stop(id: String, options: ContainerStopOptions) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n\n        // Stop should be idempotent.\n        let client: SandboxClient\n        do {\n            client = try state.getClient()\n        } catch {\n            return\n        }\n\n        do {\n            try await client.stop(options: options)\n        } catch let err as ContainerizationError {\n            if err.code != .interrupted {\n                throw err\n            }\n        }\n        try await handleContainerExit(id: id)\n    }\n\n    public func dial(id: String, port: UInt32) async throws -> FileHandle {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"port\": \"\\(port)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                    \"port\": \"\\(port)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        return try await client.dial(port)\n    }\n\n    /// Wait waits for the container's init process or exec to exit and returns the\n    /// exit status.\n    public func wait(id: String, processID: String) async throws -> ExitStatus {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"processId\": \"\\(processID)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                    \"processId\": \"\\(processID)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        return try await client.wait(processID)\n    }\n\n    /// Resize resizes the container's PTY if one exists.\n    public func resize(id: String, processID: String, size: Terminal.Size) async throws {\n        log.trace(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"processId\": \"\\(processID)\",\n            ]\n        )\n        defer {\n            log.trace(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                    \"processId\": \"\\(processID)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        try await client.resize(processID, size: size)\n    }\n\n    // Get the logs for the container.\n    public func logs(id: String) async throws -> [FileHandle] {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        // Logs doesn't care if the container is running or not, just that\n        // the bundle is there, and that the files actually exist. We do\n        // first try and get the container state so we get a nicer error message\n        // (container foo not found) however.\n        do {\n            _ = try _getContainerState(id: id)\n            let path = self.containerRoot.appendingPathComponent(id)\n            let bundle = ContainerResource.Bundle(path: path)\n            return [\n                try FileHandle(forReadingFrom: bundle.containerLog),\n                try FileHandle(forReadingFrom: bundle.bootlog),\n            ]\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to open container logs: \\(error)\"\n            )\n        }\n    }\n\n    /// Get statistics for the container.\n    public func stats(id: String) async throws -> ContainerStats {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        let client = try state.getClient()\n        return try await client.statistics()\n    }\n\n    /// Delete a container and its resources.\n    public func delete(id: String, force: Bool) async throws {\n        log.info(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n                \"force\": \"\\(force)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        let state = try self._getContainerState(id: id)\n        switch state.snapshot.status {\n        case .running:\n            if !force {\n                throw ContainerizationError(\n                    .invalidState,\n                    message: \"container \\(id) is \\(state.snapshot.status) and can not be deleted\"\n                )\n            }\n            let opts = ContainerStopOptions(\n                timeoutInSeconds: 5,\n                signal: SIGKILL\n            )\n            let client = try state.getClient()\n            try await client.stop(options: opts)\n            try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\"]) { context in\n                self.log.info(\n                    \"ContainersService: attempt cleanup\",\n                    metadata: [\n                        \"func\": \"\\(#function)\",\n                        \"id\": \"\\(id)\",\n                    ]\n                )\n                try await self.cleanUp(id: id, context: context)\n                self.log.info(\n                    \"ContainersService: successful cleanup\",\n                    metadata: [\n                        \"func\": \"\\(#function)\",\n                        \"id\": \"\\(id)\",\n                    ]\n                )\n            }\n        case .stopping:\n            throw ContainerizationError(\n                .invalidState,\n                message: \"container \\(id) is \\(state.snapshot.status) and can not be deleted\"\n            )\n        default:\n            try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\"]) { context in\n                try await self.cleanUp(id: id, context: context)\n            }\n        }\n    }\n\n    public func containerDiskUsage(id: String) async throws -> UInt64 {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        let containerPath = self.containerRoot.appendingPathComponent(id).path\n\n        return Self.calculateDirectorySize(at: containerPath)\n    }\n\n    public func exportRootfs(id: String, archive: URL) async throws {\n        self.log.debug(\"\\(#function)\")\n\n        let state = try self._getContainerState(id: id)\n        guard state.snapshot.status == .stopped else {\n            throw ContainerizationError(.invalidState, message: \"container is not stopped\")\n        }\n\n        let path = self.containerRoot.appendingPathComponent(id)\n        let bundle = ContainerResource.Bundle(path: path)\n        let rootfs = bundle.containerRootfsBlock\n        try EXT4.EXT4Reader(blockDevice: FilePath(rootfs)).export(archive: FilePath(archive))\n    }\n\n    private func handleContainerExit(id: String, code: ExitStatus? = nil) async throws {\n        try await self.lock.withLock(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\"]) { [self] context in\n            try await handleContainerExit(id: id, code: code, context: context)\n        }\n    }\n\n    private func handleContainerExit(id: String, code: ExitStatus?, context: AsyncLock.Context) async throws {\n        if let code {\n            self.log.info(\n                \"handling container exit\",\n                metadata: [\n                    \"id\": \"\\(id)\",\n                    \"rc\": \"\\(code)\",\n                ])\n        }\n\n        var state: ContainerState\n        do {\n            state = try self.getContainerState(id: id, context: context)\n            if state.snapshot.status == .stopped {\n                return\n            }\n        } catch {\n            // Was auto removed by the background thread, nothing for us to do.\n            return\n        }\n\n        await self.exitMonitor.stopTracking(id: id)\n\n        // Shutdown and deregister the sandbox service\n        self.log.info(\"shutting down sandbox service\", metadata: [\"id\": \"\\(id)\"])\n\n        let path = self.containerRoot.appendingPathComponent(id)\n        let bundle = ContainerResource.Bundle(path: path)\n        let config = try bundle.configuration\n        let label = Self.fullLaunchdServiceLabel(\n            runtimeName: config.runtimeHandler,\n            instanceId: id\n        )\n\n        // Try to shutdown the client gracefully, but if the sandbox service\n        // is already dead (e.g., killed externally), we should still continue\n        // with state cleanup.\n        if let client = state.client {\n            do {\n                try await client.shutdown()\n            } catch {\n                self.log.error(\n                    \"failed to shutdown sandbox service\",\n                    metadata: [\n                        \"id\": \"\\(id)\",\n                        \"error\": \"\\(error)\",\n                    ])\n            }\n        }\n\n        // Deregister the service, launchd will terminate the process.\n        // This may also fail if the service was already deregistered or\n        // the process was killed externally.\n        do {\n            try ServiceManager.deregister(fullServiceLabel: label)\n            self.log.info(\"deregistered sandbox service\", metadata: [\"id\": \"\\(id)\"])\n        } catch {\n            self.log.error(\n                \"failed to deregister sandbox service\",\n                metadata: [\n                    \"id\": \"\\(id)\",\n                    \"error\": \"\\(error)\",\n                ])\n        }\n\n        // Best effort deallocate network attachments for the container. Don't throw on\n        // failure so we can continue with state cleanup.\n        self.log.info(\"deallocating network attachments\", metadata: [\"id\": \"\\(id)\"])\n        for allocatedAttach in state.allocatedAttachments {\n            do {\n                try await self.networksService?.deallocate(attachment: allocatedAttach.attachment)\n            } catch {\n                self.log.error(\n                    \"failed to deallocate network attachment\",\n                    metadata: [\n                        \"id\": \"\\(id)\",\n                        \"network\": \"\\(allocatedAttach.attachment.network)\",\n                        \"error\": \"\\(error)\",\n                    ])\n            }\n        }\n\n        state.snapshot.status = .stopped\n        state.snapshot.networks = []\n        state.client = nil\n        state.allocatedAttachments = []\n        await self.setContainerState(id, state, context: context)\n\n        let options = try getContainerCreationOptions(id: id)\n        if options.autoRemove {\n            try await self.cleanUp(id: id, context: context)\n        }\n    }\n\n    private static func fullLaunchdServiceLabel(runtimeName: String, instanceId: String) -> String {\n        \"\\(Self.launchdDomainString)/\\(Self.machServicePrefix).\\(runtimeName).\\(instanceId)\"\n    }\n\n    private func _cleanUp(id: String) async throws {\n        log.debug(\n            \"ContainersService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"ContainersService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        // Did the exit container handler win?\n        if self.containers[id] == nil {\n            return\n        }\n\n        // To be pedantic. This is only needed if something in the \"launch\n        // the init process\" lifecycle fails before actually fork+exec'ing\n        // the OCI runtime.\n        await self.exitMonitor.stopTracking(id: id)\n        let path = self.containerRoot.appendingPathComponent(id)\n\n        // Try to get config for service deregistration\n        // Don't fail if bundle is incomplete\n        var config: ContainerConfiguration?\n        let bundle = ContainerResource.Bundle(path: path)\n        do {\n            config = try bundle.configuration\n        } catch {\n            self.log.warning(\n                \"failed to read bundle configuration during cleanup for container\",\n                metadata: [\n                    \"id\": \"\\(id)\",\n                    \"error\": \"\\(error)\",\n                ])\n        }\n\n        // Only try to deregister service if we have a valid config\n        // TODO: Change this so we don't have to reread the config\n        // possibly store the container ID to service label mapping\n        if let config = config {\n            let label = Self.fullLaunchdServiceLabel(\n                runtimeName: config.runtimeHandler,\n                instanceId: id\n            )\n            try? ServiceManager.deregister(fullServiceLabel: label)\n        }\n\n        // Always try to delete the bundle directory, even if it's incomplete\n        do {\n            try bundle.delete()\n        } catch {\n            self.log.warning(\n                \"failed to delete bundle for container\",\n                metadata: [\n                    \"id\": \"\\(id)\",\n                    \"error\": \"\\(error)\",\n                ])\n        }\n\n        self.containers.removeValue(forKey: id)\n    }\n\n    private func cleanUp(id: String, context: AsyncLock.Context) async throws {\n        try await self._cleanUp(id: id)\n    }\n\n    private func getContainerCreationOptions(id: String) throws -> ContainerCreateOptions {\n        let path = self.containerRoot.appendingPathComponent(id)\n        let bundle = ContainerResource.Bundle(path: path)\n        let options: ContainerCreateOptions = try bundle.load(filename: \"options.json\")\n        return options\n    }\n\n    private func getInitBlock(for platform: Platform, imageRef: String? = nil) async throws -> Filesystem {\n        let ref = imageRef ?? ClientImage.initImageRef\n        let initImage = try await ClientImage.fetch(reference: ref, platform: platform)\n        var fs = try await initImage.getCreateSnapshot(platform: platform)\n        fs.options = [\"ro\"]\n        return fs\n    }\n\n    private static func registerService(\n        plugin: Plugin,\n        loader: PluginLoader,\n        configuration: ContainerConfiguration,\n        path: URL,\n        debug: Bool\n    ) throws {\n        let args = [\n            \"start\",\n            \"--root\", path.path,\n            \"--uuid\", configuration.id,\n            debug ? \"--debug\" : nil,\n        ].compactMap { $0 }\n        try loader.registerWithLaunchd(\n            plugin: plugin,\n            pluginStateRoot: path,\n            args: args,\n            instanceId: configuration.id\n        )\n    }\n\n    private func setContainerState(_ id: String, _ state: ContainerState, context: AsyncLock.Context) async {\n        self.containers[id] = state\n    }\n\n    private func getContainerState(id: String, context: AsyncLock.Context) throws -> ContainerState {\n        try self._getContainerState(id: id)\n    }\n\n    private func _getContainerState(id: String) throws -> ContainerState {\n        let state = self.containers[id]\n        guard let state else {\n            throw ContainerizationError(\n                .notFound,\n                message: \"container with ID \\(id) not found\"\n            )\n        }\n        return state\n    }\n\n    private static func isInitProcess(id: String, processID: String) -> Bool {\n        id == processID\n    }\n\n    /// Get container configuration, either from existing bundle or from RuntimeConfiguration\n    private static func getContainerConfiguration(at path: URL) throws -> (ContainerConfiguration, ContainerCreateOptions?) {\n        let bundle = ContainerResource.Bundle(path: path)\n        do {\n            let config = try bundle.configuration\n            let options: ContainerCreateOptions? = try? bundle.load(filename: \"options.json\")\n            return (config, options)\n        } catch {\n            // Bundle doesn't exist or incomplete, try runtime configuration\n            // This handles containers that were created but not started yet\n            let runtimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: path)\n            guard let config = runtimeConfig.containerConfiguration else {\n                throw ContainerizationError(.internalError, message: \"runtime configuration missing container configuration\")\n            }\n            return (config, runtimeConfig.options)\n        }\n    }\n}\n\nextension XPCMessage {\n    func signal() throws -> Int64 {\n        self.int64(key: .signal)\n    }\n\n    func stopOptions() throws -> ContainerStopOptions {\n        guard let data = self.dataNoCopy(key: .stopOptions) else {\n            throw ContainerizationError(.invalidArgument, message: \"empty StopOptions\")\n        }\n        return try JSONDecoder().decode(ContainerStopOptions.self, from: data)\n    }\n\n    func setState(_ state: SandboxSnapshot) throws {\n        let data = try JSONEncoder().encode(state)\n        self.set(key: .snapshot, value: data)\n    }\n\n    func stdio() -> [FileHandle?] {\n        var handles = [FileHandle?](repeating: nil, count: 3)\n        if let stdin = self.fileHandle(key: .stdin) {\n            handles[0] = stdin\n        }\n        if let stdout = self.fileHandle(key: .stdout) {\n            handles[1] = stdout\n        }\n        if let stderr = self.fileHandle(key: .stderr) {\n            handles[2] = stderr\n        }\n        return handles\n    }\n\n    func setFileHandle(_ handle: FileHandle) {\n        self.set(key: .fd, value: handle)\n    }\n\n    func processConfig() throws -> ProcessConfiguration {\n        guard let data = self.dataNoCopy(key: .processConfig) else {\n            throw ContainerizationError(.invalidArgument, message: \"empty process configuration\")\n        }\n        return try JSONDecoder().decode(ProcessConfiguration.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/DiskUsage/DiskUsageHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\nimport Logging\n\n/// XPC harness for disk usage operations\npublic struct DiskUsageHarness: Sendable {\n    let log: Logger\n    let service: DiskUsageService\n\n    public init(service: DiskUsageService, log: Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func get(_ message: XPCMessage) async throws -> XPCMessage {\n        do {\n            let stats = try await service.calculateDiskUsage()\n            let data = try JSONEncoder().encode(stats)\n\n            let reply = message.reply()\n            reply.set(key: .diskUsageStats, value: data)\n            return reply\n        } catch {\n            log.error(\"failed to get disk usage\", metadata: [\"error\": \"\\(error)\"])\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get disk usage\",\n                cause: error\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/DiskUsage/DiskUsageService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Logging\n\n/// Service for calculating disk usage across all resource types\npublic actor DiskUsageService {\n    private let containersService: ContainersService\n    private let volumesService: VolumesService\n    private let log: Logger\n\n    public init(\n        containersService: ContainersService,\n        volumesService: VolumesService,\n        log: Logger\n    ) {\n        self.containersService = containersService\n        self.volumesService = volumesService\n        self.log = log\n    }\n\n    /// Calculate disk usage for all resource types\n    public func calculateDiskUsage() async throws -> DiskUsageStats {\n        log.debug(\"calculating disk usage for all resources\")\n\n        // Get active image references first (needed for image calculation)\n        let activeImageRefs = await containersService.getActiveImageReferences()\n\n        // Query all services concurrently\n        async let imageStats = ClientImage.calculateDiskUsage(activeReferences: activeImageRefs)\n        async let containerStats = containersService.calculateDiskUsage()\n        async let volumeStats = volumesService.calculateDiskUsage()\n\n        let (imageData, containerData, volumeData) = try await (imageStats, containerStats, volumeStats)\n\n        let stats = DiskUsageStats(\n            images: ResourceUsage(\n                total: imageData.totalCount,\n                active: imageData.activeCount,\n                sizeInBytes: imageData.totalSize,\n                reclaimable: imageData.reclaimableSize\n            ),\n            containers: ResourceUsage(\n                total: containerData.0,\n                active: containerData.1,\n                sizeInBytes: containerData.2,\n                reclaimable: containerData.3\n            ),\n            volumes: ResourceUsage(\n                total: volumeData.0,\n                active: volumeData.1,\n                sizeInBytes: volumeData.2,\n                reclaimable: volumeData.3\n            )\n        )\n\n        log.debug(\n            \"disk usage calculation complete\",\n            metadata: [\n                \"images_total\": \"\\(imageData.totalCount)\",\n                \"containers_total\": \"\\(containerData.0)\",\n                \"volumes_total\": \"\\(volumeData.0)\",\n            ])\n\n        return stats\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/HealthCheck/HealthCheckHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport CVersion\nimport ContainerAPIClient\nimport ContainerVersion\nimport ContainerXPC\nimport Containerization\nimport Foundation\nimport Logging\nimport SystemPackage\n\npublic actor HealthCheckHarness {\n    private let appRoot: URL\n    private let installRoot: URL\n    private let logRoot: FilePath?\n    private let log: Logger\n\n    public init(appRoot: URL, installRoot: URL, logRoot: FilePath?, log: Logger) {\n        self.appRoot = appRoot\n        self.installRoot = installRoot\n        self.logRoot = logRoot\n        self.log = log\n    }\n\n    @Sendable\n    public func ping(_ message: XPCMessage) async -> XPCMessage {\n        let reply = message.reply()\n        reply.set(key: .appRoot, value: appRoot.absoluteString)\n        reply.set(key: .installRoot, value: installRoot.absoluteString)\n        if let logRoot {\n            reply.set(key: .logRoot, value: logRoot.string)\n        }\n        reply.set(key: .apiServerVersion, value: ReleaseVersion.singleLine(appName: \"container-apiserver\"))\n        reply.set(key: .apiServerCommit, value: get_git_commit().map { String(cString: $0) } ?? \"unspecified\")\n        // Extra optional fields for richer client display\n        reply.set(key: .apiServerBuild, value: ReleaseVersion.buildType())\n        reply.set(key: .apiServerAppName, value: \"container-apiserver\")\n        return reply\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Kernel/KernelHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport Foundation\nimport Logging\n\npublic struct KernelHarness: Sendable {\n    private let log: Logging.Logger\n    private let service: KernelService\n\n    public init(service: KernelService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func install(_ message: XPCMessage) async throws -> XPCMessage {\n        let kernelFilePath = try message.kernelFilePath()\n        let platform = try message.platform()\n        let force = try message.kernelForce()\n\n        guard let kernelTarUrl = try message.kernelTarURL() else {\n            // We have been given a path to a kernel binary on disk\n            guard let kernelFile = URL(string: kernelFilePath) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid kernel file path: \\(kernelFilePath)\")\n            }\n            try await self.service.installKernel(kernelFile: kernelFile, platform: platform, force: force)\n            return message.reply()\n        }\n\n        let progressUpdateService = ProgressUpdateService(message: message)\n        try await self.service.installKernelFrom(\n            tar: kernelTarUrl, kernelFilePath: kernelFilePath, platform: platform, progressUpdate: progressUpdateService?.handler, force: force)\n        return message.reply()\n    }\n\n    @Sendable\n    public func getDefaultKernel(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let platformData = message.dataNoCopy(key: .systemPlatform) else {\n            throw ContainerizationError(.invalidArgument, message: \"missing SystemPlatform\")\n        }\n        let platform = try JSONDecoder().decode(SystemPlatform.self, from: platformData)\n        let kernel = try await self.service.getDefaultKernel(platform: platform)\n        let reply = message.reply()\n        let data = try JSONEncoder().encode(kernel)\n        reply.set(key: .kernel, value: data)\n        return reply\n    }\n}\n\nextension XPCMessage {\n    fileprivate func platform() throws -> SystemPlatform {\n        guard let platformData = self.dataNoCopy(key: .systemPlatform) else {\n            throw ContainerizationError(.invalidArgument, message: \"missing SystemPlatform in XPC Message\")\n        }\n        let platform = try JSONDecoder().decode(SystemPlatform.self, from: platformData)\n        return platform\n    }\n\n    fileprivate func kernelFilePath() throws -> String {\n        guard let kernelFilePath = self.string(key: .kernelFilePath) else {\n            throw ContainerizationError(.invalidArgument, message: \"missing kernel file path in XPC Message\")\n        }\n        return kernelFilePath\n    }\n\n    fileprivate func kernelTarURL() throws -> URL? {\n        guard let kernelTarURLString = self.string(key: .kernelTarURL) else {\n            return nil\n        }\n        guard let k = URL(string: kernelTarURLString) else {\n            throw ContainerizationError(.invalidArgument, message: \"cannot parse URL from \\(kernelTarURLString)\")\n        }\n        return k\n    }\n\n    fileprivate func kernelForce() throws -> Bool {\n        self.bool(key: .kernelForce)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Kernel/KernelService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Containerization\nimport ContainerizationArchive\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Logging\nimport TerminalProgress\n\npublic actor KernelService {\n    private static let defaultKernelNamePrefix: String = \"default.kernel-\"\n\n    private let log: Logger\n    private let kernelDirectory: URL\n\n    public init(log: Logger, appRoot: URL) throws {\n        self.log = log\n        self.kernelDirectory = appRoot.appending(path: \"kernels\")\n        try FileManager.default.createDirectory(at: self.kernelDirectory, withIntermediateDirectories: true)\n    }\n\n    /// Copies a kernel binary from a local path on disk into the managed kernels directory\n    /// as the default kernel for the provided platform.\n    public func installKernel(kernelFile url: URL, platform: SystemPlatform = .linuxArm, force: Bool) throws {\n        log.debug(\n            \"KernelService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"kernelFile\": \"\\(url)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"KernelService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"kernelFile\": \"\\(url)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let kFile = url.resolvingSymlinksInPath()\n        let destPath = self.kernelDirectory.appendingPathComponent(kFile.lastPathComponent)\n        if force {\n            do {\n                try FileManager.default.removeItem(at: destPath)\n            } catch let error as NSError {\n                guard error.code == NSFileNoSuchFileError else {\n                    throw error\n                }\n            }\n        }\n        try FileManager.default.copyItem(at: kFile, to: destPath)\n        try Task.checkCancellation()\n        do {\n            try self.setDefaultKernel(name: kFile.lastPathComponent, platform: platform)\n        } catch {\n            try? FileManager.default.removeItem(at: destPath)\n            throw error\n        }\n    }\n\n    /// Copies a kernel binary from inside of tar file into the managed kernels directory\n    /// as the default kernel for the provided platform.\n    /// The parameter `tar` maybe a location to a local file on disk, or a remote URL.\n    public func installKernelFrom(tar: URL, kernelFilePath: String, platform: SystemPlatform, progressUpdate: ProgressUpdateHandler?, force: Bool) async throws {\n        log.debug(\n            \"KernelService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"tar\": \"\\(tar)\",\n                \"kernelFilePath\": \"\\(kernelFilePath)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"KernelService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"tar\": \"\\(tar)\",\n                    \"kernelFilePath\": \"\\(kernelFilePath)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let tempDir = FileManager.default.uniqueTemporaryDirectory()\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        await progressUpdate?([\n            .setDescription(\"Downloading kernel\")\n        ])\n        let taskManager = ProgressTaskCoordinator()\n        let downloadTask = await taskManager.startTask()\n        var tarFile = tar\n        if !FileManager.default.fileExists(atPath: tar.absoluteString) {\n            self.log.debug(\"KernelService: start download\", metadata: [\"tar\": \"\\(tar)\"])\n            tarFile = tempDir.appendingPathComponent(tar.lastPathComponent)\n            var downloadProgressUpdate: ProgressUpdateHandler?\n            if let progressUpdate {\n                downloadProgressUpdate = ProgressTaskCoordinator.handler(for: downloadTask, from: progressUpdate)\n            }\n            try await ContainerAPIClient.FileDownloader.downloadFile(url: tar, to: tarFile, progressUpdate: downloadProgressUpdate)\n        }\n        await taskManager.finish()\n\n        await progressUpdate?([\n            .setDescription(\"Unpacking kernel\")\n        ])\n        let kernelFile = try self.extractFile(tarFile: tarFile, at: kernelFilePath, to: tempDir)\n        try self.installKernel(kernelFile: kernelFile, platform: platform, force: force)\n\n        if !FileManager.default.fileExists(atPath: tar.absoluteString) {\n            try FileManager.default.removeItem(at: tarFile)\n        }\n    }\n\n    private func setDefaultKernel(name: String, platform: SystemPlatform) throws {\n        log.debug(\n            \"KernelService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"name\": \"\\(name)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"KernelService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"name\": \"\\(name)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let kernelPath = self.kernelDirectory.appendingPathComponent(name)\n        guard FileManager.default.fileExists(atPath: kernelPath.path) else {\n            throw ContainerizationError(.notFound, message: \"kernel not found at \\(kernelPath)\")\n        }\n        let name = \"\\(Self.defaultKernelNamePrefix)\\(platform.architecture)\"\n        let defaultKernelPath = self.kernelDirectory.appendingPathComponent(name)\n        try? FileManager.default.removeItem(at: defaultKernelPath)\n        try FileManager.default.createSymbolicLink(at: defaultKernelPath, withDestinationURL: kernelPath)\n    }\n\n    public func getDefaultKernel(platform: SystemPlatform = .linuxArm) async throws -> Kernel {\n        log.debug(\n            \"KernelService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"KernelService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let name = \"\\(Self.defaultKernelNamePrefix)\\(platform.architecture)\"\n        let defaultKernelPath = self.kernelDirectory.appendingPathComponent(name).resolvingSymlinksInPath()\n        guard FileManager.default.fileExists(atPath: defaultKernelPath.path) else {\n            throw ContainerizationError(.notFound, message: \"default kernel not found at \\(defaultKernelPath)\")\n        }\n        return Kernel(path: defaultKernelPath, platform: platform)\n    }\n\n    private func extractFile(tarFile: URL, at: String, to directory: URL) throws -> URL {\n        var target = at\n        var archiveReader = try ArchiveReader(file: tarFile)\n        var (entry, data) = try archiveReader.extractFile(path: target)\n\n        // if the target file is a symlink, get the data for the actual file\n        if entry.fileType == .symbolicLink, let symlinkRelative = entry.symlinkTarget {\n            // the previous extractFile changes the underlying file pointer, so we need to reopen the file\n            // to ensure we traverse all the files in the archive\n            archiveReader = try ArchiveReader(file: tarFile)\n            let symlinkTarget = URL(filePath: target).deletingLastPathComponent().appending(path: symlinkRelative)\n\n            // standardize so that we remove any and all ../ and ./ in the path since symlink targets\n            // are relative paths to the target file from the symlink's parent dir itself\n            target = symlinkTarget.standardized.relativePath\n            let (_, targetData) = try archiveReader.extractFile(path: target)\n            data = targetData\n        }\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)\n        let fileName = URL(filePath: target).lastPathComponent\n        let fileURL = directory.appendingPathComponent(fileName)\n        try data.write(to: fileURL, options: .atomic)\n        return fileURL\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Networks/NetworksHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\npublic struct NetworksHarness: Sendable {\n    let log: Logging.Logger\n    let service: NetworksService\n\n    public init(service: NetworksService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func list(_ message: XPCMessage) async throws -> XPCMessage {\n        let containers = try await service.list()\n        let data = try JSONEncoder().encode(containers)\n\n        let reply = message.reply()\n        reply.set(key: .networkStates, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func create(_ message: XPCMessage) async throws -> XPCMessage {\n        let data = message.dataNoCopy(key: .networkConfig)\n        guard let data else {\n            throw ContainerizationError(.invalidArgument, message: \"network configuration cannot be empty\")\n        }\n\n        let config = try JSONDecoder().decode(NetworkConfiguration.self, from: data)\n        let networkState = try await service.create(configuration: config)\n\n        let networkData = try JSONEncoder().encode(networkState)\n\n        let reply = message.reply()\n        reply.set(key: .networkState, value: networkData)\n        return reply\n    }\n\n    @Sendable\n    public func delete(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .networkId)\n        guard let id else {\n            throw ContainerizationError(.invalidArgument, message: \"id cannot be empty\")\n        }\n        try await service.delete(id: id)\n\n        return message.reply()\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Networks/NetworksService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerNetworkServiceClient\nimport ContainerPersistence\nimport ContainerPlugin\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport Logging\n\npublic actor NetworksService {\n    struct NetworkServiceState {\n        var networkState: NetworkState\n        var client: NetworkClient\n    }\n\n    private let pluginLoader: PluginLoader\n    private let resourceRoot: URL\n    private let containersService: ContainersService\n    private let log: Logger\n    private let debugHelpers: Bool\n\n    private let store: FilesystemEntityStore<NetworkConfiguration>\n    private let networkPlugins: [Plugin]\n    private var busyNetworks = Set<String>()\n\n    private let stateLock = AsyncLock()\n    private var serviceStates = [String: NetworkServiceState]()\n\n    public init(\n        pluginLoader: PluginLoader,\n        resourceRoot: URL,\n        containersService: ContainersService,\n        log: Logger,\n        debugHelpers: Bool = false,\n    ) async throws {\n        self.pluginLoader = pluginLoader\n        self.resourceRoot = resourceRoot\n        self.containersService = containersService\n        self.log = log\n        self.debugHelpers = debugHelpers\n\n        try FileManager.default.createDirectory(at: resourceRoot, withIntermediateDirectories: true)\n        self.store = try FilesystemEntityStore<NetworkConfiguration>(\n            path: resourceRoot,\n            type: \"network\",\n            log: log\n        )\n\n        let networkPlugins =\n            pluginLoader\n            .findPlugins()\n            .filter { $0.hasType(.network) }\n        guard !networkPlugins.isEmpty else {\n            throw ContainerizationError(.internalError, message: \"cannot find any plugins with type network\")\n        }\n        self.networkPlugins = networkPlugins\n\n        let configurations = try await store.list()\n        for var configuration in configurations {\n            // Ensure the network with id \"default\" is marked as builtin.\n            if configuration.id == ClientNetwork.defaultNetworkName {\n                let role = configuration.labels[ResourceLabelKeys.role]\n                if role == nil || role != ResourceRoleValues.builtin {\n                    configuration.labels[ResourceLabelKeys.role] = ResourceRoleValues.builtin\n                    try await store.update(configuration)\n                }\n            }\n\n            // Ensure that the network always has plugin information.\n            // Before this field was added, the code always assumed we were using the\n            // container-network-vmnet network plugin, so it should be safe to fallback to that\n            // if no info was found in an on disk configuration.\n            if configuration.pluginInfo == nil {\n                configuration.pluginInfo = NetworkPluginInfo(plugin: \"container-network-vmnet\")\n                try await store.update(configuration)\n            }\n\n            // Start up the network.\n            do {\n                try await registerService(configuration: configuration)\n            } catch {\n                log.error(\n                    \"failed to start network\",\n                    metadata: [\n                        \"id\": \"\\(configuration.id)\",\n                        \"error\": \"\\(error)\",\n                    ])\n            }\n\n            // This call will normally take ~20-100ms to complete after service\n            // registration, but on a fresh system (e.g. CI runner), it may take\n            // 5 seconds or considerably more from the registration of this first\n            // network service to its execution.\n            let client = try Self.getClient(configuration: configuration)\n            var networkState = try await client.state()\n\n            // FIXME: Temporary workaround for persisted configuration being overwritten\n            // by what comes back from the network helper, which messes up creationDate.\n            // FIXME: Temporarily need to override the plugin information with the info from\n            // the helper, so we can ensure that older networks get a variant value.\n            var finalConfig = configuration\n            switch networkState {\n            case .created(let helperConfig):\n                finalConfig.pluginInfo = helperConfig.pluginInfo\n                networkState = NetworkState.created(finalConfig)\n            case .running(let helperConfig, let status):\n                finalConfig.pluginInfo = helperConfig.pluginInfo\n                networkState = NetworkState.running(finalConfig, status)\n            }\n\n            let state = NetworkServiceState(\n                networkState: networkState,\n                client: client\n            )\n\n            serviceStates[finalConfig.id] = state\n\n            guard case .running = networkState else {\n                log.error(\n                    \"network failed to start\",\n                    metadata: [\n                        \"id\": \"\\(finalConfig.id)\",\n                        \"state\": \"\\(networkState.state)\",\n                    ])\n                return\n            }\n        }\n    }\n\n    /// List all networks registered with the service.\n    public func list() async throws -> [NetworkState] {\n        log.debug(\"NetworksService: enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { log.debug(\"NetworksService: exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return serviceStates.reduce(into: [NetworkState]()) {\n            $0.append($1.value.networkState)\n        }\n    }\n\n    /// Create a new network from the provided configuration.\n    public func create(configuration: NetworkConfiguration) async throws -> NetworkState {\n        log.debug(\n            \"NetworksService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(configuration.id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"NetworksService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(configuration.id)\",\n                ]\n            )\n        }\n\n        //Ensure that the network is not named \"none\"\n        if configuration.id == ClientNetwork.noNetworkName {\n            throw ContainerizationError(.unsupported, message: \"network \\(configuration.id) is not a valid name\")\n        }\n\n        // Ensure nobody is manipulating the network already.\n        guard !busyNetworks.contains(configuration.id) else {\n            throw ContainerizationError(.exists, message: \"network \\(configuration.id) has a pending operation\")\n        }\n\n        busyNetworks.insert(configuration.id)\n        defer { busyNetworks.remove(configuration.id) }\n\n        // Ensure the network doesn't already exist.\n        return try await self.stateLock.withLock { _ in\n            guard await self.serviceStates[configuration.id] == nil else {\n                throw ContainerizationError(.exists, message: \"network \\(configuration.id) already exists\")\n            }\n\n            // Create and start the network.\n            try await self.registerService(configuration: configuration)\n            let client = try Self.getClient(configuration: configuration)\n\n            // Ensure the network is running, and set up the persistent network state\n            // using our configuration data\n            guard case .running(let helperConfig, let status) = try await client.state() else {\n                throw ContainerizationError(.invalidState, message: \"network \\(configuration.id) failed to start\")\n            }\n            var finalConfig = configuration\n            finalConfig.pluginInfo = helperConfig.pluginInfo\n\n            let networkState: NetworkState = .running(finalConfig, status)\n            let serviceState = NetworkServiceState(networkState: networkState, client: client)\n            await self.setServiceState(key: finalConfig.id, value: serviceState)\n\n            // Persist the configuration data.\n            do {\n                try await self.store.create(finalConfig)\n                return networkState\n            } catch {\n                await self.removeServiceState(key: finalConfig.id)\n                do {\n                    try await self.deregisterService(configuration: finalConfig)\n                } catch {\n                    self.log.error(\n                        \"failed to deregister network service after failed creation\",\n                        metadata: [\n                            \"id\": \"\\(finalConfig.id)\",\n                            \"error\": \"\\(error.localizedDescription)\",\n                        ])\n                }\n                throw error\n            }\n        }\n    }\n\n    /// Delete a network.\n    public func delete(id: String) async throws {\n        log.debug(\n            \"NetworksService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"NetworksService: enter\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        // check actor busy state\n        guard !busyNetworks.contains(id) else {\n            throw ContainerizationError(.exists, message: \"network \\(id) has a pending operation\")\n        }\n\n        // make actor state busy for this network\n        busyNetworks.insert(id)\n        defer { busyNetworks.remove(id) }\n\n        log.info(\n            \"deleting network\",\n            metadata: [\n                \"id\": \"\\(id)\"\n            ]\n        )\n\n        try await stateLock.withLock { _ in\n            guard let serviceState = await self.serviceStates[id] else {\n                throw ContainerizationError(.notFound, message: \"no network for id \\(id)\")\n            }\n\n            guard case .running(let netConfig, _) = serviceState.networkState else {\n                throw ContainerizationError(.invalidState, message: \"cannot delete network \\(id) in state \\(serviceState.networkState.state)\")\n            }\n\n            // basic sanity checks on network itself\n            if serviceState.networkState.isBuiltin {\n                throw ContainerizationError(.invalidArgument, message: \"cannot delete builtin network: \\(id)\")\n            }\n\n            // prevent container operations while we atomically check and delete\n            try await self.containersService.withContainerList(logMetadata: [\"acquirer\": \"\\(#function)\", \"id\": \"\\(id)\"]) { containers in\n                // find all containers that refer to the network\n                var referringContainers = Set<String>()\n                for container in containers {\n                    for attachmentConfiguration in container.configuration.networks {\n                        if attachmentConfiguration.network == id {\n                            referringContainers.insert(container.configuration.id)\n                            break\n                        }\n                    }\n                }\n\n                // bail if any referring containers\n                guard referringContainers.isEmpty else {\n                    throw ContainerizationError(\n                        .invalidState,\n                        message: \"cannot delete subnet \\(id) with referring containers: \\(referringContainers.joined(separator: \", \"))\"\n                    )\n                }\n\n                // disable the allocator so nothing else can attach\n                // TODO: remove this from the network helper later, not necesssary now that withContainerList is here\n                guard try await serviceState.client.disableAllocator() else {\n                    throw ContainerizationError(.invalidState, message: \"cannot delete subnet \\(id) because the IP allocator cannot be disabled with active containers\")\n                }\n\n                // start network deletion, this is the last place we'll want to throw\n                do {\n                    try await self.deregisterService(configuration: netConfig)\n                } catch {\n                    self.log.error(\n                        \"failed to deregister network service\",\n                        metadata: [\n                            \"id\": \"\\(id)\",\n                            \"error\": \"\\(error.localizedDescription)\",\n                        ])\n                }\n\n                // deletion is underway, do not throw anything now\n                do {\n                    try await self.store.delete(id)\n                } catch {\n                    self.log.error(\n                        \"failed to delete network from configuration store\",\n                        metadata: [\n                            \"id\": \"\\(id)\",\n                            \"error\": \"\\(error.localizedDescription)\",\n                        ])\n                }\n            }\n\n            // having deleted successfully, remove the runtime state\n            await self.removeServiceState(key: id)\n        }\n    }\n\n    /// Perform a hostname lookup on all networks.\n    ///\n    /// - Parameter hostname: A canonical DNS hostname with a trailing dot (e.g. `\"example.com.\"`).\n    public func lookup(hostname: String) async throws -> Attachment? {\n        try await self.stateLock.withLock { _ in\n            for state in await self.serviceStates.values {\n                guard let allocation = try await state.client.lookup(hostname: hostname) else {\n                    continue\n                }\n                return allocation\n            }\n            return nil\n        }\n    }\n\n    public func allocate(id: String, hostname: String, macAddress: MACAddress?) async throws -> AllocatedAttachment {\n        guard let serviceState = serviceStates[id] else {\n            throw ContainerizationError(.notFound, message: \"no network for id \\(id)\")\n        }\n        guard let pluginInfo = serviceState.networkState.pluginInfo else {\n            throw ContainerizationError(.internalError, message: \"network \\(id) missing plugin information\")\n        }\n        let (attach, additionalData) = try await serviceState.client.allocate(hostname: hostname, macAddress: macAddress)\n        return AllocatedAttachment(\n            attachment: attach,\n            additionalData: additionalData,\n            pluginInfo: pluginInfo\n        )\n    }\n\n    public func deallocate(attachment: Attachment) async throws {\n        guard let serviceState = serviceStates[attachment.network] else {\n            throw ContainerizationError(.notFound, message: \"no network for id \\(attachment.network)\")\n        }\n        return try await serviceState.client.deallocate(hostname: attachment.hostname)\n    }\n\n    private static func getClient(configuration: NetworkConfiguration) throws -> NetworkClient {\n        guard let pluginInfo = configuration.pluginInfo else {\n            throw ContainerizationError(.internalError, message: \"network \\(configuration.id) missing plugin information\")\n        }\n        return NetworkClient(id: configuration.id, plugin: pluginInfo.plugin)\n    }\n\n    private func registerService(configuration: NetworkConfiguration) async throws {\n        guard configuration.mode == .nat || configuration.mode == .hostOnly else {\n            throw ContainerizationError(.invalidArgument, message: \"unsupported network mode \\(configuration.mode.rawValue)\")\n        }\n\n        guard let pluginInfo = configuration.pluginInfo else {\n            throw ContainerizationError(.internalError, message: \"network \\(configuration.id) missing plugin information\")\n        }\n\n        guard let networkPlugin = self.networkPlugins.first(where: { $0.name == pluginInfo.plugin }) else {\n            throw ContainerizationError(\n                .notFound,\n                message: \"unable to locate network plugin \\(pluginInfo.plugin)\"\n            )\n        }\n\n        guard let serviceIdentifier = networkPlugin.getMachService(instanceId: configuration.id, type: .network) else {\n            throw ContainerizationError(.invalidArgument, message: \"unsupported network mode \\(configuration.mode.rawValue)\")\n        }\n        var args = [\n            \"start\",\n            \"--id\",\n            configuration.id,\n            \"--service-identifier\",\n            serviceIdentifier,\n            \"--mode\",\n            configuration.mode.rawValue,\n        ]\n        if debugHelpers {\n            args.append(\"--debug\")\n        }\n\n        if let ipv4Subnet = configuration.ipv4Subnet {\n            var existingCidrs: [CIDRv4] = []\n            for serviceState in serviceStates.values {\n                if case .running(_, let status) = serviceState.networkState {\n                    existingCidrs.append(status.ipv4Subnet)\n                }\n            }\n            let overlap = existingCidrs.first {\n                $0.contains(ipv4Subnet.lower)\n                    || $0.contains(ipv4Subnet.upper)\n                    || ipv4Subnet.contains($0.lower)\n                    || ipv4Subnet.contains($0.upper)\n            }\n            if let overlap {\n                throw ContainerizationError(.exists, message: \"IPv4 subnet \\(ipv4Subnet) overlaps an existing network with subnet \\(overlap)\")\n            }\n\n            args += [\"--subnet\", ipv4Subnet.description]\n        }\n\n        if let ipv6Subnet = configuration.ipv6Subnet {\n            var existingCidrs: [CIDRv6] = []\n            for serviceState in serviceStates.values {\n                if case .running(_, let status) = serviceState.networkState, let otherIPv6Subnet = status.ipv6Subnet {\n                    existingCidrs.append(otherIPv6Subnet)\n                }\n            }\n            let overlap = existingCidrs.first {\n                $0.contains(ipv6Subnet.lower)\n                    || $0.contains(ipv6Subnet.upper)\n                    || ipv6Subnet.contains($0.lower)\n                    || ipv6Subnet.contains($0.upper)\n            }\n            if let overlap {\n                throw ContainerizationError(.exists, message: \"IPv6 subnet \\(ipv6Subnet) overlaps an existing network with subnet \\(overlap)\")\n            }\n\n            args += [\"--subnet-v6\", ipv6Subnet.description]\n        }\n\n        if let variant = configuration.pluginInfo?.variant {\n            args += [\"--variant\", variant]\n        }\n\n        try await pluginLoader.registerWithLaunchd(\n            plugin: networkPlugin,\n            pluginStateRoot: store.entityUrl(configuration.id),\n            args: args,\n            instanceId: configuration.id\n        )\n    }\n\n    private func deregisterService(configuration: NetworkConfiguration) async throws {\n        guard let pluginInfo = configuration.pluginInfo else {\n            throw ContainerizationError(.internalError, message: \"network \\(configuration.id) missing plugin information\")\n        }\n        guard let networkPlugin = self.networkPlugins.first(where: { $0.name == pluginInfo.plugin }) else {\n            throw ContainerizationError(\n                .notFound,\n                message: \"unable to locate network plugin \\(pluginInfo.plugin)\"\n            )\n        }\n        try self.pluginLoader.deregisterWithLaunchd(plugin: networkPlugin, instanceId: configuration.id)\n    }\n}\n\nextension NetworksService {\n    private func removeServiceState(key: String) {\n        self.serviceStates.removeValue(forKey: key)\n    }\n\n    private func setServiceState(key: String, value: NetworkServiceState) {\n        self.serviceStates[key] = value\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Plugin/PluginsHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\nimport Logging\n\npublic struct PluginsHarness: Sendable {\n    private let log: Logging.Logger\n    private let service: PluginsService\n\n    public init(service: PluginsService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func load(_ message: XPCMessage) async throws -> XPCMessage {\n        let name = message.string(key: .pluginName)\n        guard let name else {\n            throw ContainerizationError(.invalidArgument, message: \"no plugin name found\")\n        }\n\n        try await service.load(name: name)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func get(_ message: XPCMessage) async throws -> XPCMessage {\n        let name = message.string(key: .pluginName)\n        guard let name else {\n            throw ContainerizationError(.invalidArgument, message: \"no plugin name found\")\n        }\n\n        let plugin = try await service.get(name: name)\n        let data = try JSONEncoder().encode(plugin)\n\n        let reply = message.reply()\n        reply.set(key: .plugin, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func restart(_ message: XPCMessage) async throws -> XPCMessage {\n        let name = message.string(key: .pluginName)\n        guard let name else {\n            throw ContainerizationError(.invalidArgument, message: \"no plugin name found\")\n        }\n\n        try await service.restart(name: name)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func unload(_ message: XPCMessage) async throws -> XPCMessage {\n        let name = message.string(key: .pluginName)\n        guard let name else {\n            throw ContainerizationError(.invalidArgument, message: \"no plugin name found\")\n        }\n\n        try await service.unload(name: name)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func list(_ message: XPCMessage) async throws -> XPCMessage {\n        let plugins = try await service.list()\n\n        let data = try JSONEncoder().encode(plugins)\n\n        let reply = message.reply()\n        reply.set(key: .plugins, value: data)\n        return reply\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Plugin/PluginsService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPlugin\nimport Foundation\nimport Logging\n\npublic actor PluginsService {\n    private let log: Logger\n    private var loaded: [String: Plugin]\n    private let pluginLoader: PluginLoader\n\n    public init(pluginLoader: PluginLoader, log: Logger) {\n        self.log = log\n        self.loaded = [:]\n        self.pluginLoader = pluginLoader\n    }\n\n    /// Load the specified plugins, or all plugins with services defined\n    /// if none are explicitly specified.\n    public func loadAll(\n        _ plugins: [Plugin]? = nil,\n        debug: Bool = false\n    ) throws {\n        let registerPlugins = plugins ?? pluginLoader.findPlugins()\n        for plugin in registerPlugins {\n            try pluginLoader.registerWithLaunchd(plugin: plugin, debug: debug)\n            loaded[plugin.name] = plugin\n        }\n    }\n\n    /// Stop the specified plugins, or all plugins with services defined\n    /// if none are explicitly specified.\n    public func stopAll(_ plugins: [Plugin]? = nil) throws {\n        let deregisterPlugins = plugins ?? pluginLoader.findPlugins()\n        for plugin in deregisterPlugins {\n            try pluginLoader.deregisterWithLaunchd(plugin: plugin)\n            self.loaded.removeValue(forKey: plugin.name)\n        }\n    }\n\n    // MARK: XPC API surface.\n\n    /// Load a single plugin, doing nothing if the plugin is already loaded.\n    public func load(name: String, debug: Bool = false) throws {\n        guard self.loaded[name] == nil else {\n            return\n        }\n        guard let plugin = pluginLoader.findPlugin(name: name) else {\n            throw Error.pluginNotFound(name)\n        }\n        try pluginLoader.registerWithLaunchd(plugin: plugin, debug: debug)\n        self.loaded[plugin.name] = plugin\n    }\n\n    /// Get information for a loaded plugin.\n    public func get(name: String) throws -> Plugin {\n        guard let plugin = loaded[name] else {\n            throw Error.pluginNotLoaded(name)\n        }\n        return plugin\n    }\n\n    /// Restart a loaded plugin.\n    public func restart(name: String) throws {\n        guard let plugin = self.loaded[name] else {\n            throw Error.pluginNotLoaded(name)\n        }\n        try ServiceManager.kickstart(fullServiceLabel: plugin.getLaunchdLabel())\n    }\n\n    /// Unload a loaded plugin.\n    public func unload(name: String) throws {\n        guard let plugin = self.loaded[name] else {\n            throw Error.pluginNotLoaded(name)\n        }\n        try pluginLoader.deregisterWithLaunchd(plugin: plugin)\n        self.loaded.removeValue(forKey: plugin.name)\n    }\n\n    /// List all loaded plugins.\n    public func list() throws -> [Plugin] {\n        self.loaded.map { $0.value }\n    }\n\n    public enum Error: Swift.Error, CustomStringConvertible {\n        case pluginNotFound(String)\n        case pluginNotLoaded(String)\n\n        public var description: String {\n            switch self {\n            case .pluginNotFound(let name):\n                return \"plugin not found: \\(name)\"\n            case .pluginNotLoaded(let name):\n                return \"plugin not loaded: \\(name)\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Volumes/VolumesHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerXPC\nimport ContainerizationError\nimport Foundation\nimport Logging\n\npublic struct VolumesHarness: Sendable {\n    let log: Logging.Logger\n    let service: VolumesService\n\n    public init(service: VolumesService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func list(_ message: XPCMessage) async throws -> XPCMessage {\n        let volumes = try await service.list()\n        let data = try JSONEncoder().encode(volumes)\n\n        let reply = message.reply()\n        reply.set(key: .volumes, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func create(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let name = message.string(key: .volumeName) else {\n            throw ContainerizationError(.invalidArgument, message: \"volume name cannot be empty\")\n        }\n\n        let driver = message.string(key: .volumeDriver) ?? \"local\"\n\n        let driverOpts: [String: String]\n        if let driverOptsData = message.dataNoCopy(key: .volumeDriverOpts) {\n            driverOpts = try JSONDecoder().decode([String: String].self, from: driverOptsData)\n        } else {\n            driverOpts = [:]\n        }\n\n        let labels: [String: String]\n        if let labelsData = message.dataNoCopy(key: .volumeLabels) {\n            labels = try JSONDecoder().decode([String: String].self, from: labelsData)\n        } else {\n            labels = [:]\n        }\n\n        let volume = try await service.create(name: name, driver: driver, driverOpts: driverOpts, labels: labels)\n        let responseData = try JSONEncoder().encode(volume)\n\n        let reply = message.reply()\n        reply.set(key: .volume, value: responseData)\n        return reply\n    }\n\n    @Sendable\n    public func delete(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let name = message.string(key: .volumeName) else {\n            throw ContainerizationError(.invalidArgument, message: \"volume name cannot be empty\")\n        }\n\n        try await service.delete(name: name)\n        return message.reply()\n    }\n\n    @Sendable\n    public func inspect(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let name = message.string(key: .volumeName) else {\n            throw ContainerizationError(.invalidArgument, message: \"volume name cannot be empty\")\n        }\n\n        let volume = try await service.inspect(name)\n        let data = try JSONEncoder().encode(volume)\n\n        let reply = message.reply()\n        reply.set(key: .volume, value: data)\n        return reply\n    }\n\n    @Sendable\n    public func diskUsage(_ message: XPCMessage) async throws -> XPCMessage {\n        guard let name = message.string(key: .volumeName) else {\n            throw ContainerizationError(.invalidArgument, message: \"volume name cannot be empty\")\n        }\n        let size = try await service.volumeDiskUsage(name: name)\n\n        let reply = message.reply()\n        reply.set(key: .volumeSize, value: size)\n        return reply\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerAPIService/Server/Volumes/VolumesService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerResource\nimport Containerization\nimport ContainerizationEXT4\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport Synchronization\nimport SystemPackage\n\npublic actor VolumesService {\n    private let resourceRoot: URL\n    private let store: ContainerPersistence.FilesystemEntityStore<Volume>\n    private let log: Logger\n    private let lock = AsyncLock()\n    private let containersService: ContainersService\n\n    // Storage constants\n    private static let entityFile = \"entity.json\"\n    private static let blockFile = \"volume.img\"\n\n    public init(resourceRoot: URL, containersService: ContainersService, log: Logger) throws {\n        try FileManager.default.createDirectory(at: resourceRoot, withIntermediateDirectories: true)\n        self.resourceRoot = resourceRoot\n        self.store = try FilesystemEntityStore<Volume>(path: resourceRoot, type: \"volumes\", log: log)\n        self.containersService = containersService\n        self.log = log\n    }\n\n    public func create(\n        name: String,\n        driver: String = \"local\",\n        driverOpts: [String: String] = [:],\n        labels: [String: String] = [:]\n    ) async throws -> Volume {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"name\": \"\\(name)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"name\": \"\\(name)\",\n                ]\n            )\n        }\n\n        return try await lock.withLock { _ in\n            try await self._create(name: name, driver: driver, driverOpts: driverOpts, labels: labels)\n        }\n    }\n\n    public func delete(name: String) async throws {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"name\": \"\\(name)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"name\": \"\\(name)\",\n                ]\n            )\n        }\n\n        try await lock.withLock { _ in\n            try await self._delete(name: name)\n        }\n    }\n\n    public func list() async throws -> [Volume] {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n\n        return try await store.list()\n    }\n\n    public func inspect(_ name: String) async throws -> Volume {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"name\": \"\\(name)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"name\": \"\\(name)\",\n                ]\n            )\n        }\n\n        return try await lock.withLock { _ in\n            try await self._inspect(name)\n        }\n    }\n\n    /// Calculate disk usage for a single volume\n    public func volumeDiskUsage(name: String) async throws -> UInt64 {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"name\": \"\\(name)\",\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"name\": \"\\(name)\",\n                ]\n            )\n        }\n\n        let volumePath = self.volumePath(for: name)\n        return self.calculateDirectorySize(at: volumePath)\n    }\n\n    /// Calculate disk usage for volumes\n    /// - Returns: Tuple of (total count, active count, total size, reclaimable size)\n    public func calculateDiskUsage() async throws -> (Int, Int, UInt64, UInt64) {\n        log.debug(\n            \"VolumesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            log.debug(\n                \"VolumesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n\n        return try await lock.withLock { _ in\n            let allVolumes = try await self.store.list()\n\n            // Atomically get active volumes with container list\n            return try await self.containersService.withContainerList(logMetadata: [\"acquirer\": \"\\(#function)\"]) { containers in\n                var inUseSet = Set<String>()\n\n                // Find all mounted volumes\n                for container in containers {\n                    for mount in container.configuration.mounts {\n                        if mount.isVolume, let volumeName = mount.volumeName {\n                            inUseSet.insert(volumeName)\n                        }\n                    }\n                }\n\n                var totalSize: UInt64 = 0\n                var reclaimableSize: UInt64 = 0\n\n                // Calculate sizes\n                for volume in allVolumes {\n                    let volumePath = self.volumePath(for: volume.name)\n                    let volumeSize = self.calculateDirectorySize(at: volumePath)\n                    totalSize += volumeSize\n\n                    if !inUseSet.contains(volume.name) {\n                        reclaimableSize += volumeSize\n                    }\n                }\n\n                return (allVolumes.count, inUseSet.count, totalSize, reclaimableSize)\n            }\n        }\n    }\n\n    private nonisolated func calculateDirectorySize(at path: String) -> UInt64 {\n        let url = URL(fileURLWithPath: path)\n        let fileManager = FileManager.default\n\n        guard\n            let enumerator = fileManager.enumerator(\n                at: url,\n                includingPropertiesForKeys: [.totalFileAllocatedSizeKey],\n                options: [.skipsHiddenFiles]\n            )\n        else {\n            return 0\n        }\n\n        var totalSize: UInt64 = 0\n        for case let fileURL as URL in enumerator {\n            guard let resourceValues = try? fileURL.resourceValues(forKeys: [.totalFileAllocatedSizeKey]),\n                let fileSize = resourceValues.totalFileAllocatedSize\n            else {\n                continue\n            }\n            totalSize += UInt64(fileSize)\n        }\n\n        return totalSize\n    }\n\n    private func parseSize(_ sizeString: String) throws -> UInt64 {\n        let measurement = try Measurement.parse(parsing: sizeString)\n        let bytes = measurement.converted(to: .bytes).value\n\n        // Validate minimum size\n        let minSize: UInt64 = 1.mib()  // 1mib minimum\n\n        let sizeInBytes = UInt64(bytes)\n\n        guard sizeInBytes >= minSize else {\n            throw VolumeError.storageError(\"volume size too small: minimum 1MiB\")\n        }\n\n        return sizeInBytes\n    }\n\n    private nonisolated func volumePath(for name: String) -> String {\n        resourceRoot.appendingPathComponent(name).path\n    }\n\n    private nonisolated func entityPath(for name: String) -> String {\n        \"\\(volumePath(for: name))/\\(Self.entityFile)\"\n    }\n\n    private nonisolated func blockPath(for name: String) -> String {\n        \"\\(volumePath(for: name))/\\(Self.blockFile)\"\n    }\n\n    private func createVolumeDirectory(for name: String) throws {\n        let volumePath = volumePath(for: name)\n        let fm = FileManager.default\n        try fm.createDirectory(atPath: volumePath, withIntermediateDirectories: true, attributes: nil)\n    }\n\n    private func createVolumeImage(for name: String, sizeInBytes: UInt64 = VolumeStorage.defaultVolumeSizeBytes) throws {\n        let blockPath = blockPath(for: name)\n\n        // Use the containerization library's EXT4 formatter\n        let formatter = try EXT4.Formatter(\n            FilePath(blockPath),\n            blockSize: 4096,\n            minDiskSize: sizeInBytes\n        )\n\n        try formatter.close()\n    }\n\n    private nonisolated func removeVolumeDirectory(for name: String) throws {\n        let volumePath = volumePath(for: name)\n        let fm = FileManager.default\n\n        if fm.fileExists(atPath: volumePath) {\n            try fm.removeItem(atPath: volumePath)\n        }\n    }\n\n    private func _create(\n        name: String,\n        driver: String,\n        driverOpts: [String: String],\n        labels: [String: String]\n    ) async throws -> Volume {\n        guard VolumeStorage.isValidVolumeName(name) else {\n            throw VolumeError.invalidVolumeName(\"invalid volume name '\\(name)': must match \\(VolumeStorage.volumeNamePattern)\")\n        }\n\n        // Check if volume already exists by trying to list and finding it\n        let existingVolumes = try await store.list()\n        if existingVolumes.contains(where: { $0.name == name }) {\n            throw VolumeError.volumeAlreadyExists(name)\n        }\n\n        try createVolumeDirectory(for: name)\n\n        // Parse size from driver options (default 512GB)\n        let sizeInBytes: UInt64\n        if let sizeString = driverOpts[\"size\"] {\n            sizeInBytes = try parseSize(sizeString)\n        } else {\n            sizeInBytes = VolumeStorage.defaultVolumeSizeBytes\n        }\n\n        try createVolumeImage(for: name, sizeInBytes: sizeInBytes)\n\n        let volume = Volume(\n            name: name,\n            driver: driver,\n            format: \"ext4\",\n            source: blockPath(for: name),\n            labels: labels,\n            options: driverOpts,\n            sizeInBytes: sizeInBytes\n        )\n\n        try await store.create(volume)\n\n        log.info(\n            \"created volume\",\n            metadata: [\n                \"name\": \"\\(name)\",\n                \"driver\": \"\\(driver)\",\n                \"isAnonymous\": \"\\(volume.isAnonymous)\",\n            ])\n        return volume\n    }\n\n    private func _delete(name: String) async throws {\n        guard VolumeStorage.isValidVolumeName(name) else {\n            throw VolumeError.invalidVolumeName(\"invalid volume name '\\(name)': must match \\(VolumeStorage.volumeNamePattern)\")\n        }\n\n        // Check if volume exists by trying to list and finding it\n        let existingVolumes = try await store.list()\n        guard existingVolumes.contains(where: { $0.name == name }) else {\n            throw VolumeError.volumeNotFound(name)\n        }\n\n        // Check if volume is in use by any container atomically\n        try await containersService.withContainerList(logMetadata: [\"acquirer\": \"\\(#function)\", \"name\": \"\\(name)\"]) { containers in\n            for container in containers {\n                for mount in container.configuration.mounts {\n                    if mount.isVolume && mount.volumeName == name {\n                        throw VolumeError.volumeInUse(name)\n                    }\n                }\n            }\n\n            try await self.store.delete(name)\n            try self.removeVolumeDirectory(for: name)\n        }\n\n        log.info(\"deleted volume\", metadata: [\"name\": \"\\(name)\"])\n    }\n\n    private func _inspect(_ name: String) async throws -> Volume {\n        guard VolumeStorage.isValidVolumeName(name) else {\n            throw VolumeError.invalidVolumeName(\"invalid volume name '\\(name)': must match \\(VolumeStorage.volumeNamePattern)\")\n        }\n\n        let volumes = try await store.list()\n        guard let volume = volumes.first(where: { $0.name == name }) else {\n            throw VolumeError.volumeNotFound(name)\n        }\n\n        return volume\n    }\n\n}\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Client/ImageServiceXPCKeys.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Foundation\nimport ContainerXPC\n\n/// Keys for XPC fields.\npublic enum ImagesServiceXPCKeys: String {\n    case fd\n    /// FDs pointing to container logs key.\n    case logs\n    /// Path to a file on disk key.\n    case filePath\n\n    /// Images\n    case imageReference\n    case imageNewReference\n    case imageDescription\n    case imageDescriptions\n    case filesystem\n    case ociPlatform\n    case insecureFlag\n    case garbageCollect\n    case maxConcurrentDownloads\n    case forceLoad\n    case rejectedMembers\n\n    /// ContentStore\n    case digest\n    case digests\n    case directory\n    case contentPath\n    case imageSize\n    case ingestSessionId\n\n    /// Disk Usage\n    case activeImageReferences\n    case totalCount\n    case activeCount\n    case reclaimableSize\n}\n\nextension XPCMessage {\n    public func set(key: ImagesServiceXPCKeys, value: String) {\n        self.set(key: key.rawValue, value: value)\n    }\n\n    public func set(key: ImagesServiceXPCKeys, value: UInt64) {\n        self.set(key: key.rawValue, value: value)\n    }\n\n    public func set(key: ImagesServiceXPCKeys, value: Data) {\n        self.set(key: key.rawValue, value: value)\n    }\n\n    public func set(key: ImagesServiceXPCKeys, value: Bool) {\n        self.set(key: key.rawValue, value: value)\n    }\n\n    public func set(key: ImagesServiceXPCKeys, value: Int64) {\n        self.set(key: key.rawValue, value: value)\n    }\n\n    public func string(key: ImagesServiceXPCKeys) -> String? {\n        self.string(key: key.rawValue)\n    }\n\n    public func data(key: ImagesServiceXPCKeys) -> Data? {\n        self.data(key: key.rawValue)\n    }\n\n    public func dataNoCopy(key: ImagesServiceXPCKeys) -> Data? {\n        self.dataNoCopy(key: key.rawValue)\n    }\n\n    public func uint64(key: ImagesServiceXPCKeys) -> UInt64 {\n        self.uint64(key: key.rawValue)\n    }\n\n    public func int64(key: ImagesServiceXPCKeys) -> Int64 {\n        self.int64(key: key.rawValue)\n    }\n\n    public func bool(key: ImagesServiceXPCKeys) -> Bool {\n        self.bool(key: key.rawValue)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Client/ImageServiceXPCRoutes.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Foundation\nimport ContainerXPC\n\npublic enum ImagesServiceXPCRoute: String {\n    case imageList\n    case imagePull\n    case imagePush\n    case imageTag\n    case imageBuild\n    case imageDelete\n    case imageSave\n    case imageLoad\n    case imageCleanupOrphanedBlobs\n    case imageDiskUsage\n\n    case contentGet\n    case contentDelete\n    case contentClean\n    case contentIngestStart\n    case contentIngestComplete\n    case contentIngestCancel\n\n    case imageUnpack\n    case snapshotDelete\n    case snapshotGet\n}\n\nextension XPCMessage {\n    public init(route: ImagesServiceXPCRoute) {\n        self.init(route: route.rawValue)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Client/RemoteContentStoreClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n#if os(macOS)\nimport Crypto\nimport ContainerizationError\nimport Foundation\nimport ContainerizationOCI\nimport ContainerXPC\n\npublic struct RemoteContentStoreClient: ContentStore {\n    private static let serviceIdentifier = \"com.apple.container.core.container-core-images\"\n    private static let encoder = JSONEncoder()\n\n    private static func newClient() -> XPCClient {\n        XPCClient(service: serviceIdentifier)\n    }\n\n    public init() {}\n\n    private func _get(digest: String) async throws -> URL? {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentGet)\n        request.set(key: .digest, value: digest)\n        do {\n            let response = try await client.send(request)\n            guard let path = response.string(key: .contentPath) else {\n                return nil\n            }\n            return URL(filePath: path)\n        } catch let error as ContainerizationError {\n            if error.code == .notFound {\n                return nil\n            }\n            throw error\n        }\n    }\n\n    public func get(digest: String) async throws -> Content? {\n        guard let url = try await self._get(digest: digest) else {\n            return nil\n        }\n        return try LocalContent(path: url)\n    }\n\n    public func get<T: Decodable>(digest: String) async throws -> T? {\n        guard let content: Content = try await self.get(digest: digest) else {\n            return nil\n        }\n        return try content.decode()\n    }\n\n    public func delete(keeping: [String]) async throws -> ([String], UInt64) {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentClean)\n\n        let d = try Self.encoder.encode(keeping)\n        request.set(key: .digests, value: d)\n        let response = try await client.send(request)\n\n        guard let data = response.dataNoCopy(key: .digests) else {\n            throw ContainerizationError.init(.internalError, message: \"failed to delete digests\")\n        }\n\n        let decoder = JSONDecoder()\n        let deleted = try decoder.decode([String].self, from: data)\n        let size = response.uint64(key: .imageSize)\n        return (deleted, size)\n    }\n\n    @discardableResult\n    public func delete(digests: [String]) async throws -> ([String], UInt64) {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentDelete)\n\n        let d = try Self.encoder.encode(digests)\n        request.set(key: .digests, value: d)\n        let response = try await client.send(request)\n\n        guard let data = response.dataNoCopy(key: .digests) else {\n            throw ContainerizationError.init(.internalError, message: \"failed to delete digests\")\n        }\n\n        let decoder = JSONDecoder()\n        let deleted = try decoder.decode([String].self, from: data)\n        let size = response.uint64(key: .imageSize)\n        return (deleted, size)\n    }\n\n    @discardableResult\n    public func ingest(_ body: @Sendable @escaping (URL) async throws -> Void) async throws -> [String] {\n        let (id, tempPath) = try await self.newIngestSession()\n        try await body(tempPath)\n        return try await self.completeIngestSession(id)\n    }\n\n    public func newIngestSession() async throws -> (id: String, ingestDir: URL) {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentIngestStart)\n        let response = try await client.send(request)\n        guard let id = response.string(key: .ingestSessionId) else {\n            throw ContainerizationError.init(.internalError, message: \"failed create new ingest session\")\n        }\n        guard let dir = response.string(key: .directory) else {\n            throw ContainerizationError.init(.internalError, message: \"failed create new ingest session\")\n        }\n        return (id, URL(filePath: dir))\n    }\n\n    @discardableResult\n    public func completeIngestSession(_ id: String) async throws -> [String] {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentIngestComplete)\n\n        request.set(key: .ingestSessionId, value: id)\n\n        let response = try await client.send(request)\n        guard let data = response.dataNoCopy(key: .digests) else {\n            throw ContainerizationError.init(.internalError, message: \"failed to delete digests\")\n        }\n\n        let decoder = JSONDecoder()\n        let ingested = try decoder.decode([String].self, from: data)\n        return ingested\n    }\n\n    public func cancelIngestSession(_ id: String) async throws {\n        let client = Self.newClient()\n        let request = XPCMessage(route: .contentIngestCancel)\n        request.set(key: .ingestSessionId, value: id)\n        try await client.send(request)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Server/ContentServiceHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerImagesServiceClient\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport Foundation\nimport Logging\n\npublic struct ContentServiceHarness: Sendable {\n    private let log: Logging.Logger\n    private let service: ContentStoreService\n\n    public init(service: ContentStoreService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func get(_ message: XPCMessage) async throws -> XPCMessage {\n        let d = message.string(key: .digest)\n        guard let d else {\n            throw ContainerizationError(.invalidArgument, message: \"missing digest\")\n        }\n        guard let path = try await service.get(digest: d) else {\n            let err = ContainerizationError(.notFound, message: \"digest \\(d) not found\")\n            let reply = message.reply()\n            reply.set(error: err)\n            return reply\n        }\n        let reply = message.reply()\n        reply.set(key: .contentPath, value: path.path(percentEncoded: false))\n        return reply\n    }\n\n    @Sendable\n    public func delete(_ message: XPCMessage) async throws -> XPCMessage {\n        let data = message.dataNoCopy(key: .digests)\n        guard let data else {\n            throw ContainerizationError(.invalidArgument, message: \"missing digest\")\n        }\n        let digests = try JSONDecoder().decode([String].self, from: data)\n        let (deleted, size) = try await self.service.delete(digests: digests)\n        let d = try JSONEncoder().encode(deleted)\n        let reply = message.reply()\n        reply.set(key: .digests, value: d)\n        reply.set(key: .imageSize, value: size)\n        return reply\n    }\n\n    @Sendable\n    public func clean(_ message: XPCMessage) async throws -> XPCMessage {\n        let data = message.dataNoCopy(key: .digests)\n        guard let data else {\n            throw ContainerizationError(.invalidArgument, message: \"missing digest\")\n        }\n        let digests = try JSONDecoder().decode([String].self, from: data)\n        let (deleted, size) = try await self.service.delete(keeping: digests)\n        let d = try JSONEncoder().encode(deleted)\n        let reply = message.reply()\n        reply.set(key: .digests, value: d)\n        reply.set(key: .imageSize, value: size)\n        return reply\n    }\n\n    @Sendable\n    public func newIngestSession(_ message: XPCMessage) async throws -> XPCMessage {\n        let session = try await self.service.newIngestSession()\n        let id = session.id\n        let dir = session.ingestDir\n        let reply = message.reply()\n        reply.set(key: .directory, value: dir.path(percentEncoded: false))\n        reply.set(key: .ingestSessionId, value: id)\n        return reply\n    }\n\n    @Sendable\n    public func cancelIngestSession(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .ingestSessionId)\n        guard let id else {\n            throw ContainerizationError(.invalidArgument, message: \"missing ingest session id\")\n        }\n        try await self.service.cancelIngestSession(id)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func completeIngestSession(_ message: XPCMessage) async throws -> XPCMessage {\n        let id = message.string(key: .ingestSessionId)\n        guard let id else {\n            throw ContainerizationError(.invalidArgument, message: \"missing ingest session id\")\n        }\n        let ingested = try await self.service.completeIngestSession(id)\n        let d = try JSONEncoder().encode(ingested)\n        let reply = message.reply()\n        reply.set(key: .digests, value: d)\n        return reply\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Server/ContentStoreService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerImagesServiceClient\nimport Containerization\nimport ContainerizationOCI\nimport Foundation\nimport Logging\n\npublic actor ContentStoreService {\n    private let log: Logger\n    private let contentStore: LocalContentStore\n    private let root: URL\n\n    public init(root: URL, log: Logger) throws {\n        try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)\n        self.root = root.appendingPathComponent(\"content\")\n        self.contentStore = try LocalContentStore(path: self.root)\n        self.log = log\n    }\n\n    public func get(digest: String) async throws -> URL? {\n        self.log.trace(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"digest\": \"\\(digest)\",\n            ]\n        )\n        defer {\n            self.log.trace(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"digest\": \"\\(digest)\",\n                ]\n            )\n        }\n\n        return try await self.contentStore.get(digest: digest)?.path\n    }\n\n    @discardableResult\n    public func delete(digests: [String]) async throws -> ([String], UInt64) {\n        self.log.trace(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"digests\": \"\\(digests)\",\n            ]\n        )\n        defer {\n            self.log.trace(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"digests\": \"\\(digests)\",\n                ]\n            )\n        }\n\n        return try await self.contentStore.delete(digests: digests)\n    }\n\n    @discardableResult\n    public func delete(keeping: [String]) async throws -> ([String], UInt64) {\n        self.log.debug(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"keeping\": \"\\(keeping)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"keeping\": \"\\(keeping)\",\n                ]\n            )\n        }\n\n        return try await self.contentStore.delete(keeping: keeping)\n    }\n\n    public func newIngestSession() async throws -> (id: String, ingestDir: URL) {\n        self.log.debug(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n        return try await self.contentStore.newIngestSession()\n    }\n\n    public func completeIngestSession(_ id: String) async throws -> [String] {\n        self.log.debug(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        return try await self.contentStore.completeIngestSession(id)\n    }\n\n    public func cancelIngestSession(_ id: String) async throws {\n        self.log.debug(\n            \"ContentStoreService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"id\": \"\\(id)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ContentStoreService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"id\": \"\\(id)\",\n                ]\n            )\n        }\n\n        return try await self.contentStore.cancelIngestSession(id)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Server/ImagesService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerImagesServiceClient\nimport ContainerResource\nimport Containerization\nimport ContainerizationArchive\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport Logging\nimport TerminalProgress\n\npublic actor ImagesService {\n    private let log: Logger\n    private let contentStore: ContentStore\n    private let imageStore: ImageStore\n    private let snapshotStore: SnapshotStore\n\n    public init(contentStore: ContentStore, imageStore: ImageStore, snapshotStore: SnapshotStore, log: Logger) throws {\n        self.contentStore = contentStore\n        self.imageStore = imageStore\n        self.snapshotStore = snapshotStore\n        self.log = log\n    }\n\n    private func _list() async throws -> [Containerization.Image] {\n        try await imageStore.list()\n    }\n\n    private func _get(_ reference: String) async throws -> Containerization.Image {\n        try await imageStore.get(reference: reference)\n    }\n\n    private func _get(_ description: ImageDescription) async throws -> Containerization.Image {\n        let exists = try await self._get(description.reference)\n        guard exists.descriptor == description.descriptor else {\n            throw ContainerizationError(.invalidState, message: \"descriptor mismatch: expected \\(description.descriptor), got \\(exists.descriptor)\")\n        }\n        return exists\n    }\n\n    public func list() async throws -> [ImageDescription] {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n\n        return try await imageStore.list().map { $0.description.fromCZ }\n    }\n\n    public func pull(reference: String, platform: Platform?, insecure: Bool, progressUpdate: ProgressUpdateHandler?, maxConcurrentDownloads: Int = 3) async throws\n        -> ImageDescription\n    {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"ref\": \"\\(reference)\",\n                \"platform\": \"\\(String(describing: platform))\",\n                \"insecure\": \"\\(insecure)\",\n                \"maxConcurrentDownloads\": \"\\(maxConcurrentDownloads)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"ref\": \"\\(reference)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let img = try await Self.withAuthentication(ref: reference) { auth in\n            try await self.imageStore.pull(\n                reference: reference, platform: platform, insecure: insecure, auth: auth, progress: ContainerizationProgressAdapter.handler(from: progressUpdate),\n                maxConcurrentDownloads: maxConcurrentDownloads)\n        }\n        guard let img else {\n            throw ContainerizationError(.internalError, message: \"failed to pull image \\(reference)\")\n        }\n        return img.description.fromCZ\n    }\n\n    public func push(reference: String, platform: Platform?, insecure: Bool, progressUpdate: ProgressUpdateHandler?) async throws {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"ref\": \"\\(reference)\",\n                \"platform\": \"\\(String(describing: platform))\",\n                \"insecure\": \"\\(insecure)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"ref\": \"\\(reference)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        try await Self.withAuthentication(ref: reference) { auth in\n            try await self.imageStore.push(\n                reference: reference, platform: platform, insecure: insecure, auth: auth, progress: ContainerizationProgressAdapter.handler(from: progressUpdate))\n        }\n    }\n\n    public func tag(old: String, new: String) async throws -> ImageDescription {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"old\": \"\\(old)\",\n                \"new\": \"\\(new)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"old\": \"\\(old)\",\n                    \"new\": \"\\(new)\",\n                ]\n            )\n        }\n\n        let img = try await self.imageStore.tag(existing: old, new: new)\n        return img.description.fromCZ\n    }\n\n    public func delete(reference: String, garbageCollect: Bool) async throws {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"ref\": \"\\(reference)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"ref\": \"\\(reference)\",\n                ]\n            )\n        }\n\n        try await self.imageStore.delete(reference: reference, performCleanup: garbageCollect)\n    }\n\n    public func save(references: [String], out: URL, platform: Platform?) async throws {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"references\": \"\\(references)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"references\": \"\\(references)\",\n                ]\n            )\n        }\n\n        let tempDir = FileManager.default.uniqueTemporaryDirectory()\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n        try await self.imageStore.save(references: references, out: tempDir, platform: platform)\n        let writer = try ArchiveWriter(format: .pax, filter: .none, file: out)\n        try writer.archiveDirectory(tempDir)\n        try writer.finishEncoding()\n    }\n\n    public func load(from tarFile: URL, force: Bool) async throws -> ([ImageDescription], [String]) {\n        let archivePathname = tarFile.absolutePath()\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"archivePath\": \"\\(archivePathname)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"archivePath\": \"\\(archivePathname)\",\n                ]\n            )\n        }\n\n        let reader = try ArchiveReader(file: tarFile)\n        let tempDir = FileManager.default.uniqueTemporaryDirectory()\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n        let rejectedMembers = try reader.extractContents(to: tempDir)\n        guard rejectedMembers.isEmpty || force else {\n            throw ContainerizationError(.invalidArgument, message: \"cannot load tar image with rejected paths: \\(rejectedMembers)\")\n        }\n\n        let loaded = try await self.imageStore.load(from: tempDir)\n        var images: [ImageDescription] = []\n        for image in loaded {\n            images.append(image.description.fromCZ)\n        }\n        return (images, rejectedMembers)\n    }\n\n    public func cleanUpOrphanedBlobs() async throws -> ([String], UInt64) {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\"\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\"\n                ]\n            )\n        }\n\n        let images = try await self._list()\n        let freedSnapshotBytes = try await self.snapshotStore.clean(keepingSnapshotsFor: images)\n        let (deleted, freedContentBytes) = try await self.imageStore.cleanUpOrphanedBlobs()\n        return (deleted, freedContentBytes + freedSnapshotBytes)\n    }\n\n    /// Calculate disk usage for images\n    /// - Parameter activeReferences: Set of image references currently in use by containers\n    /// - Returns: Tuple of (total count, active count, total size, reclaimable size)\n    public func calculateDiskUsage(activeReferences: Set<String>) async throws -> (Int, Int, UInt64, UInt64) {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"references\": \"\\(activeReferences)\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"references\": \"\\(activeReferences)\",\n                ]\n            )\n        }\n\n        let images = try await self._list()\n        var totalSize: UInt64 = 0\n        var reclaimableSize: UInt64 = 0\n        var activeCount = 0\n\n        for image in images {\n            // Calculate size for all platform variants\n            let imageSize = try await self.calculateImageSize(image)\n            totalSize += imageSize\n\n            // Check if image is referenced by any container\n            let isActive = activeReferences.contains(image.reference)\n            if isActive {\n                activeCount += 1\n            } else {\n                reclaimableSize += imageSize\n            }\n        }\n\n        return (images.count, activeCount, totalSize, reclaimableSize)\n    }\n\n    /// Calculate total size for an image including all platform variants\n    private func calculateImageSize(_ image: Containerization.Image) async throws -> UInt64 {\n        var totalSize: UInt64 = 0\n        let index = try await image.index()\n\n        for descriptor in index.manifests {\n            // Skip attestation manifests\n            if let refType = descriptor.annotations?[\"vnd.docker.reference.type\"],\n                refType == \"attestation-manifest\"\n            {\n                continue\n            }\n\n            guard descriptor.platform != nil else { continue }\n\n            // Get snapshot size for this platform\n            if let snapshotSize = try? await self.snapshotStore.getSnapshotSize(descriptor: descriptor) {\n                totalSize += snapshotSize\n            }\n        }\n\n        return totalSize\n    }\n}\n\n// MARK: Image Snapshot Methods\n\nextension ImagesService {\n    public func unpack(description: ImageDescription, platform: Platform?, progressUpdate: ProgressUpdateHandler?) async throws {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"description\": \"\\(description)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"description\": \"\\(description)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let img = try await self._get(description)\n        try await self.snapshotStore.unpack(image: img, platform: platform, progressUpdate: progressUpdate)\n    }\n\n    public func deleteImageSnapshot(description: ImageDescription, platform: Platform?) async throws {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"description\": \"\\(description)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"description\": \"\\(description)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let img = try await self._get(description)\n        try await self.snapshotStore.delete(for: img, platform: platform)\n    }\n\n    public func getImageSnapshot(description: ImageDescription, platform: Platform) async throws -> Filesystem {\n        self.log.debug(\n            \"ImagesService: enter\",\n            metadata: [\n                \"func\": \"\\(#function)\",\n                \"description\": \"\\(description)\",\n                \"platform\": \"\\(String(describing: platform))\",\n            ]\n        )\n        defer {\n            self.log.debug(\n                \"ImagesService: exit\",\n                metadata: [\n                    \"func\": \"\\(#function)\",\n                    \"description\": \"\\(description)\",\n                    \"platform\": \"\\(String(describing: platform))\",\n                ]\n            )\n        }\n\n        let img = try await self._get(description)\n        return try await self.snapshotStore.get(for: img, platform: platform)\n    }\n}\n\n// MARK: Static Methods\n\nextension ImagesService {\n    private static func withAuthentication<T>(\n        ref: String, _ body: @Sendable @escaping (_ auth: Authentication?) async throws -> T?\n    ) async throws -> T? {\n        var authentication: Authentication?\n        let ref = try Reference.parse(ref)\n        guard let host = ref.resolvedDomain else {\n            throw ContainerizationError(.invalidArgument, message: \"no host specified in image reference: \\(ref)\")\n        }\n        authentication = Self.authenticationFromEnv(host: host)\n        if let authentication {\n            return try await body(authentication)\n        }\n        let keychain = KeychainHelper(securityDomain: Constants.keychainID)\n        do {\n            authentication = try keychain.lookup(hostname: host)\n        } catch let err as KeychainHelper.Error {\n            guard case .keyNotFound = err else {\n                throw ContainerizationError(.internalError, message: \"error querying keychain for \\(host)\", cause: err)\n            }\n        }\n        do {\n            return try await body(authentication)\n        } catch let err as RegistryClient.Error {\n            guard case .invalidStatus(_, let status, _) = err else {\n                throw err\n            }\n            guard status == .unauthorized || status == .forbidden else {\n                throw err\n            }\n            guard authentication != nil else {\n                throw ContainerizationError(.internalError, message: \"\\(String(describing: err)), no credentials found for host \\(host)\")\n            }\n            throw err\n        }\n    }\n\n    private static func authenticationFromEnv(host: String) -> Authentication? {\n        let env = ProcessInfo.processInfo.environment\n        guard env[\"CONTAINER_REGISTRY_HOST\"] == host else {\n            return nil\n        }\n        guard let user = env[\"CONTAINER_REGISTRY_USER\"], let password = env[\"CONTAINER_REGISTRY_TOKEN\"] else {\n            return nil\n        }\n        return BasicAuthentication(username: user, password: password)\n    }\n}\n\nextension ImageDescription {\n    public var toCZ: Containerization.Image.Description {\n        .init(reference: self.reference, descriptor: self.descriptor)\n    }\n}\n\nextension Containerization.Image.Description {\n    public var fromCZ: ImageDescription {\n        .init(\n            reference: self.reference,\n            descriptor: self.descriptor\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Server/ImagesServiceHarness.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerImagesServiceClient\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOCI\nimport Foundation\nimport Logging\n\npublic struct ImagesServiceHarness: Sendable {\n    let log: Logging.Logger\n    let service: ImagesService\n\n    public init(service: ImagesService, log: Logging.Logger) {\n        self.log = log\n        self.service = service\n    }\n\n    @Sendable\n    public func pull(_ message: XPCMessage) async throws -> XPCMessage {\n        let ref = message.string(key: .imageReference)\n        guard let ref else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image reference\"\n            )\n        }\n        let platformData = message.dataNoCopy(key: .ociPlatform)\n        var platform: Platform? = nil\n        if let platformData {\n            platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        }\n        let insecure = message.bool(key: .insecureFlag)\n        let maxConcurrentDownloads = message.int64(key: .maxConcurrentDownloads)\n\n        let progressUpdateService = ProgressUpdateService(message: message)\n        let imageDescription = try await service.pull(\n            reference: ref, platform: platform, insecure: insecure, progressUpdate: progressUpdateService?.handler, maxConcurrentDownloads: Int(maxConcurrentDownloads))\n\n        let imageData = try JSONEncoder().encode(imageDescription)\n        let reply = message.reply()\n        reply.set(key: .imageDescription, value: imageData)\n        return reply\n    }\n\n    @Sendable\n    public func push(_ message: XPCMessage) async throws -> XPCMessage {\n        let ref = message.string(key: .imageReference)\n        guard let ref else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image reference\"\n            )\n        }\n        let platformData = message.dataNoCopy(key: .ociPlatform)\n        var platform: Platform? = nil\n        if let platformData {\n            platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        }\n        let insecure = message.bool(key: .insecureFlag)\n\n        let progressUpdateService = ProgressUpdateService(message: message)\n        try await service.push(reference: ref, platform: platform, insecure: insecure, progressUpdate: progressUpdateService?.handler)\n\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func tag(_ message: XPCMessage) async throws -> XPCMessage {\n        let old = message.string(key: .imageReference)\n        guard let old else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image reference\"\n            )\n        }\n        let new = message.string(key: .imageNewReference)\n        guard let new else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing new image reference\"\n            )\n        }\n        let newDescription = try await service.tag(old: old, new: new)\n        let descData = try JSONEncoder().encode(newDescription)\n        let reply = message.reply()\n        reply.set(key: .imageDescription, value: descData)\n        return reply\n    }\n\n    @Sendable\n    public func list(_ message: XPCMessage) async throws -> XPCMessage {\n        let images = try await service.list()\n        let imageData = try JSONEncoder().encode(images)\n        let reply = message.reply()\n        reply.set(key: .imageDescriptions, value: imageData)\n        return reply\n    }\n\n    @Sendable\n    public func delete(_ message: XPCMessage) async throws -> XPCMessage {\n        let ref = message.string(key: .imageReference)\n        guard let ref else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image reference\"\n            )\n        }\n        let garbageCollect = message.bool(key: .garbageCollect)\n        try await self.service.delete(reference: ref, garbageCollect: garbageCollect)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func save(_ message: XPCMessage) async throws -> XPCMessage {\n        let data = message.dataNoCopy(key: .imageDescriptions)\n        guard let data else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image description\"\n            )\n        }\n        let imageDescriptions = try JSONDecoder().decode([ImageDescription].self, from: data)\n        let references = imageDescriptions.map { $0.reference }\n\n        let platformData = message.dataNoCopy(key: .ociPlatform)\n        var platform: Platform? = nil\n        if let platformData {\n            platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        }\n        let out = message.string(key: .filePath)\n        guard let out else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing output file path\"\n            )\n        }\n        try await service.save(references: references, out: URL(filePath: out), platform: platform)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func load(_ message: XPCMessage) async throws -> XPCMessage {\n        let input = message.string(key: .filePath)\n        let force = message.bool(key: .forceLoad)\n        guard let input else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing input file path\"\n            )\n        }\n        let (images, rejectedMembers) = try await service.load(\n            from: URL(filePath: input),\n            force: force\n        )\n        let reply = message.reply()\n        let imagesData = try JSONEncoder().encode(images)\n        reply.set(key: .imageDescriptions, value: imagesData)\n        let rejectedData = try JSONEncoder().encode(rejectedMembers)\n        reply.set(key: .rejectedMembers, value: rejectedData)\n        return reply\n    }\n\n    @Sendable\n    public func cleanUpOrphanedBlobs(_ message: XPCMessage) async throws -> XPCMessage {\n        let (deleted, size) = try await service.cleanUpOrphanedBlobs()\n        let reply = message.reply()\n        let data = try JSONEncoder().encode(deleted)\n        reply.set(key: .digests, value: data)\n        reply.set(key: .imageSize, value: size)\n        return reply\n    }\n\n    @Sendable\n    public func calculateDiskUsage(_ message: XPCMessage) async throws -> XPCMessage {\n        // Decode active image references from the message\n        let activeRefsData = message.dataNoCopy(key: .activeImageReferences)\n        let activeRefs: Set<String>\n        if let activeRefsData {\n            activeRefs = try JSONDecoder().decode(Set<String>.self, from: activeRefsData)\n        } else {\n            activeRefs = Set<String>()\n        }\n\n        let (total, active, size, reclaimable) = try await service.calculateDiskUsage(activeReferences: activeRefs)\n\n        let reply = message.reply()\n        reply.set(key: .totalCount, value: Int64(total))\n        reply.set(key: .activeCount, value: Int64(active))\n        reply.set(key: .imageSize, value: size)\n        reply.set(key: .reclaimableSize, value: reclaimable)\n        return reply\n    }\n}\n\n// MARK: Image Snapshot Methods\n\nextension ImagesServiceHarness {\n    @Sendable\n    public func unpack(_ message: XPCMessage) async throws -> XPCMessage {\n        let descriptionData = message.dataNoCopy(key: .imageDescription)\n        guard let descriptionData else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing Image description\"\n            )\n        }\n        let description = try JSONDecoder().decode(ImageDescription.self, from: descriptionData)\n        var platform: Platform?\n        if let platformData = message.dataNoCopy(key: .ociPlatform) {\n            platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        }\n\n        let progressUpdateService = ProgressUpdateService(message: message)\n        try await self.service.unpack(description: description, platform: platform, progressUpdate: progressUpdateService?.handler)\n\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func deleteSnapshot(_ message: XPCMessage) async throws -> XPCMessage {\n        let descriptionData = message.dataNoCopy(key: .imageDescription)\n        guard let descriptionData else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image description\"\n            )\n        }\n        let description = try JSONDecoder().decode(ImageDescription.self, from: descriptionData)\n        let platformData = message.dataNoCopy(key: .ociPlatform)\n        var platform: Platform?\n        if let platformData {\n            platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        }\n        try await self.service.deleteImageSnapshot(description: description, platform: platform)\n        let reply = message.reply()\n        return reply\n    }\n\n    @Sendable\n    public func getSnapshot(_ message: XPCMessage) async throws -> XPCMessage {\n        let descriptionData = message.dataNoCopy(key: .imageDescription)\n        guard let descriptionData else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing image description\"\n            )\n        }\n        let description = try JSONDecoder().decode(ImageDescription.self, from: descriptionData)\n        let platformData = message.dataNoCopy(key: .ociPlatform)\n        guard let platformData else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"missing OCI platform\"\n            )\n        }\n        let platform = try JSONDecoder().decode(ContainerizationOCI.Platform.self, from: platformData)\n        let fs = try await self.service.getImageSnapshot(description: description, platform: platform)\n        let fsData = try JSONEncoder().encode(fs)\n        let reply = message.reply()\n        reply.set(key: .filesystem, value: fsData)\n        return reply\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerImagesService/Server/SnapshotStore.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport TerminalProgress\n\npublic actor SnapshotStore {\n    private static let snapshotFileName = \"snapshot\"\n    private static let snapshotInfoFileName = \"snapshot-info\"\n    private static let ingestDirName = \"ingest\"\n\n    /// Return the Unpacker to use for a given image.\n    /// If the given platform for the image cannot be unpacked return `nil`.\n    public typealias UnpackStrategy = @Sendable (Containerization.Image, Platform) async throws -> Unpacker?\n\n    public static let defaultUnpackStrategy: UnpackStrategy = { image, platform in\n        guard platform.os == \"linux\" else {\n            return nil\n        }\n        var minBlockSize = 512.gib()\n        if image.reference == DefaultsStore.get(key: .defaultInitImage) {\n            minBlockSize = 512.mib()\n        }\n        return EXT4Unpacker(blockSizeInBytes: minBlockSize)\n    }\n\n    let path: URL\n    let fm = FileManager.default\n    let ingestDir: URL\n    let unpackStrategy: UnpackStrategy\n    let log: Logger?\n\n    public init(path: URL, unpackStrategy: @escaping UnpackStrategy, log: Logger?) throws {\n        let root = path.appendingPathComponent(\"snapshots\")\n        self.path = root\n        self.ingestDir = self.path.appendingPathComponent(Self.ingestDirName)\n        self.unpackStrategy = unpackStrategy\n        self.log = log\n        try self.fm.createDirectory(at: root, withIntermediateDirectories: true)\n        try self.fm.createDirectory(at: self.ingestDir, withIntermediateDirectories: true)\n    }\n\n    public func unpack(image: Containerization.Image, platform: Platform? = nil, progressUpdate: ProgressUpdateHandler?) async throws {\n        var toUnpack: [Descriptor] = []\n        if let platform {\n            let desc = try await image.descriptor(for: platform)\n            toUnpack = [desc]\n        } else {\n            toUnpack = try await image.unpackableDescriptors()\n        }\n\n        let taskManager = ProgressTaskCoordinator()\n        var taskUpdateProgress: ProgressUpdateHandler?\n\n        for desc in toUnpack {\n            try Task.checkCancellation()\n            let snapshotDir = self.snapshotDir(desc)\n            guard !self.fm.fileExists(atPath: snapshotDir.absolutePath()) else {\n                // We have already unpacked this image + platform. Skip\n                continue\n            }\n            guard let platform = desc.platform else {\n                throw ContainerizationError(.internalError, message: \"missing platform for descriptor \\(desc.digest)\")\n            }\n            guard let unpacker = try await self.unpackStrategy(image, platform) else {\n                self.log?.warning(\"no unpacker configured, skipping unpack for \\(image.reference) for platform \\(platform.description)\")\n                continue\n            }\n            let currentSubTask = await taskManager.startTask()\n            if let progressUpdate {\n                let _taskUpdateProgress = ProgressTaskCoordinator.handler(for: currentSubTask, from: progressUpdate)\n                await _taskUpdateProgress([\n                    .setSubDescription(\"for platform \\(platform.description)\")\n                ])\n                taskUpdateProgress = _taskUpdateProgress\n            }\n\n            let tempDir = try self.tempUnpackDir()\n\n            let tempSnapshotPath = tempDir.appendingPathComponent(Self.snapshotFileName, isDirectory: false)\n            let infoPath = tempDir.appendingPathComponent(Self.snapshotInfoFileName, isDirectory: false)\n            do {\n                let progress = ContainerizationProgressAdapter.handler(from: taskUpdateProgress)\n                let mount = try await unpacker.unpack(image, for: platform, at: tempSnapshotPath, progress: progress)\n                let fs = Filesystem.block(\n                    format: mount.type,\n                    source: self.snapshotPath(desc).absolutePath(),\n                    destination: mount.destination,\n                    options: mount.options\n                )\n                let snapshotInfo = try JSONEncoder().encode(fs)\n                self.fm.createFile(atPath: infoPath.absolutePath(), contents: snapshotInfo)\n            } catch {\n                try? self.fm.removeItem(at: tempDir)\n                throw error\n            }\n            do {\n                try fm.moveItem(at: tempDir, to: snapshotDir)\n            } catch let err as NSError {\n                guard err.code == NSFileWriteFileExistsError else {\n                    throw err\n                }\n                try? self.fm.removeItem(at: tempDir)\n            }\n        }\n        await taskManager.finish()\n    }\n\n    public func delete(for image: Containerization.Image, platform: Platform? = nil) async throws {\n        var toDelete: [Descriptor] = []\n        if let platform {\n            let desc = try await image.descriptor(for: platform)\n            toDelete.append(desc)\n        } else {\n            toDelete = try await image.unpackableDescriptors()\n        }\n        for desc in toDelete {\n            let p = self.snapshotDir(desc)\n            guard self.fm.fileExists(atPath: p.absolutePath()) else {\n                continue\n            }\n            try self.fm.removeItem(at: p)\n        }\n    }\n\n    public func get(for image: Containerization.Image, platform: Platform) async throws -> Filesystem {\n        let desc = try await image.descriptor(for: platform)\n        let infoPath = snapshotInfoPath(desc)\n        let fsPath = snapshotPath(desc)\n\n        guard self.fm.fileExists(atPath: infoPath.absolutePath()),\n            self.fm.fileExists(atPath: fsPath.absolutePath())\n        else {\n            throw ContainerizationError(.notFound, message: \"image snapshot for \\(image.reference) with platform \\(platform.description)\")\n        }\n        let decoder = JSONDecoder()\n        let data = try Data(contentsOf: infoPath)\n        let fs = try decoder.decode(Filesystem.self, from: data)\n        return fs\n    }\n\n    public func clean(keepingSnapshotsFor images: [Containerization.Image] = []) async throws -> UInt64 {\n        var toKeep: [String] = [Self.ingestDirName]\n        for image in images {\n            for manifest in try await image.index().manifests {\n                guard let platform = manifest.platform else {\n                    continue\n                }\n                let desc = try await image.descriptor(for: platform)\n                toKeep.append(desc.digest.trimmingDigestPrefix)\n            }\n        }\n        let all = try self.fm.contentsOfDirectory(at: self.path, includingPropertiesForKeys: [.totalFileAllocatedSizeKey]).map {\n            $0.lastPathComponent\n        }\n        let delete = Set(all).subtracting(Set(toKeep))\n        var deletedBytes: UInt64 = 0\n        for dir in delete {\n            let unpackedPath = self.path.appending(path: dir, directoryHint: .isDirectory)\n            guard self.fm.fileExists(atPath: unpackedPath.absolutePath()) else {\n                continue\n            }\n            deletedBytes += (try? self.fm.directorySize(dir: unpackedPath)) ?? 0\n            try self.fm.removeItem(at: unpackedPath)\n        }\n        return deletedBytes\n    }\n\n    private func snapshotDir(_ desc: Descriptor) -> URL {\n        let p = self.path.appendingPathComponent(desc.digest.trimmingDigestPrefix, isDirectory: true)\n        return p\n    }\n\n    private func snapshotPath(_ desc: Descriptor) -> URL {\n        let p = self.snapshotDir(desc)\n            .appendingPathComponent(Self.snapshotFileName, isDirectory: false)\n        return p\n    }\n\n    private func snapshotInfoPath(_ desc: Descriptor) -> URL {\n        let p = self.snapshotDir(desc)\n            .appendingPathComponent(Self.snapshotInfoFileName, isDirectory: false)\n        return p\n    }\n\n    private func tempUnpackDir() throws -> URL {\n        let uniqueDirectoryURL = ingestDir.appendingPathComponent(UUID().uuidString, isDirectory: true)\n        try self.fm.createDirectory(at: uniqueDirectoryURL, withIntermediateDirectories: true, attributes: nil)\n        return uniqueDirectoryURL\n    }\n\n    /// Get the disk size for a specific snapshot descriptor\n    public func getSnapshotSize(descriptor: Descriptor) throws -> UInt64 {\n        let snapshotPath = self.snapshotDir(descriptor)\n        guard self.fm.fileExists(atPath: snapshotPath.path) else {\n            return 0\n        }\n        return try self.fm.directorySize(dir: snapshotPath)\n    }\n}\n\nextension FileManager {\n    fileprivate func directorySize(dir: URL) throws -> UInt64 {\n        var size: UInt64 = 0\n        let resourceKeys: [URLResourceKey] = [.totalFileAllocatedSizeKey]\n\n        guard\n            let enumerator = self.enumerator(\n                at: dir,\n                includingPropertiesForKeys: resourceKeys,\n                options: [.skipsHiddenFiles]\n            )\n        else {\n            return 0\n        }\n\n        for case let fileURL as URL in enumerator {\n            if let resourceValues = try? fileURL.resourceValues(forKeys: [.totalFileAllocatedSizeKey]),\n                let fileSize = resourceValues.totalFileAllocatedSize\n            {\n                size += UInt64(fileSize)\n            }\n        }\n        return size\n    }\n}\n\nextension Containerization.Image {\n    fileprivate func unpackableDescriptors() async throws -> [Descriptor] {\n        let index = try await self.index()\n        return index.manifests.filter { desc in\n            guard desc.platform != nil else {\n                return false\n            }\n            if let referenceType = desc.annotations?[\"vnd.docker.reference.type\"], referenceType == \"attestation-manifest\" {\n                return false\n            }\n            return true\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Client/NetworkClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\n\n/// A client for interacting with a single network.\npublic struct NetworkClient: Sendable {\n    static let label = \"com.apple.container.network\"\n\n    public static func machServiceLabel(id: String, plugin: String) -> String {\n        \"\\(Self.label).\\(plugin).\\(id)\"\n    }\n\n    private var machServiceLabel: String {\n        Self.machServiceLabel(id: id, plugin: plugin)\n    }\n\n    let id: String\n    let plugin: String\n\n    /// Create a client for a network.\n    public init(id: String, plugin: String) {\n        self.id = id\n        self.plugin = plugin\n    }\n}\n\n// Runtime Methods\nextension NetworkClient {\n    public func state() async throws -> NetworkState {\n        let request = XPCMessage(route: NetworkRoutes.state.rawValue)\n        let client = createClient()\n\n        let response = try await client.send(request)\n        let state = try response.state()\n        return state\n    }\n\n    public func allocate(\n        hostname: String,\n        macAddress: MACAddress? = nil\n    ) async throws -> (attachment: Attachment, additionalData: XPCMessage?) {\n        let request = XPCMessage(route: NetworkRoutes.allocate.rawValue)\n        request.set(key: NetworkKeys.hostname.rawValue, value: hostname)\n        if let macAddress = macAddress {\n            request.set(key: NetworkKeys.macAddress.rawValue, value: macAddress.description)\n        }\n\n        let client = createClient()\n\n        let response = try await client.send(request)\n        let attachment = try response.attachment()\n        let additionalData = response.additionalData()\n        return (attachment, additionalData)\n    }\n\n    public func deallocate(hostname: String) async throws {\n        let request = XPCMessage(route: NetworkRoutes.deallocate.rawValue)\n        request.set(key: NetworkKeys.hostname.rawValue, value: hostname)\n\n        let client = createClient()\n        try await client.send(request)\n    }\n\n    public func lookup(hostname: String) async throws -> Attachment? {\n        let request = XPCMessage(route: NetworkRoutes.lookup.rawValue)\n        request.set(key: NetworkKeys.hostname.rawValue, value: hostname)\n\n        let client = createClient()\n\n        let response = try await client.send(request)\n        return try response.dataNoCopy(key: NetworkKeys.attachment.rawValue).map {\n            try JSONDecoder().decode(Attachment.self, from: $0)\n        }\n    }\n\n    public func disableAllocator() async throws -> Bool {\n        let request = XPCMessage(route: NetworkRoutes.disableAllocator.rawValue)\n\n        let client = createClient()\n\n        let response = try await client.send(request)\n        return try response.allocatorDisabled()\n    }\n\n    private func createClient() -> XPCClient {\n        XPCClient(service: machServiceLabel)\n    }\n}\n\nextension XPCMessage {\n    public func additionalData() -> XPCMessage? {\n        guard let additionalData = xpc_dictionary_get_dictionary(self.underlying, NetworkKeys.additionalData.rawValue) else {\n            return nil\n        }\n        return XPCMessage(object: additionalData)\n    }\n\n    public func allocatorDisabled() throws -> Bool {\n        self.bool(key: NetworkKeys.allocatorDisabled.rawValue)\n    }\n\n    public func attachment() throws -> Attachment {\n        let data = self.dataNoCopy(key: NetworkKeys.attachment.rawValue)\n        guard let data else {\n            throw ContainerizationError(.invalidArgument, message: \"no network attachment snapshot data in message\")\n        }\n        return try JSONDecoder().decode(Attachment.self, from: data)\n    }\n\n    public func hostname() throws -> String {\n        let hostname = self.string(key: NetworkKeys.hostname.rawValue)\n        guard let hostname else {\n            throw ContainerizationError(.invalidArgument, message: \"no hostname data in message\")\n        }\n        return hostname\n    }\n\n    public func state() throws -> NetworkState {\n        let data = self.dataNoCopy(key: NetworkKeys.state.rawValue)\n        guard let data else {\n            throw ContainerizationError(.invalidArgument, message: \"no network snapshot data in message\")\n        }\n        return try JSONDecoder().decode(NetworkState.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Client/NetworkKeys.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum NetworkKeys: String {\n    case additionalData\n    case allocatorDisabled\n    case attachment\n    case hostname\n    case macAddress\n    case network\n    case state\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Client/NetworkRoutes.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum NetworkRoutes: String {\n    /// Return the current state of the network.\n    case state = \"com.apple.container.network/state\"\n    /// Allocates parameters for attaching a sandbox to the network.\n    case allocate = \"com.apple.container.network/allocate\"\n    /// Deallocates parameters for attaching a sandbox to the network.\n    case deallocate = \"com.apple.container.network/deallocate\"\n    /// Disables the allocator if no sandboxes are attached.\n    case disableAllocator = \"com.apple.container.network/disableAllocator\"\n    /// Retrieves the allocation for a hostname.\n    case lookup = \"com.apple.container.network/lookup\"\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Server/AllocationOnlyVmnetNetwork.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Logging\n\npublic actor AllocationOnlyVmnetNetwork: Network {\n    private let log: Logger\n    private var _state: NetworkState\n\n    /// Configure a bridge network that allows external system access using\n    /// network address translation.\n    public init(\n        configuration: NetworkConfiguration,\n        log: Logger\n    ) throws {\n        guard configuration.mode == .nat else {\n            throw ContainerizationError(.unsupported, message: \"invalid network mode \\(configuration.mode)\")\n        }\n\n        guard configuration.ipv4Subnet == nil else {\n            throw ContainerizationError(.unsupported, message: \"IPv4 subnet assignment is not yet implemented\")\n        }\n\n        self.log = log\n        self._state = .created(configuration)\n    }\n\n    public var state: NetworkState {\n        self._state\n    }\n\n    public nonisolated func withAdditionalData(_ handler: (XPCMessage?) throws -> Void) throws {\n        try handler(nil)\n    }\n\n    public func start() async throws {\n        guard case .created(let configuration) = _state else {\n            throw ContainerizationError(.invalidState, message: \"cannot start network \\(_state.id) in \\(_state.state) state\")\n        }\n\n        log.info(\n            \"starting allocation-only network\",\n            metadata: [\n                \"id\": \"\\(configuration.id)\",\n                \"mode\": \"\\(NetworkMode.nat.rawValue)\",\n            ]\n        )\n\n        let defaultIPv4Subnet = try CIDRv4(DefaultsStore.get(key: .defaultSubnet))\n        let ipv4Subnet = configuration.ipv4Subnet ?? defaultIPv4Subnet\n        let gateway = IPv4Address(ipv4Subnet.lower.value + 1)\n        let status = NetworkStatus(\n            ipv4Subnet: ipv4Subnet,\n            ipv4Gateway: gateway,\n            ipv6Subnet: nil,\n        )\n        self._state = .running(configuration, status)\n        log.info(\n            \"started allocation-only network\",\n            metadata: [\n                \"id\": \"\\(configuration.id)\",\n                \"mode\": \"\\(configuration.mode)\",\n                \"cidr\": \"\\(ipv4Subnet)\",\n            ]\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Server/AttachmentAllocator.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\n\nactor AttachmentAllocator {\n    private let allocator: any AddressAllocator<UInt32>\n    private var hostnames: [String: UInt32] = [:]\n\n    init(lower: UInt32, size: Int) throws {\n        allocator = try UInt32.rotatingAllocator(\n            lower: lower,\n            size: UInt32(size)\n        )\n    }\n\n    /// Allocate a network address for a host.\n    func allocate(hostname: String) async throws -> UInt32 {\n        // Client is responsible for ensuring two containers don't use same hostname, so provide existing IP if hostname exists\n        if let index = hostnames[hostname] {\n            return index\n        }\n\n        let index = try allocator.allocate()\n        hostnames[hostname] = index\n\n        return index\n    }\n\n    /// Free an allocated network address by hostname.\n    @discardableResult\n    func deallocate(hostname: String) async throws -> UInt32? {\n        guard let index = hostnames.removeValue(forKey: hostname) else {\n            return nil\n        }\n\n        try allocator.release(index)\n        return index\n    }\n\n    /// If no addresses are allocated, prevent future allocations and return true.\n    func disableAllocator() async -> Bool {\n        allocator.disableAllocator()\n    }\n\n    /// Retrieve the allocator index for a hostname.\n    func lookup(hostname: String) async throws -> UInt32? {\n        hostnames[hostname]\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Server/Network.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\n\n/// Defines common characteristics and operations for a network.\npublic protocol Network: Sendable {\n    // Contains network attributes while the network is running\n    var state: NetworkState { get async }\n\n    // Use implementation-dependent network attributes\n    nonisolated func withAdditionalData(_ handler: (XPCMessage?) throws -> Void) throws\n\n    // Start the network\n    func start() async throws\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Server/NetworkService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerNetworkServiceClient\nimport ContainerResource\nimport ContainerXPC\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Logging\n\npublic actor NetworkService: Sendable {\n    private let network: any Network\n    private let log: Logger\n    private var allocator: AttachmentAllocator\n    private var macAddresses: [UInt32: MACAddress]\n\n    /// Set up a network service for the specified network.\n    public init(\n        network: any Network,\n        log: Logger\n    ) async throws {\n        let state = await network.state\n        guard case .running(_, let status) = state else {\n            throw ContainerizationError(.invalidState, message: \"invalid network state - network \\(state.id) must be running\")\n        }\n\n        let subnet = status.ipv4Subnet\n\n        let size = Int(subnet.upper.value - subnet.lower.value - 3)\n        self.allocator = try AttachmentAllocator(lower: subnet.lower.value + 2, size: size)\n        self.macAddresses = [:]\n        self.network = network\n        self.log = log\n    }\n\n    @Sendable\n    public func state(_ message: XPCMessage) async throws -> XPCMessage {\n        let reply = message.reply()\n        let state = await network.state\n        try reply.setState(state)\n        return reply\n    }\n\n    @Sendable\n    public func allocate(_ message: XPCMessage) async throws -> XPCMessage {\n        log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        let state = await network.state\n        guard case .running(_, let status) = state else {\n            throw ContainerizationError(.invalidState, message: \"invalid network state - network \\(state.id) must be running\")\n        }\n\n        let hostname = try message.hostname()\n        let macAddress =\n            try message.string(key: NetworkKeys.macAddress.rawValue)\n            .map { try MACAddress($0) }\n            ?? MACAddress((UInt64.random(in: 0...UInt64.max) & 0x0cff_ffff_ffff) | 0xf200_0000_0000)\n        let index = try await allocator.allocate(hostname: hostname)\n        let ipv6Address = try status.ipv6Subnet\n            .map { try CIDRv6(macAddress.ipv6Address(network: $0.lower), prefix: $0.prefix) }\n        let ip = IPv4Address(index)\n        let attachment = Attachment(\n            network: state.id,\n            hostname: hostname,\n            ipv4Address: try CIDRv4(ip, prefix: status.ipv4Subnet.prefix),\n            ipv4Gateway: status.ipv4Gateway,\n            ipv6Address: ipv6Address,\n            macAddress: macAddress\n        )\n        log.info(\n            \"allocated attachment\",\n            metadata: [\n                \"hostname\": \"\\(hostname)\",\n                \"ipv4Address\": \"\\(attachment.ipv4Address)\",\n                \"ipv4Gateway\": \"\\(attachment.ipv4Gateway)\",\n                \"ipv6Address\": \"\\(attachment.ipv6Address?.description ?? \"unavailable\")\",\n                \"macAddress\": \"\\(attachment.macAddress?.description ?? \"unspecified\")\",\n            ])\n        let reply = message.reply()\n        try reply.setAttachment(attachment)\n        try network.withAdditionalData {\n            if let additionalData = $0 {\n                try reply.setAdditionalData(additionalData.underlying)\n            }\n        }\n        macAddresses[index] = macAddress\n        return reply\n    }\n\n    @Sendable\n    public func deallocate(_ message: XPCMessage) async throws -> XPCMessage {\n        log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        let hostname = try message.hostname()\n        if let index = try await allocator.deallocate(hostname: hostname) {\n            macAddresses.removeValue(forKey: index)\n        }\n        log.info(\"released attachments\", metadata: [\"hostname\": \"\\(hostname)\"])\n        return message.reply()\n    }\n\n    @Sendable\n    public func lookup(_ message: XPCMessage) async throws -> XPCMessage {\n        log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        let state = await network.state\n        guard case .running(_, let status) = state else {\n            throw ContainerizationError(.invalidState, message: \"invalid network state - network \\(state.id) must be running\")\n        }\n\n        let hostname = try message.hostname()\n        let index = try await allocator.lookup(hostname: hostname)\n        let reply = message.reply()\n        guard let index else {\n            return reply\n        }\n        guard let macAddress = macAddresses[index] else {\n            return reply\n        }\n        let address = IPv4Address(index)\n        let subnet = status.ipv4Subnet\n        let ipv4Address = try CIDRv4(address, prefix: subnet.prefix)\n        let ipv6Address = try status.ipv6Subnet\n            .map { try CIDRv6(macAddress.ipv6Address(network: $0.lower), prefix: $0.prefix) }\n        let attachment = Attachment(\n            network: state.id,\n            hostname: hostname,\n            ipv4Address: ipv4Address,\n            ipv4Gateway: status.ipv4Gateway,\n            ipv6Address: ipv6Address,\n            macAddress: macAddress\n        )\n        log.debug(\n            \"lookup attachment\",\n            metadata: [\n                \"hostname\": \"\\(hostname)\",\n                \"address\": \"\\(address)\",\n            ])\n        try reply.setAttachment(attachment)\n        return reply\n    }\n\n    @Sendable\n    public func disableAllocator(_ message: XPCMessage) async throws -> XPCMessage {\n        log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        let success = await allocator.disableAllocator()\n        log.info(\"attempted allocator disable\", metadata: [\"success\": \"\\(success)\"])\n        let reply = message.reply()\n        reply.setAllocatorDisabled(success)\n        return reply\n    }\n}\n\nextension XPCMessage {\n    fileprivate func setAdditionalData(_ additionalData: xpc_object_t) throws {\n        xpc_dictionary_set_value(self.underlying, NetworkKeys.additionalData.rawValue, additionalData)\n    }\n\n    fileprivate func setAllocatorDisabled(_ allocatorDisabled: Bool) {\n        self.set(key: NetworkKeys.allocatorDisabled.rawValue, value: allocatorDisabled)\n    }\n\n    fileprivate func setAttachment(_ attachment: Attachment) throws {\n        let data = try JSONEncoder().encode(attachment)\n        self.set(key: NetworkKeys.attachment.rawValue, value: data)\n    }\n\n    fileprivate func setState(_ state: NetworkState) throws {\n        let data = try JSONEncoder().encode(state)\n        self.set(key: NetworkKeys.state.rawValue, value: data)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerNetworkService/Server/ReservedVmnetNetwork.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport Dispatch\nimport Foundation\nimport Logging\nimport Synchronization\nimport SystemConfiguration\nimport XPC\nimport vmnet\n\n/// Creates a vmnet network with reservation APIs.\n@available(macOS 26, *)\npublic final class ReservedVmnetNetwork: Network {\n    private struct State {\n        var networkState: NetworkState\n        var network: vmnet_network_ref?\n    }\n\n    private struct NetworkInfo {\n        let network: vmnet_network_ref\n        let ipv4Subnet: CIDRv4\n        let ipv4Gateway: IPv4Address\n        let ipv6Subnet: CIDRv6\n    }\n\n    private let stateMutex: Mutex<State>\n    private let log: Logger\n\n    /// Configure a bridge network that allows external system access using\n    /// network address translation.\n    public init(\n        configuration: NetworkConfiguration,\n        log: Logger\n    ) throws {\n        guard configuration.mode == .nat || configuration.mode == .hostOnly else {\n            throw ContainerizationError(.unsupported, message: \"invalid network mode \\(configuration.mode)\")\n        }\n\n        log.info(\"creating vmnet network\")\n        self.log = log\n        let initialState = State(networkState: .created(configuration))\n        stateMutex = Mutex(initialState)\n        log.info(\"created vmnet network\")\n    }\n\n    public var state: NetworkState {\n        stateMutex.withLock { $0.networkState }\n    }\n\n    public nonisolated func withAdditionalData(_ handler: (XPCMessage?) throws -> Void) throws {\n        try stateMutex.withLock { state in\n            try handler(state.network.map { try Self.serialize_network_ref(ref: $0) })\n        }\n    }\n\n    public func start() async throws {\n        try stateMutex.withLock { state in\n            guard case .created(let configuration) = state.networkState else {\n                throw ContainerizationError(.invalidArgument, message: \"cannot start network that is in \\(state.networkState.state) state\")\n            }\n\n            let networkInfo = try startNetwork(configuration: configuration, log: log)\n\n            let networkStatus = NetworkStatus(\n                ipv4Subnet: networkInfo.ipv4Subnet,\n                ipv4Gateway: networkInfo.ipv4Gateway,\n                ipv6Subnet: networkInfo.ipv6Subnet,\n            )\n            state.networkState = NetworkState.running(configuration, networkStatus)\n            state.network = networkInfo.network\n        }\n    }\n\n    private static func serialize_network_ref(ref: vmnet_network_ref) throws -> XPCMessage {\n        var status: vmnet_return_t = .VMNET_SUCCESS\n        guard let refObject = vmnet_network_copy_serialization(ref, &status) else {\n            throw ContainerizationError(.invalidArgument, message: \"cannot serialize vmnet_network_ref to XPC object, status \\(status)\")\n        }\n        return XPCMessage(object: refObject)\n    }\n\n    private func startNetwork(configuration: NetworkConfiguration, log: Logger) throws -> NetworkInfo {\n        log.info(\n            \"starting vmnet network\",\n            metadata: [\n                \"id\": \"\\(configuration.id)\",\n                \"mode\": \"\\(configuration.mode)\",\n            ]\n        )\n\n        // set up the vmnet configuration\n        var status: vmnet_return_t = .VMNET_SUCCESS\n        let mode: vmnet.operating_modes_t = configuration.mode == .hostOnly ? .VMNET_HOST_MODE : .VMNET_SHARED_MODE\n        guard let vmnetConfiguration = vmnet_network_configuration_create(mode, &status), status == .VMNET_SUCCESS else {\n            throw ContainerizationError(.unsupported, message: \"failed to create vmnet config with status \\(status)\")\n        }\n\n        vmnet_network_configuration_disable_dhcp(vmnetConfiguration)\n\n        // subnet priority is CLI argument, UserDefault, auto\n        let defaultIpv4Subnet = try DefaultsStore.getOptional(key: .defaultSubnet).map { try CIDRv4($0) }\n        let ipv4Subnet = configuration.ipv4Subnet ?? defaultIpv4Subnet\n        let defaultIpv6Subnet = try DefaultsStore.getOptional(key: .defaultIPv6Subnet).map { try CIDRv6($0) }\n        let ipv6Subnet = configuration.ipv6Subnet ?? defaultIpv6Subnet\n\n        // set the IPv4 subnet if the caller provided one\n        if let ipv4Subnet {\n            let gateway = IPv4Address(ipv4Subnet.lower.value + 1)\n            var gatewayAddr = in_addr()\n            inet_pton(AF_INET, gateway.description, &gatewayAddr)\n            let mask = IPv4Address(ipv4Subnet.prefix.prefixMask32)\n            var maskAddr = in_addr()\n            inet_pton(AF_INET, mask.description, &maskAddr)\n            log.info(\n                \"configuring vmnet IPv4 subnet\",\n                metadata: [\"cidr\": \"\\(ipv4Subnet)\"]\n            )\n            let status = vmnet_network_configuration_set_ipv4_subnet(vmnetConfiguration, &gatewayAddr, &maskAddr)\n            guard status == .VMNET_SUCCESS else {\n                throw ContainerizationError(.internalError, message: \"failed to set subnet \\(ipv4Subnet) for IPv4 network \\(configuration.id)\")\n            }\n        }\n\n        // set the IPv6 network prefix if the caller provided one\n        if let ipv6Subnet {\n            let gateway = IPv6Address(ipv6Subnet.lower.value + 1)\n            var gatewayAddr = in6_addr()\n            inet_pton(AF_INET6, gateway.description, &gatewayAddr)\n            log.info(\n                \"configuring vmnet IPv6 prefix\",\n                metadata: [\"cidr\": \"\\(ipv6Subnet)\"]\n            )\n            let status = vmnet_network_configuration_set_ipv6_prefix(vmnetConfiguration, &gatewayAddr, ipv6Subnet.prefix.length)\n            guard status == .VMNET_SUCCESS else {\n                throw ContainerizationError(.internalError, message: \"failed to set prefix \\(ipv6Subnet) for IPv6 network \\(configuration.id)\")\n            }\n        }\n\n        // reserve the network\n        guard let network = vmnet_network_create(vmnetConfiguration, &status), status == .VMNET_SUCCESS else {\n            throw ContainerizationError(.unsupported, message: \"failed to create vmnet network with status \\(status)\")\n        }\n\n        // retrieve the subnet since the caller may not have provided one\n        var subnetAddr = in_addr()\n        var maskAddr = in_addr()\n        vmnet_network_get_ipv4_subnet(network, &subnetAddr, &maskAddr)\n        let subnetValue = UInt32(bigEndian: subnetAddr.s_addr)\n        let maskValue = UInt32(bigEndian: maskAddr.s_addr)\n        let lower = IPv4Address(subnetValue & maskValue)\n        let upper = IPv4Address(lower.value + ~maskValue)\n        let runningSubnet = try CIDRv4(lower: lower, upper: upper)\n        let runningGateway = IPv4Address(runningSubnet.lower.value + 1)\n\n        var prefixAddr = in6_addr()\n        var prefixLength = UInt8(0)\n        vmnet_network_get_ipv6_prefix(network, &prefixAddr, &prefixLength)\n        guard let prefix = Prefix(length: prefixLength) else {\n            throw ContainerizationError(.internalError, message: \"invalid IPv6 prefix length \\(prefixLength) for network \\(configuration.id)\")\n        }\n        let prefixIpv6Bytes = withUnsafeBytes(of: prefixAddr.__u6_addr.__u6_addr8) {\n            Array($0)\n        }\n        let prefixIpv6Addr = try IPv6Address(prefixIpv6Bytes)\n        let runningV6Subnet = try CIDRv6(prefixIpv6Addr, prefix: prefix)\n\n        log.info(\n            \"started vmnet network\",\n            metadata: [\n                \"id\": \"\\(configuration.id)\",\n                \"mode\": \"\\(configuration.mode)\",\n                \"cidr\": \"\\(runningSubnet)\",\n                \"cidrv6\": \"\\(runningV6Subnet)\",\n            ]\n        )\n\n        return NetworkInfo(\n            network: network,\n            ipv4Subnet: runningSubnet,\n            ipv4Gateway: runningGateway,\n            ipv6Subnet: runningV6Subnet,\n        )\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/Bundle+Log.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport Foundation\n\nextension ContainerResource.Bundle {\n    /// The pathname for the workload log file.\n    public var containerLog: URL {\n        path.appendingPathComponent(\"stdio.log\")\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/ExitMonitor.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Logging\n\n/// Track when long running work exits, and notify the caller via a callback.\npublic actor ExitMonitor {\n    /// A callback that receives the client identifier and exit code.\n    public typealias ExitCallback = @Sendable (String, ExitStatus) async throws -> Void\n\n    /// A function that waits for work to complete, returning an exit code.\n    public typealias WaitHandler = @Sendable () async throws -> ExitStatus\n\n    /// Create a new monitor.\n    ///\n    /// - Parameters:\n    ///   - log: The destination for log messages.\n    public init(log: Logger? = nil) {\n        self.log = log\n    }\n\n    private var exitCallbacks: [String: ExitCallback] = [:]\n    private var runningTasks: [String: Task<Void, Never>] = [:]\n    private let log: Logger?\n\n    /// Remove tracked work from the monitor.\n    ///\n    /// - Parameters:\n    ///   - id: The client identifier for the tracked work.\n    public func stopTracking(id: String) async {\n        if let task = self.runningTasks[id] {\n            task.cancel()\n        }\n        exitCallbacks.removeValue(forKey: id)\n        runningTasks.removeValue(forKey: id)\n    }\n\n    /// Register long running work so that the monitor invokes\n    /// a callback when the work completes.\n    ///\n    /// - Parameters:\n    ///   - id: The client identifier for the work.\n    ///   - onExit: The callback to invoke when the work completes.\n    public func registerProcess(id: String, onExit: @escaping ExitCallback) async throws {\n        guard self.exitCallbacks[id] == nil else {\n            throw ContainerizationError(.invalidState, message: \"ExitMonitor already setup for process \\(id)\")\n        }\n        self.exitCallbacks[id] = onExit\n    }\n\n    /// Await the completion of previously registered item of work.\n    ///\n    /// - Parameters:\n    ///   - id: The client identifier for the work.\n    ///   - waitingOn: A function that waits for the work to complete,\n    ///     and then returns an exit code.\n    public func track(id: String, waitingOn: @escaping WaitHandler) async throws {\n        guard let onExit = self.exitCallbacks[id] else {\n            throw ContainerizationError(.invalidState, message: \"ExitMonitor not setup for process \\(id)\")\n        }\n        guard self.runningTasks[id] == nil else {\n            throw ContainerizationError(.invalidState, message: \"already have a running task tracking process \\(id)\")\n        }\n        self.runningTasks[id] = Task {\n            do {\n                let exitStatus = try await waitingOn()\n                try await onExit(id, exitStatus)\n            } catch {\n                self.log?.error(\"WaitHandler for \\(id) threw error \\(String(describing: error))\")\n                try? await onExit(id, ExitStatus(exitCode: -1))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/SandboxClient.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationOS\nimport Foundation\nimport TerminalProgress\n\n/// A client for interacting with a single sandbox.\npublic struct SandboxClient: Sendable {\n    static let label = \"com.apple.container.runtime\"\n\n    public static func machServiceLabel(runtime: String, id: String) -> String {\n        \"\\(Self.label).\\(runtime).\\(id)\"\n    }\n\n    private var machServiceLabel: String {\n        Self.machServiceLabel(runtime: runtime, id: id)\n    }\n\n    let id: String\n    let runtime: String\n    let client: XPCClient\n\n    init(id: String, runtime: String, client: XPCClient) {\n        self.id = id\n        self.runtime = runtime\n        self.client = client\n    }\n\n    /// Create a SandboxClient by ID and runtime string. The returned client is ready to be used\n    /// without additional steps.\n    public static func create(id: String, runtime: String, timeout: Duration = XPCClient.xpcRegistrationTimeout) async throws -> SandboxClient {\n        let label = Self.machServiceLabel(runtime: runtime, id: id)\n        let client = XPCClient(service: label)\n        let request = XPCMessage(route: SandboxRoutes.createEndpoint.rawValue)\n\n        let response: XPCMessage\n        do {\n            response = try await client.send(request, responseTimeout: timeout)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to create container \\(id)\",\n                cause: error\n            )\n        }\n        guard let endpoint = response.endpoint(key: SandboxKeys.sandboxServiceEndpoint.rawValue) else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get endpoint for sandbox service\"\n            )\n        }\n\n        let endpointConnection = xpc_connection_create_from_endpoint(endpoint)\n        let xpcClient = XPCClient(connection: endpointConnection, label: label)\n        return SandboxClient(id: id, runtime: runtime, client: xpcClient)\n    }\n}\n\n// Runtime Methods\nextension SandboxClient {\n    public func bootstrap(stdio: [FileHandle?], allocatedAttachments: [AllocatedAttachment]) async throws {\n        let request = XPCMessage(route: SandboxRoutes.bootstrap.rawValue)\n\n        for (i, h) in stdio.enumerated() {\n            let key: SandboxKeys = try {\n                switch i {\n                case 0: .stdin\n                case 1: .stdout\n                case 2: .stderr\n                default:\n                    throw ContainerizationError(.invalidArgument, message: \"invalid fd \\(i)\")\n                }\n            }()\n\n            if let h {\n                request.set(key: key.rawValue, value: h)\n            }\n        }\n\n        do {\n            try request.setAllocatedAttachments(allocatedAttachments)\n            try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to bootstrap container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func state() async throws -> SandboxSnapshot {\n        let request = XPCMessage(route: SandboxRoutes.state.rawValue)\n        let response: XPCMessage\n        do {\n            response = try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get state for container \\(self.id)\",\n                cause: error\n            )\n        }\n        return try response.sandboxSnapshot()\n    }\n\n    public func createProcess(_ id: String, config: ProcessConfiguration, stdio: [FileHandle?]) async throws {\n        let request = XPCMessage(route: SandboxRoutes.createProcess.rawValue)\n        request.set(key: SandboxKeys.id.rawValue, value: id)\n        let data = try JSONEncoder().encode(config)\n        request.set(key: SandboxKeys.processConfig.rawValue, value: data)\n\n        for (i, h) in stdio.enumerated() {\n            let key: SandboxKeys = try {\n                switch i {\n                case 0: .stdin\n                case 1: .stdout\n                case 2: .stderr\n                default:\n                    throw ContainerizationError(.invalidArgument, message: \"invalid fd \\(i)\")\n                }\n            }()\n\n            if let h {\n                request.set(key: key.rawValue, value: h)\n            }\n        }\n\n        do {\n            try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to create process \\(id) in container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func startProcess(_ id: String) async throws {\n        let request = XPCMessage(route: SandboxRoutes.start.rawValue)\n        request.set(key: SandboxKeys.id.rawValue, value: id)\n        do {\n            try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to start process \\(id) in container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func stop(options: ContainerStopOptions) async throws {\n        let request = XPCMessage(route: SandboxRoutes.stop.rawValue)\n\n        let data = try JSONEncoder().encode(options)\n        request.set(key: SandboxKeys.stopOptions.rawValue, value: data)\n\n        let responseTimeout = Duration(.seconds(Int64(options.timeoutInSeconds + 1)))\n        do {\n            try await self.client.send(request, responseTimeout: responseTimeout)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to stop container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func kill(_ id: String, signal: Int64) async throws {\n        let request = XPCMessage(route: SandboxRoutes.kill.rawValue)\n        request.set(key: SandboxKeys.id.rawValue, value: id)\n        request.set(key: SandboxKeys.signal.rawValue, value: signal)\n\n        do {\n            try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to send signal \\(signal) to process \\(id) in container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func resize(_ id: String, size: Terminal.Size) async throws {\n        let request = XPCMessage(route: SandboxRoutes.resize.rawValue)\n        request.set(key: SandboxKeys.id.rawValue, value: id)\n        request.set(key: SandboxKeys.width.rawValue, value: UInt64(size.width))\n        request.set(key: SandboxKeys.height.rawValue, value: UInt64(size.height))\n\n        do {\n            try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to resize pty for process \\(id) in container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func wait(_ id: String) async throws -> ExitStatus {\n        let request = XPCMessage(route: SandboxRoutes.wait.rawValue)\n        request.set(key: SandboxKeys.id.rawValue, value: id)\n\n        let response: XPCMessage\n        do {\n            response = try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to wait for process \\(id) in container \\(self.id)\",\n                cause: error\n            )\n        }\n        let code = response.int64(key: SandboxKeys.exitCode.rawValue)\n        let date = response.date(key: SandboxKeys.exitedAt.rawValue)\n        return ExitStatus(exitCode: Int32(code), exitedAt: date)\n    }\n\n    public func dial(_ port: UInt32) async throws -> FileHandle {\n        let request = XPCMessage(route: SandboxRoutes.dial.rawValue)\n        request.set(key: SandboxKeys.port.rawValue, value: UInt64(port))\n\n        let response: XPCMessage\n        do {\n            response = try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to dial \\(port) on \\(self.id)\",\n                cause: error\n            )\n        }\n        guard let fh = response.fileHandle(key: SandboxKeys.fd.rawValue) else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get fd for vsock port \\(port)\"\n            )\n        }\n        return fh\n    }\n\n    public func shutdown() async throws {\n        let request = XPCMessage(route: SandboxRoutes.shutdown.rawValue)\n\n        do {\n            _ = try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to shutdown container \\(self.id)\",\n                cause: error\n            )\n        }\n    }\n\n    public func statistics() async throws -> ContainerStats {\n        let request = XPCMessage(route: SandboxRoutes.statistics.rawValue)\n\n        let response: XPCMessage\n        do {\n            response = try await self.client.send(request)\n        } catch {\n            throw ContainerizationError(\n                .internalError,\n                message: \"failed to get statistics for container \\(self.id)\",\n                cause: error\n            )\n        }\n\n        guard let data = response.dataNoCopy(key: SandboxKeys.statistics.rawValue) else {\n            throw ContainerizationError(\n                .internalError,\n                message: \"no statistics data returned\"\n            )\n        }\n\n        return try JSONDecoder().decode(ContainerStats.self, from: data)\n    }\n}\n\nextension XPCMessage {\n    public func id() throws -> String {\n        let id = self.string(key: SandboxKeys.id.rawValue)\n        guard let id else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"no id\"\n            )\n        }\n        return id\n    }\n\n    func sandboxSnapshot() throws -> SandboxSnapshot {\n        let data = self.dataNoCopy(key: SandboxKeys.snapshot.rawValue)\n        guard let data else {\n            throw ContainerizationError(\n                .invalidArgument,\n                message: \"no state data returned\"\n            )\n        }\n        return try JSONDecoder().decode(SandboxSnapshot.self, from: data)\n    }\n\n    func setAllocatedAttachments(_ allocatedAttachments: [AllocatedAttachment]) throws {\n        let encoder = JSONEncoder()\n        let allocatedAttachmentsArray = xpc_array_create_empty()\n        for allocatedAttach in allocatedAttachments {\n            let xpcObject: xpc_object_t = xpc_dictionary_create_empty()\n            let networkXPC = XPCMessage(object: xpcObject)\n\n            let attachmentEncoded = try encoder.encode(allocatedAttach.attachment)\n            networkXPC.set(key: SandboxKeys.networkAttachment.rawValue, value: attachmentEncoded)\n\n            let pluginInfoEncoded = try encoder.encode(allocatedAttach.pluginInfo)\n            networkXPC.set(key: SandboxKeys.networkPluginInfo.rawValue, value: pluginInfoEncoded)\n\n            if let additionalData = allocatedAttach.additionalData {\n                xpc_dictionary_set_value(networkXPC.underlying, SandboxKeys.networkAdditionalData.rawValue, additionalData.underlying)\n            }\n\n            xpc_array_append_value(allocatedAttachmentsArray, networkXPC.underlying)\n        }\n        self.set(key: SandboxKeys.allocatedAttachments.rawValue, value: allocatedAttachmentsArray)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/SandboxKeys.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum SandboxKeys: String {\n    /// ID key.\n    case id\n    /// Vsock port number key.\n    case port\n    /// Exit code for a process\n    case exitCode\n    /// Exit timestamp for a process\n    case exitedAt\n    /// FD to a container resource key.\n    case fd\n    /// Options for stopping a container key.\n    case stopOptions\n    /// An endpoint to talk to a sandbox service.\n    case sandboxServiceEndpoint\n\n    /// Process request keys.\n    case signal\n    case snapshot\n    case stdin\n    case stdout\n    case stderr\n    case width\n    case height\n    case processConfig\n\n    /// Container statistics\n    case statistics\n\n    /// Network resource keys.\n    case allocatedAttachments\n    case networkAdditionalData\n    case networkAttachment\n    case networkPluginInfo\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/SandboxRoutes.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum SandboxRoutes: String {\n    /// Create an xpc endpoint to the sandbox instance.\n    case createEndpoint = \"com.apple.container.sandbox/createEndpoint\"\n    /// Bootstrap the sandbox instance and create the init process.\n    case bootstrap = \"com.apple.container.sandbox/bootstrap\"\n    /// Create a process in the sandbox.\n    case createProcess = \"com.apple.container.sandbox/createProcess\"\n    /// Start a process in the sandbox.\n    case start = \"com.apple.container.sandbox/start\"\n    /// Stop the sandbox.\n    case stop = \"com.apple.container.sandbox/stop\"\n    /// Return the current state of the sandbox.\n    case state = \"com.apple.container.sandbox/state\"\n    /// Kill a process in the sandbox.\n    case kill = \"com.apple.container.sandbox/kill\"\n    /// Resize the pty of a process in the sandbox.\n    case resize = \"com.apple.container.sandbox/resize\"\n    /// Wait on a process in the sandbox.\n    case wait = \"com.apple.container.sandbox/wait\"\n    /// Execute a new process in the sandbox.\n    case exec = \"com.apple.container.sandbox/exec\"\n    /// Dial a vsock port in the sandbox.\n    case dial = \"com.apple.container.sandbox/dial\"\n    /// Shutdown the sandbox service process.\n    case shutdown = \"com.apple.container.sandbox/shutdown\"\n    /// Get statistics for the sandbox.\n    case statistics = \"com.apple.container.sandbox/statistics\"\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/SandboxRuntimeConfiguration.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport Containerization\nimport ContainerizationError\nimport Foundation\n\npublic struct RuntimeConfiguration: Codable, Sendable {\n    static let runtimeConfigurationFilename = \"runtime-configuration.json\"\n\n    public let path: URL\n    public let initialFilesystem: Filesystem\n    public let kernel: Kernel\n    public let containerConfiguration: ContainerConfiguration?\n    public let containerRootFilesystem: Filesystem?\n    public let options: ContainerCreateOptions?\n\n    public init(\n        path: URL,\n        initialFilesystem: Filesystem,\n        kernel: Kernel,\n        containerConfiguration: ContainerConfiguration? = nil,\n        containerRootFilesystem: Filesystem? = nil,\n        options: ContainerCreateOptions? = nil\n    ) {\n        self.path = path\n        self.initialFilesystem = initialFilesystem\n        self.kernel = kernel\n        self.containerConfiguration = containerConfiguration\n        self.containerRootFilesystem = containerRootFilesystem\n        self.options = options\n    }\n\n    public var runtimeConfigurationPath: URL {\n        self.path.appendingPathComponent(Self.runtimeConfigurationFilename)\n    }\n\n    public func writeRuntimeConfiguration() throws {\n        // Ensure the parent directory exists\n        let directory = self.runtimeConfigurationPath.deletingLastPathComponent()\n        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)\n\n        let data = try JSONEncoder().encode(self)\n        try data.write(to: self.runtimeConfigurationPath)\n    }\n\n    public static func readRuntimeConfiguration(from runtimeConfigurationPath: URL) throws -> RuntimeConfiguration {\n        let configurationPath = runtimeConfigurationPath.appendingPathComponent(RuntimeConfiguration.runtimeConfigurationFilename)\n        guard FileManager.default.fileExists(atPath: configurationPath.path) else {\n            throw ContainerizationError(\n                .notFound,\n                message: \"runtime configuration file not found at path: \\(configurationPath.path)\"\n            )\n        }\n\n        let data = try Data(contentsOf: configurationPath)\n        return try JSONDecoder().decode(RuntimeConfiguration.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Client/SandboxSnapshot.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\n\n/// A snapshot of a sandbox and its resources.\npublic struct SandboxSnapshot: Codable, Sendable {\n    /// The runtime status of the sandbox.\n    public var status: RuntimeStatus\n    /// Network attachments for the sandbox.\n    public var networks: [Attachment]\n    /// Containers placed in the sandbox.\n    public var containers: [ContainerSnapshot]\n\n    public init(\n        status: RuntimeStatus,\n        networks: [Attachment],\n        containers: [ContainerSnapshot]\n    ) {\n        self.status = status\n        self.networks = networks\n        self.containers = containers\n    }\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Server/InterfaceStrategy.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerXPC\nimport Containerization\n\n/// A strategy for mapping network attachment information to a network interface.\npublic protocol InterfaceStrategy: Sendable {\n    /// Map a client network attachment request to a network interface specification.\n    ///\n    /// - Parameters:\n    ///   - attachment: General attachment information that is common\n    ///     for all networks.\n    ///   - interfaceIndex: The zero-based index of the interface.\n    ///   - additionalData: If present, attachment information that is\n    ///     specific for the network to which the container will attach.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    func toInterface(attachment: Attachment, interfaceIndex: Int, additionalData: XPCMessage?) throws -> Interface\n}\n"
  },
  {
    "path": "Sources/Services/ContainerSandboxService/Server/SandboxService.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerOS\nimport ContainerPersistence\nimport ContainerResource\nimport ContainerSandboxServiceClient\nimport ContainerXPC\nimport Containerization\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport NIO\nimport NIOFoundationCompat\nimport SocketForwarder\nimport Synchronization\nimport SystemPackage\n\nimport struct ContainerizationOCI.Mount\nimport struct ContainerizationOCI.Process\n\n/// An XPC service that manages the lifecycle of a single VM-backed container.\npublic actor SandboxService {\n    private let connection: xpc_connection_t\n    private let root: URL\n    private let interfaceStrategies: [NetworkPluginInfo: InterfaceStrategy]\n    private var container: ContainerInfo?\n    private let monitor: ExitMonitor\n    private let eventLoopGroup: any EventLoopGroup\n    private var waiters: [String: ExitWaiter] = [:]\n    private let lock: AsyncLock = AsyncLock()\n    private let log: Logging.Logger\n    private var state: State = .created\n    private var processes: [String: ProcessInfo] = [:]\n    private var socketForwarders: [SocketForwarderResult] = []\n\n    private static let sshAuthSocketGuestPath = \"/run/host-services/ssh-auth.sock\"\n    private static let sshAuthSocketEnvVar = \"SSH_AUTH_SOCK\"\n\n    class ExitWaiter {\n        public var exitCode: Int32? = nil\n        public var continuations: [CheckedContinuation<ExitStatus, Never>] = []\n\n        public func register(_ cc: CheckedContinuation<ExitStatus, Never>) {\n            continuations.append(cc)\n        }\n\n        public func doExit(code: Int32) {\n            for cc in continuations {\n                cc.resume(returning: ExitStatus(exitCode: code))\n            }\n\n            exitCode = code\n        }\n\n        public func exited() -> Bool {\n            exitCode != nil\n        }\n    }\n\n    private static func sshAuthSocketHostUrl(config: ContainerConfiguration) -> URL? {\n        if config.ssh, let sshSocket = Foundation.ProcessInfo.processInfo.environment[Self.sshAuthSocketEnvVar] {\n            return URL(fileURLWithPath: sshSocket)\n        }\n        return nil\n    }\n\n    public init(\n        root: URL,\n        interfaceStrategies: [NetworkPluginInfo: InterfaceStrategy],\n        eventLoopGroup: any EventLoopGroup,\n        connection: xpc_connection_t,\n        log: Logger\n    ) {\n        self.root = root\n        self.interfaceStrategies = interfaceStrategies\n        self.log = log\n        self.monitor = ExitMonitor(log: log)\n        self.eventLoopGroup = eventLoopGroup\n        self.connection = connection\n    }\n\n    /// Returns an endpoint from an anonymous xpc connection.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with no parameters.\n    ///\n    /// - Returns: An XPC message with the following parameters:\n    ///   - endpoint: An XPC endpoint that can be used to communicate\n    ///     with the sandbox service.\n    @Sendable\n    public func createEndpoint(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        let endpoint = xpc_endpoint_create(self.connection)\n        let reply = message.reply()\n        reply.set(key: SandboxKeys.sandboxServiceEndpoint.rawValue, value: endpoint)\n        return reply\n    }\n\n    /// Start the VM and the guest agent process for a container.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with no parameters.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func bootstrap(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        // Create the bundle if it doesn't exist yet\n        if !self.bundleExists(at: self.root) {\n            try self.createBundle()\n        }\n\n        return try await self.lock.withLock { _ in\n            guard await self.state == .created else {\n                throw ContainerizationError(\n                    .invalidState,\n                    message: \"container expected to be in created state, got: \\(await self.state)\"\n                )\n            }\n\n            let bundle = ContainerResource.Bundle(path: self.root)\n            try bundle.createLogFile()\n\n            var config = try bundle.configuration\n\n            var kernel = try bundle.kernel\n            kernel.commandLine.kernelArgs.append(\"oops=panic\")\n            kernel.commandLine.kernelArgs.append(\"lsm=lockdown,capability,landlock,yama,apparmor\")\n            let vmm = VZVirtualMachineManager(\n                kernel: kernel,\n                initialFilesystem: bundle.initialFilesystem.asMount,\n                rosetta: config.rosetta,\n                logger: self.log\n            )\n\n            let allocatedAttachments = try message.getAllocatedAttachments()\n\n            // Dynamically configure the DNS nameserver from a network if no explicit configuration\n            if let dns = config.dns, dns.nameservers.isEmpty {\n                let defaultNameservers = try await self.getDefaultNameservers(allocatedAttachments: allocatedAttachments)\n                if !defaultNameservers.isEmpty {\n                    config.dns = ContainerConfiguration.DNSConfiguration(\n                        nameservers: defaultNameservers,\n                        domain: dns.domain,\n                        searchDomains: dns.searchDomains,\n                        options: dns.options\n                    )\n                }\n            }\n\n            var attachments: [Attachment] = []\n            var interfaces: [Interface] = []\n            for index in 0..<allocatedAttachments.count {\n                let allocatedAttach = allocatedAttachments[index]\n                attachments.append(allocatedAttach.attachment)\n\n                guard let iStrategy = self.interfaceStrategies[allocatedAttach.pluginInfo] else {\n                    throw ContainerizationError(\n                        .internalError, message: \"no available interface strategy for network \\(allocatedAttach.attachment.network), \\(allocatedAttach.pluginInfo)\")\n                }\n\n                let interface = try iStrategy.toInterface(\n                    attachment: allocatedAttach.attachment,\n                    interfaceIndex: index,\n                    additionalData: allocatedAttach.additionalData\n                )\n                interfaces.append(interface)\n            }\n\n            let stdio = message.stdio()\n            let containerLog = try FileHandle(forWritingTo: bundle.containerLog)\n            let stdout = {\n                if let h = stdio[1] {\n                    return MultiWriter(handles: [h, containerLog])\n                }\n                return MultiWriter(handles: [containerLog])\n            }()\n\n            let stderr: MultiWriter? = {\n                if !config.initProcess.terminal {\n                    if let h = stdio[2] {\n                        return MultiWriter(handles: [h, containerLog])\n                    }\n                    return MultiWriter(handles: [containerLog])\n                }\n                return nil\n            }()\n\n            let stdin = {\n                stdio[0] ?? nil\n            }()\n\n            let id = config.id\n            let rootfs = try bundle.containerRootfs.asMount\n            let container = try LinuxContainer(id, rootfs: rootfs, vmm: vmm, logger: self.log) { czConfig in\n                try Self.configureContainer(czConfig: &czConfig, config: config)\n                czConfig.interfaces = interfaces\n                czConfig.process.stdout = stdout\n                czConfig.process.stderr = stderr\n                czConfig.process.stdin = stdin\n                // NOTE: We can support a user providing new entries eventually, but for now craft\n                // a default /etc/hosts.\n                var hostsEntries = [Hosts.Entry.localHostIPV4()]\n                if !interfaces.isEmpty {\n                    let primaryIfaceAddr = interfaces[0].ipv4Address\n                    hostsEntries.append(\n                        Hosts.Entry(\n                            ipAddress: primaryIfaceAddr.address.description,\n                            hostnames: [czConfig.hostname ?? id],\n                        ))\n                }\n                czConfig.hosts = Hosts(entries: hostsEntries)\n                czConfig.bootLog = BootLog.file(path: bundle.bootlog, append: true)\n            }\n\n            let ctrInfo = ContainerInfo(\n                container: container,\n                config: config,\n                attachments: attachments,\n                bundle: bundle,\n                io: (in: stdin, out: stdout, err: stderr)\n            )\n            await self.setContainer(ctrInfo)\n\n            do {\n                try await container.create()\n\n                try await self.initializeWaiters(for: id)\n                try await self.monitor.registerProcess(id: config.id, onExit: self.onContainerExit)\n                if !container.interfaces.isEmpty {\n                    try await self.startSocketForwarders(attachment: attachments[0], publishedPorts: config.publishedPorts)\n                }\n                await self.setState(.booted)\n            } catch {\n                do {\n                    try await self.cleanUpContainer(containerInfo: ctrInfo)\n                    await self.setState(.stopped)\n                } catch {\n                    self.log.error(\"failed to clean up container\", metadata: [\"error\": \"\\(error)\"])\n                }\n                throw error\n            }\n            return message.reply()\n        }\n    }\n\n    /// Start the container workload inside the virtual machine.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: A client identifier for the process.\n    ///     - stdio: An array of file handles for standard input, output, and error.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func startProcess(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { lock in\n            let id = try message.id()\n            let containerInfo = try await self.getContainer()\n            let containerId = containerInfo.container.id\n            if id == containerId {\n                try await self.startInitProcess(lock: lock)\n                await self.setState(.running)\n            } else {\n                try await self.startExecProcess(processId: id, lock: lock)\n            }\n            return message.reply()\n        }\n    }\n\n    /// Get statistics for the container.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: A client identifier for the process.\n    ///     - stdio: An array of file handles for standard input, output, and error.\n    ///\n    /// - Returns: An XPC message with the following parameters:\n    ///   - statistics: JSON serialization of the `ContainerStats`.\n    @Sendable\n    public func statistics(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { lock in\n            let containerInfo = try await self.getContainer()\n            let stats = try await containerInfo.container.statistics()\n\n            let containerStats = ContainerStats(\n                id: stats.id,\n                memoryUsageBytes: stats.memory?.usageBytes,\n                memoryLimitBytes: stats.memory?.limitBytes,\n                cpuUsageUsec: stats.cpu?.usageUsec,\n                networkRxBytes: stats.networks?.reduce(0) { $0 + $1.receivedBytes },\n                networkTxBytes: stats.networks?.reduce(0) { $0 + $1.transmittedBytes },\n                blockReadBytes: stats.blockIO?.devices.reduce(0) { $0 + $1.readBytes },\n                blockWriteBytes: stats.blockIO?.devices.reduce(0) { $0 + $1.writeBytes },\n                numProcesses: stats.process?.current\n            )\n\n            let reply = message.reply()\n            let data = try JSONEncoder().encode(containerStats)\n            reply.set(key: SandboxKeys.statistics.rawValue, value: data)\n            return reply\n        }\n    }\n\n    /// Shutdown the SandboxService.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with no parameters.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func shutdown(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { _ in\n            switch await self.state {\n            case .created, .stopped, .stopping:\n                await self.setState(.shuttingDown)\n\n            default:\n                throw ContainerizationError(\n                    .invalidState,\n                    message: \"cannot shutdown: container is not stopped\"\n                )\n            }\n\n            return message.reply()\n        }\n    }\n\n    /// Create a process inside the virtual machine for the container.\n    ///\n    /// Use this procedure to run ad hoc processes in the virtual\n    /// machine (`container exec`).\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: A client identifier for the process.\n    ///     - processConfig: JSON serialization of the `ProcessConfiguration`\n    ///       containing the process attributes.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func createProcess(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { [self] _ in\n            switch await self.state {\n            case .running, .booted:\n                let id = try message.id()\n                let config = try message.processConfig()\n                let stdio = message.stdio()\n\n                try await self.addNewProcess(id, config, stdio)\n\n                try await self.initializeWaiters(for: id)\n                do {\n                    try await self.monitor.registerProcess(\n                        id: id,\n                        onExit: { id, exitStatus in\n                            await self.releaseWaiters(for: id, status: exitStatus)\n\n                            guard let process = await self.processes[id]?.process else {\n                                throw ContainerizationError(\n                                    .invalidState,\n                                    message: \"ProcessInfo missing for process \\(id)\"\n                                )\n                            }\n                            try await process.delete()\n                            try await self.setProcessState(id: id, state: .stopped)\n                        }\n                    )\n                } catch {\n                    await self.releaseWaiters(for: id, status: ExitStatus(exitCode: -1))\n                    throw error\n                }\n\n                return message.reply()\n            default:\n                throw ContainerizationError(\n                    .invalidState,\n                    message: \"cannot exec: container is not running\"\n                )\n            }\n        }\n    }\n\n    /// Return the state for the sandbox and its containers.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with no parameters.\n    ///\n    /// - Returns: An XPC message with the following parameters:\n    ///   - snapshot: The JSON serialization of the `SandboxSnapshot`\n    ///     that contains the state information.\n    @Sendable\n    public func state(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        var status: RuntimeStatus = .unknown\n        var networks: [Attachment] = []\n        var cs: ContainerSnapshot?\n\n        switch state {\n        case .created, .stopped, .booted, .shuttingDown:\n            status = .stopped\n        case .stopping:\n            status = .stopping\n        case .running:\n            let ctr = try getContainer()\n\n            status = .running\n            networks = ctr.attachments\n            cs = ContainerSnapshot(\n                configuration: ctr.config,\n                status: RuntimeStatus.running,\n                networks: networks\n            )\n        }\n\n        let reply = message.reply()\n        try reply.setState(\n            .init(\n                status: status,\n                networks: networks,\n                containers: cs != nil ? [cs!] : []\n            )\n        )\n        return reply\n    }\n\n    /// Stop the container workload, any ad hoc processes, and the underlying\n    /// virtual machine.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - stopOptions: JSON serialization of `ContainerStopOptions`\n    ///       that modify stop behavior.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func stop(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { _ in\n            switch await self.state {\n            case .running, .booted:\n                await self.setState(.stopping)\n\n                let ctr = try await self.getContainer()\n                let stopOptions = try message.stopOptions()\n                let exitStatus = try await self.gracefulStopContainer(\n                    ctr.container,\n                    stopOpts: stopOptions\n                )\n\n                do {\n                    if case .stopped = await self.state {\n                        return message.reply()\n                    }\n                    try await self.cleanUpContainer(containerInfo: ctr, exitStatus: exitStatus)\n                } catch {\n                    self.log.error(\"failed to clean up container\", metadata: [\"error\": \"\\(error)\"])\n                }\n                await self.setState(.stopped)\n            default:\n                break\n            }\n            return message.reply()\n        }\n    }\n\n    /// Signal a process running in the virtual machine.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: The process identifier.\n    ///     - signal: The signal value.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func kill(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        return try await self.lock.withLock { [self] _ in\n            switch await self.state {\n            case .running:\n                let ctr = try await getContainer()\n                let id = try message.id()\n                if id != ctr.container.id {\n                    guard let processInfo = await self.processes[id] else {\n                        throw ContainerizationError(.invalidState, message: \"process \\(id) does not exist\")\n                    }\n\n                    guard let proc = processInfo.process else {\n                        throw ContainerizationError(.invalidState, message: \"process \\(id) not started\")\n                    }\n                    try await proc.kill(Int32(try message.signal()))\n                    return message.reply()\n                }\n\n                // TODO: fix underlying signal value to int64\n                try await ctr.container.kill(Int32(try message.signal()))\n                return message.reply()\n            default:\n                throw ContainerizationError(\n                    .invalidState,\n                    message: \"cannot kill: container is not running\"\n                )\n            }\n        }\n    }\n\n    /// Resize the terminal for a process.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: The process identifier.\n    ///     - width: The terminal width.\n    ///     - height: The terminal height.\n    ///\n    /// - Returns: An XPC message with no parameters.\n    @Sendable\n    public func resize(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.trace(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.trace(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        switch self.state {\n        case .running:\n            let id = try message.id()\n            let ctr = try getContainer()\n            let width = message.uint64(key: SandboxKeys.width.rawValue)\n            let height = message.uint64(key: SandboxKeys.height.rawValue)\n\n            if id != ctr.container.id {\n                guard let processInfo = self.processes[id] else {\n                    throw ContainerizationError(\n                        .invalidState,\n                        message: \"process \\(id) does not exist\"\n                    )\n                }\n\n                guard let proc = processInfo.process else {\n                    throw ContainerizationError(\n                        .invalidState,\n                        message: \"process \\(id) not started\"\n                    )\n                }\n\n                try await proc.resize(\n                    to: .init(\n                        width: UInt16(width),\n                        height: UInt16(height))\n                )\n            } else {\n                try await ctr.container.resize(\n                    to: .init(\n                        width: UInt16(width),\n                        height: UInt16(height))\n                )\n            }\n\n            return message.reply()\n        default:\n            throw ContainerizationError(\n                .invalidState,\n                message: \"cannot resize: container is not running\"\n            )\n        }\n    }\n\n    /// Wait for a process.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - id: The process identifier.\n    ///\n    /// - Returns: An XPC message with the following parameters:\n    ///   - exitCode: The exit code for the process.\n    @Sendable\n    public func wait(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        guard let id = message.string(key: SandboxKeys.id.rawValue) else {\n            throw ContainerizationError(.invalidArgument, message: \"missing id in wait xpc message\")\n        }\n\n        let exitStatus = await withCheckedContinuation { cc in\n            // Is this safe since we are in an actor? :(\n            let (added, exitCode) = self.addWaiter(id: id, cont: cc)\n            if !added {\n                cc.resume(returning: ExitStatus(exitCode: exitCode ?? -1))\n            }\n        }\n        let reply = message.reply()\n        reply.set(key: SandboxKeys.exitCode.rawValue, value: Int64(exitStatus.exitCode))\n        reply.set(key: SandboxKeys.exitedAt.rawValue, value: exitStatus.exitedAt)\n        return reply\n    }\n\n    /// Dial a vsock port on the virtual machine.\n    ///\n    /// - Parameters:\n    ///   - message: An XPC message with the following parameters:\n    ///     - port: The port number.\n    ///\n    /// - Returns: An XPC message with the following parameters:\n    ///   - fd: The file descriptor for the vsock.\n    @Sendable\n    public func dial(_ message: XPCMessage) async throws -> XPCMessage {\n        self.log.debug(\"enter\", metadata: [\"func\": \"\\(#function)\"])\n        defer { self.log.debug(\"exit\", metadata: [\"func\": \"\\(#function)\"]) }\n\n        switch self.state {\n        case .running, .booted:\n            let port = message.uint64(key: SandboxKeys.port.rawValue)\n            guard port > 0 else {\n                throw ContainerizationError(\n                    .invalidArgument,\n                    message: \"no vsock port supplied for dial\"\n                )\n            }\n\n            let ctr = try getContainer()\n            let fh = try await ctr.container.dialVsock(port: UInt32(port))\n\n            let reply = message.reply()\n            reply.set(key: SandboxKeys.fd.rawValue, value: fh)\n            return reply\n        default:\n            throw ContainerizationError(\n                .invalidState,\n                message: \"cannot dial: container is not running\"\n            )\n        }\n    }\n\n    private func startInitProcess(lock: AsyncLock.Context) async throws {\n        let info = try self.getContainer()\n        let container = info.container\n        let id = container.id\n\n        guard self.state == .booted else {\n            throw ContainerizationError(\n                .invalidState,\n                message: \"container expected to be in booted state, got: \\(self.state)\"\n            )\n        }\n\n        do {\n            let io = info.io\n\n            try await container.start()\n            let waitFunc: ExitMonitor.WaitHandler = {\n                let code = try await container.wait()\n                if let out = io.out {\n                    try out.close()\n                }\n                if let err = io.err {\n                    try err.close()\n                }\n                return code\n            }\n            try await self.monitor.track(id: id, waitingOn: waitFunc)\n        } catch {\n            try? await self.cleanUpContainer(containerInfo: info)\n            self.setState(.stopped)\n            throw error\n        }\n    }\n\n    private func startExecProcess(processId id: String, lock: AsyncLock.Context) async throws {\n        let container = try self.getContainer().container\n        guard let processInfo = self.processes[id] else {\n            throw ContainerizationError(.notFound, message: \"process with id \\(id)\")\n        }\n\n        let containerInfo = try self.getContainer()\n        let czConfig = try self.configureProcessConfig(\n            config: processInfo.config,\n            stdio: processInfo.io,\n            containerConfig: containerInfo.config\n        )\n\n        let process = try await container.exec(id, configuration: czConfig)\n        try self.setUnderlyingProcess(id, process)\n\n        try await process.start()\n\n        let waitFunc: ExitMonitor.WaitHandler = {\n            let code = try await process.wait()\n            if let out = processInfo.io[1] {\n                try self.closeHandle(out.fileDescriptor)\n            }\n            if let err = processInfo.io[2] {\n                try self.closeHandle(err.fileDescriptor)\n            }\n            return code\n        }\n        try await self.monitor.track(id: id, waitingOn: waitFunc)\n    }\n\n    private func startSocketForwarders(attachment: Attachment, publishedPorts: [PublishPort]) async throws {\n        guard !publishedPorts.isEmpty else {\n            return\n        }\n        LocalNetworkPrivacy.triggerLocalNetworkPrivacyAlert()\n\n        var forwarders: [SocketForwarderResult] = []\n        guard !publishedPorts.hasOverlaps() else {\n            throw ContainerizationError(.invalidArgument, message: \"host ports for different publish port specs may not overlap\")\n        }\n\n        try await withThrowingTaskGroup(of: SocketForwarderResult.self) { group in\n            for publishedPort in publishedPorts {\n                for index in 0..<publishedPort.count {\n                    let proxyAddress = try SocketAddress(ipAddress: publishedPort.hostAddress.description, port: Int(publishedPort.hostPort + index))\n                    let containerIPAddress: String\n                    switch publishedPort.hostAddress {\n                    case .v4(_):\n                        containerIPAddress = attachment.ipv4Address.address.description\n                    case .v6(_):\n                        guard let ipv6Address = attachment.ipv6Address else {\n                            throw ContainerizationError(.invalidState, message: \"cannot configure IPv6 port forwarding for container with unknown IPv6 address\")\n                        }\n                        containerIPAddress = ipv6Address.address.description\n                    }\n                    let serverAddress = try SocketAddress(ipAddress: containerIPAddress, port: Int(publishedPort.containerPort + index))\n                    log.info(\n                        \"creating forwarder for\",\n                        metadata: [\n                            \"proxy\": \"\\(proxyAddress)\",\n                            \"server\": \"\\(serverAddress)\",\n                            \"protocol\": \"\\(publishedPort.proto)\",\n                        ])\n                    group.addTask {\n                        let forwarder: SocketForwarder\n                        switch publishedPort.proto {\n                        case .tcp:\n                            forwarder = try TCPForwarder(\n                                proxyAddress: proxyAddress,\n                                serverAddress: serverAddress,\n                                eventLoopGroup: self.eventLoopGroup,\n                                log: self.log\n                            )\n                        case .udp:\n                            forwarder = try UDPForwarder(\n                                proxyAddress: proxyAddress,\n                                serverAddress: serverAddress,\n                                eventLoopGroup: self.eventLoopGroup,\n                                log: self.log\n                            )\n                        }\n                        do {\n                            return try await forwarder.run().get()\n                        } catch let error as IOError where error.errnoCode == EACCES {\n                            if let port = proxyAddress.port, port < 1024 {\n                                throw ContainerizationError(\n                                    .invalidArgument,\n                                    message: \"Permission denied while binding to host port \\(port). Binding to ports below 1024 requires root privileges.\"\n                                )\n                            }\n                            throw error\n                        }\n                    }\n                }\n            }\n            for try await result in group {\n                forwarders.append(result)\n            }\n        }\n\n        self.socketForwarders = forwarders\n    }\n\n    private func stopSocketForwarders() async {\n        log.info(\"closing forwarders\")\n        for forwarder in self.socketForwarders {\n            forwarder.close()\n            try? await forwarder.wait()\n        }\n        log.info(\"closed forwarders\")\n    }\n\n    private func onContainerExit(id: String, exitStatus: ExitStatus) async throws {\n        self.log.info(\"init process exited\", metadata: [\"status\": \"\\(exitStatus)\"])\n\n        try await self.lock.withLock { [self] _ in\n            let ctrInfo = try await getContainer()\n\n            switch await self.state {\n            case .stopped, .stopping:\n                return\n            default:\n                break\n            }\n\n            do {\n                try await cleanUpContainer(containerInfo: ctrInfo, exitStatus: exitStatus)\n            } catch {\n                self.log.error(\"failed to clean up container\", metadata: [\"error\": \"\\(error)\"])\n            }\n            await setState(.stopped)\n        }\n    }\n\n    private static func configureContainer(\n        czConfig: inout LinuxContainer.Configuration,\n        config: ContainerConfiguration\n    ) throws {\n        czConfig.cpus = config.resources.cpus\n        czConfig.memoryInBytes = config.resources.memoryInBytes\n        czConfig.sysctl = config.sysctls.reduce(into: [String: String]()) {\n            $0[$1.key] = $1.value\n        }\n        // If the host doesn't support this, we'll throw on container creation.\n        czConfig.virtualization = config.virtualization\n        czConfig.useInit = config.useInit\n\n        for mount in config.mounts {\n            if try mount.isSocket() {\n                let socket = UnixSocketConfiguration(\n                    source: URL(filePath: mount.source),\n                    destination: URL(filePath: mount.destination)\n                )\n                czConfig.sockets.append(socket)\n            } else {\n                czConfig.mounts.append(mount.asMount)\n            }\n        }\n\n        for publishedSocket in config.publishedSockets {\n            let socketConfig = UnixSocketConfiguration(\n                source: publishedSocket.containerPath,\n                destination: publishedSocket.hostPath,\n                permissions: publishedSocket.permissions,\n                direction: .outOf\n            )\n            czConfig.sockets.append(socketConfig)\n        }\n\n        if let socketUrl = Self.sshAuthSocketHostUrl(config: config) {\n            let socketPath = socketUrl.path(percentEncoded: false)\n            let attrs = try? FileManager.default.attributesOfItem(atPath: socketPath)\n            let permissions = (attrs?[.posixPermissions] as? NSNumber)\n                .map { FilePermissions(rawValue: mode_t($0.intValue)) }\n            let socketConfig = UnixSocketConfiguration(\n                source: socketUrl,\n                destination: URL(fileURLWithPath: Self.sshAuthSocketGuestPath),\n                permissions: permissions,\n                direction: .into,\n            )\n            czConfig.sockets.append(socketConfig)\n        }\n\n        let containerId = config.id\n        czConfig.hostname =\n            containerId.split(separator: \".\", maxSplits: 1, omittingEmptySubsequences: true)\n            .first\n            .map { String($0) } ?? containerId\n\n        if let dns = config.dns {\n            czConfig.dns = DNS(\n                nameservers: dns.nameservers, domain: dns.domain,\n                searchDomains: dns.searchDomains, options: dns.options)\n        }\n\n        try Self.configureInitialProcess(czConfig: &czConfig, config: config)\n    }\n\n    private func getDefaultNameservers(allocatedAttachments: [AllocatedAttachment]) async throws -> [String] {\n        for allocatedAttach in allocatedAttachments {\n            let state = try await ClientNetwork.get(id: allocatedAttach.attachment.network)\n            guard case .running(_, let status) = state else {\n                continue\n            }\n            return [status.ipv4Gateway.description]\n        }\n\n        return []\n    }\n\n    private static func configureInitialProcess(\n        czConfig: inout LinuxContainer.Configuration,\n        config: ContainerConfiguration\n    ) throws {\n        let process = config.initProcess\n\n        czConfig.process.arguments = [process.executable] + process.arguments\n        czConfig.process.environmentVariables = process.environment\n\n        if Self.sshAuthSocketHostUrl(config: config) != nil {\n            if !czConfig.process.environmentVariables.contains(where: { $0.starts(with: \"\\(Self.sshAuthSocketEnvVar)=\") }) {\n                czConfig.process.environmentVariables.append(\"\\(Self.sshAuthSocketEnvVar)=\\(Self.sshAuthSocketGuestPath)\")\n            }\n        }\n\n        czConfig.process.terminal = process.terminal\n        czConfig.process.workingDirectory = process.workingDirectory\n        try czConfig.process.rlimits = process.rlimits.map {\n            LinuxRLimit(\n                kind: try LinuxRLimit.Kind($0.limit),\n                hard: $0.hard,\n                soft: $0.soft\n            )\n        }\n        switch process.user {\n        case .raw(let name):\n            czConfig.process.user = .init(\n                uid: 0,\n                gid: 0,\n                umask: nil,\n                additionalGids: process.supplementalGroups,\n                username: name\n            )\n        case .id(let uid, let gid):\n            czConfig.process.user = .init(\n                uid: uid,\n                gid: gid,\n                umask: nil,\n                additionalGids: process.supplementalGroups,\n                username: \"\"\n            )\n        }\n    }\n\n    private nonisolated func configureProcessConfig(config: ProcessConfiguration, stdio: [FileHandle?], containerConfig: ContainerConfiguration)\n        throws -> LinuxProcessConfiguration\n    {\n        var proc = LinuxProcessConfiguration()\n        proc.stdin = stdio[0]\n        proc.stdout = stdio[1]\n        proc.stderr = stdio[2]\n\n        proc.arguments = [config.executable] + config.arguments\n        proc.environmentVariables = config.environment\n\n        if Self.sshAuthSocketHostUrl(config: containerConfig) != nil {\n            if !proc.environmentVariables.contains(where: { $0.starts(with: \"\\(Self.sshAuthSocketEnvVar)=\") }) {\n                proc.environmentVariables.append(\"\\(Self.sshAuthSocketEnvVar)=\\(Self.sshAuthSocketGuestPath)\")\n            }\n        }\n\n        proc.terminal = config.terminal\n        proc.workingDirectory = config.workingDirectory\n        try proc.rlimits = config.rlimits.map {\n            LinuxRLimit(\n                kind: try LinuxRLimit.Kind($0.limit),\n                hard: $0.hard,\n                soft: $0.soft\n            )\n        }\n        switch config.user {\n        case .raw(let name):\n            proc.user = .init(\n                uid: 0,\n                gid: 0,\n                umask: nil,\n                additionalGids: config.supplementalGroups,\n                username: name\n            )\n        case .id(let uid, let gid):\n            proc.user = .init(\n                uid: uid,\n                gid: gid,\n                umask: nil,\n                additionalGids: config.supplementalGroups,\n                username: \"\"\n            )\n        }\n\n        return proc\n    }\n\n    private nonisolated func closeHandle(_ handle: Int32) throws {\n        guard close(handle) == 0 else {\n            guard let errCode = POSIXErrorCode(rawValue: errno) else {\n                fatalError(\"failed to convert errno to POSIXErrorCode\")\n            }\n            throw POSIXError(errCode)\n        }\n    }\n\n    private func getContainer() throws -> ContainerInfo {\n        guard let container else {\n            throw ContainerizationError(\n                .invalidState,\n                message: \"no container found\"\n            )\n        }\n        return container\n    }\n\n    private func gracefulStopContainer(_ lc: LinuxContainer, stopOpts: ContainerStopOptions) async throws -> ExitStatus {\n        // Try and gracefully shut down the process. Even if this succeeds we need to power off\n        // the vm, but we should try this first always.\n        var code = ExitStatus(exitCode: 255)\n        do {\n            code = try await withThrowingTaskGroup(of: ExitStatus.self) { group in\n                group.addTask {\n                    try await lc.wait()\n                }\n                group.addTask {\n                    try await lc.kill(stopOpts.signal)\n                    try await Task.sleep(for: .seconds(stopOpts.timeoutInSeconds))\n                    try await lc.kill(SIGKILL)\n\n                    return ExitStatus(exitCode: 137)\n                }\n                guard let code = try await group.next() else {\n                    throw ContainerizationError(\n                        .internalError,\n                        message: \"failed to get exit code from gracefully stopping container\"\n                    )\n                }\n                group.cancelAll()\n\n                return code\n            }\n        } catch {}\n\n        // Now actually bring down the vm.\n        try await lc.stop()\n\n        return code\n    }\n\n    private func cleanUpContainer(containerInfo: ContainerInfo, exitStatus: ExitStatus? = nil) async throws {\n        let container = containerInfo.container\n        let id = container.id\n\n        do {\n            try await container.stop()\n        } catch {\n            self.log.error(\"failed to stop container during cleanup\", metadata: [\"error\": \"\\(error)\"])\n        }\n\n        await self.stopSocketForwarders()\n\n        let status = exitStatus ?? ExitStatus(exitCode: 255)\n        self.releaseWaiters(for: id, status: status)\n    }\n}\n\nextension XPCMessage {\n    fileprivate func signal() throws -> Int64 {\n        self.int64(key: SandboxKeys.signal.rawValue)\n    }\n\n    fileprivate func stopOptions() throws -> ContainerStopOptions {\n        guard let data = self.dataNoCopy(key: SandboxKeys.stopOptions.rawValue) else {\n            throw ContainerizationError(.invalidArgument, message: \"empty StopOptions\")\n        }\n        return try JSONDecoder().decode(ContainerStopOptions.self, from: data)\n    }\n\n    fileprivate func setState(_ state: SandboxSnapshot) throws {\n        let data = try JSONEncoder().encode(state)\n        self.set(key: SandboxKeys.snapshot.rawValue, value: data)\n    }\n\n    fileprivate func stdio() -> [FileHandle?] {\n        var handles = [FileHandle?](repeating: nil, count: 3)\n        if let stdin = self.fileHandle(key: SandboxKeys.stdin.rawValue) {\n            handles[0] = stdin\n        }\n        if let stdout = self.fileHandle(key: SandboxKeys.stdout.rawValue) {\n            handles[1] = stdout\n        }\n        if let stderr = self.fileHandle(key: SandboxKeys.stderr.rawValue) {\n            handles[2] = stderr\n        }\n        return handles\n    }\n\n    fileprivate func setFileHandle(_ handle: FileHandle) {\n        self.set(key: SandboxKeys.fd.rawValue, value: handle)\n    }\n\n    fileprivate func processConfig() throws -> ProcessConfiguration {\n        guard let data = self.dataNoCopy(key: SandboxKeys.processConfig.rawValue) else {\n            throw ContainerizationError(.invalidArgument, message: \"empty process configuration\")\n        }\n        return try JSONDecoder().decode(ProcessConfiguration.self, from: data)\n    }\n\n    fileprivate func getAllocatedAttachments() throws -> [AllocatedAttachment] {\n        guard let attachmentArray = xpc_dictionary_get_value(self.underlying, SandboxKeys.allocatedAttachments.rawValue) else {\n            throw ContainerizationError(.invalidArgument, message: \"missing allocatedAttachments array in message\")\n        }\n\n        var results = [AllocatedAttachment]()\n        let decoder = JSONDecoder()\n\n        let arrayCount = xpc_array_get_count(attachmentArray)\n\n        for i in 0..<arrayCount {\n            guard let allocatedAttach = xpc_array_get_dictionary(attachmentArray, i) else {\n                throw ContainerizationError(.invalidArgument, message: \"invalid allocated attachment at index \\(i)\")\n            }\n\n            let allocatedAttachXPC = XPCMessage(object: allocatedAttach)\n\n            let attachmentData = allocatedAttachXPC.dataNoCopy(key: SandboxKeys.networkAttachment.rawValue)\n            let pluginInfoData = allocatedAttachXPC.dataNoCopy(key: SandboxKeys.networkPluginInfo.rawValue)\n\n            guard let attachmentData = attachmentData, let pluginInfoData = pluginInfoData else {\n                throw ContainerizationError(.invalidArgument, message: \"must have attachment and plugin information for network\")\n            }\n\n            let attachment = try decoder.decode(Attachment.self, from: attachmentData)\n            let pluginInfo = try decoder.decode(NetworkPluginInfo.self, from: pluginInfoData)\n\n            let additionalDataXPC: XPCMessage? = {\n                if let rawData = xpc_dictionary_get_dictionary(allocatedAttachXPC.underlying, SandboxKeys.networkAdditionalData.rawValue) {\n                    return XPCMessage(object: rawData)\n                }\n                return nil\n            }()\n\n            results.append(\n                AllocatedAttachment(\n                    attachment: attachment,\n                    additionalData: additionalDataXPC,\n                    pluginInfo: pluginInfo\n                ))\n        }\n        return results\n    }\n}\n\nextension ContainerResource.Bundle {\n    func createLogFile() throws {\n        // Create the log file we'll write stdio to.\n        // O_TRUNC resolves a log delay issue on restarted containers by force-updating internal state\n        let fd = Darwin.open(self.containerLog.path, O_CREAT | O_RDONLY | O_TRUNC, 0o644)\n        guard fd > 0 else {\n            throw POSIXError(.init(rawValue: errno)!)\n        }\n        close(fd)\n    }\n}\n\nextension Filesystem {\n    var asMount: Containerization.Mount {\n        switch self.type {\n        case .tmpfs:\n            return .any(\n                type: \"tmpfs\",\n                source: self.source,\n                destination: self.destination,\n                options: self.options\n            )\n        case .virtiofs:\n            return .share(\n                source: self.source,\n                destination: self.destination,\n                options: self.options\n            )\n        case .block(let format, let cacheMode, let syncMode):\n            return .block(\n                format: format,\n                source: self.source,\n                destination: self.destination,\n                options: self.options,\n                runtimeOptions: [\n                    \"\\(Filesystem.CacheMode.vzRuntimeOptionKey)=\\(cacheMode.asVZRuntimeOption)\",\n                    \"\\(Filesystem.SyncMode.vzRuntimeOptionKey)=\\(syncMode.asVZRuntimeOption)\",\n                ],\n            )\n        case .volume(_, let format, let cacheMode, let syncMode):\n            return .block(\n                format: format,\n                source: self.source,\n                destination: self.destination,\n                options: self.options,\n                runtimeOptions: [\n                    \"\\(Filesystem.CacheMode.vzRuntimeOptionKey)=\\(cacheMode.asVZRuntimeOption)\",\n                    \"\\(Filesystem.SyncMode.vzRuntimeOptionKey)=\\(syncMode.asVZRuntimeOption)\",\n                ],\n            )\n        }\n    }\n\n    func isSocket() throws -> Bool {\n        if !self.isVirtiofs {\n            return false\n        }\n        let info = try File.info(self.source)\n        return info.isSocket\n    }\n}\n\nextension Filesystem.CacheMode {\n    static let vzRuntimeOptionKey = \"vzDiskImageCachingMode\"\n\n    var asVZRuntimeOption: String {\n        switch self {\n        case .on: \"cached\"\n        case .off: \"uncached\"\n        case .auto: \"automatic\"\n        }\n    }\n}\n\nextension Filesystem.SyncMode {\n    static let vzRuntimeOptionKey = \"vzDiskImageSynchronizationMode\"\n\n    var asVZRuntimeOption: String {\n        switch self {\n        case .full: \"full\"\n        case .fsync: \"fsync\"\n        case .nosync: \"none\"\n        }\n    }\n}\n\nstruct MultiWriter: Writer {\n    let handles: [FileHandle]\n\n    init(handles: [FileHandle]) {\n        self.handles = handles\n    }\n\n    func close() throws {\n        for handle in handles {\n            try handle.close()\n        }\n    }\n\n    func write(_ data: Data) throws {\n        for handle in handles {\n            try handle.write(contentsOf: data)\n        }\n    }\n}\n\nextension FileHandle: @retroactive ReaderStream, @retroactive Writer {\n    public func write(_ data: Data) throws {\n        try self.write(contentsOf: data)\n    }\n\n    public func stream() -> AsyncStream<Data> {\n        .init { cont in\n            self.readabilityHandler = { handle in\n                let data = handle.availableData\n                if data.isEmpty {\n                    self.readabilityHandler = nil\n                    cont.finish()\n                    return\n                }\n                cont.yield(data)\n            }\n        }\n    }\n}\n\n// MARK: State handler and bundle creation helpers\n\nextension SandboxService {\n    private func initializeWaiters(for id: String) throws {\n        guard waiters[id] == nil else {\n            throw ContainerizationError(.invalidState, message: \"waiter for \\(id) already initialized\")\n        }\n        waiters[id] = ExitWaiter()\n    }\n\n    private func addWaiter(id: String, cont: CheckedContinuation<ExitStatus, Never>) -> (Bool, Int32?) {\n        guard let current = waiters[id] else {\n            // No waiter initialized at all\n            return (false, nil)\n        }\n\n        if current.exited() {\n            // Waiter initialzed but already exited\n            return (false, current.exitCode)\n        }\n\n        // Waiter initialized and not exited. Guaranteed to exit later.\n        current.register(cont)\n        return (true, nil)\n    }\n\n    private func releaseWaiters(for id: String, status: ExitStatus) {\n        waiters[id]?.doExit(code: status.exitCode)\n    }\n\n    private func setUnderlyingProcess(_ id: String, _ process: LinuxProcess) throws {\n        guard var info = self.processes[id] else {\n            throw ContainerizationError(.invalidState, message: \"process \\(id) not found\")\n        }\n        info.process = process\n        self.processes[id] = info\n    }\n\n    private func setProcessState(id: String, state: State) throws {\n        guard var info = self.processes[id] else {\n            throw ContainerizationError(.invalidState, message: \"process \\(id) not found\")\n        }\n        info.state = state\n        self.processes[id] = info\n    }\n\n    private func setContainer(_ info: ContainerInfo) {\n        self.container = info\n    }\n\n    private func addNewProcess(_ id: String, _ config: ProcessConfiguration, _ io: [FileHandle?]) throws {\n        guard self.processes[id] == nil else {\n            throw ContainerizationError(.invalidArgument, message: \"process \\(id) already exists\")\n        }\n        self.processes[id] = ProcessInfo(config: config, process: nil, state: .created, io: io)\n    }\n\n    private struct ProcessInfo {\n        let config: ProcessConfiguration\n        var process: LinuxProcess?\n        var state: State\n        let io: [FileHandle?]\n    }\n\n    private struct ContainerInfo {\n        let container: LinuxContainer\n        let config: ContainerConfiguration\n        let attachments: [Attachment]\n        let bundle: ContainerResource.Bundle\n        let io: (in: FileHandle?, out: MultiWriter?, err: MultiWriter?)\n    }\n\n    /// States the underlying sandbox can be in.\n    public enum State: Sendable, Equatable {\n        /// Sandbox is created. This should be what the service starts the sandbox in.\n        case created\n        /// Bootstrap will transition a .created state to .booted.\n        case booted\n        /// startProcess on the init process will transition .booted to .running.\n        case running\n        /// At the beginning of stop() .running will be transitioned to .stopping.\n        case stopping\n        /// Once a stop is successful, .stopping will transition to .stopped.\n        case stopped\n        /// .shuttingDown will be the last state the sandbox service will ever be in. Shortly\n        /// afterwards the process will exit.\n        case shuttingDown\n    }\n\n    func setState(_ new: State) {\n        self.state = new\n    }\n\n    /// Check if a bundle exists at the given path\n    private func bundleExists(at path: URL) -> Bool {\n        guard FileManager.default.fileExists(atPath: path.path) else {\n            return false\n        }\n\n        let bundle = ContainerResource.Bundle(path: path)\n        do {\n            _ = try bundle.configuration\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// Create bundle from RuntimeConfiguration\n    private func createBundle() throws {\n        do {\n            let runtimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: self.root)\n            _ = try ContainerResource.Bundle.create(\n                path: runtimeConfig.path,\n                initialFilesystem: runtimeConfig.initialFilesystem,\n                kernel: runtimeConfig.kernel,\n                containerConfiguration: runtimeConfig.containerConfiguration,\n                containerRootFilesystem: runtimeConfig.containerRootFilesystem,\n                options: runtimeConfig.options\n            )\n            self.log.info(\"created bundle\", metadata: [\"configPath\": \"\\(runtimeConfig.path)\"])\n        } catch {\n            self.log.error(\"failed to create bundle\", metadata: [\"error\": \"\\(error)\"])\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/ConnectHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Logging\nimport NIOCore\nimport NIOPosix\n\nfinal class ConnectHandler {\n    private var pendingBytes: [NIOAny]\n    private let serverAddress: SocketAddress\n    private var log: Logger? = nil\n\n    init(serverAddress: SocketAddress, log: Logger?) {\n        self.pendingBytes = []\n        self.serverAddress = serverAddress\n        self.log = log\n    }\n}\n\nextension ConnectHandler: ChannelInboundHandler {\n    typealias InboundIn = ByteBuffer\n    typealias OutboundOut = ByteBuffer\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        self.pendingBytes.append(data)\n    }\n\n    func handlerAdded(context: ChannelHandlerContext) {\n        // Add logger metadata.\n        self.log?[metadataKey: \"proxy\"] = \"\\(context.channel.localAddress?.description ?? \"none\")\"\n        self.log?[metadataKey: \"server\"] = \"\\(context.channel.remoteAddress?.description ?? \"none\")\"\n    }\n\n    func channelActive(context: ChannelHandlerContext) {\n        self.log?.trace(\"frontend - channel active, connecting to backend\")\n        self.connectToServer(context: context)\n        context.fireChannelActive()\n    }\n}\n\nextension ConnectHandler: RemovableChannelHandler {\n    func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {\n        var didRead = false\n\n        // We are being removed, and need to deliver any pending bytes we may have if we're upgrading.\n        while self.pendingBytes.count > 0 {\n            let data = self.pendingBytes.removeFirst()\n            context.fireChannelRead(data)\n            didRead = true\n        }\n\n        if didRead {\n            context.fireChannelReadComplete()\n        }\n\n        self.log?.trace(\"backend - removing connect handler from pipeline\")\n        context.leavePipeline(removalToken: removalToken)\n    }\n}\n\nextension ConnectHandler {\n    private func connectToServer(context: ChannelHandlerContext) {\n        self.log?.trace(\"backend - connecting\")\n\n        ClientBootstrap(group: context.eventLoop)\n            .connect(to: serverAddress)\n            .assumeIsolatedUnsafeUnchecked()\n            .whenComplete { result in\n                switch result {\n                case .success(let channel):\n                    guard context.channel.isActive else {\n                        self.log?.trace(\"backend - frontend channel closed, closing backend connection\")\n                        context.channel.close(promise: nil)\n                        return\n                    }\n                    self.log?.trace(\"backend - connected\")\n                    self.glue(channel, context: context)\n                case .failure(let error):\n                    self.log?.error(\"backend - connect failed: \\(error)\")\n                    context.close(promise: nil)\n                    context.fireErrorCaught(error)\n                }\n            }\n    }\n\n    private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {\n        self.log?.trace(\"backend - gluing channels\")\n\n        // Now we need to glue our channel and the peer channel together.\n        let (localGlue, peerGlue) = GlueHandler.matchedPair()\n        do {\n            try context.channel.pipeline.syncOperations.addHandler(localGlue)\n            try peerChannel.pipeline.syncOperations.addHandler(peerGlue)\n            context.pipeline.syncOperations.removeHandler(self, promise: nil)\n        } catch {\n            // Close connected peer channel before closing our channel.\n            peerChannel.close(mode: .all, promise: nil)\n            context.close(promise: nil)\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/GlueHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIOCore\n\nfinal class GlueHandler {\n\n    private var partner: GlueHandler?\n\n    private var context: ChannelHandlerContext?\n\n    private var pendingRead: Bool = false\n\n    private init() {}\n}\n\nextension GlueHandler {\n    static func matchedPair() -> (GlueHandler, GlueHandler) {\n        let first = GlueHandler()\n        let second = GlueHandler()\n\n        first.partner = second\n        second.partner = first\n\n        return (first, second)\n    }\n}\n\nextension GlueHandler {\n    private func partnerWrite(_ data: NIOAny) {\n        self.context?.write(data, promise: nil)\n    }\n\n    private func partnerFlush() {\n        self.context?.flush()\n    }\n\n    private func partnerWriteEOF() {\n        self.context?.close(mode: .output, promise: nil)\n    }\n\n    private func partnerCloseFull() {\n        self.context?.close(promise: nil)\n    }\n\n    private func partnerBecameWritable() {\n        if self.pendingRead {\n            self.pendingRead = false\n            self.context?.read()\n        }\n    }\n\n    private var partnerWritable: Bool {\n        self.context?.channel.isWritable ?? false\n    }\n}\n\nextension GlueHandler: ChannelDuplexHandler {\n    typealias InboundIn = NIOAny\n    typealias OutboundIn = NIOAny\n    typealias OutboundOut = NIOAny\n\n    func handlerAdded(context: ChannelHandlerContext) {\n        self.context = context\n    }\n\n    func handlerRemoved(context: ChannelHandlerContext) {\n        self.context = nil\n        self.partner = nil\n    }\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        self.partner?.partnerWrite(data)\n    }\n\n    func channelReadComplete(context: ChannelHandlerContext) {\n        self.partner?.partnerFlush()\n    }\n\n    func channelInactive(context: ChannelHandlerContext) {\n        self.partner?.partnerCloseFull()\n    }\n\n    func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) {\n        if let event = event as? ChannelEvent, case .inputClosed = event {\n            // We have read EOF.\n            self.partner?.partnerWriteEOF()\n        }\n    }\n\n    func errorCaught(context: ChannelHandlerContext, error: Error) {\n        self.partner?.partnerCloseFull()\n    }\n\n    func channelWritabilityChanged(context: ChannelHandlerContext) {\n        if context.channel.isWritable {\n            self.partner?.partnerBecameWritable()\n        }\n    }\n\n    func read(context: ChannelHandlerContext) {\n        if let partner = self.partner, partner.partnerWritable {\n            context.read()\n        } else {\n            self.pendingRead = true\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/LRUCache.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nstruct KeyExistsError: Error {}\n\nclass LRUCache<K: Hashable, V> {\n    private class Node {\n        fileprivate var prev: Node?\n        fileprivate var next: Node?\n        fileprivate let key: K\n        fileprivate let value: V\n\n        init(key: K, value: V) {\n            self.prev = nil\n            self.next = nil\n            self.key = key\n            self.value = value\n        }\n    }\n\n    private let size: UInt\n    private var head: Node?\n    private var tail: Node?\n    private var members: [K: Node]\n\n    init(size: UInt) {\n        self.size = size\n        self.head = nil\n        self.tail = nil\n        self.members = [:]\n    }\n\n    var count: Int { members.count }\n\n    func get(_ key: K) -> V? {\n        guard let node = members[key] else {\n            return nil\n        }\n        listRemove(node: node)\n        listInsert(node: node, after: tail)\n        return node.value\n    }\n\n    func put(key: K, value: V) -> (K, V)? {\n        let node = Node(key: key, value: value)\n        var evicted: (K, V)? = nil\n\n        if let existingNode = members[key] {\n            // evict the replaced node\n            listRemove(node: existingNode)\n            evicted = (existingNode.key, existingNode.value)\n        } else if self.count >= self.size {\n            // evict the least recently used node\n            evicted = evict()\n        }\n\n        // insert the new node and return any evicted node\n        members[key] = node\n        listInsert(node: node, after: tail)\n        return evicted\n    }\n\n    private func evict() -> (K, V)? {\n        guard let head else {\n            return nil\n        }\n        let ret = (head.key, head.value)\n        listRemove(node: head)\n        members.removeValue(forKey: head.key)\n        return ret\n    }\n\n    private func listRemove(node: Node) {\n        if let prev = node.prev {\n            prev.next = node.next\n        } else {\n            head = node.next\n        }\n        if let next = node.next {\n            next.prev = node.prev\n        } else {\n            tail = node.prev\n        }\n    }\n\n    private func listInsert(node: Node, after: Node?) {\n        let before: Node?\n        if let after {\n            before = after.next\n            after.next = node\n        } else {\n            before = head\n            head = node\n        }\n\n        if let before {\n            before.prev = node\n        } else {\n            tail = node\n        }\n\n        node.prev = after\n        node.next = before\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/SocketForwarder.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\npublic protocol SocketForwarder: Sendable {\n    func run() throws -> EventLoopFuture<SocketForwarderResult>\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/SocketForwarderResult.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\npublic struct SocketForwarderResult: Sendable {\n    private let channel: any Channel\n\n    public init(channel: Channel) {\n        self.channel = channel\n    }\n\n    public var proxyAddress: SocketAddress? { self.channel.localAddress }\n\n    public func close() {\n        self.channel.eventLoop.execute {\n            _ = channel.close()\n        }\n    }\n\n    public func wait() async throws {\n        try await self.channel.closeFuture.get()\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/TCPForwarder.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\nimport NIO\nimport NIOFoundationCompat\n\npublic struct TCPForwarder: SocketForwarder {\n    private let proxyAddress: SocketAddress\n\n    private let serverAddress: SocketAddress\n\n    private let eventLoopGroup: any EventLoopGroup\n\n    private let log: Logger?\n\n    public init(\n        proxyAddress: SocketAddress,\n        serverAddress: SocketAddress,\n        eventLoopGroup: any EventLoopGroup,\n        log: Logger? = nil\n    ) throws {\n        self.proxyAddress = proxyAddress\n        self.serverAddress = serverAddress\n        self.eventLoopGroup = eventLoopGroup\n        self.log = log\n    }\n\n    public func run() throws -> EventLoopFuture<SocketForwarderResult> {\n        self.log?.trace(\"frontend - creating listener\")\n\n        let bootstrap = ServerBootstrap(group: self.eventLoopGroup)\n            .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)\n            .childChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)\n            .childChannelInitializer { channel in\n                channel.eventLoop.makeCompletedFuture {\n                    try channel.pipeline.syncOperations.addHandler(\n                        ConnectHandler(serverAddress: self.serverAddress, log: log)\n                    )\n                }\n            }\n\n        return\n            bootstrap\n            .bind(to: self.proxyAddress)\n            .map { SocketForwarderResult(channel: $0) }\n    }\n}\n"
  },
  {
    "path": "Sources/SocketForwarder/UDPForwarder.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Collections\nimport Foundation\nimport Logging\nimport NIO\nimport NIOFoundationCompat\nimport Synchronization\n\n// Proxy backend for a single client address (clientIP, clientPort).\nprivate final class UDPProxyBackend: ChannelInboundHandler {\n    typealias InboundIn = AddressedEnvelope<ByteBuffer>\n    typealias OutboundOut = AddressedEnvelope<ByteBuffer>\n\n    private struct State {\n        var queuedPayloads: Deque<ByteBuffer>\n        var channel: (any Channel)?\n    }\n\n    private let clientAddress: SocketAddress\n    private let serverAddress: SocketAddress\n    private let frontendChannel: any Channel\n    private let log: Logger?\n    private var state: State\n\n    init(clientAddress: SocketAddress, serverAddress: SocketAddress, frontendChannel: any Channel, log: Logger? = nil) {\n        self.clientAddress = clientAddress\n        self.serverAddress = serverAddress\n        self.frontendChannel = frontendChannel\n        self.log = log\n        let initialState = State(queuedPayloads: Deque(), channel: nil)\n        self.state = initialState\n    }\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        // relay data from server to client.\n        let inbound = self.unwrapInboundIn(data)\n        let outbound = OutboundOut(remoteAddress: self.clientAddress, data: inbound.data)\n        self.log?.trace(\"backend - writing datagram to client\")\n        self.frontendChannel.writeAndFlush(outbound, promise: nil)\n    }\n\n    func channelActive(context: ChannelHandlerContext) {\n        if !state.queuedPayloads.isEmpty {\n            self.log?.trace(\"backend - writing \\(state.queuedPayloads.count) queued datagrams to server\")\n            while let queuedData = state.queuedPayloads.popFirst() {\n                let outbound: UDPProxyBackend.OutboundOut = OutboundOut(remoteAddress: self.serverAddress, data: queuedData)\n                context.channel.writeAndFlush(outbound, promise: nil)\n            }\n        }\n        state.channel = context.channel\n    }\n\n    func write(data: ByteBuffer) {\n        // change package remote address from proxy server to real server\n        if let channel = state.channel {\n            // channel has been initialized, so relay any queued packets, along with this one to outbound\n            self.log?.trace(\"backend - writing datagram to server\")\n            let outbound: UDPProxyBackend.OutboundOut = OutboundOut(remoteAddress: self.serverAddress, data: data)\n            channel.writeAndFlush(outbound, promise: nil)\n        } else {\n            // channel is initializing, queue\n            self.log?.trace(\"backend - queuing datagram\")\n            state.queuedPayloads.append(data)\n        }\n    }\n\n    func close() {\n        guard let channel = state.channel else {\n            self.log?.warning(\"backend - close on inactive channel\")\n            return\n        }\n        _ = channel.close()\n    }\n}\n\nprivate struct ProxyContext {\n    public let proxy: UDPProxyBackend\n    public let closeFuture: EventLoopFuture<Void>\n}\n\nprivate final class UDPProxyFrontend: ChannelInboundHandler {\n    typealias InboundIn = AddressedEnvelope<ByteBuffer>\n    typealias OutboundOut = AddressedEnvelope<ByteBuffer>\n    private let maxProxies = UInt(256)\n\n    private let proxyAddress: SocketAddress\n    private let serverAddress: SocketAddress\n    private let log: Logger?\n\n    private var proxies: LRUCache<String, ProxyContext>\n\n    init(proxyAddress: SocketAddress, serverAddress: SocketAddress, log: Logger? = nil) {\n        self.proxyAddress = proxyAddress\n        self.serverAddress = serverAddress\n        self.proxies = LRUCache(size: maxProxies)\n        self.log = log\n    }\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        let inbound = self.unwrapInboundIn(data)\n\n        guard let clientIP = inbound.remoteAddress.ipAddress else {\n            log?.error(\"frontend - no client IP address in inbound payload\")\n            return\n        }\n\n        guard let clientPort = inbound.remoteAddress.port else {\n            log?.error(\"frontend - no client port in inbound payload\")\n            return\n        }\n\n        let key = \"\\(clientIP):\\(clientPort)\"\n        do {\n            if let context = proxies.get(key) {\n                context.proxy.write(data: inbound.data)\n            } else {\n                self.log?.trace(\"frontend - creating backend\")\n                let proxy = UDPProxyBackend(\n                    clientAddress: inbound.remoteAddress,\n                    serverAddress: self.serverAddress,\n                    frontendChannel: context.channel,\n                    log: log\n                )\n                let proxyAddress = try SocketAddress(ipAddress: \"0.0.0.0\", port: 0)\n                let loopBoundProxy = NIOLoopBound(proxy, eventLoop: context.eventLoop)\n                let proxyToServerFuture = DatagramBootstrap(group: context.eventLoop)\n                    .channelInitializer { [log] channel in\n                        log?.trace(\"frontend - initializing backend\")\n                        return channel.eventLoop.makeCompletedFuture {\n                            try channel.pipeline.syncOperations.addHandler(loopBoundProxy.value)\n                        }\n                    }\n                    .bind(to: proxyAddress)\n                    .flatMap { $0.closeFuture }\n                let context = ProxyContext(proxy: proxy, closeFuture: proxyToServerFuture)\n                if let (_, evictedContext) = proxies.put(key: key, value: context) {\n                    self.log?.trace(\"frontend - closing evicted backend\")\n                    evictedContext.proxy.close()\n                }\n\n                proxy.write(data: inbound.data)\n            }\n        } catch {\n            log?.error(\"server handler - backend channel creation failed with error: \\(error)\")\n            return\n        }\n    }\n}\n\npublic struct UDPForwarder: SocketForwarder {\n    private let proxyAddress: SocketAddress\n\n    private let serverAddress: SocketAddress\n\n    private let eventLoopGroup: any EventLoopGroup\n\n    private let log: Logger?\n\n    public init(\n        proxyAddress: SocketAddress,\n        serverAddress: SocketAddress,\n        eventLoopGroup: any EventLoopGroup,\n        log: Logger? = nil\n    ) throws {\n        self.proxyAddress = proxyAddress\n        self.serverAddress = serverAddress\n        self.eventLoopGroup = eventLoopGroup\n        self.log = log\n    }\n\n    public func run() throws -> EventLoopFuture<SocketForwarderResult> {\n        self.log?.trace(\"frontend - creating channel\")\n        let bootstrap = DatagramBootstrap(group: self.eventLoopGroup)\n            .channelInitializer { serverChannel in\n                self.log?.trace(\"frontend - initializing channel\")\n                let proxyToServerHandler = UDPProxyFrontend(\n                    proxyAddress: proxyAddress,\n                    serverAddress: serverAddress,\n                    log: log\n                )\n                return serverChannel.eventLoop.makeCompletedFuture {\n                    try serverChannel.pipeline.syncOperations.addHandler(proxyToServerHandler)\n                }\n            }\n        return\n            bootstrap\n            .bind(to: proxyAddress)\n            .map { SocketForwarderResult(channel: $0) }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/Int+Formatted.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension Int {\n    func formattedTime() -> String {\n        let secondsInMinute = 60\n        let secondsInHour = secondsInMinute * 60\n        let secondsInDay = secondsInHour * 24\n\n        let days = self / secondsInDay\n        let hours = (self % secondsInDay) / secondsInHour\n        let minutes = (self % secondsInHour) / secondsInMinute\n        let seconds = self % secondsInMinute\n\n        var components = [String]()\n        if days > 0 {\n            components.append(\"\\(days)d\")\n        }\n        if hours > 0 || days > 0 {\n            components.append(\"\\(hours)h\")\n        }\n        if minutes > 0 || hours > 0 || days > 0 {\n            components.append(\"\\(minutes)m\")\n        }\n        components.append(\"\\(seconds)s\")\n        return components.joined(separator: \" \")\n    }\n\n    func formattedNumber() -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .decimal\n        guard let formattedNumber = formatter.string(from: NSNumber(value: self)) else {\n            return \"\"\n        }\n        return formattedNumber\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/Int64+Formatted.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension Int64 {\n    func formattedSize() -> String {\n        let formattedSize = ByteCountFormatter.string(fromByteCount: self, countStyle: .binary)\n        return formattedSize\n    }\n\n    func formattedSizeSpeed(from startTime: DispatchTime) -> String {\n        let elapsedTimeNanoseconds = DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds\n        let elapsedTimeSeconds = Double(elapsedTimeNanoseconds) / 1_000_000_000\n        guard elapsedTimeSeconds > 0 else {\n            return \"0 B/s\"\n        }\n\n        let speed = Double(self) / elapsedTimeSeconds\n        let formattedSpeed = ByteCountFormatter.string(fromByteCount: Int64(speed), countStyle: .binary)\n        return \"\\(formattedSpeed)/s\"\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressBar+Add.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension ProgressBar {\n    /// A handler function to update the progress bar.\n    /// - Parameter events: The events to handle.\n    public func handler(_ events: [ProgressUpdateEvent]) {\n        for event in events {\n            switch event {\n            case .setDescription(let description):\n                set(description: description)\n            case .setSubDescription(let subDescription):\n                set(subDescription: subDescription)\n            case .setItemsName(let itemsName):\n                set(itemsName: itemsName)\n            case .addTasks(let tasks):\n                add(tasks: tasks)\n            case .setTasks(let tasks):\n                set(tasks: tasks)\n            case .addTotalTasks(let totalTasks):\n                add(totalTasks: totalTasks)\n            case .setTotalTasks(let totalTasks):\n                set(totalTasks: totalTasks)\n            case .addSize(let size):\n                add(size: size)\n            case .setSize(let size):\n                set(size: size)\n            case .addTotalSize(let totalSize):\n                add(totalSize: totalSize)\n            case .setTotalSize(let totalSize):\n                set(totalSize: totalSize)\n            case .addItems(let items):\n                add(items: items)\n            case .setItems(let items):\n                set(items: items)\n            case .addTotalItems(let totalItems):\n                add(totalItems: totalItems)\n            case .setTotalItems(let totalItems):\n                set(totalItems: totalItems)\n            case .custom:\n                // Custom events are handled by the client.\n                break\n            }\n        }\n    }\n\n    /// Performs a check to see if the progress bar should be finished.\n    public func checkIfFinished() {\n        let state = self.state.withLock { $0 }\n\n        var finished = true\n        var defined = false\n        if let totalTasks = state.totalTasks, totalTasks > 0 {\n            // For tasks, we're showing the current task rather than the number of completed tasks.\n            finished = finished && state.tasks == totalTasks\n            defined = true\n        }\n        if let totalItems = state.totalItems, totalItems > 0 {\n            finished = finished && state.items == totalItems\n            defined = true\n        }\n        if let totalSize = state.totalSize, totalSize > 0 {\n            finished = finished && state.size == totalSize\n            defined = true\n        }\n        if defined && finished {\n            finish()\n        }\n    }\n\n    /// Sets the current tasks.\n    /// - Parameter newTasks: The current tasks to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(tasks newTasks: Int, render: Bool = true) {\n        state.withLock { $0.tasks = newTasks }\n        if render {\n            self.render()\n        }\n        checkIfFinished()\n    }\n\n    /// Performs an addition to the current tasks.\n    /// - Parameter delta: The tasks to add to the current tasks.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(tasks delta: Int, render: Bool = true) {\n        state.withLock {\n            let newTasks = $0.tasks + delta\n            $0.tasks = newTasks\n        }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the total tasks.\n    /// - Parameter newTotalTasks: The total tasks to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(totalTasks newTotalTasks: Int, render: Bool = true) {\n        state.withLock { $0.totalTasks = newTotalTasks }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Performs an addition to the total tasks.\n    /// - Parameter delta: The tasks to add to the total tasks.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(totalTasks delta: Int, render: Bool = true) {\n        state.withLock {\n            let totalTasks = $0.totalTasks ?? 0\n            let newTotalTasks = totalTasks + delta\n            $0.totalTasks = newTotalTasks\n        }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the items name.\n    /// - Parameter newItemsName: The current items to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(itemsName newItemsName: String, render: Bool = true) {\n        state.withLock { $0.itemsName = newItemsName }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the current items.\n    /// - Parameter newItems: The current items to set.\n    public func set(items newItems: Int, render: Bool = true) {\n        state.withLock { $0.items = newItems }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Performs an addition to the current items.\n    /// - Parameter delta: The items to add to the current items.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(items delta: Int, render: Bool = true) {\n        state.withLock {\n            let newItems = $0.items + delta\n            $0.items = newItems\n        }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the total items.\n    /// - Parameter newTotalItems: The total items to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(totalItems newTotalItems: Int, render: Bool = true) {\n        state.withLock { $0.totalItems = newTotalItems }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Performs an addition to the total items.\n    /// - Parameter delta: The items to add to the total items.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(totalItems delta: Int, render: Bool = true) {\n        state.withLock {\n            let totalItems = $0.totalItems ?? 0\n            let newTotalItems = totalItems + delta\n            $0.totalItems = newTotalItems\n        }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the current size.\n    /// - Parameter newSize: The current size to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(size newSize: Int64, render: Bool = true) {\n        state.withLock { $0.size = newSize }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Performs an addition to the current size.\n    /// - Parameter delta: The size to add to the current size.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(size delta: Int64, render: Bool = true) {\n        state.withLock {\n            let newSize = $0.size + delta\n            $0.size = newSize\n        }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Sets the total size.\n    /// - Parameter newTotalSize: The total size to set.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func set(totalSize newTotalSize: Int64, render: Bool = true) {\n        state.withLock { $0.totalSize = newTotalSize }\n        if render {\n            self.render()\n        }\n    }\n\n    /// Performs an addition to the total size.\n    /// - Parameter delta: The size to add to the total size.\n    /// - Parameter render: The flag indicating whether the progress bar has to render after the update.\n    public func add(totalSize delta: Int64, render: Bool = true) {\n        state.withLock {\n            let totalSize = $0.totalSize ?? 0\n            let newTotalSize = totalSize + delta\n            $0.totalSize = newTotalSize\n        }\n        if render {\n            self.render()\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressBar+State.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nextension ProgressBar {\n    /// State for the progress bar.\n    struct State {\n        /// A flag indicating whether the progress bar is finished.\n        var finished = false\n        var iteration = 0\n        private let speedInterval: DispatchTimeInterval = .seconds(1)\n\n        var description: String\n        var subDescription: String\n        var itemsName: String\n\n        var tasks: Int\n        var totalTasks: Int?\n\n        var items: Int\n        var totalItems: Int?\n\n        private var sizeUpdateTime: DispatchTime?\n        private var sizeUpdateValue: Int64 = 0\n        var size: Int64 {\n            didSet {\n                calculateSizeSpeed()\n            }\n        }\n\n        var totalSize: Int64?\n        private var sizeUpdateSpeed: String?\n        var sizeSpeed: String? {\n            guard sizeUpdateTime == nil || sizeUpdateTime! > .now() - speedInterval - speedInterval else {\n                return Int64(0).formattedSizeSpeed(from: startTime)\n            }\n            return sizeUpdateSpeed\n        }\n        var averageSizeSpeed: String {\n            size.formattedSizeSpeed(from: startTime)\n        }\n\n        var percent: String {\n            var value = 0\n            if let totalSize, totalSize > 0 {\n                value = Int(size * 100 / totalSize)\n            } else if let totalItems, totalItems > 0 {\n                value = Int(items * 100 / totalItems)\n            }\n            value = min(value, 100)\n            return \"\\(value)%\"\n        }\n\n        var startTime: DispatchTime\n        var output = \"\"\n        var renderTask: Task<Void, Never>?\n\n        init(\n            description: String = \"\", subDescription: String = \"\", itemsName: String = \"\", tasks: Int = 0, totalTasks: Int? = nil, items: Int = 0, totalItems: Int? = nil,\n            size: Int64 = 0, totalSize: Int64? = nil, startTime: DispatchTime = .now()\n        ) {\n            self.description = description\n            self.subDescription = subDescription\n            self.itemsName = itemsName\n            self.tasks = tasks\n            self.totalTasks = totalTasks\n            self.items = items\n            self.totalItems = totalItems\n            self.size = size\n            self.totalSize = totalSize\n            self.startTime = startTime\n        }\n\n        private mutating func calculateSizeSpeed() {\n            if sizeUpdateTime == nil || sizeUpdateTime! < .now() - speedInterval {\n                let partSize = size - sizeUpdateValue\n                let partStartTime = sizeUpdateTime ?? startTime\n                let partSizeSpeed = partSize.formattedSizeSpeed(from: partStartTime)\n                self.sizeUpdateSpeed = partSizeSpeed\n\n                sizeUpdateTime = .now()\n                sizeUpdateValue = size\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressBar+Terminal.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\n\nenum EscapeSequence {\n    static let hideCursor = \"\\u{001B}[?25l\"\n    static let showCursor = \"\\u{001B}[?25h\"\n    static let moveUp = \"\\u{001B}[1A\"\n    static let clearToEndOfLine = \"\\u{001B}[K\"\n}\n\nextension ProgressBar {\n    var termWidth: Int {\n        guard\n            let terminalHandle = term,\n            let terminal = try? Terminal(descriptor: terminalHandle.fileDescriptor)\n        else {\n            return 0\n        }\n\n        return (try? Int(terminal.size.width)) ?? 0\n    }\n\n    /// Clears the progress bar and resets the cursor.\n    public func clearAndResetCursor() {\n        state.withLock { s in\n            clear(state: &s)\n            resetCursor()\n        }\n    }\n\n    /// Clears the progress bar.\n    public func clear() {\n        state.withLock { s in\n            clear(state: &s)\n        }\n    }\n\n    /// Clears the progress bar (caller must hold state lock).\n    func clear(state: inout State) {\n        displayText(\"\", state: &state)\n    }\n\n    /// Resets the cursor.\n    public func resetCursor() {\n        display(EscapeSequence.showCursor)\n    }\n\n    func display(_ text: String) {\n        guard let term else {\n            return\n        }\n        termQueue.sync {\n            try? term.write(contentsOf: Data(text.utf8))\n            try? term.synchronize()\n        }\n    }\n\n    func displayText(_ text: String, terminating: String = \"\\r\") {\n        state.withLock { s in\n            displayText(text, state: &s, terminating: terminating)\n        }\n    }\n\n    func displayText(_ text: String, state: inout State, terminating: String = \"\\r\") {\n        state.output = text\n\n        // Clears previously printed lines.\n        var lines = \"\"\n        if terminating.hasSuffix(\"\\r\") && termWidth > 0 {\n            let lineCount = (text.count - 1) / termWidth\n            for _ in 0..<lineCount {\n                lines += EscapeSequence.moveUp\n            }\n        }\n\n        let output = \"\\(text)\\(EscapeSequence.clearToEndOfLine)\\(terminating)\\(lines)\"\n        display(output)\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressBar.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Synchronization\n\n/// A progress bar that updates itself as tasks are completed.\npublic final class ProgressBar: Sendable {\n    let config: ProgressConfig\n    let state: Mutex<State>\n    let term: FileHandle?\n    let termQueue = DispatchQueue(label: \"com.apple.container.ProgressBar\")\n\n    /// Returns `true` if the progress bar has finished.\n    public var isFinished: Bool {\n        state.withLock { $0.finished }\n    }\n\n    /// Creates a new progress bar.\n    /// - Parameter config: The configuration for the progress bar.\n    public init(config: ProgressConfig) {\n        self.config = config\n        term = isatty(config.terminal.fileDescriptor) == 1 ? config.terminal : nil\n        let state = State(\n            description: config.initialDescription, itemsName: config.initialItemsName, totalTasks: config.initialTotalTasks,\n            totalItems: config.initialTotalItems,\n            totalSize: config.initialTotalSize)\n        self.state = Mutex(state)\n        display(EscapeSequence.hideCursor)\n    }\n\n    /// Allows resetting the progress state.\n    public func reset() {\n        state.withLock {\n            $0 = State(description: config.initialDescription)\n        }\n    }\n\n    /// Allows resetting the progress state of the current task.\n    public func resetCurrentTask() {\n        state.withLock {\n            $0 = State(description: $0.description, itemsName: $0.itemsName, tasks: $0.tasks, totalTasks: $0.totalTasks, startTime: $0.startTime)\n        }\n    }\n\n    /// Updates the description of the progress bar and increments the tasks by one.\n    /// - Parameter description: The description of the action being performed.\n    public func set(description: String) {\n        resetCurrentTask()\n\n        state.withLock {\n            $0.description = description\n            $0.subDescription = \"\"\n            $0.tasks += 1\n        }\n    }\n\n    /// Updates the additional description of the progress bar.\n    /// - Parameter subDescription: The additional description of the action being performed.\n    public func set(subDescription: String) {\n        resetCurrentTask()\n\n        state.withLock { $0.subDescription = subDescription }\n    }\n\n    private func start(intervalSeconds: TimeInterval) async {\n        while true {\n            let done = state.withLock { s -> Bool in\n                guard !s.finished else {\n                    return true\n                }\n                render(state: &s)\n                s.iteration += 1\n                return false\n            }\n\n            if done {\n                return\n            }\n\n            let intervalNanoseconds = UInt64(intervalSeconds * 1_000_000_000)\n            guard (try? await Task.sleep(nanoseconds: intervalNanoseconds)) != nil else {\n                return\n            }\n        }\n    }\n\n    /// Starts an animation of the progress bar.\n    /// - Parameter intervalSeconds: The time interval between updates in seconds.\n    public func start(intervalSeconds: TimeInterval = 0.04) {\n        state.withLock {\n            if $0.renderTask != nil {\n                return\n            }\n            $0.renderTask = Task(priority: .utility) {\n                await start(intervalSeconds: intervalSeconds)\n            }\n        }\n    }\n\n    /// Finishes the progress bar.\n    /// - Parameter clearScreen: If true, clears the progress bar from the screen.\n    public func finish(clearScreen: Bool = false) {\n        state.withLock { s in\n            guard !s.finished else { return }\n\n            s.finished = true\n            s.renderTask?.cancel()\n\n            let shouldClear = clearScreen || config.clearOnFinish\n            if !config.disableProgressUpdates && !shouldClear {\n                let output = draw(state: s)\n                displayText(output, state: &s, terminating: \"\\n\")\n            }\n\n            if shouldClear {\n                clear(state: &s)\n            }\n            resetCursor()\n        }\n    }\n}\n\nextension ProgressBar {\n    private func secondsSinceStart(from startTime: DispatchTime) -> Int {\n        let timeDifferenceNanoseconds = DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds\n        let timeDifferenceSeconds = Int(floor(Double(timeDifferenceNanoseconds) / 1_000_000_000))\n        return timeDifferenceSeconds\n    }\n\n    func render(force: Bool = false) {\n        guard term != nil && !config.disableProgressUpdates else {\n            return\n        }\n        state.withLock { s in\n            render(state: &s, force: force)\n        }\n    }\n\n    func render(state: inout State, force: Bool = false) {\n        guard term != nil && !config.disableProgressUpdates else {\n            return\n        }\n        guard force || !state.finished else {\n            return\n        }\n        let output = draw(state: state)\n        displayText(output, state: &state)\n    }\n\n    /// Detail levels for progressive truncation.\n    enum DetailLevel: Int, CaseIterable {\n        case full = 0  // Everything shown\n        case noSpeed  // Drop speed from parens\n        case noSize  // Drop size from parens\n        case noParens  // Drop parens entirely (items, size, speed)\n        case noTime  // Drop time\n        case noDescription  // Drop description/subdescription\n        case minimal  // Just spinner, tasks, percent\n    }\n\n    func draw(state: State) -> String {\n        let width = termWidth\n        // If no terminal or width unknown, use full detail\n        guard width > 0 else {\n            return draw(state: state, detail: .full)\n        }\n\n        // Add a small buffer to prevent wrapping issues during resize\n        let bufferChars = 4\n        let targetWidth = max(1, width - bufferChars)\n\n        for detail in DetailLevel.allCases {\n            let output = draw(state: state, detail: detail)\n            if output.count <= targetWidth {\n                return output\n            }\n        }\n\n        return draw(state: state, detail: .minimal)\n    }\n\n    func draw(state: State, detail: DetailLevel) -> String {\n        var components = [String]()\n\n        // Spinner - always shown if configured (unless using progress bar)\n        if config.showSpinner && !config.showProgressBar {\n            if !state.finished {\n                let spinnerIcon = config.theme.getSpinnerIcon(state.iteration)\n                components.append(\"\\(spinnerIcon)\")\n            } else {\n                components.append(\"\\(config.theme.done)\")\n            }\n        }\n\n        // Tasks [x/y] - always shown if configured\n        if config.showTasks, let totalTasks = state.totalTasks {\n            let tasks = min(state.tasks, totalTasks)\n            components.append(\"[\\(tasks)/\\(totalTasks)]\")\n        }\n\n        // Description - dropped at noDescription level\n        if detail.rawValue < DetailLevel.noDescription.rawValue {\n            if config.showDescription && !state.description.isEmpty {\n                components.append(\"\\(state.description)\")\n                if !state.subDescription.isEmpty {\n                    components.append(\"\\(state.subDescription)\")\n                }\n            }\n        }\n\n        let allowProgress = !config.ignoreSmallSize || state.totalSize == nil || state.totalSize! > Int64(1024 * 1024)\n        let value = state.totalSize != nil ? state.size : Int64(state.items)\n        let total = state.totalSize ?? Int64(state.totalItems ?? 0)\n\n        // Percent - always shown if configured\n        if config.showPercent && total > 0 && allowProgress {\n            components.append(\"\\(state.finished ? \"100%\" : state.percent)\")\n        }\n\n        // Progress bar - always shown if configured\n        if config.showProgressBar, total > 0, allowProgress {\n            let usedWidth = components.joined(separator: \" \").count + 45\n            let remainingWidth = max(config.width - usedWidth, 1)\n            let barLength = state.finished ? remainingWidth : Int(Int64(remainingWidth) * value / total)\n            let barPaddingLength = remainingWidth - barLength\n            let bar = \"\\(String(repeating: config.theme.bar, count: barLength))\\(String(repeating: \" \", count: barPaddingLength))\"\n            components.append(\"|\\(bar)|\")\n        }\n\n        // Additional components in parens - progressively dropped\n        if detail.rawValue < DetailLevel.noParens.rawValue {\n            var additionalComponents = [String]()\n\n            // Items - dropped at noParens level\n            if config.showItems, state.items > 0 {\n                var itemsName = \"\"\n                if !state.itemsName.isEmpty {\n                    itemsName = \" \\(state.itemsName)\"\n                }\n                if state.finished {\n                    if let totalItems = state.totalItems {\n                        additionalComponents.append(\"\\(totalItems.formattedNumber())\\(itemsName)\")\n                    }\n                } else {\n                    if let totalItems = state.totalItems {\n                        additionalComponents.append(\"\\(state.items.formattedNumber()) of \\(totalItems.formattedNumber())\\(itemsName)\")\n                    } else {\n                        additionalComponents.append(\"\\(state.items.formattedNumber())\\(itemsName)\")\n                    }\n                }\n            }\n\n            // Size and speed - progressively dropped\n            if state.size > 0 && allowProgress {\n                if state.finished {\n                    // Size - dropped at noSize level\n                    if detail.rawValue < DetailLevel.noSize.rawValue {\n                        if config.showSize {\n                            if let totalSize = state.totalSize {\n                                var formattedTotalSize = totalSize.formattedSize()\n                                formattedTotalSize = adjustFormattedSize(formattedTotalSize)\n                                additionalComponents.append(formattedTotalSize)\n                            }\n                        }\n                    }\n                } else {\n                    // Size - dropped at noSize level\n                    var formattedCombinedSize = \"\"\n                    if detail.rawValue < DetailLevel.noSize.rawValue && config.showSize {\n                        var formattedSize = state.size.formattedSize()\n                        formattedSize = adjustFormattedSize(formattedSize)\n                        if let totalSize = state.totalSize {\n                            var formattedTotalSize = totalSize.formattedSize()\n                            formattedTotalSize = adjustFormattedSize(formattedTotalSize)\n                            formattedCombinedSize = combineSize(size: formattedSize, totalSize: formattedTotalSize)\n                        } else {\n                            formattedCombinedSize = formattedSize\n                        }\n                    }\n\n                    // Speed - dropped at noSpeed level\n                    var formattedSpeed = \"\"\n                    if detail.rawValue < DetailLevel.noSpeed.rawValue && config.showSpeed {\n                        formattedSpeed = \"\\(state.sizeSpeed ?? state.averageSizeSpeed)\"\n                        formattedSpeed = adjustFormattedSize(formattedSpeed)\n                    }\n\n                    if !formattedCombinedSize.isEmpty && !formattedSpeed.isEmpty {\n                        additionalComponents.append(formattedCombinedSize)\n                        additionalComponents.append(formattedSpeed)\n                    } else if !formattedCombinedSize.isEmpty {\n                        additionalComponents.append(formattedCombinedSize)\n                    } else if !formattedSpeed.isEmpty {\n                        additionalComponents.append(formattedSpeed)\n                    }\n                }\n            }\n\n            if additionalComponents.count > 0 {\n                let joinedAdditionalComponents = additionalComponents.joined(separator: \", \")\n                components.append(\"(\\(joinedAdditionalComponents))\")\n            }\n        }\n\n        // Time - dropped at noTime level\n        if detail.rawValue < DetailLevel.noTime.rawValue && config.showTime {\n            let timeDifferenceSeconds = secondsSinceStart(from: state.startTime)\n            let formattedTime = timeDifferenceSeconds.formattedTime()\n            components.append(\"[\\(formattedTime)]\")\n        }\n\n        return components.joined(separator: \" \")\n    }\n\n    private func adjustFormattedSize(_ size: String) -> String {\n        // Ensure we always have one digit after the decimal point to prevent flickering.\n        let zero = Int64(0).formattedSize()\n        let decimalSep = Locale.current.decimalSeparator ?? \".\"\n        guard !size.contains(decimalSep), let first = size.first, first.isNumber || !size.contains(zero) else {\n            return size\n        }\n        var size = size\n        for unit in [\"MB\", \"GB\", \"TB\"] {\n            size = size.replacingOccurrences(of: \" \\(unit)\", with: \"\\(decimalSep)0 \\(unit)\")\n        }\n        return size\n    }\n\n    private func combineSize(size: String, totalSize: String) -> String {\n        let sizeComponents = size.split(separator: \" \", maxSplits: 1)\n        let totalSizeComponents = totalSize.split(separator: \" \", maxSplits: 1)\n        guard sizeComponents.count == 2, totalSizeComponents.count == 2 else {\n            return \"\\(size)/\\(totalSize)\"\n        }\n        let sizeNumber = sizeComponents[0]\n        let sizeUnit = sizeComponents[1]\n        let totalSizeNumber = totalSizeComponents[0]\n        let totalSizeUnit = totalSizeComponents[1]\n        guard sizeUnit == totalSizeUnit else {\n            return \"\\(size)/\\(totalSize)\"\n        }\n        return \"\\(sizeNumber)/\\(totalSizeNumber) \\(totalSizeUnit)\"\n    }\n\n    func draw() -> String {\n        state.withLock { draw(state: $0) }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressConfig.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A configuration for displaying a progress bar.\npublic struct ProgressConfig: Sendable {\n    /// The file handle for progress updates.\n    let terminal: FileHandle\n    /// The initial description of the progress bar.\n    let initialDescription: String\n    /// The initial additional description of the progress bar.\n    let initialSubDescription: String\n    /// The initial items name (e.g., \"files\").\n    let initialItemsName: String\n    /// A flag indicating whether to show a spinner (e.g., \"⠋\").\n    /// The spinner is hidden when a progress bar is shown.\n    public let showSpinner: Bool\n    /// A flag indicating whether to show tasks and total tasks (e.g., \"[1]\" or \"[1/3]\").\n    public let showTasks: Bool\n    /// A flag indicating whether to show the description (e.g., \"Downloading...\").\n    public let showDescription: Bool\n    /// A flag indicating whether to show a percentage (e.g., \"100%\").\n    /// The percentage is hidden when no total size and total items are set.\n    public let showPercent: Bool\n    /// A flag indicating whether to show a progress bar (e.g., \"|███            |\").\n    /// The progress bar is hidden when no total size and total items are set.\n    public let showProgressBar: Bool\n    /// A flag indicating whether to show items and total items (e.g., \"(22 it)\" or \"(22/22 it)\").\n    public let showItems: Bool\n    /// A flag indicating whether to show a size and a total size (e.g., \"(22 MB)\" or \"(22/22 MB)\").\n    public let showSize: Bool\n    /// A flag indicating whether to show a speed (e.g., \"(4.834 MB/s)\").\n    /// The speed is combined with the size and total size (e.g., \"(22/22 MB, 4.834 MB/s)\").\n    /// The speed is hidden when no total size is set.\n    public let showSpeed: Bool\n    /// A flag indicating whether to show the elapsed time (e.g., \"[4s]\").\n    public let showTime: Bool\n    /// The flag indicating whether to ignore small size values (less than 1 MB). For example, this may help to avoid reaching 100% after downloading metadata before downloading content.\n    public let ignoreSmallSize: Bool\n    /// The initial total tasks of the progress bar.\n    let initialTotalTasks: Int?\n    /// The initial total size of the progress bar.\n    let initialTotalSize: Int64?\n    /// The initial total items of the progress bar.\n    let initialTotalItems: Int?\n    /// The width of the progress bar in characters.\n    public let width: Int\n    /// The theme of the progress bar.\n    public let theme: ProgressTheme\n    /// The flag indicating whether to clear the progress bar before resetting the cursor.\n    public let clearOnFinish: Bool\n    /// The flag indicating whether to update the progress bar.\n    public let disableProgressUpdates: Bool\n    /// Creates a new instance of `ProgressConfig`.\n    /// - Parameters:\n    ///   - terminal: The file handle for progress updates. The default value is `FileHandle.standardError`.\n    ///   - description: The initial description of the progress bar. The default value is `\"\"`.\n    ///   - subDescription: The initial additional description of the progress bar. The default value is `\"\"`.\n    ///   - itemsName: The initial items name. The default value is `\"it\"`.\n    ///   - showSpinner: A flag indicating whether to show a spinner. The default value is `true`.\n    ///   - showTasks: A flag indicating whether to show tasks and total tasks. The default value is `false`.\n    ///   - showDescription: A flag indicating whether to show the description. The default value is `true`.\n    ///   - showPercent: A flag indicating whether to show a percentage. The default value is `true`.\n    ///   - showProgressBar: A flag indicating whether to show a progress bar. The default value is `false`.\n    ///   - showItems: A flag indicating whether to show items and a total items. The default value is `false`.\n    ///   - showSize: A flag indicating whether to show a size and a total size. The default value is `true`.\n    ///   - showSpeed: A flag indicating whether to show a speed. The default value is `true`.\n    ///   - showTime: A flag indicating whether to show the elapsed time. The default value is `true`.\n    ///   - ignoreSmallSize: A flag indicating whether to ignore small size values. The default value is `false`.\n    ///   - totalTasks: The initial total tasks of the progress bar. The default value is `nil`.\n    ///   - totalItems: The initial total items of the progress bar. The default value is `nil`.\n    ///   - totalSize: The initial total size of the progress bar. The default value is `nil`.\n    ///   - width: The width of the progress bar in characters. The default value is `120`.\n    ///   - theme: The theme of the progress bar. The default value is `nil`.\n    ///   - clearOnFinish: The flag indicating whether to clear the progress bar before resetting the cursor. The default is `true`.\n    ///   - disableProgressUpdates: The flag indicating whether to update the progress bar. The default is `false`.\n    public init(\n        terminal: FileHandle = .standardError,\n        description: String = \"\",\n        subDescription: String = \"\",\n        itemsName: String = \"it\",\n        showSpinner: Bool = true,\n        showTasks: Bool = false,\n        showDescription: Bool = true,\n        showPercent: Bool = true,\n        showProgressBar: Bool = false,\n        showItems: Bool = false,\n        showSize: Bool = true,\n        showSpeed: Bool = true,\n        showTime: Bool = true,\n        ignoreSmallSize: Bool = false,\n        totalTasks: Int? = nil,\n        totalItems: Int? = nil,\n        totalSize: Int64? = nil,\n        width: Int = 120,\n        theme: ProgressTheme? = nil,\n        clearOnFinish: Bool = true,\n        disableProgressUpdates: Bool = false\n    ) throws {\n        if let totalTasks {\n            guard totalTasks > 0 else {\n                throw Error.invalid(\"totalTasks must be greater than zero\")\n            }\n        }\n        if let totalItems {\n            guard totalItems > 0 else {\n                throw Error.invalid(\"totalItems must be greater than zero\")\n            }\n        }\n        if let totalSize {\n            guard totalSize > 0 else {\n                throw Error.invalid(\"totalSize must be greater than zero\")\n            }\n        }\n\n        self.terminal = terminal\n        self.initialDescription = description\n        self.initialSubDescription = subDescription\n        self.initialItemsName = itemsName\n\n        self.showSpinner = showSpinner\n        self.showTasks = showTasks\n        self.showDescription = showDescription\n        self.showPercent = showPercent\n        self.showProgressBar = showProgressBar\n        self.showItems = showItems\n        self.showSize = showSize\n        self.showSpeed = showSpeed\n        self.showTime = showTime\n\n        self.ignoreSmallSize = ignoreSmallSize\n        self.initialTotalTasks = totalTasks\n        self.initialTotalItems = totalItems\n        self.initialTotalSize = totalSize\n\n        self.width = width\n        self.theme = theme ?? DefaultProgressTheme()\n        self.clearOnFinish = clearOnFinish\n        self.disableProgressUpdates = disableProgressUpdates\n    }\n}\n\nextension ProgressConfig {\n    /// An enumeration of errors that can occur when creating a `ProgressConfig`.\n    public enum Error: Swift.Error, CustomStringConvertible {\n        case invalid(String)\n\n        /// The description of the error.\n        public var description: String {\n            switch self {\n            case .invalid(let reason):\n                return \"failed to validate config (\\(reason))\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressTaskCoordinator.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\n/// A type that represents a task whose progress is being monitored.\npublic struct ProgressTask: Sendable, Equatable {\n    private var id = UUID()\n    private var coordinator: ProgressTaskCoordinator\n\n    init(manager: ProgressTaskCoordinator) {\n        self.coordinator = manager\n    }\n\n    static public func == (lhs: ProgressTask, rhs: ProgressTask) -> Bool {\n        lhs.id == rhs.id\n    }\n\n    /// Returns `true` if this task is the currently active task, `false` otherwise.\n    public func isCurrent() async -> Bool {\n        guard let currentTask = await coordinator.currentTask else {\n            return false\n        }\n        return currentTask == self\n    }\n}\n\n/// A type that coordinates progress tasks to ignore updates from completed tasks.\npublic actor ProgressTaskCoordinator {\n    var currentTask: ProgressTask?\n\n    /// Creates an instance of `ProgressTaskCoordinator`.\n    public init() {}\n\n    /// Returns a new task that should be monitored for progress updates.\n    public func startTask() -> ProgressTask {\n        let newTask = ProgressTask(manager: self)\n        currentTask = newTask\n        return newTask\n    }\n\n    /// Performs cleanup when the monitored tasks complete.\n    public func finish() {\n        currentTask = nil\n    }\n\n    /// Returns a handler that updates the progress of a given task.\n    /// - Parameters:\n    ///   - task: The task whose progress is being updated.\n    ///   - progressUpdate: The handler to invoke when progress updates are received.\n    public static func handler(for task: ProgressTask, from progressUpdate: @escaping ProgressUpdateHandler) -> ProgressUpdateHandler {\n        { events in\n            // Ignore updates from completed tasks.\n            if await task.isCurrent() {\n                await progressUpdate(events)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressTheme.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n/// A theme for progress bar.\npublic protocol ProgressTheme: Sendable {\n    /// The icons used to represent a spinner.\n    var spinner: [String] { get }\n    /// The icon used to represent a progress bar.\n    var bar: String { get }\n    /// The icon used to indicate that a progress bar finished.\n    var done: String { get }\n}\n\npublic struct DefaultProgressTheme: ProgressTheme {\n    public let spinner = [\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\"]\n    public let bar = \"█\"\n    public let done = \"✔\"\n}\n\nextension ProgressTheme {\n    func getSpinnerIcon(_ iteration: Int) -> String {\n        spinner[iteration % spinner.count]\n    }\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/ProgressUpdate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\npublic enum ProgressUpdateEvent: Sendable {\n    case setDescription(String)\n    case setSubDescription(String)\n    case setItemsName(String)\n    case addTasks(Int)\n    case setTasks(Int)\n    case addTotalTasks(Int)\n    case setTotalTasks(Int)\n    case addItems(Int)\n    case setItems(Int)\n    case addTotalItems(Int)\n    case setTotalItems(Int)\n    case addSize(Int64)\n    case setSize(Int64)\n    case addTotalSize(Int64)\n    case setTotalSize(Int64)\n    case custom(String)\n}\n\npublic typealias ProgressUpdateHandler = @Sendable (_ events: [ProgressUpdateEvent]) async -> Void\n\npublic protocol ProgressAdapter {\n    associatedtype T\n    static func handler(from progressUpdate: ProgressUpdateHandler?) -> (@Sendable ([T]) async -> Void)?\n}\n"
  },
  {
    "path": "Sources/TerminalProgress/StandardError.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\n\nstruct StandardError {\n    func write(_ string: String) {\n        if let data = string.data(using: .utf8) {\n            FileHandle.standardError.write(data)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuildBase.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerBuild\n\n/* CLIBuildBase is the base class used for creating builder tests. Subtests classes\n// for these tests are nested in extensions of CLIBuildBase so that we can set\n// the serialized parallelization attribute across all builder tests.\n*/\n@Suite(.serialized)\nclass TestCLIBuildBase: CLITest {\n    override init() throws {\n        try super.init()\n\n        try? builderDelete(force: true)\n        try builderStart()\n        try waitForBuilderRunning()\n    }\n\n    deinit {\n        try? builderDelete(force: true)\n    }\n\n    func waitForBuilderRunning() throws {\n        let buildkitName = \"buildkit\"\n        try waitForContainerRunning(buildkitName, 10)\n\n        // exec into buildkit and check if builder-shim is running\n        var attempt = 3\n        while attempt > 0 {\n            attempt -= 1\n            do {\n                let response = try doExec(name: buildkitName, cmd: [\"pidof\", \"-s\", \"container-builder-shim\"])\n                if !response.isEmpty {\n                    // found the init process running\n                    return\n                }\n            } catch {\n                print(\"container-builder-shim check failed with \\(error)\")\n            }\n            sleep(1)\n        }\n        throw CLIError.executionFailed(\"failed to wait for container-builder-shim process on \\(buildkitName)\")\n    }\n\n    func createTempDir() throws -> URL {\n        let tempDir = testDir.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        return tempDir\n    }\n\n    func createTempFile(suffix: String, contents: Data) throws -> URL {\n        let tempFile = testDir.appendingPathComponent(UUID().uuidString + suffix)\n        try contents.write(to: tempFile, options: .atomic)\n        return tempFile\n    }\n\n    func createContext(tempDir: URL, dockerfile: String, context: [FileSystemEntry]? = nil) throws {\n        let dockerfileBytes = dockerfile.data(using: .utf8)!\n        try dockerfileBytes.write(to: tempDir.appendingPathComponent(\"Dockerfile\"), options: .atomic)\n\n        let contextDir: URL = tempDir.appendingPathComponent(\"context\").absoluteURL\n        try FileManager.default.createDirectory(at: contextDir, withIntermediateDirectories: true, attributes: nil)\n\n        if let context {\n            for entry in context {\n                try createEntry(entry, contextDir)\n            }\n        }\n    }\n\n    @discardableResult\n    func build(\n        tag: String,\n        tempDir: URL,\n        buildArgs: [String] = [],\n        otherArgs: [String] = []\n    ) throws -> String {\n        try buildWithPaths(\n            tags: [tag],\n            tempContext: tempDir,\n            tempDockerfileContext: tempDir,\n            buildArgs: buildArgs,\n            otherArgs: otherArgs\n        )\n    }\n\n    @discardableResult\n    func build(\n        tags: [String],\n        tempDir: URL,\n        buildArgs: [String] = [],\n        otherArgs: [String] = []\n    ) throws -> String {\n        try buildWithPaths(\n            tags: tags,\n            tempContext: tempDir,\n            tempDockerfileContext: tempDir,\n            buildArgs: buildArgs,\n            otherArgs: otherArgs\n        )\n    }\n\n    // buildWithPaths is a helper function for calling build with different paths for the build context and\n    // the dockerfile path. If both paths are the same, use `build` func above.\n    @discardableResult\n    func buildWithPaths(\n        tags: [String],\n        tempContext: URL,\n        tempDockerfileContext: URL,\n        buildArgs: [String] = [],\n        otherArgs: [String] = []\n    ) throws -> String {\n        let contextDir: URL = tempContext.appendingPathComponent(\"context\")\n        let contextDirPath = contextDir.absoluteURL.path\n        var args = [\n            \"build\",\n            \"-f\",\n            tempDockerfileContext.appendingPathComponent(\"Dockerfile\").path,\n        ]\n        for tag in tags {\n            args.append(\"-t\")\n            args.append(tag)\n        }\n        for arg in buildArgs {\n            args.append(\"--build-arg\")\n            args.append(arg)\n        }\n        args.append(contextDirPath)\n\n        args.append(contentsOf: otherArgs)\n\n        let response = try run(arguments: args)\n        if response.status != 0 {\n            throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n        }\n\n        return response.output\n    }\n\n    @discardableResult\n    func buildWithStdin(\n        tags: [String],\n        tempContext: URL,\n        dockerfileContents: String,\n        buildArgs: [String] = [],\n        otherArgs: [String] = []\n    ) throws -> String {\n        let contextDir: URL = tempContext.appendingPathComponent(\"context\")\n        let contextDirPath = contextDir.absoluteURL.path\n        var args = [\n            \"build\",\n            \"-f\",\n            \"-\",\n        ]\n        for tag in tags {\n            args.append(\"-t\")\n            args.append(tag)\n        }\n        for arg in buildArgs {\n            args.append(\"--build-arg\")\n            args.append(arg)\n        }\n        args.append(contextDirPath)\n\n        args.append(contentsOf: otherArgs)\n\n        let stdinData = Data(dockerfileContents.utf8)\n        let response = try run(arguments: args, stdin: stdinData)\n        if response.status != 0 {\n            throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n        }\n\n        return response.output\n    }\n\n    enum FileSystemEntry {\n        case file(\n            _ path: String,\n            content: FileEntryContent,\n            permissions: FilePermissions = [.r, .w, .gr, .gw, .or, .ow],\n            uid: uid_t = 0,\n            gid: gid_t = 0\n        )\n        case directory(\n            _ path: String,\n            permissions: FilePermissions = [.r, .w, .x, .gr, .gw, .gx, .or, .ow, .ox],\n            uid: uid_t = 0,\n            gid: gid_t = 0\n        )\n        case symbolicLink(\n            _ path: String,\n            target: String,\n            uid: uid_t = 0,\n            gid: gid_t = 0\n        )\n    }\n\n    func createEntry(_ entry: FileSystemEntry, _ contextDir: URL) throws {\n        switch entry {\n        // last 2 params are uid and gid\n        case .file(let path, let content, let permissions, _, _):\n            let fullPath = contextDir.appending(path: path)\n            // not using .absoluteURL deletes the last component from fullPath\n            let directory: URL = fullPath.absoluteURL.deletingLastPathComponent()\n            let contentPath = fullPath.path\n\n            try FileManager.default.createDirectory(\n                atPath: directory.path,\n                withIntermediateDirectories: true,\n                attributes: nil\n            )\n\n            switch content {\n            case .data(let data):\n                try data.write(to: fullPath)\n            case .zeroFilled(let size):\n                let fd = open(contentPath, O_CREAT | O_WRONLY, permissions.rawValue)\n                if fd == -1 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) }\n                defer { close(fd) }\n                ftruncate(fd, off_t(size))\n            }\n\n        // TODO: figure out why this block fails\n        // try FileManager.default.setAttributes(\n        //     [\n        //         .posixPermissions: Int(permissions.rawValue),\n        //         .ownerAccountID: uid,\n        //         .groupOwnerAccountID: gid,\n        //     ],\n        //     ofItemAtPath: fullPath.absoluteURL.absoluteString\n        // )\n\n        case .directory(let path, let permissions, let uid, let gid):\n            let fullPath = contextDir.appendingPathComponent(path).absoluteURL\n            try FileManager.default.createDirectory(\n                atPath: fullPath.path,\n                withIntermediateDirectories: true,\n                attributes: [\n                    .posixPermissions: Int(permissions.rawValue),\n                    .ownerAccountID: uid,\n                    .groupOwnerAccountID: gid,\n                ]\n            )\n\n        case .symbolicLink(let path, let target, let uid, let gid):\n            let fullPath = contextDir.appendingPathComponent(path).absoluteURL\n            let directory: URL = fullPath.deletingLastPathComponent()\n            try FileManager.default.createDirectory(\n                atPath: directory.path,\n                withIntermediateDirectories: true,\n                attributes: nil\n            )\n            let targetURL = contextDir.appendingPathComponent(target)\n            try FileManager.default.createSymbolicLink(\n                atPath: fullPath.path,\n                withDestinationPath: targetURL.relativePathFrom(from: fullPath)\n            )\n            lchown(fullPath.path, uid, gid)\n        }\n    }\n\n    struct FilePermissions: OptionSet {\n        let rawValue: UInt16\n\n        static let r = FilePermissions(rawValue: 0o400)\n        static let w = FilePermissions(rawValue: 0o200)\n        static let x = FilePermissions(rawValue: 0o100)\n\n        static let gr = FilePermissions(rawValue: 0o040)\n        static let gw = FilePermissions(rawValue: 0o020)\n        static let gx = FilePermissions(rawValue: 0o010)\n\n        static let or = FilePermissions(rawValue: 0o004)\n        static let ow = FilePermissions(rawValue: 0o002)\n        static let ox = FilePermissions(rawValue: 0o001)\n    }\n\n    enum FileEntryContent {\n        case zeroFilled(size: Int64)\n        case data(Data)\n    }\n\n    func builderStart(cpus: Int64 = 2, memoryInGBs: Int64 = 2) throws {\n        let (_, _, error, status) = try run(arguments: [\n            \"builder\",\n            \"start\",\n            \"-c\",\n            \"\\(cpus)\",\n            \"-m\",\n            \"\\(memoryInGBs)GB\",\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func builderStop() throws {\n        let (_, _, error, status) = try run(arguments: [\n            \"builder\",\n            \"stop\",\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func builderDelete(force: Bool = false) throws {\n        let (_, _, error, status) = try run(\n            arguments: [\n                \"builder\",\n                \"delete\",\n                force ? \"--force\" : nil,\n            ].compactMap { $0 })\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuilderEnvOnlyTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nextension TestCLIBuildBase {\n    class CLIBuilderEnvOnlyTest: TestCLIBuildBase {\n        override init() throws {\n            try super.init()\n        }\n\n        deinit {\n            try? builderDelete(force: true)\n        }\n\n        @Test func testBuildEnvironmentOnlyImageFromScratch() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM scratch\n\n                ARG BUILD_DATE\n                ARG VERSION=1.0.0\n\n                ENV TERM=xterm \\\\\n                    BUILD_DATE=${BUILD_DATE} \\\\\n                    APP_VERSION=${VERSION} \\\\\n                    PATH=/usr/local/bin:/usr/bin:/bin\n\n                LABEL maintainer=\"test@example.com\" \\\\\n                      version=\"${VERSION}\"\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"test-env-only:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir, buildArgs: [\"BUILD_DATE=2025-01-01\", \"VERSION=2.0.0\"])\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildEnvironmentOnlyImageFromAlpine() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ENV APP_NAME=myapp \\\\\n                    APP_VERSION=1.0.0 \\\\\n                    APP_ENV=production\n\n                LABEL maintainer=\"test@example.com\" \\\\\n                      version=\"1.0.0\" \\\\\n                      description=\"Test environment-only image\"\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"test-alpine-env:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testMultiStageBuildWithEnvOnlyBase() throws {\n            let tempDir: URL = try createTempDir()\n            let baseImageName = \"test-env-base:\\(UUID().uuidString)\"\n\n            // First, create an environment-only base image\n            let baseDockerfile =\n                \"\"\"\n                FROM scratch\n\n                ARG JOBS=6\n                ARG ARCH=amd64\n\n                ENV MAKEOPTS=\"-j${JOBS}\" \\\\\n                    ARCH=\"${ARCH}\" \\\\\n                    PATH=/usr/local/bin:/usr/bin\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: baseDockerfile)\n            try self.build(tag: baseImageName, tempDir: tempDir, buildArgs: [\"JOBS=8\", \"ARCH=arm64\"])\n            #expect(try self.inspectImage(baseImageName) == baseImageName, \"expected base image to build successfully\")\n\n            // Now create a downstream image that uses it\n            let downstreamTempDir: URL = try createTempDir()\n            let downstreamDockerfile =\n                \"\"\"\n                FROM \\(baseImageName)\n\n                # Verify environment is inherited - note: can't use RUN with scratch base\n                LABEL test=\"env-inherited\"\n                \"\"\"\n\n            try createContext(tempDir: downstreamTempDir, dockerfile: downstreamDockerfile)\n            let downstreamImageName = \"test-env-child:\\(UUID().uuidString)\"\n            try self.build(tag: downstreamImageName, tempDir: downstreamTempDir)\n            #expect(\n                try self.inspectImage(downstreamImageName) == downstreamImageName,\n                \"expected downstream image to build successfully\"\n            )\n        }\n\n        @Test func testComplexArgAndEnvCombinations() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM scratch\n\n                ARG JOBS=6\n                ARG MAXLOAD=7.00\n                ARG ARCH=amd64\n                ARG PROFILE_PATH=23.0/split-usr/no-multilib\n                ARG CHOST=x86_64-pc-linux-gnu\n                ARG CFLAGS=-O2 -pipe\n\n                ENV JOBS=\"${JOBS}\" \\\\\n                    MAXLOAD=\"${MAXLOAD}\" \\\\\n                    GENTOO_PROFILE=\"default/linux/${ARCH}/${PROFILE_PATH}\" \\\\\n                    CHOST=\"${CHOST}\" \\\\\n                    MAKEOPTS=\"-j${JOBS}\" \\\\\n                    CFLAGS=\"${CFLAGS}\" \\\\\n                    CXXFLAGS=\"${CFLAGS}\"\n\n                LABEL maintainer=\"test@example.com\"\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"test-complex-env:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir, buildArgs: [\"JOBS=12\", \"ARCH=arm64\"])\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testLabelOnlyDockerfile() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM scratch\n\n                LABEL maintainer=\"test@example.com\" \\\\\n                      version=\"1.0.0\" \\\\\n                      description=\"Test image with only labels\" \\\\\n                      org.opencontainers.image.title=\"Test Image\"\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"test-label-only:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuilderLifecycleTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nextension TestCLIBuildBase {\n    class CLIBuilderLifecycleTest: TestCLIBuildBase {\n        override init() throws {}\n        @Test func testBuilderStartStopCommand() throws {\n            #expect(throws: Never.self) {\n                try self.builderStart()\n                try self.waitForBuilderRunning()\n                let status = try self.getContainerStatus(\"buildkit\")\n                #expect(status == \"running\", \"BuildKit container is not running\")\n            }\n            #expect(throws: Never.self) {\n                try self.builderStop()\n                let status = try self.getContainerStatus(\"buildkit\")\n                #expect(status == \"stopped\", \"BuildKit container is not stopped\")\n            }\n        }\n\n        @Test func testBuilderEnvironmentColors() throws {\n            let testColors = \"run=green:warning=yellow:error=red:cancel=cyan\"\n            let testNoColor = \"true\"\n\n            let originalColors = ProcessInfo.processInfo.environment[\"BUILDKIT_COLORS\"]\n            let originalNoColor = ProcessInfo.processInfo.environment[\"NO_COLOR\"]\n\n            defer {\n                if let originalColors {\n                    setenv(\"BUILDKIT_COLORS\", originalColors, 1)\n                } else {\n                    unsetenv(\"BUILDKIT_COLORS\")\n                }\n                if let originalNoColor {\n                    setenv(\"NO_COLOR\", originalNoColor, 1)\n                } else {\n                    unsetenv(\"NO_COLOR\")\n                }\n\n                try? builderStop()\n                try? builderDelete(force: true)\n            }\n\n            setenv(\"BUILDKIT_COLORS\", testColors, 1)\n            setenv(\"NO_COLOR\", testNoColor, 1)\n\n            try? builderStop()\n            try? builderDelete(force: true)\n\n            let (_, _, err, status) = try run(arguments: [\"builder\", \"start\"])\n            try #require(status == 0, \"builder start failed: \\(err)\")\n\n            try waitForBuilderRunning()\n\n            let container = try inspectContainer(\"buildkit\")\n            let envVars = container.configuration.initProcess.environment\n\n            #expect(\n                envVars.contains(\"BUILDKIT_COLORS=\\(testColors)\"),\n                \"Expected BUILDKIT_COLORS to be passed to container, but it was missing from env: \\(envVars)\"\n            )\n            #expect(\n                envVars.contains(\"NO_COLOR=\\(testNoColor)\"),\n                \"Expected NO_COLOR to be passed to container, but it was missing from env: \\(envVars)\"\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuilderLocalOutputTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nextension TestCLIBuildBase {\n    class CLIBuilderLocalOutputTest: TestCLIBuildBase {\n        override init() throws {\n            try super.init()\n        }\n\n        deinit {\n            try? builderDelete(force: true)\n        }\n\n        @Test func testBuildLocalOutputHappyPath() throws {\n            let tempDir: URL = try createTempDir()\n\n            // Test comprehensive multi-stage build with context and build arguments\n            let dockerfile: String =\n                \"\"\"\n                ARG MESSAGE=default\n                FROM scratch AS builder\n                ADD build.txt /build.txt\n                ADD testfile.txt /hello.txt\n\n                FROM scratch\n                COPY --from=builder /build.txt /final.txt\n                COPY --from=builder /hello.txt /app/hello.txt\n                ADD message.txt /message.txt\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"build.txt\", content: .data(\"Building stage\\n\".data(using: .utf8)!)),\n                .file(\"testfile.txt\", content: .data(\"Hello from local build\\n\".data(using: .utf8)!)),\n                .file(\"message.txt\", content: .data(\"Hello from build args\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let outputDir = tempDir.appendingPathComponent(\"comprehensive-local-output\")\n            let imageName = \"local-comprehensive-test:\\(UUID().uuidString)\"\n\n            let response = try buildWithLocalOutput(\n                tag: imageName,\n                tempDir: tempDir,\n                outputDir: outputDir,\n                args: [\"MESSAGE=Hello from build args\"]\n            )\n\n            // Verify the build succeeded\n            #expect(response.contains(\"Successfully exported to\"), \"Expected successful local export message\")\n\n            // Verify the output directory was created\n            #expect(FileManager.default.fileExists(atPath: outputDir.path), \"Expected local output directory to exist\")\n\n            // Verify the output contains expected structure\n            let contents = try FileManager.default.contentsOfDirectory(atPath: outputDir.path)\n            #expect(!contents.isEmpty, \"Expected local output directory to contain files\")\n\n            // Test basic functionality - verify basic local output works\n            let basicTempDir: URL = try createTempDir()\n            let basicDockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD testfile.txt /hello.txt\n                \"\"\"\n            let basicContext: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"Hello from basic build\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: basicTempDir, dockerfile: basicDockerfile, context: basicContext)\n\n            let basicOutputDir = basicTempDir.appendingPathComponent(\"basic-local-output\")\n            let basicImageName = \"local-basic-test:\\(UUID().uuidString)\"\n\n            let basicResponse = try buildWithLocalOutput(tag: basicImageName, tempDir: basicTempDir, outputDir: basicOutputDir)\n\n            // Verify basic build succeeded\n            #expect(basicResponse.contains(\"Successfully exported to\"), \"Expected successful basic local export message\")\n            #expect(FileManager.default.fileExists(atPath: basicOutputDir.path), \"Expected basic local output directory to exist\")\n\n            // Test context functionality - verify COPY works with context\n            let contextTempDir: URL = try createTempDir()\n            let contextDockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                COPY testfile.txt /app/testfile.txt\n                \"\"\"\n            let contextContext: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"Test content for context build\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: contextTempDir, dockerfile: contextDockerfile, context: contextContext)\n\n            let contextOutputDir = contextTempDir.appendingPathComponent(\"context-local-output\")\n            let contextImageName = \"local-context-test:\\(UUID().uuidString)\"\n\n            let contextResponse = try buildWithLocalOutput(tag: contextImageName, tempDir: contextTempDir, outputDir: contextOutputDir)\n\n            // Verify context build succeeded\n            #expect(contextResponse.contains(\"Successfully exported to\"), \"Expected successful context local export message\")\n            #expect(FileManager.default.fileExists(atPath: contextOutputDir.path), \"Expected context local output directory to exist\")\n        }\n\n        @Test func testBuildLocalOutputEdgeCases() throws {\n            // Test building with different context paths\n            let dockerfileCtxDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                COPY . /app\n                \"\"\"\n            let dockerfileCtx: [FileSystemEntry] = [\n                .file(\"dockerfile-context.txt\", content: .data(\"Dockerfile context file\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: dockerfileCtxDir, dockerfile: dockerfile, context: dockerfileCtx)\n\n            let buildContextDir: URL = try createTempDir()\n            let buildContext: [FileSystemEntry] = [\n                .file(\"build-context.txt\", content: .data(\"Build context file\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: buildContextDir, dockerfile: \"\", context: buildContext)\n\n            let outputDir = dockerfileCtxDir.appendingPathComponent(\"diffpaths-local-output\")\n            let imageName = \"local-diffpaths-test:\\(UUID().uuidString)\"\n\n            let response = try buildWithPathsAndLocalOutput(\n                tag: imageName,\n                tempContext: buildContextDir,\n                tempDockerfileContext: dockerfileCtxDir,\n                outputDir: outputDir\n            )\n\n            // Verify the build succeeded\n            #expect(response.contains(\"Successfully exported to\"), \"Expected successful local export message\")\n\n            // Verify the output directory exists\n            #expect(FileManager.default.fileExists(atPath: outputDir.path), \"Expected local output directory to exist\")\n\n            // Test building to existing output directory\n            let existingTempDir: URL = try createTempDir()\n            let existingDockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD newfile.txt /newfile.txt\n                \"\"\"\n            let existingContext: [FileSystemEntry] = [\n                .file(\"newfile.txt\", content: .data(\"New content from build\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: existingTempDir, dockerfile: existingDockerfile, context: existingContext)\n\n            let existingOutputDir = existingTempDir.appendingPathComponent(\"existing-output\")\n\n            // Create the output directory and add some existing files\n            try FileManager.default.createDirectory(at: existingOutputDir, withIntermediateDirectories: true)\n            let existingFile = existingOutputDir.appendingPathComponent(\"existing.txt\")\n            try \"Existing file content\\n\".data(using: .utf8)!.write(to: existingFile)\n\n            let existingImageName = \"local-existing-test:\\(UUID().uuidString)\"\n\n            let existingResponse = try buildWithLocalOutput(tag: existingImageName, tempDir: existingTempDir, outputDir: existingOutputDir)\n\n            // Verify the build succeeded\n            #expect(existingResponse.contains(\"Successfully exported to\"), \"Expected successful local export message\")\n\n            // Verify the output directory exists\n            #expect(FileManager.default.fileExists(atPath: existingOutputDir.path), \"Expected local output directory to exist\")\n\n            // Verify the existing file is still there (local output should merge/overwrite)\n            let contents = try FileManager.default.contentsOfDirectory(atPath: existingOutputDir.path)\n            #expect(!contents.isEmpty, \"Expected local output directory to contain files\")\n\n            // The behavior may vary - local output might overwrite the directory or merge contents\n            // This test verifies that the operation completes successfully with an existing directory\n        }\n\n        @Test func testBuildLocalOutputFailure() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD test.txt /test.txt\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"test.txt\", content: .data(\"test\\n\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            // Use a path that doesn't exist and can't be created (invalid parent)\n            let invalidOutputDir = URL(fileURLWithPath: \"/nonexistent/invalid/path\")\n            let imageName = \"local-invalid-test:\\(UUID().uuidString)\"\n\n            #expect(throws: CLIError.self) {\n                try buildWithLocalOutput(tag: imageName, tempDir: tempDir, outputDir: invalidOutputDir)\n            }\n        }\n\n        // Helper function to build with local output\n        @discardableResult\n        func buildWithLocalOutput(tag: String, tempDir: URL, outputDir: URL, args: [String]? = nil) throws -> String {\n            try buildWithPathsAndLocalOutput(\n                tag: tag,\n                tempContext: tempDir,\n                tempDockerfileContext: tempDir,\n                outputDir: outputDir,\n                args: args\n            )\n        }\n\n        // Helper function to build with different paths and local output\n        @discardableResult\n        func buildWithPathsAndLocalOutput(\n            tag: String,\n            tempContext: URL,\n            tempDockerfileContext: URL,\n            outputDir: URL,\n            args: [String]? = nil\n        ) throws -> String {\n            let contextDir: URL = tempContext.appendingPathComponent(\"context\")\n            let contextDirPath = contextDir.absoluteURL.path\n            var buildArgs = [\n                \"build\",\n                \"-f\",\n                tempDockerfileContext.appendingPathComponent(\"Dockerfile\").path,\n                \"-t\",\n                tag,\n                \"--output\",\n                \"type=local,dest=\\(outputDir.path)\",\n            ]\n            if let args = args {\n                for arg in args {\n                    buildArgs.append(\"--build-arg\")\n                    buildArgs.append(arg)\n                }\n            }\n            buildArgs.append(contextDirPath)\n\n            let response = try run(arguments: buildArgs)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            return response.output\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuilderTarExportTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nextension TestCLIBuildBase {\n    class CLIBuilderTarExportTest: TestCLIBuildBase {\n        override init() throws {\n            try super.init()\n        }\n\n        deinit {\n            try? builderDelete(force: true)\n        }\n\n        @Test func testBuildExportTar() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"emptyFile\", content: .zeroFilled(size: 1))\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let exportPath = tempDir.appendingPathComponent(\"export.tar\")\n            let response = try run(arguments: [\n                \"build\",\n                \"-f\", tempDir.appendingPathComponent(\"Dockerfile\").path,\n                \"-o\", \"type=tar,dest=\\(exportPath.path)\",\n                tempDir.appendingPathComponent(\"context\").path,\n            ])\n\n            #expect(response.status == 0, \"build with tar export should succeed\")\n            #expect(FileManager.default.fileExists(atPath: exportPath.path), \"tar file should exist at \\(exportPath.path)\")\n            #expect(response.output.contains(\"Successfully exported to \\(exportPath.path)\"), \"should show export success message\")\n\n            let attributes = try FileManager.default.attributesOfItem(atPath: exportPath.path)\n            let fileSize = attributes[.size] as? Int ?? 0\n            #expect(fileSize > 0, \"exported tar file should not be empty\")\n        }\n\n        @Test func testBuildExportTarToDirectory() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN echo \"test content\" > /test.txt\n                \"\"\"\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n\n            let exportDir = tempDir.appendingPathComponent(\"exports\")\n            try FileManager.default.createDirectory(at: exportDir, withIntermediateDirectories: true)\n\n            let response = try run(arguments: [\n                \"build\",\n                \"-f\", tempDir.appendingPathComponent(\"Dockerfile\").path,\n                \"-o\", \"type=tar,dest=\\(exportDir.path)\",\n                tempDir.appendingPathComponent(\"context\").path,\n            ])\n\n            #expect(response.status == 0, \"build with tar export to directory should succeed\")\n\n            let expectedTar = exportDir.appendingPathComponent(\"out.tar\")\n            #expect(FileManager.default.fileExists(atPath: expectedTar.path), \"tar file should exist at \\(expectedTar.path)\")\n            #expect(response.output.contains(\"Successfully exported to \\(expectedTar.path)\"), \"should show export success message\")\n        }\n\n        @Test func testBuildExportTarMultipleRuns() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n                ADD testFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"testFile\", content: .data(\"test data\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let exportDir = tempDir.appendingPathComponent(\"exports\")\n            try FileManager.default.createDirectory(at: exportDir, withIntermediateDirectories: true)\n\n            // First build\n            var response = try run(arguments: [\n                \"build\",\n                \"-f\", tempDir.appendingPathComponent(\"Dockerfile\").path,\n                \"-o\", \"type=tar,dest=\\(exportDir.path)\",\n                tempDir.appendingPathComponent(\"context\").path,\n            ])\n            #expect(response.status == 0, \"first build should succeed\")\n\n            let firstTar = exportDir.appendingPathComponent(\"out.tar\")\n            #expect(FileManager.default.fileExists(atPath: firstTar.path), \"first tar should exist\")\n\n            // Second build - should create out.tar.1\n            response = try run(arguments: [\n                \"build\",\n                \"-f\", tempDir.appendingPathComponent(\"Dockerfile\").path,\n                \"-o\", \"type=tar,dest=\\(exportDir.path)\",\n                tempDir.appendingPathComponent(\"context\").path,\n            ])\n            #expect(response.status == 0, \"second build should succeed\")\n\n            let secondTar = exportDir.appendingPathComponent(\"out.tar.1\")\n            #expect(FileManager.default.fileExists(atPath: secondTar.path), \"second tar should exist at out.tar.1\")\n        }\n\n        @Test func testBuildExportTarInvalidDest() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n                \"\"\"\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n\n            let response = try run(arguments: [\n                \"build\",\n                \"-f\", tempDir.appendingPathComponent(\"Dockerfile\").path,\n                \"-o\", \"type=tar\",  // Missing dest parameter\n                tempDir.appendingPathComponent(\"context\").path,\n            ])\n\n            #expect(response.status != 0, \"build without dest should fail\")\n            #expect(response.error.contains(\"dest field is required\"), \"error should mention missing dest\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIBuilderTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOCI\nimport Foundation\nimport Testing\n\nextension TestCLIBuildBase {\n    class CLIBuilderTest: TestCLIBuildBase {\n        override init() throws {\n            try super.init()\n        }\n\n        deinit {\n            try? builderDelete(force: true)\n        }\n\n        @Test func testBuildDotFileSucceeds() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"emptyFile\", content: .zeroFilled(size: 1)),\n                .file(\".dockerignore\", content: .data(\".dockerignore\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/dot-file:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildFromPreviousStage() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20 AS layer1\n                RUN sh -c \"echo 'layer1' > /layer1.txt\"\n\n                FROM layer1\n                CMD [\"cat\", \"/layer1.txt\"]\n                \"\"\"\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"registry.local/from-previous-layer:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully build \\(imageName)\")\n        }\n\n        @Test func testBuildFromLocalImage() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"emptyFile\", content: .zeroFilled(size: 0)),\n                .file(\".dockerignore\", content: .data(\".dockerignore\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"local-only:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n\n            let newTempDir: URL = try createTempDir()\n            let newDockerfile: String =\n                \"\"\"\n                 FROM \\(imageName)\n                \"\"\"\n            let newContext: [FileSystemEntry] = []\n            try createContext(tempDir: newTempDir, dockerfile: newDockerfile, context: newContext)\n            let newImageName = \"from-local:\\(UUID().uuidString)\"\n            try self.build(tag: newImageName, tempDir: newTempDir)\n            #expect(try self.inspectImage(newImageName) == newImageName, \"expected to have successfully built \\(newImageName)\")\n        }\n\n        @Test func testBuildAddFromSpecialDirs() throws {\n            let tempDir = URL(filePath: \"/tmp/container/.clitests/\\(testSuite)/\\(testName)\")\n            try! FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [.file(\"emptyFile\", content: .zeroFilled(size: 1))]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/scratch-add-special-dir:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildScratchAdd() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [.file(\"emptyFile\", content: .zeroFilled(size: 1))]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/scratch-add:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildAddAll() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ADD . .\n\n                RUN cat emptyFile\n                RUN cat Test/testempty\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .directory(\"Test\"),\n                .file(\"Test/testempty\", content: .zeroFilled(size: 1)),\n                .file(\"emptyFile\", content: .zeroFilled(size: 1)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName: String = \"registry.local/add-all:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildArg() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                ARG TAG=unknown\n                FROM ghcr.io/linuxcontainers/alpine:${TAG}\n                \"\"\"\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName: String = \"registry.local/build-arg:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir, buildArgs: [\"TAG=3.20\"])\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildSecret() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN --mount=type=secret,id=ENV1 \\\n                    --mount=type=secret,id=env2 \\\n                    --mount=type=secret,id=env3 \\\n                    test xyyzzz = \"`cat /run/secrets/ENV1 /run/secrets/env2 /run/secrets/env3`\"\n                RUN --mount=type=secret,id=file \\\n                    awk 'BEGIN {for(i=0; i<17; i++) for(c=0; c<256; c++) printf(\"%c\", c)}' > /tmp/foo && \\\n                    cmp /tmp/foo /run/secrets/file && \\\n                    rm /tmp/foo\n                RUN --mount=type=secret,id=empty \\\n                    test \\\\! -e /run/secrets/file && \\\n                    test -e /run/secrets/empty && \\\n                    cmp /dev/null /run/secrets/empty\n                \"\"\"\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            setenv(\"ENV1\", \"x\", 1)\n            setenv(\"ENV_VAR\", \"yy\", 1)\n            setenv(\"env3\", \"zzz\", 1)\n            let testData = Data((0..<17).flatMap { _ in Array(0...255) })\n            let tempFile: URL = try createTempFile(suffix: \" _f,i=l.e+ \", contents: testData)\n            let tempFile2: URL = try createTempFile(suffix: \"file2\", contents: Data())\n            let imageName: String = \"registry.local/secrets:\\(UUID().uuidString)\"\n            try self.build(\n                tag: imageName, tempDir: tempDir,\n                otherArgs: [\n                    \"--secret\", \"id=ENV1\",\n                    \"--secret\", \"id=env2,env=ENV_VAR\",\n                    \"--secret\", \"id=env3,env=env3\",\n                    \"--secret\", \"id=file,src=\" + tempFile.path,\n                    \"--secret\", \"id=empty,src=\" + tempFile2.path,\n                ])\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildNetworkAccess() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ARG HTTP_PROXY\n                ARG HTTPS_PROXY\n                ARG NO_PROXY\n                ARG http_proxy\n                ARG https_proxy\n                ARG no_proxy\n                RUN apk add --no-cache curl\n                \"\"\"\n            try createContext(tempDir: tempDir, dockerfile: dockerfile)\n            let imageName = \"registry.local/build-network-access:\\(UUID().uuidString)\"\n\n            var buildArgs: [String] = []\n            for key in [\"HTTP_PROXY\", \"HTTPS_PROXY\", \"NO_PROXY\", \"http_proxy\", \"https_proxy\", \"no_proxy\"] {\n                if let value = ProcessInfo.processInfo.environment[key] {\n                    buildArgs.append(\"\\(key)=\\(value)\")\n                }\n            }\n            try self.build(tag: imageName, tempDir: tempDir, buildArgs: buildArgs)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildDockerfileKeywords() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                # stage 1 Meta ARG\n                ARG TAG=3.20\n                FROM ghcr.io/linuxcontainers/alpine:${TAG}\n\n                # stage 2 RUN\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN echo \"Hello, World!\" > /hello.txt\n\n                # stage 3 - RUN []\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN [\"sh\", \"-c\", \"echo 'Exec form' > /exec.txt\"]\n\n                # stage 4 - CMD\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                CMD [\"echo\", \"Exec default\"]\n\n                # stage 5 - CMD []\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                CMD [\"echo\", \"Exec'ing\"]\n\n                #stage 6 - LABEL\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                LABEL version=\"1.0\" description=\"Test image\"\n\n                # stage 7 - EXPOSE\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                EXPOSE 8080\n\n                # stage 8 - ENV\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ENV MY_ENV=hello\n                RUN echo $MY_ENV > /env.txt\n\n                # stage 9 - ADD\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ADD emptyFile /\n\n                # stage 10 - COPY\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                COPY toCopy /toCopy\n\n                # stage 11 - ENTRYPOINT\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ENTRYPOINT [\"echo\", \"entrypoint!\"]\n\n                # stage 12 - VOLUME\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                VOLUME /data\n\n                # stage 13 - USER\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN adduser -D myuser\n                USER myuser\n                CMD whoami\n\n                # stage 14 - WORKDIR\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                RUN pwd > /pwd.out\n\n                # stage 15 - ARG\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ARG MY_VAR=default\n                RUN echo $MY_VAR > /var.out\n\n                # stage 16 - ONBUILD\n                # FROM ghcr.io/linuxcontainers/alpine:3.20\n                # ONBUILD RUN echo \"onbuild triggered\" > /onbuild.out\n\n                # stage 17 - STOPSIGNAL\n                # FROM ghcr.io/linuxcontainers/alpine:3.20\n                # STOPSIGNAL SIGTERM\n\n                # stage 18 - HEALTHCHECK\n                # FROM ghcr.io/linuxcontainers/alpine:3.20\n                # HEALTHCHECK CMD echo \"healthy\" || exit 1\n\n                # stage 19 - SHELL\n                # FROM ghcr.io/linuxcontainers/alpine:3.20\n                # SHELL [\"/bin/sh\", \"-c\"]\n                # RUN echo $0 > /shell.txt\n                \"\"\"\n\n            let context: [FileSystemEntry] = [\n                .file(\"emptyFile\", content: .zeroFilled(size: 1)),\n                .file(\"toCopy\", content: .zeroFilled(size: 1)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let imageName = \"registry.local/dockerfile-keywords:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildSymlink() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                # Test 1: Test basic symlinking\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ADD Test1Source Test1Source\n                ADD Test1Source2 Test1Source2\n\n                RUN cat Test1Source2/test.yaml\n\n                # Test2: Test symlinks in nested directories\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ADD Test2Source Test2Source\n                ADD Test2Source2 Test2Source2\n\n                RUN cat Test2Source2/Test/test.txt\n\n                # Test 3: Test symlinks to directories work\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ADD Test3Source Test3Source\n                ADD Test3Source2 Test3Source2\n\n                RUN cat Test3Source2/Dest/test.txt\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                // test 1\n                .directory(\"Test1Source\"),\n                .directory(\"Test1Source2\"),\n                .file(\"Test1Source/test.yaml\", content: .zeroFilled(size: 1)),\n                .symbolicLink(\"Test1Source2/test.yaml\", target: \"Test1Source/test.yaml\"),\n\n                // test 2\n                .directory(\"Test2Source\"),\n                .directory(\"Test2Source2\"),\n                .file(\"Test2Source/Test/Test/test.yaml\", content: .zeroFilled(size: 1)),\n                .symbolicLink(\"Test2Source2/Test/test.yaml\", target: \"Test2Source/Test/Test/test.yaml\"),\n\n                // test 3\n                .directory(\"Test3Source/Source\"),\n                .directory(\"Test3Source2\"),\n                .file(\"Test3Source/Source/test.txt\", content: .zeroFilled(size: 1)),\n                .symbolicLink(\"Test3Source2/Dest\", target: \"Test3Source/Source\"),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/build-symlinks:\\(UUID().uuidString)\"\n\n            #expect(throws: Never.self) {\n                try self.build(tag: imageName, tempDir: tempDir)\n            }\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildAndRun() throws {\n            let name: String = \"test-build-and-run\"\n\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                RUN echo \"foobar\" > /file\n                \"\"\"\n            let context: [FileSystemEntry] = []\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"\\(name):latest\"\n            let containerName = \"\\(name)-container\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n            // Check if the image we built is actually in the image store, and can be used.\n            try self.doLongRun(name: containerName, image: imageName)\n            defer {\n                try? self.doStop(name: containerName)\n            }\n            var output = try doExec(name: containerName, cmd: [\"cat\", \"/file\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            let expected = \"foobar\"\n            try self.doStop(name: containerName)\n            #expect(output == expected, \"expected file contents to be \\(expected), instead got \\(output)\")\n        }\n\n        @Test func testBuildDifferentPaths() throws {\n            let buildContextDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                RUN ls ./\n                COPY . /root\n\n                RUN cat /root/Test/test.txt\n                \"\"\"\n            let buildContext: [FileSystemEntry] = [\n                .directory(\".git\"),\n                .file(\".git/FETCH\", content: .zeroFilled(size: 1)),\n                .directory(\"Test\"),\n                .file(\"Test/test.txt\", content: .zeroFilled(size: 1)),\n            ]\n            try createContext(tempDir: buildContextDir, dockerfile: dockerfile, context: buildContext)\n\n            let imageName = \"registry.local/build-diff-context:\\(UUID().uuidString)\"\n            #expect(throws: Never.self) {\n                try self.build(tags: [imageName], tempDir: buildContextDir)\n            }\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildMultiArch() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                ADD . .\n\n                RUN cat emptyFile\n                RUN cat Test/testempty\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .directory(\"Test\"),\n                .file(\"Test/testempty\", content: .zeroFilled(size: 1)),\n                .file(\"emptyFile\", content: .zeroFilled(size: 1)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName: String = \"registry.local/multi-arch:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir, otherArgs: [\"--arch\", \"amd64,arm64\"])\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n\n            let output = try doInspectImages(image: imageName)\n            #expect(output.count == 1, \"expected a single image inspect output, got \\(output)\")\n\n            let expected = Set([\n                Platform(arch: \"amd64\", os: \"linux\", variant: nil),\n                Platform(arch: \"arm64\", os: \"linux\", variant: nil),\n            ])\n            let actual = Set(\n                output[0].variants.map { v in\n                    Platform(arch: v.platform.architecture, os: v.platform.os, variant: nil)\n                })\n            #expect(\n                actual == expected,\n                \"expected platforms \\(expected), got \\(actual)\"\n            )\n        }\n\n        @Test func testBuildMultipleTags() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile: String =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [.file(\"emptyFile\", content: .zeroFilled(size: 1))]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let uuid = UUID().uuidString\n            let tag1 = \"registry.local/multi-tag-test:\\(uuid)\"\n            let tag2 = \"registry.local/multi-tag-test:latest\"\n            let tag3 = \"registry.local/multi-tag-test:v1.0.0\"\n\n            try self.build(tags: [tag1, tag2, tag3], tempDir: tempDir)\n\n            // Verify all three tags exist and point to the same image\n            #expect(try self.inspectImage(tag1) == tag1, \"expected to have successfully built \\(tag1)\")\n            #expect(try self.inspectImage(tag2) == tag2, \"expected to have successfully built \\(tag2)\")\n            #expect(try self.inspectImage(tag3) == tag3, \"expected to have successfully built \\(tag3)\")\n        }\n\n        @Test func testBuildAfterContextChange() throws {\n            let name = \"test-build-context-change\"\n            let tempDir: URL = try createTempDir()\n\n            // Create initial context with file \"foo\" containing \"initial\"\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                COPY foo /foo\n                COPY bar /bar\n                \"\"\"\n            let initialContent = \"initial\".data(using: .utf8)!\n            let context: [FileSystemEntry] = [\n                .file(\"foo\", content: .data(Data((0..<4 * 1024 * 1024).map { UInt8($0 % 256) }))),\n                .file(\"bar\", content: .data(initialContent)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            // Build first image\n            let imageName1 = \"\\(name):v1\"\n            let containerName1 = \"\\(name)-container-v1\"\n            try self.build(tag: imageName1, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName1) == imageName1, \"expected to have successfully built \\(imageName1)\")\n\n            // Run container and verify content is \"initial\"\n            try self.doLongRun(name: containerName1, image: imageName1)\n            defer {\n                try? self.doStop(name: containerName1)\n            }\n            var output = try doExec(name: containerName1, cmd: [\"cat\", \"/bar\"])\n            #expect(output == \"initial\", \"expected file contents to be 'initial', instead got '\\(output)'\")\n\n            // Update the file \"foo\" to contain \"updated\"\n            let updatedContent = \"updated\".data(using: .utf8)!\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let barPath = contextDir.appendingPathComponent(\"bar\")\n            try updatedContent.write(to: barPath, options: .atomic)\n\n            // Build second image\n            let imageName2 = \"\\(name):v2\"\n            let containerName2 = \"\\(name)-container-v2\"\n            try self.build(tag: imageName2, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName2) == imageName2, \"expected to have successfully built \\(imageName2)\")\n\n            // Run container and verify content is \"updated\"\n            try self.doLongRun(name: containerName2, image: imageName2)\n            defer {\n                try? self.doStop(name: containerName2)\n            }\n            output = try doExec(name: containerName2, cmd: [\"cat\", \"/bar\"])\n            #expect(output == \"updated\", \"expected file contents to be 'updated', instead got '\\(output)'\")\n        }\n\n        @Test func testBuildWithDockerfileFromStdin() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM scratch\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [.file(\"emptyFile\", content: .zeroFilled(size: 1))]\n            try createContext(tempDir: tempDir, dockerfile: \"\", context: context)\n            let imageName = \"registry.local/stdin-file:\\(UUID().uuidString)\"\n            try buildWithStdin(tags: [imageName], tempContext: tempDir, dockerfileContents: dockerfile)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testLowercaseDockerfile() throws {\n            // Test 1: COPY with uppercase\n            let tempDir1: URL = try createTempDir()\n            let dockerfile1 =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                COPY . /app\n                RUN test -f /app/testfile.txt\n                \"\"\"\n            let context1: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"test\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir1, dockerfile: dockerfile1, context: context1)\n            let imageName1 = \"registry.local/copy-uppercase:\\(UUID().uuidString)\"\n            try self.build(tag: imageName1, tempDir: tempDir1)\n            #expect(try self.inspectImage(imageName1) == imageName1, \"expected COPY to work\")\n\n            // Test 2: copy with lowercase\n            let tempDir2: URL = try createTempDir()\n            let dockerfile2 =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                copy . /app\n                RUN test -f /app/testfile.txt\n                \"\"\"\n            let context2: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"test\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir2, dockerfile: dockerfile2, context: context2)\n            let imageName2 = \"registry.local/copy-lowercase:\\(UUID().uuidString)\"\n            try self.build(tag: imageName2, tempDir: tempDir2)\n            #expect(try self.inspectImage(imageName2) == imageName2, \"expected copy to work\")\n\n            // Test 3: ADD with uppercase\n            let tempDir3: URL = try createTempDir()\n            let dockerfile3 =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                ADD . /app\n                RUN test -f /app/testfile.txt\n                \"\"\"\n            let context3: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"test\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir3, dockerfile: dockerfile3, context: context3)\n            let imageName3 = \"registry.local/add-uppercase:\\(UUID().uuidString)\"\n            try self.build(tag: imageName3, tempDir: tempDir3)\n            #expect(try self.inspectImage(imageName3) == imageName3, \"expected ADD to work\")\n\n            // Test 4: add with lowercase\n            let tempDir4: URL = try createTempDir()\n            let dockerfile4 =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                add . /app\n                RUN test -f /app/testfile.txt\n                \"\"\"\n            let context4: [FileSystemEntry] = [\n                .file(\"testfile.txt\", content: .data(\"test\".data(using: .utf8)!))\n            ]\n            try createContext(tempDir: tempDir4, dockerfile: dockerfile4, context: context4)\n            let imageName4 = \"registry.local/add-lowercase:\\(UUID().uuidString)\"\n            try self.build(tag: imageName4, tempDir: tempDir4)\n            #expect(try self.inspectImage(imageName4) == imageName4, \"expected add to work\")\n        }\n\n        @Test func testRunWithBindMount() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                # Use bind mount to access build context during RUN\n                RUN --mount=type=bind,source=.,target=/mnt/context \\\n                    set -e; \\\n                    echo \"Checking files in bind mount...\"; \\\n                    ls -la /mnt/context/; \\\n                    \\\n                    echo \"Verifying files are accessible in mount...\"; \\\n                    if [ ! -f /mnt/context/app.py ]; then \\\n                        echo \"ERROR: app.py should be in bind mount!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    if [ ! -f /mnt/context/config.yaml ]; then \\\n                        echo \"ERROR: config.yaml should be in bind mount!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    \\\n                    echo \"RUN --mount bind check passed!\"; \\\n                    cp /mnt/context/app.py /app.py\n\n                RUN cat /app.py\n                \"\"\"\n\n            let context: [FileSystemEntry] = [\n                .file(\"app.py\", content: .data(\"print('Hello from bind mount')\".data(using: .utf8)!)),\n                .file(\"config.yaml\", content: .data(\"key: value\".data(using: .utf8)!)),\n            ]\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/bind-mount-test:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        @Test func testBuildDockerIgnore() throws {\n            let tempDir: URL = try createTempDir()\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n\n                # Copy all files - should respect .dockerignore\n                COPY . /app\n\n                # Verify specific files are excluded\n                RUN set -e; \\\n                    echo \"Checking specific file exclusion...\"; \\\n                    if [ -f /app/secret.txt ]; then \\\n                        echo \"ERROR: secret.txt should be excluded!\"; \\\n                        exit 1; \\\n                    fi\n\n                # Verify wildcard *.log files are excluded\n                RUN set -e; \\\n                    echo \"Checking *.log exclusion...\"; \\\n                    if [ -f /app/debug.log ]; then \\\n                        echo \"ERROR: debug.log should be excluded by *.log pattern!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    if ls /app/logs/*.log 2>/dev/null; then \\\n                        echo \"ERROR: logs/*.log files should be excluded!\"; \\\n                        exit 1; \\\n                    fi\n\n                # Verify exception pattern (!important.log) works\n                RUN set -e; \\\n                    echo \"Checking exception pattern...\"; \\\n                    if [ ! -f /app/important.log ]; then \\\n                        echo \"ERROR: important.log should be included (exception with !)\"; \\\n                        exit 1; \\\n                    fi\n\n                # Verify *.tmp files are excluded\n                RUN set -e; \\\n                    echo \"Checking *.tmp exclusion...\"; \\\n                    if find /app -name \"*.tmp\" | grep .; then \\\n                        echo \"ERROR: .tmp files should be excluded!\"; \\\n                        exit 1; \\\n                    fi\n\n                # Verify directories are excluded\n                RUN set -e; \\\n                    echo \"Checking directory exclusion...\"; \\\n                    if [ -d /app/temp ]; then \\\n                        echo \"ERROR: temp/ directory should be excluded!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    if [ -d /app/node_modules ]; then \\\n                        echo \"ERROR: node_modules/ should be excluded!\"; \\\n                        exit 1; \\\n                    fi\n\n                # Verify included files ARE present\n                RUN set -e; \\\n                    echo \"Checking included files...\"; \\\n                    if [ ! -f /app/main.go ]; then \\\n                        echo \"ERROR: main.go should be included!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    if [ ! -f /app/README.md ]; then \\\n                        echo \"ERROR: README.md should be included!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    if [ ! -f /app/src/app.go ]; then \\\n                        echo \"ERROR: src/app.go should be included!\"; \\\n                        exit 1; \\\n                    fi; \\\n                    echo \"All .dockerignore checks passed!\"\n                \"\"\"\n\n            let dockerignore =\n                \"\"\"\n                # Exclude specific files\n                secret.txt\n\n                # Exclude all log files\n                *.log\n                **/*.log\n\n                # But make an exception for important.log\n                !important.log\n\n                # Exclude all temporary files\n                *.tmp\n                **/*.tmp\n\n                # Exclude directories\n                temp/\n                node_modules/\n                \"\"\"\n\n            let context: [FileSystemEntry] = [\n                .file(\".dockerignore\", content: .data(dockerignore.data(using: .utf8)!)),\n                .file(\"secret.txt\", content: .data(\"secret content\".data(using: .utf8)!)),\n                .file(\"debug.log\", content: .data(\"debug log content\".data(using: .utf8)!)),\n                .file(\"important.log\", content: .data(\"important log content\".data(using: .utf8)!)),\n                .file(\"cache.tmp\", content: .data(\"cache\".data(using: .utf8)!)),\n                .file(\"main.go\", content: .data(\"package main\".data(using: .utf8)!)),\n                .file(\"README.md\", content: .data(\"# README\".data(using: .utf8)!)),\n                .directory(\"temp\"),\n                .file(\"temp/cache.tmp\", content: .data(\"temp cache\".data(using: .utf8)!)),\n                .directory(\"logs\"),\n                .file(\"logs/app.log\", content: .data(\"app log\".data(using: .utf8)!)),\n                .directory(\"node_modules\"),\n                .file(\"node_modules/package.json\", content: .data(\"{}\".data(using: .utf8)!)),\n                .directory(\"src\"),\n                .file(\"src/app.go\", content: .data(\"package src\".data(using: .utf8)!)),\n                .file(\"src/test.tmp\", content: .data(\"temp\".data(using: .utf8)!)),\n            ]\n\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n            let imageName = \"registry.local/dockerignore-test:\\(UUID().uuidString)\"\n            try self.build(tag: imageName, tempDir: tempDir)\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n\n        // Test 1: Basic .dockerignore\n        @Test func testDockerIgnoreBasic() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\"included.txt\", content: .data(\"This file should be included in the build context.\\n\".data(using: .utf8)!)),\n                .file(\"ignored.txt\", content: .data(\"This file should be ignored by .dockerignore.\\n\".data(using: .utf8)!)),\n                .file(\".dockerignore\", content: .data(\"ignored.txt\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let dockerfilePath = contextDir.appendingPathComponent(\"Dockerfile\")\n            let imageName = \"registry.local/dockerignore-basic:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", dockerfilePath.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-basic-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let includedResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/included.txt\"])\n            #expect(includedResult.status == 0, \"included.txt should be present in the image\")\n\n            let ignoredResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/ignored.txt\"])\n            #expect(ignoredResult.status != 0, \"ignored.txt should NOT be present in the image\")\n        }\n\n        // Test 2: Dockerfile-specific ignore file (Dockerfile.dockerignore takes precedence over .dockerignore)\n        @Test func testDockerIgnoreDockerfileSpecific() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // .dockerignore ignores general.txt; Dockerfile.dockerignore ignores specific.txt.\n            // When both exist, Dockerfile.dockerignore takes precedence, so general.txt is included.\n            // Dockerfile and its .dockerignore must be co-located; here both live in the context root.\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\".dockerignore\", content: .data(\"general.txt\\n\".data(using: .utf8)!)),\n                .file(\"Dockerfile.dockerignore\", content: .data(\"specific.txt\\n\".data(using: .utf8)!)),\n                .file(\"general.txt\", content: .data(\"This file should be included (Dockerfile.dockerignore takes precedence over .dockerignore).\\n\".data(using: .utf8)!)),\n                .file(\"specific.txt\", content: .data(\"This file should be ignored by Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let dockerfilePath = contextDir.appendingPathComponent(\"Dockerfile\")\n            let imageName = \"registry.local/dockerignore-specific:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", dockerfilePath.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-specific-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let specificResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/specific.txt\"])\n            #expect(specificResult.status != 0, \"specific.txt should NOT be present (ignored by Dockerfile.dockerignore)\")\n\n            let generalResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/general.txt\"])\n            #expect(generalResult.status == 0, \"general.txt should be present (only in .dockerignore, not Dockerfile.dockerignore)\")\n\n            let listResult = try run(arguments: [\"exec\", containerName, \"ls\", \"-a\"])\n            let listFiles = listResult.output.components(separatedBy: \"\\n\").filter { !$0.isEmpty && $0 != \".\" && $0 != \"..\" }\n            #expect(Set(listFiles) == Set([\"Dockerfile\", \".dockerignore\", \"Dockerfile.dockerignore\", \"general.txt\"]), \"temporary directory must not be detected\")\n        }\n\n        @Test func testDockerIgnoreOutsideContext() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // .dockerignore ignores general.txt; Dockerfile.dockerignore ignores specific.txt.\n            // When both exist, Dockerfile.dockerignore takes precedence, so general.txt is included.\n            // Dockerfile and its .dockerignore must be co-located; here both live in the context root.\n            let context: [FileSystemEntry] = [\n                .file(\".dockerignore\", content: .data(\"general.txt\\n\".data(using: .utf8)!)),\n                .file(\"general.txt\", content: .data(\"This file should be included (Dockerfile.dockerignore takes precedence over .dockerignore).\\n\".data(using: .utf8)!)),\n                .file(\"specific.txt\", content: .data(\"This file should be ignored by Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let dockerignore = \"specific.txt\\n\".data(using: .utf8)!\n            try dockerignore.write(to: tempDir.appendingPathComponent(\"Dockerfile.dockerignore\"), options: .atomic)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let dockerfilePath = tempDir.appendingPathComponent(\"Dockerfile\")\n            let imageName = \"registry.local/dockerignore-specific:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", dockerfilePath.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-specific-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let specificResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/specific.txt\"])\n            #expect(specificResult.status != 0, \"specific.txt should NOT be present (ignored by Dockerfile.dockerignore)\")\n\n            let generalResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/general.txt\"])\n            #expect(generalResult.status == 0, \"general.txt should be present (only in .dockerignore, not Dockerfile.dockerignore)\")\n        }\n\n        // Test 5: Build succeeds when Dockerfile is listed in .dockerignore\n        @Test func testDockerIgnoreIgnoredDockerfile() async throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // Dockerfile is listed in .dockerignore but build must still succeed.\n            // Dockerfile lives in the context root so the ignore rule applies to it.\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\".dockerignore\", content: .data(\"Dockerfile\\n.dockerignore\\n\".data(using: .utf8)!)),\n                .file(\"test.txt\", content: .data(\"This file should be included even though Dockerfile is ignored.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let dockerfilePath = contextDir.appendingPathComponent(\"Dockerfile\")\n            let imageName = \"registry.local/dockerignore-ignored-dockerfile:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", dockerfilePath.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-ignored-dockerfile\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let dockerfileResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/Dockerfile\"])\n            #expect(dockerfileResult.status != 0, \"Dockerfile should NOT be present in the image\")\n\n            let dockerignoreResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/.dockerignore\"])\n            #expect(dockerignoreResult.status != 0, \".dockerignore should NOT be present in the image\")\n\n            let testFileResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/test.txt\"])\n            #expect(testFileResult.status == 0, \"test.txt should be present in the image\")\n        }\n\n        // Test 8: Dockerfile in nested subdirectory; Dockerfile.dockerignore next to it takes precedence over root .dockerignore\n        @Test func testDockerIgnoreSubdirDockerfile() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // Root .dockerignore ignores included.txt; nested Dockerfile.dockerignore ignores secret.txt\n            // When Dockerfile is in nested/project/, Dockerfile.dockerignore next to it takes precedence\n            let context: [FileSystemEntry] = [\n                .file(\".dockerignore\", content: .data(\"included.txt\\n\".data(using: .utf8)!)),\n                .file(\"included.txt\", content: .data(\"This file should be included (Dockerfile.dockerignore takes precedence).\\n\".data(using: .utf8)!)),\n                .file(\"secret.txt\", content: .data(\"This file should be ignored by Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n                .file(\"nested/secret.txt\", content: .data(\"This file should be ignored by Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n                .file(\"nested/project/Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\"nested/project/Dockerfile.dockerignore\", content: .data(\"secret.txt\\n**/secret.txt\\n\".data(using: .utf8)!)),\n                .file(\"nested/project/config.txt\", content: .data(\"This config file should be included.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let nestedDockerfile = contextDir.appendingPathComponent(\"nested/project/Dockerfile\")\n            let imageName = \"registry.local/dockerignore-subdir:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", nestedDockerfile.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-subdir-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let includedResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/included.txt\"])\n            #expect(includedResult.status == 0, \"included.txt should be present (Dockerfile.dockerignore takes precedence over .dockerignore)\")\n\n            let secretResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/secret.txt\"])\n            #expect(secretResult.status != 0, \"secret.txt should NOT be present (ignored by Dockerfile.dockerignore)\")\n\n            let nestedSecretResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/nested/secret.txt\"])\n            #expect(nestedSecretResult.status != 0, \"nested/secret.txt should NOT be present (ignored by Dockerfile.dockerignore)\")\n\n            let configResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/nested/project/config.txt\"])\n            #expect(configResult.status == 0, \"nested/project/config.txt should be present\")\n        }\n\n        // Test 9: Custom-named Dockerfile (app1.Dockerfile) uses app1.Dockerfile.dockerignore\n        @Test func testDockerIgnoreCustomDockerfileName() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // .dockerignore ignores generic.txt; app1.Dockerfile.dockerignore ignores app1-specific.txt\n            // When building with -f app1.Dockerfile, app1.Dockerfile.dockerignore takes precedence\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\".dockerignore\", content: .data(\"generic.txt\\n\".data(using: .utf8)!)),\n                .file(\"app1.Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\"app1.Dockerfile.dockerignore\", content: .data(\"app1-specific.txt\\n\".data(using: .utf8)!)),\n                .file(\"app1-specific.txt\", content: .data(\"This file should be ignored by app1.Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n                .file(\"generic.txt\", content: .data(\"This file should be included (only in .dockerignore, not app1.Dockerfile.dockerignore).\\n\".data(using: .utf8)!)),\n                .file(\"included.txt\", content: .data(\"This file should always be included.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: \"\", context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let customDockerfile = contextDir.appendingPathComponent(\"app1.Dockerfile\")\n            let imageName = \"registry.local/dockerignore-custom-name:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", customDockerfile.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-custom-name-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let app1SpecificResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/app1-specific.txt\"])\n            #expect(app1SpecificResult.status != 0, \"app1-specific.txt should NOT be present (ignored by app1.Dockerfile.dockerignore)\")\n\n            let genericResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/generic.txt\"])\n            #expect(genericResult.status == 0, \"generic.txt should be present (only in .dockerignore, not app1.Dockerfile.dockerignore)\")\n\n            let includedResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/included.txt\"])\n            #expect(includedResult.status == 0, \"included.txt should be present\")\n        }\n\n        // Test 10: Custom-named Dockerfile in subdirectory uses its co-located .dockerignore\n        @Test func testDockerIgnoreCustomNameSubdir() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            // Root .dockerignore ignores from-root-ignore.txt\n            // nested/project/app2.Dockerfile.dockerignore ignores from-app2-ignore.txt\n            // When building with -f nested/project/app2.Dockerfile, the nested ignore takes precedence\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\".dockerignore\", content: .data(\"from-root-ignore.txt\\n\".data(using: .utf8)!)),\n                .file(\"from-root-ignore.txt\", content: .data(\"This file should be included (only in .dockerignore, not app2.Dockerfile.dockerignore).\\n\".data(using: .utf8)!)),\n                .file(\"from-app2-ignore.txt\", content: .data(\"This file should be ignored by app2.Dockerfile.dockerignore.\\n\".data(using: .utf8)!)),\n                .file(\"always-included.txt\", content: .data(\"This file should always be included.\\n\".data(using: .utf8)!)),\n                .file(\"nested/project/app2.Dockerfile\", content: .data(dockerfile.data(using: .utf8)!)),\n                .file(\"nested/project/app2.Dockerfile.dockerignore\", content: .data(\"from-app2-ignore.txt\\n\".data(using: .utf8)!)),\n                .file(\"nested/project/config.yaml\", content: .data(\"Config file in project directory.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: \"\", context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let customDockerfile = contextDir.appendingPathComponent(\"nested/project/app2.Dockerfile\")\n            let imageName = \"registry.local/dockerignore-custom-subdir:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", customDockerfile.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-custom-subdir-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let app2IgnoreResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/from-app2-ignore.txt\"])\n            #expect(app2IgnoreResult.status != 0, \"from-app2-ignore.txt should NOT be present (ignored by app2.Dockerfile.dockerignore)\")\n\n            let rootIgnoreResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/from-root-ignore.txt\"])\n            #expect(rootIgnoreResult.status == 0, \"from-root-ignore.txt should be present (only in .dockerignore, not app2.Dockerfile.dockerignore)\")\n\n            let alwaysIncludedResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/always-included.txt\"])\n            #expect(alwaysIncludedResult.status == 0, \"always-included.txt should be present\")\n\n            let configResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/nested/project/config.yaml\"])\n            #expect(configResult.status == 0, \"nested/project/config.yaml should be present\")\n        }\n\n        // Test 11: app.Dockerfile coexists with Dockerfile; app.Dockerfile.dockerignore is used, not Dockerfile.dockerignore\n        @Test func testDockerIgnoreCoexistingDockerfiles() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let appDockerfile =\n                \"\"\"\n                FROM ghcr.io/linuxcontainers/alpine:3.20\n                WORKDIR /app\n                COPY . .\n                \"\"\"\n            let context: [FileSystemEntry] = [\n                .file(\"Dockerfile\", content: .data(\"FROM ghcr.io/linuxcontainers/alpine:3.20\\nWORKDIR /app\\nCOPY . .\\n\".data(using: .utf8)!)),\n                .file(\"Dockerfile.dockerignore\", content: .data(\"dockerfile-specific.txt\\n\".data(using: .utf8)!)),\n                .file(\"app.Dockerfile\", content: .data(appDockerfile.data(using: .utf8)!)),\n                .file(\"app.Dockerfile.dockerignore\", content: .data(\"app-specific.txt\\n\".data(using: .utf8)!)),\n                .file(\n                    \"dockerfile-specific.txt\", content: .data(\"This file should NOT be copied when using Dockerfile, but SHOULD when using app.Dockerfile.\\n\".data(using: .utf8)!)),\n                .file(\"app-specific.txt\", content: .data(\"This file should NOT be copied (ignored by app.Dockerfile.dockerignore).\\n\".data(using: .utf8)!)),\n                .file(\"included.txt\", content: .data(\"This file should be copied.\\n\".data(using: .utf8)!)),\n            ]\n            try createContext(tempDir: tempDir, dockerfile: \"\", context: context)\n\n            let contextDir = tempDir.appendingPathComponent(\"context\")\n            let appDockerfilePath = contextDir.appendingPathComponent(\"app.Dockerfile\")\n            let imageName = \"registry.local/dockerignore-coexisting:\\(UUID().uuidString)\"\n            let args = [\"build\", \"-f\", appDockerfilePath.path, \"-t\", imageName, contextDir.path]\n            let response = try run(arguments: args)\n            if response.status != 0 {\n                throw CLIError.executionFailed(\"build failed: stdout=\\(response.output) stderr=\\(response.error)\")\n            }\n\n            let containerName = \"dockerignore-coexisting-\\(UUID().uuidString)\"\n            try self.doLongRun(name: containerName, image: imageName)\n            defer { try? self.doStop(name: containerName) }\n\n            let appSpecificResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/app-specific.txt\"])\n            #expect(appSpecificResult.status != 0, \"app-specific.txt should NOT be present (ignored by app.Dockerfile.dockerignore)\")\n\n            let dockerfileSpecificResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/dockerfile-specific.txt\"])\n            #expect(dockerfileSpecificResult.status == 0, \"dockerfile-specific.txt should be present (Dockerfile.dockerignore was not used)\")\n\n            let includedResult = try run(arguments: [\"exec\", containerName, \"test\", \"-f\", \"/app/included.txt\"])\n            #expect(includedResult.status == 0, \"included.txt should be present\")\n        }\n\n        @Test func testNonExistingDockerfile() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let imageName = \"registry.local/non-existing-dockerfile:\\(UUID().uuidString)\"\n\n            var args = [\"build\", \"-f\", \"non-existing-path\", \"-t\", imageName, tempDir.path]\n            var response = try run(arguments: args)\n\n            #expect(response.status != 0)\n\n            args = [\"build\", \"-t\", imageName, tempDir.path]\n            response = try run(arguments: args)\n\n            #expect(response.status != 0)\n        }\n\n        @Test func testBuildNoCachePullLatestImage() throws {\n            let tempDir: URL = try createTempDir()\n            defer {\n                try! FileManager.default.removeItem(at: tempDir)\n            }\n\n            let dockerfile =\n                \"\"\"\n                FROM \\(alpine)\n\n                ADD emptyFile /\n                \"\"\"\n            let context: [FileSystemEntry] = [.file(\"emptyFile\", content: .zeroFilled(size: 1))]\n            try createContext(tempDir: tempDir, dockerfile: dockerfile, context: context)\n\n            let imageName = \"registry.local/no-cache-pull:\\(UUID().uuidString)\"\n            try self.build(\n                tags: [imageName],\n                tempDir: tempDir,\n                otherArgs: [\"--pull\", \"--no-cache\"]\n            )\n            #expect(try self.inspectImage(imageName) == imageName, \"expected to have successfully built \\(imageName)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/CLIRunBase.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\nimport Testing\n\nclass TestCLIRunBase: CLITest {\n    var terminal: Terminal!\n    var containerName: String = UUID().uuidString\n\n    var containerImage: String {\n        fatalError(\"Subclasses must override this property\")\n    }\n\n    var interactive: Bool {\n        false\n    }\n\n    var tty: Bool {\n        false\n    }\n\n    var entrypoint: String? {\n        nil\n    }\n\n    var command: [String]? {\n        nil\n    }\n\n    var progress: String {\n        \"ansi\"\n    }\n\n    override init() throws {\n        try super.init()\n        do {\n            terminal = try containerStart(self.containerName)\n            try waitForContainerRunning(self.containerName)\n        } catch {\n            throw CLIError.containerRunFailed(\"failed to setup container \\(error)\")\n        }\n    }\n\n    func containerRun(stdin: [String], findMessage: String) async throws -> Bool {\n        let stdout = FileHandle(fileDescriptor: terminal.handle.fileDescriptor, closeOnDealloc: false)\n        let stdoutListenTask = Task {\n            for try await line in stdout.bytes.lines {\n                if line.contains(findMessage) && !line.contains(\"echo\") {\n                    return true\n                }\n            }\n            return false\n        }\n\n        let timeoutTask = Task {\n            try await Task.sleep(nanoseconds: 5 * 1_000_000_000)\n            stdoutListenTask.cancel()\n        }\n\n        do {\n            try self.exec(commands: stdin)\n            let found = try await stdoutListenTask.value\n            timeoutTask.cancel()\n            return found\n        } catch is CancellationError {\n            throw CLIError.executionFailed(\"timeout hit\")\n        } catch {\n            throw error\n        }\n    }\n\n    func exec(commands: [String]) throws {\n        let stdin = FileHandle(fileDescriptor: terminal.handle.fileDescriptor, closeOnDealloc: false)\n        try commands.forEach { cmd in\n            let cmdLine = cmd.appending(\"\\n\")\n            guard let cmdNormalized = cmdLine.data(using: .ascii) else {\n                throw CLIError.invalidInput(\"shell command \\(cmd) is invalid\")\n            }\n            try stdin.write(contentsOf: cmdNormalized)\n        }\n        try stdin.synchronize()\n    }\n\n    func containerStart(_ name: String) throws -> Terminal {\n        if name.count == 0 {\n            throw CLIError.invalidInput(\"container name cannot be empty\")\n        }\n\n        var arguments = [\n            \"run\",\n            \"--rm\",\n            \"--name\",\n            name,\n        ]\n\n        if interactive && tty {\n            arguments.append(\"-it\")\n        } else {\n            if interactive { arguments.append(\"-i\") }\n            if tty { arguments.append(\"-t\") }\n        }\n\n        arguments.append(\"--progress\")\n        arguments.append(progress)\n\n        if let entrypoint = entrypoint {\n            arguments += [\"--entrypoint\", entrypoint]\n        }\n\n        arguments.append(containerImage)\n\n        if let command = command {\n            arguments += command\n        }\n        return try runInteractive(arguments: arguments)\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Build/TestCLITermIO.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationOS\nimport Foundation\nimport Testing\n\nextension TestCLIRunBase {\n    class TestCLITermIO: TestCLIRunBase {\n        override var containerImage: String {\n            \"ghcr.io/linuxcontainers/alpine:3.20\"\n        }\n\n        override var interactive: Bool {\n            true\n        }\n\n        override var tty: Bool {\n            true\n        }\n\n        override var command: [String]? {\n            [\"/bin/sh\"]\n        }\n\n        override var progress: String {\n            \"none\"\n        }\n\n        @Test func testTermIODoesNotPanic() async throws {\n            let uniqMessage = UUID().uuidString\n            let stdin: [String] = [\n                \"echo \\(uniqMessage)\",\n                \"exit\",\n            ]\n            do {\n                guard case let statusBefore = try getContainerStatus(containerName), statusBefore == \"running\" else {\n                    Issue.record(\"test container is not running\")\n                    return\n                }\n                let found = try await containerRun(stdin: stdin, findMessage: uniqMessage)\n                if !found {\n                    Issue.record(\"did not find stdout line\")\n                    return\n                }\n            } catch {\n                Issue.record(\n                    \"failed to start test container \\(error)\"\n                )\n                return\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLICreate.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\nclass TestCLICreateCommand: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testCreateArgsPassthrough() throws {\n        let name = getTestName()\n        #expect(throws: Never.self, \"expected container create to succeed\") {\n            try doCreate(name: name, args: [\"echo\", \"-n\", \"hello\", \"world\"])\n            try doRemove(name: name)\n        }\n    }\n\n    @Test func testCreateWithMACAddress() throws {\n        let name = getTestName()\n        let expectedMAC = try MACAddress(\"02:42:ac:11:00:03\")\n        #expect(throws: Never.self, \"expected container create with MAC address to succeed\") {\n            try doCreate(name: name, networks: [\"default,mac=\\(expectedMAC)\"])\n            try doStart(name: name)\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n            let inspectResp = try inspectContainer(name)\n            #expect(inspectResp.networks.count > 0, \"expected at least one network attachment\")\n            let actualMAC = inspectResp.networks[0].macAddress?.description ?? \"nil\"\n            #expect(\n                actualMAC == expectedMAC.description, \"expected MAC address \\(expectedMAC), got \\(actualMAC)\"\n            )\n        }\n    }\n\n    @Test func testPublishPortParserMaxPorts() throws {\n        let name = getTestName()\n        var args: [String] = [\"create\", \"--name\", name]\n\n        let portCount = 64\n        for i in 0..<portCount {\n            args.append(\"--publish\")\n            args.append(\"127.0.0.1:\\(8000 + i):\\(9000 + i)\")\n        }\n\n        args.append(\"ghcr.io/linuxcontainers/alpine:3.20\")\n        args.append(\"echo\")\n        args.append(\"\\\"hello world\\\"\")\n\n        #expect(throws: Never.self, \"expected container create maximum port publishes to succeed\") {\n            let (_, _, error, status) = try run(arguments: args)\n            defer { try? doRemove(name: name) }\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n        }\n    }\n\n    @Test func testPublishPortParserTooManyPorts() throws {\n        let name = getTestName()\n        var args: [String] = [\"create\", \"--name\", name]\n\n        let portCount = 65\n        for i in 0..<portCount {\n            args.append(\"--publish\")\n            args.append(\"127.0.0.1:\\(8000 + i):\\(9000 + i)\")\n        }\n\n        args.append(\"ghcr.io/linuxcontainers/alpine:3.20\")\n        args.append(\"echo\")\n        args.append(\"\\\"hello world\\\"\")\n\n        #expect(throws: CLIError.self, \"expected container create more than maximum port publishes to fail\") {\n            let (_, _, error, status) = try run(arguments: args)\n            defer { try? doRemove(name: name) }\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n        }\n    }\n\n    @Test func testCreateWithFQDNName() throws {\n        let name = \"test.example.com\"\n        let expectedHostname = \"test\"\n        #expect(throws: Never.self, \"expected container create with FQDN name to succeed\") {\n            try doCreate(name: name)\n            try doStart(name: name)\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n            let inspectResp = try inspectContainer(name)\n            let attachmentHostname = inspectResp.networks.first?.hostname ?? \"\"\n            let gotHostname =\n                attachmentHostname\n                .split(separator: \".\", maxSplits: 1, omittingEmptySubsequences: true)\n                .first\n                .map { String($0) } ?? attachmentHostname\n            #expect(\n                gotHostname == expectedHostname,\n                \"expected hostname to be extracted as '\\(expectedHostname)' from FQDN '\\(name)', got '\\(gotHostname)' (attachment hostname: '\\(attachmentHostname)')\"\n            )\n        }\n    }\n\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLIExec.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nclass TestCLIExecCommand: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testCreateExecCommand() throws {\n        do {\n            let name = getTestName()\n            try doCreate(name: name)\n            defer {\n                try? doStop(name: name)\n            }\n            try doStart(name: name)\n            var unameActual = try doExec(name: name, cmd: [\"uname\"])\n            unameActual = unameActual.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(unameActual == \"Linux\", \"expected OS to be Linux, instead got \\(unameActual)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to exec in container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testExecDetach() throws {\n        do {\n            let name = getTestName()\n            try doCreate(name: name)\n            defer {\n                try? doStop(name: name)\n            }\n            try doStart(name: name)\n\n            // Run a long-running process in detached mode\n            let output = try doExec(name: name, cmd: [\"sh\", \"-c\", \"touch /tmp/detach_test_marker\"], detach: true)\n            let containerIdOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            try #require(containerIdOutput == name, \"exec --detach should print the container ID\")\n\n            // Verify the detached process is running by checking if we can still exec commands\n            var lsActual = try doExec(name: name, cmd: [\"ls\", \"/\"])\n            lsActual = lsActual.trimmingCharacters(in: .whitespacesAndNewlines)\n            try #require(lsActual.contains(\"tmp\"), \"container should still be running and accepting exec commands\")\n\n            // Retry loop to check if the marker file was created by the detached process\n            var markerFound = false\n            for _ in 0..<3 {\n                let (_, _, _, status) = try run(arguments: [\n                    \"exec\",\n                    name,\n                    \"test\", \"-f\", \"/tmp/detach_test_marker\",\n                ])\n                if status == 0 {\n                    markerFound = true\n                    break\n                }\n                sleep(1)\n            }\n            try #require(markerFound, \"marker file should be created by detached process within 3 seconds\")\n\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to exec with detach in container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testExecDetachProcessRunning() throws {\n        do {\n            let name = getTestName()\n            try doCreate(name: name)\n            defer {\n                try? doStop(name: name)\n            }\n            try doStart(name: name)\n\n            // Run a long-running process in detached mode\n            let output = try doExec(name: name, cmd: [\"sleep\", \"10\"], detach: true)\n            let containerIdOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            try #require(containerIdOutput == name, \"exec --detach should print the container ID\")\n\n            // Immediately check if the process is running using ps\n            var psOutput = try doExec(name: name, cmd: [\"ps\", \"aux\"])\n            psOutput = psOutput.trimmingCharacters(in: .whitespacesAndNewlines)\n            try #require(psOutput.contains(\"sleep 10\"), \"detached process 'sleep 10' should be visible in ps output\")\n\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to verify detached process is running \\(error)\")\n            return\n        }\n    }\n\n    @Test func testExecOnExitingContainer() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, containerArgs: [\"sh\"], autoRemove: false)\n            defer {\n                try? doRemove(name: name)\n            }\n            // Give time for container process to exit due to no stdin\n            sleep(1)\n\n            try doStart(name: name)\n            do {\n                _ = try doExec(name: name, cmd: [\"sleep\", \"infinity\"])\n            } catch CLIError.executionFailed(let message) {\n                // There's no nice way to check fail reason here\n                #expect(\n                    message.contains(\"is not running\") || message.contains(\"failed to create process\"),\n                    \"expected container is not running if exec failed\"\n                )\n            }\n\n            // Give time for the exec (or start) error handling settles down\n            sleep(1)\n            #expect(throws: Never.self, \"expected the container remains\") {\n                try getContainerStatus(name)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLIExport.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationArchive\nimport Foundation\nimport Testing\n\nclass TestCLIExportCommand: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testExportCommand() throws {\n        let name = getTestName()\n        try doLongRun(name: name, autoRemove: false)\n        defer {\n            try? doStop(name: name)\n            try? doRemove(name: name)\n        }\n\n        let mustBeInImage = \"must-be-in-image\"\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"echo \\(mustBeInImage) > /foo\"])\n\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"mkdir -p /parent/child\"])\n        let hardlinkMustRemain = \"hardlink-must-remain\"\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"echo \\(hardlinkMustRemain) > /parent/child/bar\"])\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"ln /parent/child/bar /bar\"])\n\n        let symlinkMustRemain = \"symlink-must-remain\"\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"echo \\(symlinkMustRemain) > /parent/child/baz\"])\n        _ = try doExec(name: name, cmd: [\"sh\", \"-c\", \"ln /parent/child/baz /baz\"])\n\n        try doStop(name: name)\n\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n        let tempFile = tempDir.appendingPathComponent(UUID().uuidString)\n\n        try doExport(name: name, filepath: tempFile.path())\n\n        let attrs = try FileManager.default.attributesOfItem(atPath: tempFile.path())\n        let fileSize = attrs[.size] as! UInt64\n        #expect(fileSize > 0)\n\n        // TODO: verify foo bar baz are in tar file.\n        let reader = try ArchiveReader(file: tempFile)\n        let (foo, fooData) = try reader.extractFile(path: \"/foo\")\n        #expect(foo.fileType == .regular)\n        #expect(String(data: fooData, encoding: .utf8)?.starts(with: mustBeInImage) ?? false)\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLIPrune.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nclass TestCLIPruneCommand: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testContainerPruneNoContainers() throws {\n        let (_, output, error, status) = try run(arguments: [\"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"container prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(\"Reclaimed Zero KB in disk space\"), \"should show no containers message\")\n    }\n\n    @Test func testContainerPruneStoppedContainers() throws {\n        let testName = getTestName()\n        let npcName = \"\\(testName)_wont_be_pruned\"\n        let pc0Name = \"\\(testName)_pruned_0\"\n        let pc1Name = \"\\(testName)_pruned_1\"\n\n        try doLongRun(name: npcName, containerArgs: [\"sleep\", \"3600\"], autoRemove: true)\n        try doLongRun(name: pc0Name, containerArgs: [\"sleep\", \"3600\"], autoRemove: false)\n        try doLongRun(name: pc1Name, containerArgs: [\"sleep\", \"3600\"], autoRemove: false)\n        defer {\n            try? doStop(name: npcName)\n            try? doStop(name: pc0Name)\n            try? doStop(name: pc1Name)\n            try? doRemove(name: npcName)\n            try? doRemove(name: pc0Name)\n            try? doRemove(name: pc1Name)\n        }\n        try waitForContainerRunning(npcName)\n        try waitForContainerRunning(pc0Name)\n        try waitForContainerRunning(pc1Name)\n\n        try doStop(name: pc0Name)\n        try doStop(name: pc1Name)\n\n        let pc0Id = try getContainerId(pc0Name)\n        let pc1Id = try getContainerId(pc1Name)\n\n        // Poll status until both containers are stopped, with interval checks and a timeout to avoid infinite loop\n        let start = Date()\n        let timeout: TimeInterval = 30  // seconds\n        while true {\n            let s0 = try getContainerStatus(pc0Name)\n            let s1 = try getContainerStatus(pc1Name)\n            if s0 == \"stopped\" && s1 == \"stopped\" { break }\n            if Date().timeIntervalSince(start) > timeout {\n                throw CLIError.executionFailed(\"Timeout waiting for containers to stop: pc0=\\(s0), pc1=\\(s1)\")\n            }\n            Thread.sleep(forTimeInterval: 0.2)\n        }\n\n        let (_, output, error, status) = try run(arguments: [\"prune\"])\n\n        if status != 0 {\n            throw CLIError.executionFailed(\"container prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(pc0Id) && output.contains(pc1Id), \"should show the stopped containers id\")\n        #expect(!output.contains(\"Reclaimed Zero KB in disk space\"), \"reclaimed spaces should not Zero KB\")\n\n        let checkStatus = try getContainerStatus(npcName)\n        #expect(checkStatus == \"running\", \"not pruned container should still be running\")\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLIRmRace.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nclass TestCLIRmRaceCondition: CLITest {\n\n    /// Helper method to check if a container exists\n    private func containerExists(_ name: String) -> Bool {\n        do {\n            _ = try getContainerStatus(name)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// Safe container removal that handles already-removed containers gracefully\n    private func safeRemove(name: String, force: Bool = false) throws {\n        guard containerExists(name) else {\n            // Container already removed, nothing to do\n            return\n        }\n        try doRemove(name: name, force: force)\n    }\n\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testStopRmRace() async throws {\n        let name = getTestName()\n\n        do {\n            // Create and start a container in detached mode that runs indefinitely\n            try doCreate(name: name, args: [\"sleep\", \"infinity\"])\n            try doStart(name: name)\n\n            // Wait for container to be running\n            try waitForContainerRunning(name)\n\n            // Call doStop - this should return immediately without waiting\n            try doStop(name: name)\n\n            // Immediately call doRemove and handle both possible outcomes:\n            // 1. Container removal succeeds immediately (race condition fixed)\n            // 2. Container removal fails because it's still stopping (race condition detected)\n            var raceConditionPrevented = false\n            var raceConditionDetected = false\n\n            do {\n                try doRemove(name: name)\n                // Success: The race condition prevention is working perfectly!\n                // Container was removed cleanly without any race condition\n                raceConditionPrevented = true\n            } catch CLITest.CLIError.executionFailed(let message) {\n                if message.contains(\"is not yet stopped and can not be deleted\") {\n                    // Expected behavior: Race condition detected and prevented\n                    raceConditionDetected = true\n                } else if message.contains(\"not found\") || message.contains(\"failed to delete one or more containers\") {\n                    // Container was already removed by background cleanup - this is also success!\n                    raceConditionPrevented = true\n                } else {\n                    Issue.record(\"Unexpected error message: \\(message)\")\n                    return\n                }\n            } catch {\n                Issue.record(\"Unexpected error type: \\(error)\")\n                return\n            }\n\n            // Either outcome is acceptable - both indicate the race condition fix is working\n            #expect(\n                raceConditionPrevented || raceConditionDetected,\n                \"Expected either immediate success (race prevented) or controlled failure (race detected)\")\n\n            // If the container was already removed, we're done\n            if raceConditionPrevented {\n                return\n            }\n\n            // If we detected a race condition, wait for cleanup and retry removal\n            #expect(raceConditionDetected, \"Should have detected race condition if we reach this point\")\n\n            // Give the background cleanup a moment to finish\n            try await Task.sleep(for: .seconds(2))\n\n            // Retry removal with exponential backoff for cleanup\n            var removeAttempts = 0\n            let maxRemoveAttempts = 5\n            let baseDelay = 1.0  // seconds\n\n            while removeAttempts < maxRemoveAttempts {\n                do {\n                    try safeRemove(name: name)\n                    break\n                } catch CLITest.CLIError.executionFailed(let message) {\n                    // If container doesn't exist, we're done\n                    if message.contains(\"not found\") {\n                        break\n                    }\n\n                    guard removeAttempts < maxRemoveAttempts - 1 else {\n                        throw CLITest.CLIError.executionFailed(\"Failed to remove container after \\(maxRemoveAttempts) attempts: \\(message)\")\n                    }\n\n                    let delay = baseDelay * pow(2.0, Double(removeAttempts))\n                    try await Task.sleep(for: .seconds(delay))\n                    removeAttempts += 1\n                } catch {\n                    guard removeAttempts < maxRemoveAttempts - 1 else {\n                        throw error\n                    }\n                    let delay = baseDelay * pow(2.0, Double(removeAttempts))\n                    try await Task.sleep(for: .seconds(delay))\n                    removeAttempts += 1\n                }\n            }\n\n        } catch {\n            Issue.record(\"failed to test stop-rm race condition: \\(error)\")\n            // Safe cleanup - only try to remove if container actually exists\n            try? safeRemove(name: name, force: true)\n            return\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Containers/TestCLIStats.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport Foundation\nimport Testing\n\nclass TestCLIStatsCommand: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testStatsNoStreamJSONFormat() throws {\n        let name = getTestName()\n        #expect(throws: Never.self, \"expected stats command to succeed\") {\n            try doLongRun(name: name)\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n\n            let (data, _, error, status) = try run(arguments: [\n                \"stats\",\n                \"--format\", \"json\",\n                \"--no-stream\",\n                name,\n            ])\n\n            try #require(status == 0, \"stats command should succeed, error: \\(error)\")\n\n            let decoder = JSONDecoder()\n            let stats = try decoder.decode([ContainerStats].self, from: data)\n\n            #expect(stats.count == 1, \"expected stats for one container\")\n            #expect(stats[0].id == name, \"container ID should match\")\n            let memoryUsageBytes = try #require(stats[0].memoryUsageBytes)\n            let numProcesses = try #require(stats[0].numProcesses)\n            #expect(memoryUsageBytes > 0, \"memory usage should be non-zero\")\n            #expect(numProcesses >= 1, \"should have at least one process\")\n        }\n    }\n\n    @Test func testStatsIdleCPUPercentage() throws {\n        let name = getTestName()\n        #expect(throws: Never.self, \"expected stats to show low CPU for idle container\") {\n            try doLongRun(name: name, containerArgs: [\"sleep\", \"3600\"])\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n\n            // Get stats in table format\n            let (_, output, _, status) = try run(arguments: [\n                \"stats\",\n                \"--no-stream\",\n                name,\n            ])\n            try #require(status == 0, \"stats command should succeed\")\n\n            // Parse the table output\n            let lines = output.components(separatedBy: .newlines)\n            #expect(lines.count >= 2, \"should have at least header and one data row\")\n\n            // Find the data row (not the header)\n            let dataLine = lines.first { $0.contains(name) }\n            try #require(dataLine != nil, \"should find container data row\")\n\n            // Extract CPU percentage - it should be in the second column\n            let columns = dataLine!.split(separator: \" \").filter { !$0.isEmpty }\n            #expect(columns.count >= 2, \"should have at least 2 columns\")\n\n            // Second column is CPU%\n            let cpuString = String(columns[1])\n            #expect(cpuString.hasSuffix(\"%\"), \"CPU column should end with %\")\n\n            // Parse the percentage\n            let cpuValue = Double(cpuString.dropLast())\n            try #require(cpuValue != nil, \"should be able to parse CPU percentage\")\n\n            // Idle container should use very little CPU (less than 5%)\n            #expect(cpuValue! < 5.0, \"idle container CPU should be < 5%, got \\(cpuValue!)%\")\n        }\n    }\n\n    @Test func testStatsHighCPUPercentage() throws {\n        let name = getTestName()\n        #expect(throws: Never.self, \"expected stats to show high CPU for busy container\") {\n            // Run a container with a busy loop\n            try doLongRun(name: name, containerArgs: [\"sh\", \"-c\", \"while true; do :; done\"])\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n\n            // Get stats in table format\n            let (_, output, _, status) = try run(arguments: [\n                \"stats\",\n                \"--no-stream\",\n                name,\n            ])\n            try #require(status == 0, \"stats command should succeed\")\n\n            // Parse the table output\n            let lines = output.components(separatedBy: .newlines)\n            #expect(lines.count >= 2, \"should have at least header and one data row\")\n\n            // Find the data row (not the header)\n            let dataLine = lines.first { $0.contains(name) }\n            try #require(dataLine != nil, \"should find container data row\")\n\n            // Extract CPU percentage - it should be in the second column\n            // Format is like: \"container_id   95.23%   ...\"\n            let columns = dataLine!.split(separator: \" \").filter { !$0.isEmpty }\n            #expect(columns.count >= 2, \"should have at least 2 columns\")\n\n            // Second column is CPU%\n            let cpuString = String(columns[1])\n            #expect(cpuString.hasSuffix(\"%\"), \"CPU column should end with %\")\n\n            // Parse the percentage\n            let cpuValue = Double(cpuString.dropLast())\n            try #require(cpuValue != nil, \"should be able to parse CPU percentage\")\n\n            // Busy loop should use significant CPU (at least 50% of one core)\n            #expect(cpuValue! > 50.0, \"busy container CPU should be > 50%, got \\(cpuValue!)%\")\n            // Should not exceed reasonable limits (one core doing while loop = ~100%)\n            #expect(cpuValue! < 150.0, \"single busy loop should not exceed 150%, got \\(cpuValue!)%\")\n        }\n    }\n\n    @Test func testStatsTableFormat() throws {\n        let name = getTestName()\n        #expect(throws: Never.self, \"expected stats table format to work\") {\n            try doLongRun(name: name)\n            defer {\n                try? doStop(name: name)\n                try? doRemove(name: name)\n            }\n            try waitForContainerRunning(name)\n\n            // Get stats in table format\n            let (_, output, error, status) = try run(arguments: [\n                \"stats\",\n                \"--no-stream\",\n                name,\n            ])\n\n            try #require(status == 0, \"stats command should succeed, error: \\(error)\")\n            #expect(output.contains(\"Container ID\"), \"output should contain table header\")\n            #expect(output.contains(\"Cpu %\"), \"output should contain CPU column\")\n            #expect(output.contains(\"Memory Usage\"), \"output should contain Memory column\")\n            #expect(output.contains(name), \"output should contain container name\")\n        }\n    }\n\n    @Test func testStatsAllContainers() throws {\n        let name1 = getTestName() + \"-1\"\n        let name2 = getTestName() + \"-2\"\n        #expect(throws: Never.self, \"expected stats for all containers\") {\n            try doLongRun(name: name1)\n            try doLongRun(name: name2)\n            defer {\n                try? doStop(name: name1)\n                try? doStop(name: name2)\n                try? doRemove(name: name1)\n                try? doRemove(name: name2)\n            }\n            try waitForContainerRunning(name1)\n            try waitForContainerRunning(name2)\n\n            // Get stats for all containers (no name specified)\n            let (data, _, error, status) = try run(arguments: [\n                \"stats\",\n                \"--format\", \"json\",\n                \"--no-stream\",\n            ])\n\n            try #require(status == 0, \"stats command should succeed, error: \\(error)\")\n\n            let stats = try JSONDecoder().decode([ContainerStats].self, from: data)\n\n            // Should have stats for both containers\n            try #require(stats.count >= 2, \"should have stats for at least 2 containers\")\n\n            let containerIds = stats.map { $0.id }\n            #expect(containerIds.contains(name1), \"should include first container\")\n            #expect(containerIds.contains(name2), \"should include second container\")\n        }\n    }\n\n    @Test func testStatsNonExistentContainer() throws {\n        #expect(throws: Never.self, \"expected stats to fail for non-existent container\") {\n            let (_, _, _, status) = try run(arguments: [\n                \"stats\",\n                \"--no-stream\",\n                \"nonexistent-container-xyz\",\n            ])\n\n            #expect(status != 0, \"stats command should fail for non-existent container\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Images/TestCLIImagesCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerizationArchive\nimport ContainerizationOCI\nimport Foundation\nimport Testing\n\nclass TestCLIImagesCommand: CLITest {\n    @Test func testPull() throws {\n        do {\n            try doPull(imageName: alpine)\n            let imagePresent = try isImagePresent(targetImage: alpine)\n            #expect(imagePresent, \"expected to see \\(alpine) pulled\")\n        } catch {\n            Issue.record(\"failed to pull alpine image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullMulti() throws {\n        do {\n            try doPull(imageName: alpine)\n            try doPull(imageName: busybox)\n\n            let alpinePresent = try isImagePresent(targetImage: alpine)\n            #expect(alpinePresent, \"expected to see \\(alpine) pulled\")\n\n            let busyPresent = try isImagePresent(targetImage: busybox)\n            #expect(busyPresent, \"expected to see \\(busybox) pulled\")\n        } catch {\n            Issue.record(\"failed to pull images \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullPlatform() throws {\n        do {\n            let os = \"linux\"\n            let arch = \"amd64\"\n            let pullArgs = [\n                \"--platform\",\n                \"\\(os)/\\(arch)\",\n            ]\n\n            try doPull(imageName: alpine, args: pullArgs)\n\n            let output = try doInspectImages(image: alpine)\n            #expect(output.count == 1, \"expected a single image inspect output, got \\(output)\")\n\n            var found = false\n            for v in output[0].variants {\n                if v.platform.os == os && v.platform.architecture == arch {\n                    found = true\n                }\n            }\n            #expect(found, \"expected to find image with os \\(os) and architecture \\(arch), instead got \\(output[0])\")\n        } catch {\n            Issue.record(\"failed to pull and inspect image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullOsArch() throws {\n        do {\n            let os = \"linux\"\n            let arch = \"amd64\"\n            let pullArgs = [\n                \"--os\",\n                os,\n                \"--arch\",\n                arch,\n            ]\n\n            try doPull(imageName: alpine318, args: pullArgs)\n\n            let output = try doInspectImages(image: alpine318)\n            #expect(output.count == 1, \"expected a single image inspect output, got \\(output)\")\n\n            var found = false\n            for v in output[0].variants {\n                if v.platform.os == os && v.platform.architecture == arch {\n                    found = true\n                }\n            }\n            #expect(found, \"expected to find image with os \\(os) and architecture \\(arch), instead got \\(output[0])\")\n        } catch {\n            Issue.record(\"failed to pull and inspect image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullOs() throws {\n        do {\n            let os = \"linux\"\n            let arch = Arch.hostArchitecture().rawValue\n            let pullArgs = [\n                \"--os\",\n                os,\n            ]\n\n            try doPull(imageName: alpine318, args: pullArgs)\n\n            let output = try doInspectImages(image: alpine318)\n            #expect(output.count == 1, \"expected a single image inspect output, got \\(output)\")\n\n            var found = false\n            for v in output[0].variants {\n                if v.platform.os == os && v.platform.architecture == arch {\n                    found = true\n                }\n            }\n            #expect(found, \"expected to find image with os \\(os) and architecture \\(arch), instead got \\(output[0])\")\n        } catch {\n            Issue.record(\"failed to pull and inspect image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullArch() throws {\n        do {\n            let os = \"linux\"\n            let arch = \"amd64\"\n            let pullArgs = [\n                \"--arch\",\n                arch,\n            ]\n\n            try doPull(imageName: alpine318, args: pullArgs)\n\n            let output = try doInspectImages(image: alpine318)\n            #expect(output.count == 1, \"expected a single image inspect output, got \\(output)\")\n\n            var found = false\n            for v in output[0].variants {\n                if v.platform.os == os && v.platform.architecture == arch {\n                    found = true\n                }\n            }\n            #expect(found, \"expected to find image with os \\(os) and architecture \\(arch), instead got \\(output[0])\")\n        } catch {\n            Issue.record(\"failed to pull and inspect image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testPullRemoveSingle() throws {\n        do {\n            try doPull(imageName: alpine)\n            let imagePulled = try isImagePresent(targetImage: alpine)\n            #expect(imagePulled, \"expected to see image \\(alpine) pulled\")\n\n            // tag image so we can safely remove later\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testPullRemoveSingle\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let taggedImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(taggedImagePresent, \"expected to see image \\(alpineTagged) tagged\")\n\n            try doRemoveImages(images: [alpineTagged])\n            let imageRemoved = try !isImagePresent(targetImage: alpineTagged)\n            #expect(imageRemoved, \"expected not to see image \\(alpineTagged)\")\n        } catch {\n            Issue.record(\"failed to pull and remove image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageTag() throws {\n        do {\n            try doPull(imageName: alpine)\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testImageTag\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let imagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(imagePresent, \"expected to see image \\(alpineTagged) tagged\")\n        } catch {\n            Issue.record(\"failed to pull and tag image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageDefaultRegistry() throws {\n        do {\n            let defaultDomain = \"ghcr.io\"\n            let imageName = \"linuxcontainers/alpine:3.20\"\n            defer {\n                try? doDefaultRegistrySet(domain: \"docker.io\")\n            }\n            try doDefaultRegistrySet(domain: defaultDomain)\n            try doPull(imageName: imageName, args: [\"--platform\", \"linux/arm64\"])\n            guard let alpineImageDetails = try doInspectImages(image: imageName).first else {\n                Issue.record(\"alpine image not found\")\n                return\n            }\n            #expect(alpineImageDetails.name == \"\\(defaultDomain)/\\(imageName)\")\n\n            try doImageTag(image: imageName, newName: \"username/image-name:mytag\")\n            guard let taggedImage = try doInspectImages(image: \"username/image-name:mytag\").first else {\n                Issue.record(\"Tagged image not found\")\n                return\n            }\n            #expect(taggedImage.name == \"\\(defaultDomain)/username/image-name:mytag\")\n\n            let listOutput = try doImageListQuite()\n            #expect(listOutput.contains(\"username/image-name:mytag\"))\n            #expect(listOutput.contains(imageName))\n        } catch {\n            Issue.record(\"failed default registry test\")\n            return\n        }\n    }\n\n    @Test func testImageSaveAndLoad() throws {\n        do {\n            // 1. pull image\n            try doPull(imageName: alpine)\n            try doPull(imageName: busybox)\n\n            // 2. Tag image so we can safely remove later\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testImageSaveAndLoad\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let alpineTaggedImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(alpineTaggedImagePresent, \"expected to see image \\(alpineTagged) tagged\")\n\n            let busyboxRef: Reference = try Reference.parse(busybox)\n            let busyboxTagged = \"\\(busyboxRef.name):testImageSaveAndLoad\"\n            try doImageTag(image: busybox, newName: busyboxTagged)\n            let busyboxTaggedImagePresent = try isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxTaggedImagePresent, \"expected to see image \\(busyboxTagged) tagged\")\n\n            // 3. save the image as a tarball\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n            let tempFile = tempDir.appendingPathComponent(UUID().uuidString)\n            let saveArgs = [\n                \"image\",\n                \"save\",\n                alpineTagged,\n                busyboxTagged,\n                \"--output\",\n                tempFile.path(),\n            ]\n            let (_, _, error, status) = try run(arguments: saveArgs)\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n\n            // 4. remove the image through container\n            try doRemoveImages(images: [alpineTagged, busyboxTagged])\n\n            // 5. verify image is no longer present\n            let alpineImageRemoved = try !isImagePresent(targetImage: alpineTagged)\n            #expect(alpineImageRemoved, \"expected image \\(alpineTagged) to be removed\")\n            let busyboxImageRemoved = try !isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxImageRemoved, \"expected image \\(busyboxTagged) to be removed\")\n\n            // 6. load the tarball\n            let loadArgs = [\n                \"image\",\n                \"load\",\n                \"-i\",\n                tempFile.path(),\n            ]\n            let (_, _, loadErr, loadStatus) = try run(arguments: loadArgs)\n            if loadStatus != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(loadErr)\")\n            }\n\n            // 7. verify image is in the list again\n            let alpineImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(alpineImagePresent, \"expected \\(alpineTagged) to be present\")\n            let busyboxImagePresent = try isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxImagePresent, \"expected \\(busyboxTagged) to be present\")\n        } catch {\n            Issue.record(\"failed to save and load image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testMaxConcurrentDownloadsValidation() throws {\n        // Test that invalid maxConcurrentDownloads value is rejected\n        let (_, _, error, status) = try run(arguments: [\n            \"image\",\n            \"pull\",\n            \"--max-concurrent-downloads\", \"0\",\n            \"alpine:latest\",\n        ])\n\n        #expect(status != 0, \"Expected command to fail with maxConcurrentDownloads=0\")\n        #expect(\n            error.contains(\"maximum number of concurrent downloads must be greater than 0\"),\n            \"Expected validation error message in output\")\n    }\n\n    @Test func testImageLoadRejectsInvalidMembersWithoutForce() throws {\n        do {\n            // 0. Generate unique malicious filename for this test run\n            let maliciousFilename = \"pwned-\\(UUID().uuidString).txt\"\n            let maliciousPath = \"/tmp/\\(maliciousFilename)\"\n\n            // 1. Pull image\n            try doPull(imageName: alpine)\n\n            // 2. Tag image so we can safely remove later\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testImageLoadRejectsInvalidMembers\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let taggedImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(taggedImagePresent, \"expected to see image \\(alpineTagged) tagged\")\n\n            // 3. Save the image as a tarball\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n            let tempFile = tempDir.appendingPathComponent(UUID().uuidString)\n            let saveArgs = [\n                \"image\",\n                \"save\",\n                alpineTagged,\n                \"--output\",\n                tempFile.path(),\n            ]\n            let (_, _, saveError, saveStatus) = try run(arguments: saveArgs)\n            if saveStatus != 0 {\n                throw CLIError.executionFailed(\"save command failed: \\(saveError)\")\n            }\n\n            // 4. Add malicious member to the tar\n            try addInvalidMemberToTar(tarPath: tempFile.path(), maliciousFilename: maliciousFilename)\n\n            // 5. Remove the image\n            try doRemoveImages(images: [alpineTagged])\n            let imageRemoved = try !isImagePresent(targetImage: alpineTagged)\n            #expect(imageRemoved, \"expected image \\(alpineTagged) to be removed\")\n\n            // 6. Try to load the modified tar without force - should fail\n            let loadArgs = [\n                \"image\",\n                \"load\",\n                \"-i\",\n                tempFile.path(),\n            ]\n            let (_, _, loadError, loadStatus) = try run(arguments: loadArgs)\n            #expect(loadStatus != 0, \"expected load to fail without force flag\")\n            #expect(loadError.contains(\"rejected paths\") || loadError.contains(maliciousFilename), \"expected error about invalid member path\")\n\n            // 7. Verify that malicious file was NOT created\n            let maliciousFileExists = FileManager.default.fileExists(atPath: maliciousPath)\n            #expect(!maliciousFileExists, \"malicious file should not have been created at \\(maliciousPath)\")\n        } catch {\n            Issue.record(\"failed to test image load with invalid members: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageLoadAcceptsInvalidMembersWithForce() throws {\n        do {\n            // 0. Generate unique malicious filename for this test run\n            let maliciousFilename = \"pwned-\\(UUID().uuidString).txt\"\n            let maliciousPath = \"/tmp/\\(maliciousFilename)\"\n\n            // 1. Pull image\n            try doPull(imageName: alpine)\n\n            // 2. Tag image so we can safely remove later\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testImageLoadAcceptsInvalidMembers\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let taggedImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(taggedImagePresent, \"expected to see image \\(alpineTagged) tagged\")\n\n            // 3. Save the image as a tarball\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n            let tempFile = tempDir.appendingPathComponent(UUID().uuidString)\n            let saveArgs = [\n                \"image\",\n                \"save\",\n                alpineTagged,\n                \"--output\",\n                tempFile.path(),\n            ]\n            let (_, _, saveError, saveStatus) = try run(arguments: saveArgs)\n            if saveStatus != 0 {\n                throw CLIError.executionFailed(\"save command failed: \\(saveError)\")\n            }\n\n            // 4. Add malicious member to the tar\n            try addInvalidMemberToTar(tarPath: tempFile.path(), maliciousFilename: maliciousFilename)\n\n            // 5. Remove the image\n            try doRemoveImages(images: [alpineTagged])\n            let imageRemoved = try !isImagePresent(targetImage: alpineTagged)\n            #expect(imageRemoved, \"expected image \\(alpineTagged) to be removed\")\n\n            // 6. Try to load the modified tar with force - should succeed with warning\n            let loadArgs = [\n                \"image\",\n                \"load\",\n                \"-i\",\n                tempFile.path(),\n                \"--force\",\n            ]\n            let (_, _, loadError, loadStatus) = try run(arguments: loadArgs)\n            #expect(loadStatus == 0, \"expected load to succeed with force flag\")\n\n            // Check that warning was logged about rejected member\n            #expect(loadError.contains(\"invalid members\") || loadError.contains(maliciousFilename), \"expected warning about rejected member path\")\n\n            // 7. Verify image is loaded\n            let imageLoaded = try isImagePresent(targetImage: alpineTagged)\n            #expect(imageLoaded, \"expected image \\(alpineTagged) to be loaded\")\n\n            // 8. Verify that malicious file was NOT created\n            let maliciousFileExists = FileManager.default.fileExists(atPath: maliciousPath)\n            #expect(!maliciousFileExists, \"malicious file should not have been created at \\(maliciousPath)\")\n        } catch {\n            Issue.record(\"failed to test image load with force and invalid members: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageSaveAndLoadStdinStdout() throws {\n        do {\n            // 1. pull image\n            try doPull(imageName: alpine)\n            try doPull(imageName: busybox)\n\n            // 2. Tag image so we can safely remove later\n            let alpineRef: Reference = try Reference.parse(alpine)\n            let alpineTagged = \"\\(alpineRef.name):testImageSaveAndLoadStdinStdout\"\n            try doImageTag(image: alpine, newName: alpineTagged)\n            let alpineTaggedImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(alpineTaggedImagePresent, \"expected to see image \\(alpineTagged) tagged\")\n\n            let busyboxRef: Reference = try Reference.parse(busybox)\n            let busyboxTagged = \"\\(busyboxRef.name):testImageSaveAndLoadStdinStdout\"\n            try doImageTag(image: busybox, newName: busyboxTagged)\n            let busyboxTaggedImagePresent = try isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxTaggedImagePresent, \"expected to see image \\(busyboxTagged) tagged\")\n\n            // 3. save the image and output to stdout\n            let saveArgs = [\n                \"image\",\n                \"save\",\n                alpineTagged,\n                busyboxTagged,\n            ]\n            let (stdoutData, _, error, status) = try run(arguments: saveArgs)\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n\n            // 4. remove the image through container\n            try doRemoveImages(images: [alpineTagged, busyboxTagged])\n\n            // 5. verify image is no longer present\n            let alpineImageRemoved = try !isImagePresent(targetImage: alpineTagged)\n            #expect(alpineImageRemoved, \"expected image \\(alpineTagged) to be removed\")\n            let busyboxImageRemoved = try !isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxImageRemoved, \"expected image \\(busyboxTagged) to be removed\")\n\n            // 6. load the tarball from the stdout data as stdin\n            let loadArgs = [\n                \"image\",\n                \"load\",\n            ]\n            let (_, _, loadErr, loadStatus) = try run(arguments: loadArgs, stdin: stdoutData)\n            if loadStatus != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(loadErr)\")\n            }\n\n            // 7. verify image is in the list again\n            let alpineImagePresent = try isImagePresent(targetImage: alpineTagged)\n            #expect(alpineImagePresent, \"expected \\(alpineTagged) to be present\")\n            let busyboxImagePresent = try isImagePresent(targetImage: busyboxTagged)\n            #expect(busyboxImagePresent, \"expected \\(busyboxTagged) to be present\")\n        } catch {\n            Issue.record(\"failed to save and load image \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageFullSizeFieldExists() throws {\n        // 1. pull image\n        try doPull(imageName: alpine)\n\n        // 2. run the image ls command\n        let (_, output, error, status) = try run(arguments: [\"image\", \"ls\", \"--format\", \"json\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"failed to list images: \\(error)\")\n        }\n\n        // 3. parse the json output\n        guard let data = output.data(using: .utf8),\n            let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]],\n            let image = json.first\n        else {\n            Issue.record(\"failed to parse JSON output or no images found: \\(output)\")\n            return\n        }\n\n        // 4. check that the output has a non-empty 'fullSize' field\n        let size = image[\"fullSize\"] as? String ?? \"\"\n        #expect(!size.isEmpty, \"expected image to have non-empty 'fullSize' field: \\(image)\")\n    }\n\n    private func addInvalidMemberToTar(tarPath: String, maliciousFilename: String) throws {\n        // Create a malicious entry with path traversal\n        let evilEntryName = \"../../../../../../../../../../../tmp/\\(maliciousFilename)\"\n        let evilEntryContent = \"pwned\\n\".data(using: .utf8)!\n\n        // Create a temporary file for the modified tar\n        let tempModifiedTar = FileManager.default.temporaryDirectory.appendingPathComponent(\"\\(UUID().uuidString).tar\")\n\n        // Open the modified tar for writing\n        let writer = try ArchiveWriter(format: .pax, filter: .none, file: tempModifiedTar)\n\n        // First, copy all existing members from the input tar\n        let reader = try ArchiveReader(file: URL(fileURLWithPath: tarPath))\n        for (entry, data) in reader {\n            if entry.fileType == .regular {\n                try writer.writeEntry(entry: entry, data: data)\n            } else {\n                try writer.writeEntry(entry: entry, data: nil)\n            }\n        }\n\n        // Now add the evil entry\n        let evilEntry = WriteEntry()\n        evilEntry.path = evilEntryName\n        evilEntry.size = Int64(evilEntryContent.count)\n        evilEntry.modificationDate = Date()\n        evilEntry.fileType = .regular\n        evilEntry.permissions = 0o644\n\n        try writer.writeEntry(entry: evilEntry, data: evilEntryContent)\n        try writer.finishEncoding()\n\n        // Replace the original tar with the modified one\n        try FileManager.default.removeItem(atPath: tarPath)\n        try FileManager.default.moveItem(at: tempModifiedTar, to: URL(fileURLWithPath: tarPath))\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Networks/TestCLINetwork.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport AsyncHTTPClient\nimport ContainerAPIClient\nimport ContainerizationError\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nclass TestCLINetwork: CLITest {\n    private static let retries = 10\n    private static let retryDelaySeconds = Int64(3)\n\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    private func getLowercasedTestName() -> String {\n        getTestName().lowercased()\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkCreateAndUse() async throws {\n        do {\n            let name = getLowercasedTestName()\n            let networkDeleteArgs = [\"network\", \"delete\", name]\n            _ = try? run(arguments: networkDeleteArgs)\n\n            let networkCreateArgs = [\"network\", \"create\", name]\n            let result = try run(arguments: networkCreateArgs)\n            if result.status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(result.error)\")\n            }\n            defer {\n                _ = try? run(arguments: networkDeleteArgs)\n            }\n            let port = UInt16.random(in: 50000..<60000)\n            try doLongRun(\n                name: name,\n                image: \"docker.io/library/python:alpine\",\n                args: [\"--network\", name],\n                containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(port)\"])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let container = try inspectContainer(name)\n            #expect(container.networks.count > 0)\n            let cidrAddress = container.networks[0].ipv4Address\n            let url = \"http://\\(cidrAddress.address):\\(port)\"\n            var request = HTTPClientRequest(url: url)\n            request.method = .GET\n            let client = getClient(useHttpProxy: false)\n            defer { _ = client.shutdown() }\n            var retriesRemaining = Self.retries\n            var success = false\n            while !success && retriesRemaining > 0 {\n                do {\n                    let response = try await client.execute(request, timeout: .seconds(Self.retryDelaySeconds))\n                    try #require(response.status == .ok)\n                    success = true\n                } catch {\n                    print(\"request to \\(url) failed, error \\(error)\")\n                    try await Task.sleep(for: .seconds(Self.retryDelaySeconds))\n                }\n                retriesRemaining -= 1\n            }\n            #expect(success, \"Request to \\(url) failed after \\(Self.retries - retriesRemaining) retries\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to create and use network \\(error)\")\n            return\n        }\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkDeleteWithContainer() async throws {\n        do {\n            // prep: delete container and network, ignoring if it doesn't exist\n            let name = getLowercasedTestName()\n            try? doRemove(name: name)\n            let networkDeleteArgs = [\"network\", \"delete\", name]\n            _ = try? run(arguments: networkDeleteArgs)\n\n            // create our network\n            let networkCreateArgs = [\"network\", \"create\", name]\n            let networkCreateResult = try run(arguments: networkCreateArgs)\n            if networkCreateResult.status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(networkCreateResult.error)\")\n            }\n\n            // ensure it's deleted\n            defer {\n                _ = try? run(arguments: networkDeleteArgs)\n            }\n\n            // create a container that refers to the network\n            try doCreate(name: name, networks: [name])\n            defer {\n                try? doRemove(name: name)\n            }\n\n            // deleting the network should fail\n            let networkDeleteResult = try run(arguments: networkDeleteArgs)\n            try #require(networkDeleteResult.status != 0)\n\n            // and should fail with a certain message\n            let msg = networkDeleteResult.error\n            #expect(msg.contains(\"delete failed\"))\n            #expect(msg.contains(\"[\\\"\\(name)\\\"]\"))\n\n            // now get rid of the container and its network reference\n            try? doRemove(name: name)\n\n            // delete should succeed\n            _ = try run(arguments: networkDeleteArgs)\n        } catch {\n            Issue.record(\"failed to safely delete network \\(error)\")\n            return\n        }\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkLabels() async throws {\n        do {\n            // prep: delete container and network, ignoring if it doesn't exist\n            let name = getLowercasedTestName()\n            try? doRemove(name: name)\n            let networkDeleteArgs = [\"network\", \"delete\", name]\n            _ = try? run(arguments: networkDeleteArgs)\n\n            // create our network\n            let networkCreateArgs = [\"network\", \"create\", \"--label\", \"foo=bar\", \"--label\", \"baz=qux\", name]\n            let networkCreateResult = try run(arguments: networkCreateArgs)\n            guard networkCreateResult.status == 0 else {\n                throw CLIError.executionFailed(\"command failed: \\(networkCreateResult.error)\")\n            }\n\n            // ensure it's deleted\n            defer {\n                _ = try? run(arguments: networkDeleteArgs)\n            }\n\n            // inspect the network\n            let networkInspectArgs = [\"network\", \"inspect\", name]\n            let networkInspectResult = try run(arguments: networkInspectArgs)\n            guard networkInspectResult.status == 0 else {\n                throw CLIError.executionFailed(\"command failed: \\(networkInspectResult.error)\")\n            }\n\n            // decode the JSON result\n            let networkInspectOutput = networkInspectResult.output\n            guard let jsonData = networkInspectOutput.data(using: .utf8) else {\n                throw CLIError.invalidOutput(\"network inspect output invalid\")\n            }\n\n            let decoder = JSONDecoder()\n            let networks = try decoder.decode([NetworkInspectOutput].self, from: jsonData)\n            guard networks.count == 1 else {\n                throw CLIError.invalidOutput(\"expected exactly one network from inspect, got \\(networks.count)\")\n            }\n\n            // validate labels\n\n            let expectedLabels = [\n                \"foo\": \"bar\",\n                \"baz\": \"qux\",\n            ]\n            #expect(expectedLabels == networks[0].config.labels)\n\n            // delete should succeed\n            _ = try run(arguments: networkDeleteArgs)\n        } catch {\n            Issue.record(\"failed to safely delete network \\(error)\")\n            return\n        }\n    }\n\n    @Test func testNetworkMTU() async throws {\n        let name = getLowercasedTestName()\n        try? doStop(name: name)\n        try? doRemove(name: name)\n\n        try doLongRun(name: name, args: [\"--network\", \"default,mtu=1500\"])\n        defer { try? doStop(name: name) }\n\n        try waitForContainerRunning(name)\n        let output = try doExec(name: name, cmd: [\"ip\", \"link\", \"show\", \"eth0\"])\n        #expect(output.contains(\"mtu 1500\"), \"expected mtu 1500 in ip link output: \\(output)\")\n    }\n\n    @available(macOS 26, *)\n    @Test func testIsolatedNetwork() async throws {\n        do {\n            let name = getLowercasedTestName()\n            let networkDeleteArgs = [\"network\", \"delete\", name]\n            _ = try? run(arguments: networkDeleteArgs)\n\n            let networkCreateArgs = [\"network\", \"create\", \"--internal\", name]\n            let result = try run(arguments: networkCreateArgs)\n            if result.status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(result.error)\")\n            }\n            defer {\n                _ = try? run(arguments: networkDeleteArgs)\n            }\n            let port = UInt16.random(in: 50000..<60000)\n            try doLongRun(\n                name: name,\n                image: \"docker.io/library/python:alpine\",\n                args: [\"--network\", name],\n                containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(port)\"]\n            )\n            defer {\n                try? doStop(name: name)\n            }\n\n            let container = try inspectContainer(name)\n            #expect(container.networks.count > 0)\n            let curlImage = \"docker.io/curlimages/curl:8.6.0\"\n            let cidrAddress = container.networks[0].ipv4Address\n            let url = \"http://\\(cidrAddress.address):\\(port)\"\n            let (_, _, _, succeed) = try run(arguments: [\n                \"run\",\n                \"--rm\",\n                \"--network\",\n                name,\n                curlImage,\n                \"curl\",\n                url,\n            ])\n\n            #expect(succeed == 0, \"internal connection should succeed\")\n\n            let (_, _, _, failed) = try run(arguments: [\n                \"run\",\n                \"--rm\",\n                \"--network\",\n                name,\n                curlImage,\n                \"curl\",\n                \"http://google.com\",\n            ])\n\n            #expect(failed == 6, \"external connection should fail\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Plugins/TestCLIPluginErrors.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Testing\n\nstruct TestCLIPluginErrors {\n    @Test\n    func testHelpfulMessageWhenPluginsUnavailable() throws {\n        // Intentionally invoke an unknown plugin command. In CI this should run\n        // without the APIServer started, so DefaultCommand will fail to create\n        // a PluginLoader and emit the improved guidance.\n        let cli = try CLITest()\n        let (_, _, stderr, status) = try cli.run(arguments: [\"nosuchplugin\"])  // non-existent plugin name\n\n        #expect(status != 0)\n        #expect(stderr.contains(\"container system start\"))\n        #expect(stderr.contains(\"Plugins are unavailable\") || stderr.contains(\"Plugin 'container-\"))\n        // Should include at least one computed plugin search path hint\n        #expect(stderr.contains(\"container-plugins\") || stderr.contains(\"container/plugins\"))\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Registry/TestCLIRegistry.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\nclass TestCLIRegistry: CLITest {\n    @Test func testListDefaultFormat() throws {\n        let (_, output, error, status) = try run(arguments: [\"registry\", \"list\"])\n        #expect(status == 0, \"registry list should succeed, stderr: \\(error)\")\n\n        // Check for table header\n        let requiredHeaders = [\"HOSTNAME\", \"USERNAME\", \"MODIFIED\", \"CREATED\"]\n        #expect(\n            requiredHeaders.allSatisfy { output.contains($0) },\n            \"output should contain all required headers\"\n        )\n    }\n\n    @Test func testListQuietMode() throws {\n        let (_, output, error, status) = try run(arguments: [\"registry\", \"list\", \"-q\"])\n        #expect(status == 0, \"registry list -q should succeed, stderr: \\(error)\")\n\n        #expect(!output.contains(\"HOSTNAME\"), \"quiet mode should not contain headers\")\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport AsyncHTTPClient\nimport ContainerAPIClient\nimport ContainerizationExtras\nimport ContainerizationOS\nimport Foundation\nimport Testing\n\n// FIXME: We've split the tests into two suites to prevent swamping\n// the API server with so many run commands that all wind up pulling\n// images.\n//\n// When https://github.com/swiftlang/swift-testing/pull/1390 lands\n// and is available on the CI runners, we can try setting the\n// environment variable to limit concurrency and rejoin these suites.\nclass TestCLIRunCommand1: CLITest {\n    func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    func getLowercasedTestName() -> String {\n        getTestName().lowercased()\n    }\n\n    @Test func testRunCommand() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [])\n            defer {\n                try? doStop(name: name)\n            }\n            let _ = try doExec(name: name, cmd: [\"date\"])\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandCWD() throws {\n        do {\n            let name = getTestName()\n            let expectedCWD = \"/tmp\"\n            try doLongRun(name: name, args: [\"--cwd\", expectedCWD])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"pwd\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == expectedCWD, \"expected current working directory to be \\(expectedCWD), instead got \\(output)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandEnv() throws {\n        do {\n            let name = getTestName()\n            let envData = \"FOO=bar\"\n            try doLongRun(name: name, args: [\"--env\", envData])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            #expect(\n                inspectResp.configuration.initProcess.environment.contains(envData),\n                \"environment variable \\(envData) not set in container configuration\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandEnvFile() throws {\n        do {\n            let name = getTestName()\n            let content = \"\"\"\n                # Really cool comment\n                FOO=bar\n                BAR=baz wow\n                URL=https://foo.bar?baz=wow\n                \"\"\"\n            let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(\"test.env\")\n            guard FileManager.default.createFile(atPath: tempFile.path(), contents: Data(content.utf8)) else {\n                Issue.record(\"failed to create temporary file \\(tempFile.path())\")\n                return\n            }\n            defer {\n                try? FileManager.default.removeItem(at: tempFile)\n            }\n            try doLongRun(name: name, args: [\"--env-file\", tempFile.path()])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            let expected = [\n                \"FOO=bar\",\n                \"BAR=baz wow\",\n                \"URL=https://foo.bar?baz=wow\",\n            ]\n            for item in expected {\n                #expect(\n                    inspectResp.configuration.initProcess.environment.contains(item),\n                    \"environment variable \\(item) not set in container configuration\")\n            }\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandUserIDGroupID() throws {\n        do {\n            let name = getTestName()\n            let uid = \"10\"\n            let gid = \"100\"\n            try doLongRun(name: name, args: [\"--uid\", uid, \"--gid\", gid])\n            defer {\n                try? doStop(name: name)\n            }\n\n            var output = try doExec(name: name, cmd: [\"id\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            try #expect(output.contains(Regex(\"uid=\\(uid).*?gid=\\(gid).*\")), \"invalid user/group id, got \\(output)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandUser() throws {\n        do {\n            let name = getTestName()\n            let user = \"nobody\"\n            try doLongRun(name: name, args: [\"--user\", user])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"whoami\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == user, \"expected user \\(user), got \\(output)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandCPUs() throws {\n        do {\n            let name = getTestName()\n            let cpus = \"2\"\n            try doLongRun(name: name, args: [\"--cpus\", cpus])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"nproc\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == cpus, \"expected \\(cpus), instead got \\(output)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandMemory() throws {\n        do {\n            let name = getTestName()\n            let expectedMBs = 1024\n            try doLongRun(name: name, args: [\"--memory\", \"\\(expectedMBs)M\"])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            let actualInBytes = inspectResp.configuration.resources.memoryInBytes\n            #expect(actualInBytes == expectedMBs.mib(), \"expected \\(expectedMBs.mib()) bytes, instead got \\(actualInBytes) bytes\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandUlimitNofile() throws {\n        do {\n            let name = getTestName()\n            let softLimit = \"1024\"\n            let hardLimit = \"2048\"\n            try doLongRun(name: name, args: [\"--ulimit\", \"nofile=\\(softLimit):\\(hardLimit)\"])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let inspectResp = try inspectContainer(name)\n            let rlimits = inspectResp.configuration.initProcess.rlimits\n            let nofileRlimit = rlimits.first { $0.limit == \"RLIMIT_NOFILE\" }\n            #expect(nofileRlimit != nil, \"expected RLIMIT_NOFILE to be set\")\n            #expect(nofileRlimit?.soft == UInt64(softLimit), \"expected soft limit \\(softLimit), got \\(nofileRlimit?.soft ?? 0)\")\n            #expect(nofileRlimit?.hard == UInt64(hardLimit), \"expected hard limit \\(hardLimit), got \\(nofileRlimit?.hard ?? 0)\")\n\n            var output = try doExec(name: name, cmd: [\"sh\", \"-c\", \"ulimit -n\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == softLimit, \"expected ulimit -n to return \\(softLimit), got \\(output)\")\n\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandUlimitNproc() throws {\n        do {\n            let name = getTestName()\n            let limit = \"256\"\n            try doLongRun(name: name, args: [\"--ulimit\", \"nproc=\\(limit)\"])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            let rlimits = inspectResp.configuration.initProcess.rlimits\n            let nprocRlimit = rlimits.first { $0.limit == \"RLIMIT_NPROC\" }\n            #expect(nprocRlimit != nil, \"expected RLIMIT_NPROC to be set\")\n            #expect(nprocRlimit?.soft == UInt64(limit), \"expected soft limit \\(limit), got \\(nprocRlimit?.soft ?? 0)\")\n            #expect(nprocRlimit?.hard == UInt64(limit), \"expected hard limit \\(limit), got \\(nprocRlimit?.hard ?? 0)\")\n\n            var output = try doExec(name: name, cmd: [\"sh\", \"-c\", \"ulimit -u\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == limit, \"expected ulimit -u to return \\(limit), got \\(output)\")\n\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandMultipleUlimits() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(\n                name: name,\n                args: [\n                    \"--ulimit\", \"nofile=1024:2048\",\n                    \"--ulimit\", \"nproc=512\",\n                    \"--ulimit\", \"stack=8388608\",\n                ])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            let rlimits = inspectResp.configuration.initProcess.rlimits\n            #expect(rlimits.count == 3, \"expected 3 rlimits, got \\(rlimits.count)\")\n\n            let nofile = rlimits.first { $0.limit == \"RLIMIT_NOFILE\" }\n            let nproc = rlimits.first { $0.limit == \"RLIMIT_NPROC\" }\n            let stack = rlimits.first { $0.limit == \"RLIMIT_STACK\" }\n\n            #expect(nofile != nil && nofile?.soft == 1024 && nofile?.hard == 2048)\n            #expect(nproc != nil && nproc?.soft == 512 && nproc?.hard == 512)\n            #expect(stack != nil && stack?.soft == 8_388_608 && stack?.hard == 8_388_608)\n\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n}\n\nclass TestCLIRunCommand2: CLITest {\n    func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    func getLowercasedTestName() -> String {\n        getTestName().lowercased()\n    }\n\n    @Test func testRunCommandMount() throws {\n        do {\n            let name = getTestName()\n            let targetContainerPath = \"/tmp/testmount\"\n            let testData = \"hello world\"\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            let tempFile = tempDir.appendingPathComponent(UUID().uuidString)\n            guard FileManager.default.createFile(atPath: tempFile.path(), contents: Data(testData.utf8)) else {\n                Issue.record(\"failed to create temporary file \\(tempFile.path())\")\n                return\n            }\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n            try doLongRun(name: name, args: [\"--mount\", \"type=virtiofs,source=\\(tempDir.path()),target=\\(targetContainerPath),readonly\"])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"cat\", \"\\(targetContainerPath)/\\(tempFile.lastPathComponent)\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == testData, \"expected file with content '\\(testData)', instead got '\\(output)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandUnixSocketMount() throws {\n        do {\n            let name = getTestName()\n            let socketPath = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n\n            let socketType = try UnixType(path: socketPath.path, unlinkExisting: true)\n            let socket = try Socket(type: socketType, closeOnDeinit: true)\n            try socket.listen()\n            defer {\n                try? socket.close()\n                try? FileManager.default.removeItem(at: socketPath)\n            }\n\n            try doLongRun(\n                name: name,\n                args: [\"-v\", \"\\(socketPath.path):/woo\"]\n            )\n            defer {\n                try? doStop(name: name)\n            }\n            let output = try doExec(name: name, cmd: [\"ls\", \"-alh\", \"woo\"])\n            let splitOutput = output.components(separatedBy: .whitespaces)\n            #expect(splitOutput.count > 0, \"expected split output of 'ls -alh' to be at least 1, instead got \\(splitOutput.count)\")\n\n            let perms = splitOutput[0]\n            let firstChar = perms[perms.startIndex]\n            #expect(firstChar == \"s\", \"expected file in guest to be of type socket, instead got '\\(firstChar)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandTmpfs() throws {\n        do {\n            let name = getTestName()\n            let targetContainerPath = \"/tmp/testtmpfs\"\n            let expectedFilesystem = \"tmpfs\"\n            try doLongRun(name: name, args: [\"--tmpfs\", targetContainerPath])\n            defer {\n                try? doStop(name: name)\n            }\n            let output = try doExec(name: name, cmd: [\"df\", targetContainerPath])\n            let lines = output.split(separator: \"\\n\")\n            #expect(lines.count == 2, \"expected only two rows of output, instead got \\(lines.count)\")\n            let words = lines[1].split(separator: \" \")\n            #expect(words.count > 1, \"expected information to contain multiple words, got \\(words.count)\")\n            #expect(words[0].lowercased() == expectedFilesystem, \"expected filesystem type to be \\(expectedFilesystem), instead got \\(output)\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandOSArch() throws {\n        do {\n            let name = getLowercasedTestName()\n            let os = \"linux\"\n            let arch = \"amd64\"\n            let expectedArch = \"x86_64\"\n            try doLongRun(name: name, args: [\"--os\", os, \"--arch\", arch])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"uname\", \"-sm\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n            #expect(output == \"\\(os) \\(expectedArch)\", \"expected container to use '\\(os) \\(expectedArch)', instead got '\\(output)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandPlatform() throws {\n        do {\n            let name = getTestName()\n            let os = \"linux\"\n            let platform = \"linux/amd64\"\n            let expectedArch = \"x86_64\"\n            try doLongRun(name: name, args: [\"--platform\", platform])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"uname\", \"-sm\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()\n            #expect(output == \"\\(os) \\(expectedArch)\", \"expected container to use '\\(os) \\(expectedArch)', instead got '\\(output)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandVolume() throws {\n        do {\n            let name = getTestName()\n            let targetContainerPath = \"/tmp/testvolume\"\n            let testData = \"one small step\"\n            let volume = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: volume, withIntermediateDirectories: true)\n            let volumeFile = volume.appendingPathComponent(UUID().uuidString)\n            guard FileManager.default.createFile(atPath: volumeFile.path(), contents: Data(testData.utf8)) else {\n                Issue.record(\"failed to create file at \\(volumeFile)\")\n                return\n            }\n            defer {\n                try? FileManager.default.removeItem(at: volume)\n            }\n            try doLongRun(name: name, args: [\"--volume\", \"\\(volume.path):\\(targetContainerPath)\"])\n            defer {\n                try? doStop(name: name)\n            }\n            var output = try doExec(name: name, cmd: [\"cat\", \"\\(targetContainerPath)/\\(volumeFile.lastPathComponent)\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(output == testData, \"expected file with content '\\(testData)', instead got '\\(output)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandCidfile() throws {\n        do {\n            let name = getTestName()\n            let filePath = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n            defer {\n                try? FileManager.default.removeItem(at: filePath)\n            }\n            try doLongRun(name: name, args: [\"--cidfile\", filePath.path()])\n            defer {\n                try? doStop(name: name)\n            }\n            let actualID = try String(contentsOf: filePath, encoding: .utf8)\n            #expect(actualID == name, \"expected container ID '\\(name)', instead got '\\(actualID)'\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandNoDNS() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [\"--no-dns\"])\n            defer {\n                try? doStop(name: name)\n            }\n            #expect(throws: (any Error).self) {\n                try doExec(name: name, cmd: [\"cat\", \"/etc/resolv.conf\"])\n            }\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandInit() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [\"--init\"])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            #expect(inspectResp.configuration.useInit == true, \"expected useInit to be true in container configuration\")\n\n            // With --init, PID 1 should be the init process, not \"sleep\".\n            var output = try doExec(name: name, cmd: [\"cat\", \"/proc/1/cmdline\"])\n            output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n            #expect(\n                !output.hasPrefix(\"sleep\"),\n                \"expected PID 1 to be init process, not 'sleep', got '\\(output)'\"\n            )\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container with --init: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandInitReapsZombies() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [\"--init\"])\n            defer {\n                try? doStop(name: name)\n            }\n\n            _ = try doExec(\n                name: name,\n                cmd: [\n                    \"sh\", \"-c\",\n                    \"sh -c 'sh -c \\\"exit 0\\\" &' && sleep 1\",\n                ])\n\n            let psOutput = try doExec(name: name, cmd: [\"sh\", \"-c\", \"ps aux | grep -c '\\\\[sh\\\\]' || true\"])\n            let zombieCount = Int(psOutput.trimmingCharacters(in: .whitespacesAndNewlines)) ?? -1\n            #expect(\n                zombieCount == 0,\n                \"expected no zombie processes with --init, found \\(zombieCount)\"\n            )\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to verify zombie reaping with --init: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandWithoutInitDefault() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [])\n            defer {\n                try? doStop(name: name)\n            }\n            let inspectResp = try inspectContainer(name)\n            #expect(inspectResp.configuration.useInit == false, \"expected useInit to be false by default\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container without --init: \\(error)\")\n            return\n        }\n    }\n}\n\nclass TestCLIRunCommand3: CLITest {\n    func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    func getLowercasedTestName() -> String {\n        getTestName().lowercased()\n    }\n\n    @Test func testRunCommandDefaultResolvConf() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let output = try doExec(name: name, cmd: [\"cat\", \"/etc/resolv.conf\"])\n            let actualLines = output.components(separatedBy: .newlines)\n                .filter { !$0.isEmpty }\n                .map { $0.components(separatedBy: .whitespaces) }\n                .map { $0.joined(separator: \" \") }\n\n            let inspectOutput = try inspectContainer(name)\n            let ip = inspectOutput.networks[0].ipv4Address.address\n            let expectedNameserver = IPv4Address((ip.value & Prefix(length: 24)!.prefixMask32) + 1).description\n            let defaultDomain = try getDefaultDomain()\n            let expectedLines: [String] = [\n                \"nameserver \\(expectedNameserver)\",\n                defaultDomain.map { \"domain \\($0)\" },\n            ].compactMap { $0 }\n\n            #expect(expectedLines == actualLines)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandNonDefaultResolvConf() throws {\n        do {\n            let expectedDns: String = \"8.8.8.8\"\n            let expectedDomain = \"example.com\"\n            let expectedSearch = \"test.com\"\n            let expectedOption = \"debug\"\n            let name = getTestName()\n            try doLongRun(\n                name: name,\n                args: [\n                    \"--dns\", expectedDns,\n                    \"--dns-domain\", expectedDomain,\n                    \"--dns-search\", expectedSearch,\n                    \"--dns-option\", expectedOption,\n                ])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let output = try doExec(name: name, cmd: [\"cat\", \"/etc/resolv.conf\"])\n            let actualLines = output.components(separatedBy: .newlines)\n                .filter { !$0.isEmpty }\n                .map { $0.components(separatedBy: .whitespaces) }\n                .map { $0.joined(separator: \" \") }\n\n            let expectedLines: [String] = [\n                \"nameserver \\(expectedDns)\",\n                \"domain \\(expectedDomain)\",\n                \"search \\(expectedSearch)\",\n                \"options \\(expectedOption)\",\n            ]\n            #expect(expectedLines == actualLines)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunDefaultHostsEntries() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name)\n            defer {\n                try? doStop(name: name)\n            }\n\n            let inspectOutput = try inspectContainer(name)\n            let ip = inspectOutput.networks[0].ipv4Address.address\n\n            let output = try doExec(name: name, cmd: [\"cat\", \"/etc/hosts\"])\n            let lines = output.split(separator: \"\\n\")\n\n            let expectedEntries = [(\"127.0.0.1\", \"localhost\"), (ip.description, name)]\n\n            for (i, line) in lines.enumerated() {\n                let words = line.split(separator: \" \").map { String($0) }\n                #expect(words.count >= 2, \"expected /etc/hosts entry to have 2 or more entries\")\n                let expected = expectedEntries[i]\n                #expect(expected.0 == words[0], \"expected /etc/hosts entries IP to be \\(expected.0), instead got \\(words[0])\")\n                #expect(expected.1 == words[1], \"expected /etc/hosts entries host to be \\(expected.1), instead got \\(words[1])\")\n            }\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testForwardTCP() async throws {\n        let retries = 10\n        let retryDelaySeconds = Int64(3)\n        do {\n            let name = getLowercasedTestName()\n            let proxyIp = \"127.0.0.1\"\n            let proxyPort = UInt16.random(in: 50000..<55000)\n            let serverPort = UInt16.random(in: 55000..<60000)\n            try doLongRun(\n                name: name,\n                image: \"docker.io/library/python:alpine\",\n                args: [\"--publish\", \"\\(proxyIp):\\(proxyPort):\\(serverPort)/tcp\"],\n                containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(serverPort)\"])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let url = \"http://\\(proxyIp):\\(proxyPort)\"\n            var request = HTTPClientRequest(url: url)\n            request.method = .GET\n            let config = HTTPClient.Configuration(proxy: nil)\n            let client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config)\n            defer { _ = client.shutdown() }\n            var retriesRemaining = retries\n            var success = false\n            while !success && retriesRemaining > 0 {\n                do {\n                    let response = try await client.execute(request, timeout: .seconds(retryDelaySeconds))\n                    try #require(response.status == .ok)\n                    success = true\n                    print(\"request to \\(url) succeeded\")\n                } catch {\n                    print(\"request to \\(url) failed, error \\(error)\")\n                    try await Task.sleep(for: .seconds(retryDelaySeconds))\n                }\n                retriesRemaining -= 1\n            }\n            try #require(success, \"Request to \\(url) failed after \\(retries - retriesRemaining) retries\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testForwardTCPPortRange() async throws {\n        let range = UInt16(10)\n        for portOffset in 0..<range {\n            let retries = 10\n            let retryDelaySeconds = Int64(3)\n            do {\n                let name = getLowercasedTestName()\n                let proxyIp = \"127.0.0.1\"\n                let proxyPortStart = UInt16.random(in: 50000..<55000)\n                let serverPortStart = UInt16.random(in: 55000..<60000)\n                let proxyPortEnd = proxyPortStart + range\n                let serverPortEnd = serverPortStart + range\n                try doLongRun(\n                    name: name,\n                    image: \"docker.io/library/python:alpine\",\n                    args: [\"--publish\", \"\\(proxyIp):\\(proxyPortStart)-\\(proxyPortEnd):\\(serverPortStart)-\\(serverPortEnd)/tcp\"],\n                    containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(serverPortStart + portOffset)\"])\n                defer {\n                    try? doStop(name: name)\n                }\n\n                let url = \"http://\\(proxyIp):\\(proxyPortStart + portOffset)\"\n                var request = HTTPClientRequest(url: url)\n                request.method = .GET\n                let config = HTTPClient.Configuration(proxy: nil)\n                let client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config)\n                defer { _ = client.shutdown() }\n                var retriesRemaining = retries\n                var success = false\n                while !success && retriesRemaining > 0 {\n                    do {\n                        let response = try await client.execute(request, timeout: .seconds(retryDelaySeconds))\n                        try #require(response.status == .ok)\n                        success = true\n                        print(\"request to \\(url) succeeded\")\n                    } catch {\n                        print(\"request to \\(url) failed, error: \\(error)\")\n                        try await Task.sleep(for: .seconds(retryDelaySeconds))\n                    }\n                    retriesRemaining -= 1\n                }\n                try #require(success, \"Request to \\(url) failed after \\(retries - retriesRemaining) retries\")\n                try doStop(name: name)\n            } catch {\n                Issue.record(\"failed to run container \\(error)\")\n                return\n            }\n        }\n    }\n\n    @available(macOS 26, *)\n    @Test func testForwardTCPv6() async throws {\n        let retries = 10\n        let retryDelaySeconds = Int64(3)\n        do {\n            let name = getLowercasedTestName()\n            let proxyIp = \"[::1]\"\n            let proxyPort = UInt16.random(in: 50000..<55000)\n            let serverPort = UInt16.random(in: 55000..<60000)\n            try doLongRun(\n                name: name,\n                image: \"docker.io/library/node:alpine\",\n                args: [\"--publish\", \"\\(proxyIp):\\(proxyPort):\\(serverPort)/tcp\"],\n                containerArgs: [\"npx\", \"http-server\", \"-a\", \"::\", \"-p\", \"\\(serverPort)\"])\n            defer {\n                try? doStop(name: name)\n            }\n\n            let url = \"http://\\(proxyIp):\\(proxyPort)\"\n            var request = HTTPClientRequest(url: url)\n            request.method = .GET\n            let config = HTTPClient.Configuration(proxy: nil)\n            let client = HTTPClient(eventLoopGroupProvider: .singleton, configuration: config)\n            defer { _ = client.shutdown() }\n            var retriesRemaining = retries\n            var success = false\n            while !success && retriesRemaining > 0 {\n                do {\n                    let response = try await client.execute(request, timeout: .seconds(retryDelaySeconds))\n                    try #require(response.status == .ok)\n                    success = true\n                    print(\"request to \\(url) succeeded\")\n                } catch {\n                    print(\"request to \\(url) failed, error \\(error)\")\n                    try await Task.sleep(for: .seconds(retryDelaySeconds))\n                }\n                retriesRemaining -= 1\n            }\n            try #require(success, \"Request to \\(url) failed after \\(retries - retriesRemaining) retries\")\n            try doStop(name: name)\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    @Test func testRunCommandEnvFileFromNamedPipe() throws {\n        do {\n            let name = getTestName()\n            let pipePath = FileManager.default.temporaryDirectory.appendingPathComponent(\"envfile-pipe\\(UUID().uuidString)\")\n\n            // create pipe\n            let result = mkfifo(pipePath.path(), 0o600)\n            guard result == 0 else {\n                Issue.record(\"failed to create named pipe: \\(String(cString: strerror(errno)))\")\n                return\n            }\n\n            defer {\n                try? FileManager.default.removeItem(at: pipePath)\n            }\n\n            let content = \"\"\"\n                FOO=bar\n                BAR=baz\n                \"\"\"\n\n            let group = DispatchGroup()\n\n            group.enter()\n            DispatchQueue.global().async {\n                do {\n                    let handle = try FileHandle(forWritingTo: pipePath)\n                    try handle.write(contentsOf: Data(content.utf8))\n                    try handle.close()\n                } catch {\n                    Issue.record(error)\n                    return\n                }\n\n                group.leave()\n            }\n\n            try doLongRun(name: name, args: [\"--env-file\", pipePath.path()])\n            defer {\n                try? doStop(name: name)\n            }\n\n            group.wait()\n\n            let inspectResult = try inspectContainer(name)\n            let expected = [\n                \"FOO=bar\",\n                \"BAR=baz\",\n            ]\n\n            for item in expected {\n                #expect(\n                    inspectResult.configuration.initProcess.environment.contains(item),\n                    \"expected environment variable \\(item) not found\"\n                )\n            }\n            try doStop(name: name)\n        } catch {\n            Issue.record(error)\n        }\n    }\n\n    @Test func testRunCommandReadOnly() throws {\n        do {\n            let name = getTestName()\n            try doLongRun(name: name, args: [\"--read-only\"])\n            defer {\n                try? doStop(name: name)\n            }\n            // Attempt to touch a file on the read-only rootfs should fail\n            #expect(throws: (any Error).self) {\n                try doExec(name: name, cmd: [\"touch\", \"/testfile\"])\n            }\n        } catch {\n            Issue.record(\"failed to run container \\(error)\")\n            return\n        }\n    }\n\n    func getDefaultDomain() throws -> String? {\n        let (_, output, err, status) = try run(arguments: [\"system\", \"property\", \"get\", \"dns.domain\"])\n        try #require(status == 0, \"default DNS domain retrieval returned status \\(status): \\(err)\")\n        let trimmedOutput = output.trimmingCharacters(in: .whitespacesAndNewlines)\n        if trimmedOutput == \"\" {\n            return nil\n        }\n\n        return trimmedOutput\n    }\n\n    @Test func testPrivilegedPortError() throws {\n        try #require(geteuid() != 0)\n\n        let name = getTestName()\n        let privilegedPort = 80\n        let (_, _, error, status) = try run(arguments: [\n            \"run\",\n            \"--name\", name,\n            \"--publish\", \"127.0.0.1:\\(privilegedPort):80\",\n            alpine,\n        ])\n        defer {\n            try? doRemove(name: name, force: true)\n        }\n        #expect(status != 0, \"Command should have failed\")\n        #expect(\n            error.contains(\"Permission denied while binding to host port \\(privilegedPort)\"),\n            \"Error message should mention permission denied for the port. Got: \\(error)\"\n        )\n        #expect(\n            error.contains(\"root privileges\"),\n            \"Error message should mention root privileges requirement. Got: \\(error)\"\n        )\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Run/TestCLIRunInitImage.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n/// Tests for the `--init-image` flag which allows specifying a custom init filesystem\n/// image for microvms. This enables customizing boot-time behavior before the OCI\n/// container starts.\n///\n/// See: https://github.com/apple/container/discussions/838\n///\n/// Note: A full integration test that verifies custom init behavior would require\n/// a pre-built test init image that writes a marker to /dev/kmsg. This can be added\n/// once a test init image is published to the registry.\nclass TestCLIRunInitImage: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    /// Test that specifying a non-existent init-image fails with an appropriate error.\n    @Test func testRunWithNonExistentInitImage() throws {\n        let name = getTestName()\n        let nonExistentImage = \"nonexistent.invalid/init-image:does-not-exist\"\n\n        #expect(throws: CLIError.self, \"expected container run with non-existent init-image to fail\") {\n            let (_, _, error, status) = try run(arguments: [\n                \"run\",\n                \"--rm\",\n                \"--name\", name,\n                \"-d\",\n                \"--init-image\", nonExistentImage,\n                alpine,\n                \"sleep\", \"infinity\",\n            ])\n            defer { try? doRemove(name: name, force: true) }\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n        }\n    }\n\n    /// Test that the `--init-image` flag is recognized and documented in CLI help.\n    @Test func testInitImageFlagInHelp() throws {\n        let (_, output, _, status) = try run(arguments: [\"run\", \"--help\"])\n        #expect(status == 0, \"expected help command to succeed\")\n        #expect(\n            output.contains(\"--init-image\"),\n            \"expected help output to contain --init-image flag\"\n        )\n        #expect(\n            output.contains(\"custom init image\"),\n            \"expected help output to describe the init-image flag\"\n        )\n    }\n\n    /// Test that the `--init-image` flag works with `container create` command.\n    @Test func testCreateWithNonExistentInitImage() throws {\n        let name = getTestName()\n        let nonExistentImage = \"nonexistent.invalid/init-image:does-not-exist\"\n\n        #expect(throws: CLIError.self, \"expected container create with non-existent init-image to fail\") {\n            let (_, _, error, status) = try run(arguments: [\n                \"create\",\n                \"--rm\",\n                \"--name\", name,\n                \"--init-image\", nonExistentImage,\n                alpine,\n                \"echo\", \"hello\",\n            ])\n            defer { try? doRemove(name: name, force: true) }\n            if status != 0 {\n                throw CLIError.executionFailed(\"command failed: \\(error)\")\n            }\n        }\n    }\n\n    /// Test that explicitly specifying the default init image works the same as\n    /// not specifying any init image.\n    @Test func testRunWithExplicitDefaultInitImage() throws {\n        let name = getTestName()\n\n        // Get the default init image reference\n        let (_, defaultInitImage, _, propStatus) = try run(arguments: [\n            \"system\", \"property\", \"get\", \"image.init\",\n        ])\n\n        guard propStatus == 0 else {\n            print(\"Skipping testRunWithExplicitDefaultInitImage: could not get default init image\")\n            return\n        }\n\n        let initImage = defaultInitImage.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        // Run container with explicit default init image\n        try doLongRun(name: name, args: [\"--init-image\", initImage])\n        defer {\n            try? doStop(name: name)\n        }\n\n        // Verify container is running and functional\n        try waitForContainerRunning(name)\n        let output = try doExec(name: name, cmd: [\"echo\", \"hello\"])\n        #expect(\n            output.trimmingCharacters(in: .whitespacesAndNewlines) == \"hello\",\n            \"expected 'hello' output from exec, got '\\(output)'\"\n        )\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Run/TestCLIRunLifecycle.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport Testing\n\nclass TestCLIRunLifecycle: CLITest {\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testRunFailureCleanup() throws {\n        let name = getTestName()\n\n        // try to create a container we know will fail\n        let badArgs: [String] = [\n            \"--rm\",\n            \"--user\",\n            name,\n        ]\n        #expect(throws: CLIError.self, \"expect container to fail with invalid user\") {\n            try self.doLongRun(name: name, args: badArgs)\n        }\n\n        // try to create a container with the same name but no user that should succeed\n        #expect(throws: Never.self, \"expected container run to succeed\") {\n            try self.doLongRun(name: name, args: [])\n            defer {\n                try? self.doStop(name: name)\n            }\n            let _ = try self.doExec(name: name, cmd: [\"date\"])\n            try self.doStop(name: name)\n        }\n    }\n\n    @Test func testStartIdempotent() throws {\n        let name = getTestName()\n\n        #expect(throws: Never.self, \"expected container run to succeed\") {\n            try self.doLongRun(name: name, args: [])\n            defer {\n                try? self.doStop(name: name)\n            }\n            try self.waitForContainerRunning(name)\n\n            let (_, output, _, status) = try self.run(arguments: [\"start\", name])\n            #expect(status == 0, \"expected start to succeed on already running container\")\n            #expect(output.trimmingCharacters(in: .whitespacesAndNewlines) == name, \"expected output to be container name\")\n\n            // Don't care about the resp, just that the container is still there and not cleaned up.\n            let _ = try inspectContainer(name)\n\n            try self.doStop(name: name)\n        }\n    }\n\n    @Test func testStartIdempotentAttachFails() throws {\n        let name = getTestName()\n\n        #expect(throws: Never.self, \"expected container run to succeed\") {\n            try self.doLongRun(name: name, args: [])\n            defer {\n                try? self.doStop(name: name)\n            }\n            try self.waitForContainerRunning(name)\n\n            let (_, _, error, status) = try self.run(arguments: [\"start\", \"-a\", name])\n            #expect(status != 0, \"expected start with attach to fail on already running container\")\n            #expect(error.contains(\"attach is currently unsupported on already running containers\"), \"expected error message about attach not supported\")\n\n            try self.doStop(name: name)\n        }\n    }\n\n    @Test func testStartPortBindFails() async throws {\n        let port = UInt16.random(in: 50000..<60000)\n\n        let name = getTestName()\n        try self.doCreate(name: name, ports: [\"\\(port)\"])\n        defer {\n            try? self.doRemove(name: name)\n        }\n\n        let server = \"\\(name)-server\"\n        try doLongRun(\n            name: server,\n            image: \"docker.io/library/python:alpine\",\n            args: [\"--publish\", \"\\(port):\\(port)\"],\n            containerArgs: [\"python3\", \"-m\", \"http.server\", \"\\(port)\"]\n        )\n        defer {\n            try? doStop(name: server)\n        }\n\n        #expect(throws: CLIError.self) {\n            try doStart(name: name)\n        }\n\n        let status = try getContainerStatus(name)\n        #expect(status == \"stopped\")\n    }\n\n    @Test func testRunInvalidExcutable() async throws {\n        let name = getTestName()\n        #expect(throws: CLIError.self, \"running invalid executable must throw error, not hang\") {\n            try doLongRun(\n                name: name,\n                containerArgs: [\"foobarbaz\"]\n            )\n        }\n        try? doRemove(name: name)\n    }\n\n    @Test func testExecInvalidExcutable() async throws {\n        let name = getTestName()\n        try doLongRun(name: name)\n        defer {\n            try? doStop(name: name)\n        }\n\n        #expect(throws: CLIError.self, \"executing invalid executable must throw error, not hang\") {\n            try doExec(\n                name: name,\n                cmd: [\"foobarbaz\"]\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/System/TestCLIStatus.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n/// Tests for `container system status` output formats and content validation.\nfinal class TestCLIStatus: CLITest {\n    struct StatusJSON: Codable {\n        let status: String\n        let appRoot: String\n        let installRoot: String\n        let logRoot: String?\n        let apiServerVersion: String\n        let apiServerCommit: String\n        let apiServerBuild: String\n        let apiServerAppName: String\n    }\n\n    @Test func defaultDisplaysTable() throws {\n        let (data, out, _, status) = try run(arguments: [\"system\", \"status\"])  // default is table\n\n        // If apiserver is not running, skip this test\n        guard status == 0 else {\n            return\n        }\n\n        #expect(!out.isEmpty)\n\n        // Validate table structure\n        let lines = out.trimmingCharacters(in: .whitespacesAndNewlines)\n            .components(separatedBy: .newlines)\n        #expect(lines.count >= 2)  // header + at least one data row\n\n        // Check for header row\n        #expect(lines[0].contains(\"FIELD\") && lines[0].contains(\"VALUE\"))\n\n        // Check for key fields in output\n        let fullOutput = lines.joined(separator: \"\\n\")\n        #expect(fullOutput.contains(\"status\"))\n        #expect(fullOutput.contains(\"running\"))\n        #expect(fullOutput.contains(\"app-root\"))\n        #expect(fullOutput.contains(\"install-root\"))\n        #expect(fullOutput.contains(\"apiserver-version\"))\n        #expect(fullOutput.contains(\"apiserver-commit\"))\n\n        _ = data  // silence unused warning if assertions short-circuit\n    }\n\n    @Test func jsonFormat() throws {\n        let (data, out, _, status) = try run(arguments: [\"system\", \"status\", \"--format\", \"json\"])\n\n        // If apiserver is not running, validate error JSON\n        if status != 0 {\n            #expect(!out.isEmpty)\n            let decoded = try JSONDecoder().decode(StatusJSON.self, from: data)\n            #expect(decoded.status == \"not running\" || decoded.status == \"unregistered\")\n            return\n        }\n\n        #expect(!out.isEmpty)\n\n        let decoded = try JSONDecoder().decode(StatusJSON.self, from: data)\n        #expect(decoded.status == \"running\")\n        #expect(!decoded.appRoot.isEmpty)\n        #expect(!decoded.installRoot.isEmpty)\n        #expect(!decoded.apiServerVersion.isEmpty)\n        #expect(!decoded.apiServerCommit.isEmpty)\n        #expect(!decoded.apiServerBuild.isEmpty)\n        #expect(!decoded.apiServerAppName.isEmpty)\n    }\n\n    @Test func explicitTableFormat() throws {\n        let (_, out, _, status) = try run(arguments: [\"system\", \"status\", \"--format\", \"table\"])\n\n        // If apiserver is not running, skip validation\n        guard status == 0 else {\n            return\n        }\n\n        #expect(!out.isEmpty)\n\n        let lines = out.trimmingCharacters(in: .whitespacesAndNewlines)\n            .components(separatedBy: .newlines)\n        #expect(lines.count >= 2)\n        #expect(lines[0].contains(\"FIELD\") && lines[0].contains(\"VALUE\"))\n\n        let fullOutput = lines.joined(separator: \"\\n\")\n        #expect(fullOutput.contains(\"status\"))\n        #expect(fullOutput.contains(\"running\"))\n    }\n\n    @Test func statusFieldsMatch() throws {\n        // Validate that JSON and table outputs contain the same information\n        let (jsonData, _, _, jsonStatus) = try run(arguments: [\"system\", \"status\", \"--format\", \"json\"])\n        let (_, tableOut, _, tableStatus) = try run(arguments: [\"system\", \"status\", \"--format\", \"table\"])\n\n        #expect(jsonStatus == tableStatus)\n\n        // If apiserver is not running, just verify consistency\n        guard jsonStatus == 0 else {\n            return\n        }\n\n        let decoded = try JSONDecoder().decode(StatusJSON.self, from: jsonData)\n\n        // Verify table output contains key values from JSON\n        #expect(tableOut.contains(decoded.status))\n        #expect(tableOut.contains(decoded.appRoot))\n        #expect(tableOut.contains(decoded.installRoot))\n        #expect(tableOut.contains(decoded.apiServerVersion))\n        #expect(tableOut.contains(decoded.apiServerCommit))\n        #expect(tableOut.contains(decoded.apiServerBuild))\n        #expect(tableOut.contains(decoded.apiServerAppName))\n    }\n\n    @Test func jsonOutputValidStructure() throws {\n        let (data, _, _, status) = try run(arguments: [\"system\", \"status\", \"--format\", \"json\"])\n\n        // Should always produce valid JSON regardless of status\n        #expect(throws: Never.self) {\n            _ = try JSONDecoder().decode(StatusJSON.self, from: data)\n        }\n\n        let decoded = try JSONDecoder().decode(StatusJSON.self, from: data)\n\n        if status == 0 {\n            // When running, all fields should be populated\n            #expect(decoded.status == \"running\")\n            #expect(!decoded.appRoot.isEmpty)\n            #expect(!decoded.installRoot.isEmpty)\n            #expect(!decoded.apiServerVersion.isEmpty)\n        } else {\n            // When not running, status should indicate the issue\n            #expect(decoded.status == \"not running\" || decoded.status == \"unregistered\")\n        }\n    }\n\n    @Test func prefixOption() throws {\n        // Test with explicit prefix (should work the same as default)\n        let (_, out, _, status) = try run(arguments: [\"system\", \"status\", \"--prefix\", \"com.apple.container.\"])\n\n        guard status == 0 else {\n            return\n        }\n\n        #expect(!out.isEmpty)\n        let lines = out.trimmingCharacters(in: .whitespacesAndNewlines)\n            .components(separatedBy: .newlines)\n        #expect(lines.count >= 2)\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/System/TestCLIVersion.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n/// Tests for `container system version` output formats and build type detection.\nfinal class TestCLIVersion: CLITest {\n    struct VersionInfo: Codable {\n        let version: String\n        let buildType: String\n        let commit: String\n        let appName: String\n    }\n\n    struct VersionJSON: Codable {\n        let version: String\n        let buildType: String\n        let commit: String\n        let appName: String\n        let server: VersionInfo?\n    }\n\n    private func expectedBuildType() throws -> String {\n        let path = try executablePath\n        if path.path.contains(\"/debug/\") {\n            return \"debug\"\n        } else if path.path.contains(\"/release/\") {\n            return \"release\"\n        }\n        // Fallback: prefer debug when ambiguous (matches SwiftPM default for tests)\n        return \"debug\"\n    }\n\n    @Test func defaultDisplaysTable() throws {\n        let (data, out, err, status) = try run(arguments: [\"system\", \"version\"])  // default is table\n        #expect(status == 0, \"system version should succeed, stderr: \\(err)\")\n        #expect(!out.isEmpty)\n\n        // Validate table structure\n        let lines = out.trimmingCharacters(in: .whitespacesAndNewlines)\n            .components(separatedBy: .newlines)\n        #expect(lines.count >= 2)  // header + at least CLI row\n        #expect(lines[0].contains(\"COMPONENT\") && lines[0].contains(\"VERSION\") && lines[0].contains(\"BUILD\") && lines[0].contains(\"COMMIT\"))\n        #expect(lines[1].hasPrefix(\"CLI \"))\n\n        // Build should reflect the binary we are running (debug/release)\n        let expected = try expectedBuildType()\n        #expect(lines.joined(separator: \"\\n\").contains(\" CLI \"))\n        #expect(lines.joined(separator: \"\\n\").contains(\" \\(expected) \"))\n        _ = data  // silence unused warning if assertions short-circuit\n    }\n\n    @Test func jsonFormat() throws {\n        let (data, out, err, status) = try run(arguments: [\"system\", \"version\", \"--format\", \"json\"])\n        #expect(status == 0, \"system version --format json should succeed, stderr: \\(err)\")\n        #expect(!out.isEmpty)\n\n        let decoded = try JSONDecoder().decode(VersionJSON.self, from: data)\n        #expect(decoded.appName == \"container CLI\")\n        #expect(!decoded.version.isEmpty)\n        #expect(!decoded.commit.isEmpty)\n\n        let expected = try expectedBuildType()\n        #expect(decoded.buildType == expected)\n    }\n\n    @Test func explicitTableFormat() throws {\n        let (_, out, err, status) = try run(arguments: [\"system\", \"version\", \"--format\", \"table\"])\n        #expect(status == 0, \"system version --format table should succeed, stderr: \\(err)\")\n        #expect(!out.isEmpty)\n\n        let lines = out.trimmingCharacters(in: .whitespacesAndNewlines)\n            .components(separatedBy: .newlines)\n        #expect(lines.count >= 2)\n        #expect(lines[0].contains(\"COMPONENT\") && lines[0].contains(\"VERSION\") && lines[0].contains(\"BUILD\") && lines[0].contains(\"COMMIT\"))\n        #expect(lines[1].hasPrefix(\"CLI \"))\n    }\n\n    @Test func buildTypeMatchesBinary() throws {\n        // Validate build type via JSON to avoid parsing table text loosely\n        let (data, _, err, status) = try run(arguments: [\"system\", \"version\", \"--format\", \"json\"])\n        #expect(status == 0, \"version --format json should succeed, stderr: \\(err)\")\n        let decoded = try JSONDecoder().decode(VersionJSON.self, from: data)\n\n        let expected = try expectedBuildType()\n        #expect(decoded.buildType == expected, \"Expected build type \\(expected) but got \\(decoded.buildType)\")\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/System/TestKernelSet.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationArchive\nimport Foundation\nimport Testing\n\n// This suite is run serialized since each test modifies the global default kernel\n@Suite(.serialized)\nclass TestCLIKernelSet: CLITest {\n    let defaultKernelTar = DefaultsStore.get(key: .defaultKernelURL)\n    var remoteTar: URL! {\n        URL(string: defaultKernelTar)\n    }\n    let defaultBinaryPath = DefaultsStore.get(key: .defaultKernelBinaryPath)\n\n    deinit {\n        try? resetDefaultBinary()\n    }\n\n    func resetDefaultBinary() throws {\n        let arguments: [String] = [\n            \"system\",\n            \"kernel\",\n            \"set\",\n            \"--recommended\",\n            \"--force\",\n        ]\n        let (_, _, error, status) = try run(arguments: arguments)\n        if status != 0 {\n            throw CLIError.executionFailed(\"failed to reset kernel to recommended: \\(error)\")\n        }\n    }\n\n    func doKernelSet(extraArgs: [String]) throws {\n        var arguments = [\n            \"system\",\n            \"kernel\",\n            \"set\",\n            \"--force\",\n        ]\n        arguments.append(contentsOf: extraArgs)\n\n        let (_, _, error, status) = try run(arguments: arguments)\n        if status != 0 {\n            throw CLIError.executionFailed(\"failed to set kernel: \\(error)\")\n        }\n    }\n\n    func validateContainerRun() throws {\n        let name = getTestName()\n        try doLongRun(name: name, args: [])\n        defer { try? doStop(name: name) }\n\n        _ = try doExec(name: name, cmd: [\"date\"])\n        try doStop(name: name)\n    }\n\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func fromLocalTar() async throws {\n        let symlinkBinaryPath: String = URL(filePath: defaultBinaryPath).deletingLastPathComponent().appending(path: \"vmlinux.container\").relativePath\n\n        try await withTempDir { tempDir in\n            // manually download the tar file\n            let localTarPath = tempDir.appending(path: remoteTar.lastPathComponent)\n            try await ContainerAPIClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)\n\n            let extraArgs: [String] = [\n                \"--tar\",\n                localTarPath.path,\n                \"--binary\",\n                symlinkBinaryPath,\n            ]\n\n            try doKernelSet(extraArgs: extraArgs)\n            try validateContainerRun()\n        }\n    }\n\n    @Test func fromRemoteTarSymlink() throws {\n        // opt/kata/share/kata-containers/vmlinux.container should point to opt/kata/share/kata-containers/vmlinux-<version> in the archive\n        let symlinkBinaryPath: String = URL(filePath: defaultBinaryPath).deletingLastPathComponent().appending(path: \"vmlinux.container\").relativePath\n        let extraArgs: [String] = [\n            \"--tar\",\n            defaultKernelTar,\n            \"--binary\",\n            symlinkBinaryPath,\n        ]\n\n        try doKernelSet(extraArgs: extraArgs)\n        try validateContainerRun()\n    }\n\n    @Test func fromLocalDisk() async throws {\n        try await withTempDir { tempDir in\n            // manually download the tar file\n            let localTarPath = tempDir.appending(path: remoteTar.lastPathComponent)\n            try await ContainerAPIClient.FileDownloader.downloadFile(url: remoteTar, to: localTarPath)\n\n            // extract just the file we want\n            let targetPath = tempDir.appending(path: URL(string: defaultBinaryPath)!.lastPathComponent)\n            let archiveReader = try ArchiveReader(file: localTarPath)\n            let (_, data) = try archiveReader.extractFile(path: defaultBinaryPath)\n            try data.write(to: targetPath, options: .atomic)\n\n            let extraArgs = [\n                \"--binary\",\n                targetPath.path,\n            ]\n            try doKernelSet(extraArgs: extraArgs)\n            try validateContainerRun()\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Volumes/TestCLIAnonymousVolumes.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nclass TestCLIAnonymousVolumes: CLITest {\n\n    override init() throws {\n        try super.init()\n        // Clean up any leftover resources from previous test runs\n        cleanUpAllTestResources()\n    }\n\n    private func cleanUpAllTestResources() {\n        // Clean up test containers (force remove)\n        if let (_, output, _, status) = try? run(arguments: [\"ls\", \"-a\"]), status == 0 {\n            let containers = output.components(separatedBy: .newlines)\n                .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                .filter { $0.lowercased().starts(with: \"test\") }\n\n            for container in containers {\n                let _ = (try? run(arguments: [\"delete\", \"--force\", container]))\n            }\n        }\n\n        // Clean up test volumes (both anonymous and named)\n        if let (_, output, _, status) = try? run(arguments: [\"volume\", \"list\", \"--quiet\"]), status == 0 {\n            let volumes = output.components(separatedBy: .newlines)\n                .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                .filter { isValidUUID($0) || $0.lowercased().starts(with: \"test\") }\n\n            for volume in volumes {\n                doVolumeDeleteIfExists(name: volume)\n            }\n        }\n    }\n\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    func getAnonymousVolumeNames() throws -> [String] {\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        guard status == 0 else {\n            throw CLIError.executionFailed(\"volume list failed: \\(error)\")\n        }\n        return output.components(separatedBy: .newlines)\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { isValidUUID($0) }\n    }\n\n    func volumeExists(name: String) throws -> Bool {\n        let (_, output, _, status) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        guard status == 0 else { return false }\n        let volumes = output.components(separatedBy: .newlines)\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n        return volumes.contains(name)\n    }\n\n    func isValidUUID(_ name: String) -> Bool {\n        let pattern = #\"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\"#\n        guard let regex = try? Regex(pattern) else { return false }\n        return (try? regex.firstMatch(in: name)) != nil\n    }\n\n    func doVolumeCreate(name: String) throws {\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"create\", name])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume create failed: \\(error)\")\n        }\n    }\n\n    func doVolumeDeleteIfExists(name: String) {\n        let (_, _, _, _) = (try? run(arguments: [\"volume\", \"rm\", name])) ?? (nil, \"\", \"\", 1)\n    }\n\n    func doRemoveIfExists(name: String, force: Bool = false) {\n        var args = [\"delete\"]\n        if force {\n            args.append(\"--force\")\n        }\n        args.append(name)\n        let (_, _, _, _) = (try? run(arguments: args)) ?? (nil, \"\", \"\", 1)\n    }\n\n    @Test func testAnonymousVolumeCreationAndPersistence() async throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            // Clean up anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Get count of anonymous volumes before\n        let beforeCount = try getAnonymousVolumeNames().count\n\n        // Run container with --rm and anonymous volume\n        let (_, _, _, status) = try run(arguments: [\n            \"run\",\n            \"--rm\",\n            \"--name\",\n            containerName,\n            \"-v\",\n            \"/data\",\n            alpine,\n            \"echo\",\n            \"test\",\n        ])\n\n        #expect(status == 0, \"container run should succeed\")\n\n        // Give time for container removal to complete\n        try await Task.sleep(for: .seconds(1))\n\n        // Verify container was removed\n        let (_, lsOutput, _, _) = try run(arguments: [\"ls\", \"-a\"])\n        let containers = lsOutput.components(separatedBy: .newlines)\n            .filter { $0.contains(containerName) }\n        #expect(containers.isEmpty, \"container should be removed with --rm\")\n\n        // Verify anonymous volume persists (no auto-cleanup)\n        let afterCount = try getAnonymousVolumeNames().count\n        #expect(afterCount == beforeCount + 1, \"anonymous volume should persist even with --rm\")\n    }\n\n    @Test func testAnonymousVolumePersistenceWithoutRm() throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n        let testData = \"persistent-data\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            // Clean up any anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Run container WITHOUT --rm\n        try doLongRun(name: containerName, args: [\"-v\", \"/data\"], autoRemove: false)\n        try waitForContainerRunning(containerName)\n\n        // Write data to anonymous volume\n        _ = try doExec(name: containerName, cmd: [\"sh\", \"-c\", \"echo '\\(testData)' > /data/test.txt\"])\n\n        // Get the anonymous volume ID\n        let volumeNames = try getAnonymousVolumeNames()\n        #expect(volumeNames.count == 1, \"should have exactly one anonymous volume\")\n        let volumeID = volumeNames[0]\n\n        // Stop and remove container\n        try doStop(name: containerName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        // Verify volume still exists\n        let exists = try volumeExists(name: volumeID)\n        #expect(exists, \"anonymous volume should persist without --rm\")\n\n        // Mount same volume in new container and verify data\n        let containerName2 = \"\\(testName)_c2\"\n        try doLongRun(name: containerName2, args: [\"-v\", \"\\(volumeID):/data\"], autoRemove: false)\n        try waitForContainerRunning(containerName2)\n\n        var output = try doExec(name: containerName2, cmd: [\"cat\", \"/data/test.txt\"])\n        output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n        #expect(output == testData, \"data should persist in anonymous volume\")\n\n        // Clean up\n        try doStop(name: containerName2)\n        doRemoveIfExists(name: containerName2, force: true)\n        doVolumeDeleteIfExists(name: volumeID)\n    }\n\n    @Test func testMultipleAnonymousVolumes() async throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            // Clean up anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        let beforeCount = try getAnonymousVolumeNames().count\n\n        // Run with multiple anonymous volumes\n        let (_, _, _, status) = try run(arguments: [\n            \"run\",\n            \"--rm\",\n            \"--name\",\n            containerName,\n            \"-v\", \"/data1\",\n            \"-v\", \"/data2\",\n            \"-v\", \"/data3\",\n            alpine,\n            \"sh\", \"-c\", \"ls -d /data*\",\n        ])\n\n        #expect(status == 0, \"container run should succeed\")\n\n        // Give time for container removal\n        try await Task.sleep(for: .seconds(1))\n\n        // All 3 volumes should persist (no auto-cleanup)\n        let afterCount = try getAnonymousVolumeNames().count\n        #expect(afterCount == beforeCount + 3, \"all 3 anonymous volumes should persist\")\n    }\n\n    @Test func testAnonymousMountSyntax() async throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            // Clean up anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        let beforeCount = try getAnonymousVolumeNames().count\n\n        // Use --mount syntax\n        let (_, _, _, status) = try run(arguments: [\n            \"run\",\n            \"--rm\",\n            \"--name\",\n            containerName,\n            \"--mount\", \"type=volume,dst=/mydata\",\n            alpine,\n            \"ls\", \"-la\", \"/mydata\",\n        ])\n\n        #expect(status == 0, \"container run with --mount should succeed\")\n\n        // Give time for container removal\n        try await Task.sleep(for: .seconds(1))\n\n        // Anonymous volume should persist (no auto-cleanup)\n        let afterCount = try getAnonymousVolumeNames().count\n        #expect(afterCount == beforeCount + 1, \"anonymous volume should persist\")\n    }\n\n    @Test func testAnonymousVolumeUUIDFormat() throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Create container with anonymous volume\n        try doLongRun(name: containerName, args: [\"-v\", \"/data\"])\n        try waitForContainerRunning(containerName)\n\n        // Get the anonymous volume name\n        let volumeNames = try getAnonymousVolumeNames()\n        #expect(volumeNames.count == 1, \"should have exactly one anonymous volume\")\n\n        let volumeName = volumeNames[0]\n\n        // Verify UUID format: {lowercase uuid}\n        #expect(isValidUUID(volumeName), \"volume name should match UUID format: \\(volumeName)\")\n\n        // Verify total length is 36 characters (UUID without prefix)\n        #expect(volumeName.count == 36, \"volume name should be 36 characters long\")\n    }\n\n    @Test func testAnonymousVolumeMetadata() throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Create container with anonymous volume\n        try doLongRun(name: containerName, args: [\"-v\", \"/data\"])\n        try waitForContainerRunning(containerName)\n\n        // Get the anonymous volume\n        let volumeNames = try getAnonymousVolumeNames()\n        #expect(volumeNames.count == 1, \"should have exactly one anonymous volume\")\n        let volumeName = volumeNames[0]\n\n        // Inspect volume in JSON format\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"list\", \"--format\", \"json\"])\n        #expect(status == 0, \"volume list should succeed: \\(error)\")\n\n        // Parse JSON to verify metadata\n        let data = output.data(using: .utf8)!\n        let volumes = try JSONDecoder().decode([Volume].self, from: data)\n\n        let anonVolume = volumes.first { $0.name == volumeName }\n        #expect(anonVolume != nil, \"should find anonymous volume in list\")\n\n        if let vol = anonVolume {\n            #expect(vol.isAnonymous == true, \"isAnonymous should be true\")\n        }\n    }\n\n    @Test func testAnonymousVolumeListDisplay() throws {\n        let testName = getTestName()\n        let namedVolumeName = \"\\(testName)_namedvol\"\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: namedVolumeName)\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Create named volume\n        try doVolumeCreate(name: namedVolumeName)\n\n        // Create container with anonymous volume\n        try doLongRun(name: containerName, args: [\"-v\", \"/data\"])\n        try waitForContainerRunning(containerName)\n\n        // List volumes\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"list\"])\n        #expect(status == 0, \"volume list should succeed: \\(error)\")\n\n        // Verify TYPE column exists and shows both types\n        #expect(output.contains(\"TYPE\"), \"output should contain TYPE column\")\n        #expect(output.contains(\"named\"), \"output should show named volume type\")\n        #expect(output.contains(\"anonymous\"), \"output should show anonymous volume type\")\n        #expect(output.contains(namedVolumeName), \"output should contain named volume\")\n    }\n\n    @Test func testAnonymousVolumeMixedWithNamedVolume() async throws {\n        let testName = getTestName()\n        let namedVolumeName = \"\\(testName)_namedvol\"\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: namedVolumeName)\n            // Clean up anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        // Create named volume\n        try doVolumeCreate(name: namedVolumeName)\n\n        let beforeAnonCount = try getAnonymousVolumeNames().count\n\n        // Run with both named and anonymous volumes, with --rm\n        let (_, _, _, status) = try run(arguments: [\n            \"run\",\n            \"--rm\",\n            \"--name\",\n            containerName,\n            \"-v\", \"\\(namedVolumeName):/named\",\n            \"-v\", \"/anon\",\n            alpine,\n            \"sh\", \"-c\", \"ls -d /*\",\n        ])\n\n        #expect(status == 0, \"container run should succeed\")\n\n        // Give time for container removal\n        try await Task.sleep(for: .seconds(1))\n\n        // Named volume should still exist\n        let namedExists = try volumeExists(name: namedVolumeName)\n        #expect(namedExists, \"named volume should persist\")\n\n        let afterAnonCount = try getAnonymousVolumeNames().count\n        #expect(afterAnonCount == beforeAnonCount + 1, \"anonymous volume should persist\")\n    }\n\n    @Test func testAnonymousVolumeManualDeletion() throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n        }\n\n        // Create container WITHOUT --rm\n        try doLongRun(name: containerName, args: [\"-v\", \"/data\"], autoRemove: false)\n        try waitForContainerRunning(containerName)\n\n        // Get volume ID\n        let volumeNames = try getAnonymousVolumeNames()\n        #expect(volumeNames.count == 1, \"should have one anonymous volume\")\n        let volumeID = volumeNames[0]\n\n        // Stop container (unmounts volume)\n        try doStop(name: containerName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        // Manual deletion should succeed (volume is unmounted)\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"rm\", volumeID])\n        #expect(status == 0, \"manual deletion of unmounted anonymous volume should succeed: \\(error)\")\n\n        // Verify volume is gone\n        let exists = try volumeExists(name: volumeID)\n        #expect(!exists, \"volume should be deleted\")\n    }\n\n    @Test func testAnonymousVolumeDetachedMode() async throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            // Clean up anonymous volumes\n            if let volumes = try? getAnonymousVolumeNames() {\n                volumes.forEach { doVolumeDeleteIfExists(name: $0) }\n            }\n        }\n\n        let beforeCount = try getAnonymousVolumeNames().count\n\n        // Run in detached mode with --rm\n        let (_, _, _, status) = try run(arguments: [\n            \"run\",\n            \"-d\",\n            \"--rm\",\n            \"--name\",\n            containerName,\n            \"-v\", \"/data\",\n            alpine,\n            \"sleep\", \"2\",\n        ])\n\n        #expect(status == 0, \"detached container run should succeed\")\n\n        // Wait for container to exit\n        try await Task.sleep(for: .seconds(3))\n\n        // Container should be removed\n        let (_, lsOutput, _, _) = try run(arguments: [\"ls\", \"-a\"])\n        let containers = lsOutput.components(separatedBy: .newlines)\n            .filter { $0.contains(containerName) }\n        #expect(containers.isEmpty, \"container should be auto-removed\")\n\n        let afterCount = try getAnonymousVolumeNames().count\n        #expect(afterCount == beforeCount + 1, \"anonymous volume should persist\")\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Subcommands/Volumes/TestCLIVolumes.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport Foundation\nimport Testing\n\n@Suite(.serialized)\nclass TestCLIVolumes: CLITest {\n\n    func doVolumeCreate(name: String) throws {\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"create\", name])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume create failed: \\(error)\")\n        }\n    }\n\n    func doVolumeDelete(name: String) throws {\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"rm\", name])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume delete failed: \\(error)\")\n        }\n    }\n\n    func doVolumeDeleteIfExists(name: String) {\n        let (_, _, _, _) = (try? run(arguments: [\"volume\", \"rm\", name])) ?? (nil, \"\", \"\", 1)\n    }\n\n    func doRemoveIfExists(name: String, force: Bool = false) {\n        var args = [\"delete\"]\n        if force {\n            args.append(\"--force\")\n        }\n        args.append(name)\n        let (_, _, _, _) = (try? run(arguments: args)) ?? (nil, \"\", \"\", 1)\n    }\n\n    func doesVolumeDeleteFail(name: String) throws -> Bool {\n        let (_, _, _, status) = try run(arguments: [\"volume\", \"rm\", name])\n        return status != 0\n    }\n\n    private func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    @Test func testVolumeDataPersistenceAcrossContainers() throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n        let container1Name = \"\\(testName)_c1\"\n        let container2Name = \"\\(testName)_c2\"\n        let testData = \"persistent-data-test\"\n        let testFile = \"/data/test.txt\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n        doRemoveIfExists(name: container1Name, force: true)\n        doRemoveIfExists(name: container2Name, force: true)\n\n        defer {\n            // Clean up containers and volume\n            try? doStop(name: container1Name)\n            doRemoveIfExists(name: container1Name, force: true)\n            try? doStop(name: container2Name)\n            doRemoveIfExists(name: container2Name, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Create volume\n        try doVolumeCreate(name: volumeName)\n\n        // Run first container with volume, write data, then stop\n        try doLongRun(name: container1Name, args: [\"-v\", \"\\(volumeName):/data\"])\n        try waitForContainerRunning(container1Name)\n\n        // Write test data to the volume\n        _ = try doExec(name: container1Name, cmd: [\"sh\", \"-c\", \"echo '\\(testData)' > \\(testFile)\"])\n\n        // Stop first container\n        try doStop(name: container1Name)\n\n        // Run second container with same volume\n        try doLongRun(name: container2Name, args: [\"-v\", \"\\(volumeName):/data\"])\n        try waitForContainerRunning(container2Name)\n\n        // Verify data persisted\n        var output = try doExec(name: container2Name, cmd: [\"cat\", testFile])\n        output = output.trimmingCharacters(in: .whitespacesAndNewlines)\n\n        #expect(output == testData, \"expected persisted data '\\(testData)', instead got '\\(output)'\")\n\n        try doStop(name: container2Name)\n        try doVolumeDelete(name: volumeName)\n    }\n\n    @Test func testVolumeSharedAccessConflict() throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n        let container1Name = \"\\(testName)_c1\"\n        let container2Name = \"\\(testName)_c2\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n        doRemoveIfExists(name: container1Name, force: true)\n        doRemoveIfExists(name: container2Name, force: true)\n\n        defer {\n            // Clean up containers and volume\n            try? doStop(name: container1Name)\n            doRemoveIfExists(name: container1Name, force: true)\n            try? doStop(name: container2Name)\n            doRemoveIfExists(name: container2Name, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Create volume\n        try doVolumeCreate(name: volumeName)\n\n        // Run first container with volume\n        try doLongRun(name: container1Name, args: [\"-v\", \"\\(volumeName):/data\"])\n        try waitForContainerRunning(container1Name)\n\n        // Try to run second container with same volume - should fail\n        let (_, _, _, status) = try run(arguments: [\"run\", \"--name\", container2Name, \"-v\", \"\\(volumeName):/data\", alpine] + defaultContainerArgs)\n\n        #expect(status != 0, \"second container should fail when trying to use volume already in use\")\n\n        // Clean up\n        try doStop(name: container1Name)\n        doRemoveIfExists(name: container1Name, force: true)\n        doVolumeDeleteIfExists(name: volumeName)\n    }\n\n    @Test func testVolumeDeleteProtectionWhileInUse() throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n        let containerName = \"\\(testName)_c1\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        defer {\n            // Clean up container and volume\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Create volume\n        try doVolumeCreate(name: volumeName)\n\n        // Run container with volume\n        try doLongRun(name: containerName, args: [\"-v\", \"\\(volumeName):/data\"])\n        try waitForContainerRunning(containerName)\n\n        // Try to delete volume while container is running - should fail\n        let deleteFailedWhileInUse = try doesVolumeDeleteFail(name: volumeName)\n        #expect(deleteFailedWhileInUse, \"volume delete should fail while volume is in use\")\n\n        // Stop container\n        try doStop(name: containerName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        // Now volume delete should succeed\n        try doVolumeDelete(name: volumeName)\n    }\n\n    @Test func testVolumeDeleteProtectionWithCreatedContainer() async throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n        let containerName = \"\\(testName)_c1\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        defer {\n            // Clean up container and volume\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Create volume\n        try doVolumeCreate(name: volumeName)\n\n        // Create (but don't start) container with volume\n        try doCreate(name: containerName, image: alpine, volumes: [\"\\(volumeName):/mnt/data\"])\n\n        // Give some time for container to be fully registered\n        try await Task.sleep(for: .seconds(1))\n\n        // Try to delete volume while container is created - should fail\n        let deleteFailedWhileInUse = try doesVolumeDeleteFail(name: volumeName)\n        #expect(deleteFailedWhileInUse, \"volume delete should fail when volume is used by created container\")\n\n        // Remove the container\n        doRemoveIfExists(name: containerName, force: true)\n\n        // Now volume delete should succeed\n        doVolumeDeleteIfExists(name: volumeName)\n    }\n\n    @Test func testVolumeBasicOperations() throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n\n        defer {\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Create volume\n        try doVolumeCreate(name: volumeName)\n\n        // List volumes and verify it exists\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume list failed: \\(error)\")\n        }\n\n        let volumes = output.components(separatedBy: .newlines)\n            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n            .filter { !$0.isEmpty }\n\n        #expect(volumes.contains(volumeName), \"created volume should appear in list\")\n\n        // Inspect volume\n        let (_, inspectOutput, inspectError, inspectStatus) = try run(arguments: [\"volume\", \"inspect\", volumeName])\n        if inspectStatus != 0 {\n            throw CLIError.executionFailed(\"volume inspect failed: \\(inspectError)\")\n        }\n\n        #expect(inspectOutput.contains(volumeName), \"volume inspect should contain volume name\")\n\n        // Delete volume\n        try doVolumeDelete(name: volumeName)\n    }\n\n    @Test func testImplicitNamedVolumeCreation() throws {\n        let testName = getTestName()\n        let containerName = \"\\(testName)_c1\"\n        let volumeName = \"\\(testName)_autovolume\"\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // Verify volume doesn't exist yet\n        let (_, listOutput, _, _) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        let volumeExistsBefore = listOutput.contains(volumeName)\n        #expect(!volumeExistsBefore, \"volume should not exist initially\")\n\n        // Run container with non-existent named volume - should auto-create\n        let (_, output, _, status) = try run(arguments: [\n            \"run\",\n            \"--name\",\n            containerName,\n            \"-v\", \"\\(volumeName):/data\",\n            alpine,\n            \"echo\", \"test\",\n        ])\n\n        // Should succeed and create volume automatically\n        #expect(status == 0, \"should succeed and auto-create named volume\")\n        #expect(output.contains(\"test\"), \"container should run successfully\")\n\n        // Volume should now exist\n        let (_, listOutputAfter, _, _) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        let volumeExistsAfter = listOutputAfter.contains(volumeName)\n        #expect(volumeExistsAfter, \"volume should be created\")\n    }\n\n    @Test func testImplicitNamedVolumeReuse() throws {\n        let testName = getTestName()\n        let containerName1 = \"\\(testName)_c1\"\n        let containerName2 = \"\\(testName)_c2\"\n        let volumeName = \"\\(testName)_sharedvolume\"\n\n        defer {\n            doRemoveIfExists(name: containerName1, force: true)\n            doRemoveIfExists(name: containerName2, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        // First container - should auto-create volume\n        let (_, _, _, status1) = try run(arguments: [\n            \"run\",\n            \"--name\",\n            containerName1,\n            \"-v\", \"\\(volumeName):/data\",\n            alpine,\n            \"sh\", \"-c\", \"echo 'first' > /data/test.txt\",\n        ])\n\n        #expect(status1 == 0, \"first container should succeed\")\n\n        // Second container - should reuse existing volume\n        let (_, _, _, status2) = try run(arguments: [\n            \"run\",\n            \"--name\",\n            containerName2,\n            \"-v\", \"\\(volumeName):/data\",\n            alpine,\n            \"cat\", \"/data/test.txt\",\n        ])\n\n        #expect(status2 == 0, \"second container should succeed\")\n    }\n\n    @Test func testVolumePruneNoVolumes() throws {\n        // Prune with no volumes should succeed with 0 reclaimed\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(\"Zero KB\"), \"should show no space reclaimed\")\n    }\n\n    @Test func testVolumePruneUnusedVolumes() throws {\n        let testName = getTestName()\n        let volumeName1 = \"\\(testName)_vol1\"\n        let volumeName2 = \"\\(testName)_vol2\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName1)\n        doVolumeDeleteIfExists(name: volumeName2)\n\n        defer {\n            doVolumeDeleteIfExists(name: volumeName1)\n            doVolumeDeleteIfExists(name: volumeName2)\n        }\n\n        try doVolumeCreate(name: volumeName1)\n        try doVolumeCreate(name: volumeName2)\n        let (_, listBefore, _, statusBefore) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        #expect(statusBefore == 0)\n        #expect(listBefore.contains(volumeName1))\n        #expect(listBefore.contains(volumeName2))\n\n        // Prune should remove both\n        let (_, output, error, status) = try run(arguments: [\"volume\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(volumeName1) || !output.contains(\"No volumes to prune\"), \"should prune volume1\")\n        #expect(output.contains(volumeName2) || !output.contains(\"No volumes to prune\"), \"should prune volume2\")\n        #expect(output.contains(\"Reclaimed\"), \"should show reclaimed space\")\n\n        // Verify volumes are gone\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(!listAfter.contains(volumeName1), \"volume1 should be pruned\")\n        #expect(!listAfter.contains(volumeName2), \"volume2 should be pruned\")\n    }\n\n    @Test func testVolumePruneSkipsVolumeInUse() throws {\n        let testName = getTestName()\n        let volumeInUse = \"\\(testName)_inuse\"\n        let volumeUnused = \"\\(testName)_unused\"\n        let containerName = \"\\(testName)_c1\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeInUse)\n        doVolumeDeleteIfExists(name: volumeUnused)\n        doRemoveIfExists(name: containerName, force: true)\n\n        defer {\n            try? doStop(name: containerName)\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: volumeInUse)\n            doVolumeDeleteIfExists(name: volumeUnused)\n        }\n\n        try doVolumeCreate(name: volumeInUse)\n        try doVolumeCreate(name: volumeUnused)\n        try doLongRun(name: containerName, args: [\"-v\", \"\\(volumeInUse):/data\"])\n        try waitForContainerRunning(containerName)\n\n        // Prune should only remove the unused volume\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume prune failed: \\(error)\")\n        }\n\n        // Verify in-use volume still exists\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(listAfter.contains(volumeInUse), \"volume in use should NOT be pruned\")\n        #expect(!listAfter.contains(volumeUnused), \"unused volume should be pruned\")\n\n        try doStop(name: containerName)\n        doRemoveIfExists(name: containerName, force: true)\n        doVolumeDeleteIfExists(name: volumeInUse)\n    }\n\n    @Test func testVolumePruneSkipsVolumeAttachedToStoppedContainer() async throws {\n        let testName = getTestName()\n        let volumeName = \"\\(testName)_vol\"\n        let containerName = \"\\(testName)_c1\"\n\n        // Clean up any existing resources from previous runs\n        doVolumeDeleteIfExists(name: volumeName)\n        doRemoveIfExists(name: containerName, force: true)\n\n        defer {\n            doRemoveIfExists(name: containerName, force: true)\n            doVolumeDeleteIfExists(name: volumeName)\n        }\n\n        try doVolumeCreate(name: volumeName)\n        try doCreate(name: containerName, image: alpine, volumes: [\"\\(volumeName):/data\"])\n        try await Task.sleep(for: .seconds(1))\n\n        // Prune should NOT remove the volume (container exists, even if stopped)\n        let (_, _, error, status) = try run(arguments: [\"volume\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"volume prune failed: \\(error)\")\n        }\n\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(listAfter.contains(volumeName), \"volume attached to stopped container should NOT be pruned\")\n\n        doRemoveIfExists(name: containerName, force: true)\n        let (_, _, error2, status2) = try run(arguments: [\"volume\", \"prune\"])\n        if status2 != 0 {\n            throw CLIError.executionFailed(\"volume prune failed: \\(error2)\")\n        }\n\n        // Verify volume is gone\n        let (_, listFinal, _, statusFinal) = try run(arguments: [\"volume\", \"list\", \"--quiet\"])\n        #expect(statusFinal == 0)\n        #expect(!listFinal.contains(volumeName), \"volume should be pruned after container is deleted\")\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/TestCLINoParallelCases.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerAPIClient\nimport ContainerPersistence\nimport ContainerizationExtras\nimport ContainerizationOCI\nimport Foundation\nimport Testing\n\n/// Tests that need total control over environment to avoid conflicts.\n@Suite(.serialized)\nclass TestCLINoParallelCases: CLITest {\n    func getTestName() -> String {\n        Test.current!.name.trimmingCharacters(in: [\"(\", \")\"]).lowercased()\n    }\n\n    func getLowercasedTestName() -> String {\n        getTestName().lowercased()\n    }\n\n    @Test func testImageSingleConcurrentDownload() throws {\n        // removing this image during parallel tests breaks stuff!\n        _ = try? run(arguments: [\"image\", \"rm\", alpine])\n        defer { _ = try? run(arguments: [\"image\", \"rm\", \"--all\"]) }\n        do {\n            try doPull(imageName: alpine, args: [\"--max-concurrent-downloads\", \"1\"])\n            let imagePresent = try isImagePresent(targetImage: alpine)\n            #expect(imagePresent, \"Expected image to be pulled with maxConcurrentDownloads=1\")\n        } catch {\n            Issue.record(\"failed to pull image with maxConcurrentDownloads flag: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImageManyConcurrentDownloads() throws {\n        // removing this image during parallel tests breaks stuff!\n        _ = try? run(arguments: [\"image\", \"rm\", alpine])\n        defer { _ = try? run(arguments: [\"image\", \"rm\", \"--all\"]) }\n        do {\n            try doPull(imageName: alpine, args: [\"--max-concurrent-downloads\", \"64\"])\n            let imagePresent = try isImagePresent(targetImage: alpine)\n            #expect(imagePresent, \"Expected image to be pulled with maxConcurrentDownloads=64\")\n        } catch {\n            Issue.record(\"failed to pull image with maxConcurrentDownloads flag: \\(error)\")\n            return\n        }\n    }\n\n    @Test func testImagePruneNoImages() throws {\n        // Prune with no images should succeed\n        _ = try? run(arguments: [\"image\", \"rm\", \"--all\"])\n        let (_, output, error, status) = try run(arguments: [\"image\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"image prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(\"Zero KB\"), \"should show no space reclaimed\")\n    }\n\n    @Test func testImagePruneUnusedImages() throws {\n        // 1. Pull the images\n        _ = try? run(arguments: [\"image\", \"rm\", \"--all\"])\n        defer { _ = try? run(arguments: [\"image\", \"rm\", \"--all\"]) }\n        try doPull(imageName: alpine)\n        try doPull(imageName: busybox)\n\n        // 2. Verify the images are present\n        let alpinePresent = try isImagePresent(targetImage: alpine)\n        #expect(alpinePresent, \"expected to see image \\(alpine) pulled\")\n        let busyBoxPresent = try isImagePresent(targetImage: busybox)\n        #expect(busyBoxPresent, \"expected to see image \\(busybox) pulled\")\n\n        // 3. Prune with the -a flag should remove all unused images\n        let (_, output, error, status) = try run(arguments: [\"image\", \"prune\", \"-a\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"image prune failed: \\(error)\")\n        }\n        #expect(output.contains(alpine), \"should prune alpine image\")\n        #expect(output.contains(busybox), \"should prune busybox image\")\n\n        // 4. Verify the images are gone\n        let alpineRemoved = try !isImagePresent(targetImage: alpine)\n        #expect(alpineRemoved, \"expected image \\(alpine) to be removed\")\n        let busyboxRemoved = try !isImagePresent(targetImage: busybox)\n        #expect(busyboxRemoved, \"expected image \\(busybox) to be removed\")\n    }\n\n    @Test func testImagePruneDanglingImages() throws {\n        let name = getTestName()\n        let containerName = \"\\(name)_container\"\n\n        // 1. Pull the images\n        _ = try? run(arguments: [\"image\", \"rm\", \"--all\"])\n        defer { _ = try? run(arguments: [\"image\", \"rm\", \"--all\"]) }\n        _ = try? run(arguments: [\"rm\", \"--all\", \"--force\"])\n        defer { _ = try? run(arguments: [\"rm\", \"--all\", \"--force\"]) }\n        try doPull(imageName: alpine)\n        try doPull(imageName: busybox)\n\n        // 2. Verify the images are present\n        let alpinePresent = try isImagePresent(targetImage: alpine)\n        #expect(alpinePresent, \"expected to see image \\(alpine) pulled\")\n        let busyBoxPresent = try isImagePresent(targetImage: busybox)\n        #expect(busyBoxPresent, \"expected to see image \\(busybox) pulled\")\n\n        // 3. Create a running container based on alpine\n        try doLongRun(\n            name: containerName,\n            image: alpine\n        )\n        try waitForContainerRunning(containerName)\n\n        // 4. Prune should only remove the dangling image\n        let (_, output, error, status) = try run(arguments: [\"image\", \"prune\", \"-a\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"image prune failed: \\(error)\")\n        }\n        #expect(output.contains(busybox), \"should prune busybox image\")\n\n        // 5. Verify the busybox image is gone\n        let busyboxRemoved = try !isImagePresent(targetImage: busybox)\n        #expect(busyboxRemoved, \"expected image \\(busybox) to be removed\")\n\n        // 6. Verify the alpine image still exists\n        let alpineStillPresent = try isImagePresent(targetImage: alpine)\n        #expect(alpineStillPresent, \"expected image \\(alpine) to remain\")\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkPruneNoNetworks() throws {\n        // Ensure the testnetworkcreateanduse network is deleted\n        // Clean up is necessary for testing prune with no networks\n        doNetworkDeleteIfExists(name: \"testnetworkcreateanduse\")\n\n        // Prune with no networks should succeed\n        let (_, _, _, statusBefore) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusBefore == 0)\n        let (_, output, error, status) = try run(arguments: [\"network\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"network prune failed: \\(error)\")\n        }\n\n        #expect(output.isEmpty, \"should show no networks pruned\")\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkPruneUnusedNetworks() throws {\n        let name = getTestName()\n        let network1 = \"\\(name)_1\"\n        let network2 = \"\\(name)_2\"\n\n        // Clean up any existing resources from previous runs\n        doNetworkDeleteIfExists(name: network1)\n        doNetworkDeleteIfExists(name: network2)\n\n        defer {\n            doNetworkDeleteIfExists(name: network1)\n            doNetworkDeleteIfExists(name: network2)\n        }\n\n        try doNetworkCreate(name: network1)\n        try doNetworkCreate(name: network2)\n\n        // Verify networks are created\n        let (_, listBefore, _, statusBefore) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusBefore == 0)\n        #expect(listBefore.contains(network1))\n        #expect(listBefore.contains(network2))\n\n        // Prune should remove both\n        let (_, output, error, status) = try run(arguments: [\"network\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"network prune failed: \\(error)\")\n        }\n\n        #expect(output.contains(network1), \"should prune network1\")\n        #expect(output.contains(network2), \"should prune network2\")\n\n        // Verify networks are gone\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(!listAfter.contains(network1), \"network1 should be pruned\")\n        #expect(!listAfter.contains(network2), \"network2 should be pruned\")\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkPruneSkipsNetworksInUse() throws {\n        let name = getTestName()\n        let containerName = \"\\(name)_c1\"\n        let networkInUse = \"\\(name)_inuse\"\n        let networkUnused = \"\\(name)_unused\"\n\n        // Clean up any existing resources from previous runs\n        try? doStop(name: containerName)\n        try? doRemove(name: containerName)\n        doNetworkDeleteIfExists(name: networkInUse)\n        doNetworkDeleteIfExists(name: networkUnused)\n\n        defer {\n            try? doStop(name: containerName)\n            try? doRemove(name: containerName)\n            doNetworkDeleteIfExists(name: networkInUse)\n            doNetworkDeleteIfExists(name: networkUnused)\n        }\n\n        try doNetworkCreate(name: networkInUse)\n        try doNetworkCreate(name: networkUnused)\n\n        // Verify networks are created\n        let (_, listBefore, _, statusBefore) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusBefore == 0)\n        #expect(listBefore.contains(networkInUse))\n        #expect(listBefore.contains(networkUnused))\n\n        // Creation of container with network connection\n        let port = UInt16.random(in: 50000..<60000)\n        try doLongRun(\n            name: containerName,\n            image: \"docker.io/library/python:alpine\",\n            args: [\"--network\", networkInUse],\n            containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(port)\"]\n        )\n        try waitForContainerRunning(containerName)\n        let container = try inspectContainer(containerName)\n        #expect(container.networks.count > 0)\n\n        // Prune should only remove the unused network\n        let (_, _, error, status) = try run(arguments: [\"network\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"network prune failed: \\(error)\")\n        }\n\n        // Verify in-use network still exists\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(listAfter.contains(networkInUse), \"network in use should NOT be pruned\")\n        #expect(!listAfter.contains(networkUnused), \"unused network should be pruned\")\n    }\n\n    @available(macOS 26, *)\n    @Test func testNetworkPruneSkipsNetworkAttachedToStoppedContainer() async throws {\n        let name = getTestName()\n        let containerName = \"\\(name)_c1\"\n        let networkName = \"\\(name)\"\n\n        // Clean up any existing resources from previous runs\n        try? doStop(name: containerName)\n        try? doRemove(name: containerName)\n        doNetworkDeleteIfExists(name: networkName)\n\n        defer {\n            try? doStop(name: containerName)\n            try? doRemove(name: containerName)\n            doNetworkDeleteIfExists(name: networkName)\n        }\n\n        try doNetworkCreate(name: networkName)\n\n        // Creation of container with network connection\n        let port = UInt16.random(in: 50000..<60000)\n        try doLongRun(\n            name: containerName,\n            image: \"docker.io/library/python:alpine\",\n            args: [\"--network\", networkName],\n            containerArgs: [\"python3\", \"-m\", \"http.server\", \"--bind\", \"0.0.0.0\", \"\\(port)\"]\n        )\n        try await Task.sleep(for: .seconds(1))\n\n        // Prune should NOT remove the network (container exists, even if stopped)\n        let (_, _, error, status) = try run(arguments: [\"network\", \"prune\"])\n        if status != 0 {\n            throw CLIError.executionFailed(\"network prune failed: \\(error)\")\n        }\n\n        let (_, listAfter, _, statusAfter) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusAfter == 0)\n        #expect(listAfter.contains(networkName), \"network attached to stopped container should NOT be pruned\")\n\n        try? doStop(name: containerName)\n        try? doRemove(name: containerName)\n\n        let (_, _, error2, status2) = try run(arguments: [\"network\", \"prune\"])\n        if status2 != 0 {\n            throw CLIError.executionFailed(\"network prune failed: \\(error2)\")\n        }\n\n        // Verify network is gone\n        let (_, listFinal, _, statusFinal) = try run(arguments: [\"network\", \"list\", \"--quiet\"])\n        #expect(statusFinal == 0)\n        #expect(!listFinal.contains(networkName), \"network should be pruned after container is deleted\")\n    }\n\n    // MARK: - Parser.resources (DefaultsStore-dependent)\n\n    @Test func testResourcesCustomDefaults() throws {\n        let result = try Parser.resources(\n            cpus: nil, memory: nil,\n            cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,\n            defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()\n        )\n        #expect(result.cpus == 2)\n        #expect(result.memoryInBytes == 2048.mib())\n    }\n\n    @Test func testResourcesBuildPropertyLookup() throws {\n        DefaultsStore.set(value: \"8\", key: .defaultBuildCPUs)\n        DefaultsStore.set(value: \"4g\", key: .defaultBuildMemory)\n        defer {\n            DefaultsStore.unset(key: .defaultBuildCPUs)\n            DefaultsStore.unset(key: .defaultBuildMemory)\n        }\n        let result = try Parser.resources(\n            cpus: nil, memory: nil,\n            cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,\n            defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()\n        )\n        #expect(result.cpus == 8)\n        #expect(result.memoryInBytes == 4096.mib())\n    }\n\n    @Test func testResourcesCPUsFromProperty() throws {\n        DefaultsStore.set(value: \"8\", key: .defaultContainerCPUs)\n        defer { DefaultsStore.unset(key: .defaultContainerCPUs) }\n        let result = try Parser.resources(cpus: nil, memory: nil)\n        #expect(result.cpus == 8)\n    }\n\n    @Test func testResourcesMemoryFromProperty() throws {\n        DefaultsStore.set(value: \"2g\", key: .defaultContainerMemory)\n        defer { DefaultsStore.unset(key: .defaultContainerMemory) }\n        let result = try Parser.resources(cpus: nil, memory: nil)\n        #expect(result.memoryInBytes == 2048.mib())\n    }\n\n    @Test func testResourcesFlagOverridesProperty() throws {\n        DefaultsStore.set(value: \"8\", key: .defaultContainerCPUs)\n        DefaultsStore.set(value: \"2g\", key: .defaultContainerMemory)\n        defer {\n            DefaultsStore.unset(key: .defaultContainerCPUs)\n            DefaultsStore.unset(key: .defaultContainerMemory)\n        }\n        let result = try Parser.resources(cpus: 1, memory: \"256m\")\n        #expect(result.cpus == 1)\n        #expect(result.memoryInBytes == 256.mib())\n    }\n\n    @Test func testResourcesPropertyKeysAreIsolated() throws {\n        DefaultsStore.set(value: \"16\", key: .defaultContainerCPUs)\n        DefaultsStore.set(value: \"8g\", key: .defaultContainerMemory)\n        defer {\n            DefaultsStore.unset(key: .defaultContainerCPUs)\n            DefaultsStore.unset(key: .defaultContainerMemory)\n        }\n        let result = try Parser.resources(\n            cpus: nil, memory: nil,\n            cpuPropertyKey: .defaultBuildCPUs, memoryPropertyKey: .defaultBuildMemory,\n            defaultCPUs: 2, defaultMemoryInBytes: 2048.mib()\n        )\n        #expect(result.cpus == 2)\n        #expect(result.memoryInBytes == 2048.mib())\n    }\n}\n"
  },
  {
    "path": "Tests/CLITests/Utilities/CLITest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport AsyncHTTPClient\nimport ContainerLog\nimport ContainerResource\nimport Containerization\nimport ContainerizationOS\nimport Foundation\nimport Logging\nimport Synchronization\nimport SystemPackage\nimport Testing\n\nclass CLITest {\n    private static let commandSeq = Mutex<Int>(0)\n    struct Image: Codable {\n        let reference: String\n    }\n\n    // These structs need to track their counterpart presentation structs in CLI.\n    struct ImageInspectOutput: Codable {\n        let name: String\n        let variants: [variant]\n        struct variant: Codable {\n            let platform: imagePlatform\n            struct imagePlatform: Codable {\n                let os: String\n                let architecture: String\n            }\n        }\n    }\n\n    struct NetworkInspectOutput: Codable {\n        let id: String\n        let state: String\n        let config: NetworkConfiguration\n        let status: NetworkStatus?\n    }\n\n    let testName: String\n    let testSuite: String\n    var log: Logger\n\n    init() throws {\n        let name = Test.current.map { $0.name.hasSuffix(\"()\") ? String($0.name.dropLast(2)) : $0.name } ?? UUID().uuidString\n        let suite = \"\\(type(of: self))\"\n        self.testName = name\n        self.testSuite = suite\n        let logger = Logger(label: \"com.apple.container.test\") { label in\n            if let logRootString = ProcessInfo.processInfo.environment[\"CLITEST_LOG_ROOT\"],\n                !logRootString.isEmpty\n            {\n                let logPath = FilePath(logRootString).appending(\"clitests\").appending(suite).appending(name + \".log\")\n                if let handler = try? FileLogHandler(label: label, category: \"clitests\", path: logPath) {\n                    return handler\n                }\n            }\n            return StderrLogHandler()\n        }\n        self.log = logger\n        self.log[metadataKey: \"testID\"] = \"\\(name)\"\n        self.log[metadataKey: \"suite\"] = \"\\(suite)\"\n    }\n\n    var testDir: URL! {\n        let tempDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)\n            .appendingPathComponent(\".clitests\")\n            .appendingPathComponent(testName)\n        try! FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        return tempDir\n    }\n\n    let alpine = \"ghcr.io/linuxcontainers/alpine:3.20\"\n    let alpine318 = \"ghcr.io/linuxcontainers/alpine:3.18\"\n    let busybox = \"ghcr.io/containerd/busybox:1.36\"\n\n    let defaultContainerArgs = [\"sleep\", \"infinity\"]\n\n    var executablePath: URL {\n        get throws {\n            let containerPath = ProcessInfo.processInfo.environment[\"CONTAINER_CLI_PATH\"]\n            if let containerPath {\n                return URL(filePath: containerPath)\n            }\n            let fileManager = FileManager.default\n            let currentDir = fileManager.currentDirectoryPath\n\n            let releaseURL = URL(fileURLWithPath: currentDir)\n                .appendingPathComponent(\".build\")\n                .appendingPathComponent(\"release\")\n                .appendingPathComponent(\"container\")\n\n            let debugURL = URL(fileURLWithPath: currentDir)\n                .appendingPathComponent(\".build\")\n                .appendingPathComponent(\"debug\")\n                .appendingPathComponent(\"container\")\n\n            let releaseExists = fileManager.fileExists(atPath: releaseURL.path)\n            let debugExists = fileManager.fileExists(atPath: debugURL.path)\n\n            if releaseExists && debugExists {  // choose the latest build\n                do {\n                    let releaseAttributes = try fileManager.attributesOfItem(atPath: releaseURL.path)\n                    let debugAttributes = try fileManager.attributesOfItem(atPath: debugURL.path)\n\n                    if let releaseDate = releaseAttributes[.modificationDate] as? Date,\n                        let debugDate = debugAttributes[.modificationDate] as? Date\n                    {\n                        return (releaseDate > debugDate) ? releaseURL : debugURL\n                    }\n                } catch {\n                    throw CLIError.binaryAttributesNotFound(error)\n                }\n            } else if releaseExists {\n                return releaseURL\n            } else if debugExists {\n                return debugURL\n            }\n            // both do not exist\n            throw CLIError.binaryNotFound\n        }\n    }\n\n    func run(arguments: [String], stdin: Data? = nil, currentDirectory: URL? = nil) throws -> (outputData: Data, output: String, error: String, status: Int32) {\n        let seq = CLITest.commandSeq.withLock { counter in\n            defer { counter += 1 }\n            return counter\n        }\n        log.info(\n            \"command start\",\n            metadata: [\n                \"seq\": \"\\(seq)\",\n                \"args\": \"\\(arguments.joined(separator: \" \"))\",\n            ]\n        )\n\n        let process = Process()\n        process.executableURL = try executablePath\n        process.arguments = arguments\n        if let directory = currentDirectory {\n            process.currentDirectoryURL = directory\n        }\n\n        let inputPipe = Pipe()\n        let outputPipe = Pipe()\n        let errorPipe = Pipe()\n        process.standardInput = inputPipe\n        process.standardOutput = outputPipe\n        process.standardError = errorPipe\n\n        let outputData: Data\n        let errorData: Data\n        do {\n            try process.run()\n            if let data = stdin {\n                inputPipe.fileHandleForWriting.write(data)\n            }\n            inputPipe.fileHandleForWriting.closeFile()\n            outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()\n            errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()\n            process.waitUntilExit()\n        } catch {\n            throw CLIError.executionFailed(\"Failed to run CLI: \\(error)\")\n        }\n\n        let output = String(data: outputData, encoding: .utf8) ?? \"\"\n        let error = String(data: errorData, encoding: .utf8) ?? \"\"\n\n        log.info(\n            \"command end\",\n            metadata: [\n                \"seq\": \"\\(seq)\",\n                \"status\": \"\\(process.terminationStatus)\",\n                \"stdout\": \"\\(String(output.prefix(64)).debugDescription)\",\n                \"stderr\": \"\\(String(error.prefix(64)).debugDescription)\",\n            ]\n        )\n\n        return (outputData: outputData, output: output, error: error, status: process.terminationStatus)\n    }\n\n    func runInteractive(arguments: [String], currentDirectory: URL? = nil) throws -> Terminal {\n        let process = Process()\n        process.executableURL = try executablePath\n        process.arguments = arguments\n        if let directory = currentDirectory {\n            process.currentDirectoryURL = directory\n        }\n\n        do {\n            let (parent, child) = try Terminal.create()\n            process.standardInput = child.handle\n            process.standardOutput = child.handle\n            process.standardError = child.handle\n\n            try process.run()\n            return parent\n        } catch {\n            fatalError(error.localizedDescription)\n        }\n    }\n\n    func waitForContainerRunning(_ name: String, _ totalAttempts: Int64 = 100) throws {\n        var attempt = 0\n        var found = false\n        while attempt < totalAttempts && !found {\n            attempt += 1\n            let status = try? getContainerStatus(name)\n            if status == \"running\" {\n                found = true\n                continue\n            }\n            sleep(1)\n        }\n        if !found {\n            throw CLIError.containerNotFound(name)\n        }\n    }\n\n    enum CLIError: Error {\n        case executionFailed(String)\n        case invalidInput(String)\n        case invalidOutput(String)\n        case containerNotFound(String)\n        case containerRunFailed(String)\n        case binaryNotFound\n        case binaryAttributesNotFound(Error)\n    }\n\n    func doLongRun(\n        name: String,\n        image: String? = nil,\n        args: [String]? = nil,\n        containerArgs: [String]? = nil,\n        autoRemove: Bool = true\n    ) throws {\n        var runArgs = [\n            \"run\"\n        ]\n        if autoRemove {\n            runArgs.append(\"--rm\")\n        }\n        runArgs.append(contentsOf: [\n            \"--name\",\n            name,\n            \"-d\",\n        ])\n        if let args {\n            runArgs.append(contentsOf: args)\n        }\n\n        runArgs.append(contentsOf: getProxyEnvironment())\n\n        if let image {\n            runArgs.append(image)\n        } else {\n            runArgs.append(alpine)\n        }\n\n        if let containerArgs {\n            runArgs.append(contentsOf: containerArgs)\n        } else {\n            runArgs.append(contentsOf: defaultContainerArgs)\n        }\n\n        let (_, _, error, status) = try run(arguments: runArgs)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doExec(name: String, cmd: [String], detach: Bool = false) throws -> String {\n        var execArgs = [\n            \"exec\"\n        ]\n        execArgs.append(contentsOf: getProxyEnvironment())\n        if detach {\n            execArgs.append(\"-d\")\n        }\n        execArgs.append(name)\n        execArgs.append(contentsOf: cmd)\n        let (_, resp, error, status) = try run(arguments: execArgs)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n        return resp\n    }\n\n    func doStop(name: String, signal: String = \"SIGKILL\") throws {\n        let (_, _, error, status) = try run(arguments: [\n            \"stop\",\n            \"-s\",\n            signal,\n            name,\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doCreate(\n        name: String,\n        image: String? = nil,\n        args: [String]? = nil,\n        volumes: [String] = [],\n        networks: [String] = [],\n        ports: [String] = []\n    ) throws {\n        let image = image ?? alpine\n        let args: [String] = args ?? [\"sleep\", \"infinity\"]\n\n        var arguments = [\"create\", \"--rm\", \"--name\", name]\n\n        arguments.append(contentsOf: getProxyEnvironment())\n\n        // Add volume mounts\n        for volume in volumes {\n            arguments += [\"-v\", volume]\n        }\n\n        // Add networks (can include properties like \"network,mac=XX:XX:XX:XX:XX:XX\")\n        for network in networks {\n            arguments += [\"--network\", network]\n        }\n\n        for port in ports {\n            arguments += [\"--publish\", \"\\(port):\\(port)\"]\n        }\n\n        arguments += [image] + args\n\n        let (_, _, error, status) = try run(arguments: arguments)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doStart(name: String) throws {\n        let (_, _, error, status) = try run(arguments: [\n            \"start\",\n            name,\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    struct inspectOutput: Codable {\n        let status: String\n        let configuration: ContainerConfiguration\n        let networks: [ContainerResource.Attachment]\n    }\n\n    func getContainerStatus(_ name: String) throws -> String {\n        try inspectContainer(name).status\n    }\n\n    func getContainerId(_ name: String) throws -> String {\n        try inspectContainer(name).configuration.id\n    }\n\n    func inspectContainer(_ name: String) throws -> inspectOutput {\n        let response = try run(arguments: [\n            \"inspect\",\n            name,\n        ])\n        let cmdStatus = response.status\n        guard cmdStatus == 0 else {\n            throw CLIError.executionFailed(\"container inspect failed: exit \\(cmdStatus)\")\n        }\n\n        let output = response.output\n        guard let jsonData = output.data(using: .utf8) else {\n            throw CLIError.invalidOutput(\"container inspect output invalid\")\n        }\n\n        let decoder = JSONDecoder()\n\n        typealias inspectOutputs = [inspectOutput]\n\n        let io = try decoder.decode(inspectOutputs.self, from: jsonData)\n        guard io.count > 0 else {\n            throw CLIError.containerNotFound(name)\n        }\n        return io[0]\n    }\n\n    func inspectImage(_ name: String) throws -> String {\n        let response = try run(arguments: [\n            \"image\",\n            \"inspect\",\n            name,\n        ])\n        let cmdStatus = response.status\n        guard cmdStatus == 0 else {\n            throw CLIError.executionFailed(\"image inspect failed: exit \\(cmdStatus)\")\n        }\n\n        let output = response.output\n        guard let jsonData = output.data(using: .utf8) else {\n            throw CLIError.invalidOutput(\"image inspect output invalid\")\n        }\n\n        let decoder = JSONDecoder()\n\n        struct inspectOutput: Codable {\n            let name: String\n        }\n\n        typealias inspectOutputs = [inspectOutput]\n\n        let io = try decoder.decode(inspectOutputs.self, from: jsonData)\n        guard io.count > 0 else {\n            throw CLIError.containerNotFound(name)\n        }\n        return io[0].name\n    }\n\n    func doPull(imageName: String, args: [String]? = nil) throws {\n        var pullArgs = [\n            \"image\",\n            \"pull\",\n        ]\n        if let args {\n            pullArgs.append(contentsOf: args)\n        }\n        pullArgs.append(imageName)\n\n        let (_, _, error, status) = try run(arguments: pullArgs)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doImageListQuite() throws -> [String] {\n        let args = [\n            \"image\",\n            \"list\",\n            \"-q\",\n        ]\n\n        let (_, out, error, status) = try run(arguments: args)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n        return out.trimmingCharacters(in: .whitespacesAndNewlines).components(separatedBy: .newlines)\n    }\n\n    func doInspectImages(image: String) throws -> [ImageInspectOutput] {\n        let (_, output, error, status) = try run(arguments: [\n            \"image\",\n            \"inspect\",\n            image,\n        ])\n\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n\n        guard let jsonData = output.data(using: .utf8) else {\n            throw CLIError.invalidOutput(\"image inspect output invalid \\(output)\")\n        }\n\n        let decoder = JSONDecoder()\n        return try decoder.decode([ImageInspectOutput].self, from: jsonData)\n    }\n\n    func doDefaultRegistrySet(domain: String) throws {\n        let args = [\n            \"system\",\n            \"property\",\n            \"set\",\n            \"registry.domain\",\n            domain,\n        ]\n        let (_, _, error, status) = try run(arguments: args)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doDefaultRegistryUnset() throws {\n        let args = [\n            \"system\",\n            \"property\",\n            \"clear\",\n            \"registry.domain\",\n        ]\n        let (_, _, error, status) = try run(arguments: args)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doRemove(name: String, force: Bool = false) throws {\n        var args = [\"delete\"]\n        if force {\n            args.append(\"--force\")\n        }\n        args.append(name)\n\n        let (_, _, error, status) = try run(arguments: args)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func getClient(useHttpProxy: Bool) -> HTTPClient {\n        var httpConfiguration = HTTPClient.Configuration()\n        let proxyConfig: HTTPClient.Configuration.Proxy? = {\n            guard useHttpProxy else {\n                return nil\n            }\n            let proxyEnv = ProcessInfo.processInfo.environment[\"HTTP_PROXY\"]\n            guard let proxyEnv else {\n                return nil\n            }\n            guard let url = URL(string: proxyEnv), let host = url.host(), let port = url.port else {\n                return nil\n            }\n            return .server(host: host, port: port)\n        }()\n        httpConfiguration.proxy = proxyConfig\n        return HTTPClient(eventLoopGroupProvider: .singleton, configuration: httpConfiguration)\n    }\n\n    func withTempDir<T>(_ body: (URL) async throws -> T) async throws -> T {\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        return try await body(tempDir)\n    }\n\n    func doRemoveImages(images: [String]? = nil) throws {\n        var args = [\n            \"image\",\n            \"rm\",\n        ]\n\n        if let images {\n            args.append(contentsOf: images)\n        } else {\n            args.append(\"--all\")\n        }\n\n        let (_, _, error, status) = try run(arguments: args)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func isImagePresent(targetImage: String) throws -> Bool {\n        let images = try doListImages()\n        return images.contains(where: { image in\n            if image.reference == targetImage {\n                return true\n            }\n            return false\n        })\n    }\n\n    func doListImages() throws -> [Image] {\n        let (_, output, error, status) = try run(arguments: [\n            \"image\",\n            \"list\",\n            \"--format\",\n            \"json\",\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n\n        guard let jsonData = output.data(using: .utf8) else {\n            throw CLIError.invalidOutput(\"image list output invalid \\(output)\")\n        }\n\n        let decoder = JSONDecoder()\n        return try decoder.decode([Image].self, from: jsonData)\n    }\n\n    func doImageTag(image: String, newName: String) throws {\n        let tagArgs = [\n            \"image\",\n            \"tag\",\n            image,\n            newName,\n        ]\n\n        let (_, _, error, status) = try run(arguments: tagArgs)\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n\n    func doNetworkCreate(name: String) throws {\n        let (_, _, error, status) = try run(arguments: [\"network\", \"create\", name])\n        if status != 0 {\n            throw CLIError.executionFailed(\"network create failed: \\(error)\")\n        }\n    }\n\n    func doNetworkDeleteIfExists(name: String) {\n        let (_, _, _, _) = (try? run(arguments: [\"network\", \"rm\", name])) ?? (nil, \"\", \"\", 1)\n    }\n\n    private func getProxyEnvironment() -> [String] {\n        let proxyVars = Set([\n            \"HTTP_PROXY\", \"http_proxy\",\n            \"HTTPS_PROXY\", \"https_proxy\",\n            \"NO_PROXY\", \"no_proxy\",\n        ])\n        return ProcessInfo.processInfo.environment\n            .filter { (key, val) in proxyVars.contains(key) }\n            .flatMap { (key, val) in [\"-e\", \"\\(key)=\\(val)\"] }\n    }\n\n    func doExport(name: String, filepath: String) throws {\n        let (_, _, error, status) = try run(arguments: [\n            \"export\",\n            name,\n            \"-o\",\n            filepath,\n        ])\n        if status != 0 {\n            throw CLIError.executionFailed(\"command failed: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/ArchTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct ArchTests {\n\n    @Test func testAmd64Initialization() throws {\n        let arch = Arch(rawValue: \"amd64\")\n        #expect(arch != nil)\n        #expect(arch == .amd64)\n    }\n\n    @Test func testX86_64Alias() throws {\n        let arch = Arch(rawValue: \"x86_64\")\n        #expect(arch != nil)\n        #expect(arch == .amd64)\n    }\n\n    @Test func testX86_64WithDashAlias() throws {\n        let arch = Arch(rawValue: \"x86-64\")\n        #expect(arch != nil)\n        #expect(arch == .amd64)\n    }\n\n    @Test func testArm64Initialization() throws {\n        let arch = Arch(rawValue: \"arm64\")\n        #expect(arch != nil)\n        #expect(arch == .arm64)\n    }\n\n    @Test func testAarch64Alias() throws {\n        let arch = Arch(rawValue: \"aarch64\")\n        #expect(arch != nil)\n        #expect(arch == .arm64)\n    }\n\n    @Test func testCaseInsensitive() throws {\n        #expect(Arch(rawValue: \"AMD64\") == .amd64)\n        #expect(Arch(rawValue: \"X86_64\") == .amd64)\n        #expect(Arch(rawValue: \"ARM64\") == .arm64)\n        #expect(Arch(rawValue: \"AARCH64\") == .arm64)\n        #expect(Arch(rawValue: \"Amd64\") == .amd64)\n    }\n\n    @Test func testInvalidArchitecture() throws {\n        #expect(Arch(rawValue: \"invalid\") == nil)\n        #expect(Arch(rawValue: \"i386\") == nil)\n        #expect(Arch(rawValue: \"powerpc\") == nil)\n        #expect(Arch(rawValue: \"\") == nil)\n    }\n\n    @Test func testRawValueRoundTrip() throws {\n        #expect(Arch.amd64.rawValue == \"amd64\")\n        #expect(Arch.arm64.rawValue == \"arm64\")\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/DefaultPlatformTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationOCI\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct DefaultPlatformTests {\n\n    // MARK: - fromEnvironment\n\n    @Test\n    func testFromEnvironmentWithLinuxAmd64() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/amd64\"]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result != nil)\n        #expect(result?.os == \"linux\")\n        #expect(result?.architecture == \"amd64\")\n    }\n\n    @Test\n    func testFromEnvironmentWithLinuxArm64() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result != nil)\n        #expect(result?.os == \"linux\")\n        #expect(result?.architecture == \"arm64\")\n    }\n\n    @Test\n    func testFromEnvironmentNotSet() throws {\n        let env: [String: String] = [:]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result == nil)\n    }\n\n    @Test\n    func testFromEnvironmentEmptyString() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"\"]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result == nil)\n    }\n\n    @Test\n    func testFromEnvironmentInvalidPlatformThrows() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"not-a-valid-platform\"]\n        #expect {\n            _ = try DefaultPlatform.fromEnvironment(environment: env)\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"CONTAINER_DEFAULT_PLATFORM\")\n                && error.description.contains(\"not-a-valid-platform\")\n        }\n    }\n\n    @Test\n    func testFromEnvironmentIgnoresOtherVariables() throws {\n        let env = [\"SOME_OTHER_VAR\": \"linux/amd64\"]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result == nil)\n    }\n\n    @Test\n    func testFromEnvironmentWithVariant() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm/v7\"]\n        let result = try DefaultPlatform.fromEnvironment(environment: env)\n        #expect(result != nil)\n        #expect(result?.os == \"linux\")\n        #expect(result?.architecture == \"arm\")\n        #expect(result?.variant == \"v7\")\n    }\n\n    // MARK: - resolve (optional os/arch, used by image pull/push/save)\n\n    @Test\n    func testResolveExplicitPlatformWins() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: \"linux/amd64\", os: nil, arch: nil, environment: env\n        )\n        #expect(result != nil)\n        #expect(result?.architecture == \"amd64\")\n        #expect(result?.os == \"linux\")\n    }\n\n    @Test\n    func testResolveExplicitArchWinsOverEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: nil, os: nil, arch: \"amd64\", environment: env\n        )\n        #expect(result != nil)\n        #expect(result?.architecture == \"amd64\")\n        #expect(result?.os == \"linux\")\n    }\n\n    @Test\n    func testResolveExplicitOsAndArchWinOverEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: nil, os: \"linux\", arch: \"amd64\", environment: env\n        )\n        #expect(result != nil)\n        #expect(result?.architecture == \"amd64\")\n    }\n\n    @Test\n    func testResolveExplicitOsWinsOverEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: nil, os: \"linux\", arch: nil, environment: env\n        )\n        #expect(result != nil)\n        #expect(result?.os == \"linux\")\n    }\n\n    @Test\n    func testResolveFallsBackToEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/amd64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: nil, os: nil, arch: nil, environment: env\n        )\n        #expect(result != nil)\n        #expect(result?.os == \"linux\")\n        #expect(result?.architecture == \"amd64\")\n    }\n\n    @Test\n    func testResolveReturnsNilWithNoFlagsOrEnvVar() throws {\n        let env: [String: String] = [:]\n        let result = try DefaultPlatform.resolve(\n            platform: nil, os: nil, arch: nil, environment: env\n        )\n        #expect(result == nil)\n    }\n\n    @Test\n    func testResolveExplicitPlatformOverridesEverything() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolve(\n            platform: \"linux/amd64\", os: \"linux\", arch: \"arm64\", environment: env\n        )\n        #expect(result?.architecture == \"amd64\")\n    }\n\n    @Test\n    func testResolveExplicitPlatformIgnoresInvalidEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"garbage\"]\n        let result = try DefaultPlatform.resolve(\n            platform: \"linux/amd64\", os: nil, arch: nil, environment: env\n        )\n        #expect(result?.architecture == \"amd64\")\n        #expect(result?.os == \"linux\")\n    }\n\n    // MARK: - resolveWithDefaults (required os/arch, used by run/create)\n\n    @Test\n    func testResolveWithDefaultsExplicitPlatformWins() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/arm64\"]\n        let result = try DefaultPlatform.resolveWithDefaults(\n            platform: \"linux/amd64\", os: \"linux\", arch: \"arm64\", environment: env\n        )\n        #expect(result.architecture == \"amd64\")\n    }\n\n    @Test\n    func testResolveWithDefaultsEnvVarOverridesDefaults() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/amd64\"]\n        let result = try DefaultPlatform.resolveWithDefaults(\n            platform: nil, os: \"linux\", arch: \"arm64\", environment: env\n        )\n        #expect(result.architecture == \"amd64\")\n        #expect(result.os == \"linux\")\n    }\n\n    @Test\n    func testResolveWithDefaultsFallsBackToOsArch() throws {\n        let env: [String: String] = [:]\n        let result = try DefaultPlatform.resolveWithDefaults(\n            platform: nil, os: \"linux\", arch: \"arm64\", environment: env\n        )\n        #expect(result.os == \"linux\")\n        #expect(result.architecture == \"arm64\")\n    }\n\n    @Test\n    func testResolveWithDefaultsEnvVarWithDifferentOs() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"linux/amd64\"]\n        let result = try DefaultPlatform.resolveWithDefaults(\n            platform: nil, os: \"linux\", arch: Arch.hostArchitecture().rawValue, environment: env\n        )\n        #expect(result.architecture == \"amd64\")\n    }\n\n    @Test\n    func testResolveWithDefaultsInvalidEnvVarThrows() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"garbage\"]\n        #expect {\n            _ = try DefaultPlatform.resolveWithDefaults(\n                platform: nil, os: \"linux\", arch: \"arm64\", environment: env\n            )\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"CONTAINER_DEFAULT_PLATFORM\")\n        }\n    }\n\n    @Test\n    func testResolveWithDefaultsExplicitPlatformIgnoresInvalidEnvVar() throws {\n        let env = [\"CONTAINER_DEFAULT_PLATFORM\": \"garbage\"]\n        let result = try DefaultPlatform.resolveWithDefaults(\n            platform: \"linux/amd64\", os: \"linux\", arch: \"arm64\", environment: env\n        )\n        #expect(result.architecture == \"amd64\")\n    }\n\n    // MARK: - Environment variable name\n\n    @Test\n    func testEnvironmentVariableName() {\n        #expect(DefaultPlatform.environmentVariable == \"CONTAINER_DEFAULT_PLATFORM\")\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/DiskUsageTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct DiskUsageTests {\n\n    @Test(\"DiskUsageStats JSON encoding and decoding\")\n    func testJSONSerialization() throws {\n        let stats = DiskUsageStats(\n            images: ResourceUsage(total: 10, active: 5, sizeInBytes: 1024, reclaimable: 512),\n            containers: ResourceUsage(total: 3, active: 2, sizeInBytes: 2048, reclaimable: 1024),\n            volumes: ResourceUsage(total: 7, active: 4, sizeInBytes: 4096, reclaimable: 2048)\n        )\n\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(stats)\n\n        let decoder = JSONDecoder()\n        let decoded = try decoder.decode(DiskUsageStats.self, from: data)\n\n        #expect(decoded.images.total == stats.images.total)\n        #expect(decoded.images.active == stats.images.active)\n        #expect(decoded.images.sizeInBytes == stats.images.sizeInBytes)\n        #expect(decoded.images.reclaimable == stats.images.reclaimable)\n\n        #expect(decoded.containers.total == stats.containers.total)\n        #expect(decoded.containers.active == stats.containers.active)\n        #expect(decoded.containers.sizeInBytes == stats.containers.sizeInBytes)\n        #expect(decoded.containers.reclaimable == stats.containers.reclaimable)\n\n        #expect(decoded.volumes.total == stats.volumes.total)\n        #expect(decoded.volumes.active == stats.volumes.active)\n        #expect(decoded.volumes.sizeInBytes == stats.volumes.sizeInBytes)\n        #expect(decoded.volumes.reclaimable == stats.volumes.reclaimable)\n    }\n\n    @Test(\"ResourceUsage with zero values\")\n    func testZeroValues() throws {\n        let emptyUsage = ResourceUsage(total: 0, active: 0, sizeInBytes: 0, reclaimable: 0)\n\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(emptyUsage)\n\n        let decoder = JSONDecoder()\n        let decoded = try decoder.decode(ResourceUsage.self, from: data)\n\n        #expect(decoded.total == 0)\n        #expect(decoded.active == 0)\n        #expect(decoded.sizeInBytes == 0)\n        #expect(decoded.reclaimable == 0)\n    }\n\n    @Test(\"ResourceUsage with large values\")\n    func testLargeValues() throws {\n        let largeUsage = ResourceUsage(\n            total: 1000,\n            active: 500,\n            sizeInBytes: UInt64.max,\n            reclaimable: UInt64.max / 2\n        )\n\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(largeUsage)\n\n        let decoder = JSONDecoder()\n        let decoded = try decoder.decode(ResourceUsage.self, from: data)\n\n        #expect(decoded.total == 1000)\n        #expect(decoded.active == 500)\n        #expect(decoded.sizeInBytes == UInt64.max)\n        #expect(decoded.reclaimable == UInt64.max / 2)\n    }\n\n    @Test(\"ResourceUsage percentage calculations\")\n    func testPercentageCalculations() throws {\n        // 0% reclaimable\n        let noneReclaimable = ResourceUsage(total: 10, active: 10, sizeInBytes: 1000, reclaimable: 0)\n        #expect(Double(noneReclaimable.reclaimable) / Double(noneReclaimable.sizeInBytes) == 0.0)\n\n        // 50% reclaimable\n        let halfReclaimable = ResourceUsage(total: 10, active: 5, sizeInBytes: 1000, reclaimable: 500)\n        #expect(Double(halfReclaimable.reclaimable) / Double(halfReclaimable.sizeInBytes) == 0.5)\n\n        // 100% reclaimable\n        let allReclaimable = ResourceUsage(total: 10, active: 0, sizeInBytes: 1000, reclaimable: 1000)\n        #expect(Double(allReclaimable.reclaimable) / Double(allReclaimable.sizeInBytes) == 1.0)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/HostDNSResolverTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct HostDNSResolverTest {\n    @Test\n    func testHostDNSCreate() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n\n        let resolver = HostDNSResolver(configURL: tempURL)\n        try resolver.createDomain(name: \"foo.bar\")\n        let resolverConfigURL = tempURL.appending(path: \"containerization.foo.bar\")\n        let actualText = try String(contentsOf: resolverConfigURL, encoding: .utf8)\n        let expectedText = \"\"\"\n            domain foo.bar\n            search foo.bar\n            nameserver 127.0.0.1\n            port 2053\n\n            \"\"\"\n\n        #expect(actualText == expectedText)\n\n        try resolver.createDomain(name: \"bar.foo\")\n        let domains = resolver.listDomains()\n        #expect(domains == [\"bar.foo\", \"foo.bar\"])\n    }\n\n    @Test\n    func testHostDNSCreateAlreadyExists() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n\n        let resolver = HostDNSResolver(configURL: tempURL)\n        try resolver.createDomain(name: \"foo.bar\")\n        #expect {\n            try resolver.createDomain(name: \"foo.bar\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError, error.code == .exists else {\n                return false\n            }\n            return true\n        }\n    }\n\n    @Test\n    func testHostDNSDelete() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n\n        let resolver = HostDNSResolver(configURL: tempURL)\n        try resolver.createDomain(name: \"foo.bar\")\n        _ = try resolver.deleteDomain(name: \"foo.bar\")\n\n        let localhost = try! IPAddress(\"127.0.0.1\")\n        try resolver.createDomain(name: \"bar.baz\", localhost: localhost)\n        let deletedLocalhost = try resolver.deleteDomain(name: \"bar.baz\")\n        #expect(localhost == deletedLocalhost)\n\n        let domains = resolver.listDomains()\n        #expect(domains == [])\n    }\n\n    @Test\n    func testHostDNSDeleteNotFound() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n\n        let resolver = HostDNSResolver(configURL: tempURL)\n        try resolver.createDomain(name: \"foo.bar\")\n        #expect {\n            _ = try resolver.deleteDomain(name: \"bar.foo\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError, error.code == .notFound else {\n                return false\n            }\n            return true\n        }\n    }\n\n    @Test\n    func testHostDNSReinitialize() async throws {\n        let isAdmin = getuid() == 0\n        do {\n            try HostDNSResolver.reinitialize()\n            #expect(isAdmin)\n        } catch {\n            let containerizationError = try #require(error as? ContainerizationError)\n            #expect(containerizationError.code == .internalError)\n            #expect(containerizationError.message == \"mDNSResponder restart failed with status 1\")\n            #expect(!isAdmin)\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/Measurement+ParseTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct MeasurementParseTests {\n\n    @Test(\"Parse binary units - bare unit symbols\")\n    func testBinaryUnits() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"4k\")\n        #expect(result1.value == 4.0)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"2m\")\n        #expect(result2.value == 2.0)\n        #expect(result2.unit == .mebibytes)\n\n        let result3 = try Measurement<UnitInformationStorage>.parse(parsing: \"1g\")\n        #expect(result3.value == 1.0)\n        #expect(result3.unit == .gibibytes)\n\n        let result4 = try Measurement<UnitInformationStorage>.parse(parsing: \"512b\")\n        #expect(result4.value == 512.0)\n        #expect(result4.unit == .bytes)\n    }\n\n    @Test(\"Parse binary units - ib suffix\")\n    func testBinaryUnitsWithIbSuffix() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"4kib\")\n        #expect(result1.value == 4.0)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"2mib\")\n        #expect(result2.value == 2.0)\n        #expect(result2.unit == .mebibytes)\n\n        let result3 = try Measurement<UnitInformationStorage>.parse(parsing: \"1gib\")\n        #expect(result3.value == 1.0)\n        #expect(result3.unit == .gibibytes)\n\n        let result4 = try Measurement<UnitInformationStorage>.parse(parsing: \"3tib\")\n        #expect(result4.value == 3.0)\n        #expect(result4.unit == .tebibytes)\n\n        let result5 = try Measurement<UnitInformationStorage>.parse(parsing: \"1pib\")\n        #expect(result5.value == 1.0)\n        #expect(result5.unit == .pebibytes)\n    }\n\n    @Test(\"Parse binary units - all suffixes now use binary\")\n    func testAllSuffixesUseBinary() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"4kb\")\n        #expect(result1.value == 4.0)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"2mb\")\n        #expect(result2.value == 2.0)\n        #expect(result2.unit == .mebibytes)\n\n        let result3 = try Measurement<UnitInformationStorage>.parse(parsing: \"1gb\")\n        #expect(result3.value == 1.0)\n        #expect(result3.unit == .gibibytes)\n\n        let result4 = try Measurement<UnitInformationStorage>.parse(parsing: \"3tb\")\n        #expect(result4.value == 3.0)\n        #expect(result4.unit == .tebibytes)\n\n        let result5 = try Measurement<UnitInformationStorage>.parse(parsing: \"1pb\")\n        #expect(result5.value == 1.0)\n        #expect(result5.unit == .pebibytes)\n    }\n\n    @Test(\"Parse with whitespace\")\n    func testParsingWithWhitespace() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \" 4k \")\n        #expect(result1.value == 4.0)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"  2.5mb  \")\n        #expect(result2.value == 2.5)\n        #expect(result2.unit == .mebibytes)\n    }\n\n    @Test(\"Parse decimal values\")\n    func testDecimalValues() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"4.5k\")\n        #expect(result1.value == 4.5)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"1.25gb\")\n        #expect(result2.value == 1.25)\n        #expect(result2.unit == .gibibytes)\n\n        let result3 = try Measurement<UnitInformationStorage>.parse(parsing: \"0.5mib\")\n        #expect(result3.value == 0.5)\n        #expect(result3.unit == .mebibytes)\n    }\n\n    @Test(\"Parse case insensitive\")\n    func testCaseInsensitive() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"4K\")\n        #expect(result1.value == 4.0)\n        #expect(result1.unit == .kibibytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"2GB\")\n        #expect(result2.value == 2.0)\n        #expect(result2.unit == .gibibytes)\n\n        let result3 = try Measurement<UnitInformationStorage>.parse(parsing: \"1MIB\")\n        #expect(result3.value == 1.0)\n        #expect(result3.unit == .mebibytes)\n    }\n\n    @Test(\"Parse bytes unit\")\n    func testBytesUnit() throws {\n        let result1 = try Measurement<UnitInformationStorage>.parse(parsing: \"1024\")\n        #expect(result1.value == 1024.0)\n        #expect(result1.unit == .bytes)\n\n        let result2 = try Measurement<UnitInformationStorage>.parse(parsing: \"512b\")\n        #expect(result2.value == 512.0)\n        #expect(result2.unit == .bytes)\n    }\n\n    @Test(\"Parse invalid size throws error\")\n    func testInvalidSizeThrowsError() {\n        #expect {\n            _ = try Measurement<UnitInformationStorage>.parse(parsing: \"abc\")\n        } throws: { error in\n            guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {\n                return false\n            }\n            return parseError.description == \"invalid size\"\n        }\n\n        #expect {\n            _ = try Measurement<UnitInformationStorage>.parse(parsing: \"k4\")\n        } throws: { error in\n            guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {\n                return false\n            }\n            return parseError.description == \"invalid size\"\n        }\n    }\n\n    @Test(\"Parse invalid symbol throws error\")\n    func testInvalidSymbolThrowsError() {\n        #expect {\n            _ = try Measurement<UnitInformationStorage>.parse(parsing: \"4x\")\n        } throws: { error in\n            guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {\n                return false\n            }\n            return parseError.description == \"invalid symbol: x\"\n        }\n\n        #expect {\n            _ = try Measurement<UnitInformationStorage>.parse(parsing: \"4kx\")\n        } throws: { error in\n            guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {\n                return false\n            }\n            return parseError.description == \"invalid symbol: kx\"\n        }\n    }\n\n    @Test(\"Parse empty string throws error\")\n    func testEmptyStringThrowsError() {\n        #expect {\n            _ = try Measurement<UnitInformationStorage>.parse(parsing: \"\")\n        } throws: { error in\n            guard let parseError = error as? Measurement<UnitInformationStorage>.ParseError else {\n                return false\n            }\n            return parseError.description == \"invalid size\"\n        }\n    }\n\n    @Test(\"Verify all suffixes now use binary units\")\n    func testAllSuffixesUseBinaryUnits() throws {\n        let bareK = try Measurement<UnitInformationStorage>.parse(parsing: \"1k\")\n        let kib = try Measurement<UnitInformationStorage>.parse(parsing: \"1kib\")\n        let kb = try Measurement<UnitInformationStorage>.parse(parsing: \"1kb\")\n\n        #expect(bareK.unit == .kibibytes)\n        #expect(kib.unit == .kibibytes)\n        #expect(kb.unit == .kibibytes)\n\n        let allInBytes = bareK.converted(to: .bytes).value\n\n        #expect(allInBytes == 1024.0)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/PacketFilterTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct PacketFilterTest {\n    @Test\n    func testRedirectRuleUpdate() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let configURL = tempURL.appending(path: \"pf.conf\")\n\n        let pf = PacketFilter(configURL: configURL, anchorsURL: tempURL)\n        let from1 = try! IPAddress(\"203.0.113.113\")\n        let domain1 = \"aaa.com\"\n        let to = try! IPAddress(\"127.0.0.1\")\n        try pf.createRedirectRule(from: from1, to: to, domain: domain1)\n\n        let anchorURL = tempURL.appending(path: \"com.apple.container\")\n        var actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)\n        var expectedAnchorTest = \"\"\"\n            rdr inet from any to \\(from1) -> \\(to) # \\(domain1)\\n\n            \"\"\"\n\n        #expect(actualAnchorText == expectedAnchorTest)\n\n        let from2 = try! IPAddress(\"172.31.72.1\")\n        let domain2 = \"bbb.com\"\n        try pf.createRedirectRule(from: from2, to: to, domain: domain2)\n\n        actualAnchorText = try String(contentsOf: anchorURL, encoding: .utf8)\n        expectedAnchorTest += \"\"\"\n            rdr inet from any to \\(from2) -> \\(to) # \\(domain2)\\n\n            \"\"\"\n        #expect(actualAnchorText == expectedAnchorTest)\n\n        let actualConfigText = try String(contentsOf: configURL, encoding: .utf8)\n        let expectedConfigText = try Regex(\n            #\"\"\"\n            scrub-anchor \"([^\"]+)\"\n            nat-anchor \"([^\"]+)\"\n            rdr-anchor \"([^\"]+)\"\n            dummynet-anchor \"([^\"]+)\"\n            anchor \"([^\"]+)\"\n            load anchor \"([^\"]+)\" from \"[^\"]+\"\n            \"\"\"#\n        )\n\n        #expect(actualConfigText.contains(expectedConfigText))\n\n        try pf.removeRedirectRule(from: from1, to: to, domain: domain1)\n        try pf.removeRedirectRule(from: from2, to: to, domain: domain2)\n\n        #expect(!fm.fileExists(atPath: anchorURL.path))\n        let configText = try String(contentsOf: configURL, encoding: .utf8)\n        #expect(configText == \"\")\n    }\n\n    @Test\n    func testPacketFilterReinitialize() async throws {\n        let pf = PacketFilter()\n        #expect(throws: ContainerizationError.self) {\n            try pf.reinitialize()\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/ParserTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerizationError\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct ParserTest {\n    @Test\n    func testPublishPortParserTcp() throws {\n        let result = try Parser.publishPorts([\"127.0.0.1:8080:8000/tcp\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"127.0.0.1\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(8000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortParserUdp() throws {\n        let result = try Parser.publishPorts([\"192.168.32.36:8000:8080/UDP\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"192.168.32.36\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8000))\n        #expect(result[0].containerPort == UInt16(8080))\n        #expect(result[0].proto == .udp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortRange() throws {\n        let result = try Parser.publishPorts([\"127.0.0.1:8080-8179:9000-9099/tcp\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"127.0.0.1\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(9000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 100)\n    }\n\n    @Test\n    func testPublishPortRangeSingle() throws {\n        let result = try Parser.publishPorts([\"127.0.0.1:8080-8080:9000-9000/tcp\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"127.0.0.1\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(9000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortNoHostAddress() throws {\n        let result = try Parser.publishPorts([\"8080:8000/tcp\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"0.0.0.0\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(8000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortNoProtocol() throws {\n        let result = try Parser.publishPorts([\"8080:8000\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"0.0.0.0\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(8000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortParserIPv6() throws {\n        let result = try Parser.publishPorts([\"[fe80::36f3:5e50:ed71:1bb]:8080:8000/tcp\"])\n        #expect(result.count == 1)\n        let expectedAddress = try IPAddress(\"fe80::36f3:5e50:ed71:1bb\")\n        #expect(result[0].hostAddress == expectedAddress)\n        #expect(result[0].hostPort == UInt16(8080))\n        #expect(result[0].containerPort == UInt16(8000))\n        #expect(result[0].proto == .tcp)\n        #expect(result[0].count == 1)\n    }\n\n    @Test\n    func testPublishPortInvalidProtocol() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8080:8000/sctp\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish protocol\")\n        }\n    }\n\n    @Test\n    func testPublishPortInvalidValue() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish value\")\n        }\n    }\n\n    @Test\n    func testPublishPortMissingPort() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"1234\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish value\")\n        }\n    }\n\n    @Test\n    func testPublishInvalidIPv4Address() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"1234:8080:8000\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish IPv4 address\")\n        }\n    }\n\n    @Test\n    func testPublishInvalidIPv6Address() throws {\n        #expect {\n            _ = try Parser.publishPorts([\n                \"[1234:5678]:8080:8000\",\n                \"[2001::db8::1]:8080:8080\",\n                \"[2001:db8:85a3::8a2e:370g:7334]:8080:8080\",\n                \"[2001:db8:85a3::][8a2e::7334]:8080:8080\",\n            ])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish IPv6 address\")\n        }\n    }\n\n    @Test\n    func testPublishPortInvalidHostPort() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"65536:1234\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortInvalidContainerPort() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"1234:65536\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeMismatch() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8000:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"counts are not equal\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidHostPortStart() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"65536-65537:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeZeroHostPortStart() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"0-1:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidHostPortEnd() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"65535-65536:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidHostPortRange() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001-8002:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeNegativeHostPortRange() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8001-8000:9000-9001\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish host port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidContainerPortStart() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001:65536-65537\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeZeroContainerPortStart() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001:0-1\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidContainerPortEnd() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001:65535-65536\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeInvalidContainerPortRange() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001:9000-9001-9002\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testPublishPortRangeNegativeContainerPortRange() throws {\n        #expect {\n            _ = try Parser.publishPorts([\"8000-8001:9001-9000\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid publish container port\")\n        }\n    }\n\n    @Test\n    func testRelativePaths() throws {\n        // Test bind mount with relative path \".\"\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-bind-\\(UUID().uuidString)\")\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let result = try Parser.mount(\"type=bind,src=.,dst=/foo\", relativeTo: tempDir)\n\n            switch result {\n            case .filesystem(let fs):\n                #expect(fs.source == tempDir.standardizedFileURL.path)\n                #expect(fs.destination == \"/foo\")\n                #expect(!fs.isVolume)\n            case .volume:\n                #expect(Bool(false), \"Expected filesystem mount, got volume\")\n            }\n        }\n\n        // Test volume with relative path \"./\"\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-volume-rel-\\(UUID().uuidString)\")\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let result = try Parser.volume(\"./:/foo\", relativeTo: tempDir)\n\n            switch result {\n            case .filesystem(let fs):\n                let expectedPath = tempDir.standardizedFileURL.path\n                // Normalize trailing slashes for comparison\n                #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")))\n                #expect(fs.destination == \"/foo\")\n            case .volume:\n                #expect(Bool(false), \"Expected filesystem mount, got volume\")\n            }\n        }\n\n        // Test volume with nested relative path \"./subdir\"\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-volume-rel-nested-\\(UUID().uuidString)\")\n            let nestedDir = tempDir.appendingPathComponent(\"subdir\")\n            try FileManager.default.createDirectory(at: nestedDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let result = try Parser.volume(\"./subdir:/foo\", relativeTo: tempDir)\n\n            switch result {\n            case .filesystem(let fs):\n                let expectedPath = nestedDir.standardizedFileURL.path\n                // Normalize trailing slashes for comparison\n                #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")))\n                #expect(fs.destination == \"/foo\")\n            case .volume:\n                #expect(Bool(false), \"Expected filesystem mount, got volume\")\n            }\n        }\n\n        // Test volume with bare \".\" as source (current directory)\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-volume-dot-\\(UUID().uuidString)\")\n            try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let result = try Parser.volume(\".:/docs:ro\", relativeTo: tempDir)\n\n            switch result {\n            case .filesystem(let fs):\n                let expectedPath = tempDir.standardizedFileURL.path\n                #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")))\n                #expect(fs.destination == \"/docs\")\n                #expect(fs.options.contains(\"ro\"))\n            case .volume:\n                #expect(Bool(false), \"Expected filesystem mount, got volume\")\n            }\n        }\n\n        // Test volume with \"..\" as source (parent directory)\n        do {\n            let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-volume-dotdot-\\(UUID().uuidString)\")\n            let childDir = tempDir.appendingPathComponent(\"child\")\n            try FileManager.default.createDirectory(at: childDir, withIntermediateDirectories: true)\n            defer {\n                try? FileManager.default.removeItem(at: tempDir)\n            }\n\n            let result = try Parser.volume(\"..:/data\", relativeTo: childDir)\n\n            switch result {\n            case .filesystem(let fs):\n                let expectedPath = tempDir.standardizedFileURL.path\n                #expect(fs.source.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")) == expectedPath.trimmingCharacters(in: CharacterSet(charactersIn: \"/\")))\n                #expect(fs.destination == \"/data\")\n            case .volume:\n                #expect(Bool(false), \"Expected filesystem mount, got volume\")\n            }\n        }\n    }\n\n    @Test\n    func testMountBindAbsolutePath() throws {\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-bind-abs-\\(UUID().uuidString)\")\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        let result = try Parser.mount(\"type=bind,src=\\(tempDir.path),dst=/foo\")\n\n        switch result {\n        case .filesystem(let fs):\n            #expect(fs.source == tempDir.path)\n            #expect(fs.destination == \"/foo\")\n            #expect(!fs.isVolume)\n        case .volume:\n            #expect(Bool(false), \"Expected filesystem mount, got volume\")\n        }\n    }\n\n    @Test\n    func testMountVolumeValidName() throws {\n        let result = try Parser.mount(\"type=volume,src=myvolume,dst=/data\")\n\n        switch result {\n        case .filesystem:\n            #expect(Bool(false), \"Expected volume mount, got filesystem\")\n        case .volume(let vol):\n            #expect(vol.name == \"myvolume\")\n            #expect(vol.destination == \"/data\")\n        }\n    }\n\n    @Test\n    func testMountVolumeInvalidName() throws {\n        #expect {\n            _ = try Parser.mount(\"type=volume,src=.,dst=/data\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid volume name\")\n        }\n    }\n\n    @Test\n    func testMountBindNonExistentPath() throws {\n        #expect {\n            _ = try Parser.mount(\"type=bind,src=/nonexistent/path,dst=/foo\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"path\") && error.description.contains(\"does not exist\")\n        }\n    }\n\n    @Test\n    func testMountBindFileInsteadOfDirectory() throws {\n        let tempFile = FileManager.default.temporaryDirectory.appendingPathComponent(\"test-file-\\(UUID().uuidString)\")\n        try \"test content\".write(to: tempFile, atomically: true, encoding: .utf8)\n        defer {\n            try? FileManager.default.removeItem(at: tempFile)\n        }\n\n        #expect {\n            _ = try Parser.mount(\"type=bind,src=\\(tempFile.path),dst=/foo\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"path\") && error.description.contains(\"is not a directory\")\n        }\n    }\n\n    @Test\n    func testIsValidDomainNameOk() throws {\n        let names = [\n            \"a\",\n            \"a.b\",\n            \"foo.bar\",\n            \"F-O.B-R\",\n            [\n                String(repeating: \"0\", count: 63),\n                String(repeating: \"1\", count: 63),\n                String(repeating: \"2\", count: 63),\n                String(repeating: \"3\", count: 63),\n            ].joined(separator: \".\"),\n        ]\n        for name in names {\n            #expect(Parser.isValidDomainName(name))\n        }\n    }\n\n    @Test\n    func testIsValidDomainNameBad() throws {\n        let names = [\n            \".foo\",\n            \"foo.\",\n            \".foo.bar\",\n            \"foo.bar.\",\n            \"-foo.bar\",\n            \"foo.bar-\",\n            [\n                String(repeating: \"0\", count: 63),\n                String(repeating: \"1\", count: 63),\n                String(repeating: \"2\", count: 63),\n                String(repeating: \"3\", count: 62),\n                \"4\",\n            ].joined(separator: \".\"),\n        ]\n        for name in names {\n            #expect(!Parser.isValidDomainName(name))\n        }\n    }\n\n    // MARK: - Environment Variable Tests\n\n    @Test\n    func testEnvExplicitValue() throws {\n        let result = Parser.env(envList: [\"FOO=bar\", \"BAZ=qux\"])\n        #expect(result == [\"FOO=bar\", \"BAZ=qux\"])\n    }\n\n    @Test\n    func testEnvImplicitInheritance() throws {\n        guard let homeValue = ProcessInfo.processInfo.environment[\"PATH\"] else {\n            Issue.record(\"PATH environment variable not set\")\n            return\n        }\n\n        let result = Parser.env(envList: [\"PATH\"])\n        #expect(result == [\"PATH=\\(homeValue)\"])\n    }\n\n    @Test\n    func testEnvImplicitUndefinedVariable() throws {\n        // A variable that doesn't exist should be silently skipped\n        let result = Parser.env(envList: [\"THIS_VAR_DEFINITELY_DOES_NOT_EXIST_12345\"])\n        #expect(result.isEmpty)\n    }\n\n    @Test\n    func testEnvMixedExplicitAndImplicit() throws {\n        guard let homeValue = ProcessInfo.processInfo.environment[\"HOME\"] else {\n            Issue.record(\"HOME environment variable not set\")\n            return\n        }\n\n        let result = Parser.env(envList: [\"FOO=bar\", \"HOME\", \"BAZ=qux\"])\n        #expect(result == [\"FOO=bar\", \"HOME=\\(homeValue)\", \"BAZ=qux\"])\n    }\n\n    @Test\n    func testEnvEmptyValue() throws {\n        // Explicit empty value should be preserved\n        let result = Parser.env(envList: [\"EMPTY=\"])\n        #expect(result == [\"EMPTY=\"])\n    }\n\n    @Test\n    func testAllEnvUserOverridesImage() throws {\n        let result = try Parser.allEnv(\n            imageEnvs: [\"FOO=fromimage\", \"BAR=kept\"],\n            envFiles: [],\n            envs: [\"FOO=fromuser\"]\n        )\n        #expect(Set(result) == Set([\"FOO=fromuser\", \"BAR=kept\"]))\n    }\n\n    @Test\n    func testAllEnvFileOverridesImage() throws {\n        let tmpFile = try tmpFileWithContent(\"FOO=fromfile\\n\")\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let result = try Parser.allEnv(\n            imageEnvs: [\"FOO=fromimage\", \"BAR=kept\"],\n            envFiles: [tmpFile.path],\n            envs: []\n        )\n        #expect(Set(result) == Set([\"FOO=fromfile\", \"BAR=kept\"]))\n    }\n\n    @Test\n    func testAllEnvUserOverridesFileOverridesImage() throws {\n        let tmpFile = try tmpFileWithContent(\"FOO=fromfile\\nBAZ=fromfile\\n\")\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let result = try Parser.allEnv(\n            imageEnvs: [\"FOO=fromimage\", \"BAR=fromimage\"],\n            envFiles: [tmpFile.path],\n            envs: [\"FOO=fromuser\"]\n        )\n        #expect(Set(result) == Set([\"FOO=fromuser\", \"BAR=fromimage\", \"BAZ=fromfile\"]))\n    }\n\n    private func tmpFileWithContent(_ content: String) throws -> URL {\n        let tempDir = FileManager.default.temporaryDirectory\n        let tempFile = tempDir.appendingPathComponent(\"envfile-test-\\(UUID().uuidString)\")\n        try content.write(to: tempFile, atomically: true, encoding: .utf8)\n        return tempFile\n    }\n\n    // NOTE: A lot of these env-file tests are recreations of the docker cli's unit tests for their\n    // env-file support.\n\n    @Test\n    func testParseEnvFileGoodFile() throws {\n        var content = \"\"\"\n            foo=bar\n                baz=quux\n            # comment\n\n            _foobar=foobaz\n            with.dots=working\n            and_underscore=working too\n            \"\"\"\n        content += \"\\n    \\t  \"\n\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let lines = try Parser.envFile(path: tmpFile.path)\n\n        let expectedLines = [\n            \"foo=bar\",\n            \"baz=quux\",\n            \"_foobar=foobaz\",\n            \"with.dots=working\",\n            \"and_underscore=working too\",\n        ]\n\n        #expect(lines == expectedLines)\n    }\n\n    @Test\n    func testParseEnvFileMultipleEqualsSigns() throws {\n        let content = \"\"\"\n            URL=https://foo.bar?baz=woo\n            \"\"\"\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let lines = try Parser.envFile(path: tmpFile.path)\n\n        let expectedLines = [\n            \"URL=https://foo.bar?baz=woo\"\n        ]\n\n        #expect(lines == expectedLines)\n    }\n\n    @Test\n    func testParseEnvFileEmptyFile() throws {\n        let tmpFile = try tmpFileWithContent(\"\")\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let lines = try Parser.envFile(path: tmpFile.path)\n        #expect(lines.isEmpty)\n    }\n\n    @Test\n    func testParseEnvFileNonExistentFile() throws {\n        #expect {\n            _ = try Parser.envFile(path: \"/nonexistent/foo_bar_baz\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError,\n                let cause = error.cause\n            else {\n                return false\n            }\n            return String(describing: cause).contains(\"No such file or directory\")\n        }\n    }\n\n    @Test\n    func testParseEnvFileBadlyFormattedFile() throws {\n        let content = \"\"\"\n            foo=bar\n                f   =quux\n            \"\"\"\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        #expect {\n            _ = try Parser.envFile(path: tmpFile.path)\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"contains whitespaces\")\n        }\n    }\n\n    @Test\n    func testParseEnvFileRandomFile() throws {\n        let content = \"\"\"\n            first line\n            another invalid line\n            \"\"\"\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        #expect {\n            _ = try Parser.envFile(path: tmpFile.path)\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"first line\") && error.description.contains(\"contains whitespaces\")\n        }\n    }\n\n    @Test\n    func testParseEnvVariableDefinitionsFile() throws {\n        let content = \"\"\"\n            # comment=\n            UNDEFINED_VAR\n            HOME\n            \"\"\"\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        let variables = try Parser.envFile(path: tmpFile.path)\n\n        // HOME should be imported from environment\n        guard let homeValue = ProcessInfo.processInfo.environment[\"HOME\"] else {\n            Issue.record(\"HOME environment variable not set\")\n            return\n        }\n\n        #expect(variables.count == 1)\n        #expect(variables[0] == \"HOME=\\(homeValue)\")\n    }\n\n    @Test\n    func testParseEnvVariableWithNoNameFile() throws {\n        let content = \"\"\"\n            # comment=\n            =blank variable names are an error case\n            \"\"\"\n        let tmpFile = try tmpFileWithContent(content)\n        defer { try? FileManager.default.removeItem(at: tmpFile) }\n\n        #expect {\n            _ = try Parser.envFile(path: tmpFile.path)\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"no variable name\")\n        }\n    }\n\n    @Test\n    func testParseEnvFileFromNamedPipe() throws {\n        let pipePath = FileManager.default.temporaryDirectory\n            .appendingPathComponent(\"envfile-pipe-\\(UUID().uuidString)\")\n\n        // Create a named pipe (FIFO)\n        let result = mkfifo(pipePath.path, 0o600)\n        guard result == 0 else {\n            throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EPERM)\n        }\n        defer { try? FileManager.default.removeItem(at: pipePath) }\n\n        let group = DispatchGroup()\n\n        group.enter()\n        DispatchQueue.global().async {\n            do {\n                let handle = try FileHandle(forWritingTo: pipePath)\n                try handle.write(contentsOf: \"SECRET_KEY=value123\\n\".data(using: .utf8)!)\n                try handle.close()\n            } catch {\n                Issue.record(error)\n            }\n            group.leave()\n        }\n\n        // Read from pipe (blocks until writer connects)\n        let lines = try Parser.envFile(path: pipePath.path)\n\n        // Wait for write to complete\n        group.wait()\n\n        #expect(lines == [\"SECRET_KEY=value123\"])\n    }\n\n    // MARK: Network Parser Tests\n\n    @Test\n    func testParseNetworkSimpleName() throws {\n        let result = try Parser.network(\"default\")\n        #expect(result.name == \"default\")\n        #expect(result.macAddress == nil)\n    }\n\n    @Test\n    func testParseNetworkWithMACAddress() throws {\n        let result = try Parser.network(\"backend,mac=02:42:ac:11:00:02\")\n        #expect(result.name == \"backend\")\n        #expect(result.macAddress == \"02:42:ac:11:00:02\")\n    }\n\n    @Test\n    func testParseNetworkWithMACAddressHyphenSeparator() throws {\n        let result = try Parser.network(\"backend,mac=02-42-ac-11-00-02\")\n        #expect(result.name == \"backend\")\n        #expect(result.macAddress == \"02-42-ac-11-00-02\")\n    }\n\n    @Test\n    func testParseNetworkEmptyString() throws {\n        #expect {\n            _ = try Parser.network(\"\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"network specification cannot be empty\")\n        }\n    }\n\n    @Test\n    func testParseNetworkEmptyName() throws {\n        #expect {\n            _ = try Parser.network(\",mac=02:42:ac:11:00:02\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"network name cannot be empty\")\n        }\n    }\n\n    @Test\n    func testParseNetworkEmptyMACAddress() throws {\n        #expect {\n            _ = try Parser.network(\"backend,mac=\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"mac address value cannot be empty\")\n        }\n    }\n\n    @Test\n    func testParseNetworkUnknownProperty() throws {\n        #expect {\n            _ = try Parser.network(\"backend,unknown=value\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"unknown network property\") && error.description.contains(\"unknown\")\n        }\n    }\n\n    @Test\n    func testParseNetworkInvalidPropertyFormat() throws {\n        #expect {\n            _ = try Parser.network(\"backend,invalidproperty\")\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid property format\")\n        }\n    }\n\n    // MARK: - Relative Path Passthrough Tests\n\n    @Test\n    func testProcessEntrypointRelativePathPassthrough() throws {\n        let processFlags = try Flags.Process.parse([\"--cwd\", \"/bin\"])\n        let managementFlags = try Flags.Management.parse([\"--entrypoint\", \"./uname\"])\n\n        let result = try Parser.process(\n            arguments: [],\n            processFlags: processFlags,\n            managementFlags: managementFlags,\n            config: nil\n        )\n\n        #expect(result.executable == \"./uname\")\n        #expect(result.workingDirectory == \"/bin\")\n    }\n\n    @Test\n    func testUlimitParserSoftAndHard() throws {\n        let result = try Parser.rlimits([\"nofile=1024:2048\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_NOFILE\")\n        #expect(result[0].soft == 1024)\n        #expect(result[0].hard == 2048)\n    }\n\n    @Test\n    func testUlimitParserSingleValue() throws {\n        let result = try Parser.rlimits([\"nproc=512\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_NPROC\")\n        #expect(result[0].soft == 512)\n        #expect(result[0].hard == 512)\n    }\n\n    @Test\n    func testUlimitParserUnlimited() throws {\n        let result = try Parser.rlimits([\"memlock=unlimited\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_MEMLOCK\")\n        #expect(result[0].soft == UInt64.max)\n        #expect(result[0].hard == UInt64.max)\n    }\n\n    @Test\n    func testUlimitParserUnlimitedHardOnly() throws {\n        let result = try Parser.rlimits([\"stack=8192:unlimited\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_STACK\")\n        #expect(result[0].soft == 8192)\n        #expect(result[0].hard == UInt64.max)\n    }\n\n    @Test\n    func testUlimitParserMinusOneAsUnlimited() throws {\n        let result = try Parser.rlimits([\"core=-1\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_CORE\")\n        #expect(result[0].soft == UInt64.max)\n        #expect(result[0].hard == UInt64.max)\n    }\n\n    @Test\n    func testUlimitParserMultipleUlimits() throws {\n        let result = try Parser.rlimits([\"nofile=1024:2048\", \"nproc=256\", \"cpu=60:120\"])\n        #expect(result.count == 3)\n        #expect(result[0].limit == \"RLIMIT_NOFILE\")\n        #expect(result[1].limit == \"RLIMIT_NPROC\")\n        #expect(result[2].limit == \"RLIMIT_CPU\")\n    }\n\n    @Test\n    func testUlimitParserAllSupportedTypes() throws {\n        let types = [\"core\", \"cpu\", \"data\", \"fsize\", \"memlock\", \"nofile\", \"nproc\", \"rss\", \"stack\"]\n        let expectedRlimits = [\n            \"RLIMIT_CORE\", \"RLIMIT_CPU\", \"RLIMIT_DATA\", \"RLIMIT_FSIZE\",\n            \"RLIMIT_MEMLOCK\", \"RLIMIT_NOFILE\", \"RLIMIT_NPROC\", \"RLIMIT_RSS\", \"RLIMIT_STACK\",\n        ]\n\n        for (i, type) in types.enumerated() {\n            let result = try Parser.rlimits([\"\\(type)=100\"])\n            #expect(result.count == 1)\n            #expect(result[0].limit == expectedRlimits[i])\n        }\n    }\n\n    @Test\n    func testUlimitParserCaseInsensitive() throws {\n        let result = try Parser.rlimits([\"NOFILE=1024\", \"Nproc=512\"])\n        #expect(result.count == 2)\n        #expect(result[0].limit == \"RLIMIT_NOFILE\")\n        #expect(result[1].limit == \"RLIMIT_NPROC\")\n    }\n\n    @Test\n    func testUlimitParserInvalidFormat() throws {\n        #expect {\n            _ = try Parser.rlimits([\"nofile\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid ulimit format\")\n        }\n    }\n\n    @Test\n    func testUlimitParserUnsupportedType() throws {\n        #expect {\n            _ = try Parser.rlimits([\"foo=100\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"unsupported ulimit type\")\n        }\n    }\n\n    @Test\n    func testUlimitParserSoftExceedsHard() throws {\n        #expect {\n            _ = try Parser.rlimits([\"nofile=2048:1024\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"soft limit\") && error.description.contains(\"cannot exceed hard limit\")\n        }\n    }\n\n    @Test\n    func testUlimitParserDuplicateType() throws {\n        #expect {\n            _ = try Parser.rlimits([\"nofile=1024\", \"nofile=2048\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"duplicate ulimit type\")\n        }\n    }\n\n    @Test\n    func testUlimitParserInvalidValue() throws {\n        #expect {\n            _ = try Parser.rlimits([\"nofile=abc\"])\n        } throws: { error in\n            guard let error = error as? ContainerizationError else {\n                return false\n            }\n            return error.description.contains(\"invalid ulimit value\")\n        }\n    }\n\n    @Test\n    func testUlimitParserEmptyArray() throws {\n        let result = try Parser.rlimits([])\n        #expect(result.isEmpty)\n    }\n\n    @Test\n    func testUlimitParserZeroValue() throws {\n        let result = try Parser.rlimits([\"core=0\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_CORE\")\n        #expect(result[0].soft == 0)\n        #expect(result[0].hard == 0)\n    }\n\n    @Test\n    func testUlimitParserLargeValues() throws {\n        let result = try Parser.rlimits([\"nproc=\\(UInt64.max - 1):\\(UInt64.max)\"])\n        #expect(result.count == 1)\n        #expect(result[0].limit == \"RLIMIT_NPROC\")\n        #expect(result[0].soft == UInt64.max - 1)\n        #expect(result[0].hard == UInt64.max)\n    }\n\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/RequestSchemeTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPersistence\nimport ContainerizationError\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct RequestSchemeTests {\n    static let defaultDnsDomain = DefaultsStore.get(key: .defaultDNSDomain)\n\n    internal struct TestArg {\n        let scheme: String\n        let host: String\n        let expected: RequestScheme\n    }\n\n    @Test(arguments: [\n        TestArg(scheme: \"http\", host: \"myregistry.io\", expected: .http),\n        TestArg(scheme: \"https\", host: \"myregistry.io\", expected: .https),\n        TestArg(scheme: \"auto\", host: \"myregistry.io\", expected: .https),\n        TestArg(scheme: \"https\", host: \"localhost\", expected: .https),\n        TestArg(scheme: \"http\", host: \"localhost\", expected: .http),\n        TestArg(scheme: \"auto\", host: \"localhost\", expected: .http),\n        TestArg(scheme: \"http\", host: \"127.0.0.1\", expected: .http),\n        TestArg(scheme: \"https\", host: \"127.0.0.1\", expected: .https),\n        TestArg(scheme: \"auto\", host: \"127.0.0.1\", expected: .http),\n        TestArg(scheme: \"https\", host: \"10.3.4.1\", expected: .https),\n        TestArg(scheme: \"auto\", host: \"10.3.4.1\", expected: .http),\n        TestArg(scheme: \"auto\", host: \"some-dns-name.io.\\(Self.defaultDnsDomain)\", expected: .http),\n        TestArg(scheme: \"auto\", host: \"some-dns-name.io\", expected: .https),\n        TestArg(scheme: \"auto\", host: \"172.32.0.1\", expected: .https),\n        TestArg(scheme: \"auto\", host: \"172.22.23.61\", expected: .http),\n    ])\n\n    func testIsConnectionSecure(arg: TestArg) throws {\n        let requestScheme = RequestScheme(rawValue: arg.scheme)!\n        #expect(try requestScheme.schemeFor(host: arg.host) == arg.expected)\n    }\n\n    func testEmptyHostThrowsError() throws {\n        #expect(throws: (any Error).self) {\n            let requestScheme = RequestScheme(rawValue: \"https\")!\n            _ = try requestScheme.schemeFor(host: \"\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerAPIClientTests/UtilityTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerResource\nimport ContainerizationError\nimport Foundation\nimport Testing\n\n@testable import ContainerAPIClient\n\nstruct UtilityTests {\n\n    @Test(\"Parse simple key-value pairs\")\n    func testSimpleKeyValuePairs() {\n        let result = Utility.parseKeyValuePairs([\"key1=value1\", \"key2=value2\"])\n\n        #expect(result[\"key1\"] == \"value1\")\n        #expect(result[\"key2\"] == \"value2\")\n    }\n\n    @Test(\"Parse standalone keys\")\n    func testStandaloneKeys() {\n        let result = Utility.parseKeyValuePairs([\"standalone\"])\n\n        #expect(result[\"standalone\"] == \"\")\n    }\n\n    @Test(\"Parse empty input\")\n    func testEmptyInput() {\n        let result = Utility.parseKeyValuePairs([])\n\n        #expect(result.isEmpty)\n    }\n\n    @Test(\"Parse mixed format\")\n    func testMixedFormat() {\n        let result = Utility.parseKeyValuePairs([\"key1=value1\", \"standalone\", \"key2=value2\"])\n\n        #expect(result[\"key1\"] == \"value1\")\n        #expect(result[\"standalone\"] == \"\")\n        #expect(result[\"key2\"] == \"value2\")\n    }\n\n    @Test(\"Valid MAC address with colons\")\n    func testValidMACAddressWithColons() throws {\n        try Utility.validMACAddress(\"02:42:ac:11:00:02\")\n        try Utility.validMACAddress(\"AA:BB:CC:DD:EE:FF\")\n        try Utility.validMACAddress(\"00:00:00:00:00:00\")\n        try Utility.validMACAddress(\"ff:ff:ff:ff:ff:ff\")\n    }\n\n    @Test(\"Valid MAC address with hyphens\")\n    func testValidMACAddressWithHyphens() throws {\n        try Utility.validMACAddress(\"02-42-ac-11-00-02\")\n        try Utility.validMACAddress(\"AA-BB-CC-DD-EE-FF\")\n    }\n\n    @Test(\"Invalid MAC address format\")\n    func testInvalidMACAddressFormat() {\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"invalid\")\n        }\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"02:42:ac:11:00\")  // Too short\n        }\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"02:42:ac:11:00:02:03\")  // Too long\n        }\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"ZZ:ZZ:ZZ:ZZ:ZZ:ZZ\")  // Invalid hex\n        }\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"02:42:ac:11:00:\")  // Incomplete\n        }\n        #expect(throws: Error.self) {\n            try Utility.validMACAddress(\"02.42.ac.11.00.02\")  // Wrong separator\n        }\n    }\n\n    @Test\n    func testPublishPortParser() throws {\n        let ports = try Parser.publishPorts([\n            \"127.0.0.1:8000:9080\",\n            \"8080-8179:9000-9099/udp\",\n        ])\n        #expect(ports.count == 2)\n        #expect(ports[0].hostAddress.description == \"127.0.0.1\")\n        #expect(ports[0].hostPort == 8000)\n        #expect(ports[0].containerPort == 9080)\n        #expect(ports[0].proto == .tcp)\n        #expect(ports[0].count == 1)\n        #expect(ports[1].hostAddress.description == \"0.0.0.0\")\n        #expect(ports[1].hostPort == 8080)\n        #expect(ports[1].containerPort == 9000)\n        #expect(ports[1].proto == .udp)\n        #expect(ports[1].count == 100)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerBuildTests/BuildFile.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Logging\nimport Testing\n\n@testable import ContainerBuild\n\n@Suite class BuildFileResolvePathTests {\n    private var baseTempURL: URL\n    private let fileManager = FileManager.default\n\n    init() throws {\n        self.baseTempURL = URL.temporaryDirectory\n            .appendingPathComponent(\"BuildFileTests-\\(UUID().uuidString)\")\n        try fileManager.createDirectory(at: baseTempURL, withIntermediateDirectories: true, attributes: nil)\n    }\n\n    deinit {\n        try? fileManager.removeItem(at: baseTempURL)\n    }\n\n    private func createFile(at url: URL, content: String = \"\") throws {\n        try fileManager.createDirectory(\n            at: url.deletingLastPathComponent(),\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n        let created = fileManager.createFile(\n            atPath: url.path,\n            contents: content.data(using: .utf8),\n            attributes: nil\n        )\n        try #require(created)\n    }\n\n    @Test func testResolvePathFindsDockerfile() throws {\n        let contextDir = baseTempURL.path\n        let dockerfilePath = baseTempURL.appendingPathComponent(\"Dockerfile\")\n        try createFile(at: dockerfilePath, content: \"FROM alpine\")\n\n        let result = try BuildFile.resolvePath(contextDir: contextDir)\n\n        #expect(result == dockerfilePath.path)\n    }\n\n    @Test func testResolvePathFindsContainerfile() throws {\n        let contextDir = baseTempURL.path\n        let containerfilePath = baseTempURL.appendingPathComponent(\"Containerfile\")\n        try createFile(at: containerfilePath, content: \"FROM alpine\")\n\n        let result = try BuildFile.resolvePath(contextDir: contextDir)\n\n        #expect(result == containerfilePath.path)\n    }\n\n    @Test func testResolvePathPrefersDockerfileWhenBothExist() throws {\n        let contextDir = baseTempURL.path\n        let dockerfilePath = baseTempURL.appendingPathComponent(\"Dockerfile\")\n        let containerfilePath = baseTempURL.appendingPathComponent(\"Containerfile\")\n        try createFile(at: dockerfilePath, content: \"FROM alpine\")\n        try createFile(at: containerfilePath, content: \"FROM ubuntu\")\n\n        let result = try BuildFile.resolvePath(contextDir: contextDir)\n\n        #expect(result == dockerfilePath.path)\n    }\n\n    @Test func testResolvePathReturnsNilWhenNoFilesExist() throws {\n        let contextDir = baseTempURL.path\n\n        let result = try BuildFile.resolvePath(contextDir: contextDir)\n\n        #expect(result == nil)\n    }\n\n    @Test func testResolvePathWithEmptyDirectory() throws {\n        let emptyDir = baseTempURL.appendingPathComponent(\"empty\")\n        try fileManager.createDirectory(at: emptyDir, withIntermediateDirectories: true, attributes: nil)\n\n        let result = try BuildFile.resolvePath(contextDir: emptyDir.path)\n\n        #expect(result == nil)\n    }\n\n    @Test func testResolvePathWithNestedContextDirectory() throws {\n        let nestedDir = baseTempURL.appendingPathComponent(\"project/build\")\n        try fileManager.createDirectory(at: nestedDir, withIntermediateDirectories: true, attributes: nil)\n        let dockerfilePath = nestedDir.appendingPathComponent(\"Dockerfile\")\n        try createFile(at: dockerfilePath, content: \"FROM node\")\n\n        let result = try BuildFile.resolvePath(contextDir: nestedDir.path)\n\n        #expect(result == dockerfilePath.path)\n    }\n\n    @Test func testResolvePathWithRelativeContextDirectory() throws {\n        let nestedDir = baseTempURL.appendingPathComponent(\"project\")\n        try fileManager.createDirectory(at: nestedDir, withIntermediateDirectories: true, attributes: nil)\n        let dockerfilePath = nestedDir.appendingPathComponent(\"Dockerfile\")\n        try createFile(at: dockerfilePath, content: \"FROM python\")\n\n        // Test with the absolute path\n        let result = try BuildFile.resolvePath(contextDir: nestedDir.path)\n\n        #expect(result == dockerfilePath.path)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerBuildTests/BuilderExtensionsTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerBuild\n\n@Suite class URLExtensionFileSystemTests {\n\n    private var baseTempURL: URL!\n    private let fileManager = FileManager.default\n\n    init() throws {\n        baseTempURL = URL(fileURLWithPath: NSTemporaryDirectory())\n            .appendingPathComponent(\"URLExtensionTests-\\(UUID().uuidString)\")\n        try fileManager.createDirectory(at: baseTempURL, withIntermediateDirectories: true, attributes: nil)\n    }\n\n    deinit {\n        if let baseTempURL = baseTempURL {\n            try? fileManager.removeItem(at: baseTempURL)\n        }\n    }\n\n    // MARK: - Helpers\n\n    private func createDirectory(at url: URL) throws {\n        try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)\n    }\n\n    private func createFile(at url: URL, content: String = \"\") throws {\n        try fileManager.createDirectory(\n            at: url.deletingLastPathComponent(),\n            withIntermediateDirectories: true,\n            attributes: nil)\n        #expect(\n            fileManager.createFile(\n                atPath: url.path,\n                contents: content.data(using: .utf8),\n                attributes: nil))\n    }\n\n    // MARK: - parentOf Tests\n\n    @Test func testParentOfDirectParent() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir1\")\n        let childDir = parentDir.appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        #expect(parentDir.parentOf(childDir))\n    }\n\n    @Test func testParentOfGrandparent() throws {\n        let grandParent = baseTempURL.appendingPathComponent(\"dir3\").appendingPathComponent(\"test\")\n        let childDir = grandParent.appendingPathComponent(\"dir4\").appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        #expect(grandParent.parentOf(childDir))\n    }\n\n    @Test func testParentOfBaseTemp() throws {\n        let childDir = baseTempURL.appendingPathComponent(\"dir4\").appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        #expect(baseTempURL.parentOf(childDir))\n    }\n\n    @Test func testParentOfRoot() throws {\n        let rootURL = URL(fileURLWithPath: \"/\")\n        let childDir = baseTempURL.appendingPathComponent(\"dir4\")\n        try createDirectory(at: childDir)\n        #expect(rootURL.parentOf(childDir))\n        #expect(rootURL.parentOf(baseTempURL))\n    }\n\n    @Test func testParentOfSamePath() throws {\n        let dir = baseTempURL.appendingPathComponent(\"dir4\")\n        try createDirectory(at: dir)\n        let sameDir = URL(fileURLWithPath: dir.path)\n        #expect(dir.parentOf(sameDir))\n        #expect(sameDir.parentOf(dir))\n    }\n\n    @Test func testParentOfRootToRoot() {\n        let root1 = URL(fileURLWithPath: \"/\")\n        let root2 = URL(fileURLWithPath: \"/\")\n        #expect(root1.parentOf(root2))\n    }\n\n    @Test func testParentOfDifferentPaths() throws {\n        let dir1 =\n            baseTempURL\n            .appendingPathComponent(\"dir3\")\n            .appendingPathComponent(\"test\")\n            .appendingPathComponent(\"dir4\")\n        let dir2 =\n            baseTempURL\n            .appendingPathComponent(\"dir3\")\n            .appendingPathComponent(\"another\")\n            .appendingPathComponent(\"file\")\n        try createDirectory(at: dir1)\n        try createDirectory(at: dir2)\n        #expect(false == dir1.parentOf(dir2))\n        #expect(false == dir2.parentOf(dir1))\n    }\n\n    @Test func testParentOfSiblingPaths() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir3\").appendingPathComponent(\"test\")\n        let sibling1 = parentDir.appendingPathComponent(\"dir4\")\n        let sibling2 = parentDir.appendingPathComponent(\"dir5\")\n        try createDirectory(at: sibling1)\n        try createDirectory(at: sibling2)\n        #expect(false == sibling1.parentOf(sibling2))\n        #expect(false == sibling2.parentOf(sibling1))\n    }\n\n    @Test func testParentOfChildIsParentFalse() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let childDir = parentDir.appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        #expect(false == childDir.parentOf(parentDir))\n    }\n\n    @Test func testParentOfPartialNameMatch() throws {\n        let partial = baseTempURL.appendingPathComponent(\"Doc\")\n        let actualDir = baseTempURL.appendingPathComponent(\"dir4\")\n        try createDirectory(at: actualDir)\n        #expect(false == partial.parentOf(actualDir))\n    }\n\n    @Test func testParentOfPathNormalization() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let childDir = parentDir.appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        let normalized =\n            baseTempURL\n            .appendingPathComponent(\"dir8\")\n            .appendingPathComponent(\"..\")\n            .appendingPathComponent(\"dir4\")\n        #expect(normalized.parentOf(childDir))\n    }\n\n    @Test func testParentOfChildWithNormalization() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let targetChildDir = parentDir.appendingPathComponent(\"dir2\")\n        try createDirectory(at: targetChildDir)\n        let normalizedChild =\n            parentDir\n            .appendingPathComponent(\"dir9\")\n            .appendingPathComponent(\"..\")\n            .appendingPathComponent(\"dir2\")\n        #expect(parentDir.parentOf(normalizedChild))\n    }\n\n    @Test func testParentOfPercentEncoding() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"My dir4\")\n        let childDir = parentDir.appendingPathComponent(\"dir2 X\")\n        try createDirectory(at: childDir)\n        let parentEncoded = URL(fileURLWithPath: baseTempURL.path + \"/My%20dir4\")\n        let childEncoded = URL(fileURLWithPath: baseTempURL.path + \"/My%20dir4/dir2%20X\")\n        #expect(parentDir.parentOf(childDir))\n        #expect(parentEncoded.parentOf(childEncoded))\n        #expect(parentDir.parentOf(childEncoded))\n        #expect(parentEncoded.parentOf(childDir))\n    }\n\n    @Test func testParentOfNonFileURL() throws {\n        let httpURL = URL(string: \"http://example.com/path\")!\n        let fileURL = baseTempURL.appendingPathComponent(\"file\")\n        try createFile(at: fileURL)\n        #expect(false == httpURL.parentOf(fileURL))\n        #expect(false == fileURL.parentOf(httpURL))\n    }\n\n    @Test func testRelativeChildPathDirectChild() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir1\")\n        let childFile = parentDir.appendingPathComponent(\"dir2\").appendingPathComponent(\"file\")\n        try createFile(at: childFile)\n        let relative = try childFile.relativeChildPath(to: parentDir)\n        #expect(relative == \"dir2/file\")\n    }\n\n    @Test func testRelativeChildPathDeeperChild() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir3\").appendingPathComponent(\"test\")\n        let childFile = parentDir.appendingPathComponent(\"dir4/dir2/file\")\n        try createFile(at: childFile)\n        let relative = try childFile.relativeChildPath(to: parentDir)\n        #expect(relative == \"dir4/dir2/file\")\n    }\n\n    @Test func testRelativeChildPathDirectlyInsideBase() throws {\n        let childFile = baseTempURL.appendingPathComponent(\"file\")\n        try createFile(at: childFile)\n        let relative = try childFile.relativeChildPath(to: baseTempURL)\n        #expect(relative == \"file\")\n    }\n\n    @Test func testRelativeChildPathSamePath() throws {\n        let dir = baseTempURL.appendingPathComponent(\"dir4\")\n        try createDirectory(at: dir)\n        let dirCopy = URL(fileURLWithPath: dir.path)\n        #expect(try dir.relativeChildPath(to: dirCopy) == \"\")\n        #expect(try dirCopy.relativeChildPath(to: dir) == \"\")\n    }\n\n    @Test func testRelativeChildPathRootChild() throws {\n        let rootURL = URL(fileURLWithPath: \"/\")\n        let childDir = baseTempURL.appendingPathComponent(\"dir4\")\n        try createDirectory(at: childDir)\n\n        // Compare only the portion that comes after \"/\"\n        let expected =\n            baseTempURL\n            .standardizedFileURL\n            .pathComponents\n            .dropFirst()  // remove \"/\"\n            .joined(separator: \"/\") + \"/dir4\"\n\n        let relative = try childDir.relativeChildPath(to: rootURL)\n        #expect(relative == expected)\n    }\n\n    @Test func testRelativeChildPathRootToRootIsEmpty() throws {\n        let root1 = URL(fileURLWithPath: \"/\")\n        let root2 = URL(fileURLWithPath: \"/\")\n        #expect(try root1.relativeChildPath(to: root2) == \"\")\n    }\n\n    @Test func testRelativeChildPathNormalization() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let childFile = parentDir.appendingPathComponent(\"dir2/file\")\n        try createFile(at: childFile)\n        let normalizedParent =\n            baseTempURL\n            .appendingPathComponent(\"dir8\")\n            .appendingPathComponent(\"..\")\n            .appendingPathComponent(\"dir4\")\n        #expect(try childFile.relativeChildPath(to: normalizedParent) == \"dir2/file\")\n    }\n\n    @Test func testRelativeChildPathNormalizedChild() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let childFile = parentDir.appendingPathComponent(\"dir2/file\")\n        try createFile(at: childFile)\n        let normalizedChild =\n            parentDir\n            .appendingPathComponent(\"dir9\")\n            .appendingPathComponent(\"..\")\n            .appendingPathComponent(\"dir2\")\n            .appendingPathComponent(\"file\")\n        #expect(try normalizedChild.relativeChildPath(to: parentDir) == \"dir2/file\")\n    }\n\n    @Test func testRelativeChildPathPercentEncoding() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"My dir4\")\n        let childFile = parentDir.appendingPathComponent(\"dir2 X/file1\")\n        try createFile(at: childFile)\n        #expect(try childFile.relativeChildPath(to: parentDir) == \"dir2 X/file1\")\n\n        let parentEncoded = URL(fileURLWithPath: baseTempURL.path + \"/My%20dir4\")\n        let childEncoded = URL(fileURLWithPath: baseTempURL.path + \"/My%20dir4/dir2%20X/file1\")\n\n        #expect(try childEncoded.relativeChildPath(to: parentDir) == \"dir2 X/file1\")\n        #expect(try childEncoded.relativeChildPath(to: parentEncoded) == \"dir2 X/file1\")\n    }\n\n    // MARK: - relativeChildPath Error Tests\n\n    @Test func testRelativeChildPathThrowsWhenNotAChild() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let otherDir = baseTempURL.appendingPathComponent(\"dir7/file\")\n        try createDirectory(at: parentDir)\n        try createDirectory(at: otherDir)\n\n        #expect(throws: (BuildFSSync.Error.pathIsNotChild(otherDir.cleanPath, parentDir.cleanPath)).self) {\n            try otherDir.relativeChildPath(to: parentDir)\n        }\n    }\n\n    @Test func testRelativeChildPathThrowsForSiblings() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir3/test\")\n        let sibling1 = parentDir.appendingPathComponent(\"dir4\")\n        let sibling2 = parentDir.appendingPathComponent(\"dir5\")\n        try createDirectory(at: sibling1)\n        try createDirectory(at: sibling2)\n        #expect(throws: (BuildFSSync.Error.pathIsNotChild(sibling2.cleanPath, sibling1.cleanPath)).self) {\n            try sibling2.relativeChildPath(to: sibling1)\n        }\n    }\n\n    @Test func testRelativeChildPathParentAsChildThrows() throws {\n        let parentDir = baseTempURL.appendingPathComponent(\"dir4\")\n        let childDir = parentDir.appendingPathComponent(\"dir2\")\n        try createDirectory(at: childDir)\n        #expect(throws: (BuildFSSync.Error.pathIsNotChild(parentDir.cleanPath, childDir.cleanPath)).self) {\n            try parentDir.relativeChildPath(to: childDir)\n        }\n    }\n\n    // MARK: - cleanPath Tests\n\n    @Test func testCleanPathSimple() throws {\n        let file = baseTempURL.appendingPathComponent(\"file\")\n        try createFile(at: file)\n        #expect(file.cleanPath.hasSuffix(\"/file\"))\n        #expect(file.cleanPath.contains(baseTempURL.lastPathComponent))\n    }\n\n    @Test func testCleanPathWithSpaces() throws {\n        let file = baseTempURL.appendingPathComponent(\"my file with spaces\")\n        try createFile(at: file)\n        #expect(file.cleanPath.hasSuffix(\"/my file with spaces\"))\n        #expect(file.cleanPath.contains(baseTempURL.lastPathComponent))\n    }\n\n    @Test func testCleanPathWithPercentEncoding() throws {\n        let fileWithSpace = baseTempURL.appendingPathComponent(\"my file\")\n        try createFile(at: fileWithSpace)\n\n        let encodedPathString = baseTempURL.path + \"/my%20file\"\n        let urlFromString = URL(fileURLWithPath: encodedPathString)\n\n        #expect(urlFromString.cleanPath == fileWithSpace.cleanPath)\n        #expect(urlFromString.cleanPath.hasSuffix(\"/my file\"))\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerBuildTests/GlobberTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerBuild\n\nstruct TestCase {\n    let pattern: String\n    let fileName: String\n    let expectSuccess: Bool\n}\n\n// test cases adapted from https://github.com/moby/patternmatcher/tree/main\nlet globTestCases = [\n    TestCase(pattern: \"*\", fileName: \"test.go\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"test.go\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"file\", expectSuccess: true),\n    TestCase(pattern: \"*.go\", fileName: \"test.go\", expectSuccess: true),\n    TestCase(pattern: \"a.|)$(}+{bc\", fileName: \"a.|)$(}+{bc\", expectSuccess: true),\n    TestCase(pattern: \"abc.def\", fileName: \"abcdef\", expectSuccess: false),\n    TestCase(pattern: \"abc.def\", fileName: \"abc.def\", expectSuccess: true),\n    TestCase(pattern: \"abc.def\", fileName: \"abcZdef\", expectSuccess: false),\n    TestCase(pattern: \"abc?def\", fileName: \"abcZdef\", expectSuccess: true),\n    TestCase(pattern: \"abc?def\", fileName: \"abcdef\", expectSuccess: false),\n    TestCase(pattern: \"a[b-d]e\", fileName: \"ae\", expectSuccess: false),\n    TestCase(pattern: \"a[b-d]e\", fileName: \"ace\", expectSuccess: true),\n    TestCase(pattern: \"a[b-d]e\", fileName: \"aae\", expectSuccess: false),\n    TestCase(pattern: \"a[^b-d]e\", fileName: \"aze\", expectSuccess: true),\n    TestCase(pattern: \"a[\\\\^b-d]e\", fileName: \"abe\", expectSuccess: true),\n    TestCase(pattern: \"a[\\\\^b-d]e\", fileName: \"aze\", expectSuccess: false),\n]\n\nlet errorGlobTestCases = [\n    TestCase(pattern: \"[]a]\", fileName: \"]\", expectSuccess: true),\n    TestCase(pattern: \"[\", fileName: \"a\", expectSuccess: true),\n    TestCase(pattern: \"[^\", fileName: \"a\", expectSuccess: true),\n    TestCase(pattern: \"[^bc\", fileName: \"a\", expectSuccess: true),\n    TestCase(pattern: \"a[\", fileName: \"a\", expectSuccess: true),\n    TestCase(pattern: \"a[\", fileName: \"ab\", expectSuccess: true),\n]\n\nlet testCases = [\n    TestCase(pattern: \"*\", fileName: \"test/test.go\", expectSuccess: true),\n    TestCase(pattern: \"**.go\", fileName: \"test/test.go\", expectSuccess: true),\n    TestCase(pattern: \"**file\", fileName: \"test/file\", expectSuccess: true),\n    TestCase(pattern: \"**/*\", fileName: \"test/test.go\", expectSuccess: true),\n    TestCase(pattern: \"**/\", fileName: \"file\", expectSuccess: true),\n    TestCase(pattern: \"**/\", fileName: \"file/\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"file\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"file/\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**\", fileName: \"dir/file/\", expectSuccess: true),\n    TestCase(pattern: \"**/\", fileName: \"dir/file/\", expectSuccess: true),\n    TestCase(pattern: \"**/**\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/**\", fileName: \"dir/file/\", expectSuccess: true),\n    TestCase(pattern: \"dir/**\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"dir/**\", fileName: \"dir/file/\", expectSuccess: true),\n    TestCase(pattern: \"dir/**\", fileName: \"dir/dir2/file\", expectSuccess: true),\n    TestCase(pattern: \"dir/**\", fileName: \"dir/dir2/file/\", expectSuccess: true),\n    TestCase(pattern: \"**/dir\", fileName: \"dir\", expectSuccess: true),\n    TestCase(pattern: \"**/dir\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/dir2/*\", fileName: \"dir/dir2/file\", expectSuccess: true),\n    TestCase(pattern: \"**/dir2/*\", fileName: \"dir/dir2/file/\", expectSuccess: true),\n    TestCase(pattern: \"**/dir2/**\", fileName: \"dir/dir2/dir3/file\", expectSuccess: true),\n    TestCase(pattern: \"**/dir2/**\", fileName: \"dir/dir2/dir3/file/\", expectSuccess: true),\n    TestCase(pattern: \"**file\", fileName: \"file\", expectSuccess: true),\n    TestCase(pattern: \"**file\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/file\", fileName: \"dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**file\", fileName: \"dir/dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/file\", fileName: \"dir/dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/file*\", fileName: \"dir/dir/file\", expectSuccess: true),\n    TestCase(pattern: \"**/file*\", fileName: \"dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/file*txt\", fileName: \"dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/file*.txt\", fileName: \"dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/file*.txt*\", fileName: \"dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/**/*.txt\", fileName: \"dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/**/*.txt2\", fileName: \"dir/dir/file.txt\", expectSuccess: false),\n    TestCase(pattern: \"**/*.txt\", fileName: \"file.txt\", expectSuccess: true),\n    TestCase(pattern: \"**/**/*.txt\", fileName: \"file.txt\", expectSuccess: true),\n    TestCase(pattern: \"a**/*.txt\", fileName: \"a/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"a**/*.txt\", fileName: \"a/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"a**/*.txt\", fileName: \"a/dir/dir/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"a/*.txt\", fileName: \"a/dir/file.txt\", expectSuccess: false),\n    TestCase(pattern: \"a/*.txt\", fileName: \"a/file.txt\", expectSuccess: true),\n    TestCase(pattern: \"a/*.txt**\", fileName: \"a/file.txt\", expectSuccess: true),\n    TestCase(pattern: \".*\", fileName: \".foo\", expectSuccess: true),\n    TestCase(pattern: \".*\", fileName: \"foo\", expectSuccess: false),\n    TestCase(pattern: \"abc.def\", fileName: \"abcdef\", expectSuccess: false),\n    TestCase(pattern: \"abc.def\", fileName: \"abc.def\", expectSuccess: true),\n    TestCase(pattern: \"abc.def\", fileName: \"abcZdef\", expectSuccess: false),\n    TestCase(pattern: \"abc?def\", fileName: \"abcZdef\", expectSuccess: true),\n    TestCase(pattern: \"abc?def\", fileName: \"abcdef\", expectSuccess: false),\n    TestCase(pattern: \"**/foo/bar\", fileName: \"foo/bar\", expectSuccess: true),\n    TestCase(pattern: \"**/foo/bar\", fileName: \"dir/foo/bar\", expectSuccess: true),\n    TestCase(pattern: \"**/foo/bar\", fileName: \"dir/dir2/foo/bar\", expectSuccess: true),\n    TestCase(pattern: \"abc/**\", fileName: \"abc/def\", expectSuccess: true),\n    TestCase(pattern: \"abc/**\", fileName: \"abc/def/ghi\", expectSuccess: true),\n    TestCase(pattern: \"**/.foo\", fileName: \".foo\", expectSuccess: true),\n    TestCase(pattern: \"**/.foo\", fileName: \"bar.foo\", expectSuccess: false),\n    TestCase(pattern: \"./bar.*\", fileName: \"bar.foo\", expectSuccess: true),\n    TestCase(pattern: \"./bar.*/\", fileName: \"bar.foo\", expectSuccess: true),\n    TestCase(pattern: \"a(b)c/def\", fileName: \"a(b)c/def\", expectSuccess: true),\n    TestCase(pattern: \"a(b)c/def\", fileName: \"a(b)c/xyz\", expectSuccess: false),\n    TestCase(pattern: \"a.|)$(}+{bc\", fileName: \"a.|)$(}+{bc\", expectSuccess: true),\n    TestCase(pattern: \"dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl\", fileName: \"dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl\", expectSuccess: true),\n    TestCase(pattern: \"dist/*.whl\", fileName: \"dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl\", expectSuccess: true),\n]\n\n@Suite struct TestGlobber {\n    @Test(\"All glob patterns match\", arguments: globTestCases)\n    func testGlobMatching(_ test: TestCase) throws {\n        let globber = Globber(URL(fileURLWithPath: \"/\"))\n        let found = try globber.glob(test.fileName, test.pattern)\n        #expect(found == test.expectSuccess, \"expected found to be \\(test.expectSuccess), instead got \\(found)\")\n    }\n\n    @Test(\"Invalid computed regex patterns throw error\", arguments: errorGlobTestCases)\n    func testInvalidGlob(_ test: TestCase) throws {\n        let globber = Globber(URL(fileURLWithPath: \"/\"))\n        #expect(throws: (any Error).self) {\n            try globber.glob(test.fileName, test.pattern)\n        }\n    }\n\n    @Test(\"All expected patterns match\", arguments: testCases)\n    func testExpectedPatterns(_ test: TestCase) throws {\n        let charactersToTrim = CharacterSet(charactersIn: \"/\")\n        let components = test.fileName\n            .trimmingCharacters(in: charactersToTrim)\n            .components(separatedBy: \"/\")\n\n        // tempDir is the directory we're making the files or nested files in\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        var fileDir: URL = tempDir\n\n        // testDir is the directory before the last component that we need to create\n        components.dropLast().forEach { component in\n            var d = fileDir\n            if component == \"..\" {\n                d = fileDir.deletingLastPathComponent()\n            } else if component != \".\" {\n                d = fileDir.appendingPathComponent(component)\n            }\n            #expect(throws: Never.self) {\n                try FileManager.default.createDirectory(at: d, withIntermediateDirectories: true)\n            }\n            fileDir = d\n        }\n\n        #expect(throws: Never.self) {\n            try FileManager.default.createDirectory(at: fileDir, withIntermediateDirectories: true)\n        }\n        let testFile = fileDir.appendingPathComponent(components.last!)\n        #expect(throws: Never.self) {\n            try \"\".write(to: testFile, atomically: true, encoding: .utf8)\n        }\n\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        let globber = Globber(tempDir)\n        #expect(throws: Never.self) {\n            try globber.match(test.pattern)\n            let found: Bool = !globber.results.isEmpty\n            #expect(found == test.expectSuccess, \"expected match to be \\(test.expectSuccess), instead got \\(found) \\(tempDir.childrenRecursive)\")\n        }\n    }\n\n    @Test(\"Test the base directory is not include in results\")\n    func testBaseDirNotIncluded() throws {\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        let testDir = tempDir.appendingPathComponent(\"abc\")\n\n        #expect(throws: Never.self) {\n            try FileManager.default.createDirectory(at: testDir, withIntermediateDirectories: true)\n        }\n\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        let globber = Globber(testDir)\n        #expect(throws: Never.self) {\n            try globber.match(\"abc/**\")\n            #expect(globber.results.isEmpty, \"expected to find no matches, instead found \\(globber.results)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerNetworkServiceTests/AttachmentAllocatorTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Testing\n\n@testable import ContainerNetworkService\n\nstruct AttachmentAllocatorTest {\n    @Test func testAllocateSingleHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address = try await allocator.allocate(hostname: \"test-host\")\n\n        #expect(address >= 100)\n        #expect(address < 110)\n    }\n\n    @Test func testAllocateSameHostnameTwice() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address1 = try await allocator.allocate(hostname: \"test-host\")\n        let address2 = try await allocator.allocate(hostname: \"test-host\")\n\n        #expect(address1 == address2)\n    }\n\n    @Test func testAllocateMultipleHostnames() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address1 = try await allocator.allocate(hostname: \"host1\")\n        let address2 = try await allocator.allocate(hostname: \"host2\")\n        let address3 = try await allocator.allocate(hostname: \"host3\")\n\n        #expect(address1 != address2)\n        #expect(address2 != address3)\n        #expect(address1 != address3)\n    }\n\n    @Test func testLookupAllocatedHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let allocatedAddress = try await allocator.allocate(hostname: \"test-host\")\n        let lookedUpAddress = try await allocator.lookup(hostname: \"test-host\")\n\n        #expect(lookedUpAddress == allocatedAddress)\n    }\n\n    @Test func testLookupNonExistentHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address = try await allocator.lookup(hostname: \"non-existent\")\n\n        #expect(address == nil)\n    }\n\n    @Test func testDeallocateAllocatedHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let allocatedAddress = try await allocator.allocate(hostname: \"test-host\")\n        let deallocatedAddress = try await allocator.deallocate(hostname: \"test-host\")\n\n        #expect(deallocatedAddress == allocatedAddress)\n\n        // After deallocation, lookup should return nil\n        let lookedUpAddress = try await allocator.lookup(hostname: \"test-host\")\n        #expect(lookedUpAddress == nil)\n    }\n\n    @Test func testDeallocateNonExistentHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let deallocatedAddress = try await allocator.deallocate(hostname: \"non-existent\")\n\n        #expect(deallocatedAddress == nil)\n    }\n\n    @Test func testReallocateAfterDeallocation() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address1 = try await allocator.allocate(hostname: \"test-host\")\n        let released1 = try await allocator.deallocate(hostname: \"test-host\")\n        #expect(address1 == released1)\n        let address2 = try await allocator.allocate(hostname: \"test-host\")\n\n        // After deallocation, allocating the same hostname should give a new address\n        #expect(address2 >= 100)\n        #expect(address2 < 110)\n    }\n\n    @Test func testAllocateUntilFull() async throws {\n        let size = 5\n        let allocator = try AttachmentAllocator(lower: 100, size: size)\n\n        // Allocate up to the limit\n        for i in 0..<size {\n            _ = try await allocator.allocate(hostname: \"host\\(i)\")\n        }\n\n        // Attempting to allocate one more should throw\n        await #expect(throws: Error.self) {\n            try await allocator.allocate(hostname: \"extra-host\")\n        }\n    }\n\n    @Test func testDeallocateAndReallocateDifferentHostname() async throws {\n        let size = 3\n        let allocator = try AttachmentAllocator(lower: 100, size: size)\n\n        // Fill up the allocator\n        let address1 = try await allocator.allocate(hostname: \"host1\")\n        let address2 = try await allocator.allocate(hostname: \"host2\")\n        let address3 = try await allocator.allocate(hostname: \"host3\")\n\n        // Deallocate one\n        let released2 = try await allocator.deallocate(hostname: \"host2\")\n        #expect(address2 == released2)\n\n        // Should be able to allocate a new hostname now\n        let newAddress = try await allocator.allocate(hostname: \"host4\")\n        #expect(newAddress >= 100)\n        #expect(newAddress < 103)\n\n        // The three remaining allocations should all be different\n        let finalAddress1 = try await allocator.lookup(hostname: \"host1\")\n        let finalAddress3 = try await allocator.lookup(hostname: \"host3\")\n        let finalAddress4 = try await allocator.lookup(hostname: \"host4\")\n\n        #expect(finalAddress1 == address1)\n        #expect(finalAddress3 == address3)\n        #expect(finalAddress4 == newAddress)\n    }\n\n    @Test func testDisableAllocatorWhenEmpty() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let disabled = await allocator.disableAllocator()\n\n        #expect(disabled == true)\n\n        // After disabling, allocation should fail\n        await #expect(throws: Error.self) {\n            try await allocator.allocate(hostname: \"test-host\")\n        }\n    }\n\n    @Test func testDisableAllocatorWhenNotEmpty() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        _ = try await allocator.allocate(hostname: \"test-host\")\n\n        let disabled = await allocator.disableAllocator()\n\n        #expect(disabled == false)\n\n        // Since disable failed, should still be able to allocate\n        let address = try await allocator.allocate(hostname: \"another-host\")\n        #expect(address >= 100)\n        #expect(address < 110)\n    }\n\n    @Test func testDisableAfterDeallocatingAll() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        _ = try await allocator.allocate(hostname: \"host1\")\n        _ = try await allocator.allocate(hostname: \"host2\")\n\n        try await allocator.deallocate(hostname: \"host1\")\n        try await allocator.deallocate(hostname: \"host2\")\n\n        let disabled = await allocator.disableAllocator()\n\n        #expect(disabled == true)\n\n        // After disabling, allocation should fail\n        await #expect(throws: Error.self) {\n            try await allocator.allocate(hostname: \"test-host\")\n        }\n    }\n\n    @Test func testMultipleDeallocationsOfSameHostname() async throws {\n        let allocator = try AttachmentAllocator(lower: 100, size: 10)\n\n        let address = try await allocator.allocate(hostname: \"test-host\")\n\n        let firstDeallocate = try await allocator.deallocate(hostname: \"test-host\")\n        #expect(firstDeallocate == address)\n\n        // Second deallocation should return nil since it's already deallocated\n        let secondDeallocate = try await allocator.deallocate(hostname: \"test-host\")\n        #expect(secondDeallocate == nil)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerOSTests/DirectoryWatcherTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerOS\nimport ContainerizationError\nimport DNSServer\nimport Foundation\nimport Testing\n\nstruct DirectoryWatcherTest {\n    let testUUID = UUID().uuidString\n\n    private var testDir: URL! {\n        let tempDir = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)\n            .appendingPathComponent(\".clitests\")\n            .appendingPathComponent(testUUID)\n        try! FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n        return tempDir\n    }\n\n    private func withTempDir<T>(_ body: (URL) async throws -> T) async throws -> T {\n        let tempDir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)\n\n        defer {\n            try? FileManager.default.removeItem(at: tempDir)\n        }\n\n        return try await body(tempDir)\n    }\n\n    private actor CreatedURLs {\n        nonisolated(unsafe) public var urls: [URL]\n\n        public init() {\n            self.urls = []\n        }\n    }\n\n    @Test func testWatchingExistingDirectory() async throws {\n        try await withTempDir { tempDir in\n\n            let watcher = DirectoryWatcher(directoryURL: tempDir, log: nil)\n            let createdURLs = CreatedURLs()\n            let name = \"newFile\"\n\n            await watcher.startWatching { [createdURLs] urls in\n                for url in urls where url.lastPathComponent == name {\n                    createdURLs.urls.append(url)\n                }\n            }\n\n            try await Task.sleep(for: .milliseconds(100))\n            let newFile = tempDir.appendingPathComponent(name)\n            FileManager.default.createFile(atPath: newFile.path, contents: nil)\n            try await Task.sleep(for: .milliseconds(500))\n\n            #expect(!createdURLs.urls.isEmpty, \"directory watcher failed to detect new file\")\n            #expect(createdURLs.urls.first!.lastPathComponent == name)\n        }\n    }\n\n    @Test func testWatchingNonExistingDirectory() async throws {\n        try await withTempDir { tempDir in\n            let uuid = UUID().uuidString\n            let childDir = tempDir.appendingPathComponent(uuid)\n\n            let watcher = DirectoryWatcher(directoryURL: childDir, log: nil)\n            let createdURLs = CreatedURLs()\n            let name = \"newFile\"\n\n            await watcher.startWatching { [createdURLs] urls in\n                for url in urls where url.lastPathComponent == name {\n                    createdURLs.urls.append(url)\n                }\n            }\n\n            try await Task.sleep(for: .milliseconds(100))\n            try FileManager.default.createDirectory(at: childDir, withIntermediateDirectories: true)\n\n            try await Task.sleep(for: DirectoryWatcher.watchPeriod)\n            let newFile = childDir.appendingPathComponent(name)\n            FileManager.default.createFile(atPath: newFile.path, contents: nil)\n            try await Task.sleep(for: .milliseconds(500))\n\n            #expect(!createdURLs.urls.isEmpty, \"directory watcher failed to detect parent directory\")\n            #expect(createdURLs.urls.first!.lastPathComponent == name)\n        }\n    }\n\n    @Test func testWatchingNonExistingParent() async throws {\n        try await withTempDir { tempDir in\n            let parent = UUID().uuidString\n            let child = UUID().uuidString\n            let childDir = tempDir.appendingPathComponent(parent).appendingPathComponent(child)\n\n            let watcher = DirectoryWatcher(directoryURL: childDir, log: nil)\n            let createdURLs = CreatedURLs()\n            let name = \"newFile\"\n\n            await watcher.startWatching { urls in\n                for url in urls where url.lastPathComponent == name {\n                    createdURLs.urls.append(url)\n                }\n            }\n\n            try await Task.sleep(for: .milliseconds(100))\n            try FileManager.default.createDirectory(at: childDir, withIntermediateDirectories: true)\n\n            try await Task.sleep(for: DirectoryWatcher.watchPeriod)\n\n            let newFile = childDir.appendingPathComponent(name)\n            FileManager.default.createFile(atPath: newFile.path, contents: nil)\n            try await Task.sleep(for: .milliseconds(500))\n\n            #expect(!createdURLs.urls.isEmpty, \"directory watcher failed to detect parent directory\")\n            #expect(createdURLs.urls.first!.lastPathComponent == name)\n        }\n    }\n\n    @Test func testWatchingRecreatedDirectory() async throws {\n        try await withTempDir { tempDir in\n            let dir = tempDir.appendingPathComponent(UUID().uuidString)\n            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)\n\n            let watcher = DirectoryWatcher(directoryURL: dir, log: nil)\n            let createdURLs = CreatedURLs()\n            let beforeDelete = \"beforeDelete\"\n            let afterDelete = \"afterDelete\"\n\n            await watcher.startWatching { [createdURLs] urls in\n                for url in urls\n                where url.lastPathComponent == beforeDelete || url.lastPathComponent == afterDelete {\n                    createdURLs.urls.append(url)\n                }\n            }\n\n            try await Task.sleep(for: .milliseconds(100))\n            let file1 = dir.appendingPathComponent(beforeDelete)\n            FileManager.default.createFile(atPath: file1.path, contents: nil)\n            try await Task.sleep(for: .milliseconds(100))\n\n            try FileManager.default.removeItem(at: dir)\n            try await Task.sleep(for: .milliseconds(100))\n            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)\n            try await Task.sleep(for: DirectoryWatcher.watchPeriod)\n\n            let file2 = dir.appendingPathComponent(afterDelete)\n            FileManager.default.createFile(atPath: file2.path, contents: nil)\n\n            try await Task.sleep(for: .milliseconds(500))\n\n            #expect(!createdURLs.urls.isEmpty, \"directory watcher failed to detect new file\")\n            #expect(Set(createdURLs.urls.map { $0.lastPathComponent }) == Set([beforeDelete, afterDelete]))\n        }\n\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/CommandLine+ExecutableTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerPlugin\n\nstruct CommandLineExecutableTest {\n    @Test\n    func testCLIPluginConfigLoad() async throws {\n        #expect(CommandLine.executablePathUrl.lastPathComponent == \"swiftpm-testing-helper\")\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/MockPluginFactory.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerPlugin\nimport Foundation\nimport Testing\n\nstruct MockPluginError: Error {}\n\nstruct MockPluginFactory: PluginFactory {\n    public static let throwSuffix = \"throw\"\n\n    private let plugins: [URL: Plugin]\n\n    private let throwingURL: URL\n\n    public init(tempURL: URL, plugins: [String: Plugin?]) throws {\n        let fm = FileManager.default\n        var prefixedPlugins: [URL: Plugin] = [:]\n        for (suffix, plugin) in plugins {\n            let url = tempURL.appending(path: suffix)\n            try fm.createDirectory(at: url, withIntermediateDirectories: true)\n            prefixedPlugins[url.standardizedFileURL] = plugin\n        }\n        self.plugins = prefixedPlugins\n        self.throwingURL = tempURL.appending(path: Self.throwSuffix).standardizedFileURL\n    }\n\n    public func create(installURL: URL) throws -> Plugin? {\n        let url = installURL.standardizedFileURL\n        guard url != self.throwingURL else {\n            throw MockPluginError()\n        }\n        return plugins[url]\n    }\n\n    public func create(parentURL: URL, name: String) throws -> Plugin? {\n        let url = parentURL.appendingPathComponent(name).standardizedFileURL\n        guard url != self.throwingURL else {\n            throw MockPluginError()\n        }\n        return plugins[url]\n    }\n\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/PluginConfigTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerPlugin\n\nstruct PluginConfigTest {\n    @Test\n    func testCLIPluginConfigLoad() async throws {\n        let tempURL = try FileManager.default.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let configURL = tempURL.appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n        let config = try #require(try PluginConfig(configURL: configURL))\n\n        #expect(config.isCLI)\n        #expect(config.abstract == \"Default network management service\")\n        #expect(config.author == \"Apple\")\n    }\n\n    @Test\n    func testServicePluginConfigLoad() async throws {\n        let tempURL = try FileManager.default.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let configURL = tempURL.appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\",\n                \"servicesConfig\" : {\n                    \"loadAtBoot\" : true,\n                    \"runAtLoad\" : true,\n                    \"defaultArguments\" : [\"start\"],\n                    \"services\" : [\n                        {\n                            \"type\" : \"network\",\n                            \"description\": \"foo\"\n                        }\n                    ]\n                }\n            }\n            \"\"\"\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n        let config = try #require(try PluginConfig(configURL: configURL))\n\n        #expect(!config.isCLI)\n        #expect(config.abstract == \"Default network management service\")\n        #expect(config.author == \"Apple\")\n\n        let servicesConfig = try #require(config.servicesConfig)\n        #expect(servicesConfig.loadAtBoot)\n        #expect(servicesConfig.runAtLoad)\n        #expect(servicesConfig.services.count == 1)\n        #expect(servicesConfig.services[0].type == .network)\n        #expect(servicesConfig.services[0].description == \"foo\")\n        #expect(servicesConfig.defaultArguments == [\"start\"])\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/PluginFactoryTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerPlugin\n\nstruct PluginFactoryTest {\n    @Test\n    func testDefaultFactory() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let name = tempURL.lastPathComponent\n\n        // write config to {name}/config.json\n        let configURL = tempURL.appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n\n        // write binary to {name}/bin/{name}\n        let binaryDirURL = tempURL.appending(path: \"bin\")\n        try fm.createDirectory(at: binaryDirURL, withIntermediateDirectories: true)\n        let binaryURL = binaryDirURL.appending(path: name)\n        try \"\".write(to: binaryURL, atomically: true, encoding: .utf8)\n\n        let factory = DefaultPluginFactory()\n        let plugin = try #require(try factory.create(installURL: tempURL))\n\n        #expect(plugin.name == name)\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.\\(name)\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.\\(name).1\")\n        #expect(plugin.getMachServices() == [])\n        #expect(plugin.getMachServices(instanceId: \"1\") == [])\n        #expect(plugin.getMachService(type: .runtime) == nil)\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == nil)\n        #expect(!plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.helpText(padding: 40).hasSuffix(\"Default network management service\"))\n    }\n\n    @Test\n    func testDefaultFactoryByName() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let name = tempURL.lastPathComponent\n\n        // write config to {name}/config.json\n        let configURL = tempURL.appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n\n        // write binary to {name}/bin/{name}\n        let binaryDirURL = tempURL.appending(path: \"bin\")\n        try fm.createDirectory(at: binaryDirURL, withIntermediateDirectories: true)\n        let binaryURL = binaryDirURL.appending(path: name)\n        try \"\".write(to: binaryURL, atomically: true, encoding: .utf8)\n\n        let factory = DefaultPluginFactory()\n        let plugin = try #require(try factory.create(parentURL: tempURL.deletingLastPathComponent(), name: name))\n\n        #expect(plugin.name == name)\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.\\(name)\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.\\(name).1\")\n        #expect(plugin.getMachServices() == [])\n        #expect(plugin.getMachServices(instanceId: \"1\") == [])\n        #expect(plugin.getMachService(type: .runtime) == nil)\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == nil)\n        #expect(!plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.helpText(padding: 40).hasSuffix(\"Default network management service\"))\n    }\n\n    @Test\n    func testDefaultFactoryMissingConfig() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let name = tempURL.lastPathComponent\n\n        // write binary to {name}/bin/{name}\n        let binaryDirURL = tempURL.appending(path: \"bin\")\n        try fm.createDirectory(at: binaryDirURL, withIntermediateDirectories: true)\n        let binaryURL = binaryDirURL.appending(path: name)\n        try \"\".write(to: binaryURL, atomically: true, encoding: .utf8)\n\n        let factory = DefaultPluginFactory()\n        let plugin = try factory.create(installURL: tempURL)\n        #expect(plugin == nil)\n    }\n\n    @Test\n    func testDefaultFactoryMissingBinary() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n\n        // write config to {name}/config.json\n        let configURL = tempURL.appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n\n        let factory = DefaultPluginFactory()\n        let plugin = try factory.create(installURL: tempURL)\n        #expect(plugin == nil)\n    }\n\n    @Test\n    func testAppBundleFactory() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let installURL = tempURL.appending(path: \"test.app\")\n        try fm.createDirectory(at: installURL, withIntermediateDirectories: true)\n        let name = String(installURL.lastPathComponent.dropLast(4))\n\n        // write config to {name}/config.json\n        let configURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"Resources\")\n            .appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try fm.createDirectory(at: configURL.deletingLastPathComponent(), withIntermediateDirectories: true)\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n\n        // write binary to {name}/bin/{name}\n        let binaryURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"MacOS\")\n            .appending(path: name)\n        try fm.createDirectory(at: binaryURL.deletingLastPathComponent(), withIntermediateDirectories: true)\n        try \"\".write(to: binaryURL, atomically: true, encoding: .utf8)\n\n        let factory = AppBundlePluginFactory()\n        let plugin = try #require(try factory.create(installURL: installURL))\n\n        #expect(plugin.name == name)\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.\\(name)\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.\\(name).1\")\n        #expect(plugin.getMachServices() == [])\n        #expect(plugin.getMachServices(instanceId: \"1\") == [])\n        #expect(plugin.getMachService(type: .runtime) == nil)\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == nil)\n        #expect(!plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.helpText(padding: 40).hasSuffix(\"Default network management service\"))\n    }\n\n    @Test\n    func testAppBundleFactoryByName() async throws {\n        let fm = FileManager.default\n        let tempURL = try fm.url(\n            for: .itemReplacementDirectory,\n            in: .userDomainMask,\n            appropriateFor: .temporaryDirectory,\n            create: true\n        )\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let installURL = tempURL.appending(path: \"test.app\")\n        try fm.createDirectory(at: installURL, withIntermediateDirectories: true)\n        let name = String(installURL.lastPathComponent.dropLast(4))\n\n        // write config to {name}/config.json\n        let configURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"Resources\")\n            .appending(path: \"config.json\")\n        let configJson = \"\"\"\n            {\n                \"abstract\" : \"Default network management service\",\n                \"author\": \"Apple\"\n            }\n            \"\"\"\n        try fm.createDirectory(at: configURL.deletingLastPathComponent(), withIntermediateDirectories: true)\n        try configJson.write(to: configURL, atomically: true, encoding: .utf8)\n\n        // write binary to {name}/bin/{name}\n        let binaryURL =\n            installURL\n            .appending(path: \"Contents\")\n            .appending(path: \"MacOS\")\n            .appending(path: name)\n        try fm.createDirectory(at: binaryURL.deletingLastPathComponent(), withIntermediateDirectories: true)\n        try \"\".write(to: binaryURL, atomically: true, encoding: .utf8)\n\n        let factory = AppBundlePluginFactory()\n        let plugin = try #require(try factory.create(parentURL: tempURL, name: name))\n\n        #expect(plugin.name == name)\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.\\(name)\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.\\(name).1\")\n        #expect(plugin.getMachServices() == [])\n        #expect(plugin.getMachServices(instanceId: \"1\") == [])\n        #expect(plugin.getMachService(type: .runtime) == nil)\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == nil)\n        #expect(!plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.helpText(padding: 40).hasSuffix(\"Default network management service\"))\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/PluginLoaderTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerPlugin\n\nstruct PluginLoaderTest {\n    @Test\n    func testFindAll() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n        let plugins = loader.findPlugins()\n\n        #expect(Set(plugins.map { $0.name }) == Set([\"cli\", \"service\"]))\n    }\n\n    @Test\n    func testFindAllSymlink() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n\n        // move the CLI plugin elsewhere and symlink it\n        let otherTempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: otherTempURL) }\n        try FileManager.default.createDirectory(at: otherTempURL, withIntermediateDirectories: true)\n        let srcURL = tempURL.appendingPathComponent(\"cli\")\n        let dstURL = otherTempURL.appendingPathComponent(\"cli\")\n        try FileManager.default.moveItem(\n            at: srcURL,\n            to: dstURL\n        )\n        try FileManager.default.createSymbolicLink(\n            at: srcURL,\n            withDestinationURL: dstURL\n        )\n\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n        let plugins = loader.findPlugins()\n\n        #expect(Set(plugins.map { $0.name }) == Set([\"cli\", \"service\"]))\n    }\n\n    @Test\n    func testFindByName() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n\n        #expect(loader.findPlugin(name: \"cli\")?.name == \"cli\")\n        #expect(loader.findPlugin(name: \"service\")?.name == \"service\")\n        #expect(loader.findPlugin(name: \"throw\") == nil)\n    }\n\n    @Test\n    func testFilterEnvironmentWithContainerPrefix() async throws {\n        let env = [\n            \"CONTAINER_FOO\": \"bar\",\n            \"CONTAINER_BAZ\": \"qux\",\n            \"OTHER_VAR\": \"value\",\n        ]\n        let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: [])\n\n        #expect(filtered == [\"CONTAINER_FOO\": \"bar\", \"CONTAINER_BAZ\": \"qux\"])\n    }\n\n    @Test\n    func testFilterEnvironmentWithProxyKeys() async throws {\n        let env = [\n            \"http_proxy\": \"http://proxy:8080\",\n            \"HTTP_PROXY\": \"http://proxy:8080\",\n            \"https_proxy\": \"https://proxy:8443\",\n            \"HTTPS_PROXY\": \"https://proxy:8443\",\n            \"no_proxy\": \"localhost,127.0.0.1\",\n            \"NO_PROXY\": \"localhost,127.0.0.1\",\n            \"OTHER_VAR\": \"value\",\n        ]\n        let filtered = PluginLoader.filterEnvironment(env: env)\n\n        #expect(\n            filtered == [\n                \"http_proxy\": \"http://proxy:8080\",\n                \"HTTP_PROXY\": \"http://proxy:8080\",\n                \"https_proxy\": \"https://proxy:8443\",\n                \"HTTPS_PROXY\": \"https://proxy:8443\",\n                \"no_proxy\": \"localhost,127.0.0.1\",\n                \"NO_PROXY\": \"localhost,127.0.0.1\",\n            ])\n    }\n\n    @Test\n    func testFilterEnvironmentWithBothContainerAndProxy() async throws {\n        let env = [\n            \"CONTAINER_FOO\": \"bar\",\n            \"http_proxy\": \"http://proxy:8080\",\n            \"OTHER_VAR\": \"value\",\n            \"ANOTHER_VAR\": \"value2\",\n        ]\n        let filtered = PluginLoader.filterEnvironment(env: env)\n\n        #expect(\n            filtered == [\n                \"CONTAINER_FOO\": \"bar\",\n                \"http_proxy\": \"http://proxy:8080\",\n            ])\n    }\n\n    @Test\n    func testFilterEnvironmentWithCustomAllowKeys() async throws {\n        let env = [\n            \"CONTAINER_FOO\": \"bar\",\n            \"CUSTOM_KEY\": \"custom_value\",\n            \"OTHER_VAR\": \"value\",\n        ]\n        let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: [\"CUSTOM_KEY\"])\n\n        #expect(\n            filtered == [\n                \"CONTAINER_FOO\": \"bar\",\n                \"CUSTOM_KEY\": \"custom_value\",\n            ])\n    }\n\n    @Test\n    func testFilterEnvironmentEmpty() async throws {\n        let filtered = PluginLoader.filterEnvironment(env: [:])\n\n        #expect(filtered.isEmpty)\n    }\n\n    @Test\n    func testFilterEnvironmentNoMatches() async throws {\n        let env = [\n            \"PATH\": \"/usr/bin\",\n            \"HOME\": \"/Users/test\",\n            \"USER\": \"testuser\",\n        ]\n        let filtered = PluginLoader.filterEnvironment(env: env, additionalAllowKeys: [])\n\n        #expect(filtered.isEmpty)\n    }\n\n    @Test\n    func testRegisterWithLaunchdDebugTrue() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n\n        let plugin = loader.findPlugin(name: \"service\")!\n        let stateRoot = tempURL.appendingPathComponent(\"test-state\")\n        try loader.registerWithLaunchd(plugin: plugin, pluginStateRoot: stateRoot, debug: true)\n\n        let plistURL = stateRoot.appendingPathComponent(\"service.plist\")\n        #expect(FileManager.default.fileExists(atPath: plistURL.path))\n\n        let plistData = try Data(contentsOf: plistURL)\n        let plist = try PropertyListSerialization.propertyList(from: plistData, format: nil) as! [String: Any]\n        let programArguments = plist[\"ProgramArguments\"] as! [String]\n\n        #expect(programArguments.contains(\"--debug\"))\n    }\n\n    @Test\n    func testRegisterWithLaunchdDebugFalse() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n\n        let plugin = loader.findPlugin(name: \"service\")!\n        let stateRoot = tempURL.appendingPathComponent(\"test-state\")\n        try loader.registerWithLaunchd(plugin: plugin, pluginStateRoot: stateRoot, debug: false)\n\n        let plistURL = stateRoot.appendingPathComponent(\"service.plist\")\n        #expect(FileManager.default.fileExists(atPath: plistURL.path))\n\n        let plistData = try Data(contentsOf: plistURL)\n        let plist = try PropertyListSerialization.propertyList(from: plistData, format: nil) as! [String: Any]\n        let programArguments = plist[\"ProgramArguments\"] as! [String]\n\n        #expect(!programArguments.contains(\"--debug\"))\n    }\n\n    @Test\n    func testRegisterWithLaunchdDebugDefault() async throws {\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)\n        defer { try? FileManager.default.removeItem(at: tempURL) }\n        let factory = try setupMock(tempURL: tempURL)\n        let loader = try PluginLoader(\n            appRoot: tempURL,\n            installRoot: URL(filePath: \"/usr/local/\"),\n            logRoot: nil,\n            pluginDirectories: [tempURL],\n            pluginFactories: [factory]\n        )\n\n        let plugin = loader.findPlugin(name: \"service\")!\n        let stateRoot = tempURL.appendingPathComponent(\"test-state\")\n        try loader.registerWithLaunchd(plugin: plugin, pluginStateRoot: stateRoot)\n\n        let plistURL = stateRoot.appendingPathComponent(\"service.plist\")\n        #expect(FileManager.default.fileExists(atPath: plistURL.path))\n\n        let plistData = try Data(contentsOf: plistURL)\n        let plist = try PropertyListSerialization.propertyList(from: plistData, format: nil) as! [String: Any]\n        let programArguments = plist[\"ProgramArguments\"] as! [String]\n\n        #expect(!programArguments.contains(\"--debug\"))\n    }\n\n    private func setupMock(tempURL: URL) throws -> MockPluginFactory {\n        let cliConfig = PluginConfig(abstract: \"cli\", author: \"CLI\", servicesConfig: nil)\n        let cliPlugin: Plugin = Plugin(binaryURL: URL(filePath: \"/bin/cli\"), config: cliConfig)\n        let serviceServicesConfig = PluginConfig.ServicesConfig(\n            loadAtBoot: false,\n            runAtLoad: false,\n            services: [PluginConfig.Service(type: .runtime, description: nil)],\n            defaultArguments: []\n        )\n        let serviceConfig = PluginConfig(abstract: \"service\", author: \"SERVICE\", servicesConfig: serviceServicesConfig)\n        let servicePlugin: Plugin = Plugin(binaryURL: URL(filePath: \"/bin/service\"), config: serviceConfig)\n        let mockPlugins = [\n            \"cli\": cliPlugin,\n            MockPluginFactory.throwSuffix: nil,\n            \"service\": servicePlugin,\n        ]\n\n        return try MockPluginFactory(tempURL: tempURL, plugins: mockPlugins)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerPluginTests/PluginTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerPlugin\n\nstruct PluginTest {\n    @Test\n    func testCLIPlugin() async throws {\n        let config = PluginConfig(\n            abstract: \"abstract\",\n            author: \"Ted Klondike\",\n            servicesConfig: nil\n        )\n\n        let binaryPath = \"/usr/local/libexec/container/plugin/bin/container-foo\"\n        let plugin = Plugin(\n            binaryURL: URL(filePath: binaryPath),\n            config: config\n        )\n\n        #expect(plugin.name == \"container-foo\")\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.container-foo\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.container-foo.1\")\n        #expect(plugin.getMachServices() == [])\n        #expect(plugin.getMachServices(instanceId: \"1\") == [])\n        #expect(plugin.getMachService(type: .runtime) == nil)\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == nil)\n        #expect(!plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.helpText(padding: 20) == \"  container-foo       abstract\")\n    }\n\n    @Test\n    func testServicePlugin() async throws {\n        let config = PluginConfig(\n            abstract: \"abstract\",\n            author: \"Ted Klondike\",\n            servicesConfig: .init(\n                loadAtBoot: false,\n                runAtLoad: false,\n                services: [\n                    .init(type: .runtime, description: \"runtime service\")\n                ],\n                defaultArguments: [\"foo-bar\"]\n            )\n        )\n\n        let binaryPath = \"/usr/local/libexec/container/plugin/linux-sandboxd/bin/linux-sandboxd\"\n        let plugin = Plugin(\n            binaryURL: URL(filePath: binaryPath),\n            config: config\n        )\n\n        #expect(plugin.name == \"linux-sandboxd\")\n        #expect(!plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.linux-sandboxd\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.linux-sandboxd.1\")\n        #expect(\n            plugin.getMachServices() == [\n                \"com.apple.container.runtime.linux-sandboxd\"\n            ])\n        #expect(\n            plugin.getMachServices(instanceId: \"1\") == [\n                \"com.apple.container.runtime.linux-sandboxd.1\"\n            ])\n        #expect(plugin.getMachService(type: .runtime) == \"com.apple.container.runtime.linux-sandboxd\")\n        #expect(plugin.getMachService(instanceId: \"1\", type: .runtime) == \"com.apple.container.runtime.linux-sandboxd.1\")\n        #expect(plugin.hasType(.runtime))\n        #expect(!plugin.hasType(.network))\n        #expect(plugin.config.servicesConfig!.defaultArguments == [\"foo-bar\"])\n    }\n\n    @Test\n    func testMultipleServicePlugin() async throws {\n        let config = PluginConfig(\n            abstract: \"abstract\",\n            author: \"Ted Klondike\",\n            servicesConfig: .init(\n                loadAtBoot: true,\n                runAtLoad: true,\n                services: [\n                    .init(type: .runtime, description: \"runtime service\"),\n                    .init(type: .network, description: \"network service\"),\n                ],\n                defaultArguments: [\"start\", \"with\", \"params\"]\n            )\n        )\n\n        let binaryPath = \"/usr/local/libexec/container/plugin/hydra/bin/hydra\"\n        let plugin = Plugin(\n            binaryURL: URL(filePath: binaryPath),\n            config: config\n        )\n\n        #expect(plugin.name == \"hydra\")\n        #expect(plugin.shouldBoot)\n        #expect(plugin.getLaunchdLabel() == \"com.apple.container.hydra\")\n        #expect(plugin.getLaunchdLabel(instanceId: \"1\") == \"com.apple.container.hydra.1\")\n        #expect(\n            plugin.getMachServices() == [\n                \"com.apple.container.runtime.hydra\",\n                \"com.apple.container.network.hydra\",\n            ])\n        #expect(\n            plugin.getMachServices(instanceId: \"1\") == [\n                \"com.apple.container.runtime.hydra.1\",\n                \"com.apple.container.network.hydra.1\",\n            ])\n        #expect(plugin.getMachService(type: .network) == \"com.apple.container.network.hydra\")\n        #expect(plugin.getMachService(instanceId: \"1\", type: .network) == \"com.apple.container.network.hydra.1\")\n        #expect(plugin.hasType(.runtime))\n        #expect(plugin.hasType(.network))\n        #expect(plugin.config.servicesConfig!.defaultArguments == [\"start\", \"with\", \"params\"])\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerResourceTests/ManagedResourceTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerResource\n\nstruct ManagedResourceTests {\n\n    // Mock implementation to test the randomId function\n    struct MockManagedResource: ManagedResource {\n        var id: String\n        var name: String\n        var creationDate: Date\n        var labels: [String: String]\n\n        static func nameValid(_ name: String) -> Bool {\n            true\n        }\n    }\n\n    @Test(\"randomId generates valid hex string SHA256 hash format\")\n    func testRandomIdFormat() {\n        let id = MockManagedResource.generateId()\n\n        // SHA256 hash is 64 hex characters (256 bits / 4 bits per hex char)\n        #expect(id.count == 64, \"randomId should generate 64 character string\")\n\n        // Should only contain valid hexadecimal characters (0-9, a-f)\n        let hexCharacterSet = CharacterSet(charactersIn: \"0123456789abcdef\")\n        let idCharacterSet = CharacterSet(charactersIn: id)\n        #expect(\n            hexCharacterSet.isSuperset(of: idCharacterSet),\n            \"randomId should only contain hexadecimal characters (0-9, a-f)\")\n    }\n\n    @Test(\"randomId generates unique values\")\n    func testRandomIdUniqueness() {\n        // Generate multiple IDs and verify they're all different\n        let ids = (0..<100).map { _ in MockManagedResource.generateId() }\n        let uniqueIds = Set(ids)\n\n        #expect(uniqueIds.count == 100, \"All generated IDs should be unique\")\n    }\n\n    @Test(\"randomId uses lowercase hexadecimal\")\n    func testRandomIdLowercase() {\n        let id = MockManagedResource.generateId()\n\n        // Should not contain uppercase letters\n        let uppercaseLetters = CharacterSet.uppercaseLetters\n        let idCharacterSet = CharacterSet(charactersIn: id)\n        #expect(\n            uppercaseLetters.isDisjoint(with: idCharacterSet),\n            \"randomId should use lowercase hexadecimal characters\")\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerResourceTests/NetworkConfigurationTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationError\nimport ContainerizationExtras\nimport Testing\n\n@testable import ContainerResource\n\nstruct NetworkConfigurationTest {\n    let defaultNetworkPluginInfo = NetworkPluginInfo(plugin: \"container-network-vmnet\")\n\n    @Test func testValidationOkDefaults() throws {\n        let id = \"foo\"\n        _ = try NetworkConfiguration(\n            id: id,\n            mode: .nat,\n            pluginInfo: defaultNetworkPluginInfo\n        )\n    }\n\n    @Test func testValidationGoodId() throws {\n        let ids = [\n            String(repeating: \"0\", count: 63),\n            \"0\",\n            \"0-_.1\",\n        ]\n        for id in ids {\n            let ipv4Subnet = try CIDRv4(\"192.168.64.1/24\")\n            let labels = [\n                \"foo\": \"bar\",\n                \"baz\": String(repeating: \"0\", count: 4096 - \"baz\".count - \"=\".count),\n            ]\n            _ = try NetworkConfiguration(\n                id: id,\n                mode: .nat,\n                ipv4Subnet: ipv4Subnet,\n                labels: labels,\n                pluginInfo: defaultNetworkPluginInfo\n            )\n        }\n    }\n\n    @Test func testValidationBadId() throws {\n        let ids = [\n            String(repeating: \"0\", count: 64),\n            \"-foo\",\n            \"foo_\",\n            \"Foo\",\n        ]\n        for id in ids {\n            let ipv4Subnet = try CIDRv4(\"192.168.64.1/24\")\n            let labels = [\n                \"foo\": \"bar\",\n                \"baz\": String(repeating: \"0\", count: 4096 - \"baz\".count - \"=\".count),\n            ]\n            #expect {\n                _ = try NetworkConfiguration(\n                    id: id,\n                    mode: .nat,\n                    ipv4Subnet: ipv4Subnet,\n                    labels: labels,\n                    pluginInfo: defaultNetworkPluginInfo\n                )\n            } throws: { error in\n                guard let err = error as? ContainerizationError else { return false }\n                #expect(err.code == .invalidArgument)\n                #expect(err.message.starts(with: \"invalid network ID\"))\n                return true\n            }\n        }\n    }\n\n    @Test func testValidationGoodLabels() throws {\n        let allLabels = [\n            [\"com.example.my-label\": \"bar\"],\n            [\"mycompany.com/my-label\": \"bar\"],\n            [\"foo\": String(repeating: \"0\", count: 4096 - \"foo\".count - \"=\".count)],\n            [String(repeating: \"0\", count: 128): \"\"],\n        ]\n        for labels in allLabels {\n            let id = \"foo\"\n            let ipv4Subnet = try CIDRv4(\"192.168.64.1/24\")\n            _ = try NetworkConfiguration(\n                id: id,\n                mode: .nat,\n                ipv4Subnet: ipv4Subnet,\n                labels: labels,\n                pluginInfo: defaultNetworkPluginInfo\n            )\n        }\n    }\n\n    @Test func testValidationBadLabels() throws {\n        let allLabels = [\n            [String(repeating: \"0\", count: 129): \"\"],\n            [\"foo\": String(repeating: \"0\", count: 4097 - \"foo\".count - \"=\".count)],\n            [\"com..example.my-label\": \"bar\"],\n            [\"mycompany.com//my-label\": \"bar\"],\n            [\"\": String(repeating: \"0\", count: 4096 - \"foo\".count - \"=\".count)],\n        ]\n        for labels in allLabels {\n            let id = \"foo\"\n            let ipv4Subnet = try CIDRv4(\"192.168.64.1/24\")\n            #expect {\n                _ = try NetworkConfiguration(\n                    id: id,\n                    mode: .nat,\n                    ipv4Subnet: ipv4Subnet,\n                    labels: labels,\n                    pluginInfo: defaultNetworkPluginInfo\n                )\n            } throws: { error in\n                guard let err = error as? ContainerizationError else { return false }\n                #expect(err.code == .invalidArgument)\n                #expect(err.message.starts(with: \"invalid label\"))\n                return true\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Tests/ContainerResourceTests/PublishPortTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\n@testable import ContainerResource\n\nstruct PublishPortTests {\n    @Test\n    func testPublishPortsNonOverlapping() throws {\n        let ports = [\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 100),\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),\n        ]\n        #expect(!ports.hasOverlaps())\n    }\n\n    @Test\n    func testPublishPortsOverlapping() throws {\n        let ports = [\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 101),\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),\n        ]\n        #expect(ports.hasOverlaps())\n    }\n\n    @Test\n    func testPublishPortsSamePortDifferentProtocols() throws {\n        let ports = [\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 8080, containerPort: 8080, proto: .tcp, count: 1),\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 8080, containerPort: 8080, proto: .udp, count: 1),\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 1024, containerPort: 1024, proto: .tcp, count: 1025),\n            PublishPort(hostAddress: try IPAddress(\"0.0.0.0\"), hostPort: 1024, containerPort: 1024, proto: .udp, count: 1025),\n        ]\n        #expect(!ports.hasOverlaps())\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerResourceTests/RegistryResourceTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerResource\n@testable import ContainerizationOS\n\nstruct RegistryResourceTests {\n\n    func createRegistryInfo(\n        hostname: String = \"docker.io\",\n        username: String = \"testuser\"\n    ) -> RegistryInfo {\n        RegistryInfo(\n            hostname: hostname,\n            username: username,\n            modifiedDate: Date(timeIntervalSince1970: 1_700_000_000),\n            createdDate: Date(timeIntervalSince1970: 1_690_000_000)\n        )\n    }\n\n    @Test(\"RegistryResource id and name are both hostname\")\n    func testRegistryResourceIdAndName() {\n        let hostname = \"ghcr.io\"\n        let registryInfo = createRegistryInfo(hostname: hostname, username: \"myuser\")\n        let resource = RegistryResource(from: registryInfo)\n\n        #expect(resource.id == hostname, \"id should be the hostname\")\n        #expect(resource.name == hostname, \"name should be the hostname\")\n        #expect(resource.id == resource.name, \"id and name should be identical\")\n    }\n\n    @Test(\"RegistryResource maps RegistryInfo correctly\")\n    func testRegistryResourceMapping() {\n        let hostname = \"registry.example.com:5000\"\n        let username = \"developer\"\n        let registryInfo = createRegistryInfo(hostname: hostname, username: username)\n\n        let resource = RegistryResource(from: registryInfo)\n\n        #expect(resource.id == hostname)\n        #expect(resource.name == hostname)\n        #expect(resource.username == username)\n        #expect(resource.creationDate == registryInfo.createdDate)\n        #expect(resource.modificationDate == registryInfo.modifiedDate)\n        #expect(resource.labels.isEmpty, \"default labels should be empty\")\n    }\n\n    @Test(\"RegistryResource implements ManagedResource\")\n    func testManagedResourceConformance() {\n        let registryInfo = createRegistryInfo()\n        let resource = RegistryResource(from: registryInfo)\n\n        // Test that it conforms to ManagedResource protocol\n        let managedResource: any ManagedResource = resource\n        #expect(managedResource.id == \"docker.io\")\n        #expect(managedResource.name == \"docker.io\")\n        #expect(managedResource.creationDate == registryInfo.createdDate)\n        #expect(managedResource.labels.isEmpty)\n    }\n\n    @Test(\"RegistryResource is Codable - JSON encoding\")\n    func testRegistryResourceJSONEncoding() throws {\n        let hostname = \"docker.io\"\n        let username = \"testuser\"\n        let registryInfo = createRegistryInfo(hostname: hostname, username: username)\n        let resource = RegistryResource(from: registryInfo)\n\n        // Encode to JSON\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = [.sortedKeys, .prettyPrinted]\n        let jsonData = try encoder.encode(resource)\n        let jsonString = String(data: jsonData, encoding: .utf8)!\n\n        // Verify JSON contains expected fields\n        #expect(jsonString.contains(\"\\\"id\\\"\"), \"JSON should contain id field\")\n        #expect(jsonString.contains(\"\\\"name\\\"\"), \"JSON should contain name field\")\n        #expect(jsonString.contains(\"\\\"username\\\"\"), \"JSON should contain username field\")\n        #expect(jsonString.contains(\"\\\"creationDate\\\"\"), \"JSON should contain creationDate field\")\n        #expect(jsonString.contains(\"\\\"modificationDate\\\"\"), \"JSON should contain modificationDate field\")\n        #expect(jsonString.contains(hostname), \"JSON should contain the hostname\")\n        #expect(jsonString.contains(username), \"JSON should contain the username\")\n    }\n\n    @Test(\"RegistryResource is Codable - round trip\")\n    func testRegistryResourceRoundTrip() throws {\n        let hostname = \"ghcr.io\"\n        let username = \"developer\"\n        let registryInfo = createRegistryInfo(hostname: hostname, username: username)\n        let original = RegistryResource(from: registryInfo)\n\n        // Encode\n        let encoder = JSONEncoder()\n        let jsonData = try encoder.encode(original)\n\n        // Decode\n        let decoder = JSONDecoder()\n        let decoded = try decoder.decode(RegistryResource.self, from: jsonData)\n\n        // Verify\n        #expect(decoded.id == original.id)\n        #expect(decoded.name == original.name)\n        #expect(decoded.username == original.username)\n        #expect(decoded.creationDate.timeIntervalSince1970 == original.creationDate.timeIntervalSince1970)\n        #expect(decoded.modificationDate.timeIntervalSince1970 == original.modificationDate.timeIntervalSince1970)\n        #expect(decoded.labels == original.labels)\n    }\n\n    @Test(\"RegistryResource nameValid validates hostnames\")\n    func testRegistryResourceNameValidation() {\n        // Valid hostnames\n        #expect(RegistryResource.nameValid(\"docker.io\"), \"docker.io should be valid\")\n        #expect(RegistryResource.nameValid(\"ghcr.io\"), \"ghcr.io should be valid\")\n        #expect(RegistryResource.nameValid(\"registry.example.com\"), \"registry.example.com should be valid\")\n        #expect(RegistryResource.nameValid(\"localhost:5000\"), \"localhost:5000 should be valid\")\n        #expect(RegistryResource.nameValid(\"registry.k8s.io\"), \"registry.k8s.io should be valid\")\n\n        // Invalid hostnames\n        #expect(!RegistryResource.nameValid(\"\"), \"empty string should be invalid\")\n        #expect(!RegistryResource.nameValid(\"-invalid.com\"), \"hostname starting with hyphen should be invalid\")\n        #expect(!RegistryResource.nameValid(\"invalid-.com\"), \"hostname ending with hyphen should be invalid\")\n    }\n\n    @Test(\"RegistryResource can have labels\")\n    func testRegistryResourceWithLabels() {\n        let hostname = \"docker.io\"\n        let username = \"testuser\"\n        let labels = [\n            \"environment\": \"production\",\n            ResourceLabelKeys.role: \"primary\",\n        ]\n\n        let resource = RegistryResource(\n            hostname: hostname,\n            username: username,\n            creationDate: Date(),\n            modificationDate: Date(),\n            labels: labels\n        )\n\n        #expect(resource.labels.count == 2)\n        #expect(resource.labels[\"environment\"] == \"production\")\n        #expect(resource.labels[ResourceLabelKeys.role] == \"primary\")\n    }\n\n    @Test(\"RegistryResource handles hostname with port\")\n    func testRegistryResourceWithPort() {\n        let hostname = \"localhost:5000\"\n        let registryInfo = createRegistryInfo(hostname: hostname, username: \"admin\")\n        let resource = RegistryResource(from: registryInfo)\n\n        #expect(resource.id == hostname)\n        #expect(resource.name == hostname)\n        #expect(RegistryResource.nameValid(hostname))\n    }\n\n    @Test(\"Multiple RegistryResources can be encoded as array\")\n    func testMultipleRegistryResourcesJSONEncoding() throws {\n        let registries = [\n            RegistryResource(from: createRegistryInfo(hostname: \"docker.io\", username: \"user1\")),\n            RegistryResource(from: createRegistryInfo(hostname: \"ghcr.io\", username: \"user2\")),\n            RegistryResource(from: createRegistryInfo(hostname: \"quay.io\", username: \"user3\")),\n        ]\n\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = [.sortedKeys, .prettyPrinted]\n        let jsonData = try encoder.encode(registries)\n        let jsonString = String(data: jsonData, encoding: .utf8)!\n\n        // Verify all hostnames are present\n        #expect(jsonString.contains(\"docker.io\"))\n        #expect(jsonString.contains(\"ghcr.io\"))\n        #expect(jsonString.contains(\"quay.io\"))\n\n        // Verify all usernames are present\n        #expect(jsonString.contains(\"user1\"))\n        #expect(jsonString.contains(\"user2\"))\n        #expect(jsonString.contains(\"user3\"))\n\n        // Print for manual verification\n        print(\"Encoded JSON:\")\n        print(jsonString)\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerResourceTests/VolumeValidationTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Foundation\nimport Testing\n\n@testable import ContainerResource\n\nstruct VolumeValidationTests {\n\n    @Test(\"Valid volume names should pass validation\")\n    func testValidVolumeNames() {\n        let validNames = [\n            \"a\",  // Single alphanumeric\n            \"1\",  // Single numeric\n            \"volume1\",  // Alphanumeric\n            \"my-volume\",  // With hyphen\n            \"my_volume\",  // With underscore\n            \"my.volume\",  // With period\n            \"volume-1.2_test\",  // Mixed valid characters\n            \"1volume\",  // Starting with number\n            \"Avolume\",  // Starting with uppercase\n            \"a\" + String(repeating: \"x\", count: 254),  // Max length (255)\n        ]\n\n        for name in validNames {\n            #expect(VolumeStorage.isValidVolumeName(name), \"'\\(name)' should be valid\")\n        }\n    }\n\n    @Test(\"Invalid volume names should fail validation\")\n    func testInvalidVolumeNames() {\n        let invalidNames = [\n            \"\",  // Empty string\n            \".volume\",  // Starting with period\n            \"_volume\",  // Starting with underscore\n            \"-volume\",  // Starting with hyphen\n            \"volume@\",  // Contains invalid character (@)\n            \"volume space\",  // Contains space\n            \"volume/path\",  // Contains slash\n            \"volume:tag\",  // Contains colon\n            \"volume#hash\",  // Contains hash\n            \"volume$\",  // Contains dollar sign\n            \"volume!\",  // Contains exclamation\n            \"volume%\",  // Contains percent\n            \"volume*\",  // Contains asterisk\n            \"volume+\",  // Contains plus\n            \"volume=\",  // Contains equals\n            \"volume[\",  // Contains bracket\n            \"volume]\",  // Contains bracket\n            \"volume{\",  // Contains brace\n            \"volume}\",  // Contains brace\n            \"volume|\",  // Contains pipe\n            \"volume\\\\\",  // Contains backslash\n            \"volume\\\"\",  // Contains quote\n            \"volume'\",  // Contains single quote\n            \"volume<\",  // Contains less than\n            \"volume>\",  // Contains greater than\n            \"volume?\",  // Contains question mark\n            \"volume,\",  // Contains comma\n            \"volume;\",  // Contains semicolon\n            \"a\" + String(repeating: \"x\", count: 255),  // Too long (256 chars)\n        ]\n\n        for name in invalidNames {\n            #expect(!VolumeStorage.isValidVolumeName(name), \"'\\(name)' should be invalid\")\n        }\n    }\n\n    @Test(\"Edge cases for volume name validation\")\n    func testVolumeNameEdgeCases() {\n        // Test exact boundary conditions\n        #expect(VolumeStorage.isValidVolumeName(\"a\"), \"Single character should be valid\")\n        #expect(!VolumeStorage.isValidVolumeName(\"\"), \"Empty string should be invalid\")\n\n        // Test maximum length boundary\n        let maxLengthName = String(repeating: \"a\", count: 255)\n        let tooLongName = String(repeating: \"a\", count: 256)\n        #expect(VolumeStorage.isValidVolumeName(maxLengthName), \"255 character name should be valid\")\n        #expect(!VolumeStorage.isValidVolumeName(tooLongName), \"256 character name should be invalid\")\n\n        // Test other edge cases\n        #expect(VolumeStorage.isValidVolumeName(\"0volume\"), \"Name starting with digit should be valid\")\n        #expect(VolumeStorage.isValidVolumeName(\"Volume\"), \"Name starting with uppercase should be valid\")\n        #expect(!VolumeStorage.isValidVolumeName(\".hidden\"), \"Name starting with period should be invalid\")\n        #expect(!VolumeStorage.isValidVolumeName(\"_private\"), \"Name starting with underscore should be invalid\")\n        #expect(!VolumeStorage.isValidVolumeName(\"-dash\"), \"Name starting with hyphen should be invalid\")\n    }\n\n    @Test(\"Unicode and special character handling\")\n    func testUnicodeCharacters() {\n        let unicodeNames = [\n            \"volume-ñ\",  // Non-ASCII letter\n            \"volume-中文\",  // Chinese characters\n            \"volume-🍎\",  // Emoji\n            \"volume-café\",  // Accented characters\n            \"αβγ\",  // Greek letters\n        ]\n\n        for name in unicodeNames {\n            #expect(!VolumeStorage.isValidVolumeName(name), \"Unicode name '\\(name)' should be invalid\")\n        }\n    }\n\n    @Test(\"Common Container volume name patterns\")\n    func testCommonVolumeNames() {\n        let commonPatterns = [\n            \"myapp-data\",  // Common app data volume\n            \"postgres_data\",  // Database volume\n            \"nginx.conf\",  // Config volume\n            \"logs-2024\",  // Log volume with year\n            \"cache_redis_v1.2\",  // Version-tagged cache\n            \"backup.daily\",  // Backup volume\n            \"shared-storage\",  // Shared volume\n        ]\n\n        for name in commonPatterns {\n            #expect(VolumeStorage.isValidVolumeName(name), \"Common volume name pattern '\\(name)' should be valid\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/ContainerSandboxServiceTests/RuntimeConfigurationTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\n// import ContainerAPIService\nimport ContainerResource\nimport ContainerSandboxServiceClient\nimport Containerization\n// import ContainerizationOCI\nimport Foundation\nimport Testing\n\n/// Unit tests for RuntimeConfiguration functionality.\n///\n/// These tests verify the runtime configuration serialization and deserialization,\n/// ensuring that configuration can be properly written, read, and used to create bundles.\nstruct RuntimeConfigurationTests {\n\n    /// Test that reading non-existent runtime configuration file throws\n    /// appropriate error\n    @Test\n    func testReadNonExistentRuntimeConfiguration() throws {\n        let tempDir = FileManager.default.temporaryDirectory\n        let nonExistentPath = tempDir.appendingPathComponent(\"non-existent-\\(UUID()).json\")\n\n        #expect(throws: Error.self) {\n            _ = try RuntimeConfiguration.readRuntimeConfiguration(from: nonExistentPath)\n        }\n    }\n\n    /// Test that runtime configuration reads and writes as expected\n    @Test\n    func testRuntimeConfigurationReadWrite() throws {\n        let tempDir = FileManager.default.temporaryDirectory\n        let bundlePath = tempDir.appendingPathComponent(\"test-bundle-\\(UUID())\")\n\n        defer {\n            try? FileManager.default.removeItem(at: bundlePath)\n        }\n\n        let initFs = Filesystem.virtiofs(\n            source: \"/path/to/initfs\",\n            destination: \"/\",\n            options: [\"ro\"]\n        )\n\n        let kernel = Kernel(\n            path: URL(fileURLWithPath: \"/path/to/kernel\"),\n            platform: .linuxArm\n        )\n\n        let runtimeConfig = RuntimeConfiguration(\n            path: bundlePath,\n            initialFilesystem: initFs,\n            kernel: kernel,\n            containerConfiguration: nil,\n            containerRootFilesystem: nil,\n            options: nil\n        )\n\n        try runtimeConfig.writeRuntimeConfiguration()\n\n        defer {\n            try? FileManager.default.removeItem(at: runtimeConfig.runtimeConfigurationPath)\n        }\n\n        let readRuntimeConfig = try RuntimeConfiguration.readRuntimeConfiguration(from: bundlePath)\n\n        #expect(\n            readRuntimeConfig.path == bundlePath,\n            \"Path should match\")\n        #expect(\n            readRuntimeConfig.kernel.path == kernel.path,\n            \"Kernel path should match\")\n        #expect(\n            readRuntimeConfig.initialFilesystem.source == initFs.source,\n            \"Initial filesystem source should match\")\n        #expect(\n            readRuntimeConfig.containerConfiguration == nil,\n            \"Container configuration should be nil\")\n        #expect(\n            readRuntimeConfig.containerRootFilesystem == nil,\n            \"Root filesystem should be nil\")\n        #expect(\n            readRuntimeConfig.options == nil,\n            \"Options should be nil\")\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/CompositeResolverTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Testing\n\n@testable import DNSServer\n\nstruct CompositeResolverTest {\n    @Test func testCompositeResolver() async throws {\n        let foo = FooHandler()\n        let bar = BarHandler()\n        let resolver = CompositeResolver(handlers: [foo, bar])\n\n        let fooQuery = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let fooResponse = try await resolver.answer(query: fooQuery)\n        #expect(.noError == fooResponse?.returnCode)\n        #expect(1 == fooResponse?.id)\n        #expect(1 == fooResponse?.answers.count)\n        let fooAnswer = fooResponse?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"1.2.3.4\") == fooAnswer?.ip)\n\n        let barQuery = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"bar.\", type: .host)\n            ])\n\n        let barResponse = try await resolver.answer(query: barQuery)\n        #expect(.noError == barResponse?.returnCode)\n        #expect(1 == barResponse?.id)\n        #expect(1 == barResponse?.answers.count)\n        let barAnswer = barResponse?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"5.6.7.8\") == barAnswer?.ip)\n\n        let otherQuery = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"other.\", type: .host)\n            ])\n\n        let otherResponse = try await resolver.answer(query: otherQuery)\n        #expect(nil == otherResponse)\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/HostTableResolverTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Testing\n\n@testable import DNSServer\n\nstruct HostTableResolverTest {\n    @Test func testEmptyQuestionsReturnsNil() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(id: UInt16(1), type: .query, questions: [])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(nil == response)\n    }\n\n    @Test func testUnsupportedQuestionType() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .mailExchange)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.notImplemented == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testAAAAQueryReturnsNoDataWhenARecordExists() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host6)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        // AAAA queries should return NODATA (noError with empty answers) when A record exists\n        // to avoid musl libc issues where NXDOMAIN causes complete DNS resolution failure\n        #expect(.noError == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testAAAAQueryReturnsNilWhenHostDoesNotExist() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"bar.\", type: .host6)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        // AAAA queries for non-existent hosts should return nil (which becomes NXDOMAIN)\n        #expect(nil == response)\n    }\n\n    @Test func testHostNotPresent() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"bar.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(nil == response)\n    }\n\n    @Test func testHostPresent() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.noError == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(1 == response?.answers.count)\n        let answer = response?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"1.2.3.4\") == answer?.ip)\n    }\n\n    @Test func testHostPresentUppercaseTable() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"FOO.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.noError == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(1 == response?.answers.count)\n        let answer = response?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"1.2.3.4\") == answer?.ip)\n    }\n\n    @Test func testHostPresentUppercaseQuestion() async throws {\n        let ip = try IPv4Address(\"1.2.3.4\")\n        let handler = try HostTableResolver(hosts4: [\"foo.\": ip])\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"FOO.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.noError == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"FOO.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(1 == response?.answers.count)\n        let answer = response?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"1.2.3.4\") == answer?.ip)\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/MockHandlers.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Testing\n\n@testable import DNSServer\n\nstruct FooHandler: DNSHandler {\n    public func answer(query: Message) async throws -> Message? {\n        if query.questions[0].name == \"foo.\" {\n            let ip = try IPv4Address(\"1.2.3.4\")\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .noError,\n                questions: query.questions,\n                answers: [HostRecord<IPv4Address>(name: query.questions[0].name, ttl: 0, ip: ip)]\n            )\n        }\n        return nil\n    }\n}\n\nstruct BarHandler: DNSHandler {\n    public func answer(query: Message) async throws -> Message? {\n        let question = query.questions[0]\n        if question.name == \"foo.\" || question.name == \"bar.\" {\n            let ip = try IPv4Address(\"5.6.7.8\")\n            return Message(\n                id: query.id,\n                type: .response,\n                returnCode: .noError,\n                questions: query.questions,\n                answers: [HostRecord<IPv4Address>(name: query.questions[0].name, ttl: 0, ip: ip)]\n            )\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/NxDomainResolverTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Testing\n\n@testable import DNSServer\n\nstruct NxDomainResolverTest {\n    @Test func testUnsupportedQuestionType() async throws {\n        let handler: NxDomainResolver = NxDomainResolver()\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host6)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.notImplemented == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testHostNotPresent() async throws {\n        let handler: NxDomainResolver = NxDomainResolver()\n\n        let query = Message(\n            id: UInt16(1),\n            type: .query,\n            questions: [\n                Question(name: \"bar.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.nonExistentDomain == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(0 == response?.answers.count)\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/RecordsTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Foundation\nimport Testing\n\n@testable import DNSServer\n\n@Suite(\"DNS Records Tests\")\nstruct RecordsTests {\n\n    // MARK: - DNSName Tests\n\n    @Suite(\"DNSName\")\n    struct DNSNameTests {\n        @Test(\"Create from string\")\n        func createFromString() throws {\n            let name = try DNSName(\"example.com\")\n            #expect(name.labels == [\"example\", \"com\"])\n        }\n\n        @Test(\"Create from string with trailing dot\")\n        func createFromStringTrailingDot() throws {\n            let name = try DNSName(\"example.com.\")\n            #expect(name.labels == [\"example\", \"com\"])\n        }\n\n        @Test(\"Description includes trailing dot\")\n        func descriptionTrailingDot() throws {\n            let name = try DNSName(\"example.com\")\n            #expect(name.description == \"example.com.\")\n        }\n\n        @Test(\"Root domain\")\n        func rootDomain() throws {\n            let name = try DNSName(\"\")\n            #expect(name.labels == [])\n            #expect(name.description == \".\")\n        }\n\n        @Test(\"Size calculation\")\n        func sizeCalculation() throws {\n            let name = try DNSName(\"example.com\")\n            // [7]example[3]com[0] = 1 + 7 + 1 + 3 + 1 = 13\n            #expect(name.size == 13)\n        }\n\n        @Test(\"Serialize and deserialize\")\n        func serializeDeserialize() throws {\n            let original = try DNSName(\"test.example.com\")\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try original.appendBuffer(&buffer, offset: 0)\n\n            var parsed = DNSName()\n            let readOffset = try parsed.bindBuffer(&buffer, offset: 0)\n\n            // [4]test[7]example[3]com[0] = 5+8+4+1 = 18\n            #expect(endOffset == 18)\n            #expect(readOffset == endOffset)\n            #expect(parsed.labels == original.labels)\n        }\n\n        @Test(\"Serialize subdomain\")\n        func serializeSubdomain() throws {\n            let name = try DNSName(\"a.b.c.d.example.com\")\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try name.appendBuffer(&buffer, offset: 0)\n\n            var parsed = DNSName()\n            let readOffset = try parsed.bindBuffer(&buffer, offset: 0)\n\n            // [1]a[1]b[1]c[1]d[7]example[3]com[0] = 2+2+2+2+8+4+1 = 21\n            #expect(endOffset == 21)\n            #expect(readOffset == endOffset)\n            #expect(parsed.labels == [\"a\", \"b\", \"c\", \"d\", \"example\", \"com\"])\n        }\n\n        @Test(\"Reject label too long\")\n        func rejectLabelTooLong() {\n            let longLabel = String(repeating: \"a\", count: 64)\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(longLabel + \".com\")\n            }\n        }\n\n        @Test(\"Reject embedded carriage return\")\n        func rejectEmbeddedCarriageReturn() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo\\r.com\")\n            }\n        }\n\n        @Test(\"Reject embedded newline\")\n        func rejectEmbeddedNewline() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo\\n.com\")\n            }\n        }\n\n        @Test(\"Reject embedded null byte\")\n        func rejectEmbeddedNullByte() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo\\0.com\")\n            }\n        }\n\n        @Test(\"Reject empty label\")\n        func rejectEmptyLabel() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo..com\")\n            }\n        }\n\n        @Test(\"Reject name too long\")\n        func rejectNameTooLong() {\n            // 9 labels * (1 + 30) bytes + 1 null = 280 bytes > 255\n            let label = String(repeating: \"a\", count: 30)\n            let name = Array(repeating: label, count: 9).joined(separator: \".\")\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(name)\n            }\n        }\n\n        @Test(\"Reject leading hyphen\")\n        func rejectLeadingHyphen() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"-foo.com\")\n            }\n        }\n\n        @Test(\"Reject trailing hyphen\")\n        func rejectTrailingHyphen() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo-.com\")\n            }\n        }\n\n        @Test(\"Reject leading underscore\")\n        func rejectLeadingUnderscore() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"_foo.com\")\n            }\n        }\n\n        @Test(\"Reject trailing underscore\")\n        func rejectTrailingUnderscore() {\n            #expect(throws: DNSBindError.self) {\n                _ = try DNSName(\"foo_.com\")\n            }\n        }\n\n        @Test(\"Accept service labels via init(labels:)\")\n        func acceptServiceLabels() throws {\n            let name = try DNSName(labels: [\"_dns-sd\", \"_udp\", \"local\"])\n            #expect(name.labels == [\"_dns-sd\", \"_udp\", \"local\"])\n        }\n\n        @Test(\"Lowercase labels on init\")\n        func lowercaseLabelsOnInit() throws {\n            let name = try DNSName(\"EXAMPLE.COM\")\n            #expect(name.labels == [\"example\", \"com\"])\n        }\n\n        @Test(\"Lowercase labels on init with trailing dot\")\n        func lowercaseLabelsOnInitTrailingDot() throws {\n            let name = try DNSName(\"Example.Com.\")\n            #expect(name.labels == [\"example\", \"com\"])\n        }\n\n        @Test(\"Lowercase labels from wire format\")\n        func lowercaseLabelsFromWire() throws {\n            // Wire-encode \"EXAMPLE.COM\" with uppercase bytes, then decode\n            let upper = try DNSName(labels: [\"EXAMPLE\", \"COM\"])\n            var buffer = [UInt8](repeating: 0, count: 64)\n            let endOffset = try upper.appendBuffer(&buffer, offset: 0)\n\n            var parsed = DNSName()\n            let readOffset = try parsed.bindBuffer(&buffer, offset: 0)\n\n            // [7]example[3]com[0] = 8+4+1 = 13\n            #expect(endOffset == 13)\n            #expect(readOffset == endOffset)\n            #expect(parsed.labels == [\"example\", \"com\"])\n        }\n\n        @Test(\"Follow valid compression pointer\")\n        func followCompressionPointer() throws {\n            // Build a buffer with two names:\n            //   offset  0: \"example.com.\" — [7]example[3]com[0] (13 bytes)\n            //   offset 13: \"test.\"        — [4]test 0xC0 0x00   ( 7 bytes)\n            // The pointer 0xC0 0x00 points back to offset 0.\n            var buffer: [UInt8] = [\n                0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,  // [7]example\n                0x03, 0x63, 0x6f, 0x6d,  // [3]com\n                0x00,  // null terminator\n                0x04, 0x74, 0x65, 0x73, 0x74,  // [4]test\n                0xC0, 0x00,  // pointer to offset 0\n            ]\n\n            var name = DNSName()\n            let readOffset = try name.bindBuffer(&buffer, offset: 13)\n\n            // Pointer bytes are at offset 18–19; returnOffset = 18 + 2 = 20\n            #expect(readOffset == 20)\n            #expect(name.labels == [\"test\", \"example\", \"com\"])\n        }\n\n        @Test(\"Reject forward compression pointer\")\n        func rejectForwardCompressionPointer() throws {\n            // Craft a packet with a forward compression pointer at offset 12 pointing to offset 20\n            // Header (12 bytes) + pointer bytes\n            var buffer = [UInt8](repeating: 0, count: 32)\n            // At offset 0: compression pointer to offset 20 (forward)\n            buffer[0] = 0xC0\n            buffer[1] = 0x14  // points to offset 20, which is > 0\n\n            #expect(throws: DNSBindError.self) {\n                var b = buffer\n                var name = DNSName()\n                _ = try name.bindBuffer(&b, offset: 0)\n            }\n        }\n\n        @Test(\"Reject self-referential compression pointer\")\n        func rejectSelfReferentialCompressionPointer() throws {\n            var buffer = [UInt8](repeating: 0, count: 16)\n            // At offset 0: compression pointer pointing back to offset 0 (same location)\n            buffer[0] = 0xC0\n            buffer[1] = 0x00  // points to offset 0 == current offset, not prior\n\n            #expect(throws: DNSBindError.self) {\n                var b = buffer\n                var name = DNSName()\n                _ = try name.bindBuffer(&b, offset: 0)\n            }\n        }\n\n        @Test(\"Reject compression pointer hop limit exceeded\")\n        func rejectCompressionPointerHopLimit() throws {\n            // Build a chain of backward pointers:\n            //   offset  0: [1]a[0]          — terminal name (3 bytes)\n            //   offset  3: 0xC0 0x00        — pointer → 0\n            //   offset  5: 0xC0 0x03        — pointer → 3\n            //   ...each entry points to the one before it...\n            //   offset 23: 0xC0 0x15        — pointer → 21\n            //   offset 25: 0xC0 0x17        — pointer → 23\n            //\n            // Reading from offset 25 follows 11 hops (25→23→21→...→3→0),\n            // which exceeds the limit of 10.\n            var buffer: [UInt8] = [\n                0x01, 0x61, 0x00,  // offset  0: [1]a[0]\n                0xC0, 0x00,  // offset  3: → 0\n                0xC0, 0x03,  // offset  5: → 3\n                0xC0, 0x05,  // offset  7: → 5\n                0xC0, 0x07,  // offset  9: → 7\n                0xC0, 0x09,  // offset 11: → 9\n                0xC0, 0x0B,  // offset 13: → 11\n                0xC0, 0x0D,  // offset 15: → 13\n                0xC0, 0x0F,  // offset 17: → 15\n                0xC0, 0x11,  // offset 19: → 17\n                0xC0, 0x13,  // offset 21: → 19\n                0xC0, 0x15,  // offset 23: → 21\n                0xC0, 0x17,  // offset 25: → 23\n            ]\n\n            #expect(throws: DNSBindError.self) {\n                var name = DNSName()\n                _ = try name.bindBuffer(&buffer, offset: 25)\n            }\n        }\n    }\n\n    // MARK: - Question Tests\n\n    @Suite(\"Question\")\n    struct QuestionTests {\n        @Test(\"Create question\")\n        func create() {\n            let q = Question(name: \"example.com.\", type: .host, recordClass: .internet)\n            #expect(q.name == \"example.com.\")\n            #expect(q.type == .host)\n            #expect(q.recordClass == .internet)\n        }\n\n        @Test(\"Serialize and deserialize A record question\")\n        func serializeDeserializeA() throws {\n            let original = Question(name: \"example.com.\", type: .host, recordClass: .internet)\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try original.appendBuffer(&buffer, offset: 0)\n\n            var parsed = Question(name: \"\")\n            let readOffset = try parsed.bindBuffer(&buffer, offset: 0)\n\n            // name([7]example[3]com[0]=13) + type(2) + class(2) = 17\n            #expect(endOffset == 17)\n            #expect(readOffset == endOffset)\n            #expect(parsed.type == .host)\n            #expect(parsed.recordClass == .internet)\n        }\n\n        @Test(\"Serialize and deserialize AAAA record question\")\n        func serializeDeserializeAAAA() throws {\n            let original = Question(name: \"example.com.\", type: .host6, recordClass: .internet)\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try original.appendBuffer(&buffer, offset: 0)\n\n            var parsed = Question(name: \"\")\n            let readOffset = try parsed.bindBuffer(&buffer, offset: 0)\n\n            // name([7]example[3]com[0]=13) + type(2) + class(2) = 17\n            #expect(endOffset == 17)\n            #expect(readOffset == endOffset)\n            #expect(parsed.type == .host6)\n        }\n    }\n\n    // MARK: - HostRecord Tests\n\n    @Suite(\"HostRecord\")\n    struct HostRecordTests {\n        @Test(\"Create A record\")\n        func createARecord() throws {\n            let ip = try IPv4Address(\"192.168.1.1\")\n            let record = HostRecord(name: \"example.com.\", ttl: 300, ip: ip)\n\n            #expect(record.name == \"example.com.\")\n            #expect(record.type == .host)\n            #expect(record.ttl == 300)\n            #expect(record.ip == ip)\n        }\n\n        @Test(\"Create AAAA record\")\n        func createAAAARecord() throws {\n            let ip = try IPv6Address(\"::1\")\n            let record = HostRecord(name: \"example.com.\", ttl: 600, ip: ip)\n\n            #expect(record.name == \"example.com.\")\n            #expect(record.type == .host6)\n            #expect(record.ttl == 600)\n        }\n\n        @Test(\"Serialize A record\")\n        func serializeARecord() throws {\n            let ip = try IPv4Address(\"10.0.0.1\")\n            let record = HostRecord(name: \"test.com.\", ttl: 300, ip: ip)\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try record.appendBuffer(&buffer, offset: 0)\n\n            // name([4]test[3]com[0]=10) + type(2) + class(2) + ttl(4) + rdlen(2) + rdata(4) = 24\n            #expect(endOffset == 24)\n\n            // Verify IP bytes at the end\n            #expect(buffer[endOffset - 4] == 10)\n            #expect(buffer[endOffset - 3] == 0)\n            #expect(buffer[endOffset - 2] == 0)\n            #expect(buffer[endOffset - 1] == 1)\n        }\n\n        @Test(\"Serialize AAAA record\")\n        func serializeAAAARecord() throws {\n            let ip = try IPv6Address(\"::1\")\n            let record = HostRecord(name: \"test.com.\", ttl: 300, ip: ip)\n            var buffer = [UInt8](repeating: 0, count: 64)\n\n            let endOffset = try record.appendBuffer(&buffer, offset: 0)\n\n            // name([4]test[3]com[0]=10) + type(2) + class(2) + ttl(4) + rdlen(2) + rdata(16) = 36\n            #expect(endOffset == 36)\n            #expect(buffer[endOffset - 1] == 1)\n        }\n    }\n\n    // MARK: - Message Tests\n\n    @Suite(\"Message\")\n    struct MessageTests {\n        @Test(\"Create query message\")\n        func createQuery() {\n            let msg = Message(\n                id: 0x1234,\n                type: .query,\n                questions: [Question(name: \"example.com.\", type: .host)]\n            )\n\n            #expect(msg.id == 0x1234)\n            #expect(msg.type == .query)\n            #expect(msg.questions.count == 1)\n        }\n\n        @Test(\"Create response message\")\n        func createResponse() throws {\n            let ip = try IPv4Address(\"192.168.1.1\")\n            let msg = Message(\n                id: 0x1234,\n                type: .response,\n                returnCode: .noError,\n                questions: [Question(name: \"example.com.\", type: .host)],\n                answers: [HostRecord(name: \"example.com.\", ttl: 300, ip: ip)]\n            )\n\n            #expect(msg.type == .response)\n            #expect(msg.returnCode == .noError)\n            #expect(msg.answers.count == 1)\n        }\n\n        @Test(\"Serialize and deserialize query\")\n        func serializeDeserializeQuery() throws {\n            let original = Message(\n                id: 0xABCD,\n                type: .query,\n                recursionDesired: true,\n                questions: [Question(name: \"example.com.\", type: .host)]\n            )\n\n            let data = try original.serialize()\n            let parsed = try Message(deserialize: data)\n\n            #expect(parsed.id == 0xABCD)\n            #expect(parsed.type == .query)\n            #expect(parsed.recursionDesired == true)\n            #expect(parsed.questions.count == 1)\n            #expect(parsed.questions[0].type == .host)\n        }\n\n        @Test(\"Serialize response with answer\")\n        func serializeResponse() throws {\n            let ip = try IPv4Address(\"10.0.0.1\")\n            let msg = Message(\n                id: 0x1234,\n                type: .response,\n                authoritativeAnswer: true,\n                returnCode: .noError,\n                questions: [Question(name: \"test.com.\", type: .host)],\n                answers: [HostRecord(name: \"test.com.\", ttl: 300, ip: ip)]\n            )\n\n            let data = try msg.serialize()\n\n            // Verify we can at least parse the header back\n            let parsed = try Message(deserialize: data)\n            #expect(parsed.id == 0x1234)\n            #expect(parsed.type == .response)\n            #expect(parsed.authoritativeAnswer == true)\n            #expect(parsed.returnCode == .noError)\n        }\n\n        @Test(\"Serialize NXDOMAIN response\")\n        func serializeNxdomain() throws {\n            let msg = Message(\n                id: 0x1234,\n                type: .response,\n                returnCode: .nonExistentDomain,\n                questions: [Question(name: \"unknown.com.\", type: .host)],\n                answers: []\n            )\n\n            let data = try msg.serialize()\n            let parsed = try Message(deserialize: data)\n\n            #expect(parsed.returnCode == .nonExistentDomain)\n            #expect(parsed.answers.count == 0)\n        }\n\n        @Test(\"Serialize NODATA response (empty answers with noError)\")\n        func serializeNodata() throws {\n            let msg = Message(\n                id: 0x1234,\n                type: .response,\n                returnCode: .noError,\n                questions: [Question(name: \"example.com.\", type: .host6)],\n                answers: []\n            )\n\n            let data = try msg.serialize()\n            let parsed = try Message(deserialize: data)\n\n            #expect(parsed.returnCode == .noError)\n            #expect(parsed.answers.count == 0)\n        }\n\n        @Test(\"Multiple questions\")\n        func multipleQuestions() throws {\n            let msg = Message(\n                id: 0x1234,\n                type: .query,\n                questions: [\n                    Question(name: \"a.com.\", type: .host),\n                    Question(name: \"b.com.\", type: .host6),\n                ]\n            )\n\n            let data = try msg.serialize()\n            let parsed = try Message(deserialize: data)\n\n            #expect(parsed.questions.count == 2)\n            #expect(parsed.questions[0].type == .host)\n            #expect(parsed.questions[1].type == .host6)\n        }\n\n        @Test(\"Reject too many questions\")\n        func rejectTooManyQuestions() {\n            let questions = Array(repeating: Question(name: \"a.com.\", type: .host), count: Int(UInt16.max) + 1)\n            let msg = Message(id: 0, type: .query, questions: questions)\n            #expect(throws: DNSBindError.self) {\n                _ = try msg.serialize()\n            }\n        }\n\n        @Test(\"Reject too many answers\")\n        func rejectTooManyAnswers() throws {\n            let ip = try IPv4Address(\"1.2.3.4\")\n            let answers = Array(repeating: HostRecord(name: \"a.com.\", ttl: 0, ip: ip), count: Int(UInt16.max) + 1)\n            let msg = Message(id: 0, type: .response, answers: answers)\n            #expect(throws: DNSBindError.self) {\n                _ = try msg.serialize()\n            }\n        }\n    }\n\n    // MARK: - Wire Format Tests\n\n    @Suite(\"Wire Format\")\n    struct WireFormatTests {\n        @Test(\"Parse real DNS query bytes\")\n        func parseRealQuery() throws {\n            // A minimal DNS query for \"example.com\" A record\n            // Header: ID=0x1234, QR=0, OPCODE=0, RD=1, QDCOUNT=1\n            let queryBytes: [UInt8] = [\n                0x12, 0x34,  // ID\n                0x01, 0x00,  // Flags: RD=1\n                0x00, 0x01,  // QDCOUNT=1\n                0x00, 0x00,  // ANCOUNT=0\n                0x00, 0x00,  // NSCOUNT=0\n                0x00, 0x00,  // ARCOUNT=0\n                // Question: example.com A IN\n                0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,  // \"example\"\n                0x03, 0x63, 0x6f, 0x6d,  // \"com\"\n                0x00,  // null terminator\n                0x00, 0x01,  // QTYPE=A\n                0x00, 0x01,  // QCLASS=IN\n            ]\n\n            let msg = try Message(deserialize: Data(queryBytes))\n\n            #expect(msg.id == 0x1234)\n            #expect(msg.type == .query)\n            #expect(msg.recursionDesired == true)\n            #expect(msg.questions.count == 1)\n            #expect(msg.questions[0].type == .host)\n            #expect(msg.questions[0].recordClass == .internet)\n        }\n\n        @Test(\"Roundtrip preserves data\")\n        func roundtrip() throws {\n            let ip = try IPv4Address(\"1.2.3.4\")\n            let original = Message(\n                id: 0xBEEF,\n                type: .response,\n                operationCode: .query,\n                authoritativeAnswer: true,\n                truncation: false,\n                recursionDesired: true,\n                recursionAvailable: true,\n                returnCode: .noError,\n                questions: [Question(name: \"test.example.com.\", type: .host)],\n                answers: [HostRecord(name: \"test.example.com.\", ttl: 3600, ip: ip)]\n            )\n\n            let data = try original.serialize()\n            let parsed = try Message(deserialize: data)\n\n            #expect(parsed.id == original.id)\n            #expect(parsed.type == original.type)\n            #expect(parsed.authoritativeAnswer == original.authoritativeAnswer)\n            #expect(parsed.truncation == original.truncation)\n            #expect(parsed.recursionDesired == original.recursionDesired)\n            #expect(parsed.recursionAvailable == original.recursionAvailable)\n            #expect(parsed.returnCode == original.returnCode)\n            #expect(parsed.questions.count == original.questions.count)\n        }\n\n        @Test(\"Reject unknown opcode\")\n        func rejectUnknownOpcode() {\n            // Opcode occupies bits 14–11 of the flags word. Value 3 is reserved.\n            // Flags: 0x18 0x00 = QR=0, OPCODE=3, all other bits clear.\n            let bytes: [UInt8] = [\n                0x00, 0x01,  // ID\n                0x18, 0x00,  // Flags: OPCODE=3 (reserved)\n                0x00, 0x00,  // QDCOUNT=0\n                0x00, 0x00,  // ANCOUNT=0\n                0x00, 0x00,  // NSCOUNT=0\n                0x00, 0x00,  // ARCOUNT=0\n            ]\n            #expect(throws: DNSBindError.self) {\n                _ = try Message(deserialize: Data(bytes))\n            }\n        }\n\n        @Test(\"Reject unknown RCODE\")\n        func rejectUnknownRcode() {\n            // RCODE occupies bits 3–0 of the flags word. Value 12 is reserved.\n            // Flags: 0x00 0x0C = QR=0, OPCODE=0, RCODE=12.\n            let bytes: [UInt8] = [\n                0x00, 0x01,  // ID\n                0x00, 0x0C,  // Flags: RCODE=12 (reserved)\n                0x00, 0x00,  // QDCOUNT=0\n                0x00, 0x00,  // ANCOUNT=0\n                0x00, 0x00,  // NSCOUNT=0\n                0x00, 0x00,  // ARCOUNT=0\n            ]\n            #expect(throws: DNSBindError.self) {\n                _ = try Message(deserialize: Data(bytes))\n            }\n        }\n\n        @Test(\"Reject unknown query type\")\n        func rejectUnknownQueryType() {\n            // Type 54 is unassigned in the IANA DNS parameters registry.\n            let bytes: [UInt8] = [\n                0x00, 0x01,  // ID\n                0x00, 0x00,  // Flags: standard query\n                0x00, 0x01,  // QDCOUNT=1\n                0x00, 0x00,  // ANCOUNT=0\n                0x00, 0x00,  // NSCOUNT=0\n                0x00, 0x00,  // ARCOUNT=0\n                0x01, 0x61, 0x00,  // name: [1]a[0]\n                0x00, 0x36,  // QTYPE=54 (unassigned)\n                0x00, 0x01,  // QCLASS=IN\n            ]\n            #expect(throws: DNSBindError.self) {\n                _ = try Message(deserialize: Data(bytes))\n            }\n        }\n\n        @Test(\"Reject unknown record class\")\n        func rejectUnknownRecordClass() {\n            // Class 2 is unassigned in the IANA DNS parameters registry.\n            let bytes: [UInt8] = [\n                0x00, 0x01,  // ID\n                0x00, 0x00,  // Flags: standard query\n                0x00, 0x01,  // QDCOUNT=1\n                0x00, 0x00,  // ANCOUNT=0\n                0x00, 0x00,  // NSCOUNT=0\n                0x00, 0x00,  // ARCOUNT=0\n                0x01, 0x61, 0x00,  // name: [1]a[0]\n                0x00, 0x01,  // QTYPE=A\n                0x00, 0x02,  // QCLASS=2 (unassigned)\n            ]\n            #expect(throws: DNSBindError.self) {\n                _ = try Message(deserialize: Data(bytes))\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/DNSServerTests/StandardQueryValidatorTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport ContainerizationExtras\nimport Testing\n\n@testable import DNSServer\n\nstruct StandardQueryValidatorTest {\n    @Test func testRejectResponseAsQuery() async throws {\n        let fooHandler = FooHandler()\n        let handler = StandardQueryValidator(handler: fooHandler)\n\n        let query = Message(\n            id: UInt16(1),\n            type: .response,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.formatError == response?.returnCode)\n        #expect(1 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testRejectNonQueryOperation() async throws {\n        let fooHandler = FooHandler()\n        let handler = StandardQueryValidator(handler: fooHandler)\n\n        let query = Message(\n            id: UInt16(2),\n            type: .query,\n            operationCode: .notify,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.notImplemented == response?.returnCode)\n        #expect(2 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testRejectNoQuestions() async throws {\n        let fooHandler = FooHandler()\n        let handler = StandardQueryValidator(handler: fooHandler)\n\n        let query = Message(id: UInt16(3), type: .query, questions: [])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.formatError == response?.returnCode)\n        #expect(3 == response?.id)\n        #expect(.response == response?.type)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testRejectMultipleQuestions() async throws {\n        let fooHandler = FooHandler()\n        let handler = StandardQueryValidator(handler: fooHandler)\n\n        let query = Message(\n            id: UInt16(2),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host),\n                Question(name: \"bar.\", type: .host),\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.formatError == response?.returnCode)\n        #expect(2 == response?.id)\n        #expect(.response == response?.type)\n        #expect(2 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(\"bar.\" == response?.questions[1].name)\n        #expect(.host == response?.questions[1].type)\n        #expect(0 == response?.answers.count)\n    }\n\n    @Test func testSuccessfulValidation() async throws {\n        let fooHandler = FooHandler()\n        let handler = StandardQueryValidator(handler: fooHandler)\n\n        let query = Message(\n            id: UInt16(2),\n            type: .query,\n            questions: [\n                Question(name: \"foo.\", type: .host)\n            ])\n\n        let response = try await handler.answer(query: query)\n\n        #expect(.noError == response?.returnCode)\n        #expect(2 == response?.id)\n        #expect(.response == response?.type)\n        #expect(1 == response?.questions.count)\n        #expect(\"foo.\" == response?.questions[0].name)\n        #expect(.host == response?.questions[0].type)\n        #expect(1 == response?.answers.count)\n        let answer = response?.answers[0] as? HostRecord<IPv4Address>\n        #expect(try IPv4Address(\"1.2.3.4\") == answer?.ip)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/ConnectHandlerRaceTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\nimport Testing\n\n@testable import SocketForwarder\n\nstruct ConnectHandlerRaceTest {\n    let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)\n\n    @Test\n    func testRapidConnectDisconnect() async throws {\n        let requestCount = 500\n\n        let serverAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let server = TCPEchoServer(serverAddress: serverAddress, eventLoopGroup: eventLoopGroup)\n        let serverChannel = try await server.run().get()\n        let actualServerAddress = try #require(serverChannel.localAddress)\n\n        let proxyAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let forwarder = try TCPForwarder(\n            proxyAddress: proxyAddress,\n            serverAddress: actualServerAddress,\n            eventLoopGroup: eventLoopGroup\n        )\n        let forwarderResult = try await forwarder.run().get()\n        let actualProxyAddress = try #require(forwarderResult.proxyAddress)\n\n        try await withThrowingTaskGroup(of: Void.self) { group in\n            for _ in 0..<requestCount {\n                group.addTask {\n                    do {\n                        let channel = try await ClientBootstrap(group: self.eventLoopGroup)\n                            .connect(to: actualProxyAddress)\n                            .get()\n\n                        try await channel.close()\n                    } catch {\n                        // Going to ignore connection errors as we are intentionally stressing it\n                    }\n                }\n            }\n            try await group.waitForAll()\n        }\n\n        serverChannel.eventLoop.execute { _ = serverChannel.close() }\n        try await serverChannel.closeFuture.get()\n\n        forwarderResult.close()\n        try await forwarderResult.wait()\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/LRUCacheTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Testing\n\n@testable import SocketForwarder\n\nstruct LRUCacheTest {\n    @Test\n    func testLRUCache() throws {\n        let cache = LRUCache<String, String>(size: 3)\n        #expect(cache.count == 0)\n\n        #expect(cache.put(key: \"foo\", value: \"1\") == nil)\n        #expect(cache.count == 1)\n\n        #expect(cache.put(key: \"bar\", value: \"2\") == nil)\n        #expect(cache.count == 2)\n\n        #expect(cache.put(key: \"baz\", value: \"3\") == nil)\n        #expect(cache.count == 3)\n\n        let replaced = try #require(cache.put(key: \"bar\", value: \"4\"))\n        #expect(replaced == (\"bar\", \"2\"))\n        #expect(cache.count == 3)\n\n        let firstEvicted = try #require(cache.put(key: \"qux\", value: \"5\"))\n        #expect(firstEvicted == (\"foo\", \"1\"))\n        #expect(cache.count == 3)\n\n        let secondEvicted = try #require(cache.put(key: \"quux\", value: \"6\"))\n        #expect(secondEvicted == (\"baz\", \"3\"))\n        #expect(cache.count == 3)\n\n        #expect(cache.get(\"foo\") == nil)\n        #expect(cache.get(\"bar\") == \"4\")\n        #expect(cache.get(\"baz\") == nil)\n        #expect(cache.get(\"qux\") == \"5\")\n        #expect(cache.get(\"quux\") == \"6\")\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/TCPEchoHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\nfinal class TCPEchoHandler: ChannelInboundHandler {\n\n    typealias InboundIn = ByteBuffer\n    typealias OutboundOut = ByteBuffer\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        context.writeAndFlush(data, promise: nil)\n    }\n\n    func errorCaught(context: ChannelHandlerContext, error: Error) {\n        context.close(promise: nil)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/TCPEchoServer.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\nstruct TCPEchoServer: Sendable {\n    private let serverAddress: SocketAddress\n\n    private let eventLoopGroup: MultiThreadedEventLoopGroup\n\n    public init(serverAddress: SocketAddress, eventLoopGroup: MultiThreadedEventLoopGroup) {\n        self.serverAddress = serverAddress\n        self.eventLoopGroup = eventLoopGroup\n    }\n\n    public func run() throws -> EventLoopFuture<any Channel> {\n        let bootstrap = ServerBootstrap(group: self.eventLoopGroup)\n            .serverChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)\n            .childChannelOption(ChannelOptions.socket(.init(SOL_SOCKET), .init(SO_REUSEADDR)), value: 1)\n            .childChannelInitializer { channel in\n                channel.eventLoop.makeCompletedFuture {\n                    try channel.pipeline.syncOperations.addHandler(\n                        BackPressureHandler()\n                    )\n                    try channel.pipeline.syncOperations.addHandler(\n                        TCPEchoHandler()\n                    )\n                }\n            }\n\n        return bootstrap.bind(to: self.serverAddress)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/TCPForwarderTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\nimport Testing\n\n@testable import SocketForwarder\n\nstruct TCPForwarderTest {\n    let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)\n\n    @Test\n    func testTCPForwarder() async throws {\n        let requestCount = 100\n        var responses: [String] = []\n\n        // bring up server on ephemeral port and get address\n        let serverAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let server = TCPEchoServer(serverAddress: serverAddress, eventLoopGroup: eventLoopGroup)\n        let serverChannel = try await server.run().get()\n        let actualServerAddress = try #require(serverChannel.localAddress)\n\n        // bring up proxy on ephemeral port and get address\n        let proxyAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let forwarder = try TCPForwarder(\n            proxyAddress: proxyAddress,\n            serverAddress: actualServerAddress,\n            eventLoopGroup: eventLoopGroup\n        )\n        let forwarderResult = try await forwarder.run().get()\n        let actualProxyAddress = try #require(forwarderResult.proxyAddress)\n\n        // send a bunch of messages and collect them\n        try await withThrowingTaskGroup(of: String.self) { group in\n            for i in 0..<requestCount {\n                group.addTask {\n                    var response: String = \"\\(i): error\"\n                    let channel = try await ClientBootstrap(group: self.eventLoopGroup)\n                        .connectTimeout(.seconds(2))\n                        .connect(to: actualProxyAddress) { channel in\n                            channel.eventLoop.makeCompletedFuture {\n                                // We are using two simple handlers here to frame our messages with \"\\n\"\n                                try channel.pipeline.syncOperations.addHandler(ByteToMessageHandler(NewlineDelimiterCoder()))\n                                try channel.pipeline.syncOperations.addHandler(MessageToByteHandler(NewlineDelimiterCoder()))\n\n                                return try NIOAsyncChannel(\n                                    wrappingChannelSynchronously: channel,\n                                    configuration: NIOAsyncChannel.Configuration(\n                                        inboundType: String.self,\n                                        outboundType: String.self\n                                    )\n                                )\n                            }\n                        }\n\n                    try await channel.executeThenClose { inbound, outbound in\n                        try await outbound.write(\"\\(i): success-tcp\")\n                        for try await inboundData in inbound {\n                            response = \"\\(inboundData)\"\n                            break\n                        }\n                    }\n\n                    return response\n                }\n            }\n\n            for try await response in group {\n                responses.append(response)\n            }\n        }\n\n        // close everything down\n        print(\"testTCPForwarder: close server\")\n        serverChannel.eventLoop.execute { _ = serverChannel.close() }\n        try await serverChannel.closeFuture.get()\n\n        print(\"testTCPForwarder: close forwarder\")\n        forwarderResult.close()\n        try await forwarderResult.wait()\n\n        // verify all expected messages\n        print(\"testTCPForwarder: validate responses\")\n        let sortedResponses = try responses.sorted { (a, b) in\n            let aParts = a.split(separator: \":\")\n            let bParts = b.split(separator: \":\")\n            #expect(aParts.count > 1)\n            #expect(bParts.count > 1)\n            let aIndex = try #require(Int(aParts[0]))\n            let bIndex = try #require(Int(bParts[0]))\n            return aIndex < bIndex\n        }\n        #expect(sortedResponses.count == requestCount)\n        for i in 0..<requestCount {\n            #expect(sortedResponses[i] == \"\\(i): success-tcp\")\n        }\n    }\n}\n\nprivate final class NewlineDelimiterCoder: ByteToMessageDecoder, MessageToByteEncoder {\n    typealias InboundIn = ByteBuffer\n    typealias InboundOut = String\n\n    private let newLine = UInt8(ascii: \"\\n\")\n\n    init() {}\n\n    func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState {\n        let readableBytes = buffer.readableBytesView\n\n        guard let firstLine = readableBytes.firstIndex(of: self.newLine).map({ readableBytes[..<$0] }) else {\n            return .needMoreData\n        }\n        buffer.moveReaderIndex(forwardBy: firstLine.count + 1)\n        // Fire a read without a newline\n        let data = Self.wrapInboundOut(String(buffer: ByteBuffer(firstLine)))\n        context.fireChannelRead(data)\n        return .continue\n    }\n\n    func encode(data: String, out: inout ByteBuffer) throws {\n        out.writeString(data)\n        out.writeInteger(self.newLine)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/UDPEchoHandler.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\nfinal class UDPEchoHandler: ChannelInboundHandler {\n\n    typealias InboundIn = AddressedEnvelope<ByteBuffer>\n\n    func channelRead(context: ChannelHandlerContext, data: NIOAny) {\n        context.writeAndFlush(data, promise: nil)\n    }\n\n    func errorCaught(context: ChannelHandlerContext, error: Error) {\n        context.close(promise: nil)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/UDPEchoServer.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport NIO\n\nstruct UDPEchoServer: Sendable {\n    private let serverAddress: SocketAddress\n\n    private let eventLoopGroup: MultiThreadedEventLoopGroup\n\n    public init(serverAddress: SocketAddress, eventLoopGroup: MultiThreadedEventLoopGroup) {\n        self.serverAddress = serverAddress\n        self.eventLoopGroup = eventLoopGroup\n    }\n\n    public func run() throws -> EventLoopFuture<any Channel> {\n        let bootstrap = DatagramBootstrap(group: self.eventLoopGroup)\n            .channelInitializer { channel in\n                channel.eventLoop.makeCompletedFuture {\n                    try channel.pipeline.syncOperations.addHandler(\n                        UDPEchoHandler()\n                    )\n                }\n            }\n\n        return bootstrap.bind(to: self.serverAddress)\n    }\n}\n"
  },
  {
    "path": "Tests/SocketForwarderTests/UDPForwarderTest.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport Logging\nimport NIO\nimport Testing\n\n@testable import SocketForwarder\n\nstruct UDPForwarderTest {\n    let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)\n\n    @Test\n    func testUDPForwarder() async throws {\n        let requestCount = 100\n        var responses: [String] = []\n\n        // bring up server on ephemeral port and get address\n        let serverAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let server = UDPEchoServer(serverAddress: serverAddress, eventLoopGroup: eventLoopGroup)\n        let serverChannel = try await server.run().get()\n        let actualServerAddress = try #require(serverChannel.localAddress)\n\n        // bring up proxy on ephemeral port and get address\n        let proxyAddress = try SocketAddress(ipAddress: \"127.0.0.1\", port: 0)\n        let forwarder = try UDPForwarder(\n            proxyAddress: proxyAddress,\n            serverAddress: actualServerAddress,\n            eventLoopGroup: eventLoopGroup\n        )\n        let forwarderResult = try await forwarder.run().get()\n        let actualProxyAddress = try #require(forwarderResult.proxyAddress)\n\n        // send a bunch of messages and collect them\n        print(\"testUDPForwarder: send messages\")\n        try await withThrowingTaskGroup(of: String.self) { group in\n            for i in 0..<requestCount {\n                group.addTask {\n                    var response: String = \"\\(i): error\"\n                    let channel = try await DatagramBootstrap(group: self.eventLoopGroup)\n                        .connect(to: actualProxyAddress) { channel in\n                            channel.eventLoop.makeCompletedFuture {\n                                try NIOAsyncChannel(\n                                    wrappingChannelSynchronously: channel,\n                                    configuration: NIOAsyncChannel.Configuration(\n                                        inboundType: AddressedEnvelope<ByteBuffer>.self,\n                                        outboundType: AddressedEnvelope<ByteBuffer>.self\n                                    )\n                                )\n                            }\n                        }\n\n                    try await channel.executeThenClose { inbound, outbound in\n                        let remoteAddress = try #require(channel.channel.remoteAddress)\n                        let data = ByteBufferAllocator().buffer(string: \"\\(i): success-udp\")\n                        try await outbound.write(AddressedEnvelope<ByteBuffer>(remoteAddress: remoteAddress, data: data))\n                        for try await inboundData in inbound {\n                            response = String(buffer: inboundData.data)\n                            break\n                        }\n                    }\n\n                    return response\n                }\n            }\n\n            for try await response in group {\n                responses.append(response)\n            }\n        }\n\n        // close everything down\n        print(\"testUDPForwarder: close server\")\n        serverChannel.eventLoop.execute { _ = serverChannel.close() }\n        try await serverChannel.closeFuture.get()\n\n        print(\"testUDPForwarder: close forwarder\")\n        forwarderResult.close()\n        try await forwarderResult.wait()\n\n        // verify all expected messages\n        print(\"testUDPForwarder: validate responses\")\n        let sortedResponses = try responses.sorted { (a, b) in\n            let aParts = a.split(separator: \":\")\n            let bParts = b.split(separator: \":\")\n            #expect(aParts.count > 1)\n            #expect(bParts.count > 1)\n            let aIndex = try #require(Int(aParts[0]))\n            let bIndex = try #require(Int(bParts[0]))\n            return aIndex < bIndex\n        }\n        #expect(sortedResponses.count == requestCount)\n        for i in 0..<requestCount {\n            #expect(sortedResponses[i] == \"\\(i): success-udp\")\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/TerminalProgressTests/ProgressBarTests.swift",
    "content": "//===----------------------------------------------------------------------===//\n// Copyright © 2025-2026 Apple Inc. and the container project authors.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//   https://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//===----------------------------------------------------------------------===//\n\nimport XCTest\n\n@testable import TerminalProgress\n\nfinal class ProgressBarTests: XCTestCase {\n    func testSpinner() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\"\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testSpinnerFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\"\n        )\n        let progress = ProgressBar(config: config)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task [0s]\")\n    }\n\n    func testNoSpinner() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSpinner: false\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task [0s]\")\n    }\n\n    func testNoSpinnerFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSpinner: false\n        )\n        let progress = ProgressBar(config: config)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task [0s]\")\n    }\n\n    func testNoTasks() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: false\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testTasks() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testTasksAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(tasks: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testTasksSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(tasks: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testTotalTasks() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true,\n            totalTasks: 2\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ [0/2] Task [0s]\")\n    }\n\n    func testTotalTasksFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true,\n            totalTasks: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ [0/2] Task [0s]\")\n    }\n\n    func testTotalTasksAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true,\n            totalTasks: 1\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(totalTasks: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ [0/2] Task [0s]\")\n    }\n\n    func testTotalTasksSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTasks: true,\n            totalTasks: 1\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(totalTasks: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ [0/2] Task [0s]\")\n    }\n\n    func testTotalTasksInvalid() throws {\n        do {\n            let _ = try ProgressConfig(description: \"test\", totalTasks: 0)\n        } catch ProgressConfig.Error.invalid(_) {\n            return\n        }\n        XCTFail(\"expected ProgressConfig.Error.invalid\")\n    }\n\n    func testDescription() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\"\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testNoDescription() async throws {\n        let config = try ProgressConfig()\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ [0s]\")\n    }\n\n    func testNoPercent() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: false,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testPercentHidden() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: true\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testPercentItems() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: true,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% [0s]\")\n    }\n\n    func testPercentItemsFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: true,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% [0s]\")\n    }\n\n    func testPercentSize() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: true,\n            showSize: false,\n            showSpeed: false,\n            totalSize: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% [0s]\")\n    }\n\n    func testPercentSizeFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showPercent: true,\n            showSize: false,\n            showSpeed: false,\n            totalSize: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% [0s]\")\n    }\n\n    func testNoProgressBar() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showProgressBar: false,\n            totalItems: 2,\n            width: 57\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% [0s]\")\n    }\n\n    func testProgressBar() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showProgressBar: true,\n            totalItems: 2,\n            width: 57\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task 50% |██  | [0s]\")\n    }\n\n    func testProgressBarFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showProgressBar: true,\n            totalItems: 2,\n            width: 57\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task 100% |███| [0s]\")\n    }\n\n    func testProgressBarMinWidth() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showProgressBar: true,\n            totalItems: 2,\n            width: 13\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task 50% | | [0s]\")\n    }\n\n    func testProgressBarMinWidthFinished() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showProgressBar: true,\n            totalItems: 2,\n            width: 13\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"Task 100% |█| [0s]\")\n    }\n\n    func testNoItems() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: false\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testItemsZero() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testItemsAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task (1 it) [0s]\")\n    }\n\n    func testItemsAddFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(items: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task [0s]\")\n    }\n\n    func testItemsSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task (2 it) [0s]\")\n    }\n\n    func testTotalItemsZeroItems() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            totalItems: 1\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 0% [0s]\")\n    }\n\n    func testTotalItems() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 of 2 it) [0s]\")\n    }\n\n    func testTotalItemsFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% (2 it) [0s]\")\n    }\n\n    func testTotalItemsAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            totalItems: 1\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.add(totalItems: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 of 2 it) [0s]\")\n    }\n\n    func testTotalItemsSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            totalItems: 1\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.set(totalItems: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 of 2 it) [0s]\")\n    }\n\n    func testTotalItemsInvalid() throws {\n        do {\n            let _ = try ProgressConfig(description: \"test\", totalItems: 0)\n        } catch ProgressConfig.Error.invalid(_) {\n            return\n        }\n        XCTFail(\"expected ProgressConfig.Error.invalid\")\n    }\n\n    func testNoSize() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: false\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testSizeZero() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testSizeAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(size: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task (1 byte) [0s]\")\n    }\n\n    func testSizeAddFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false\n        )\n        let progress = ProgressBar(config: config)\n        progress.add(size: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task [0s]\")\n    }\n\n    func testSizeSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task (2 bytes) [0s]\")\n    }\n\n    func testTotalSizeZeroSize() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            totalSize: 1\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 0% [0s]\")\n    }\n\n    func testTotalSizeDifferentUnits() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 byte/2 bytes) [0s]\")\n    }\n\n    func testTotalSizeDifferentUnitsFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 1)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% (2 bytes) [0s]\")\n    }\n\n    func testTotalSizeSameUnits() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (2/4 bytes) [0s]\")\n    }\n\n    func testTotalSizeSameUnitsFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% (4 bytes) [0s]\")\n    }\n\n    func testTotalSizeAdd() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 3\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        progress.add(totalSize: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (2/4 bytes) [0s]\")\n    }\n\n    func testTotalSizeSet() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSize: true,\n            showSpeed: false,\n            totalSize: 3\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        progress.set(totalSize: 4)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (2/4 bytes) [0s]\")\n    }\n\n    func testTotalSizeInvalid() throws {\n        do {\n            let _ = try ProgressConfig(description: \"test\", totalSize: 0)\n        } catch ProgressConfig.Error.invalid(_) {\n            return\n        }\n        XCTFail(\"expected ProgressConfig.Error.invalid\")\n    }\n\n    func testItemsAndSize() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            showSize: true,\n            showSpeed: false,\n            totalItems: 2,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 of 2 it, 2/4 bytes) [0s]\")\n    }\n\n    func testItemsAndSizeFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            showSize: true,\n            showSpeed: false,\n            totalItems: 2,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.set(size: 2)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertEqual(output, \"✔ Task 100% (2 it, 4 bytes) [0s]\")\n    }\n\n    func testNoSpeed() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSpeed: false,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (2/4 bytes) [0s]\")\n    }\n\n    func testSpeed() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSpeed: true,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertTrue(output.contains(\"/s\"))\n    }\n\n    func testSpeedFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showSpeed: true,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertFalse(output.contains(\"/s\"))\n    }\n\n    func testItemsSizeAndSpeed() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            showSize: true,\n            showSpeed: true,\n            totalItems: 2,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertTrue(output.contains(\"1 of 2 it, 2/4 bytes\"))\n        XCTAssertTrue(output.contains(\"/s\"))\n    }\n\n    func testItemsSizeAndSpeedFinish() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showItems: true,\n            showSize: true,\n            showSpeed: true,\n            totalItems: 2,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        progress.set(size: 2)\n        progress.finish()\n        let output = progress.draw()\n        XCTAssertTrue(output.contains(\"2 it, 4 bytes\"))\n        XCTAssertFalse(output.contains(\"/s\"))\n    }\n\n    func testNoTime() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTime: false\n        )\n        let progress = ProgressBar(config: config)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task\")\n    }\n\n    func testTime() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            showTime: true\n        )\n        let progress = ProgressBar(config: config)\n        sleep(1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [1s]\")\n    }\n\n    func testIgnoreSmallSize() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            ignoreSmallSize: true,\n            totalSize: 4\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(size: 2)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task [0s]\")\n    }\n\n    func testItemsName() async throws {\n        let config = try ProgressConfig(\n            description: \"Task\",\n            itemsName: \"files\",\n            showItems: true,\n            totalItems: 2\n        )\n        let progress = ProgressBar(config: config)\n        progress.set(items: 1)\n        let output = progress.draw()\n        XCTAssertEqual(output, \"⠋ Task 50% (1 of 2 files) [0s]\")\n    }\n}\n"
  },
  {
    "path": "config/container-core-images-config.json",
    "content": "{\n    \"abstract\" : \"Core image management plugin\",\n    \"version\":  \"0.1\",\n    \"author\": \"Apple\",\n    \"servicesConfig\" : {\n        \"loadAtBoot\" : true,\n        \"runAtLoad\" : false,\n        \"services\" : [\n            {\n                \"type\" : \"core\", \n                \"description\": \"Provide an XPC interface to interact with an image store.\"\n            }\n        ],\n        \"defaultArguments\": []\n    }\n}\n"
  },
  {
    "path": "config/container-network-vmnet-config.json",
    "content": "{\n    \"abstract\" : \"vmnet network management plugin\",\n    \"version\":  \"0.1\",\n    \"author\": \"Apple\",\n    \"servicesConfig\" : {\n        \"loadAtBoot\" : false,\n        \"runAtLoad\" : true,\n        \"services\" : [\n            {\n                \"type\" : \"network\"\n            }\n        ],\n        \"defaultArguments\": []\n    }\n}\n"
  },
  {
    "path": "config/container-runtime-linux-config.json",
    "content": "{\n    \"abstract\" : \"Linux container runtime plugin\",\n    \"version\":  \"0.1\",\n    \"author\": \"Apple\",\n    \"servicesConfig\" : {\n        \"loadAtBoot\" : false,\n        \"runAtLoad\" : false,\n        \"services\" : [\n            {\n                \"type\" : \"runtime\"\n            }\n        ],\n        \"defaultArguments\": []\n    }\n}\n"
  },
  {
    "path": "docs/command-reference.md",
    "content": "# Container CLI Command Reference\n\n> [!IMPORTANT]\n> This file contains documentation for the CURRENT BRANCH. To find documentation for official releases, find the target release on the [Release Page](https://github.com/apple/container/releases) and click the tag corresponding to your release version. \n>\n> Example: [release 0.4.1 tag](https://github.com/apple/container/tree/0.4.1)\n\nNote: Command availability may vary depending on host operating system and macOS version.\n\n## Core Commands\n\n### `container run`\n\nRuns a container from an image. If a command is provided, it will execute inside the container; otherwise the image's default command runs. By default the container runs in the foreground and stdin remains closed unless `-i`/`--interactive` is specified.\n\n**Usage**\n\n```bash\ncontainer run [<options>] <image> [<arguments> ...]\n```\n\n**Arguments**\n\n*   `<image>`: Image name\n*   `<arguments>`: Container init process arguments\n\n**Process Options**\n\n*   `-e, --env <env>`: Set environment variables (format: key=value)\n*   `--env-file <env-file>`: Read in a file of environment variables (key=value format, ignores # comments and blank lines)\n*   `--gid <gid>`: Set the group ID for the process\n*   `-i, --interactive`: Keep the standard input open even if not attached\n*   `-t, --tty`: Open a TTY with the process\n*   `-u, --user <user>`: Set the user for the process (format: name|uid[:gid])\n*   `--uid <uid>`: Set the user ID for the process\n*   `-w, --workdir, --cwd <dir>`: Set the initial working directory inside the container\n\n**Resource Options**\n\n*   `-c, --cpus <cpus>`: Number of CPUs to allocate to the container\n*   `-m, --memory <memory>`: Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix\n\n**Management Options**\n\n*   `-a, --arch <arch>`: Set arch if image can target multiple architectures (default: arm64)\n*   `--cidfile <cidfile>`: Write the container ID to the path provided\n*   `-d, --detach`: Run the container and detach from the process\n*   `--dns <ip>`: DNS nameserver IP address\n*   `--dns-domain <domain>`: Default DNS domain\n*   `--dns-option <option>`: DNS options\n*   `--dns-search <domain>`: DNS search domains\n*   `--entrypoint <cmd>`: Override the entrypoint of the image\n*   `--init`: Run an init process inside the container that forwards signals and reaps processes\n*   `--init-image <image>`: Use a custom init image instead of the default. This allows customizing boot-time behavior before the OCI container starts, such as running VM-level daemons, configuring eBPF filters, or debugging the init process.\n*   `-k, --kernel <path>`: Set a custom kernel path\n*   `-l, --label <label>`: Add a key=value label to the container\n*   `--mount <mount>`: Add a mount to the container (format: type=<>,source=<>,target=<>,readonly)\n*   `--name <name>`: Use the specified name as the container ID\n*   `--network <network>`: Attach the container to a network\n*   `--no-dns`: Do not configure DNS in the container\n*   `--os <os>`: Set OS if image can target multiple operating systems (default: linux)\n*   `-p, --publish <spec>`: Publish a port from container to host (format: [host-ip:]host-port:container-port[/protocol])\n*   `--platform <platform>`: Platform for the image if it's multi-platform. This takes precedence over --os and --arch\n*   `--publish-socket <spec>`: Publish a socket from container to host (format: host_path:container_path)\n*   `--read-only`: Mount the container's root filesystem as read-only\n*   `--rm, --remove`: Remove the container after it stops\n*   `--rosetta`: Enable Rosetta in the container\n*   `--runtime`: Set the runtime handler for the container (default: container-runtime-linux)\n*   `--ssh`: Forward SSH agent socket to container\n*   `--tmpfs <tmpfs>`: Add a tmpfs mount to the container at the given path\n*   `-v, --volume <volume>`: Bind mount a volume into the container\n*   `--virtualization`: Expose virtualization capabilities to the container (requires host and guest support)\n\n**Registry Options**\n\n*   `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)\n\n    * **Behavior of `auto`**\n\n        When `auto` is selected, the target registry is considered **internal/local** if the registry host matches any of these criteria:\n        - The host is a loopback address (e.g., `localhost`, `127.*`)\n        - The host is within the `RFC1918` private IP ranges:\n            - `10.*.*.*`\n            - `192.168.*.*`\n            - `172.16.*.*` through `172.31.*.*`\n        - The host ends with the machine's default container DNS domain (as defined in `DefaultsStore.Keys.defaultDNSDomain`, located [here](../Sources/ContainerPersistence/DefaultsStore.swift))\n\n        For internal/local registries, the client uses **HTTP**. Otherwise, it uses **HTTPS**.\n\n**Progress Options**\n\n*   `--progress <type>`: Progress type (format: none|ansi) (default: ansi)\n\n**Examples**\n\n```bash\n# run a container and attach an interactive shell\ncontainer run -it ubuntu:latest /bin/bash\n\n# run a background web server\ncontainer run -d --name web -p 8080:80 nginx:latest\n\n# set environment variables and limit resources\ncontainer run -e NODE_ENV=production --cpus 2 --memory 1G node:18\n\n# run a container with a specific MAC address\ncontainer run --network default,mac=02:42:ac:11:00:02 ubuntu:latest\n\n# run a container with an init process to reap zombies and forward signals\ncontainer run --init ubuntu:latest my-app\n\n# run a container with a custom init image for boot customization\ncontainer run --init-image local/custom-init:latest ubuntu:latest\n```\n\n### `container build`\n\nBuilds an OCI image from a local build context. It reads a Dockerfile (default `Dockerfile`) or Containerfile and produces an image tagged with `-t` option. The build runs in isolation using BuildKit, and resource limits may be set for the build process itself.\n\nWhen no `-f/--file` is specified, the build command will look for `Dockerfile` first, then fall back to `Containerfile` if `Dockerfile` is not found.\n\n**Usage**\n\n```bash\ncontainer build [<options>] [<context-dir>]\n```\n\n**Arguments**\n\n*   `<context-dir>`: Build directory (default: .)\n\n**Options**\n\n*   `-a, --arch <value>`: Add the architecture type to the build\n*   `--build-arg <key=val>`: Set build-time variables\n*   `-c, --cpus <cpus>`: Number of CPUs to allocate to the builder container (default: 2)\n*   `-f, --file <path>`: Path to Dockerfile\n*   `-l, --label <key=val>`: Set a label\n*   `-m, --memory <memory>`: Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix (default: 2048MB)\n*   `--no-cache`: Do not use cache\n*   `-o, --output <value>`: Output configuration for the build (format: type=<oci|tar|local>[,dest=]) (default: type=oci)\n*   `--os <value>`: Add the OS type to the build\n*   `--platform <platform>`: Add the platform to the build (format: os/arch[/variant], takes precedence over --os and --arch)\n*   `--progress <type>`: Progress type (format: auto|plain|tty) (default: auto)\n*   `--pull`: Pull latest image\n*   `-q, --quiet`: Suppress build output\n*   `--secret <id=key,...>`: Set build-time secrets (format: id=<key>[,env=<ENV_VAR>|,src=<local/path>])\n*   `-t, --tag <name>`: Name for the built image (can be specified multiple times)\n*   `--target <stage>`: Set the target build stage\n*   `--vsock-port <port>`: Builder shim vsock port (default: 8088)\n\n**Examples**\n\n```bash\n# build an image and tag it as my-app:latest\ncontainer build -t my-app:latest .\n\n# use a custom Dockerfile\ncontainer build -f docker/Dockerfile.prod -t my-app:prod .\n\n# pass build args\ncontainer build --build-arg NODE_VERSION=18 -t my-app .\n\n# build the production stage only and disable cache\ncontainer build --target production --no-cache -t my-app:prod .\n\n# build with multiple tags\ncontainer build -t my-app:latest -t my-app:v1.0.0 -t my-app:stable .\n```\n\n## Container Management\n\n### `container create`\n\nCreates a container from an image without starting it. This command accepts most of the same process/resource/management flags as `container run`, but leaves the container stopped after creation.\n\n**Usage**\n\n```bash\ncontainer create [<options>] <image> [<arguments> ...]\n```\n\n**Arguments**\n\n*   `<image>`: Image name\n*   `<arguments>`: Container init process arguments\n\n**Process Options**\n\n*   `-e, --env <env>`: Set environment variables (format: key=value)\n*   `--env-file <env-file>`: Read in a file of environment variables (key=value format, ignores # comments and blank lines)\n*   `--gid <gid>`: Set the group ID for the process\n*   `-i, --interactive`: Keep the standard input open even if not attached\n*   `-t, --tty`: Open a TTY with the process\n*   `-u, --user <user>`: Set the user for the process (format: name|uid[:gid])\n*   `--uid <uid>`: Set the user ID for the process\n*   `-w, --workdir, --cwd <dir>`: Set the initial working directory inside the container\n\n**Resource Options**\n\n*   `-c, --cpus <cpus>`: Number of CPUs to allocate to the container\n*   `-m, --memory <memory>`: Amount of memory (1MiByte granularity), with optional K, M, G, T, or P suffix\n\n**Management Options**\n\n*   `-a, --arch <arch>`: Set arch if image can target multiple architectures (default: arm64)\n*   `--cidfile <cidfile>`: Write the container ID to the path provided\n*   `-d, --detach`: Run the container and detach from the process\n*   `--dns <ip>`: DNS nameserver IP address\n*   `--dns-domain <domain>`: Default DNS domain\n*   `--dns-option <option>`: DNS options\n*   `--dns-search <domain>`: DNS search domains\n*   `--entrypoint <cmd>`: Override the entrypoint of the image\n*   `--init`: Run an init process inside the container that forwards signals and reaps processes\n*   `--init-image <image>`: Use a custom init image instead of the default. This allows customizing boot-time behavior before the OCI container starts, such as running VM-level daemons, configuring eBPF filters, or debugging the init process.\n*   `-k, --kernel <path>`: Set a custom kernel path\n*   `-l, --label <label>`: Add a key=value label to the container\n*   `--mount <mount>`: Add a mount to the container (format: type=<>,source=<>,target=<>,readonly)\n*   `--name <name>`: Use the specified name as the container ID\n*   `--network <network>`: Attach the container to a network\n*   `--no-dns`: Do not configure DNS in the container\n*   `--os <os>`: Set OS if image can target multiple operating systems (default: linux)\n*   `-p, --publish <spec>`: Publish a port from container to host (format: [host-ip:]host-port:container-port[/protocol])\n*   `--platform <platform>`: Platform for the image if it's multi-platform. This takes precedence over --os and --arch\n*   `--publish-socket <spec>`: Publish a socket from container to host (format: host_path:container_path)\n*   `--read-only`: Mount the container's root filesystem as read-only\n*   `--rm, --remove`: Remove the container after it stops\n*   `--rosetta`: Enable Rosetta in the container\n*   `--runtime`: Set the runtime handler for the container (default: container-runtime-linux)  \n*   `--ssh`: Forward SSH agent socket to container\n*   `--tmpfs <tmpfs>`: Add a tmpfs mount to the container at the given path\n*   `-v, --volume <volume>`: Bind mount a volume into the container\n*   `--virtualization`: Expose virtualization capabilities to the container (requires host and guest support)\n\n**Registry Options**\n\n*   `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)\n\n### `container start`\n\nStarts a stopped container. You can attach to the container's output streams and optionally keep STDIN open.\n\n**Usage**\n\n```bash\ncontainer start [--attach] [--interactive] [--debug] <container-id>\n```\n\n**Arguments**\n\n*   `<container-id>`: Container ID\n\n**Options**\n\n*   `-a, --attach`: Attach stdout/stderr\n*   `-i, --interactive`: Attach stdin\n\n### `container stop`\n\nStops running containers gracefully by sending a signal. A timeout can be specified before a SIGKILL is issued. If no containers are specified, nothing is stopped unless `--all` is used.\n\n**Usage**\n\n```bash\ncontainer stop [--all] [--signal <signal>] [--time <time>] [--debug] [<container-ids> ...]\n```\n\n**Arguments**\n\n*   `<container-ids>`: Container IDs\n\n**Options**\n\n*   `-a, --all`: Stop all running containers\n*   `-s, --signal <signal>`: Signal to send to the containers (default: SIGTERM)\n*   `-t, --time <time>`: Seconds to wait before killing the containers (default: 5)\n\n### `container kill`\n\nImmediately kills running containers by sending a signal (defaults to `KILL`). Use with caution: it does not allow for graceful shutdown.\n\n**Usage**\n\n```bash\ncontainer kill [--all] [--signal <signal>] [--debug] [<container-ids> ...]\n```\n\n**Arguments**\n\n*   `<container-ids>`: Container IDs\n\n**Options**\n\n*   `-a, --all`: Kill or signal all running containers\n*   `-s, --signal <signal>`: Signal to send to the container(s) (default: KILL)\n\n### `container delete (rm)`\n\nDeletes one or more containers. If the container is running, you may force deletion with `--force`. Without a container ID, nothing happens unless `--all` is supplied.\n\n**Usage**\n\n```bash\ncontainer delete [--all] [--force] [--debug] [<container-ids> ...]\n```\n\n**Arguments**\n\n*   `<container-ids>`: Container IDs\n\n**Options**\n\n*   `-a, --all`: Delete all containers\n*   `-f, --force`: Delete containers even if they are running\n\n### `container list (ls)`\n\nLists containers. By default only running containers are shown. Output can be formatted as a table or JSON.\n\n**Usage**\n\n```bash\ncontainer list [--all] [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `-a, --all`: Include containers that are not running\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the container ID\n\n### `container exec`\n\nExecutes a command inside a running container. It uses the same process flags as `container run` to control environment, user, and TTY settings.\n\n**Usage**\n\n```bash\ncontainer exec [--detach] [--env <env> ...] [--env-file <env-file> ...] [--gid <gid>] [--interactive] [--tty] [--user <user>] [--uid <uid>] [--workdir <dir>] [--debug] <container-id> <arguments> ...\n```\n\n**Arguments**\n\n*   `<container-id>`: Container ID\n*   `<arguments>`: New process arguments\n\n**Options**\n\n*   `-d, --detach`: Run the process and detach from it\n\n**Process Options**\n\n*   `-e, --env <env>`: Set environment variables (format: key=value)\n*   `--env-file <env-file>`: Read in a file of environment variables (key=value format, ignores # comments and blank lines)\n*   `--gid <gid>`: Set the group ID for the process\n*   `-i, --interactive`: Keep the standard input open even if not attached\n*   `-t, --tty`: Open a TTY with the process\n*   `-u, --user <user>`: Set the user for the process (format: name|uid[:gid])\n*   `--uid <uid>`: Set the user ID for the process\n*   `-w, --workdir, --cwd <dir>`: Set the initial working directory inside the container\n\n### `container export`\n\nExports a stopped container's filesystem as a tar archive. The container must be stopped before exporting. If no output file is specified, the tar stream is written to stdout.\n\n**Usage**\n\n```bash\ncontainer export [-o <output>] [--debug] <container-id>\n```\n\n**Arguments**\n\n*   `<container-id>`: Container ID\n\n**Options**\n\n*   `-o, --output <output>`: Pathname for the saved container filesystem (defaults to stdout)\n\n**Examples**\n\n```bash\n# export a container's filesystem to a file\ncontainer stop mycontainer\ncontainer export -o mycontainer.tar mycontainer\n\n# export to stdout and pipe to another tool\ncontainer export mycontainer > mycontainer.tar\n```\n\n### `container logs`\n\nFetches logs from a container. You can follow the logs (`-f`/`--follow`), restrict the number of lines shown, or view boot logs.\n\n**Usage**\n\n```bash\ncontainer logs [--boot] [--follow] [-n <n>] [--debug] <container-id>\n```\n\n**Arguments**\n\n*   `<container-id>`: Container ID\n\n**Options**\n\n*   `--boot`: Display the boot log for the container instead of stdio\n*   `-f, --follow`: Follow log output\n*   `-n <n>`: Number of lines to show from the end of the logs. If not provided this will print all of the logs\n\n### `container inspect`\n\nDisplays detailed container information in JSON. Pass one or more container IDs to inspect multiple containers.\n\n**Usage**\n\n```bash\ncontainer inspect [--debug] <container-ids> ...\n```\n\n**Arguments**\n\n*   `<container-ids>`: Container IDs\n\n**Options**\n\nNo options.\n\n### `container stats`\n\nDisplays real-time resource usage statistics for containers. Shows CPU percentage, memory usage, network I/O, block I/O, and process count. By default, continuously updates statistics in an interactive display (like `top`). Use `--no-stream` for a single snapshot.\n\n**Usage**\n\n```bash\ncontainer stats [--format <format>] [--no-stream] [--debug] [<container-ids> ...]\n```\n\n**Arguments**\n\n*   `<container-ids>`: Container IDs or names (optional, shows all running containers if not specified)\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `--no-stream`: Disable streaming stats and only pull the first result\n\n**Examples**\n\n```bash\n# show stats for all running containers (interactive)\ncontainer stats\n\n# show stats for specific containers\ncontainer stats web db cache\n\n# get a single snapshot of stats (non-interactive)\ncontainer stats --no-stream web\n\n# output stats as JSON\ncontainer stats --format json --no-stream web\n```\n\n### `container prune`\n\nRemoves stopped containers to reclaim disk space. The command outputs the amount of space freed after deletion.\n\n**Usage**\n\n```bash\ncontainer prune [--debug]\n```\n\n**Options**\n\nNo options.\n\n## Image Management\n\n### `container image list (ls)`\n\nLists local images. Verbose output provides additional details such as image ID, creation time and full size; JSON output provides the same data in machine-readable form.\n\n**Usage**\n\n```bash\ncontainer image list [--format <format>] [--quiet] [--verbose] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the image name\n*   `-v, --verbose`: Verbose output\n\n### `container image pull`\n\nPulls an image from a registry. Supports specifying a platform and controlling progress display.\n\n**Usage**\n\n```bash\ncontainer image pull [--debug] [--scheme <scheme>] [--progress <type>] [--arch <arch>] [--os <os>] [--platform <platform>] <reference>\n```\n\n**Arguments**\n\n*   `<reference>`: Image reference to pull\n\n**Options**\n\n*   `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)\n*   `--progress <type>`: Progress type (format: none|ansi) (default: ansi)\n*   `-a, --arch <arch>`: Limit the pull to the specified architecture\n*   `--os <os>`: Limit the pull to the specified OS\n*   `--platform <platform>`: Limit the pull to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch)\n\n### `container image push`\n\nPushes an image to a registry. The flags mirror those for `image pull` with the addition of specifying a platform for multi-platform images.\n\n**Usage**\n\n```bash\ncontainer image push [--scheme <scheme>] [--progress <type>] [--arch <arch>] [--os <os>] [--platform <platform>] [--debug] <reference>\n```\n\n**Arguments**\n\n*   `<reference>`: Image reference to push\n\n**Options**\n\n*   `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)\n*   `--progress <type>`: Progress type (format: none|ansi) (default: ansi)\n*   `-a, --arch <arch>`: Limit the push to the specified architecture\n*   `--os <os>`: Limit the push to the specified OS\n*   `--platform <platform>`: Limit the push to the specified platform (format: os/arch[/variant], takes precedence over --os and --arch)\n\n### `container image save`\n\nSaves an image to a tar archive on disk. Useful for exporting images for offline transport.\n\n**Usage**\n\n```bash\ncontainer image save [--arch <arch>] [--os <os>] --output <output> [--platform <platform>] [--debug] <references> ...\n```\n\n**Arguments**\n\n*   `<references>`: Image references to save\n\n**Options**\n\n*   `-a, --arch <arch>`: Architecture for the saved image\n*   `--os <os>`: OS for the saved image\n*   `-o, --output <output>`: Pathname for the saved image\n*   `--platform <platform>`: Platform for the saved image (format: os/arch[/variant], takes precedence over --os and --arch)\n\n### `container image load`\n\nLoads images from a tar archive created by `image save`. The tar file must be specified via `--input`.\n\n**Usage**\n\n```bash\ncontainer image load --input <input> [--force] [--debug]\n```\n\n**Options**\n\n*   `-i, --input <input>`: Path to the image tar archive\n*   `-f, --force`: Load images even if invalid member files are detected\n\n### `container image tag`\n\nApplies a new tag to an existing image. The original image reference remains unchanged.\n\n**Usage**\n\n```bash\ncontainer image tag <source> <target> [--debug]\n```\n\n**Arguments**\n\n*   `<source>`: The existing image reference (format: image-name[:tag])\n*   `<target>`: The new image reference\n\n**Options**\n\nNo options.\n\n### `container image delete (rm)`\n\nDeletes one or more images. If no images are provided, `--all` can be used to delete all images. Images currently referenced by running containers cannot be deleted without first removing those containers.\n\n**Usage**\n\n```bash\ncontainer image delete [--all] [--force] [--debug] [<images> ...]\n```\n\n**Arguments**\n\n*   `<images>`: Image names or IDs\n\n**Options**\n\n*   `-a, --all`: Delete all images\n*   `-f, --force`: Ignore errors for images that are not found\n\n### `container image prune`\n\nRemoves unused images to reclaim disk space. By default, only removes dangling images (images with no tags). Use `-a` to remove all images not referenced by any container.\n\n**Usage**\n\n```bash\ncontainer image prune [--all] [--debug]\n```\n\n**Options**\n\n*   `-a, --all`: Remove all unused images, not just dangling ones\n\n### `container image inspect`\n\nShows detailed information for one or more images in JSON format. Accepts image names or IDs.\n\n**Usage**\n\n```bash\ncontainer image inspect [--debug] <images> ...\n```\n\n**Arguments**\n\n*   `<images>`: Images to inspect\n\n**Options**\n\nNo options.\n\n## Builder Management\n\nThe builder commands manage the BuildKit-based builder used for image builds.\n\n### `container builder start`\n\nStarts the BuildKit builder container. CPU and memory limits can be set for the builder.\n\n**Usage**\n\n```bash\ncontainer builder start [--cpus <cpus>] [--memory <memory>] [--debug]\n```\n\n**Options**\n\n*   `-c, --cpus <cpus>`: Number of CPUs to allocate to the builder container (default: 2)\n*   `-m, --memory <memory>`: Amount of builder container memory (1MiByte granularity), with optional K, M, G, T, or P suffix (default: 2048MB)\n\n### `container builder status`\n\nShows the current status of the BuildKit builder. Without flags a human-readable table is displayed; with `--format json` the status is returned as JSON.\n\n**Usage**\n\n```bash\ncontainer builder status [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the container ID\n\n### `container builder stop`\n\nStops the BuildKit builder container.\n\n**Usage**\n\n```bash\ncontainer builder stop [--debug]\n```\n\n**Options**\n\nNo options.\n\n### `container builder delete (rm)`\n\nDeletes the BuildKit builder container. It can optionally force deletion if the builder is still running.\n\n**Usage**\n\n```bash\ncontainer builder delete [--force] [--debug]\n```\n\n**Options**\n\n*   `-f, --force`: Delete the builder even if it is running\n\n## Network Management (macOS 26+)\n\nThe network commands are available on macOS 26 and later and allow creation and management of user-defined container networks.\n\n### `container network create`\n\nCreates a new network with the given name.\n\n**Usage**\n\n```bash\ncontainer network create [--label <label> ...] [--subnet <subnet>] [--subnet-v6 <subnet-v6>] [--debug] <name>\n```\n\n**Arguments**\n\n*   `<name>`: Network name\n\n**Options**\n\n*   `--label <label>`: Set metadata for a network\n*   `--subnet <subnet>`: Set the IPv4 subnet for a network (CIDR format, e.g., 192.168.100.0/24)\n*   `--subnet-v6 <subnet-v6>`: Set the IPv6 prefix for a network (CIDR format, e.g., fd00:1234::/64)\n\n### `container network delete (rm)`\n\nDeletes one or more networks. When deleting multiple networks, pass them as separate arguments. To delete all networks, use `--all`.\n\n**Usage**\n\n```bash\ncontainer network delete [--all] [--debug] [<network-names> ...]\n```\n\n**Arguments**\n\n*   `<network-names>`: Network names\n\n**Options**\n\n*   `-a, --all`: Delete all networks\n\n### `container network prune`\n\nRemoves networks not connected to any containers. However, default and system networks are preserved.\n\n**Usage**\n\n```bash\ncontainer network prune [--debug]\n```\n\n**Options**\n\nNo options.\n\n### `container network list (ls)`\n\nLists user-defined networks.\n\n**Usage**\n\n```bash\ncontainer network list [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the network name\n\n### `container network inspect`\n\nShows detailed information about one or more networks.\n\n**Usage**\n\n```bash\ncontainer network inspect <networks> ... [--debug]\n```\n\n**Arguments**\n\n*   `<networks>`: Networks to inspect\n\n**Options**\n\nNo options.\n\n## Volume Management\n\nManage persistent volumes for containers. Volumes can be explicitly created with `volume create` or implicitly created when referenced in container commands (e.g., `-v myvolume:/path` or `-v /path` for anonymous volumes).\n\n### `container volume create`\n\nCreates a new named volume with an optional size and driver-specific options.\n\n**Usage**\n\n```bash\ncontainer volume create [--label <label> ...] [--opt <opt> ...] [-s <s>] [--debug] <name>\n```\n\n**Arguments**\n\n*   `<name>`: Volume name\n\n**Options**\n\n*   `--label <label>`: Set metadata for a volume\n*   `--opt <opt>`: Set driver specific options\n*   `-s <s>`: Size of the volume in bytes, with optional K, M, G, T, or P suffix\n\n**Anonymous Volumes**\n\nAnonymous volumes are auto-created when using `-v /path` or `--mount type=volume,dst=/path` without specifying a source. They use UUID-based naming (`anon-{36-char-uuid}`):\n\n```bash\n# Creates anonymous volume\ncontainer run -v /data alpine\n\n# Reuse anonymous volume by ID\nVOL=$(container volume list -q | grep anon)\ncontainer run -v $VOL:/data alpine\n\n# Manual cleanup\ncontainer volume rm $VOL\n```\n\n**Note**: Unlike Docker, anonymous volumes do NOT auto-cleanup with `--rm`. Manual deletion is required.\n\n### `container volume delete (rm)`\n\nDeletes one or more volumes by name. Volumes that are currently in use by containers (running or stopped) cannot be deleted.\n\n**Usage**\n\n```bash\ncontainer volume delete [--all] [--debug] [<names> ...]\n```\n\n**Arguments**\n\n*   `<names>`: Volume names\n\n**Options**\n\n*   `-a, --all`: Delete all volumes\n\n**Examples**\n\n```bash\n# delete a specific volume\ncontainer volume delete myvolume\n\n# delete multiple volumes\ncontainer volume delete vol1 vol2 vol3\n\n# delete all unused volumes\ncontainer volume delete --all\n```\n\n### `container volume prune`\n\nRemoves all volumes that have no container references. This includes volumes that are not attached to any running or stopped containers. The command reports the actual disk space reclaimed after deletion.\n\n**Usage**\n\n```bash\ncontainer volume prune [--debug]\n```\n\n**Options**\n\nNo options.\n\n### `container volume list (ls)`\n\nLists volumes.\n\n**Usage**\n\n```bash\ncontainer volume list [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the volume name\n\n### `container volume inspect`\n\nDisplays detailed information for one or more volumes in JSON.\n\n**Usage**\n\n```bash\ncontainer volume inspect [--debug] <names> ...\n```\n\n**Arguments**\n\n*   `<names>`: Volume names\n\n**Options**\n\nNo options.\n\n## Registry Management\n\nThe registry commands manage authentication and defaults for container registries.\n\n### `container registry login`\n\nAuthenticates with a registry. Credentials can be provided interactively or via flags. The login is stored for reuse by subsequent commands.\n\n**Usage**\n\n```bash\ncontainer registry login [--scheme <scheme>] [--password-stdin] [--username <username>] [--debug] <server>\n```\n\n**Arguments**\n\n*   `<server>`: Registry server name\n\n**Options**\n\n*   `--scheme <scheme>`: Scheme to use when connecting to the container registry. One of (http, https, auto) (default: auto)\n*   `--password-stdin`: Take the password from stdin\n*   `-u, --username <username>`: Registry user name\n\n### `container registry logout`\n\nLogs out of a registry, removing stored credentials.\n\n**Usage**\n\n```bash\ncontainer registry logout [--debug] <registry>\n```\n\n**Arguments**\n\n*   `<registry>`: Registry server name\n\n**Options**\n\nNo options.\n\n### `container registry list`\n\nList image registry logins.\n\n**Usage**\n\n```bash\ncontainer registry list [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the image registry name\n\n## System Management\n\nSystem commands manage the container apiserver, logs, DNS settings and kernel. These are only available on macOS hosts.\n\n### `container system start`\n\nStarts the container services and (optionally) installs a default kernel. It will start the `container-apiserver` and background services.\n\n**Usage**\n\n```bash\ncontainer system start [--app-root <app-root>] [--install-root <install-root>] [--log-root <log-root>] [--enable-kernel-install] [--disable-kernel-install] [--debug]\n```\n\n**Options**\n\n*   `-a, --app-root <app-root>`: Path to the root directory for application data\n*   `--install-root <install-root>`: Path to the root directory for application executables and plugins\n*   `--log-root <log-root>`: Path to the root directory for log data, using macOS log facility if not set\n*   `--enable-kernel-install/--disable-kernel-install`: Specify whether the default kernel should be installed or not (default: prompt user)\n\n### `container system stop`\n\nStops the container services and deregisters them from launchd. You can specify a prefix to target services created with a different launchd prefix.\n\n**Usage**\n\n```bash\ncontainer system stop [--prefix <prefix>] [--debug]\n```\n\n**Options**\n\n*   `-p, --prefix <prefix>`: Launchd prefix for services (default: com.apple.container.)\n\n### `container system status`\n\nChecks whether the container services are running and prints status information. It sends a health check request to the API server, which returns basic system information.\n\n**Usage**\n\n```bash\ncontainer system status [--prefix <prefix>] [--format <format>] [--debug]\n```\n\n**Options**\n\n*   `-p, --prefix <prefix>`: Launchd prefix for services (default: com.apple.container.)\n*   `--format <format>`    : Format of the output (values: json, table; default: table)\n\n### `container system version`\n\nShows version information for the CLI and, if available, the API server. The table format is consistent with other list outputs and includes a header. If the API server responds to a health check, a second row for the server is added.\n\n**Usage**\n\n```bash\ncontainer system version [--format <format>]\n```\n\n**Options**\n\n*   `--format <format>`: Output format (values: json, table; default: table)\n\n**Table Output**\n\nColumns: `COMPONENT`, `VERSION`, `BUILD`, `COMMIT`.\n\nExample:\n\n```bash\ncontainer system version\n```\n\n```\nCOMPONENT   VERSION                         BUILD   COMMIT\nCLI         1.2.3                           debug   abcdef1\nAPI Server  container-apiserver 1.2.3       release 1234abc\n```\n\n**JSON Output**\n\nBackward-compatible with previous CLI-only output. Top-level fields describe the CLI. When available, a `server` object is included with the same fields.\n\n```json\n{\n  \"version\": \"1.2.3\",\n  \"buildType\": \"debug\",\n  \"commit\": \"abcdef1\",\n  \"appName\": \"container CLI\",\n  \"server\": {\n    \"version\": \"container-apiserver 1.2.3\",\n    \"buildType\": \"release\",\n    \"commit\": \"1234abc\",\n    \"appName\": \"container API Server\"\n  }\n}\n```\n\n### `container system logs`\n\nDisplays logs from the container services. You can specify a time interval or follow new logs in real time.\n\n**Usage**\n\n```bash\ncontainer system logs [--follow] [--last <last>] [--debug]\n```\n\n**Options**\n\n*   `-f, --follow`: Follow log output\n*   `--last <last>`: Fetch logs starting from the specified time period (minus the current time); supported formats: m, h, d (default: 5m)\n\n### `container system df`\n\nShows disk usage for images, containers, and volumes. Displays total count, active count, size, and reclaimable space for each resource type.\n\n**Usage**\n\n```bash\ncontainer system df [--format <format>] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n\n### `container system dns create`\n\nCreates a local DNS domain for containers. Requires administrator privileges (use sudo).\n\n**Usage**\n\n```bash\ncontainer system dns create [--debug] <domain-name>\n```\n\n**Arguments**\n\n*   `<domain-name>`: The local domain name\n\n**Options**\n\nNo options.\n\n### `container system dns delete (rm)`\n\nDeletes a local DNS domain. Requires administrator privileges (use sudo).\n\n**Usage**\n\n```bash\ncontainer system dns delete [--debug] <domain-name>\n```\n\n**Arguments**\n\n*   `<domain-name>`: The local domain name\n\n**Options**\n\nNo options.\n\n### `container system dns list (ls)`\n\nLists configured local DNS domains for containers.\n\n**Usage**\n\n```bash\ncontainer system dns list [--debug]\n```\n\n**Options**\n\nNo options.\n\n### `container system kernel set`\n\nInstalls or updates the Linux kernel used by the container runtime on macOS hosts.\n\n**Usage**\n\n```bash\ncontainer system kernel set [--arch <arch>] [--binary <binary>] [--force] [--recommended] [--tar <tar>] [--debug]\n```\n\n**Options**\n\n*   `--arch <arch>`: The architecture of the kernel binary (values: amd64, arm64) (default: arm64)\n*   `--binary <binary>`: Path to the kernel file (or archive member, if used with --tar)\n*   `--force`: Overwrites an existing kernel with the same name\n*   `--recommended`: Download and install the recommended kernel as the default (takes precedence over all other flags)\n*   `--tar <tar>`: Filesystem path or remote URL to a tar archive containing a kernel file\n\n### `container system property list (ls)`\n\nLists all available system properties with their current values, types, and descriptions. Output can be formatted as a table or JSON.\n\n**Usage**\n\n```bash\ncontainer system property list [--format <format>] [--quiet] [--debug]\n```\n\n**Options**\n\n*   `--format <format>`: Format of the output (values: json, table; default: table)\n*   `-q, --quiet`: Only output the property ID\n\n**Examples**\n\n```bash\n# list all properties in table format\ncontainer system property list\n\n# get only property IDs\ncontainer system property list --quiet\n\n# output as JSON for scripting\ncontainer system property list --format json\n```\n\n### `container system property get`\n\nRetrieves the current value of a specific system property by its ID.\n\n**Usage**\n\n```bash\ncontainer system property get [--debug] <id>\n```\n\n**Arguments**\n\n*   `<id>`: The property ID\n\n**Options**\n\nNo options.\n\n**Examples**\n\n```bash\n# get the default registry domain\ncontainer system property get registry.domain\n\n# get the current DNS domain setting\ncontainer system property get dns.domain\n```\n\n### `container system property set`\n\nSets the value of a system property. The command validates the value based on the property type (boolean, domain name, image reference, URL, or CIDR address).\n\n**Usage**\n\n```bash\ncontainer system property set [--debug] <id> <value>\n```\n\n**Arguments**\n\n*   `<id>`: The property ID\n*   `<value>`: The property value\n\n**Options**\n\nNo options.\n\n**Examples**\n\n```bash\n# enable Rosetta for AMD64 builds on ARM64\ncontainer system property set build.rosetta true\n\n# set a custom DNS domain\ncontainer system property set dns.domain mycompany.local\n\n# configure a custom registry\ncontainer system property set registry.domain registry.example.com\n\n# set a custom builder image\ncontainer system property set image.builder myregistry.com/custom-builder:latest\n```\n\n### `container system property clear`\n\nClears (unsets) a system property, reverting it to its default value.\n\n**Usage**\n\n```bash\ncontainer system property clear [--debug] <id>\n```\n\n**Arguments**\n\n*   `<id>`: The property ID\n\n**Options**\n\nNo options.\n\n**Examples**\n\n```bash\n# clear custom DNS domain (revert to default)\ncontainer system property clear dns.domain\n\n# clear custom registry setting\ncontainer system property clear registry.domain\n"
  },
  {
    "path": "docs/how-to.md",
    "content": "# How-to\n\n> [!IMPORTANT]\n> This file contains documentation for the CURRENT BRANCH. To find documentation for official releases, find the target release on the [Release Page](https://github.com/apple/container/releases) and click the tag corresponding to your release version. \n>\n> Example: [release 0.4.1 tag](https://github.com/apple/container/tree/0.4.1)\n\nHow to use the features of `container`.\n\n## Configure memory and CPUs for your containers\n\nSince the containers created by `container` are lightweight virtual machines, consider the needs of your containerized application when you use `container run`.  The `--memory` and `--cpus` options allow you to override the default memory and CPU limits for the virtual machine. The default values are 1 gigabyte of RAM and 4 CPUs. You can use abbreviations for memory units; for example, to run a container for image `big` with 8 CPUs and 32 GiBytes of memory, use:\n\n```bash\ncontainer run --rm --cpus 8 --memory 32g big\n```\n\n## Configure memory and CPUs for large builds\n\nWhen you first run `container build`, `container` starts a *builder*, which is a utility container that builds images from your `Dockerfile`s. As with anything you run with `container run`, the builder runs in a lightweight virtual machine, so for resource-intensive builds, you may need to increase the memory and CPU limits for the builder VM.\n\nBy default, the builder VM receives 2 GiBytes of RAM and 2 CPUs. You can change these limits by starting the builder container before running `container build`:\n\n```bash\ncontainer builder start --cpus 8 --memory 32g\n```\n\nIf your builder is already running and you need to modify the limits, just stop, delete, and restart the builder:\n\n```bash\ncontainer builder stop\ncontainer builder delete\ncontainer builder start --cpus 8 --memory 32g\n```\n\n## Share host files with your container\n\nWith the `--volume` option of `container run`, you can share data between the host system and one or more containers, and you can persist data across multiple container runs. The volume option allows you to mount a folder on your host to a filesystem path in the container.\n\nThis example mounts a folder named `assets` on your Desktop to the directory `/content/assets` in a container:\n\n<pre>\n% ls -l ~/Desktop/assets\ntotal 8\n-rw-r--r--@ 1 fido  staff  2410 May 13 18:36 link.svg\n% container run --volume ${HOME}/Desktop/assets:/content/assets docker.io/python:alpine ls -l /content/assets\ntotal 4\n-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg\n%\n</pre>\n\nThe argument to `--volume` in the example consists of the full pathname for the host folder and the full pathname for the mount point in the container, separated by a colon.\n\nThe `--mount` option uses a comma-separated `key=value` syntax to achieve the same result:\n\n<pre>\n% container run --mount source=${HOME}/Desktop/assets,target=/content/assets docker.io/python:alpine ls -l /content/assets\ntotal 4\n-rw-r--r-- 1 root root 2410 May 14 01:36 link.svg\n%\n</pre>\n\n## Build and run a multiplatform image\n\nUsing the [project from the tutorial example](tutorial.md#set-up-a-simple-project), you can create an image to use both on Apple silicon Macs and on x86-64 servers.\n\nWhen building the image, just add `--arch` options that direct the builder to create an image supporting both the `arm64` and `amd64` architectures:\n\n```bash\ncontainer build --arch arm64 --arch amd64 --tag registry.example.com/fido/web-test:latest --file Dockerfile .\n```\n\nTry running the command `uname -a` with the `arm64` variant of the image to see the system information that the virtual machine reports:\n\n<pre>\n% container run --arch arm64 --rm registry.example.com/fido/web-test:latest uname -a\nLinux 7932ce5f-ec10-4fbe-a2dc-f29129a86b64 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 aarch64 GNU/Linux\n%\n</pre>\n\nWhen you run the command with the `amd64` architecture, the x86-64 version of `uname` runs under Rosetta translation, so that you will see information for an x86-64 system:\n\n<pre>\n% container run --arch amd64 --rm registry.example.com/fido/web-test:latest uname -a\nLinux c0376e0a-0bfd-4eea-9e9e-9f9a2c327051 6.1.68 #1 SMP Mon Mar 31 18:27:51 UTC 2025 x86_64 GNU/Linux\n%\n</pre>\n\nThe command to push your multiplatform image to a registry is no different than that for a single-platform image:\n\n```bash\ncontainer image push registry.example.com/fido/web-test:latest\n```\n\n## Get container or image details\n\n`container image list` and `container list` provide basic information for all of your images and containers. You can also use `list` and `inspect` commands to print detailed JSON output for one or more resources.\n\nUse the `inspect` command and send the result to the `jq` command to get pretty-printed JSON for the images or containers that you specify:\n\n<pre>\n% container image inspect web-test | jq\n[\n  {\n    \"name\": \"web-test:latest\",\n    \"variants\": [\n      {\n        \"platform\": {\n          \"os\": \"linux\",\n          \"architecture\": \"arm64\"\n        },\n        \"config\": {\n          \"created\": \"2025-05-08T22:27:23Z\",\n          \"architecture\": \"arm64\",\n...\n% container inspect my-web-server | jq\n[\n  {\n    \"status\": \"running\",\n    \"networks\": [\n      {\n        \"address\": \"192.168.64.3/24\",\n        \"gateway\": \"192.168.64.1\",\n        \"hostname\": \"my-web-server.test.\",\n        \"network\": \"default\"\n      }\n    ],\n    \"configuration\": {\n      \"mounts\": [],\n      \"hostname\": \"my-web-server\",\n      \"id\": \"my-web-server\",\n      \"resources\": {\n        \"cpus\": 4,\n        \"memoryInBytes\": 1073741824,\n      },\n...\n</pre>\n\nUse the `list` command with the `--format` option to display information for all images or containers. In this example, the `--all` option shows stopped as well as running containers, and `jq` selects the IP address for each running container:\n\n<pre>\n% container ls --format json --all | jq '.[] | select ( .status == \"running\" ) | [ .configuration.id, .networks[0].address ]'\n[\n  \"my-web-server\",\n  \"192.168.64.3/24\"\n]\n[\n  \"buildkit\",\n  \"192.168.64.2/24\"\n]\n</pre>\n\n## Forward traffic from `localhost` to your container\n\nUse the `--publish` option to forward TCP or UDP traffic from your loopback IP to the container you run. The option value has the form `[host-ip:]host-port:container-port[/protocol]`, where protocol may be `tcp` or `udp`, case insensitive.\n\nIf your container attaches to multiple networks, the ports you publish forward to the IP address of the interface attached to the first network.\n\nTo forward requests from port 8080 on the IPv4 loopback IP to a NodeJS webserver on container port 8000, run:\n\n```bash\ncontainer run -d --rm -p 127.0.0.1:8080:8000 node:latest npx http-server -a :: -p 8000\n```\n\nTest access using `curl`:\n\n```console\n% curl http://127.0.0.1:8080\n<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width\">\n    <title>Index of /</title>\n...\n<br><address>Node.js v25.2.1/ <a href=\"https://github.com/http-party/http-server\">http-server</a> server running @ 127.0.0.1:8080</address>\n</body></html>\n```\n\nTo forward requests from port 8080 on the IPv6 loopback IP to a NodeJS webserver on container port 8000, run:\n\n```bash\ncontainer run -d --rm -p '[::1]:8080:8000' node:latest npx http-server -a :: -p 8000\n```\n\nTest access using `curl`:\n\n```console\n% curl -6 'http://[::1]:8080'\n<!doctype html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width\">\n    <title>Index of /</title>\n...\n<br><address>Node.js v25.2.1/ <a href=\"https://github.com/http-party/http-server\">http-server</a> server running @ [::1]:8080</address>\n</body></html>\n```\n\n## Access a host service from a container\n\nCreate a DNS domain with `--localhost <ipv4-address>` to make a domain used by a container to access a host service. Any IPv4 address can be used as `<ipv4-address>`, which will be assigned to the domain name in container.\n\nChoose an IP address that is least likely to conflict with any networks or reserved IP addresses in your environment. Reasonably safe address ranges include:\n\n- The documentation ranges 192.0.2.0/24, 198.51.100.0/24, and 203.0.113.0/24.\n- The 172.16.0.0/12 private range.\n\nTo connect a host HTTP server from a container, run:\n\n```bash\nmkdir -p /tmp/test; cd /tmp/test; echo \"hello\" > index.html\npython3 -m http.server 8000 --bind 127.0.0.1\n```\n\nCreate a domain for host connection:\n\n```bash\nsudo container system dns create host.container.internal --localhost 203.0.113.113\n```\n\nTest access to the host HTTP server from a container:\n\n```console\n% container run -it --rm alpine/curl curl http://host.container.internal:8000\nhello\n```\n\n## Set a custom MAC address for your container\n\nUse the `mac` option to specify a custom MAC address for your container's network interface. This is useful for:\n- Network testing scenarios requiring predictable MAC addresses\n- Consistent network configuration across container restarts\n\nThe MAC address must be in the format `XX:XX:XX:XX:XX:XX` (with colons or hyphens as separators). Set the two least significant bits of the first octet to `10` (locally signed, unicast address). \n\n```bash\ncontainer run --network default,mac=02:42:ac:11:00:02 ubuntu:latest\n```\n\nTo verify the MAC address is set correctly, run `ip addr show` inside the container:\n\n```console\n% container run --rm --network default,mac=02:42:ac:11:00:02 ubuntu:latest ip addr show eth0\n2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000\n    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff\n    inet 192.168.64.2/24 brd 192.168.64.255 scope global eth0\n       valid_lft forever preferred_lft forever\n```\n\nIf you don't specify a MAC address, `container` will generate one for you. The generated address has a first nibble set to hexadecimal `f` (`fX:XX:XX:XX:XX:XX`) in case you want to minimize the very small chance of conflict between your MAC address and generated addresses. \n\n## Mount your host SSH authentication socket in your container\n\nUse the `--ssh` option to mount the macOS SSH authentication socket into your container, so that you can clone private git repositories and perform other tasks requiring passwordless SSH authentication.\n\nWhen you use `--ssh`, it performs the equivalent of the options `--volume \"${SSH_AUTH_SOCK}:/run/host-services/ssh-auth.sock\" --env SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock\"`. The added benefit of `--ssh` is that when you stop your container, log out, log back in, and restart your container, the system automatically updates the target path for the socket mount to the new value of `SSH_AUTH_SOCK`, so that socket forwarding continues to function.\n\n```console\n% container run -it --rm --ssh alpine:latest sh \n/ # env\nSHLVL=1\nHOME=/root\nTERM=xterm\nPATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nSSH_AUTH_SOCK=/run/host-services/ssh-auth.sock\nPWD=/\n/ # apk add openssh-client\n(1/6) Installing openssh-keygen (10.0_p1-r7)\n(2/6) Installing ncurses-terminfo-base (6.5_p20250503-r0)\n(3/6) Installing libncursesw (6.5_p20250503-r0)\n(4/6) Installing libedit (20250104.3.1-r1)\n(5/6) Installing openssh-client-common (10.0_p1-r7)\n(6/6) Installing openssh-client-default (10.0_p1-r7)\nExecuting busybox-1.37.0-r18.trigger\nOK: 12 MiB in 22 packages\n/ # ssh-add -l\n...auth key output...\n/ # apk add git\n(1/12) Installing brotli-libs (1.1.0-r2)\n(2/12) Installing c-ares (1.34.5-r0)\n(3/12) Installing libunistring (1.3-r0)\n(4/12) Installing libidn2 (2.3.7-r0)\n(5/12) Installing nghttp2-libs (1.65.0-r0)\n(6/12) Installing libpsl (0.21.5-r3)\n(7/12) Installing zstd-libs (1.5.7-r0)\n(8/12) Installing libcurl (8.14.1-r1)\n(9/12) Installing libexpat (2.7.1-r0)\n(10/12) Installing pcre2 (10.43-r1)\n(11/12) Installing git (2.49.1-r0)\n(12/12) Installing git-init-template (2.49.1-r0)\nExecuting busybox-1.37.0-r18.trigger\nOK: 24 MiB in 34 packages\n/ # git clone git@github.com:some-org/some-private-repo.git\nCloning into 'some-private-repo'...\n...\n```\n\n## Create and use a separate isolated network\n\n> [!NOTE]\n> This feature is available on macOS 26 and later.\n\nRunning `container system start` creates a vmnet network named `default` to which your containers will attach unless you specify otherwise.\n\nYou can create a separate isolated network using `container network create`.\n\nThis command creates a network named `foo`:\n\n```bash\ncontainer network create foo\n```\n\nYou can also specify custom IPv4 and IPv6 subnets when creating a network:\n\n```bash\ncontainer network create foo --subnet 192.168.100.0/24 --subnet-v6 fd00:1234::/64\n```\n\nThe `foo` network, the default network, and any other networks you create are isolated from one another. A container on one network has no connectivity to containers on other networks.\n\nRun `container network list` to see the networks that exist:\n\n```console\n% container network list\nNETWORK  STATE    SUBNET\ndefault  running  192.168.64.0/24\nfoo      running  192.168.65.0/24\n%\n```\n\nRun a container that is attached to that network using the `--network` flag:\n\n```console\ncontainer run -d --name my-web-server --network foo --rm web-test\n```\n\nUse `container ls` to see that the container is on the `foo` subnet:\n\n```console\n % container ls\nID             IMAGE            OS     ARCH   STATE    ADDR\nmy-web-server  web-test:latest  linux  arm64  running  192.168.65.2\n```\n\nYou can delete networks that you create once no containers are attached:\n\n```bash\ncontainer stop my-web-server\ncontainer network delete foo\n```\n\nNetworks support both IPv4 and IPv6. When creating a network without explicit subnet options, the system uses default values if configured via system properties (see below), or automatically allocates subnets. The system validates that custom subnets don't overlap with existing networks.\n\n## Configure default network subnets\n\nYou can customize the default IPv4 and IPv6 subnets used for new networks using system properties.\n\n### Set default IPv4 subnet\n\n```bash\ncontainer system property set network.subnet 192.168.100.1/24\n```\n\n### Set default IPv6 prefix\n\n```bash\ncontainer system property set network.subnetv6 fd00:abcd::/64\n```\n\nThese settings apply to networks created without explicit `--subnet` or `--subnet-v6` options.\n\n## View container logs\n\nThe `container logs` command displays the output from your containerized application:\n\n<pre>\n% container run -d --name my-web-server --rm registry.example.com/fido/web-test:latest\nmy-web-server\n% curl http://my-web-server.test\n&lt;!DOCTYPE html>&lt;html>&lt;head>&lt;title>Hello&lt;/title>&lt;/head>&lt;body>&lt;h1>Hello, world!&lt;/h1>&lt;/body>&lt;/html>\n% container logs my-web-server\n192.168.64.1 - - [15/May/2025 03:00:03] \"GET / HTTP/1.1\" 200 -\n%\n</pre>\n\nUse the `--boot` option to see the logs for the virtual machine boot and init process:\n\n<pre>\n% container logs --boot my-web-server\n[    0.098284] cacheinfo: Unable to detect cache hierarchy for CPU 0\n[    0.098466] random: crng init done\n[    0.099657] brd: module loaded\n[    0.100707] loop: module loaded\n[    0.100838] virtio_blk virtio2: 1/0/0 default/read/poll queues\n[    0.101051] virtio_blk virtio2: [vda] 1073741824 512-byte logical blocks (550 GB/512 GiB)\n...\n[    0.127467] EXT4-fs (vda): mounted filesystem without journal. Quota mode: disabled.\n[    0.127525] VFS: Mounted root (ext4 filesystem) readonly on device 254:0.\n[    0.127635] devtmpfs: mounted\n[    0.127773] Freeing unused kernel memory: 2816K\n[    0.143252] Run /sbin/vminitd as init process\n2025-05-15T02:24:08+0000 info vminitd : [vminitd] vminitd booting...\n2025-05-15T02:24:08+0000 info vminitd : [vminitd] serve vminitd api\n2025-05-15T02:24:08+0000 debug vminitd : [vminitd] starting process supervisor\n2025-05-15T02:24:08+0000 debug vminitd : port=1024 [vminitd] booting grpc server on vsock\n...\n2025-05-15T02:24:08+0000 debug vminitd : exits=[362: 0] pid=363 [vminitd] checking for exit of managed process\n2025-05-15T02:24:08+0000 debug vminitd : [vminitd] waiting on process my-web-server\n[    1.122742] IPv6: ADDRCONF(NETDEV_CHANGE): eth0: link becomes ready\n2025-05-15T02:24:39+0000 debug vminitd : sec=1747275879 usec=478412 [vminitd] setTime\n%\n</pre>\n\n## Monitor container resource usage\n\nThe `container stats` command displays real-time resource usage statistics for your running containers, similar to the `top` command for processes. This is useful for:\n- Monitoring CPU and memory consumption\n- Tracking network and disk I/O\n- Identifying resource-intensive containers\n- Verifying container resource limits are appropriate\n\nBy default, `container stats` shows live statistics for all running containers in an interactive display:\n\n```console\n% container stats\nContainer ID    Cpu %    Memory Usage           Net Rx/Tx              Block I/O               Pids\nmy-web-server   2.45%    45.23 MiB / 1.00 GiB   1.23 MiB / 856.00 KiB  4.50 MiB / 2.10 MiB     3\ndb              125.12%  512.50 MiB / 2.00 GiB  5.67 MiB / 3.21 MiB    125.00 MiB / 89.00 MiB  12\n```\n\nTo monitor specific containers, provide their names or IDs:\n\n```console\n% container stats my-web-server db\n```\n\nFor a single snapshot (non-interactive), use the `--no-stream` flag:\n\n```console\n% container stats --no-stream my-web-server\nContainer ID    Cpu %    Memory Usage          Net Rx/Tx              Block I/O              Pids\nmy-web-server   30.45%    45.23 MiB / 1.00 GiB  1.23 MiB / 856.00 KiB  4.50 MiB / 2.10 MiB    3\n```\n\nYou can also output statistics in JSON format for scripting:\n\n```console\n% container stats --format json --no-stream my-web-server | jq\n[\n  {\n    \"id\": \"my-web-server\",\n    \"memoryUsageBytes\": 47431680,\n    \"memoryLimitBytes\": 1073741824,\n    \"cpuUsageUsec\": 1234567,\n    \"networkRxBytes\": 1289011,\n    \"networkTxBytes\": 876544,\n    \"blockReadBytes\": 4718592,\n    \"blockWriteBytes\": 2202009,\n    \"numProcesses\": 3\n  }\n]\n```\n\n**Understanding the metrics:**\n\n- **Cpu %**: Percentage of CPU usage. ~100% = one fully utilized core. A multi-core container can show > 100%.\n- **Memory Usage**: Current memory usage vs. the container's memory limit.\n- **Net Rx/Tx**: Network bytes received and transmitted.\n- **Block I/O**: Disk bytes read and written.\n- **Pids**: Number of processes running in the container.\n\n## Expose virtualization capabilities to a container\n\n> [!NOTE]\n> This feature requires a M3 or newer Apple silicon machine and a Linux kernel that supports virtualization. For a kernel configuration that has all of the right features enabled, see https://github.com/apple/containerization/blob/0.5.0/kernel/config-arm64#L602.\n\nYou can enable virtualization capabilities in containers by using the `--virtualization` option of `container run` and `container create`.\n\nIf your machine does not have support for nested virtualization, you will see the following:\n\n```console\ncontainer run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c \"dmesg | grep kvm\"\nError: unsupported: \"nested virtualization is not supported on the platform\"\n```\n\nWhen nested virtualization is enabled successfully, `dmesg` will show output like the following:\n\n```console\ncontainer run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c \"dmesg | grep kvm\"\n[    0.017245] kvm [1]: IPA Size Limit: 40 bits\n[    0.017499] kvm [1]: GICv3: no GICV resource entry\n[    0.017501] kvm [1]: disabling GICv2 emulation\n[    0.017506] kvm [1]: GIC system register CPU interface enabled\n[    0.017685] kvm [1]: vgic interrupt IRQ9\n[    0.017893] kvm [1]: Hyp mode initialized successfully\n```\n\n## Run a container with a provided init process\n\nBy default, the command you specify in `container run` runs as PID 1 inside the container. This means it is responsible for reaping zombie processes and handling signals, which many applications are not designed to do. The `--init` flag runs a lightweight init process as PID 1 that automatically forwards signals and reaps orphaned child processes.\n\n```bash\ncontainer run --init ubuntu:latest my-app\n```\n\nThe init process is also available with `container create`:\n\n```bash\ncontainer create --init --name my-container ubuntu:latest my-app\ncontainer start my-container\n```\n\n## Use a custom init image\n\nThe `--init-image` flag allows you to specify a custom init filesystem image for the lightweight VM that runs your container. This enables:\n\n- Custom boot-time logic before the OCI container starts\n- Running additional processes and daemons (e.g., eBPF network filters, logging agents) inside the VM\n- Debugging or instrumenting the init process\n\n### Create a custom init image\n\nA custom init image wraps the default `vminitd` binary, allowing you to run custom logic before handing off to the standard init process.\n\n**1. Create a wrapper binary (example in Go for easy cross-compilation):**\n\n```go\n// wrapper.go\npackage main\n\nimport (\n    \"os\"\n    \"syscall\"\n)\n\nfunc main() {\n    // Write a message to kernel log\n    kmsg, err := os.OpenFile(\"/dev/kmsg\", os.O_WRONLY, 0)\n    if err == nil {\n        kmsg.WriteString(\"<6>custom-init: === CUSTOM INIT IMAGE RUNNING ===\\n\")\n        kmsg.Close()\n    }\n\n    // Execute the real vminitd\n    err = syscall.Exec(\"/sbin/vminitd.real\", os.Args, os.Environ())\n    if err != nil {\n        os.Exit(1)\n    }\n}\n```\n\n**2. Build the wrapper for Linux arm64:**\n\n```bash\nCGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o wrapper wrapper.go\n```\n\n**3. Create a Containerfile:**\n\n```dockerfile\nFROM ghcr.io/apple/containerization/vminit:latest AS base\n\nFROM ghcr.io/apple/containerization/vminit:latest\nCOPY --from=base /sbin/vminitd /sbin/vminitd.real\nCOPY wrapper /sbin/vminitd\n```\n\n**4. Build the custom init image:**\n\n```bash\ncontainer build -t local/custom-init:latest .\n```\n\n### Run a container with a custom init image\n\n```bash\ncontainer run --name my-container --init-image local/custom-init:latest alpine:latest echo \"hello\"\n```\n\n### Verify the custom init is running\n\nCheck the VM boot logs to confirm your custom init code executed:\n\n```console\n% container logs --boot my-container | grep custom-init\n[    0.129230] custom-init: === CUSTOM INIT IMAGE RUNNING ===\n```\n\n## Configure system properties\n\nThe `container system property` subcommand manages the configuration settings for the `container` CLI and services. You can customize various aspects of container behavior, including build settings, default images, and network configuration.\n\nUse `container system property list` to show information for all available properties:\n\n```console\n% bin/container system property ls\nID                 TYPE    VALUE                                     DESCRIPTION\nbuild.rosetta      Bool    true                                      Build amd64 images on arm64 using Rosetta, instead of QEMU.\ndns.domain         String  *undefined*                               If defined, the local DNS domain to use for containers with unqualified names.\nimage.builder      String  ghcr.io/apple/container-builder-shim/...  The image reference for the utility container that `container build` uses.\nimage.init         String  ghcr.io/apple/containerization/vminit...  The image reference for the default initial filesystem image.\nkernel.binaryPath  String  opt/kata/share/kata-containers/vmlinu...  If the kernel URL is for an archive, the archive member pathname for the kernel file.\nkernel.url         String  https://github.com/kata-containers/ka...  The URL for the kernel file to install, or the URL for an archive containing the kernel file.\nnetwork.subnet     String  *undefined*                               Default subnet for IPv4 allocation.\nnetwork.subnetv6   String  *undefined*                               Default IPv6 network prefix.\n```\n\n### Example: Disable Rosetta for builds\n\nIf you want to prevent the use of Rosetta translation during container builds on Apple Silicon Macs:\n\n```bash\ncontainer system property set build.rosetta false\n```\n\nThis is useful when you want to ensure builds only produce native arm64 images and avoid any x86_64 emulation.\n\n## View system logs\n\nThe `container system logs` command allows you to look at the log messages that `container` writes:\n\n<pre>\n% container system logs | tail -8\n2025-06-02 16:46:11.560780-0700 0xf6dc5    Info        0x0                  61684  0    container-apiserver: [com.apple.container:APIServer] Registering plugin [id=com.apple.container.container-runtime-linux.my-web-server]\n2025-06-02 16:46:11.699095-0700 0xf6ea8    Info        0x0                  61733  0    container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting container-runtime-linux [uuid=my-web-server]\n2025-06-02 16:46:11.699125-0700 0xf6ea8    Info        0x0                  61733  0    container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] configuring XPC server [uuid=my-web-server]\n2025-06-02 16:46:11.700908-0700 0xf6ea8    Info        0x0                  61733  0    container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] starting XPC server [uuid=my-web-server]\n2025-06-02 16:46:11.703028-0700 0xf6ea8    Info        0x0                  61733  0    container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `bootstrap` xpc handler [uuid=my-web-server]\n2025-06-02 16:46:11.720836-0700 0xf6dc3    Info        0x0                  61689  0    container-network-vmnet: [com.apple.container:NetworkVmnetHelper] allocated attachment [hostname=my-web-server.test.] [address=192.168.64.2/24] [gateway=192.168.64.1] [id=default]\n2025-06-02 16:46:12.293193-0700 0xf6eaa    Info        0x0                  61733  0    container-runtime-linux: [com.apple.container:RuntimeLinuxHelper] `start` xpc handler [uuid=my-web-server]\n2025-06-02 16:46:12.368723-0700 0xf6e93    Info        0x0                  61684  0    container-apiserver: [com.apple.container:APIServer] Handling container my-web-server Start.\n%\n</pre>\n\n## Generating and installing completion scripts\n\n### Overview\n\nThe `container --generate-completion-script [zsh|bash|fish]` command generates completion scripts for the provided shell. Below is a detailed guide on how to install the completion scripts.\n\n> [!NOTE]\n> See the [swift-argument-parser documentation](https://apple.github.io/swift-argument-parser/documentation/argumentparser/installingcompletionscripts/#Installing-Zsh-Completions) for more information about generating and installing shell completion scripts.\n\n### Installing `zsh` completions\n\nIf you have [oh-my-zsh](https://ohmyz.sh/) installed, you already have a directory of automatically loaded completion scripts — `.oh-my-zsh/completions`. Copy your new completion script to that directory. If the `completions` directory does not exist, simply make it.\n\n```zsh\nmkdir -p ~/.oh-my-zsh/completions\ncontainer --generate-completion-script zsh > ~/.oh-my-zsh/completions/_container\nsource ~/.oh-my-zsh/completions/_container\n```\n\n> [!NOTE]\n> Your completion script must have the filename `_container`.\n\nWithout oh-my-zsh, you’ll need to add a path for completion scripts to your function path, and turn on completion script autoloading. First, add these lines to your `~/.zshrc` file:\n\n```bash\nfpath=(~/.zsh/completion $fpath)\nautoload -U compinit\ncompinit\n```\n\nNext, create a directory at `~/.zsh/completion` and copy the completion script to the new directory.\n\n```zsh\nmkdir -p ~/.zsh/completion\ncontainer --generate-completion-script zsh > ~/.zsh/completion/_container\nsource ~/.zshrc\n```\n\n### Installing `bash` completions\n\nIf you have [bash-completion](https://github.com/scop/bash-completion) installed, you can just copy your new completion script to the `bash_completion.d` directory.\n\n> [!NOTE]\n> The path to the directory is dependent on how bash-completion was installed. Find the correct path and then copy the completion script there. For example, if you used homebrew to install `bash-completion`:\n>  ```bash\n>  container --generate-completion-script bash > /opt/homebrew/etc/bash_completion.d/container\n>  source /opt/homebrew/etc/bash_completion.d/container\n>  ```\n\nWithout bash-completion, you’ll need to source the completion script directly. Create and copy it to a directory such as `~/.bash_completions`. \n\n```bash\nmkdir -p ~/.bash_completions\ncontainer --generate-completion-script bash >  ~/.bash_completions/container\nsource ~/.bash_completions/container\n```\n\nFurthermore, you can add the following line to `~/.bash_profile` or `~/.bashrc`, in order for every new bash session to have autocompletion ready.\n\n```bash\nsource ~/.bash_completions/container\n```\n\n### Installing `fish` completions\n\nCopy the completion script to any path listed in the environment variable `$fish_completion_path`.\n\n```bash\ncontainer --generate-completion-script fish > ~/.config/fish/completions/container.fish\n```\n"
  },
  {
    "path": "docs/technical-overview.md",
    "content": "# Technical Overview\n\n> [!IMPORTANT]\n> This file contains documentation for the CURRENT BRANCH. To find documentation for official releases, find the target release on the [Release Page](https://github.com/apple/container/releases) and click the tag corresponding to your release version. \n>\n> Example: [release 0.4.1 tag](https://github.com/apple/container/tree/0.4.1)\n\nA brief description and technical overview of `container`.\n\n## What are containers?\n\nContainers are a way to package an application and its dependencies into a single unit.  At runtime, containers provide isolation from the host machine as well as other colocated containers, allowing applications to run securely and efficiently in a wide variety of environments.\n\nContainerization is an important server-side technology that is used throughout the software lifecycle:\n\n- Backend developers use containers on their personal systems to create predictable execution environments for applications, and to develop and test their applications under conditions that better approximate how they would run in the datacenter.\n- Continuous integration and deployment (CI/CD) systems use containerization to perform reproducible builds of applications, package the results as deployable images, and deploy them to the datacenter.\n- Datacenters run container orchestration platforms that use the images to run containerized applications in a reliable, highly available compute cluster.\n\nNone of this workflow would be practical without ensuring interoperability between different container implementations. The Open Container Initiative (OCI) creates and maintains these standards for container images and runtimes.\n\n## How does `container` run my container?\n\nMany operating systems support containers, but the most commonly encountered containers are those that run on the Linux operating system. With macOS, the typical way to run Linux containers is to launch a Linux virtual machine (VM) that hosts all of your containers.\n\n`container` runs containers differently. Using the open source [Containerization](https://github.com/apple/containerization) package, it runs a lightweight VM for each container that you create. This approach has the following properties:\n\n- Security: Each container has the isolation properties of a full VM, using a minimal set of core utilities and dynamic libraries to reduce resource utilization and attack surface.\n- Privacy: When sharing host data using `container`, you mount only necessary data into each VM. With a shared VM, you need to mount all data that you may ever want to use into the VM, so that it can be mounted selectively into containers.\n- Performance: Containers created using `container` require less memory than full VMs, with boot times that are comparable to containers running in a shared VM.\n\nSince `container` consumes and produces standard OCI images, you can easily build with and run images produced by other container applications, and the images that you build will run everywhere.\n\n`container` and the underlying Containerization package integrate with many of the key technologies and frameworks of macOS:\n\n- The Virtualization framework for managing Linux virtual machines and their attached devices.\n- The vmnet framework for managing the virtual network to which the containers attach.\n- XPC for interprocess communication.\n- Launchd for service management.\n- Keychain services for access to registry credentials.\n- The unified logging system for application logging.\n\nYou use the `container` command line interface (CLI) to start and manage your containers, build container images, and transfer images from and to OCI container registries. The CLI uses a client library that communicates with `container-apiserver` and its helpers.\n\nThe `container-apiserver` is a launch agent that launches when you run the `container system start` command, and terminates when you run `container system stop`. It provides the client APIs for managing container and network resources.\n\nWhen `container-apiserver` starts, it launches an XPC helper `container-core-images` that exposes an API for image management and manages the local content store, and another XPC helper `container-network-vmnet` for the virtual network. For each container that you create, `container-apiserver` launches a container runtime helper `container-runtime-linux` that exposes the management API for that specific container.\n\n![diagram showing `container` functional organization](/docs/assets/functional-model-light.svg)\n\n## What limitations does `container` have today?\n\nWith the initial release of `container`, you get basic facilities for building and running containers, but many common containerization features remain to be implemented. Consider [contributing](../CONTRIBUTING.md) new features and bug fixes to `container` and the Containerization projects!\n\n### Releasing container memory to macOS\n\nThe macOS Virtualization framework implements only partial support for memory ballooning, which is a technology that allows virtual machines to dynamically use and relinquish host memory. When you create a container, the underlying virtual machine only uses the amount of memory that the containerized application needs. For example, you might start a container using the option `--memory 16g`, but see that the application is only using 2 GiBytes of RAM in the macOS Activity Monitor.\n\nCurrently, memory pages freed to the Linux operating system by processes running in the container's VM are not relinquished to the host. If you run many memory-intensive containers, you may need to occasionally restart them to reduce memory utilization.\n\n### macOS 15 limitations\n\n`container` relies on the new features and enhancements present in macOS 26. You can run `container` on macOS 15, but you will need to be aware of some user experience and functional limitations. There is no plan to address issues found with macOS 15 that cannot be reproduced on macOS 26.\n\n#### Network isolation\n\nThe vmnet framework in macOS 15 can only provide networks where the attached containers are isolated from one another. Container-to-container communication over the virtual network is not possible.\n\n#### Multiple networks\n\nIn macOS 15, all containers attach to the default vmnet network. The `container network` commands are not available on macOS 15, and using the `--network` option for `container run` or `container create` will result in an error.\n\n#### Container IP addresses\n\nIn macOS 15, limitations in the vmnet framework mean that the container network can only be created when the first container starts. Since the network XPC helper provides IP addresses to containers, and the helper has to start before the first container, it is possible for the network helper and vmnet to disagree on the subnet address, resulting in containers that are completely cut off from the network.\n\nNormally, vmnet creates the container network using the CIDR address 192.168.64.1/24, and on macOS 15, `container` defaults to using this CIDR address in the network helper. To diagnose and resolve issues stemming from a subnet address mismatch between vmnet and the network helper:\n\n- Before creating the first container, scan the output of the command `ifconfig` for a bridge interface named similarly to `bridge100`.\n- After creating the first container, run `ifconfig` again, and locate the new bridge interface to determine the container subnet address.\n- Run `container ls` to check the IP address given to the container by the network helper. If the address corresponds to a different network:\n  - Run `container system stop` to terminate the services for `container`.\n  - Using the macOS `defaults` command, update the default subnet value used by the network helper process. For example, if the bridge address shown by `ifconfig` is 192.168.66.1, run:\n    ```bash\n    defaults write com.apple.container.defaults network.subnet 192.168.66.1/24\n    ```\n  - Run `container system start` to launch services again.\n  - Try running the container again and verify that its IP address matches the current bridge interface value.\n"
  },
  {
    "path": "docs/tutorial.md",
    "content": "# Tutorial\n\n> [!IMPORTANT]\n> This file contains documentation for the CURRENT BRANCH. To find documentation for official releases, find the target release on the [Release Page](https://github.com/apple/container/releases) and click the tag corresponding to your release version. \n>\n> Example: [release 0.4.1 tag](https://github.com/apple/container/tree/0.4.1)\n\nTake a guided tour of `container` by building, running, and publishing a simple web server image.\n\n## Try out the `container` CLI\n\nStart the application, and try out some basic commands to familiarize yourself with the command line interface (CLI) tool.\n\n### Start the container service\n\nStart the services that `container` uses:\n\n```bash\ncontainer system start\n```\n\nIf you have not installed a Linux kernel yet, the command will prompt you to install one:\n\n<pre>\n% container system start\n\nVerifying apiserver is running...\nInstalling base container filesystem...\nNo default kernel configured.\nInstall the recommended default kernel from [https://github.com/kata-containers/kata-containers/releases/download/3.17.0/kata-static-3.17.0-arm64.tar.xz]? [Y/n]: y\nInstalling kernel...\n%\n</pre>\n\nThen, verify that the application is working by running a command to list all containers:\n\n```bash\ncontainer list --all\n```\n\nIf you haven't created any containers yet, the command outputs an empty list:\n\n<pre>\n% container list --all\nID  IMAGE  OS  ARCH  STATE  ADDR\n%\n</pre>\n\n### Get CLI help\n\nYou can get help for any `container` CLI command by appending the `--help` option:\n\n<pre>\n% container --help\nOVERVIEW: A container platform for macOS\n\nUSAGE: container [--debug] <subcommand>\n\nOPTIONS:\n  --debug                 Enable debug output [environment: CONTAINER_DEBUG]\n  --version               Show the CLI version (single line).\n  -h, --help              Show help information.\n\nDetailed version information is available under the system command:\n\n```\ncontainer system version [--format json|table]\n```\n\nCONTAINER SUBCOMMANDS:\n  create                  Create a new container\n  delete, rm              Delete one or more containers\n  exec                    Run a new command in a running container\n  inspect                 Display information about one or more containers\n  kill                    Kill one or more running containers\n  list, ls                List containers\n  logs                    Fetch container stdio or boot logs\n  run                     Run a container\n  start                   Start a container\n  stop                    Stop one or more running containers\n\nIMAGE SUBCOMMANDS:\n  build                   Build an image from a Dockerfile\n  image, i                Manage images\n  registry, r             Manage registry configurations\n\nSYSTEM SUBCOMMANDS:\n  builder                 Manage an image builder instance\n  system, s               Manage system components\n\n%\n</pre>\n\n### Abbreviations\n\nYou can save keystrokes by abbreviating commands and options. For example, abbreviate the `container list` command to `container ls`, and the `--all` option to `-a`:\n\n<pre>\n% container ls -a\nID  IMAGE  OS  ARCH  STATE  ADDR\n%\n</pre>\n\nUse the `--help` flag to see which abbreviations exist.\n\n### Set up a local DNS domain (optional)\n\n`container` includes an embedded DNS service that simplifies access to your containerized applications. If you want to configure a local DNS domain named `test` for this tutorial, run:\n\n```bash\nsudo container system dns create test\ncontainer system property set dns.domain test\n```\n\nEnter your administrator password when prompted. The first command requires administrator privileges to create a file containing the domain configuration under the `/etc/resolver` directory, and to tell the macOS DNS resolver to reload its configuration files.\n\nThe second command makes `test` the default domain to use when running a container with an unqualified name. For example, if the default domain is `test` and you use `--name my-web-server` to start a container, queries to `my-web-server.test` will respond with that container's IP address.\n\n## Build an image\n\nSet up a `Dockerfile` for a basic Python web server, and use it to build a container image named `web-test`.\n\n### Set up a simple project\n\nStart a terminal, create a directory named `web-test` for the files needed to create the container image:\n\n```bash\nmkdir web-test\ncd web-test\n```\n\nIn the `web-test` directory, create a file named `Dockerfile` with this content:\n\n```dockerfile\nFROM docker.io/python:alpine\nWORKDIR /content\nRUN apk add curl\nRUN echo '<!DOCTYPE html><html><head><title>Hello</title></head><body><h1>Hello, world!</h1></body></html>' > index.html\nCMD [\"python3\", \"-m\", \"http.server\", \"80\", \"--bind\", \"0.0.0.0\"]\n```\n\nThe `FROM` line instructs the `container` builder to start with a base image containing the latest production version of Python 3.\n\nThe `WORKDIR` line creates a directory `/content` in the image, and makes it the current directory.\n\nThe first `RUN` line adds the `curl` command to your image, and the second `RUN` line creates a simple HTML landing page named `/content/index.html`.\n\nThe `CMD` line configures the container to run a simple web server in Python on port 80. Since the working directory is `/content`, the web server runs in that directory and delivers the content of the file `/content/index.html` when a user requests the index page URL.\n\nThe server listens on the wildcard address `0.0.0.0` to allow connections from the host and other containers. You can safely use the listen address `0.0.0.0` inside the container, because external systems have no access to the virtual network to which the container attaches.\n\n### Build the web server image\n\nRun the `container build` command to create an image with the name `web-test` from your `Dockerfile`:\n\n```bash\ncontainer build --tag web-test --file Dockerfile .\n```\n\nThe last argument `.` tells the builder to use the current directory (`web-test`) as the root of the build context. You can copy files within the build context into your image using the `COPY` command in your Dockerfile.\n\nAfter the build completes, list the images. You should see both the base image and the image that you built in the results:\n\n<pre>\n% container image list\nNAME      TAG     DIGEST\npython    alpine  b4d299311845147e7e47c970...\nweb-test  latest  25b99501f174803e21c58f9c...\n%\n</pre>\n\n## Run containers\n\nUsing your container image, run a web server and try out different ways of interacting with it.\n\n### Start the webserver\n\nUse `container run` to start a container named `my-web-server` that runs your webserver:\n\n```bash\ncontainer run --name my-web-server --detach --rm web-test\n```\n\nThe `--detach` flag runs the container in the background, so that you can continue running commands in the same terminal. The `--rm` flag causes the container to be removed automatically after it stops.\n\nWhen you list containers now, `my-web-server` is present, along with the container that `container` started to build your image. Note that its IP address, shown in the `ADDR` column, is `192.168.64.3`:\n\n<pre>\n% container ls\nID             IMAGE                                               OS     ARCH   STATE    ADDR\nbuildkit       ghcr.io/apple/container-builder-shim/builder:0.0.3  linux  arm64  running  192.168.64.2\nmy-web-server  web-test:latest                                     linux  arm64  running  192.168.64.3\n%\n</pre>\n\nOpen the website, using the container's IP address in the URL:\n\n```bash\nopen http://192.168.64.3\n```\n\nIf you configured the local domain `test` earlier in the tutorial, you can also open the page with the full hostname for the container:\n\n```bash\nopen http://my-web-server.test\n```\n\n### Monitor container resource usage\n\nNow that your web server is running, you can monitor its resource usage with the `container stats` command:\n\n```bash\ncontainer stats my-web-server\n```\n\nThis displays real-time statistics about CPU usage, memory consumption, network traffic, disk I/O, and the number of running processes:\n\n<pre>\n% container stats --no-stream my-web-server\nContainer ID    Cpu %   Memory Usage          Net Rx/Tx            Block I/O            Pids\nmy-web-server   0.23%   12.45 MiB / 1.00 GiB  856.00 KiB / 1.2 KiB 2.10 MiB / 512 KiB   2\n%\n</pre>\n\n> [!NOTE]\n> Without the `--no-stream` flag, `container stats` continuously updates the display in real-time, similar to the `top` command. Press Ctrl+C to exit the live view.\n\n### Run other commands in the container\n\nYou can run other commands in `my-web-server` by using the `container exec` command. To list the files under the content directory, run an `ls` command:\n\n<pre>\n% container exec my-web-server ls /content\nindex.html\n%\n</pre>\n\nIf you want to poke around in the container, run a shell and issue one or more commands:\n\n<pre>\n% container exec --tty --interactive my-web-server sh\n/content # ls\nindex.html\n/content # uname -a\nLinux my-web-server 6.12.28 #1 SMP Tue May 20 15:19:05 UTC 2025 aarch64 Linux\n/content # exit\n%\n</pre>\n\nThe `--tty` and `--interactive` flag allow you to interact with the shell from your host terminal. The `--tty` flag tells the shell in the container that its input is a terminal device, and the `--interactive` flag connects what you input in your host terminal to the input of the shell in the container.\n\nYou will often see these two options abbreviated and specified together as `-ti` or `-it`.\n\n### Access the web server from another container\n\nYour web server is accessible from other containers as well as from your host. Launch a second container using your `web-test` image, and this time, specify a `curl` command to retrieve the `index.html` content from the first container.\n\n> [!NOTE]\n> Container relies on the new features and enhancements present in macOS 26.\n> As a result, the functionality of accessing the web server from another container will not work on macOS 15.\n> See https://github.com/apple/container/blob/main/docs/technical-overview.md#macos-15-limitations for more details.\n\n```bash\ncontainer run -it --rm web-test curl http://192.168.64.3\n```\n\nThe output should appear as:\n\n<pre>\n% container run -it --rm web-test curl http://192.168.64.3\n&lt;!DOCTYPE html>&lt;html>&lt;head>&lt;title>Hello&lt;/title>&lt;/head>&lt;body>&lt;h1>Hello, world!&lt;/h1>&lt;/body>&lt;/html>\n%\n</pre>\n\nIf you set up the `test` domain earlier, you can achieve the same result with:\n\n```bash\ncontainer run -it --rm web-test curl http://my-web-server.test\n```\n\n## Run a published image\n\nPush your image to a container registry, publishing it so that you and others can use it.\n\n### Publish the web server image\n\nTo publish your image, you need to push images to a registry service that stores the image for future use. Typically, you need to authenticate with a registry to push an image. This example assumes that you have an account at a hypothetical registry named `some-registry.example.com` with username `fido` and a password or token `my-secret`, and that your personal repository name is the same as your username.\n\nTo sign into a secure registry with your login credentials, enter your username and password at the prompts after running:\n\n```bash\ncontainer registry login some-registry.example.com\n```\n\nCreate another name for your image that includes the registry name, your repository name, and the image name, with the tag `latest`:\n\n```bash\ncontainer image tag web-test some-registry.example.com/fido/web-test:latest\n```\n\nThen, push the image:\n\n```bash\ncontainer image push some-registry.example.com/fido/web-test:latest\n```\n\n> [!NOTE]\n> By default `container` is configured to use Docker Hub.\n> You can change the default registry to another value by running `container system property set registry.domain some-registry.example.com`.\n> See the other sub commands under `container registry` for more options.\n\n### Pull and run your image\n\nTo validate your published image, stop your current web server container, remove the image that you built, and then run using the remote image:\n\n```bash\ncontainer stop my-web-server\ncontainer image delete web-test some-registry.example.com/fido/web-test:latest\ncontainer run --name my-web-server --detach --rm some-registry.example.com/fido/web-test:latest\n```\n\n## Clean up\n\nStop your container and shut down the application.\n\n### Shut down the web server\n\nStop your web server container with:\n\n```bash\ncontainer stop my-web-server\n```\n\nIf you list all running and stopped containers, you will see that the `--rm` flag you supplied with the `container run` command caused the container to be removed:\n\n<pre>\n% container list --all\nID        IMAGE                                               OS     ARCH   STATE    ADDR\nbuildkit  ghcr.io/apple/container-builder-shim/builder:0.0.3  linux  arm64  running  192.168.64.2\n%\n</pre>\n\n### Stop the container service\n\nWhen you want to stop `container` completely, run:\n\n```bash\ncontainer system stop\n```\n"
  },
  {
    "path": "licenserc.toml",
    "content": "additionalHeaders = [\"scripts/container-header-style.toml\"]\n\nheaderPath = \"scripts/license-header.txt\"\n\nincludes = [\n    \"Makefile\",\n    \"*.Makefile\",\n    \"*.swift\",\n    \"*.h\",\n    \"*.cpp\",\n    \"*.c\",\n    \"*.sh\",\n]\n\nexcludes = []\n\n[git]\nattrs = 'enable'\nignore = 'enable'\n\n[properties]\ncopyrightOwner = \"Apple Inc. and the container project authors\"\n\n[mapping.SWIFT_STYLE]\nextensions = [\"swift\", \"c\", \"h\"]\n"
  },
  {
    "path": "scripts/container-header-style.toml",
    "content": "[SWIFT_STYLE]\nfirstLine = '//===----------------------------------------------------------------------===//'\nendLine = \"//===----------------------------------------------------------------------===//\\n\"\nbeforeEachLine = '// '\nafterEachLine = ''\nallowBlankLines = false\nmultipleLines = true\npadLines = false\nfirstLineDetectionPattern = '//\\s?==='\nlastLineDetectionPattern = '//\\s?==='\nskipLinePattern = '// swift-tools-version'\n"
  },
  {
    "path": "scripts/ensure-container-stopped.sh",
    "content": "#! /bin/bash -f\n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nALL_DOMAINS=false\n\nusage() {\n    echo \"Usage: $0 [-a] [-h]\"\n    echo \"Stop container services\"\n    echo\n    echo \"Options:\"\n    echo \"a     Stop container services in all launchd domains.\"\n    echo \"h     Show this help message.\"\n    echo\n    exit 1\n}\n\nwhile getopts \":ah\" arg; do\n    case \"$arg\" in\n        a)\n            ALL_DOMAINS=true\n            ;;\n        h)\n            usage\n            ;;\n        *)\n            echo \"Invalid option: -${OPTARG}\"\n            usage\n            ;;\n    esac\ndone\n\nif $ALL_DOMAINS; then\n    uid=$(id -u)\n    for domain in \"gui/$uid\" \"user/$uid\" \"system\"; do\n        launchctl print \"$domain\" 2>/dev/null \\\n            | grep -oE 'com\\.apple\\.container\\.[^ ]+' \\\n            | sort -u \\\n            | while read -r service; do\n                launchctl bootout \"$domain/$service\"\n            done\n    done\nelse\n    domain_string=\"\"\n\n    launchd_domain=$(launchctl managername)\n\n    if [[ \"$launchd_domain\" == \"System\" ]]; then\n      domain_string=\"system\"\n    elif [[ \"$launchd_domain\" == \"Aqua\" ]]; then\n      domain_string=\"gui/$(id -u)\"\n    elif [[ \"$launchd_domain\" == \"Background\" ]]; then\n      domain_string=\"user/$(id -u)\"\n    else\n        echo \"Unsupported launchd domain. Exiting\"\n        exit 1\n    fi\n\n    launchctl list | grep -e 'com\\.apple\\.container\\W' | awk '{print $3}' | xargs -I % launchctl bootout $domain_string/%\nfi\n"
  },
  {
    "path": "scripts/ensure-hawkeye-exists.sh",
    "content": "#!/usr/bin/env bash\n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\necho \"Checking existence of hawkeye...\"\n\nif command -v .local/bin/hawkeye >/dev/null 2>&1; then\n    echo \"hawkeye found!\"\nelse\n    echo \"hawkeye not found in PATH\"\n    echo \"please install hawkeye. For convenience, you can run scripts/install-hawkeye.sh\"\n    exit 1\nfi\n"
  },
  {
    "path": "scripts/install-hawkeye.sh",
    "content": "#!/usr/bin/env bash \n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nif command -v .local/bin/hawkeye >/dev/null 2>&1; then\n    echo \"hawkeye already installed\"\nelse\n    echo \"Installing hawkeye\"\n    export VERSION=v6.1.0\n    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\nfi\n"
  },
  {
    "path": "scripts/install-init.sh",
    "content": "#! /bin/bash -e\n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [-a APP_ROOT | --app-root APP_ROOT] [-l LOG_ROOT | --log-root LOG_ROOT] [-h | --help]\n\nInstall the init image for container system.\n\nOptions:\n    -a, --app-root APP_ROOT    Install the init image under the APP_ROOT path\n    -l, --log-root LOG_ROOT    Install the init image under the LOG_ROOT path\n    -h, --help                 Show this help message\n\nEOF\n    exit 0\n}\n\n# Parse command line options\nSTART_ARGS=()\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        -a|--app-root)\n            if [[ -z \"$2\" || \"$2\" == -* ]]; then\n                echo \"Option $1 requires an argument.\" >&2\n                usage\n            fi\n            START_ARGS+=(--app-root \"$2\")\n            shift 2\n            ;;\n        -l|--log-root)\n            if [[ -z \"$2\" || \"$2\" == -* ]]; then\n                echo \"Option $1 requires an argument.\" >&2\n                usage\n            fi\n            START_ARGS+=(--log-root \"$2\")\n            shift 2\n            ;;\n        -h|--help)\n            usage\n            ;;\n        *)\n            echo \"Invalid option: $1\" >&2\n            usage\n            ;;\n    esac\ndone\n\nSWIFT=\"/usr/bin/swift\"\nIMAGE_NAME=\"vminit:latest\"\n\nCONTAINERIZATION_VERSION=\"$(${SWIFT} package show-dependencies --format json | jq -r '.dependencies[] | select(.identity == \"containerization\") | .version')\"\nif [ \"${CONTAINERIZATION_VERSION}\" == \"unspecified\" ] ; then\n\tCONTAINERIZATION_PATH=\"$(${SWIFT} package show-dependencies --format json | jq -r '.dependencies[] | select(.identity == \"containerization\") | .path')\"\n\tif [ ! -d \"${CONTAINERIZATION_PATH}\" ] ; then\n\t\techo \"editable containerization directory at ${CONTAINERIZATION_PATH} does not exist\"\n\t\texit 1\n\tfi\n\techo \"Creating InitImage\"\n\tmake -C ${CONTAINERIZATION_PATH} init\n\t${CONTAINERIZATION_PATH}/bin/cctl images save -o /tmp/init.tar ${IMAGE_NAME}\n\n\t# Sleep because commands after stop and start are racy.\n\tbin/container system stop\n    sleep 3\n\tbin/container --debug system start \"${START_ARGS[@]}\"\n\tsleep 3\n\tbin/container i load -i /tmp/init.tar\n\trm /tmp/init.tar\nfi\n"
  },
  {
    "path": "scripts/license-header.txt",
    "content": "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\"] }}.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n  https://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "scripts/make-docs.sh",
    "content": "#! /bin/bash -e\n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nopts=()\nopts+=(\"--allow-writing-to-directory\" \"$1\")\nopts+=(\"generate-documentation\")\nopts+=(\"--target\" \"ContainerAPIService\")\nopts+=(\"--target\" \"ContainerAPIClient\")\nopts+=(\"--target\" \"ContainerSandboxService\")\nopts+=(\"--target\" \"ContainerSandboxServiceClient\")\nopts+=(\"--target\" \"ContainerNetworkService\")\nopts+=(\"--target\" \"ContainerNetworkServiceClient\")\nopts+=(\"--target\" \"ContainerImagesService\")\nopts+=(\"--target\" \"ContainerImagesServiceClient\")\nopts+=(\"--target\" \"ContainerResource\")\nopts+=(\"--target\" \"ContainerLog\")\nopts+=(\"--target\" \"ContainerPlugin\")\nopts+=(\"--target\" \"ContainerXPC\")\nopts+=(\"--target\" \"TerminalProgress\")\nopts+=(\"--output-path\" \"$1\")\nopts+=(\"--disable-indexing\")\nopts+=(\"--transform-for-static-hosting\")\nopts+=(\"--enable-experimental-combined-documentation\")\nopts+=(\"--experimental-documentation-coverage\")\n\nif [ ! -z \"$2\" ] ; then\n    opts+=(\"--hosting-base-path\" \"$2\")\nfi\n\n/usr/bin/swift package ${opts[@]}\n\necho '{}' > \"$1/theme-settings.json\"\n\ncat > \"$1/index.html\" <<'EOF'\n<!DOCTYPE html>\n<html lang=\"en-US\">\n  <head>\n    <meta charset=\"utf-8\">\n    <title>Redirecting...</title>\n    <meta http-equiv=\"refresh\" content=\"0; url=./documentation/\">\n  </head>\n  <body>\n    <p>If you are not redirected automatically, <a href=\"./documentation/\">click here</a>.</p>\n  </body>\n</html>\nEOF\n"
  },
  {
    "path": "scripts/pre-commit.fmt",
    "content": "#! /bin/bash -e\n\nsetup_error() {\n    echo failed to run: $1 1>&2\n    echo run '\"make pre-commit\"' and try again 1>&2\n    exit 1\n}\n\nif [ ! -z \"${PRECOMMIT_NOFMT}\" ] ; then\n    exit 0\nfi\n\necho checking formatting and licenses 1>&2\nproject_pathname=$(git rev-parse --show-toplevel)\ncd \"${project_pathname}\"\nmake check\n"
  },
  {
    "path": "scripts/uninstall-container.sh",
    "content": "#!/bin/bash \n# Copyright © 2025-2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -uo pipefail\n\nINSTALL_DIR=\"/usr/local\"\nDELETE_DATA=\nOPTS=0\n\nusage() { \n    echo \"Usage: $0 {-d | -k}\"\n    echo \"Uninstall container\" \n    echo \n    echo \"Options:\"\n    echo \"d     Delete user data directory.\"\n    echo \"k     Don't delete user data directory.\"\n    echo \n    exit 1\n}\n\nwhile getopts \":dk\" arg; do\n    case \"$arg\" in\n        d)\n            DELETE_DATA=true\n            ((OPTS+=1))\n            ;;\n        k)\n            DELETE_DATA=false\n            ((OPTS+=1))\n            ;;\n        *)\n            echo \"Invalid option: -${OPTARG}\"\n            usage\n            ;;\n    esac\ndone\n\nif [ $OPTS != 1 ]; then \n    echo \"Invalid number of options. Must provide either -d OR -k\"\n    usage\n    exit 1\nfi\n\n# check if container is still running \nCONTAINER_RUNNING=$(launchctl list | grep -e 'com\\.apple\\.container\\W')\nif [ -n \"$CONTAINER_RUNNING\" ]; then\n    echo '`container` is still running. Please ensure the service is stopped by running `container system stop`'\n    exit 1\nfi\n\nif [ \"$EUID\" -ne 0 ]; then\n    echo \"This script requires an administrator password to remove the application files from system directories.\"\nfi\n\nFILES=$(pkgutil --only-files --files com.apple.container-installer)\nfor i in ${FILES[@]}; do\n    # this command can fail for some of the reported files from pkgutil such as \n    # `/usr/local/bin/._uninstall-container.sh``\n    sudo rm $INSTALL_DIR/$i &> /dev/null\ndone\n\n\nDIRS=($(pkgutil --only-dirs --files com.apple.container-installer))\nfor ((i=${#DIRS[@]}-1; i>=0; i--)); do \n    # this command will fail when trying to remove `bin` and `libexec` since those directories\n    # may not be empty\n    sudo rmdir $INSTALL_DIR/${DIRS[$i]} &> /dev/null\ndone\n\nsudo pkgutil --forget com.apple.container-installer > /dev/null\necho 'Removed `container` tool and helpers'\n\nif [ \"$DELETE_DATA\" = true ]; then\n    echo 'Removing `container` user data'\n    sudo rm -rf ~/Library/Application\\ Support/com.apple.container\n    echo 'Removing `container` user defaults'\n    defaults delete com.apple.container.defaults > /dev/null 2>&1 || true\nfi\n"
  },
  {
    "path": "scripts/update-container.sh",
    "content": "#!/bin/bash\n# Copyright © 2026 Apple Inc. and the container project authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#   https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nset -uo pipefail\n\nINSTALL_DIR=\"/usr/local\"\nOPTS=0\nLATEST=false\nVERSION=\nTMP_DIR=\n\n# Release Info\nRELEASE_URL=\nRELEASE_JSON=\nRELEASE_VERSION=\n\n# Package Info\nPKG_URL=\nPKG_FILE=\nPRIMARY_PKG=\nFALLBACK_PKG=\n\ncheck_installed_version() {\n    local target_version=\"$1\"\n    if command -v container &>/dev/null; then\n        local installed_version\n        installed_version=$(container --version | awk '{print $4}')\n        installed_version=${installed_version%\\)}\n        if [[ \"$installed_version\" == \"$target_version\" ]]; then\n            return 0\n        fi\n    fi\n    return 1\n}\n\nusage() {\n    echo \"Usage: $0 {-v <version>}\"\n    echo \"Update container\"\n    echo\n    echo \"Options:\"\n    echo \"v <version>     Install a specific release version\"\n    echo \"No argument     Defaults to latest release version\"\n    exit 1\n}\n\nwhile getopts \":v:\" arg; do\n    case \"$arg\" in\n        v)\n            VERSION=\"$OPTARG\"\n            ((OPTS+=1))\n            ;;\n        *)\n            echo \"Invalid option: -${OPTARG}\"\n            usage\n            ;;\n    esac\ndone\n\n# Default to install the latest release version\nif [ \"$OPTS\" -eq 0 ]; then\n    LATEST=true\nfi\n\n# Check if container is still running\nCONTAINER_RUNNING=$(launchctl list | grep -e 'com\\.apple\\.container\\W')\nif [ -n \"$CONTAINER_RUNNING\" ]; then\n    echo '`container` is still running. Please ensure the service is stopped by running `container system stop`'\n    exit 1\nfi\n\nif [ \"$EUID\" -ne 0 ]; then\n    echo \"This script requires admin privileges to update files under $INSTALL_DIR\"\nfi\n\n# Temporary directory creation for install/download\nTMP_DIR=$(mktemp -d)\ntrap 'rm -rf \"$TMP_DIR\"' EXIT\nerror() { echo \"Error: $*\" >&2; exit 1; }\n\n# Determine the release URL and version\nif [[ \"$LATEST\" == true ]]; then\n    RELEASE_URL=\"https://api.github.com/repos/apple/container/releases/latest\"\n    RELEASE_VERSION=$(curl -fsSL \"$RELEASE_URL\" | jq -r '.tag_name')\n    if check_installed_version \"$RELEASE_VERSION\"; then\n        echo \"Container is already on latest version $RELEASE_VERSION\"\n        exit 0\n    else\n        echo \"Updating to latest version $RELEASE_VERSION\"\n    fi\nelif [[ -n \"$VERSION\" ]]; then\n    RELEASE_URL=\"https://api.github.com/repos/apple/container/releases/tags/$VERSION\"\n    RELEASE_VERSION=\"$VERSION\"\n    if check_installed_version \"$RELEASE_VERSION\"; then\n        echo \"Container is already on version $RELEASE_VERSION\"\n        exit 0\n    else\n        echo \"Updating to release version $RELEASE_VERSION\"\n    fi\nfi\n\n# Fetch the release json\nRELEASE_JSON=$(curl -fsSL \"$RELEASE_URL\") || {\n    error $([[ \"$LATEST\" == true ]] && echo \"Failed fetching latest release\" || echo \"Release '$VERSION' not found\")\n}\n\n# Possible package names\nPRIMARY_PKG=\"container-installer-signed.pkg\"\nFALLBACK_PKG=\"container-$RELEASE_VERSION-installer-signed.pkg\"\n\n# Find the package URL\nPKG_URL=$(echo \"$RELEASE_JSON\" | jq -r \\\n    --arg primary \"$PRIMARY_PKG\" \\\n    --arg fallback \"$FALLBACK_PKG\" \\\n    '.assets[] | select(.name == $primary or .name == $fallback) | .browser_download_url' | head -n1)\n[[ -n \"$PKG_URL\" ]] || error \"Neither $PRIMARY_PKG nor $FALLBACK_PKG found\"\n\nPKG_FILE=\"$TMP_DIR/$(basename \"$PKG_URL\")\"\n\necho \"Downloading package from: $PKG_URL...\"\ncurl -fSL \"$PKG_URL\" -o \"$PKG_FILE\"\n[[ -s \"$PKG_FILE\" ]] || error \"Downloaded package is empty\"\n\necho \"Installing package to $INSTALL_DIR...\"\nsudo installer -pkg \"$PKG_FILE\" -target / >/dev/null 2>&1 || error \"Installer failed\"\n\necho \"Installed successfully\"\ncontainer --version || error \"'container' command not found\"\n"
  },
  {
    "path": "signing/container-network-vmnet.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.virtualization</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "signing/container-runtime-linux.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.virtualization</key>\n\t<true/>\n</dict>\n</plist>\n"
  }
]