[
  {
    "path": ".cargo/config.toml",
    "content": "[profile.release]\nstrip = \"symbols\""
  },
  {
    "path": ".codecov.yml",
    "content": "---\ncodecov:\n  notify:\n    after_n_builds: 1\n    require_ci_to_pass: false\n\ncoverage:\n  precision: 2\n  round: down\n  range: 50..75\n\ncomment:\n  layout: \"header, diff\"\n  behavior: default\n  require_changes: false\n"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "content": "ARG VARIANT=\"bullseye\"\nFROM mcr.microsoft.com/vscode/devcontainers/rust:1-${VARIANT}\n\n# Install docker with youki\nCOPY <<EOF /etc/docker/daemon.json\n  {\n    \"runtimes\": {\n      \"youki\": {\n        \"path\": \"/workspaces/youki/youki\"\n      }\n    }\n  }\nEOF\n\nRUN <<EOF\napt-get update\n\n# For building\napt-get install -y \\\n  build-essential \\\n  git \\\n  libclang-dev \\\n  libelf-dev \\\n  libseccomp-dev \\\n  libssl-dev \\\n  libsystemd-dev \\\n  pkg-config\n\n# For debugging\napt-get install -y \\\n  podman \\\n  bpftrace\n\n# Since systemd is not running inside the Dev Container,\n# but the default events_logger for podman is set to journald, container startup fails.\n# Therefore, change it to file.\nsudo sed -i 's/^# events_logger = \"journald\"/events_logger = \"file\"/' /usr/share/containers/containers.conf\n\ncurl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/bin\n\ncurl -LO \"https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl\"\nchmod +x kubectl\nmv ./kubectl /usr/bin/kubectl\n\n# nightly build is required for `cargo fmt` as `rustfmt.toml` uses unstable features.\ncurl https://sh.rustup.rs -sSf | sh -s -- -y\nrustup install nightly\nrustup component add rustfmt\nrustup component add clippy \n\n# Install mdbook\nVERSION=$(curl -sSfL https://api.github.com/repos/rust-lang/mdBook/releases/latest | jq -r .tag_name)\ncurl -sSfL https://github.com/rust-lang/mdBook/releases/download/$VERSION/mdbook-$VERSION-$(uname -m)-unknown-linux-musl.tar.gz | tar -xzvC /usr/bin/ mdbook\nEOF\n"
  },
  {
    "path": ".devcontainer/devcontainer-lock.json",
    "content": "{\n  \"features\": {\n    \"ghcr.io/devcontainers/features/common-utils:2\": {\n      \"version\": \"2.5.4\",\n      \"resolved\": \"ghcr.io/devcontainers/features/common-utils@sha256:00fd45550f578d9d515044d9e2226e908dbc3d7aa6fcb9dee4d8bdb60be114cf\",\n      \"integrity\": \"sha256:00fd45550f578d9d515044d9e2226e908dbc3d7aa6fcb9dee4d8bdb60be114cf\"\n    },\n    \"ghcr.io/devcontainers/features/docker-in-docker:2\": {\n      \"version\": \"2.12.2\",\n      \"resolved\": \"ghcr.io/devcontainers/features/docker-in-docker@sha256:842d2ed40827dc91b95ef727771e170b0e52272404f00dba063cee94eafac4bb\",\n      \"integrity\": \"sha256:842d2ed40827dc91b95ef727771e170b0e52272404f00dba063cee94eafac4bb\"\n    }\n  }\n}"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"Youki\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/docker-in-docker:2\": {}\n\t},\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"settings\": {\n\t\t\t\t\"lldb.executable\": \"/usr/bin/lldb\",\n\t\t\t\t\"files.watcherExclude\": {\n\t\t\t\t\t\"**/target/**\": true\n\t\t\t\t},\n\t\t\t\t\"rust-analyzer.check\": {\n\t\t\t\t\t\"command\": \"clippy\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"extensions\": [\n\t\t\t\t\"vadimcn.vscode-lldb\",\n\t\t\t\t\"mutantdino.resourcemonitor\",\n\t\t\t\t\"rust-lang.rust-analyzer\",\n\t\t\t\t\"tamasfe.even-better-toml\",\n\t\t\t\t\"fill-labs.dependi\"\n\t\t\t]\n\t\t}\n\t},\n\t\"privileged\": true,\n\t\"runArgs\": [\n\t\t\"--name\",\n\t\t\"youki-devcontainer\"\n\t],\n\t\"build\": {\n\t\t\"dockerfile\": \"Dockerfile\"\n\t}\n}\n"
  },
  {
    "path": ".git-blame-ignore-revs",
    "content": "# Ignore commits in the blame view\n# see:\n#   https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view\n\n# Bulk apply formatting\n13a3fd516e9904d5cd7ef20ead76dd70cd172fe7\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BUG_REPORT.yml",
    "content": "name: Bug Report\ndescription: File a bug report.\ntitle: \"[Bug]: \"\nlabels: [\"kind/bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: textarea\n    id: description\n    attributes:\n      label: Bug Description\n      description: A clear and concise description of what the bug is \n      placeholder: Youki crashes when running...\n    validations:\n      required: true\n  - type: textarea\n    id: reproduction-steps\n    attributes:\n      label: Steps to Reproduce\n      description: How can we reproduce this, or how did you run into this?\n      placeholder: |\n        1. Create '...'\n        2. Run youki as '....'\n    validations:\n      required: true\n  - type: textarea\n    id: expectations\n    attributes:\n      label: Expectation\n      description: What did you expect to happen instead?\n      placeholder: Youki should have ran the container...\n  - type: textarea\n    id: info\n    attributes:\n      label: System and Setup Info\n      description: Give us more info about the environment and setup\n      placeholder: |\n        Add information about your system. If Youki is compiling and running on your system, please add o/p of `youki info` .\n        If you are experiencing issues in compiling Youki, or related deps, please add details regarding your setup, dependencies etc. \n        Also if you are running Youki via something else (Docker, podman, k8s) add its info here.\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional Context\n      description: Please add any additional context about the issue\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/FEATURE_REQ.yml",
    "content": "name: Feature Request\ndescription: Suggest an idea for this project\ntitle: \"[FEATURE]: \"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: Thank you for creating a feature request!\n  - type: textarea\n    id: background\n    attributes:\n      label: Background\n      description: Please add required background information for this request\n      placeholder: I'm working on a project where...\n  - type: textarea\n    id: request\n    attributes:\n      label: Feature Request\n      description: Description of the feature you are requesting\n      placeholder: I'm proposing that youki should...\n    validations:\n      required: true\n  - type: textarea\n    id: problem-desc\n    attributes:\n      label: Is the request related to some problem running youki?\n      description: A clear and concise description of what the problem is.\n      placeholder: I'm always frustrated when [...]\n  - type: textarea\n    id: expectations\n    attributes:\n      label: Proposed Solution\n      description: A clear and concise description of what you want to happen.\n      placeholder: Youki should ....\n  - type: textarea\n    id: considerations\n    attributes:\n      label: Considerations\n      description: A clear and concise description of any alternative solutions, features and issues you've considered.\n  - type: textarea\n    id: additional\n    attributes:\n      label: Additional Context\n      description: Please add any additional context or screenshots here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true # allow people to not use forms, just directly create issues\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \".\"\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 10\n    allow:\n      - dependency-type: direct\n    groups:\n      patch:\n        update-types: \n        - patch\n"
  },
  {
    "path": ".github/grcov.yml",
    "content": "branch: true\nignore-not-existing: true\nllvm: true\nfilter: covered\noutput-type: lcov\noutput-path: ./lcov.info\nprefix-dir: /home/user/build/"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Description\n<!-- Provide a clear and concise description of your changes -->\n\n## Type of Change\n<!-- Mark the appropriate option with an [x] -->\n- [ ] Bug fix (non-breaking change that fixes an issue)\n- [ ] New feature (non-breaking change that adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n- [ ] Documentation update\n- [ ] Refactoring (no functional changes)\n- [ ] Performance improvement\n- [ ] Test updates\n- [ ] CI/CD related changes\n- [ ] Other (please describe):\n\n## Testing\n<!-- Describe the tests you ran and/or added to verify your changes -->\n- [ ] Added new unit tests\n- [ ] Added new integration tests\n- [ ] Ran existing test suite\n- [ ] Tested manually (please provide steps)\n\n## Related Issues\n<!-- Link any related issues using the GitHub issue syntax: #issue-number\nIf there is no open issue for this, please add details of the problem or open a new issue. -->\nFixes #\n\n## Additional Context\n<!-- Add any other context about the pull request here -->\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    authors:\n      - dependabot\n  categories:\n    - title: 💪 Improvements\n      labels:\n        - kind/feature\n    - title: 💥 Breaking Changes\n      labels:\n        - breaking change\n    - title: 🐛 Bug Fixes\n      labels:\n        - kind/bug\n    - title: 📖 Documentation improvements\n      labels:\n        - kind/documentation\n    - title: 🧪 Test improvements and Misc Fixes\n      labels:\n        - kind/test\n        - kind/cleanup\n    - title: Other Changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/basic.yml",
    "content": "name: 🔍 Basic Checks\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  changes:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    outputs:\n      any_modified: ${{ steps.filter.outputs.any_modified }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0  # Required to get full history\n      # Using Git commands instead of tj-actions/changed-files\n      - name: Get changed files\n        id: filter\n        run: |\n          # grep will exit with non-zero if no matching pattern\n          # but we are ok with that, so to prevent workflow failing\n          # we set allow errors\n          set +e \n\n          # Change the base commit depending on event type\n          if [[ \"${{ github.event_name }}\" == \"push\" ]]; then\n            # For push events\n            if [[ -n \"${{ github.event.before }}\" ]]; then\n              BASE_COMMIT=\"${{ github.event.before }}\"\n            else\n              # For workflow dispatch, etc.\n              git fetch origin main --depth=1\n              BASE_COMMIT=\"origin/main\"\n            fi\n          elif [[ \"${{ github.event_name }}\" == \"pull_request\" ]]; then\n            # For pull request events\n            git fetch origin \"${{ github.base_ref }}\" --depth=1\n            BASE_COMMIT=\"origin/${{ github.base_ref }}\"\n          else\n            # For workflow dispatch events\n            git fetch origin main --depth=1\n            BASE_COMMIT=\"HEAD~1\"\n          fi\n          \n          echo \"Using base commit: $BASE_COMMIT\"\n          \n          # Get changed files and filter out the ones to ignore\n          ALL_CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT \"$BASE_COMMIT\" HEAD)\n          FILTERED_FILES=$(echo \"$ALL_CHANGED_FILES\" | grep -v -E '^docs/|^LICENSE$|\\.md$')\n          \n          # Set the results\n          if [[ -n \"$FILTERED_FILES\" ]]; then\n            echo \"any_modified=true\" >> $GITHUB_OUTPUT\n            echo \"all_modified_files<<EOF\" >> $GITHUB_OUTPUT\n            echo \"$FILTERED_FILES\" >> $GITHUB_OUTPUT\n            echo \"EOF\" >> $GITHUB_OUTPUT\n          else\n            echo \"any_modified=false\" >> $GITHUB_OUTPUT\n            echo \"all_modified_files=\" >> $GITHUB_OUTPUT\n          fi\n      - name: List all changed files\n        run: |\n          if [[ \"${{ steps.filter.outputs.any_modified }}\" == \"true\" ]]; then\n            echo \"Changed files detected:\"\n            echo \"${{ steps.filter.outputs.all_modified_files }}\"\n          else\n            echo \"No relevant changes detected\"\n          fi\n\n  check:\n    needs: [changes]\n    if: needs.changes.outputs.any_modified == 'true'\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    strategy:\n      matrix:\n        arch: [ \"x86_64\", \"aarch64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        with:\n          components: clippy\n      - name: Install nightly rustfmt\n        run: rustup toolchain install nightly --component rustfmt --profile minimal --no-self-update\n      - name: typos-action\n        uses: crate-ci/typos@v1.32.0\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Install cross-rs\n        run: RUSTFLAGS=\"\" cargo install cross --git https://github.com/cross-rs/cross\n      - name: Setup target\n        run: |\n          echo \"CARGO=cross\" >> ${GITHUB_ENV}\n          echo \"TARGET=${{ matrix.arch }}-unknown-linux-${{ matrix.libc }}\" >> ${GITHUB_ENV}\n      - name: Check formatting and lints\n        run: just lint\n      - name: Install cargo machete\n        uses: taiki-e/install-action@v2\n        with:\n          tool: cargo-machete@0.7.0\n      - name: Check unused deps\n        run: cargo machete\n\n  tests:\n    needs: [changes]\n    if: needs.changes.outputs.any_modified == 'true'\n    runs-on: ubuntu-24.04\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Install cross-rs\n        run: RUSTFLAGS=\"\" cargo install cross --git https://github.com/cross-rs/cross\n      - name: Create test user\n        # Create user and home directory for tests that require them\n        run: sudo useradd -m -d /tmp/testuser testuser\n      - name: Setup environment variables\n        run: |\n          echo \"CARGO=cross\" >> ${GITHUB_ENV}\n          echo \"TARGET=${{ matrix.arch }}-unknown-linux-${{ matrix.libc }}\" >> ${GITHUB_ENV}\n          echo \"TEST_NON_ROOT_UID=$(id -u testuser)\" >> ${GITHUB_ENV}\n      - name: Disable AppArmor restrictions\n        run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns\n      - name: Run tests\n        run: just test-basic\n      - name: Run feature tests\n        run: just test-features\n\n  # We do not yet enforce some minimum coverage, and there were come codecov issues\n  # so commenting this out for now. When we are ready to enforce coverage, uncomment \n  # and check this works or not.\n  # coverage:\n  #   needs: [changes]\n  #   if: needs.changes.outputs.any_modified == 'true'\n  #   runs-on: ubuntu-24.04\n  #   timeout-minutes: 20\n  #   name: Run test coverage\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - name: Setup Rust toolchain and cache\n  #       uses: actions-rust-lang/setup-rust-toolchain@v1.3.7\n  #     - name: Install llvm-tools-preview\n  #       run: rustup component add llvm-tools-preview\n  #     - name: install cargo-llvm-cov\n  #       uses: taiki-e/install-action@v2\n  #       with:\n  #         tool: cargo-llvm-cov@0.4.0\n  #     - uses: taiki-e/install-action@just\n  #     - name: Install requirements\n  #       run: sudo env PATH=$PATH just ci-prepare\n  #     - name: Run Test Coverage for youki\n  #       run: |\n  #         cargo llvm-cov clean --workspace\n  #         cargo llvm-cov --no-report -- --test-threads=1\n  #         cargo llvm-cov --no-run --lcov --output-path ./coverage.lcov\n  #     - name: Upload Youki Code Coverage Results\n  #       uses: codecov/codecov-action@v4\n  #       with:\n  #         file: ./coverage.lcov\n"
  },
  {
    "path": ".github/workflows/benchmark_execution_time.yml",
    "content": "name: ⏱️ Benchmark execution time comparison with the main branch\n\non:\n  issue_comment:\n    types: [created, edited, deleted]\n\njobs:\n  building-pr-branch:\n    if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github easy-benchmark'\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n\n    steps:\n      - name: Checkout to PR branch\n        uses: actions/checkout@v4\n\n      - name: Install requirements\n        run: sudo ./.github/scripts/dependency.sh\n\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1.3.7\n\n      - name: Install Just\n        uses: taiki-e/install-action@just\n\n      - name: Building PR branch\n        run: just youki-release\n\n      - name: Uploading PR build to artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: pr-youki\n          path: ./youki\n\n  building-main-branch:\n    if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github easy-benchmark'\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n\n    steps:\n      - name: Checkout to main branch\n        uses: actions/checkout@v4\n        with:\n          ref: main\n\n      - name: Install requirements\n        run: sudo ./.github/scripts/dependency.sh\n\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1.3.7\n      - name: Install just\n        uses: taiki-e/install-action@just\n\n      - name: Building main branch\n        run: just youki-release\n\n      - name: Uploading main build to artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: main-youki\n          path: ./youki\n\n  benchmark-exec:\n    if: (github.event.issue.pull_request != null) && github.event.comment.body == '!github easy-benchmark'\n    needs:\n      - building-pr-branch\n      - building-main-branch\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n\n    steps:\n      - name: Setup Linux env\n        run: |\n          sudo apt -y update\n          sudo apt install jq podman\n\n      - name: Checkout to PR branch\n        uses: actions/checkout@v4\n\n      - name: Downloading PR build from artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: pr-youki\n          path: ./pr_youki\n\n      - name: Downloading main build from artifact\n        uses: actions/download-artifact@v4\n        with:\n          name: main-youki\n          path: ./main_youki\n\n      - name: Preparing binaries\n        run: |\n          mv ./main_youki/youki ./youki_main\n          mv ./pr_youki/youki ./youki_pr\n          chmod +x ./youki_main\n          chmod +x ./youki_pr\n\n      - name: Preparing benchmark bundle\n        run: |\n          mkdir -p ./benchmark_exec_bundle/rootfs\n          cd benchmark_exec_bundle\n          sudo bash -c 'podman export $(podman create busybox) | tar -C rootfs -xvf -'\n          ../youki_main spec \n          sed -i 's/\"sh\"/\"sh\", \"-c\", \"echo Hi, my PID is $$; echo Bye Bye\"/' config.json\n          sed -i 's/\"terminal\": true/\"terminal\": false/' config.json\n\n      - name: Installing hyperfine\n        run: |\n          curl -s https://api.github.com/repos/sharkdp/hyperfine/releases/latest \\\n          | jq -r '.assets[] | select(.name? | match(\"hyperfine-musl_.*_amd64.deb\")) | .browser_download_url' \\\n          | wget -qi - -O hyperfine.deb\n          sudo dpkg -i hyperfine.deb\n\n      - name: Running benchmark\n        run: |\n          hyperfine --prepare 'sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' \\\n          --export-markdown bench_exec_result.md \\\n          --warmup 10 \\\n          --min-runs 100 \\\n          --command-name 'main youki build' \\\n          'sudo ./youki_main run -b benchmark_exec_bundle main_container && sudo ./youki_main delete -f main_container' \\\n          --command-name 'pr youki build' \\\n          'sudo ./youki_pr run -b benchmark_exec_bundle pr_container && sudo ./youki_pr delete -f pr_container'\n\n      - name: Adding info into result\n        run: |\n          sed -i '1i > commit (${{ github.event.pull_request.head.sha }})\\n' bench_exec_result.md\n\n      # since the GITHUB_TOKEN is needed to let the bot commit messages in the PR\n      # but right now it is controlled by the organization.\n      # TODO: change back to use this when the permission granted\n      # - name: Writing report to PR comment\n      #   uses: marocchino/sticky-pull-request-comment@v2\n      #   with:\n      #     append: true\n      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      #     path: ./bench_exec_result.md\n\n      # temp solution, print the benchmark result in the terminal.\n      # glow is the prettiest markdown rendering tool I know.\n      # TODO: remove this step when GITHUB_TOKEN permission granted\n      - name: Printing report in the terminal\n        run: |\n          curl -s https://api.github.com/repos/charmbracelet/glow/releases/latest \\\n          | jq -r '.assets[] | select(.name? | match(\"glow_.*_linux_amd64.deb\")) | .browser_download_url' \\\n          | wget -qi - -O glow.deb\n          sudo dpkg -i glow.deb\n          glow bench_exec_result.md\n"
  },
  {
    "path": ".github/workflows/dependabot_auto.yaml",
    "content": "name: 🤖 Dependabot automation\n\non:\n  pull_request:\n    types:\n      - opened\n\npermissions:\n  pull-requests: write\n  contents: write\n  repository-projects: write\n\njobs:\n  dependabot-automation:\n    runs-on: ubuntu-latest\n    if: ${{ github.actor == 'dependabot[bot]' }}\n    steps:\n      - name: Dependabot metadata\n        id: metadata\n        uses: dependabot/fetch-metadata@v1.3.5\n        with:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Approve & enable auto-merge for Dependabot PR\n        if: |\n          steps.metadata.outputs.update-type == 'version-update:semver-patch'\n        run: |\n          gh pr review --approve \"$PR_URL\"\n          gh pr edit \"$PR_URL\" -t \"(auto merged) $PR_TITLE\"\n        env:\n          PR_URL: ${{ github.event.pull_request.html_url }}\n          PR_TITLE: ${{ github.event.pull_request.title }}\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          # This is needed otherwise the pr edit fails for some reason\n          # see https://github.com/cli/cli/issues/7558\n          GH_REPO: ${{ github.repository_owner }}/${{ github.event.repository.name }}\n      - name: Automerge\n        id: automerge\n        uses: pascalgn/automerge-action@v0.15.6\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          MERGE_LABELS: dependencies\n          MERGE_REQUIRED_APPROVALS: 1\n          MERGE_RETRY_SLEEP: 300000\n          MERGE_DELETE_BRANCH: true\n          MERGE_FILTER_AUTHOR: dependabot[bot]\n"
  },
  {
    "path": ".github/workflows/docs.yaml",
    "content": "name: 📓 Deploy the documentation\n\non:\n  push:\n    branches:\n      - main\n\njobs:\n  changes:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    outputs:\n      dirs: ${{ steps.filter.outputs.changes }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: dorny/paths-filter@v2\n        id: filter\n        with:\n          filters: |\n            docs: docs/**\n  deploy:\n    needs: [changes]\n    if: ${{ !contains(needs.changes.outputs.dirs, '[]') }}\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    concurrency:\n      group: ${{ github.workflow }}-${{ github.ref }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup mdBook\n        uses: peaceiris/actions-mdbook@v1\n        with:\n          mdbook-version: \"latest\"\n      - name: Build mdbook\n        working-directory: ./docs\n        run: mdbook build\n      - name: Deploy\n        uses: peaceiris/actions-gh-pages@v3\n        if: ${{ github.ref == 'refs/heads/main' }}\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: ./docs/book\n"
  },
  {
    "path": ".github/workflows/e2e.yaml",
    "content": "name: 🧪 e2e test\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  youki-build:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    strategy:\n      matrix:\n        arch: [ \"x86_64\", \"aarch64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1.3.7\n        env:\n          RUST_CACHE_KEY_OS: rust-cache-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Install cross-rs\n        run: RUSTFLAGS=\"\" cargo install cross --git https://github.com/cross-rs/cross\n      - name: Setup target\n        run: |\n          echo \"CARGO=cross\" >> ${GITHUB_ENV}\n          echo \"TARGET=${{ matrix.arch }}-unknown-linux-${{ matrix.libc }}\" >> ${GITHUB_ENV}\n      - name: Build youki\n        run: just youki-release\n      - name: Upload youki binary\n        if: ${{ matrix.arch == 'x86_64' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n          path: youki\n\n  containerd-integration-tests:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    timeout-minutes: 40\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          repository: containerd/containerd\n          ref: v1.7.11\n      - uses: actions/setup-go@v5\n        with:\n          go-version: '1.20.12'\n          cache: true\n      - run: sudo apt-get -y update\n      - run: sudo apt-get install -y pkg-config libsystemd-dev libelf-dev libseccomp-dev btrfs-progs libbtrfs-dev\n      - name: Build containerd\n        run: |\n          make build\n          make binaries\n          sudo make install\n          ./script/setup/install-cni\n          ./script/setup/install-critools\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Replace runc to youki\n        run: |\n          sudo rm -f /usr/bin/runc /usr/local/bin/runc /usr/sbin/runc\n          sudo chmod 755 youki\n          sudo cp youki /usr/bin/runc\n          runc --version\n      - name: Integration Test\n        run: sudo make RUNC_FLAVOR=crun TEST_RUNTIME=io.containerd.runc.v2 TESTFLAGS=\"-timeout 40m\" integration\n\n  k8s-tests:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    timeout-minutes: 40\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: test/k8s/deploy\n        run: just test-kind\n\n  oci-validation-go:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    timeout-minutes: 15\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - uses: actions/setup-go@v5\n        with:\n          go-version: '1.20'\n          cache: true\n          cache-dependency-path: tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools/go.sum\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      - name: Run integration tests\n        run: just test-oci\n\n  oci-validation-rust:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Add CRIU PPA\n        run: sudo add-apt-repository -y ppa:criu/ppa\n      - name: Install requirements\n        run: sudo env PATH=$PATH just ci-prepare\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      - name: Validate tests on youki\n        run: just test-contest\n\n  rootless-podman-test:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        arch: [ \"x86_64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Add CRIU PPA\n        run: sudo add-apt-repository -y ppa:criu/ppa\n      - name: Install requirements\n        run: sudo env PATH=$PATH just ci-prepare\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      - name: Run tests\n        run: just test-rootless-podman\n\n  docker-in-docker:\n    runs-on: ubuntu-24.04\n    needs: [youki-build]\n    strategy:\n      matrix:\n        cgroup-version: [\"v2\"]\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Download youki binary\n        uses: actions/download-artifact@v4\n        with:\n          name: youki-x86_64-musl\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      # Steps for cgroups-v1 only (using Lima with AlmaLinux 8)\n      - name: Setup Lima\n        if: matrix.cgroup-version == 'v1'\n        uses: lima-vm/lima-actions/setup@v1\n        id: lima-actions-setup\n      - uses: actions/cache@v4\n        if: matrix.cgroup-version == 'v1'\n        with:\n          path: ~/.cache/lima\n          key: lima-${{ steps.lima-actions-setup.outputs.version }}\n      - name: Start the guest VM\n        if: matrix.cgroup-version == 'v1'\n        run: |\n          set -eux\n          # containerd=none is set because the built-in containerd support conflicts with Docker\n          limactl start \\\n            --name=default \\\n            --cpus=4 \\\n            --memory=8 \\\n            --containerd=none \\\n            --set '.mounts=[{\"location\":\"~/\",\"writable\":true}] | .portForwards=[{\"guestSocket\":\"/var/run/docker.sock\",\"hostSocket\":\"{{.Dir}}/sock/docker.sock\"}]' \\\n            template://almalinux-8\n      - name: Install dockerd in the guest VM\n        if: matrix.cgroup-version == 'v1'\n        run: |\n          set -eux\n          lima sudo mkdir -p /etc/systemd/system/docker.socket.d\n          cat <<-EOF | lima sudo tee /etc/systemd/system/docker.socket.d/override.conf\n          [Socket]\n          SocketUser=$(whoami)\n          EOF\n          lima sudo dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo\n          lima sudo dnf -q -y install docker-ce --nobest\n          lima sudo systemctl enable --now docker\n      - name: Configure the host to use dockerd in the guest VM\n        if: matrix.cgroup-version == 'v1'\n        run: |\n          set -eux\n          sudo systemctl disable --now docker.service docker.socket\n          export DOCKER_HOST=\"unix://$(limactl ls --format '{{.Dir}}/sock/docker.sock' default)\"\n          echo \"DOCKER_HOST=${DOCKER_HOST}\" >>$GITHUB_ENV\n          docker info\n          docker version\n      # Run tests differently based on cgroup version\n      - name: Run tests (cgroups-v2)\n        if: matrix.cgroup-version == 'v2'\n        run: just test-dind\n      - name: Run tests (cgroups-v1)\n        if: matrix.cgroup-version == 'v1'\n        run: just test-dind\n"
  },
  {
    "path": ".github/workflows/integration_tests_validation.yaml",
    "content": "name: ✔️ Verification of integration\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  changes:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    outputs:\n      any_modified: ${{ steps.filter.outputs.any_modified }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0  # Required to get full history\n      # Using Git commands instead of tj-actions/changed-files\n      - name: Get changed files\n        id: filter\n        run: |\n          # grep will exit with non-zero if no matching pattern\n          # but we are ok with that, so to prevent workflow failing\n          # we set allow errors\n          set +e \n          # Change the base commit depending on event type\n          if [[ \"${{ github.event_name }}\" == \"push\" ]]; then\n            # For push events\n            if [[ -n \"${{ github.event.before }}\" ]]; then\n              BASE_COMMIT=\"${{ github.event.before }}\"\n            else\n              # For workflow dispatch, etc.\n              git fetch origin main --depth=1\n              BASE_COMMIT=\"origin/main\"\n            fi\n          elif [[ \"${{ github.event_name }}\" == \"pull_request\" ]]; then\n            # For pull request events\n            git fetch origin \"${{ github.base_ref }}\" --depth=1\n            BASE_COMMIT=\"origin/${{ github.base_ref }}\"\n          else\n            # For workflow dispatch events\n            git fetch origin main --depth=1\n            BASE_COMMIT=\"HEAD~1\"\n          fi\n          \n          echo \"Using base commit: $BASE_COMMIT\"\n          \n          # Get all changed files\n          ALL_CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT \"$BASE_COMMIT\" HEAD)\n          \n          # Filter only files matching the specified patterns\n          # Match files in .github/workflows/integration_tests_validation.yaml and tests/contest/**\n          TARGET_FILES=$(echo \"$ALL_CHANGED_FILES\" | grep -E '^.github/workflows/integration_tests_validation.yaml$|^tests/contest/')\n          \n          # Exclude markdown files\n          FILTERED_FILES=$(echo \"$TARGET_FILES\" | grep -v '\\.md$')\n          \n          # Set the results\n          if [[ -n \"$FILTERED_FILES\" ]]; then\n            echo \"any_modified=true\" >> $GITHUB_OUTPUT\n            echo \"all_modified_files<<EOF\" >> $GITHUB_OUTPUT\n            echo \"$FILTERED_FILES\" >> $GITHUB_OUTPUT\n            echo \"EOF\" >> $GITHUB_OUTPUT\n          else\n            echo \"any_modified=false\" >> $GITHUB_OUTPUT\n            echo \"all_modified_files=\" >> $GITHUB_OUTPUT\n          fi\n      - name: List all changed files\n        run: |\n          if [[ \"${{ steps.filter.outputs.any_modified }}\" == \"true\" ]]; then\n            echo \"Changed files detected:\"\n            echo \"${{ steps.filter.outputs.all_modified_files }}\"\n          else\n            echo \"No relevant changes detected\"\n          fi\n  validate:\n    needs: [changes]\n    if: needs.changes.outputs.any_modified == 'true'\n    runs-on: ubuntu-24.04\n    timeout-minutes: 30\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1.3.7\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Add CRIU PPA\n        run: sudo add-apt-repository -y ppa:criu/ppa\n      - name: Install requirements\n        run: sudo env PATH=$PATH just ci-prepare\n      - name: Install runc 1.4.0\n        run: |\n          wget -q https://github.com/opencontainers/runc/releases/download/v1.4.0/runc.amd64\n          sudo mv runc.amd64 /usr/bin/runc\n          sudo chmod 755 /usr/bin/runc\n      - name: Build\n        run: just runtimetest contest\n      - name: Validate tests on runc\n        run: just validate-contest-runc\n"
  },
  {
    "path": ".github/workflows/label.yaml",
    "content": "name: 🏷️ Pull Request Labels\n\non:\n  pull_request:\n    types: [opened, labeled, unlabeled, synchronize]\njobs:\n  label:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n    steps:\n      - uses: mheap/github-action-required-labels@v5\n        with:\n          mode: exactly\n          count: 1\n          labels: \"kind/feature, kind/bug, kind/documentation, kind/test, kind/cleanup, dependencies, kind/experimental\"\n"
  },
  {
    "path": ".github/workflows/podman_tests.yaml",
    "content": "name: 🧪 Test for podman\n\non:\n  schedule:\n  - cron: \"0 0 * * *\"\n  workflow_dispatch:\n\njobs:\n  podman-tests:\n    runs-on: ubuntu-24.04\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Add CRIU PPA\n        run: sudo add-apt-repository -y ppa:criu/ppa\n      - name: Install requirements\n        run: sudo env PATH=$PATH just ci-prepare\n      \n      - name: Install skopeo and podman requirements\n        run: |\n          sudo apt-get install -y pkg-config libsystemd-dev libelf-dev libseccomp-dev libgpgme-dev libassuan-dev libbtrfs-dev libdevmapper-dev bats socat protobuf-compiler jq conmon\n          cargo install netavark aardvark-dns --locked\n      \n      - name: Copy binaries\n        run: |\n          sudo mkdir -p /usr/local/lib/podman\n          sudo cp $(which netavark) /usr/local/lib/podman && sudo cp $(which netavark)-dhcp-proxy-client /usr/local/lib/podman && sudo cp $(which aardvark-dns) /usr/local/lib/podman\n      \n      # setup go\n      - uses: actions/setup-go@v5\n        with:\n          go-version: '1.22'\n          cache: false\n      \n      # build skopeo\n      # These build steps are taken from https://github.com/containers/skopeo/issues/1648#issuecomment-1132161659\n      - name: Download skopeo 1.13.1 source # because ubuntu 22.04 does not have latest, and podman tests depend on that\n        run: mkdir /tmp/skopeo && curl -fsSL \"https://github.com/containers/skopeo/archive/v1.13.1.tar.gz\" | tar -xzf - -C /tmp/skopeo --strip-components=1\n      - name: Build skopeo\n        run: cd /tmp/skopeo && DISABLE_DOCS=1 make\n      - name: copy skopeo binaries\n        run: sudo cp /tmp/skopeo/bin/skopeo /usr/local/bin/skopeo && sudo cp /tmp/skopeo/default-policy.json /etc/containers/policy.json\n      - name: Skopeo version\n        run: skopeo --version\n\n      # build youki\n      - run: just youki-release\n      - run: sudo rm /usr/bin/crun && sudo cp youki /usr/local/bin && sudo cp youki /usr/bin/crun\n\n      # build podman\n      - name: Clone podman repository\n        uses: actions/checkout@v4\n        with:\n          repository: containers/podman\n      - name: Build podman\n        run: make binaries\n      - name: Install tools\n        run: make install.tools\n      \n      # run tests\n      - name: Run podman test\n        run: BATS_TEST_TMPDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} podman_bats.XXXXXX) && PODMAN_TMPDIR=$BATS_TEST_TMPDIR && OCI_RUNTIME=/usr/local/bin/youki sudo make localsystem 2>&1 | tee build.log \n      - name: Adding Summary\n        run: |\n          echo \"Total tests: 600+ Failed tests: $(cat build.log | grep \" ok \" | wc -l)\" >> $GITHUB_STEP_SUMMARY\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: 🚀 Release\n\non:\n  push:\n    tags: [\"v[0-9]+.[0-9]+.[0-9]+*\"]\n  workflow_dispatch:\n\njobs:\n  parse:\n    runs-on: ubuntu-latest\n    name: Parse ref\n    outputs:\n      version: ${{ steps.parse.outputs.version }}\n    steps:\n      - id: parse\n        name: Parse ref\n        run: echo \"version=${GITHUB_REF##refs/tags/v}\" >> ${GITHUB_OUTPUT}\n\n  build:\n    name: Build\n    runs-on: ubuntu-24.04\n    needs:\n        - parse\n    strategy:\n      matrix:\n        arch: [ \"x86_64\", \"aarch64\" ]\n        libc: [ \"gnu\", \"musl\" ]\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n        env:\n          RUST_CACHE_KEY_OS: rust-cache-${{ matrix.arch }}-${{ matrix.libc }}\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: Install cross-rs\n        run: RUSTFLAGS=\"\" cargo install cross --git https://github.com/cross-rs/cross\n      - name: Setup target\n        run: |\n          echo \"CARGO=cross\" >> ${GITHUB_ENV}\n          echo \"TARGET=${{ matrix.arch }}-unknown-linux-${{ matrix.libc }}\" >> ${GITHUB_ENV}\n      - name: Release build\n        run: just youki-release\n      - name: Disable AppArmor restrictions\n        run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns\n      - name: test\n        # TODO(utam0k): The feature test needs nightly\n        # run: just test-basic featuretest test-oci\n        if: ${{ matrix.arch == 'x86_64' }}\n        run: just test-basic test-oci\n      - name: Create output directory\n        run: mkdir output\n      - name: Create artifact\n        run: tar -zcvf youki-${{ needs.parse.outputs.version }}-${{ matrix.arch }}-${{ matrix.libc }}.tar.gz youki README.md LICENSE\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: youki-${{ matrix.arch }}-${{ matrix.libc }}\n          path: youki-${{ needs.parse.outputs.version }}-${{ matrix.arch }}-${{ matrix.libc }}.tar.gz\n\n  release:\n    name: Create Draft Release\n    environment:\n      name: release\n    runs-on: ubuntu-24.04\n    permissions:\n      contents: write\n    needs:\n      - parse\n      - build\n    steps:\n      - uses: actions/checkout@v4\n      - name: Create artifacts directory\n        run: mkdir -p artifacts\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: artifacts\n      - name: Show artifacts\n        run: ls -alhR artifacts\n      - name: Create release draft\n        shell: bash\n        run: |\n          set -x\n          gh release create \"${{ github.ref }}\" --generate-notes --draft\n          gh release upload \"${{ github.ref }}\" artifacts/*/*\n        env:\n          GH_TOKEN: ${{ github.token }}\n          RELEASE_NAME: \"${{ needs.parse.outputs.version }} Release\"\n\n  publish:\n    name: Publish Packages\n    needs: release\n    runs-on: ubuntu-24.04\n    if: ${{ github.repository == 'youki-dev/youki' }}\n    env:\n      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n    steps:\n      - uses: actions/checkout@v4\n      - name: Setup Rust toolchain and cache\n        uses: actions-rust-lang/setup-rust-toolchain@v1\n      - name: Publish libcgroups\n        run: cargo publish -p libcgroups --no-verify\n      - name: Publish libcontainer\n        run: cargo publish -p libcontainer --no-verify\n      - name: Publish liboci-cli\n        run: cargo publish -p liboci-cli --no-verify\n      - name: Publish youki\n        run: cargo publish -p youki --no-verify\n"
  },
  {
    "path": ".github/workflows/runc_integration_tests.yaml",
    "content": "name: 🧪 Runc integration test\n\non:\n  schedule:\n  - cron: \"0 0 * * *\"\n  workflow_dispatch:\n\njobs:\n  runc-integration-tests:\n    runs-on: ubuntu-24.04\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - name: install runc and youki  requirements\n        run: |\n          sudo apt update\n          sudo apt -y install libseccomp-dev sshfs uidmap \\\n               pkg-config libsystemd-dev build-essential libelf-dev \\\n               libclang-dev libssl-dev\n\n      - name: Build youki\n        run: just youki-release\n\n      - uses: actions/setup-go@v5\n        with:\n          go-version: '1.23.x'\n          cache: true\n\n      - name: Add the permission to run\n        run: chmod +x ./youki\n      - name: Setup Bats and bats libs\n        uses: bats-core/bats-action@3.0.1\n        with:\n          bats-version: 1.9.0\n          support-install: false\n          assert-install: false\n          detik-install: false\n          file-install: false\n      - name: Allow userns for runc\n        # https://github.com/opencontainers/runc/pull/4286\n        run: |\n          sed \"s;^profile runc /usr/sbin/;profile runc-test $(pwd)/tests/runc/src/github.com/opencontainers/runc/;\" < /etc/apparmor.d/runc | sudo apparmor_parser\n      - name: Runc integration test\n        run: just test-runc-comp\n"
  },
  {
    "path": ".github/workflows/selinux.yaml",
    "content": "name: 🧪 SELinux Tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  changes:\n    runs-on: ubuntu-latest\n    outputs:\n      dirs: ${{ steps.filter.outputs.changes }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - name: Check for changes\n        uses: dorny/paths-filter@v2\n        id: filter\n        with:\n          filters: |\n            selinux: experiment/selinux/**\n\n  test:\n    name: SELinux Lima Tests\n    needs: [changes]\n    if: ${{ !contains(needs.changes.outputs.dirs, '[]') }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Set up Lima\n        uses: lima-vm/lima-actions/setup@v1\n        id: lima-setup\n\n      - name: Cache Lima images\n        uses: actions/cache@v4\n        with:\n          path: ~/.cache/lima\n          key: lima-${{ steps.lima-setup.outputs.version }}-selinux\n\n      - name: Create Lima VM\n        working-directory: experiment/selinux\n        run: |\n          chmod +x ./lima-setup.sh\n          ./lima-setup.sh --cpus 2 --memory 2GiB\n\n      - name: Run tests\n        working-directory: experiment/selinux\n        run: |\n          ./lima-run.sh cargo test\n\n      - name: Run application\n        working-directory: experiment/selinux\n        run: |\n          ./lima-run.sh cargo run\n\n      - name: Clean up\n        if: always()\n        working-directory: experiment/selinux\n        run: |\n          ./lima-setup.sh --cleanup\n"
  },
  {
    "path": ".github/workflows/tagpr.yaml",
    "content": "name: 🚀 Tagpr for GitHub Actions\non:\n  push:\n    branches:\n      - main\njobs:\n  tagpr:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n      - name: Install just\n        uses: taiki-e/install-action@just\n      - uses: Songmu/tagpr@v1\n        id: tagpr\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      - name: Trigger Release Workflow(only when tagged)\n        uses: actions/github-script@v6\n        if: \"steps.tagpr.outputs.tag != ''\"\n        with:\n          script: |\n            github.rest.actions.createWorkflowDispatch({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              workflow_id: 'release.yaml',\n              ref: \"refs/tags/${{ steps.tagpr.outputs.tag }}\",\n            })\n"
  },
  {
    "path": ".github/workflows/update_version_config.yaml",
    "content": "name:  🤖 Automated Update tagpr config\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'Version to release'\n        required: true\njobs:\n  config:\n    runs-on: ubuntu-24.04\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Update tagpr config\n        run: |\n          cat << EOF > .tagpr\n          [tagpr]\n              vPrefix = true\n              releaseBranch = main\n              versionFile = justfile\n              command = just version-up ${{ github.event.inputs.version }}\n              release = false\n              changelog = true\n          EOF\n      - uses: peter-evans/create-pull-request@v5\n        with:\n          title: 'config: Automated Tagpr Update for ${{ github.event.inputs.version }}'\n          add-paths: |\n            .tagpr\n          body: |\n            \n            Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action\n          commit-message: |\n            🤖 update tagpr config using robot.\n          branch: tagpr-${{ github.event.inputs.version }}\n          base: main\n          signoff: true\n          delete-branch: true\n          token: ${{ secrets.GITHUB_TOKEN }}\n          committer: github-actions[bot] <github-actions[bot]@users.noreply.github.com>\n          author: github-actions[bot] <github-actions[bot]@users.noreply.github.com>\n"
  },
  {
    "path": ".gitignore",
    "content": "/tutorial\n.idea/\n\n**/target\n/contest-target\n/bin\n.vagrant/\n\ntags\ntags.lock\ntags.temp\n\n/youki\n/runtimetest\n/contest\n\n.vscode\n\n*~\n\n/bundle.tar.gz\n/test.log\n\n/tests/k8s/_out/\nreplace_content.txt\n/e2e/k8s/_out/\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools\"]\n\tpath = tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools\n\turl = https://github.com/opencontainers/runtime-tools.git\n    ignore = dirty\n\n[submodule \"tests/runc/src/github.com/opencontainers/runc\"]\n\tpath = tests/runc/src/github.com/opencontainers/runc\n\turl = https://github.com/opencontainers/runc.git\n    ignore = dirty\n"
  },
  {
    "path": ".tagpr",
    "content": "[tagpr]\n    vPrefix = true\n    releaseBranch = main\n    versionFile = -\n    command = just version-up 0.6.1\n    release = false\n    changelog = true\n"
  },
  {
    "path": ".typos.toml",
    "content": "# Configuration Reference:\n#  - https://github.com/crate-ci/typos/blob/927308c726b1fba730f7aaa8bde602148b82004d/docs/reference.md\n\n[files]\nextend-exclude = [\n    \"**/*.svg\",\n    \"tests/oci-runtime-tests/**\",\n    \"CHANGELOG.md\"\n]\n\n[default.extend-identifiers]\n# This is a cgroup slice ID used in examples. It is easier to ignore this\n# instance than write a regex.\n569d5ce3afe1074769f67 = \"569d5ce3afe1074769f67\"\n\n[type.rust.extend-words]\nser = \"ser\"\nflate = \"flate\"\nclos = \"clos\"\nSetted = \"Setted\"\nhve = \"hve\"\ntyp = \"typ\"\n\n[type.md.extend-words]\n# This is used as \"yoh-key\" in markdown files to describe the pronunciation \n# for Youki\n\"yoh\" = \"yoh\"\n\"typ\" = \"typ\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [v0.6.0](https://github.com/youki-dev/youki/compare/v0.5.7...v0.6.0) - 2026-02-25\n### 💪 Improvements\n- Add net device feature by @nayuta723 in https://github.com/youki-dev/youki/pull/3163\n- feat(info): add rustc, spec, and libseccomp version by @nayuta723 in https://github.com/youki-dev/youki/pull/3318\n- Implement Linux memory policy by @n4mlz in https://github.com/youki-dev/youki/pull/3230\n- feat: add io limits controller for systemd by @gokulmaxi in https://github.com/youki-dev/youki/pull/3235\n- Added SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV by @viboognesh in https://github.com/youki-dev/youki/pull/3404\n### 💥 Breaking Changes\n- fix hooks order by @saku3 in https://github.com/youki-dev/youki/pull/3256\n- mount info provider by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3280\n- Use oci spec container process state for seccomp by @nayuta723 in https://github.com/youki-dev/youki/pull/3330\n- refactor(hooks): pass OCI-compliant state to lifecycle hooks by @nayuta723 in https://github.com/youki-dev/youki/pull/3346\n### 🐛 Bug Fixes\n- Implement mount destination validation to ensure absolute paths in OCI Runtime Spec by @nayuta723 in https://github.com/youki-dev/youki/pull/3315\n- Fix default filemode for device creation by @you-matsuura in https://github.com/youki-dev/youki/pull/3276\n- fix(3293) Ambient capabilities are not applied as expected by @tommady in https://github.com/youki-dev/youki/pull/3294\n- fix(libcgroups): set `sz` field in `bpf_prog_load_opts` by @sou1118 in https://github.com/youki-dev/youki/pull/3340\n- Fix recursive mount_setattr handling for rec_attr and improve mounts_recursive tests by @saku3 in https://github.com/youki-dev/youki/pull/3345\n- fix(libcgroups): pass `full_path` to Devices controller instead of `cgroup_path` by @sou1118 in https://github.com/youki-dev/youki/pull/3355\n- refactor(tty): call setup_console after pivot_root, use syscall for mount_console by @nayuta723 in https://github.com/youki-dev/youki/pull/3333\n- Align with runc: use user's HOME when HOME is empty string by @bells17 in https://github.com/youki-dev/youki/pull/3269\n- Refactor checkpoint by @nayuta723 in https://github.com/youki-dev/youki/pull/3365\n### 📖 Documentation improvements\n- chore: fix docs mdbook toml by @YJDoc2 in https://github.com/youki-dev/youki/pull/3307\n- Doc: delete redundant statement on youki.md in dev doc by @logica0419 in https://github.com/youki-dev/youki/pull/3310\n- Fix typos in documentation by @oglok in https://github.com/youki-dev/youki/pull/3343\n- (chore) Fix broken links in user document by @donkomura in https://github.com/youki-dev/youki/pull/3361\n- add tommady as reviewers into doc by @tommady in https://github.com/youki-dev/youki/pull/3369\n- added saku3 as committer into doc by @saku3 in https://github.com/youki-dev/youki/pull/3370\n- add nayuta723 as reviewer into doc by @nayuta723 in https://github.com/youki-dev/youki/pull/3373\n### 🧪 Test improvements and Misc Fixes\n- Update netlink-packet dependencies to versions 0.8.1 and 0.25.1 in Cargo.toml and Cargo.lock by @nayuta723 in https://github.com/youki-dev/youki/pull/3297\n- Fixed minor spelling errors in libcontainer documentation. by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3305\n- Add poststart hook test by @fspv in https://github.com/youki-dev/youki/pull/3292\n- Update/runc 1.4.0 by @nayuta723 in https://github.com/youki-dev/youki/pull/3304\n- chore: runc compatibility test improvements by @saku3 in https://github.com/youki-dev/youki/pull/3319\n- Replace once_cell with stdlib OnceLock/LazyLock by @yan-ace62 in https://github.com/youki-dev/youki/pull/3323\n- Update Kind and Kubernetes versions for k8s e2e tests by @IrvingMg in https://github.com/youki-dev/youki/pull/3328\n- ci(basic): pin Rust toolchain to 1.92.0 for cross-rs compatibility by @nayuta723 in https://github.com/youki-dev/youki/pull/3348\n- test: output contest logs to stdout by @saku3 in https://github.com/youki-dev/youki/pull/3349\n- Add poststart_fail hook test by @fspv in https://github.com/youki-dev/youki/pull/3313\n- Added new test \"kill no effect\" by @oneplus1000 in https://github.com/youki-dev/youki/pull/3332\n- Pass State directly to `run_hooks` instead of Container reference by @IrvingMg in https://github.com/youki-dev/youki/pull/3360\n- Batch running the test groups in test_framework by @donkomura in https://github.com/youki-dev/youki/pull/3372\n- refact mount_recursive test by @saku3 in https://github.com/youki-dev/youki/pull/3383\n- Add test poststop hook by @donkomura in https://github.com/youki-dev/youki/pull/3395\n- Add prestart hook test by @fspv in https://github.com/youki-dev/youki/pull/3382\n- Add create_runtime hook test by @fspv in https://github.com/youki-dev/youki/pull/3396\n- Sync the state to confirm hooks execution by @donkomura in https://github.com/youki-dev/youki/pull/3385\n- Include container status to IncorrectStatus error messaging by @CarloQuick in https://github.com/youki-dev/youki/pull/3411\n- Add prestart_fail hook test by @fspv in https://github.com/youki-dev/youki/pull/3406\n- chore(deps): bump wasmer, wasmtime by @YJDoc2 in https://github.com/youki-dev/youki/pull/3423\n- prepare v0.6.0 by @saku3 in https://github.com/youki-dev/youki/pull/3424\n### Other Changes\n- chore(deps): bump which from 7.0.2 to 8.0.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3287\n- (auto merged) chore(deps): bump the patch group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3302\n- (auto merged) chore(deps): bump tracing-journald from 0.3.1 to 0.3.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3303\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3306\n- chore(deps): bump mockall from 0.13.1 to 0.14.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3301\n- chore(deps): bump wasmtime from 31.0.0 to 35.0.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3288\n- (auto merged) chore(deps): bump libc from 0.2.177 to 0.2.178 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3308\n- chore(deps): bump netlink-packet-route from 0.25.1 to 0.26.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3316\n- (auto merged) chore(deps): bump oci-spec from 0.8.3 to 0.8.4 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3329\n- (auto merged) chore(deps): bump tracing from 0.1.43 to 0.1.44 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3331\n- (auto merged) chore(deps): bump serde_json from 1.0.145 to 1.0.146 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3334\n- (auto merged) chore(deps): bump serde_json from 1.0.146 to 1.0.147 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3337\n- (auto merged) chore(deps): bump serde_json from 1.0.147 to 1.0.148 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3341\n- (auto merged) chore(deps): bump libc from 0.2.178 to 0.2.179 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3352\n- (auto merged) chore(deps): bump serde_json from 1.0.148 to 1.0.149 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3354\n- chore(deps): bump serial_test from 3.2.0 to 3.3.1 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3353\n- chore(deps): bump wasmtime from 35.0.0 to 40.0.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3335\n- chore(deps): bump tempfile from 3.23.0 to 3.24.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3338\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3356\n- (auto merged) chore(deps): bump libc from 0.2.179 to 0.2.180 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3357\n- (auto merged) chore(deps): bump flate2 from 1.1.5 to 1.1.8 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3359\n- (auto merged) chore(deps): bump the patch group with 3 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3363\n- (auto merged) chore(deps): bump the patch group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3371\n- chore(deps): bump vergen-gitcl from 1.0.8 to 9.1.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3368\n- (auto merged) chore(deps): bump wasmtime from 40.0.2 to 40.0.3 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3376\n- (auto merged) chore(deps): bump pathrs from 0.2.2 to 0.2.3 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3379\n- (auto merged) chore(deps): bump bytes from 1.11.0 to 1.11.1 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3388\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3389\n- (auto merged) chore(deps): bump libbpf-sys from 1.6.2+v1.6.2 to 1.6.3+v1.6.3 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3390\n- (auto merged) chore(deps): bump anyhow from 1.0.100 to 1.0.101 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3393\n- chore(deps): bump quickcheck from 1.0.3 to 1.1.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3401\n- chore(deps): bump rand from 0.9.2 to 0.10.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3397\n- chore(deps): bump tempfile from 3.24.0 to 3.25.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3400\n- (auto merged) chore(deps): bump the patch group across 1 directory with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3413\n- (auto merged) chore(deps): bump chrono from 0.4.43 to 0.4.44 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3414\n- (auto merged) chore(deps): bump wasmtime from 40.0.3 to 40.0.4 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3420\n- chore(deps): bump serial_test from 3.3.1 to 3.4.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3416\n- chore(deps): bump wasmtime and wasi-common from 40.0.4 to 41.0.4 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3421\n- chore(deps): bump rbpf from 0.3.0 to 0.4.1 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3398\n\n## [v0.5.7](https://github.com/youki-dev/youki/compare/v0.5.6...v0.5.7) - 2025-11-05\n### 💪 Improvements\n- Drop cgroup v1 in github workflows by @utam0k in https://github.com/youki-dev/youki/pull/3284\n### 🐛 Bug Fixes\n- Waiting on systemd to add intermediate process to cgroup. by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3262\n### 🧪 Test improvements and Misc Fixes\n- Update/runc 1.3.2 by @n4mlz in https://github.com/youki-dev/youki/pull/3274\n### Other Changes\n- (auto merged) chore(deps): bump flate2 from 1.1.4 to 1.1.5 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3281\n\n## [v0.5.6](https://github.com/youki-dev/youki/compare/v0.5.5...v0.5.6) - 2025-10-24\n### 💪 Improvements\n- fix(3197): fix youki version command Part of Enhancing Compatibility with runc by @tommady in https://github.com/youki-dev/youki/pull/3200\n- feat(3199): Add Linux personality support by @tommady in https://github.com/youki-dev/youki/pull/3202\n### 💥 Breaking Changes\n- Upgrade to Rust 1.89 and Edition 2024 by @utam0k in https://github.com/youki-dev/youki/pull/3244\n### 📖 Documentation improvements\n- added saku3 as reviewers by @saku3 in https://github.com/youki-dev/youki/pull/3228\n- Changed the events_logger in the Dev Container to file by @bells17 in https://github.com/youki-dev/youki/pull/3221\n- Update Rust edition requirement in docs to 2024 by @FalkWoldmann in https://github.com/youki-dev/youki/pull/3246\n- Update basic_setup.md by @bells17 in https://github.com/youki-dev/youki/pull/3253\n### 🧪 Test improvements and Misc Fixes\n- Update Vagrantfile to support the ARM architecture by @bells17 in https://github.com/youki-dev/youki/pull/3222\n- setup runc integration test by @saku3 in https://github.com/youki-dev/youki/pull/3182\n- update runc ci to 1.3.1 by @saku3 in https://github.com/youki-dev/youki/pull/3237\n- Add mdbook binary to devcontainer by @bells17 in https://github.com/youki-dev/youki/pull/3240\n- Unskip runc tests after CI runc update 1.3.1 by @saku3 in https://github.com/youki-dev/youki/pull/3249\n- Fix podman ci by @saku3 in https://github.com/youki-dev/youki/pull/3260\n- add misc_props test by @YamasouA in https://github.com/youki-dev/youki/pull/3250\n- chore(deps): bump libseccomp from 0.3.0 to 0.4.0 by @MattPatchava in https://github.com/youki-dev/youki/pull/3275\n### Other Changes\n- (auto merged) chore(deps): bump thiserror from 2.0.14 to 2.0.15 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3223\n- (auto merged) chore(deps): bump serde_json from 1.0.142 to 1.0.143 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3225\n- (auto merged) chore(deps): bump thiserror from 2.0.15 to 2.0.16 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3226\n- chore(deps): bump tempfile from 3.20.0 to 3.21.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3224\n- (auto merged) chore(deps): bump regex from 1.11.1 to 1.11.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3229\n- (auto merged) chore(deps): bump tracing-subscriber from 0.3.19 to 0.3.20 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3231\n- (auto merged) chore(deps): bump chrono from 0.4.41 to 0.4.42 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3239\n- (auto merged) chore(deps): bump errno from 0.3.13 to 0.3.14 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3241\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3245\n- chore(deps): bump tempfile from 3.21.0 to 3.22.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3242\n- (auto merged) chore(deps): bump serde from 1.0.223 to 1.0.224 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3247\n- (auto merged) chore(deps): bump serde from 1.0.224 to 1.0.225 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3248\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3251\n- (auto merged) chore(deps): bump libc from 0.2.175 to 0.2.176 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3254\n- chore(deps): bump tempfile from 3.22.0 to 3.23.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3255\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3257\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3261\n- (auto merged) chore(deps): bump flate2 from 1.1.2 to 1.1.4 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3268\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3270\n- (auto merged) chore(deps): bump libc from 0.2.176 to 0.2.177 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3271\n- chore(deps): bump regex from 1.11.3 to 1.12.1 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3272\n- (auto merged) chore(deps): bump regex from 1.12.1 to 1.12.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3273\n- (auto merged) chore(deps): bump caps from 0.5.5 to 0.5.6 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3277\n\n## [v0.5.5](https://github.com/youki-dev/youki/compare/v0.5.4...v0.5.5) - 2025-08-14\n### 💪 Improvements\n- fix(3198): fix difference in how commands are passed after exec and ps by @tommady in https://github.com/youki-dev/youki/pull/3201\n### 📖 Documentation improvements\n- Add license scan report and status by @fossabot in https://github.com/youki-dev/youki/pull/3204\n### 🧪 Test improvements and Misc Fixes\n- Revert \"[DNM] ci: temp disable workflows\" by @YJDoc2 in https://github.com/youki-dev/youki/pull/3194\n- Fixed Minor Spelling Errors by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3205\n- chore(justfile):add install recipe by @saku3 in https://github.com/youki-dev/youki/pull/3213\n### Other Changes\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3203\n- (auto merged) chore(deps): bump serde_json from 1.0.141 to 1.0.142 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3212\n- (auto merged) chore(deps): bump the patch group with 3 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3217\n- (auto merged) chore(deps): bump oci-spec from 0.8.1 to 0.8.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3219\n- chore(deps): bump libbpf-sys from 1.5.2+v1.5.1 to 1.6.1+v1.6.1 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3218\n\n## [v0.5.4](https://github.com/youki-dev/youki/compare/v0.5.3...v0.5.4) - 2025-07-17\n### 💪 Improvements\n- add support exec-cpu-affinity by @saku3 in https://github.com/youki-dev/youki/pull/3164\n- fix: allow duplicate additionalGids by @saku3 in https://github.com/youki-dev/youki/pull/3189\n### 🐛 Bug Fixes\n- use additional gids,user,group in exec, inject path iif not given by @YJDoc2 in https://github.com/youki-dev/youki/pull/3131\n- fix: mount retry and logging by @z63d in https://github.com/youki-dev/youki/pull/3157\n- fix: Gracefully terminate processes after successful execution of Wasm executors by @z63d in https://github.com/youki-dev/youki/pull/3099\n- fix: Running create_runtime hook after container is set to created. by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3181\n- fix: Ignoring CPU realtime on cgroupsv2 if set to zero by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3180\n### 📖 Documentation improvements\n- Add the CNCF footer in README.md by @utam0k in https://github.com/youki-dev/youki/pull/3140\n- chore(docs): Fix codecov link in README by @khanhtc1202 in https://github.com/youki-dev/youki/pull/3129\n- Fixed grammatical error in README by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3160\n- fix: protobuf bug on docs rs by @mdaffad in https://github.com/youki-dev/youki/pull/3159\n- docs: clarify reviewer qualification and self-nomination process by @utam0k in https://github.com/youki-dev/youki/pull/3175\n### 🧪 Test improvements and Misc Fixes\n- bump nix to 0.29.0 by @kemingy in https://github.com/youki-dev/youki/pull/3123\n- update rust version to 1.85.0 by @YJDoc2 in https://github.com/youki-dev/youki/pull/3085\n- add-test-linux_rootfs_propagation by @saku3 in https://github.com/youki-dev/youki/pull/3024\n- Add a relative_network_cgroups test as one of the integration tests by @moz-sec in https://github.com/youki-dev/youki/pull/2986\n- Refactor init process by @utam0k in https://github.com/youki-dev/youki/pull/3158\n- add kill test by @YamasouA in https://github.com/youki-dev/youki/pull/2996\n- allow running selected tests in contest.sh and justfile by @saku3 in https://github.com/youki-dev/youki/pull/3165\n- fix: capet Ambient log level by @z63d in https://github.com/youki-dev/youki/pull/3150\n- add test process_capabilities_fail by @kazmsk in https://github.com/youki-dev/youki/pull/3010\n- fix typos and outdated typos ci action by @howjmay in https://github.com/youki-dev/youki/pull/3168\n- add a system call mock for uid/gid. by @nayuta-ai in https://github.com/youki-dev/youki/pull/3173\n- fix: remove println statements from contest tests by @YJDoc2 in https://github.com/youki-dev/youki/pull/3167\n- Installing kubectl in dev container. by @CheatCodeSam in https://github.com/youki-dev/youki/pull/3177\n- Add uid_mappings test by @moz-sec in https://github.com/youki-dev/youki/pull/3161\n- fix: update devcontainer.json by @AobaIwaki123 in https://github.com/youki-dev/youki/pull/3172\n- Remove oci tests that are duplicates of contest by @utam0k in https://github.com/youki-dev/youki/pull/3042\n- Remove oci tests that are duplicates of contest by @saku3 in https://github.com/youki-dev/youki/pull/3184\n- Fix debug logging for CPU affinity bitmask by @saku3 in https://github.com/youki-dev/youki/pull/3191\n- [DNM] ci: temp disable workflows by @YJDoc2 in https://github.com/youki-dev/youki/pull/3192\n### Other Changes\n- chore(deps): bump uuid from 1.15.1 to 1.16.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3113\n- (auto merged) chore(deps): bump once_cell from 1.21.1 to 1.21.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3126\n- (auto merged) chore(deps): bump once_cell from 1.21.2 to 1.21.3 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3128\n- (auto merged) chore(deps): bump the patch group with 2 updates by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3133\n- (auto merged) chore(deps): bump errno from 0.3.10 to 0.3.11 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3135\n- (auto merged) chore(deps): bump openssl from 0.10.70 to 0.10.72 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3134\n- chore(deps): bump wasmtime from 29.0.1 to 31.0.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3121\n- (auto merged) chore(deps): bump vergen-gitcl from 1.0.5 to 1.0.7 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3142\n- (auto merged) chore(deps): bump crossbeam-channel from 0.5.12 to 0.5.15 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3143\n- (auto merged) chore(deps): bump vergen-gitcl from 1.0.7 to 1.0.8 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3145\n- (auto merged) chore(deps): bump anyhow from 1.0.97 to 1.0.98 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3147\n- (auto merged) chore(deps): bump libc from 0.2.171 to 0.2.172 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3148\n- (auto merged) chore(deps): bump rand from 0.9.0 to 0.9.1 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3149\n- chore(deps): bump tokio from 1.37.0 to 1.44.2 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3137\n- Bump oci-spec.rs to v0.8.1 by @saku3 in https://github.com/youki-dev/youki/pull/3154\n- (auto merged) chore(deps): bump chrono from 0.4.40 to 0.4.41 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3156\n- (auto merged) chore(deps): bump errno from 0.3.11 to 0.3.12 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3169\n- selinux: lima vm by @utam0k in https://github.com/youki-dev/youki/pull/3162\n- chore(deps): bump tokio from 1.37.0 to 1.38.2 in /experiment/seccomp by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3138\n- (auto merged) chore(deps): bump libbpf-sys from 1.5.0+v1.5.0 to 1.5.1+v1.5.1 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3171\n- chore(deps): bump num_cpus from 1.16.0 to 1.17.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3176\n- chore(deps): bump tempfile from 3.19.1 to 3.20.0 by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3166\n- (auto merged) chore(deps): bump flate2 from 1.1.1 to 1.1.2 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3183\n- chore(deps): bump libc from 0.2.172 to 0.2.173 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3185\n- (auto merged) chore(deps): bump libc from 0.2.173 to 0.2.174 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3187\n- (auto merged) chore(deps): bump errno from 0.3.12 to 0.3.13 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3188\n- (auto merged) chore(deps): bump libbpf-sys from 1.5.1+v1.5.1 to 1.5.2+v1.5.1 in the patch group by @dependabot[bot] in https://github.com/youki-dev/youki/pull/3190\n\n## [v0.5.3](https://github.com/youki-dev/youki/compare/v0.5.2...v0.5.3) - 2025-03-21\n### 🐛 Bug Fixes\n- Security: Fix compromised `tj-actions/changed-files` action by @sou1118 in https://github.com/youki-dev/youki/pull/3112\n### 🧪 Test improvements and Misc Fixes\n- Fix the release flow by @utam0k in https://github.com/youki-dev/youki/pull/3098\n- chore(ci): add cgroup v1 compatibility for tests on ubuntu-24.04 by @sou1118 in https://github.com/youki-dev/youki/pull/3102\n- fix: CPU controller tests for Kernel 6.10 cgroup v2 changes by @sou1118 in https://github.com/youki-dev/youki/pull/3106\n- chore(ci): Upgrade GitHub Actions workflows for `ubuntu-24.04` by @sou1118 in https://github.com/youki-dev/youki/pull/3097\n- fix: release ci tests also need apparmor disable by @YJDoc2 in https://github.com/youki-dev/youki/pull/3118\n- chore(ci): add criu ppa for podman-tests ci by @sou1118 in https://github.com/youki-dev/youki/pull/3120\n\n## [v0.5.2](https://github.com/youki-dev/youki/compare/v0.5.1...v0.5.2) - 2025-03-04\n### 💪 Improvements\n- Support feature subcommand by @musaprg in https://github.com/youki-dev/youki/pull/2837\n### 🐛 Bug Fixes\n- fix(libcgroup): fix disable_oom_killer in cgroup v1 by @xujihui1985 in https://github.com/youki-dev/youki/pull/3090\n### 🧪 Test improvements and Misc Fixes\n- Add a PR template file by @Gekko0114 in https://github.com/youki-dev/youki/pull/3049\n- add process rlimits fail test by @ntkm61027 in https://github.com/youki-dev/youki/pull/3051\n- Use MountOption enum to parse mount options defined in the spec by @musaprg in https://github.com/youki-dev/youki/pull/2937\n- ci: Publish packages after the release flow by @utam0k in https://github.com/youki-dev/youki/pull/3064\n- Make `sepc` into `&spec` in test_{outside,inside}_containe by @utam0k in https://github.com/youki-dev/youki/pull/3068\n- linux_masked_paths integration test by @nayuta-ai in https://github.com/youki-dev/youki/pull/2950\n- fix: compilation errors in contest by @YJDoc2 in https://github.com/youki-dev/youki/pull/3086\n- Remove problematic comments between package name in apt install by @musaprg in https://github.com/youki-dev/youki/pull/3060\n- Add `delete` test by @sou1118 in https://github.com/youki-dev/youki/pull/3082\n### Other Changes\n- Upgrade direct dep rand to 0.9.0 by @YJDoc2 in https://github.com/youki-dev/youki/pull/3083\n- rollup multiple dep updates by @YJDoc2 in https://github.com/youki-dev/youki/pull/3084\n- lset_file_label should check for symlink instead of raw file by @foreverddong in https://github.com/youki-dev/youki/pull/3073\n\n## [v0.5.1](https://github.com/youki-dev/youki/compare/v0.5.0...v0.5.1) - 2025-01-06\n### 🐛 Bug Fixes\n- Fix building the wasmedge feature by @utam0k in https://github.com/youki-dev/youki/pull/3041\n### 🧪 Test improvements and Misc Fixes\n- Do `cargo check` before releasing a new version by @utam0k in https://github.com/youki-dev/youki/pull/3039\n\n## [v0.5.0](https://github.com/youki-dev/youki/compare/v0.4.1...v0.5.0) - 2025-01-02\n### 💪 Improvements\n- libcontainer: support set stdios for container by @abel-von in https://github.com/youki-dev/youki/pull/2961\n- Add option to spawn processes as siblings by @jprendes in https://github.com/youki-dev/youki/pull/3012\n### 💥 Breaking Changes\n- libcontainer: use OwnedFd as console_socket  in ContainerBuilder by @abel-von in https://github.com/youki-dev/youki/pull/2966\n### 🐛 Bug Fixes\n- Fixed ENAMETOOLONG error in setup_console_socket by @morganllewellynjones in https://github.com/youki-dev/youki/pull/2915\n- fix(libcontainer) no_pivot args is not used by @xujihui1985 in https://github.com/youki-dev/youki/pull/2923\n- Fix/return multi errors on create failed by @xujihui1985 in https://github.com/youki-dev/youki/pull/2998\n- fix duplicate gids in container creation by @YJDoc2 in https://github.com/youki-dev/youki/pull/3019\n- Fix --preserve-fds, eliminate stray FD being passed into container by @aidanhs in https://github.com/youki-dev/youki/pull/2893\n### 📖 Documentation improvements\n- Add the affiliations of youki maintainers by @utam0k in https://github.com/youki-dev/youki/pull/2947\n- docs: update github pages links by @tskxz in https://github.com/youki-dev/youki/pull/2969\n- switch from license-file to license by @jprendes in https://github.com/youki-dev/youki/pull/3023\n### 🧪 Test improvements and Misc Fixes\n- ci: update action versions to fix deprecation warnings by @YJDoc2 in https://github.com/youki-dev/youki/pull/2918\n- deps: update wasmedge to 0.14.0 by @YJDoc2 in https://github.com/youki-dev/youki/pull/2928\n- Bump oci-spec to 0.7.0 by @kiokuless in https://github.com/youki-dev/youki/pull/2934\n- remove incorrect dependency in readme by @YJDoc2 in https://github.com/youki-dev/youki/pull/2940\n- Add seccomp into feature flags of youki to be compiled in by @musaprg in https://github.com/youki-dev/youki/pull/2924\n- Add unittest to expertiment seccomp programs by @sat0ken in https://github.com/youki-dev/youki/pull/2956\n- print \"unknown\" instead of defaults if we cannot get kernel config by @YJDoc2 in https://github.com/youki-dev/youki/pull/2964\n- Add test process rlimits by @sat0ken in https://github.com/youki-dev/youki/pull/2977\n- Add test process user by @sat0ken in https://github.com/youki-dev/youki/pull/2978\n- add test process_oom_score_adj by @saku3 in https://github.com/youki-dev/youki/pull/2987\n- Add  process test  by @sat0ken in https://github.com/youki-dev/youki/pull/2968\n- refactor(test): refine function create_container by @xujihui1985 in https://github.com/youki-dev/youki/pull/2973\n- Add test root readonly by @sat0ken in https://github.com/youki-dev/youki/pull/2976\n- Adding Discord link to docs  by @crmejia in https://github.com/youki-dev/youki/pull/3005\n- Prepare for v0.5.0 by @utam0k in https://github.com/youki-dev/youki/pull/3016\n- Use later stable rust version 1.81.0 to fix the CI by @musaprg in https://github.com/youki-dev/youki/pull/3033\n- Don't specify the versionFile for tagpr by @utam0k in https://github.com/youki-dev/youki/pull/3036\n### Other Changes\n- selinux: create Vagrantfile for SELinux by @Gekko0114 in https://github.com/youki-dev/youki/pull/2900\n- Cargo.toml: remove unused dependnecies by @Mossaka in https://github.com/youki-dev/youki/pull/2921\n- deps: update wasmtime by @YJDoc2 in https://github.com/youki-dev/youki/pull/2929\n- selinux: fix xattr and remove anyhow by @Gekko0114 in https://github.com/youki-dev/youki/pull/2936\n- .github/workflows/basic: check unused deps on 'check' job by @Mossaka in https://github.com/youki-dev/youki/pull/2941\n- seccomp: Update experiment seccomp program by @sat0ken in https://github.com/youki-dev/youki/pull/2946\n- create mount_rootfs method by @Gekko0114 in https://github.com/youki-dev/youki/pull/2953\n- Update deps: roll multiple dependabot PRs into one by @YJDoc2 in https://github.com/youki-dev/youki/pull/2993\n\n## [v0.4.1](https://github.com/containers/youki/compare/v0.4.0...v0.4.1) - 2024-09-02\n### 🧪 Test improvements and Misc Fixes\n- prepare for version 0.4.1 by @YJDoc2 in https://github.com/containers/youki/pull/2897\n\n## [v0.4.0](https://github.com/containers/youki/compare/v0.3.3...v0.4.0) - 2024-08-23\n### 💪 Improvements\n- Export max_usage in cgroups v2 mode by @HeRaNO in https://github.com/containers/youki/pull/2802\n- Add new `setup_envs` method for the `Executor` trait by @musaprg in https://github.com/containers/youki/pull/2820\n### 💥 Breaking Changes\n- Rename to improve readability by @utam0k in https://github.com/containers/youki/pull/2818\n### 🐛 Bug Fixes\n- Fix/dbus call issue by @YJDoc2 in https://github.com/containers/youki/pull/2838\n### 📖 Documentation improvements\n- Add the governance by @utam0k in https://github.com/containers/youki/pull/2806\n- optimization runtime_tools.md doc by @lengrongfu in https://github.com/containers/youki/pull/2816\n- Update README.md by @utam0k in https://github.com/containers/youki/pull/2822\n- Fix typo by @utam0k in https://github.com/containers/youki/pull/2836\n- docs: fix `with_executor` method description by @Andreagit97 in https://github.com/containers/youki/pull/2834\n### 🧪 Test improvements and Misc Fixes\n- Update nix to 0.28.0 by @omprakaash in https://github.com/containers/youki/pull/2728\n- Fix word order in README sentence justifying Rust usage by @andrewimeson in https://github.com/containers/youki/pull/2805\n- move macro define youki_version to use before by @lengrongfu in https://github.com/containers/youki/pull/2813\n- Use HashMap for envs in container_init_process by @musaprg in https://github.com/containers/youki/pull/2817\n- Ignore linter for MOUNT_ATTR__ATIME by @yihuaf in https://github.com/containers/youki/pull/2819\n- Update go version in podman CI and vagrantfile by @YJDoc2 in https://github.com/containers/youki/pull/2828\n- Fix typos and bump version for typos ci by @Jerrypoi in https://github.com/containers/youki/pull/2839\n- Install nightly for running linter inside devcontainer by @musaprg in https://github.com/containers/youki/pull/2845\n- Add issue templates by @YJDoc2 in https://github.com/containers/youki/pull/2829\n- chore(deps): update oci-spec to v0.6.7 by @Mossaka in https://github.com/containers/youki/pull/2847\n- Bump oci-spec by @keisku in https://github.com/containers/youki/pull/2854\n- Update devcontainer.json by @keisku in https://github.com/containers/youki/pull/2857\n- Apply building best practices to `.devcontainer/Dockerfile` by @keisku in https://github.com/containers/youki/pull/2856\n- Fix markdown format in experiment/selinux/README.md by @keisku in https://github.com/containers/youki/pull/2855\n- initial progress on supporting OwnedFd by @zahash in https://github.com/containers/youki/pull/2809\n- Rust 1.80.0 by @utam0k in https://github.com/containers/youki/pull/2869\n- Update nc dependency to 0.9.2 by @posutsai in https://github.com/containers/youki/pull/2884\n- Prepare for v0.4.0 by @utam0k in https://github.com/containers/youki/pull/2880\n### Other Changes\n- Init a selinux project by @Gekko0114 in https://github.com/containers/youki/pull/2800\n- selinux: write xattr related codes. by @Gekko0114 in https://github.com/containers/youki/pull/2825\n- selinux: implemented remaining selinux functions by @Gekko0114 in https://github.com/containers/youki/pull/2850\n\n## [v0.3.3](https://github.com/containers/youki/compare/v0.3.2...v0.3.3) - 2024-05-16\n### 💪 Improvements\n- Add support for rsvd hugetlb cgroup by @omprakaash in https://github.com/containers/youki/pull/2719\n### 💥 Breaking Changes\n- Improve error reporting and logging by @YJDoc2 in https://github.com/containers/youki/pull/2705\n### 🐛 Bug Fixes\n- Fix cgroups determination in exec implementation by @YJDoc2 in https://github.com/containers/youki/pull/2720\n- Remove unnecessary chdir by @utam0k in https://github.com/containers/youki/pull/2780\n### 🧪 Test improvements and Misc Fixes\n- Rollup dep updates by @YJDoc2 in https://github.com/containers/youki/pull/2667\n- Fill in TODO by @utam0k in https://github.com/containers/youki/pull/2677\n- Fix the links of contest by @utam0k in https://github.com/containers/youki/pull/2680\n- Set '--test-threads' option to 1 in unit tests by @YJDoc2 in https://github.com/containers/youki/pull/2685\n- add io priority e2e test by @lengrongfu in https://github.com/containers/youki/pull/2646\n- (fix) podman e2e : Update workflow for new required deps, add vagrantfile by @YJDoc2 in https://github.com/containers/youki/pull/2687\n- Add missed test-threads=1 to coverage CI by @YJDoc2 in https://github.com/containers/youki/pull/2699\n- Fix integration test validation CI, make io_priority test conditional by @YJDoc2 in https://github.com/containers/youki/pull/2707\n- :memo: Remove GitPod and add link to GitHub codespaces by @homersimpsons in https://github.com/containers/youki/pull/2717\n- Limt dependabot updates to only direct dependencies by @utam0k in https://github.com/containers/youki/pull/2725\n- fix observability default log level comment by @zahash in https://github.com/containers/youki/pull/2737\n- Update deps via cargo update by @YJDoc2 in https://github.com/containers/youki/pull/2747\n- Rust 1.77.1 by @utam0k in https://github.com/containers/youki/pull/2746\n- Make our codespaces more useful by @utam0k in https://github.com/containers/youki/pull/2753\n- Fix README.md by @utam0k in https://github.com/containers/youki/pull/2759\n- update wasmtime dep to 19.0.1, replace wasmtime-wasi with wasi-common by @YJDoc2 in https://github.com/containers/youki/pull/2752\n- Reset console sockets to original in setup_console test by @YJDoc2 in https://github.com/containers/youki/pull/2764\n- Update rust version to 1.77.2 by @YJDoc2 in https://github.com/containers/youki/pull/2779\n- Add linux_devices test by @omprakaash in https://github.com/containers/youki/pull/2708\n- deps: Disable unused/unnecessary regex features in libcontainer by @jirutka in https://github.com/containers/youki/pull/2781\n- Add `rustfmt.toml` to standardize formatting by @jprendes in https://github.com/containers/youki/pull/2787\n### Other Changes\n- Rollup dep update by @YJDoc2 in https://github.com/containers/youki/pull/2674\n- Init a seccomp project by @utam0k in https://github.com/containers/youki/pull/2729\n- seccomp: Use offset_of! by @utam0k in https://github.com/containers/youki/pull/2763\n- seccomp: Add a case for checking arguments by @utam0k in https://github.com/containers/youki/pull/2775\n\n## [v0.3.2](https://github.com/containers/youki/compare/v0.3.1...v0.3.2) - 2024-01-31\n### 💪 Improvements\n- (feat) add support for `musl` using `cross-rs` by @jprendes in https://github.com/containers/youki/pull/2536\n- add schedule entity by @lengrongfu in https://github.com/containers/youki/pull/2495\n- Address GHSA-xr7r-f8xq-vfvv by @utam0k in https://github.com/containers/youki/pull/2663\n### 📖 Documentation improvements\n- fix: just instead make by @bestgopher in https://github.com/containers/youki/pull/2585\n- [doc] Update doc with `cross-rs` and `musl` builds by @jprendes in https://github.com/containers/youki/pull/2621\n### 🧪 Test improvements and Misc Fixes\n- New Releases needs approval from the maintainer by @utam0k in https://github.com/containers/youki/pull/2583\n- Updaet to Containerd 1.7.11 by @utam0k in https://github.com/containers/youki/pull/2558\n- chore(deps) bump tabwriter, windows-core, tempfile, memchr, clang-sys by @YJDoc2 in https://github.com/containers/youki/pull/2608\n- Name the test tools `contest` by @utam0k in https://github.com/containers/youki/pull/2486\n- Fix the missed naming changes in integration test validation CI by @YJDoc2 in https://github.com/containers/youki/pull/2629\n- Roll up various minor and major version dep upgrade by @YJDoc2 in https://github.com/containers/youki/pull/2638\n- Add docker-in-docker e2e test by @jprendes in https://github.com/containers/youki/pull/2645\n- Add domainname test by @higuruchi in https://github.com/containers/youki/pull/1544\n- Re enable skipped e2e tests by @YJDoc2 in https://github.com/containers/youki/pull/2647\n\n## [v0.3.1](https://github.com/containers/youki/compare/v0.3.0...v0.3.1) - 2023-12-19\n### 💪 Improvements\n- fix(libcgroups): report CPU throttling stats in 'libcgroups::v2' by @xiaoyang-sde in https://github.com/containers/youki/pull/2524\n- fix(main): support arm64 release youki by @cuisongliu in https://github.com/containers/youki/pull/2498\n### 🐛 Bug Fixes\n- Specify the protobuf crate because of the rust-criu crate by @utam0k in https://github.com/containers/youki/pull/2497\n### 📖 Documentation improvements\n- docs(main): support arm64 release docs by @cuisongliu in https://github.com/containers/youki/pull/2510\n- fix docs by @lengrongfu in https://github.com/containers/youki/pull/2550\n- docs(main): auto release node using just by @cuisongliu in https://github.com/containers/youki/pull/2537\n### 🧪 Test improvements and Misc Fixes\n- Grouping patch updates in dependabot. by @utam0k in https://github.com/containers/youki/pull/2496\n- Fix the config of the dependenda bot by @utam0k in https://github.com/containers/youki/pull/2502\n- feature(main): add release  strip by @cuisongliu in https://github.com/containers/youki/pull/2503\n- test(integration_test): port 'runtime-tools/validation/linux_sysctl' by @xiaoyang-sde in https://github.com/containers/youki/pull/2527\n- docs(libcgroup): add docs for several items in 'libcgroup::v2' by @xiaoyang-sde in https://github.com/containers/youki/pull/2525\n- test(integration_test): port 'runtime-tools/validation/linux_seccomp' by @xiaoyang-sde in https://github.com/containers/youki/pull/2531\n- fix(libcgroups): clean up 'libcgroups::v1::manager' by @xiaoyang-sde in https://github.com/containers/youki/pull/2530\n- small typo in trace message by @Pvlerick in https://github.com/containers/youki/pull/2535\n- Set up userns in a straightforward way by @utam0k in https://github.com/containers/youki/pull/2548\n- Rust 1.74.1 by @utam0k in https://github.com/containers/youki/pull/2557\n- Simplify release workflow by @jprendes in https://github.com/containers/youki/pull/2541\n- config: Automated Tagpr Update for 0.3.1 by @github-actions in https://github.com/containers/youki/pull/2571\n- Release for v0.3.1 by @github-actions in https://github.com/containers/youki/pull/2570\n- Ignore CHANGELOG.md in typos by @utam0k in https://github.com/containers/youki/pull/2572\n\n## [v0.3.1](https://github.com/containers/youki/compare/v0.3.0...v0.3.1) - 2023-12-17\n### 💪 Improvements\n- fix(libcgroups): report CPU throttling stats in 'libcgroups::v2' by @xiaoyang-sde in https://github.com/containers/youki/pull/2524\n- fix(main): support arm64 release youki by @cuisongliu in https://github.com/containers/youki/pull/2498\n### 🐛 Bug Fixes\n- Specify the protobuf crate because of the rust-criu crate by @utam0k in https://github.com/containers/youki/pull/2497\n### 📖 Documentation improvements\n- docs(main): support arm64 release docs by @cuisongliu in https://github.com/containers/youki/pull/2510\n- fix docs by @lengrongfu in https://github.com/containers/youki/pull/2550\n- docs(main): auto release node using just by @cuisongliu in https://github.com/containers/youki/pull/2537\n### 🧪 Test improvements and Misc Fixes\n- Grouping patch updates in dependabot. by @utam0k in https://github.com/containers/youki/pull/2496\n- Fix the config of the dependenda bot by @utam0k in https://github.com/containers/youki/pull/2502\n- feature(main): add release  strip by @cuisongliu in https://github.com/containers/youki/pull/2503\n- test(integration_test): port 'runtime-tools/validation/linux_sysctl' by @xiaoyang-sde in https://github.com/containers/youki/pull/2527\n- docs(libcgroup): add docs for several items in 'libcgroup::v2' by @xiaoyang-sde in https://github.com/containers/youki/pull/2525\n- test(integration_test): port 'runtime-tools/validation/linux_seccomp' by @xiaoyang-sde in https://github.com/containers/youki/pull/2531\n- fix(libcgroups): clean up 'libcgroups::v1::manager' by @xiaoyang-sde in https://github.com/containers/youki/pull/2530\n- small typo in trace message by @Pvlerick in https://github.com/containers/youki/pull/2535\n- Set up userns in a straightforward way by @utam0k in https://github.com/containers/youki/pull/2548\n- Rust 1.74.1 by @utam0k in https://github.com/containers/youki/pull/2557\n- Simplify release workflow by @jprendes in https://github.com/containers/youki/pull/2541\n- config: Automated Tagpr Update for 0.3.1 by @github-actions in https://github.com/containers/youki/pull/2571\n\n## [v0.3.0](https://github.com/containers/youki/compare/v0.2.0...v0.3.0) - 2023-10-15\n### 💪 Improvements\n- Feat/podman rootless by @YJDoc2 in https://github.com/containers/youki/pull/2370\n- feat: allow customize cgroup root path by @fengxsong in https://github.com/containers/youki/pull/2411\n### 🐛 Bug Fixes\n- Use raw syscalls to avoid sporadic hangs by @jprendes in https://github.com/containers/youki/pull/2425\n- Fix device duplication in rootfs preparation by @YJDoc2 in https://github.com/containers/youki/pull/2438\n### 📖 Documentation improvements\n- Add the documentation for debugging by @utam0k in https://github.com/containers/youki/pull/2382\n- Update the developer documentation for the e2e tests. by @utam0k in https://github.com/containers/youki/pull/2381\n- docs: update docs regarding the changes in #2411 by @fengxsong in https://github.com/containers/youki/pull/2434\n### 🧪 Test improvements and Misc Fixes\n- Change rootless required function and privilege decision by @YJDoc2 in https://github.com/containers/youki/pull/2279\n- Skip the tests related to criu when criu is not found by @utam0k in https://github.com/containers/youki/pull/2365\n- Refactor doc test and justfile by @yihuaf in https://github.com/containers/youki/pull/2330\n- Add initial tests for rootless podman by @YJDoc2 in https://github.com/containers/youki/pull/2406\n- update nix to 0.27.1 by @anti-entropy123 in https://github.com/containers/youki/pull/2369\n- Refactor test dir structure by @YJDoc2 in https://github.com/containers/youki/pull/2421\n- Use static build of wasmedge by @jprendes in https://github.com/containers/youki/pull/2420\n- v0.3.0 by @utam0k in https://github.com/containers/youki/pull/2437\n\n## [v0.2.0](https://github.com/containers/youki/compare/v0.1.0...v0.2.0) - 2023-09-01\n### 💪 Improvements\n- Liboci additional flags and subcommands, as required by ociplex by @c3d in https://github.com/containers/youki/pull/2149\n- add io priority by @lengrongfu in https://github.com/containers/youki/pull/2164\n- Implemented the clone fallback when clone3 returns ENOSYS by @yihuaf in https://github.com/containers/youki/pull/2203\n- Return an error when passing unsupported mount options by @utam0k in https://github.com/containers/youki/pull/2308\n- v0.2.0 by @utam0k in https://github.com/containers/youki/pull/2333\n### 💥 Breaking Changes\n- Use syscall type to delay the creation of syscall struct. by @yihuaf in https://github.com/containers/youki/pull/2155\n- Refactor the libcgroups interface by @yihuaf in https://github.com/containers/youki/pull/2168\n- refactored executor and executor manager by @yihuaf in https://github.com/containers/youki/pull/2186\n- Refactored the Executor interface yet again by @yihuaf in https://github.com/containers/youki/pull/2230\n- Rename the rootless struct  to UserNamespaceConfig by @YJDoc2 in https://github.com/containers/youki/pull/2257\n- move the validation logic into executor by @yihuaf in https://github.com/containers/youki/pull/2258\n### 📖 Documentation improvements\n- Add one label to generate release notes by @utam0k in https://github.com/containers/youki/pull/2122\n### 🧪 Test improvements and Misc Fixes\n- [Trivial] exclude the oci-runtime-test from the typos by @yihuaf in https://github.com/containers/youki/pull/2133\n- disable musl test for now by @yihuaf in https://github.com/containers/youki/pull/2150\n- Fix musl test function not parametered correctly by @yihuaf in https://github.com/containers/youki/pull/2158\n- Rust 1.71.0 by @utam0k in https://github.com/containers/youki/pull/2167\n- Make container_args clone-able by @yihuaf in https://github.com/containers/youki/pull/2193\n- Update CI go version to 1.20 by @YJDoc2 in https://github.com/containers/youki/pull/2227\n- Fix podman tests to properly run by @YJDoc2 in https://github.com/containers/youki/pull/2233\n- Named all GitHub Actions workflows by @utam0k in https://github.com/containers/youki/pull/2256\n- Include Breaking Changes section in the release note by @utam0k in https://github.com/containers/youki/pull/2265\n- Extend wait time for auto-merge by @utam0k in https://github.com/containers/youki/pull/2278\n- Switch codespace from gitpod by @utam0k in https://github.com/containers/youki/pull/2306\n- Rust 1.72 by @utam0k in https://github.com/containers/youki/pull/2323\n- Update Migration Guide for 0.2.0 release by @YJDoc2 in https://github.com/containers/youki/pull/2334\n### Other Changes\n- turn on musl test in CI by @yihuaf in https://github.com/containers/youki/pull/2069\n- Update wasm related deps by @YJDoc2 in https://github.com/containers/youki/pull/2087\n- Quick install guide by @utam0k in https://github.com/containers/youki/pull/2096\n- re-export oci-spec in libcontainer by @yihuaf in https://github.com/containers/youki/pull/2068\n- Increate musl CI test timeout to 20 by @YJDoc2 in https://github.com/containers/youki/pull/2143\n\n## [v0.1.0](https://github.com/containers/youki/compare/v0.0.5...v0.1.0) - 2023-06-21\n- (auto merged) chore(deps): bump cap-std from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1747\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1750\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1749\n- chore(deps): bump wasmtime from 6.0.1 to 7.0.0 by @dependabot in https://github.com/containers/youki/pull/1702\n- [Trivial] Fix makefile targets to use PHONY by @yihuaf in https://github.com/containers/youki/pull/1743\n- Fix stop container when prestart hook fails. by @yihuaf in https://github.com/containers/youki/pull/1745\n- (auto merged) chore(deps): bump system-interface from 0.25.4 to 0.25.5 by @dependabot in https://github.com/containers/youki/pull/1753\n- (auto merged) chore(deps): bump futures-core from 0.3.27 to 0.3.28 by @dependabot in https://github.com/containers/youki/pull/1754\n- (auto merged) chore(deps): bump futures-io from 0.3.27 to 0.3.28 by @dependabot in https://github.com/containers/youki/pull/1756\n- (auto merged) chore(deps): bump iana-time-zone from 0.1.54 to 0.1.55 by @dependabot in https://github.com/containers/youki/pull/1758\n- (auto merged) chore(deps): bump futures-sink from 0.3.27 to 0.3.28 by @dependabot in https://github.com/containers/youki/pull/1759\n- [Trivial] Remove the metadata semvar causing a warning. by @yihuaf in https://github.com/containers/youki/pull/1744\n- chore(deps): bump serial_test from 1.0.0 to 2.0.0 by @dependabot in https://github.com/containers/youki/pull/1755\n- (auto merged) chore(deps): bump is-terminal from 0.4.5 to 0.4.6 by @dependabot in https://github.com/containers/youki/pull/1746\n- (auto merged) chore(deps): bump cap-primitives from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1748\n- chore(deps): bump tempfile from 3.4.0 to 3.5.0 by @dependabot in https://github.com/containers/youki/pull/1751\n- (auto merged) chore(deps): bump memfd from 0.6.2 to 0.6.3 by @dependabot in https://github.com/containers/youki/pull/1752\n- (auto merged) chore(deps): bump clang-sys from 1.6.0 to 1.6.1 by @dependabot in https://github.com/containers/youki/pull/1757\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1760\n- (auto merged) chore(deps): bump rkyv from 0.7.40 to 0.7.41 by @dependabot in https://github.com/containers/youki/pull/1761\n- (auto merged) chore(deps): bump cap-std from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1762\n- (auto merged) chore(deps): bump futures from 0.3.27 to 0.3.28 by @dependabot in https://github.com/containers/youki/pull/1765\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.54 to 1.0.55 by @dependabot in https://github.com/containers/youki/pull/1763\n- (auto merged) chore(deps): bump core-foundation-sys from 0.8.3 to 0.8.4 by @dependabot in https://github.com/containers/youki/pull/1766\n- (auto merged) chore(deps): bump cap-rand from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1768\n- (auto merged) chore(deps): bump fd-lock from 3.0.10 to 3.0.11 by @dependabot in https://github.com/containers/youki/pull/1770\n- (auto merged) chore(deps): bump iana-time-zone from 0.1.55 to 0.1.56 by @dependabot in https://github.com/containers/youki/pull/1771\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.55 to 1.0.56 by @dependabot in https://github.com/containers/youki/pull/1772\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1773\n- chore(deps): bump rustix from 0.36.11 to 0.36.12 by @dependabot in https://github.com/containers/youki/pull/1774\n- (auto merged) chore(deps): bump libc from 0.2.140 to 0.2.141 by @dependabot in https://github.com/containers/youki/pull/1776\n- chore(deps): bump vergen from 7.5.1 to 8.1.1 by @dependabot in https://github.com/containers/youki/pull/1764\n- (auto merged) chore(deps): bump zstd-sys from 2.0.7+zstd.1.5.4 to 2.0.8+zstd.1.5.5 by @dependabot in https://github.com/containers/youki/pull/1778\n- (auto merged) chore(deps): bump filetime from 0.2.20 to 0.2.21 by @dependabot in https://github.com/containers/youki/pull/1780\n- Fix path to youki binary in dockerd command by @kemkemG0 in https://github.com/containers/youki/pull/1781\n- chore(deps): bump bitflags from 2.0.2 to 2.1.0 by @dependabot in https://github.com/containers/youki/pull/1779\n- Address ECHILD by @utam0k in https://github.com/containers/youki/pull/1777\n- (auto merged) chore(deps): bump io-lifetimes from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1775\n- New logo! by @utam0k in https://github.com/containers/youki/pull/1782\n- (auto merged) chore(deps): bump system-interface from 0.25.5 to 0.25.6 by @dependabot in https://github.com/containers/youki/pull/1783\n- (auto merged) chore(deps): bump getrandom from 0.2.8 to 0.2.9 by @dependabot in https://github.com/containers/youki/pull/1784\n- (auto merged) chore(deps): bump cap-rand from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1785\n- (auto merged) chore(deps): bump cap-primitives from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1787\n- (auto merged) chore(deps): bump io-extras from 0.17.2 to 0.17.4 by @dependabot in https://github.com/containers/youki/pull/1788\n- (auto merged) chore(deps): bump is-terminal from 0.4.6 to 0.4.7 by @dependabot in https://github.com/containers/youki/pull/1790\n- (auto merged) chore(deps): bump winx from 0.35.0 to 0.35.1 by @dependabot in https://github.com/containers/youki/pull/1789\n- (auto merged) chore(deps): bump fd-lock from 3.0.11 to 3.0.12 by @dependabot in https://github.com/containers/youki/pull/1786\n- Update version check in validate_spec to support 1.X.Y version.  by @utam0k in https://github.com/containers/youki/pull/1793\n- (auto merged) chore(deps): bump cap-std from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1796\n- (auto merged) chore(deps): bump crossbeam-channel from 0.5.7 to 0.5.8 by @dependabot in https://github.com/containers/youki/pull/1797\n- (auto merged) chore(deps): bump cap-rand from 1.0.10 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1798\n- (auto merged) chore(deps): bump errno from 0.3.0 to 0.3.1 by @dependabot in https://github.com/containers/youki/pull/1799\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1800\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1802\n- (auto merged) chore(deps): bump uuid from 1.3.0 to 1.3.1 by @dependabot in https://github.com/containers/youki/pull/1801\n- (auto merged) chore(deps): bump cap-primitives from 1.0.10 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1795\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.10 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1804\n- (auto merged) chore(deps): bump cap-std from 1.0.10 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1805\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.10 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1803\n- Modify pointer type from i8 to c_char by @kemkemG0 in https://github.com/containers/youki/pull/1792\n- (auto merged) chore(deps): bump serde from 1.0.159 to 1.0.160 by @dependabot in https://github.com/containers/youki/pull/1806\n- (auto merged) chore(deps): bump cap-rand from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1807\n- (auto merged) chore(deps): bump serde_json from 1.0.95 to 1.0.96 by @dependabot in https://github.com/containers/youki/pull/1809\n- (auto merged) chore(deps): bump cap-primitives from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1808\n- Add the bpftrace program file for debugging. by @utam0k in https://github.com/containers/youki/pull/1794\n- (auto merged) chore(deps): bump wat from 1.0.61 to 1.0.62 by @dependabot in https://github.com/containers/youki/pull/1813\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1814\n- (auto merged) chore(deps): bump cap-std from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1815\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1816\n- youki exec should not clean up on error by @yihuaf in https://github.com/containers/youki/pull/1818\n- (auto merged) chore(deps): bump rustc-demangle from 0.1.22 to 0.1.23 by @dependabot in https://github.com/containers/youki/pull/1820\n- (auto merged) chore(deps): bump libdbus-sys from 0.2.4 to 0.2.5 by @dependabot in https://github.com/containers/youki/pull/1821\n- (auto merged) chore(deps): bump libc from 0.2.141 to 0.2.142 by @dependabot in https://github.com/containers/youki/pull/1829\n- (auto merged) chore(deps): bump cap-primitives from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/1831\n- (auto merged) chore(deps): bump cpufeatures from 0.2.6 to 0.2.7 by @dependabot in https://github.com/containers/youki/pull/1832\n- (auto merged) chore(deps): bump system-interface from 0.25.6 to 0.25.7 by @dependabot in https://github.com/containers/youki/pull/1833\n- (auto merged) chore(deps): bump cap-rand from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/1834\n- chore(deps): bump regex from 1.7.3 to 1.8.0 by @dependabot in https://github.com/containers/youki/pull/1830\n- Fix Errno as unresolved type. by @yihuaf in https://github.com/containers/youki/pull/1836\n- (auto merged) chore(deps): bump cap-std from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/1837\n- (auto merged) chore(deps): bump syscalls from 0.6.9 to 0.6.10 by @dependabot in https://github.com/containers/youki/pull/1838\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/1839\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/1840\n- (auto merged) chore(deps): bump regex-syntax from 0.7.0 to 0.7.1 by @dependabot in https://github.com/containers/youki/pull/1842\n- (auto merged) chore(deps): bump rustix from 0.36.12 to 0.36.13 by @dependabot in https://github.com/containers/youki/pull/1841\n- (auto merged) chore(deps): bump bumpalo from 3.12.0 to 3.12.1 by @dependabot in https://github.com/containers/youki/pull/1843\n- (auto merged) chore(deps): bump regex from 1.8.0 to 1.8.1 by @dependabot in https://github.com/containers/youki/pull/1844\n- add cleanup container by @lengrongfu in https://github.com/containers/youki/pull/1824\n- chore(deps): bump wasmer from 2.3.0 to 3.2.0 by @dependabot in https://github.com/containers/youki/pull/1825\n- chore(deps): bump bitflags from 2.1.0 to 2.2.1 by @dependabot in https://github.com/containers/youki/pull/1845\n- Named process for debugging. by @utam0k in https://github.com/containers/youki/pull/1846\n- (auto merged) chore(deps): bump openssl-sys from 0.9.86 to 0.9.87 by @dependabot in https://github.com/containers/youki/pull/1848\n- (auto merged) chore(deps): bump target-lexicon from 0.12.6 to 0.12.7 by @dependabot in https://github.com/containers/youki/pull/1850\n- (auto merged) chore(deps): bump openssl from 0.10.51 to 0.10.52 by @dependabot in https://github.com/containers/youki/pull/1849\n- (auto merged) chore(deps): bump tracing-attributes from 0.1.23 to 0.1.24 by @dependabot in https://github.com/containers/youki/pull/1851\n- (auto merged) chore(deps): bump tracing from 0.1.37 to 0.1.38 by @dependabot in https://github.com/containers/youki/pull/1853\n- (auto merged) chore(deps): bump vergen from 8.1.1 to 8.1.2 by @dependabot in https://github.com/containers/youki/pull/1854\n- (auto merged) chore(deps): bump tokio-util from 0.7.7 to 0.7.8 by @dependabot in https://github.com/containers/youki/pull/1855\n- Rust 1.69.0 by @utam0k in https://github.com/containers/youki/pull/1852\n- chore(deps): bump tokio from 1.27.0 to 1.28.0 by @dependabot in https://github.com/containers/youki/pull/1856\n- chore(deps): bump wasmtime from 7.0.0 to 8.0.0 by @dependabot in https://github.com/containers/youki/pull/1835\n- Requires linux kernel 5.3 because of clone3(2) by @utam0k in https://github.com/containers/youki/pull/1857\n- Override log opt when specified more than once by @boaz-quotient in https://github.com/containers/youki/pull/1847\n- Add support to Intel RDT. by @ipuustin in https://github.com/containers/youki/pull/1822\n- (auto merged) chore(deps): bump wasmtime from 8.0.0 to 8.0.1 by @dependabot in https://github.com/containers/youki/pull/1858\n- (auto merged) chore(deps): bump wat from 1.0.62 to 1.0.63 by @dependabot in https://github.com/containers/youki/pull/1859\n- (auto merged) chore(deps): bump vergen from 8.1.2 to 8.1.3 by @dependabot in https://github.com/containers/youki/pull/1860\n- (auto merged) chore(deps): bump flate2 from 1.0.25 to 1.0.26 by @dependabot in https://github.com/containers/youki/pull/1862\n- (auto merged) chore(deps): bump uuid from 1.3.1 to 1.3.2 by @dependabot in https://github.com/containers/youki/pull/1863\n- (auto merged) chore(deps): bump reqwest from 0.11.16 to 0.11.17 by @dependabot in https://github.com/containers/youki/pull/1864\n- (auto merged) chore(deps): bump winnow from 0.4.1 to 0.4.4 by @dependabot in https://github.com/containers/youki/pull/1865\n- (auto merged) chore(deps): bump wasmtime-wasi from 8.0.0 to 8.0.1 by @dependabot in https://github.com/containers/youki/pull/1866\n- (auto merged) chore(deps): bump anyhow from 1.0.70 to 1.0.71 by @dependabot in https://github.com/containers/youki/pull/1867\n- (auto merged) chore(deps): bump winnow from 0.4.4 to 0.4.5 by @dependabot in https://github.com/containers/youki/pull/1870\n- (auto merged) chore(deps): bump enumset from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1869\n- (auto merged) chore(deps): bump anstream from 0.3.1 to 0.3.2 by @dependabot in https://github.com/containers/youki/pull/1871\n- (auto merged) chore(deps): bump winnow from 0.4.5 to 0.4.6 by @dependabot in https://github.com/containers/youki/pull/1873\n- Adopt `thiserror` for libcgroups by @squili in https://github.com/containers/youki/pull/1872\n- Update the version of containerd used for testing by @utam0k in https://github.com/containers/youki/pull/1875\n- (auto merged) chore(deps): bump slice-group-by from 0.3.0 to 0.3.1 by @dependabot in https://github.com/containers/youki/pull/1879\n- Implement `thiserror` for libcontainer - Part 1 by @yihuaf in https://github.com/containers/youki/pull/1876\n- rewrote the bpf example by @yihuaf in https://github.com/containers/youki/pull/1877\n- (auto merged) chore(deps): bump serde from 1.0.160 to 1.0.161 by @dependabot in https://github.com/containers/youki/pull/1882\n- (auto merged) chore(deps): bump pkg-config from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1878\n- chore(deps): bump wasmer-wasix from 0.3.1 to 0.4.0 by @dependabot in https://github.com/containers/youki/pull/1880\n- Refactor the lifecycle test by @yihuaf in https://github.com/containers/youki/pull/1868\n- (auto merged) chore(deps): bump libc from 0.2.142 to 0.2.143 by @dependabot in https://github.com/containers/youki/pull/1885\n- (auto merged) chore(deps): bump serde from 1.0.161 to 1.0.162 by @dependabot in https://github.com/containers/youki/pull/1886\n- Implemented more `thiserror` for libcontainer (Part 2) by @yihuaf in https://github.com/containers/youki/pull/1881\n- replaced tempdir in libcgroup by @yihuaf in https://github.com/containers/youki/pull/1888\n- Add easy way to test with K8s by @utam0k in https://github.com/containers/youki/pull/1884\n- (auto merged) chore(deps): bump libc from 0.2.143 to 0.2.144 by @dependabot in https://github.com/containers/youki/pull/1892\n- Migrate to `tempfile` for `libcontainer` and `youki` crate by @yihuaf in https://github.com/containers/youki/pull/1887\n- chore(deps): bump enumset from 1.0.13 to 1.1.1 by @dependabot in https://github.com/containers/youki/pull/1893\n- (auto merged) chore(deps): bump quote from 1.0.26 to 1.0.27 by @dependabot in https://github.com/containers/youki/pull/1894\n- (auto merged) chore(deps): bump js-sys from 0.3.61 to 0.3.62 by @dependabot in https://github.com/containers/youki/pull/1896\n- (auto merged) chore(deps): bump bumpalo from 3.12.1 to 3.12.2 by @dependabot in https://github.com/containers/youki/pull/1898\n- Migrate integration test to use tempfile by @yihuaf in https://github.com/containers/youki/pull/1891\n- Implement `thiserror` for libcontainer - Part 3 by @yihuaf in https://github.com/containers/youki/pull/1895\n- (auto merged) chore(deps): bump tokio from 1.28.0 to 1.28.1 by @dependabot in https://github.com/containers/youki/pull/1903\n- (auto merged) chore(deps): bump enumset from 1.1.1 to 1.1.2 by @dependabot in https://github.com/containers/youki/pull/1902\n- (auto merged) chore(deps): bump wasm-bindgen-futures from 0.4.34 to 0.4.35 by @dependabot in https://github.com/containers/youki/pull/1904\n- (auto merged) chore(deps): bump web-sys from 0.3.61 to 0.3.62 by @dependabot in https://github.com/containers/youki/pull/1905\n- (auto merged) chore(deps): bump enumset_derive from 0.8.0 to 0.8.1 by @dependabot in https://github.com/containers/youki/pull/1906\n- Docs: Update readme by @njucjc in https://github.com/containers/youki/pull/1907\n- deps: do not use chrono default-features. by @ipuustin in https://github.com/containers/youki/pull/1900\n- (auto merged) chore(deps): bump serde from 1.0.162 to 1.0.163 by @dependabot in https://github.com/containers/youki/pull/1909\n- (auto merged) chore(deps): bump tracing-core from 0.1.30 to 0.1.31 by @dependabot in https://github.com/containers/youki/pull/1910\n- [Trivial] fix dependency for fedora by @yihuaf in https://github.com/containers/youki/pull/1908\n- convert youki to use tracing by @yihuaf in https://github.com/containers/youki/pull/1899\n- Use safe_path crate instead of our original secure_join by @utam0k in https://github.com/containers/youki/pull/1911\n- (auto merged) chore(deps): bump rkyv_derive from 0.7.41 to 0.7.42 by @dependabot in https://github.com/containers/youki/pull/1913\n- (auto merged) chore(deps): bump bytecheck_derive from 0.6.10 to 0.6.11 by @dependabot in https://github.com/containers/youki/pull/1914\n- (auto merged) chore(deps): bump rkyv from 0.7.41 to 0.7.42 by @dependabot in https://github.com/containers/youki/pull/1916\n- (auto merged) chore(deps): bump h2 from 0.3.18 to 0.3.19 by @dependabot in https://github.com/containers/youki/pull/1918\n- (auto merged) chore(deps): bump iana-time-zone-haiku from 0.1.1 to 0.1.2 by @dependabot in https://github.com/containers/youki/pull/1919\n- chore(deps): bump security-framework-sys from 2.8.0 to 2.9.0 by @dependabot in https://github.com/containers/youki/pull/1920\n- chore(deps): bump pin-project from 1.0.12 to 1.1.0 by @dependabot in https://github.com/containers/youki/pull/1917\n- chore(deps): bump wasmedge-sdk from 0.7.1 to 0.8.1 by @dependabot in https://github.com/containers/youki/pull/1915\n- Implemented `thiserror` for libcontainer - Part 4 by @yihuaf in https://github.com/containers/youki/pull/1912\n- (auto merged) chore(deps): bump js-sys from 0.3.62 to 0.3.63 by @dependabot in https://github.com/containers/youki/pull/1922\n- (auto merged) chore(deps): bump wat from 1.0.63 to 1.0.64 by @dependabot in https://github.com/containers/youki/pull/1923\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.56 to 1.0.57 by @dependabot in https://github.com/containers/youki/pull/1925\n- (auto merged) chore(deps): bump uuid from 1.3.2 to 1.3.3 by @dependabot in https://github.com/containers/youki/pull/1927\n- (auto merged) chore(deps): bump wasm-bindgen from 0.2.85 to 0.2.86 by @dependabot in https://github.com/containers/youki/pull/1926\n- chore(deps): bump security-framework from 2.8.2 to 2.9.0 by @dependabot in https://github.com/containers/youki/pull/1924\n- (auto merged) chore(deps): bump web-sys from 0.3.62 to 0.3.63 by @dependabot in https://github.com/containers/youki/pull/1931\n- (auto merged) chore(deps): bump cap-primitives from 1.0.14 to 1.0.15 by @dependabot in https://github.com/containers/youki/pull/1932\n- (auto merged) chore(deps): bump wasm-bindgen-futures from 0.4.35 to 0.4.36 by @dependabot in https://github.com/containers/youki/pull/1933\n- (auto merged) chore(deps): bump reqwest from 0.11.17 to 0.11.18 by @dependabot in https://github.com/containers/youki/pull/1934\n- (auto merged) chore(deps): bump cap-rand from 1.0.14 to 1.0.15 by @dependabot in https://github.com/containers/youki/pull/1935\n- Fixed typo by @CreepyPvP in https://github.com/containers/youki/pull/1928\n- implemented thiserror for containers - Part 5 by @yihuaf in https://github.com/containers/youki/pull/1930\n- main_process: close the channel receivers. by @ipuustin in https://github.com/containers/youki/pull/1936\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.14 to 1.0.15 by @dependabot in https://github.com/containers/youki/pull/1938\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.57 to 1.0.58 by @dependabot in https://github.com/containers/youki/pull/1939\n- (auto merged) chore(deps): bump syscalls from 0.6.10 to 0.6.11 by @dependabot in https://github.com/containers/youki/pull/1940\n- (auto merged) chore(deps): bump cap-time-ext from 1.0.14 to 1.0.15 by @dependabot in https://github.com/containers/youki/pull/1941\n- chore(deps): bump bitflags from 2.2.1 to 2.3.1 by @dependabot in https://github.com/containers/youki/pull/1943\n- (auto merged) chore(deps): bump security-framework from 2.9.0 to 2.9.1 by @dependabot in https://github.com/containers/youki/pull/1944\n- (auto merged) chore(deps): bump toml_edit from 0.19.8 to 0.19.9 by @dependabot in https://github.com/containers/youki/pull/1945\n- Finally, remove `anyhow` from the libcontainer dependency. by @yihuaf in https://github.com/containers/youki/pull/1937\n- Simplified syscall error by @yihuaf in https://github.com/containers/youki/pull/1949\n- (auto merged) chore(deps): bump rustix from 0.36.13 to 0.36.14 by @dependabot in https://github.com/containers/youki/pull/1952\n- (auto merged) chore(deps): bump digest from 0.10.6 to 0.10.7 by @dependabot in https://github.com/containers/youki/pull/1954\n- (auto merged) chore(deps): bump base64 from 0.21.0 to 0.21.1 by @dependabot in https://github.com/containers/youki/pull/1955\n- chore(deps): bump vergen from 8.1.3 to 8.2.0 by @dependabot in https://github.com/containers/youki/pull/1953\n- (auto merged) chore(deps): bump regex from 1.8.1 to 1.8.2 by @dependabot in https://github.com/containers/youki/pull/1958\n- chore(deps): bump bumpalo from 3.12.2 to 3.13.0 by @dependabot in https://github.com/containers/youki/pull/1957\n- Fix the test to not use sigkill by @yihuaf in https://github.com/containers/youki/pull/1948\n- (auto merged) chore(deps): bump wat from 1.0.64 to 1.0.65 by @dependabot in https://github.com/containers/youki/pull/1962\n- (auto merged) chore(deps): bump toml_edit from 0.19.9 to 0.19.10 by @dependabot in https://github.com/containers/youki/pull/1961\n- chore(deps): bump wasmtime from 8.0.1 to 9.0.1 by @dependabot in https://github.com/containers/youki/pull/1959\n- Update dependencies described in docs by @l0rem1psum in https://github.com/containers/youki/pull/1960\n- (auto merged) chore(deps): bump io-lifetimes from 1.0.10 to 1.0.11 by @dependabot in https://github.com/containers/youki/pull/1964\n- (auto merged) chore(deps): bump vergen from 8.2.0 to 8.2.1 by @dependabot in https://github.com/containers/youki/pull/1965\n- (auto merged) chore(deps): bump unicode-ident from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1966\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.58 to 1.0.59 by @dependabot in https://github.com/containers/youki/pull/1970\n- (auto merged) chore(deps): bump quote from 1.0.27 to 1.0.28 by @dependabot in https://github.com/containers/youki/pull/1971\n- (auto merged) chore(deps): bump regex from 1.8.2 to 1.8.3 by @dependabot in https://github.com/containers/youki/pull/1972\n- (auto merged) chore(deps): bump base64 from 0.21.1 to 0.21.2 by @dependabot in https://github.com/containers/youki/pull/1968\n- Add some clean up that improves coverage by @yihuaf in https://github.com/containers/youki/pull/1963\n- (auto merged) chore(deps): bump mio from 0.8.6 to 0.8.7 by @dependabot in https://github.com/containers/youki/pull/1976\n- (auto merged) chore(deps): bump syscalls from 0.6.11 to 0.6.12 by @dependabot in https://github.com/containers/youki/pull/1977\n- (auto merged) chore(deps): bump tokio from 1.28.1 to 1.28.2 by @dependabot in https://github.com/containers/youki/pull/1978\n- (auto merged) chore(deps): bump wat from 1.0.65 to 1.0.66 by @dependabot in https://github.com/containers/youki/pull/1980\n- (auto merged) chore(deps): bump log from 0.4.17 to 0.4.18 by @dependabot in https://github.com/containers/youki/pull/1982\n- (auto merged) chore(deps): bump webc from 5.0.0 to 5.0.2 by @dependabot in https://github.com/containers/youki/pull/1983\n- (auto merged) chore(deps): bump wasmtime from 9.0.1 to 9.0.2 by @dependabot in https://github.com/containers/youki/pull/1981\n- (auto merged) chore(deps): bump cranelift-control from 0.96.1 to 0.96.2 by @dependabot in https://github.com/containers/youki/pull/1979\n- deprecate crossbeam since it is merged with std by @yihuaf in https://github.com/containers/youki/pull/1984\n- (auto merged) chore(deps): bump once_cell from 1.17.1 to 1.17.2 by @dependabot in https://github.com/containers/youki/pull/1985\n- (auto merged) chore(deps): bump wasmtime-wasi from 9.0.1 to 9.0.2 by @dependabot in https://github.com/containers/youki/pull/1986\n- (auto merged) chore(deps): bump chrono from 0.4.24 to 0.4.25 by @dependabot in https://github.com/containers/youki/pull/1987\n- (auto merged) chore(deps): bump openssl from 0.10.52 to 0.10.53 by @dependabot in https://github.com/containers/youki/pull/1988\n- (auto merged) chore(deps): bump mio from 0.8.7 to 0.8.8 by @dependabot in https://github.com/containers/youki/pull/1989\n- (auto merged) chore(deps): bump chrono from 0.4.25 to 0.4.26 by @dependabot in https://github.com/containers/youki/pull/1990\n- Implemented sending logs to systemd-journald by @yihuaf in https://github.com/containers/youki/pull/1975\n- chore(deps): bump rbpf from 0.1.0 to 0.2.0 by @dependabot in https://github.com/containers/youki/pull/1994\n- (auto merged) chore(deps): bump openssl from 0.10.53 to 0.10.54 by @dependabot in https://github.com/containers/youki/pull/1998\n- (auto merged) chore(deps): bump aho-corasick from 1.0.1 to 1.0.2 by @dependabot in https://github.com/containers/youki/pull/2002\n- (auto merged) chore(deps): bump libc from 0.2.144 to 0.2.145 by @dependabot in https://github.com/containers/youki/pull/2003\n- do not log error in the syscall crate by @yihuaf in https://github.com/containers/youki/pull/1973\n- chore(deps): bump once_cell from 1.17.2 to 1.18.0 by @dependabot in https://github.com/containers/youki/pull/2001\n- (auto merged) chore(deps): bump wasmtime from 9.0.2 to 9.0.3 by @dependabot in https://github.com/containers/youki/pull/1993\n- (auto merged) chore(deps): bump cranelift-control from 0.96.2 to 0.96.3 by @dependabot in https://github.com/containers/youki/pull/1995\n- Replace Makefiles with Just by @YJDoc2 in https://github.com/containers/youki/pull/1823\n- (auto merged) chore(deps): bump wasmtime-wasi from 9.0.2 to 9.0.3 by @dependabot in https://github.com/containers/youki/pull/2006\n- (auto merged) chore(deps): bump lock_api from 0.4.9 to 0.4.10 by @dependabot in https://github.com/containers/youki/pull/2008\n- (auto merged) chore(deps): bump parking_lot_core from 0.9.7 to 0.9.8 by @dependabot in https://github.com/containers/youki/pull/2009\n- chore(deps): bump percent-encoding from 2.2.0 to 2.3.0 by @dependabot in https://github.com/containers/youki/pull/2010\n- chore(deps): bump url from 2.3.1 to 2.4.0 by @dependabot in https://github.com/containers/youki/pull/2011\n- (auto merged) chore(deps): bump regex from 1.8.3 to 1.8.4 by @dependabot in https://github.com/containers/youki/pull/2007\n- Do not try to acquire capabilities we are not allowed to by @jprendes in https://github.com/containers/youki/pull/2000\n- (auto merged) chore(deps): bump getrandom from 0.2.9 to 0.2.10 by @dependabot in https://github.com/containers/youki/pull/2014\n- (auto merged) chore(deps): bump webc from 5.0.2 to 5.0.3 by @dependabot in https://github.com/containers/youki/pull/2015\n- (auto merged) chore(deps): bump object from 0.30.3 to 0.30.4 by @dependabot in https://github.com/containers/youki/pull/2017\n- (auto merged) chore(deps): bump libc from 0.2.145 to 0.2.146 by @dependabot in https://github.com/containers/youki/pull/2016\n- Refactor CI by @yihuaf in https://github.com/containers/youki/pull/2012\n- add rsymfollow recursive mount test by @adrianalin in https://github.com/containers/youki/pull/1967\n- chore(deps): bump tempfile from 3.5.0 to 3.6.0 by @dependabot in https://github.com/containers/youki/pull/2013\n- (auto merged) chore(deps): bump iana-time-zone from 0.1.56 to 0.1.57 by @dependabot in https://github.com/containers/youki/pull/2020\n- (auto merged) chore(deps): bump shared-buffer from 0.1.2 to 0.1.3 by @dependabot in https://github.com/containers/youki/pull/2021\n- Using `typos-cli` to catch typos + fixes for existing typos by @yihuaf in https://github.com/containers/youki/pull/2018\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.59 to 1.0.60 by @dependabot in https://github.com/containers/youki/pull/2023\n- (auto merged) chore(deps): bump serde from 1.0.163 to 1.0.164 by @dependabot in https://github.com/containers/youki/pull/2024\n- (auto merged) chore(deps): bump webc from 5.0.3 to 5.0.4 by @dependabot in https://github.com/containers/youki/pull/2025\n- Bump the oci-spec-rs to 0.6.1 to resolve seccomp rule issue by @yihuaf in https://github.com/containers/youki/pull/2029\n- Add the test with kind to github action by @utam0k in https://github.com/containers/youki/pull/2027\n- (auto merged) chore(deps): bump log from 0.4.18 to 0.4.19 by @dependabot in https://github.com/containers/youki/pull/2033\n- (auto merged) chore(deps): bump tokio-rustls from 0.24.0 to 0.24.1 by @dependabot in https://github.com/containers/youki/pull/2035\n- Don't create a file when it already exists when mounting with bind by @utam0k in https://github.com/containers/youki/pull/2031\n- chore(deps): bump fastrand from 1.9.0 to 2.0.0 by @dependabot in https://github.com/containers/youki/pull/2032\n- Update podman test workflow for new justfile setup by @YJDoc2 in https://github.com/containers/youki/pull/2037\n- (auto merged) chore(deps): bump crossbeam-epoch from 0.9.14 to 0.9.15 by @dependabot in https://github.com/containers/youki/pull/2040\n- (auto merged) chore(deps): bump wasm-bindgen from 0.2.86 to 0.2.87 by @dependabot in https://github.com/containers/youki/pull/2041\n- (auto merged) chore(deps): bump js-sys from 0.3.63 to 0.3.64 by @dependabot in https://github.com/containers/youki/pull/2042\n- (auto merged) chore(deps): bump crossbeam-utils from 0.8.15 to 0.8.16 by @dependabot in https://github.com/containers/youki/pull/2043\n- (auto merged) chore(deps): bump arrayvec from 0.7.2 to 0.7.3 by @dependabot in https://github.com/containers/youki/pull/2044\n- (auto merged) chore(deps): bump uuid from 1.3.3 to 1.3.4 by @dependabot in https://github.com/containers/youki/pull/2047\n- (auto merged) chore(deps): bump web-sys from 0.3.63 to 0.3.64 by @dependabot in https://github.com/containers/youki/pull/2048\n- (auto merged) chore(deps): bump cranelift-control from 0.96.3 to 0.96.4 by @dependabot in https://github.com/containers/youki/pull/2049\n- (auto merged) chore(deps): bump bitflags from 2.3.1 to 2.3.2 by @dependabot in https://github.com/containers/youki/pull/2050\n- (auto merged) chore(deps): bump wasm-bindgen-futures from 0.4.36 to 0.4.37 by @dependabot in https://github.com/containers/youki/pull/2051\n- (auto merged) chore(deps): bump wasmtime from 9.0.3 to 9.0.4 by @dependabot in https://github.com/containers/youki/pull/2052\n- (auto merged) chore(deps): bump winnow from 0.4.6 to 0.4.7 by @dependabot in https://github.com/containers/youki/pull/2054\n- (auto merged) chore(deps): bump rustls from 0.21.1 to 0.21.2 by @dependabot in https://github.com/containers/youki/pull/2056\n- (auto merged) chore(deps): bump want from 0.3.0 to 0.3.1 by @dependabot in https://github.com/containers/youki/pull/2053\n- (auto merged) chore(deps): bump wasmtime-wasi from 9.0.3 to 9.0.4 by @dependabot in https://github.com/containers/youki/pull/2055\n- (auto merged) chore(deps): bump sha2 from 0.10.6 to 0.10.7 by @dependabot in https://github.com/containers/youki/pull/2058\n- (auto merged) chore(deps): bump cpufeatures from 0.2.7 to 0.2.8 by @dependabot in https://github.com/containers/youki/pull/2059\n- Introduce a `log-level` flag. by @yihuaf in https://github.com/containers/youki/pull/2036\n- (auto merged) chore(deps): bump serde_json from 1.0.96 to 1.0.97 by @dependabot in https://github.com/containers/youki/pull/2062\n- (auto merged) chore(deps): bump arrayvec from 0.7.3 to 0.7.4 by @dependabot in https://github.com/containers/youki/pull/2063\n- Fix the feature test and turn on in CI by @yihuaf in https://github.com/containers/youki/pull/2060\n- (auto merged) chore(deps): bump tracing-attributes from 0.1.24 to 0.1.25 by @dependabot in https://github.com/containers/youki/pull/2067\n- v0.1.0 by @utam0k in https://github.com/containers/youki/pull/2061\n- Fix the release workflow by @utam0k in https://github.com/containers/youki/pull/2070\n- (auto merged) chore(deps): bump openssl-sys from 0.9.88 to 0.9.90 by @dependabot in https://github.com/containers/youki/pull/2071\n- (auto merged) chore(deps): bump target-lexicon from 0.12.7 to 0.12.8 by @dependabot in https://github.com/containers/youki/pull/2072\n- (auto merged) chore(deps): bump anstyle from 1.0.0 to 1.0.1 by @dependabot in https://github.com/containers/youki/pull/2074\n- (auto merged) chore(deps): bump anstyle-parse from 0.2.0 to 0.2.1 by @dependabot in https://github.com/containers/youki/pull/2075\n- (auto merged) chore(deps): bump openssl from 0.10.54 to 0.10.55 by @dependabot in https://github.com/containers/youki/pull/2073\n\n## [v0.0.5](https://github.com/containers/youki/compare/v0.0.4...v0.0.5) - 2023-03-29\n- Support recursive mount attrs by using mount_setattr(2). by @higuruchi in https://github.com/containers/youki/pull/1398\n- chore(deps): bump procfs from 0.14.1 to 0.14.2 by @dependabot in https://github.com/containers/youki/pull/1391\n- chore(deps): bump io-lifetimes from 1.0.1 to 1.0.3 by @dependabot in https://github.com/containers/youki/pull/1411\n- chore(deps): bump backtrace from 0.3.66 to 0.3.67 by @dependabot in https://github.com/containers/youki/pull/1412\n- chore(deps): bump rustix from 0.36.4 to 0.36.5 by @dependabot in https://github.com/containers/youki/pull/1413\n- chore(deps): bump linux-raw-sys from 0.1.3 to 0.1.4 by @dependabot in https://github.com/containers/youki/pull/1414\n- chore(deps): bump predicates from 2.1.3 to 2.1.4 by @dependabot in https://github.com/containers/youki/pull/1395\n- chore(deps): bump filetime from 0.2.18 to 0.2.19 by @dependabot in https://github.com/containers/youki/pull/1399\n- chore(deps): bump libc from 0.2.133 to 0.2.138 by @dependabot in https://github.com/containers/youki/pull/1392\n- chore(deps): bump cc from 1.0.77 to 1.0.78 by @dependabot in https://github.com/containers/youki/pull/1416\n- chore(deps): bump thiserror from 1.0.37 to 1.0.38 by @dependabot in https://github.com/containers/youki/pull/1425\n- chore(deps): bump unicode-ident from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1423\n- chore(deps): bump ryu from 1.0.11 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1421\n- chore(deps): bump paste from 1.0.10 to 1.0.11 by @dependabot in https://github.com/containers/youki/pull/1420\n- chore(deps): bump serial_test from 0.9.0 to 0.10.0 by @dependabot in https://github.com/containers/youki/pull/1417\n- chore(deps): bump scratch from 1.0.2 to 1.0.3 by @dependabot in https://github.com/containers/youki/pull/1418\n- chore(deps): bump serde_repr from 0.1.9 to 0.1.10 by @dependabot in https://github.com/containers/youki/pull/1419\n- chore(deps): bump proc-macro2 from 1.0.47 to 1.0.49 by @dependabot in https://github.com/containers/youki/pull/1422\n- chore(deps): bump quote from 1.0.21 to 1.0.23 by @dependabot in https://github.com/containers/youki/pull/1430\n- chore(deps): bump cxx-build from 1.0.83 to 1.0.85 by @dependabot in https://github.com/containers/youki/pull/1435\n- chore(deps): bump proc-macro-hack from 0.5.19 to 0.5.20+deprecated by @dependabot in https://github.com/containers/youki/pull/1429\n- chore(deps): bump syn from 1.0.105 to 1.0.107 by @dependabot in https://github.com/containers/youki/pull/1431\n- chore(deps): bump link-cplusplus from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1432\n- chore(deps): bump serde_json from 1.0.89 to 1.0.91 by @dependabot in https://github.com/containers/youki/pull/1433\n- chore(deps): bump rustversion from 1.0.9 to 1.0.11 by @dependabot in https://github.com/containers/youki/pull/1434\n- chore(deps): bump cxx from 1.0.83 to 1.0.85 by @dependabot in https://github.com/containers/youki/pull/1436\n- chore(deps): bump serde_bytes from 0.11.7 to 0.11.8 by @dependabot in https://github.com/containers/youki/pull/1437\n- chore(deps): bump is-terminal from 0.4.1 to 0.4.2 by @dependabot in https://github.com/containers/youki/pull/1438\n- chore(deps): bump num_cpus from 1.14.0 to 1.15.0 by @dependabot in https://github.com/containers/youki/pull/1439\n- chore(deps): bump itoa from 1.0.4 to 1.0.5 by @dependabot in https://github.com/containers/youki/pull/1440\n- chore(deps): bump serde from 1.0.150 to 1.0.151 by @dependabot in https://github.com/containers/youki/pull/1441\n- Update  Rust 1.66 by @utam0k in https://github.com/containers/youki/pull/1444\n- Add support for wasmtime by @Furisto in https://github.com/containers/youki/pull/1402\n- chore(deps): bump serde from 1.0.151 to 1.0.152 by @dependabot in https://github.com/containers/youki/pull/1445\n- chore(deps): bump predicates from 2.1.4 to 2.1.5 by @dependabot in https://github.com/containers/youki/pull/1450\n- Upgrade clap to v4 by @Overflow0xFFFF in https://github.com/containers/youki/pull/1443\n- chore(deps): bump once_cell from 1.16.0 to 1.17.0 by @dependabot in https://github.com/containers/youki/pull/1451\n- skip cgroup v2 test of oci-tools by @utam0k in https://github.com/containers/youki/pull/1406\n- chore(deps): bump nom from 7.1.1 to 7.1.2 by @dependabot in https://github.com/containers/youki/pull/1453\n- Update wasmtime v4.0.0 by @utam0k in https://github.com/containers/youki/pull/1452\n- chore(deps): bump libdbus-sys from 0.2.2 to 0.2.3 by @dependabot in https://github.com/containers/youki/pull/1461\n- chore(deps): bump cxx from 1.0.85 to 1.0.86 by @dependabot in https://github.com/containers/youki/pull/1459\n- chore(deps): bump vergen from 7.4.2 to 7.5.0 by @dependabot in https://github.com/containers/youki/pull/1455\n- chore(deps): bump async-trait from 0.1.60 to 0.1.61 by @dependabot in https://github.com/containers/youki/pull/1465\n- chore(deps): bump regex from 1.7.0 to 1.7.1 by @dependabot in https://github.com/containers/youki/pull/1464\n- chore(deps): bump glob from 0.3.0 to 0.3.1 by @dependabot in https://github.com/containers/youki/pull/1463\n- chore(deps): bump ipnet from 2.7.0 to 2.7.1 by @dependabot in https://github.com/containers/youki/pull/1460\n- chore(deps): bump dbus from 0.9.6 to 0.9.7 by @dependabot in https://github.com/containers/youki/pull/1457\n- chore(deps): bump file-per-thread-logger from 0.1.5 to 0.1.6 by @dependabot in https://github.com/containers/youki/pull/1456\n- chore(deps): bump zstd-sys from 2.0.4+zstd.1.5.2 to 2.0.5+zstd.1.5.2 by @dependabot in https://github.com/containers/youki/pull/1471\n- chore(deps): bump parking_lot_core from 0.9.5 to 0.9.6 by @dependabot in https://github.com/containers/youki/pull/1473\n- chore(deps): bump cxx-build from 1.0.85 to 1.0.86 by @dependabot in https://github.com/containers/youki/pull/1470\n- chore(deps): bump sysinfo from 0.27.2 to 0.27.5 by @dependabot in https://github.com/containers/youki/pull/1469\n- chore(deps): bump libbpf-sys from 1.0.4+v1.0.1 to 1.1.1+v1.1.0 by @dependabot in https://github.com/containers/youki/pull/1468\n- chore(deps): bump system-interface from 0.25.2 to 0.25.3 by @dependabot in https://github.com/containers/youki/pull/1466\n- chore(deps): bump libgit2-sys from 0.14.0+1.5.0 to 0.14.1+1.5.0 by @dependabot in https://github.com/containers/youki/pull/1467\n- chore(deps): bump sysinfo from 0.27.5 to 0.27.6 by @dependabot in https://github.com/containers/youki/pull/1477\n- chore(deps): bump clap_lex from 0.3.0 to 0.3.1 by @dependabot in https://github.com/containers/youki/pull/1480\n- chore(deps): bump nom from 7.1.2 to 7.1.3 by @dependabot in https://github.com/containers/youki/pull/1479\n- chore(deps): bump termcolor from 1.1.3 to 1.2.0 by @dependabot in https://github.com/containers/youki/pull/1478\n- chore(deps): bump io-lifetimes from 1.0.3 to 1.0.4 by @dependabot in https://github.com/containers/youki/pull/1475\n- chore(deps): bump proc-macro2 from 1.0.49 to 1.0.50 by @dependabot in https://github.com/containers/youki/pull/1482\n- chore(deps): bump sysinfo from 0.27.6 to 0.27.7 by @dependabot in https://github.com/containers/youki/pull/1489\n- chore(deps): bump serial_test from 0.10.0 to 1.0.0 by @dependabot in https://github.com/containers/youki/pull/1488\n- chore(deps): bump bumpalo from 3.11.1 to 3.12.0 by @dependabot in https://github.com/containers/youki/pull/1487\n- chore(deps): bump wat from 1.0.52 to 1.0.53 by @dependabot in https://github.com/containers/youki/pull/1485\n- chore(deps): bump windows_x86_64_gnullvm from 0.42.0 to 0.42.1 by @dependabot in https://github.com/containers/youki/pull/1474\n- Automating Dependabot with GitHub Actions by @utam0k in https://github.com/containers/youki/pull/1481\n- chore(deps): bump oci-spec from 0.5.8 to 0.6.0 by @dependabot in https://github.com/containers/youki/pull/1491\n- chore(deps): bump rustix from 0.36.6 to 0.36.7 by @dependabot in https://github.com/containers/youki/pull/1492\n- (auto merged) chore(deps): bump ittapi from 0.3.2 to 0.3.3 by @dependabot in https://github.com/containers/youki/pull/1495\n- (auto merged) chore(deps): bump unicode-bidi from 0.3.8 to 0.3.9 by @dependabot in https://github.com/containers/youki/pull/1496\n- (auto merged) chore(deps): bump ittapi-sys from 0.3.2 to 0.3.3 by @dependabot in https://github.com/containers/youki/pull/1497\n- chore(deps): bump rust-criu from 0.2.0 to 0.4.0 by @dependabot in https://github.com/containers/youki/pull/1494\n- chore(deps): bump cap-rand from 1.0.3 to 1.0.4 by @dependabot in https://github.com/containers/youki/pull/1490\n- chore(deps): bump anyhow from 1.0.65 to 1.0.68 by @dependabot in https://github.com/containers/youki/pull/1424\n- chore(deps): bump windows_aarch64_gnullvm from 0.42.0 to 0.42.1 by @dependabot in https://github.com/containers/youki/pull/1476\n- chore(deps): bump libc from 0.2.138 to 0.2.139 by @dependabot in https://github.com/containers/youki/pull/1442\n- (auto merged) chore(deps): bump libgit2-sys from 0.14.1+1.5.0 to 0.14.2+1.5.1 by @dependabot in https://github.com/containers/youki/pull/1498\n- (auto merged) chore(deps): bump cxx from 1.0.86 to 1.0.87 by @dependabot in https://github.com/containers/youki/pull/1501\n- (auto merged) chore(deps): bump rayon-core from 1.10.1 to 1.10.2 by @dependabot in https://github.com/containers/youki/pull/1504\n- (auto merged) chore(deps): bump wat from 1.0.53 to 1.0.55 by @dependabot in https://github.com/containers/youki/pull/1506\n- (auto merged) chore(deps): bump cxx-build from 1.0.86 to 1.0.87 by @dependabot in https://github.com/containers/youki/pull/1507\n- (auto merged) chore(deps): bump async-trait from 0.1.61 to 0.1.63 by @dependabot in https://github.com/containers/youki/pull/1509\n- (auto merged) chore(deps): bump toml from 0.5.10 to 0.5.11 by @dependabot in https://github.com/containers/youki/pull/1508\n- (auto merged) chore(deps): bump unicode-bidi from 0.3.9 to 0.3.10 by @dependabot in https://github.com/containers/youki/pull/1510\n- chore(deps): bump which from 4.3.0 to 4.4.0 by @dependabot in https://github.com/containers/youki/pull/1503\n- (auto merged) chore(deps): bump fd-lock from 3.0.8 to 3.0.9 by @dependabot in https://github.com/containers/youki/pull/1512\n- Add descriptors.json when creating checkpoint by @adrianreber in https://github.com/containers/youki/pull/1511\n- Relax the version of some crates we dependent by @utam0k in https://github.com/containers/youki/pull/1500\n- (auto merged) chore(deps): bump wat from 1.0.55 to 1.0.56 by @dependabot in https://github.com/containers/youki/pull/1513\n- (auto merged) chore(deps): bump either from 1.8.0 to 1.8.1 by @dependabot in https://github.com/containers/youki/pull/1514\n- (auto merged) chore(deps): bump cc from 1.0.78 to 1.0.79 by @dependabot in https://github.com/containers/youki/pull/1518\n- (auto merged) chore(deps): bump cxx from 1.0.87 to 1.0.88 by @dependabot in https://github.com/containers/youki/pull/1519\n- (auto merged) chore(deps): bump cxx-build from 1.0.87 to 1.0.88 by @dependabot in https://github.com/containers/youki/pull/1520\n- Added recursive mount attr test by @higuruchi in https://github.com/containers/youki/pull/1428\n- (auto merged) chore(deps): bump futures-core from 0.3.25 to 0.3.26 by @dependabot in https://github.com/containers/youki/pull/1521\n- (auto merged) chore(deps): bump futures from 0.3.25 to 0.3.26 by @dependabot in https://github.com/containers/youki/pull/1522\n- (auto merged) chore(deps): bump zstd-sys from 2.0.5+zstd.1.5.2 to 2.0.6+zstd.1.5.2 by @dependabot in https://github.com/containers/youki/pull/1531\n- Fix formatting with `cargo fmt --check` by @rumpl in https://github.com/containers/youki/pull/1532\n- (auto merged) chore(deps): bump futures-sink from 0.3.25 to 0.3.26 by @dependabot in https://github.com/containers/youki/pull/1527\n- (auto merged) chore(deps): bump async-trait from 0.1.63 to 0.1.64 by @dependabot in https://github.com/containers/youki/pull/1528\n- (auto merged) chore(deps): bump cxx-build from 1.0.88 to 1.0.89 by @dependabot in https://github.com/containers/youki/pull/1535\n- (auto merged) chore(deps): bump wasm-bindgen from 0.2.83 to 0.2.84 by @dependabot in https://github.com/containers/youki/pull/1536\n- (auto merged) chore(deps): bump cxx from 1.0.88 to 1.0.89 by @dependabot in https://github.com/containers/youki/pull/1537\n- chore(deps): bump uuid from 1.2.2 to 1.3.0 by @dependabot in https://github.com/containers/youki/pull/1538\n- (auto merged) chore(deps): bump miniz_oxide from 0.6.2 to 0.6.4 by @dependabot in https://github.com/containers/youki/pull/1539\n- (auto merged) chore(deps): bump js-sys from 0.3.60 to 0.3.61 by @dependabot in https://github.com/containers/youki/pull/1540\n- (auto merged) chore(deps): bump heck from 0.4.0 to 0.4.1 by @dependabot in https://github.com/containers/youki/pull/1541\n- Update to rust 1.67 by @Furisto in https://github.com/containers/youki/pull/1516\n- chore(deps): bump pnet_datalink from 0.31.0 to 0.33.0 by @dependabot in https://github.com/containers/youki/pull/1552\n- (auto merged) chore(deps): bump serde_json from 1.0.91 to 1.0.92 by @dependabot in https://github.com/containers/youki/pull/1553\n- (auto merged) chore(deps): bump tinyvec_macros from 0.1.0 to 0.1.1 by @dependabot in https://github.com/containers/youki/pull/1546\n- (auto merged) chore(deps): bump serde_bytes from 0.11.8 to 0.11.9 by @dependabot in https://github.com/containers/youki/pull/1554\n- (auto merged) chore(deps): bump serde_json from 1.0.92 to 1.0.93 by @dependabot in https://github.com/containers/youki/pull/1555\n- (auto merged) chore(deps): bump wasm-encoder from 0.22.0 to 0.22.1 by @dependabot in https://github.com/containers/youki/pull/1551\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.50 to 1.0.51 by @dependabot in https://github.com/containers/youki/pull/1550\n- (auto merged) chore(deps): bump wat from 1.0.56 to 1.0.57 by @dependabot in https://github.com/containers/youki/pull/1547\n- (auto merged) chore(deps): bump cap-rand from 1.0.4 to 1.0.5 by @dependabot in https://github.com/containers/youki/pull/1548\n- Add rust-toolchain file  by @utam0k in https://github.com/containers/youki/pull/1557\n- (auto merged) chore(deps): bump wat from 1.0.57 to 1.0.58 by @dependabot in https://github.com/containers/youki/pull/1558\n- (auto merged) chore(deps): bump libdbus-sys from 0.2.3 to 0.2.4 by @dependabot in https://github.com/containers/youki/pull/1559\n- (auto merged) chore(deps): bump darling from 0.14.2 to 0.14.3 by @dependabot in https://github.com/containers/youki/pull/1560\n- Sort out github actions by @utam0k in https://github.com/containers/youki/pull/1561\n- (auto merged) chore(deps): bump cxx-build from 1.0.89 to 1.0.90 by @dependabot in https://github.com/containers/youki/pull/1566\n- (auto merged) chore(deps): bump target-lexicon from 0.12.5 to 0.12.6 by @dependabot in https://github.com/containers/youki/pull/1565\n- (auto merged) chore(deps): bump cxx from 1.0.89 to 1.0.90 by @dependabot in https://github.com/containers/youki/pull/1567\n- (auto merged) chore(deps): bump zstd-sys from 2.0.6+zstd.1.5.2 to 2.0.7+zstd.1.5.4 by @dependabot in https://github.com/containers/youki/pull/1568\n- fix: doc link by @lengrongfu in https://github.com/containers/youki/pull/1542\n- fix the warns from cargo clippy by @utam0k in https://github.com/containers/youki/pull/1564\n- (auto merged) chore(deps): bump anyhow from 1.0.68 to 1.0.69 by @dependabot in https://github.com/containers/youki/pull/1549\n- Update runtime-tools by @utam0k in https://github.com/containers/youki/pull/1569\n- (auto merged) chore(deps): bump vergen from 7.5.0 to 7.5.1 by @dependabot in https://github.com/containers/youki/pull/1570\n- chore(deps): bump nix from 0.25.0 to 0.26.2 by @dependabot in https://github.com/containers/youki/pull/1486\n- chore(deps): bump wasmtime from 4.0.0 to 5.0.0 by @dependabot in https://github.com/containers/youki/pull/1505\n- (auto merged) chore(deps): bump wat from 1.0.58 to 1.0.59 by @dependabot in https://github.com/containers/youki/pull/1572\n- (auto merged) chore(deps): bump mio from 0.8.5 to 0.8.6 by @dependabot in https://github.com/containers/youki/pull/1573\n- (auto merged) chore(deps): bump once_cell from 1.17.0 to 1.17.1 by @dependabot in https://github.com/containers/youki/pull/1571\n- chore(deps): bump errno from 0.2.8 to 0.3.0 by @dependabot in https://github.com/containers/youki/pull/1574\n- (auto merged) chore(deps): bump cxx from 1.0.90 to 1.0.91 by @dependabot in https://github.com/containers/youki/pull/1577\n- chore(deps): bump procfs from 0.14.2 to 0.15.0 by @dependabot in https://github.com/containers/youki/pull/1575\n- (auto merged) chore(deps): bump cxx-build from 1.0.90 to 1.0.91 by @dependabot in https://github.com/containers/youki/pull/1576\n- (auto merged) chore(deps): bump memmap2 from 0.5.8 to 0.5.9 by @dependabot in https://github.com/containers/youki/pull/1582\n- (auto merged) chore(deps): bump slab from 0.4.7 to 0.4.8 by @dependabot in https://github.com/containers/youki/pull/1583\n- (auto merged) chore(deps): bump procfs from 0.15.0 to 0.15.1 by @dependabot in https://github.com/containers/youki/pull/1584\n- Use saturating_sub instead of - for unsigned ints by @rumpl in https://github.com/containers/youki/pull/1530\n- buffer read and write by @wlsnx in https://github.com/containers/youki/pull/1581\n- Fix github actions by @utam0k in https://github.com/containers/youki/pull/1588\n- libcontainer: make device creation interfaces public by @ipuustin in https://github.com/containers/youki/pull/1578\n- chore(deps): bump wasmtime from 5.0.0 to 6.0.0 by @dependabot in https://github.com/containers/youki/pull/1586\n- (auto merged) chore(deps): bump memmap2 from 0.5.9 to 0.5.10 by @dependabot in https://github.com/containers/youki/pull/1589\n- (auto merged) chore(deps): bump clap_lex from 0.3.1 to 0.3.2 by @dependabot in https://github.com/containers/youki/pull/1590\n- (auto merged) chore(deps): bump syn from 1.0.107 to 1.0.108 by @dependabot in https://github.com/containers/youki/pull/1591\n- (auto merged) chore(deps): bump is-terminal from 0.4.3 to 0.4.4 by @dependabot in https://github.com/containers/youki/pull/1593\n- (auto merged) chore(deps): bump wat from 1.0.59 to 1.0.60 by @dependabot in https://github.com/containers/youki/pull/1594\n- (auto merged) chore(deps): bump wit-parser from 0.6.1 to 0.6.2 by @dependabot in https://github.com/containers/youki/pull/1592\n- (auto merged) chore(deps): bump sysinfo from 0.27.7 to 0.27.8 by @dependabot in https://github.com/containers/youki/pull/1596\n- (auto merged) chore(deps): bump syn from 1.0.108 to 1.0.109 by @dependabot in https://github.com/containers/youki/pull/1599\n- chore(deps): bump tempfile from 3.3.0 to 3.4.0 by @dependabot in https://github.com/containers/youki/pull/1597\n- chore(deps): bump oci-spec from 0.5.8 to 0.6.0 by @dependabot in https://github.com/containers/youki/pull/1598\n- chore(deps): bump path-clean from 0.1.0 to 1.0.1 by @dependabot in https://github.com/containers/youki/pull/1600\n- (auto merged) chore(deps): bump crossbeam-deque from 0.8.2 to 0.8.3 by @dependabot in https://github.com/containers/youki/pull/1604\n- (auto merged) chore(deps): bump crossbeam-utils from 0.8.14 to 0.8.15 by @dependabot in https://github.com/containers/youki/pull/1607\n- (auto merged) chore(deps): bump bytecheck from 0.6.9 to 0.6.10 by @dependabot in https://github.com/containers/youki/pull/1605\n- (auto merged) chore(deps): bump crossbeam-epoch from 0.9.13 to 0.9.14 by @dependabot in https://github.com/containers/youki/pull/1609\n- (auto merged) chore(deps): bump jobserver from 0.1.25 to 0.1.26 by @dependabot in https://github.com/containers/youki/pull/1606\n- (auto merged) chore(deps): bump crossbeam-channel from 0.5.6 to 0.5.7 by @dependabot in https://github.com/containers/youki/pull/1608\n- Allow specification of syscall impl for devices by @Furisto in https://github.com/containers/youki/pull/1603\n- feat Add container id validate by @lengrongfu in https://github.com/containers/youki/pull/1602\n- fix: youki's image in doc by @shimatar0 in https://github.com/containers/youki/pull/1614\n- (auto merged) chore(deps): bump cxx from 1.0.91 to 1.0.92 by @dependabot in https://github.com/containers/youki/pull/1618\n- (auto merged) chore(deps): bump unicode-ident from 1.0.6 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1617\n- (auto merged) chore(deps): bump rustversion from 1.0.11 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1623\n- (auto merged) chore(deps): bump serde_json from 1.0.93 to 1.0.94 by @dependabot in https://github.com/containers/youki/pull/1624\n- (auto merged) chore(deps): bump thiserror from 1.0.38 to 1.0.39 by @dependabot in https://github.com/containers/youki/pull/1626\n- chore(deps): bump rayon from 1.6.1 to 1.7.0 by @dependabot in https://github.com/containers/youki/pull/1625\n- Implement the container_clone using CLONE_PARENT by @yihuaf in https://github.com/containers/youki/pull/1610\n- feat add rdiratime/rnodiratime recursive mount test by @lengrongfu in https://github.com/containers/youki/pull/1616\n- (auto merged) chore(deps): bump ryu from 1.0.12 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/1622\n- (auto merged) chore(deps): bump scratch from 1.0.3 to 1.0.5 by @dependabot in https://github.com/containers/youki/pull/1621\n- (auto merged) chore(deps): bump serde_repr from 0.1.10 to 0.1.11 by @dependabot in https://github.com/containers/youki/pull/1619\n- (auto merged) chore(deps): bump cxx-build from 1.0.91 to 1.0.92 by @dependabot in https://github.com/containers/youki/pull/1628\n- (auto merged) chore(deps): bump wit-parser from 0.6.2 to 0.6.4 by @dependabot in https://github.com/containers/youki/pull/1629\n- (auto merged) chore(deps): bump async-trait from 0.1.64 to 0.1.66 by @dependabot in https://github.com/containers/youki/pull/1630\n- (auto merged) chore(deps): bump io-lifetimes from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1631\n- (auto merged) chore(deps): bump itoa from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1632\n- (auto merged) chore(deps): bump wat from 1.0.60 to 1.0.61 by @dependabot in https://github.com/containers/youki/pull/1633\n- (auto merged) chore(deps): bump paste from 1.0.11 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1635\n- feat add rdev/rnodev recursive mount test by @lengrongfu in https://github.com/containers/youki/pull/1615\n- add rrw/rexec recursive mount test by @lengrongfu in https://github.com/containers/youki/pull/1611\n- (auto merged) chore(deps): bump serde from 1.0.152 to 1.0.153 by @dependabot in https://github.com/containers/youki/pull/1636\n- (auto merged) chore(deps): bump wasmtime from 6.0.0 to 6.0.1 by @dependabot in https://github.com/containers/youki/pull/1644\n- (auto merged) chore(deps): bump serde from 1.0.153 to 1.0.154 by @dependabot in https://github.com/containers/youki/pull/1645\n- (auto merged) chore(deps): bump unicode-bidi from 0.3.10 to 0.3.11 by @dependabot in https://github.com/containers/youki/pull/1647\n- (auto merged) chore(deps): bump wasmtime-wasi from 6.0.0 to 6.0.1 by @dependabot in https://github.com/containers/youki/pull/1646\n- Fix CI rules not filtering integration test files properly by @lengrongfu in https://github.com/containers/youki/pull/1643\n- Fix clippy warning by @yihuaf in https://github.com/containers/youki/pull/1638\n- fix typo in container_main_process.rs by @minatoaquaMK2 in https://github.com/containers/youki/pull/1641\n- (auto merged) chore(deps): bump rustix from 0.36.8 to 0.36.9 by @dependabot in https://github.com/containers/youki/pull/1634\n- (auto merged) chore(deps): bump darling from 0.14.3 to 0.14.4 by @dependabot in https://github.com/containers/youki/pull/1651\n- (auto merged) chore(deps): bump block-buffer from 0.10.3 to 0.10.4 by @dependabot in https://github.com/containers/youki/pull/1653\n- fix container delete error by @lengrongfu in https://github.com/containers/youki/pull/1649\n- (auto merged) chore(deps): bump libc from 0.2.139 to 0.2.140 by @dependabot in https://github.com/containers/youki/pull/1652\n- Fixed container init process not re-parent to youki main process by @yihuaf in https://github.com/containers/youki/pull/1637\n- add rrelatime mount test by @lengrongfu in https://github.com/containers/youki/pull/1642\n- (auto merged) chore(deps): bump futures-sink from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1658\n- (auto merged) chore(deps): bump futures-channel from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1657\n- (auto merged) chore(deps): bump futures-task from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1659\n- (auto merged) chore(deps): bump serde from 1.0.154 to 1.0.155 by @dependabot in https://github.com/containers/youki/pull/1660\n- (auto merged) chore(deps): bump futures-io from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1661\n- (auto merged) chore(deps): bump chrono from 0.4.23 to 0.4.24 by @dependabot in https://github.com/containers/youki/pull/1662\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.51 to 1.0.52 by @dependabot in https://github.com/containers/youki/pull/1664\n- (auto merged) chore(deps): bump futures from 0.3.26 to 0.3.27 by @dependabot in https://github.com/containers/youki/pull/1665\n- libcontainer: Make the workloads injectable by @utam0k in https://github.com/containers/youki/pull/1403\n- ci: Fix test for podman by @utam0k in https://github.com/containers/youki/pull/1655\n- (auto merged) chore(deps): bump windows_aarch64_gnullvm from 0.42.1 to 0.42.2 by @dependabot in https://github.com/containers/youki/pull/1667\n- (auto merged) chore(deps): bump windows-targets from 0.42.1 to 0.42.2 by @dependabot in https://github.com/containers/youki/pull/1666\n- (auto merged) chore(deps): bump quote from 1.0.23 to 1.0.26 by @dependabot in https://github.com/containers/youki/pull/1669\n- chore(deps): bump bitflags from 1.3.2 to 2.0.0 by @dependabot in https://github.com/containers/youki/pull/1670\n- (auto merged) chore(deps): bump serde from 1.0.155 to 1.0.156 by @dependabot in https://github.com/containers/youki/pull/1671\n- (auto merged) chore(deps): bump predicates-tree from 1.0.7 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1673\n- (auto merged) chore(deps): bump predicates-core from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1674\n- (auto merged) chore(deps): bump cap-rand from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1678\n- (auto merged) chore(deps): bump io-lifetimes from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1679\n- (auto merged) chore(deps): bump clap_lex from 0.3.2 to 0.3.3 by @dependabot in https://github.com/containers/youki/pull/1680\n- (auto merged) chore(deps): bump bitflags from 2.0.0 to 2.0.1 by @dependabot in https://github.com/containers/youki/pull/1681\n- Implement basic foreground mode by @yihuaf in https://github.com/containers/youki/pull/1656\n- (auto merged) chore(deps): bump cap-primitives from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1677\n- (auto merged) chore(deps): bump cxx-build from 1.0.92 to 1.0.93 by @dependabot in https://github.com/containers/youki/pull/1687\n- (auto merged) chore(deps): bump bitflags from 2.0.1 to 2.0.2 by @dependabot in https://github.com/containers/youki/pull/1688\n- (auto merged) chore(deps): bump is-terminal from 0.4.4 to 0.4.5 by @dependabot in https://github.com/containers/youki/pull/1693\n- (auto merged) chore(deps): bump cap-rand from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1692\n- (auto merged) chore(deps): bump unicode-bidi from 0.3.11 to 0.3.12 by @dependabot in https://github.com/containers/youki/pull/1695\n- (auto merged) chore(deps): bump anyhow from 1.0.69 to 1.0.70 by @dependabot in https://github.com/containers/youki/pull/1694\n- (auto merged) chore(deps): bump cap-std from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1696\n- (auto merged) chore(deps): bump async-trait from 0.1.66 to 0.1.67 by @dependabot in https://github.com/containers/youki/pull/1686\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1700\n- (auto merged) chore(deps): bump io-lifetimes from 1.0.7 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1699\n- (auto merged) chore(deps): bump unicode-bidi from 0.3.12 to 0.3.13 by @dependabot in https://github.com/containers/youki/pull/1701\n- (auto merged) chore(deps): bump syscalls from 0.6.7 to 0.6.8 by @dependabot in https://github.com/containers/youki/pull/1703\n- (auto merged) chore(deps): bump cap-primitives from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1689\n- (auto merged) chore(deps): bump thiserror from 1.0.39 to 1.0.40 by @dependabot in https://github.com/containers/youki/pull/1690\n- chore(deps): bump os_str_bytes from 6.4.1 to 6.5.0 by @dependabot in https://github.com/containers/youki/pull/1691\n- chore(deps): bump cxx from 1.0.92 to 1.0.93 by @dependabot in https://github.com/containers/youki/pull/1697\n- (auto merged) chore(deps): bump serde from 1.0.156 to 1.0.157 by @dependabot in https://github.com/containers/youki/pull/1705\n- (auto merged) chore(deps): bump cap-std from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1708\n- (auto merged) chore(deps): bump cap-fs-ext from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1709\n- (auto merged) chore(deps): bump iana-time-zone from 0.1.53 to 0.1.54 by @dependabot in https://github.com/containers/youki/pull/1710\n- (auto merged) chore(deps): bump regex-syntax from 0.6.28 to 0.6.29 by @dependabot in https://github.com/containers/youki/pull/1711\n- (auto merged) chore(deps): bump serde_repr from 0.1.11 to 0.1.12 by @dependabot in https://github.com/containers/youki/pull/1712\n- (auto merged) chore(deps): bump serde from 1.0.157 to 1.0.158 by @dependabot in https://github.com/containers/youki/pull/1715\n- (auto merged) chore(deps): bump rustix from 0.36.9 to 0.36.11 by @dependabot in https://github.com/containers/youki/pull/1714\n- (auto merged) chore(deps): bump regex from 1.7.1 to 1.7.2 by @dependabot in https://github.com/containers/youki/pull/1716\n- chore(deps): bump cap-time-ext from 1.0.5 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1713\n- Refactor youki delete to match runc/crun. by @yihuaf in https://github.com/containers/youki/pull/1654\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.52 to 1.0.53 by @dependabot in https://github.com/containers/youki/pull/1718\n- add rsuid and rnosymfollow by @lengrongfu in https://github.com/containers/youki/pull/1685\n- fix(libcontainer): Run test_is_executable with a more common file by @Overflow0xFFFF in https://github.com/containers/youki/pull/1676\n- add cargo fmt to make lint by @lengrongfu in https://github.com/containers/youki/pull/1719\n- Introduce seccomp feature for libcontainer with musl by @krisnova in https://github.com/containers/youki/pull/1484\n- (auto merged) chore(deps): bump rustc-demangle from 0.1.21 to 0.1.22 by @dependabot in https://github.com/containers/youki/pull/1721\n- (auto merged) chore(deps): bump syscalls from 0.6.8 to 0.6.9 by @dependabot in https://github.com/containers/youki/pull/1722\n- (auto merged) chore(deps): bump regex from 1.7.2 to 1.7.3 by @dependabot in https://github.com/containers/youki/pull/1724\n- (auto merged) chore(deps): bump cxx from 1.0.93 to 1.0.94 by @dependabot in https://github.com/containers/youki/pull/1726\n- (auto merged) chore(deps): bump proc-macro2 from 1.0.53 to 1.0.54 by @dependabot in https://github.com/containers/youki/pull/1727\n- (auto merged) chore(deps): bump indexmap from 1.9.2 to 1.9.3 by @dependabot in https://github.com/containers/youki/pull/1728\n- (auto merged) chore(deps): bump cpufeatures from 0.2.5 to 0.2.6 by @dependabot in https://github.com/containers/youki/pull/1729\n- (auto merged) chore(deps): bump cxx-build from 1.0.93 to 1.0.94 by @dependabot in https://github.com/containers/youki/pull/1730\n- (auto merged) chore(deps): bump mockall from 0.11.3 to 0.11.4 by @dependabot in https://github.com/containers/youki/pull/1731\n- (auto merged) chore(deps): bump generic-array from 0.14.6 to 0.14.7 by @dependabot in https://github.com/containers/youki/pull/1732\n- (auto merged) chore(deps): bump ipnet from 2.7.1 to 2.7.2 by @dependabot in https://github.com/containers/youki/pull/1733\n- (auto merged) chore(deps): bump serde_json from 1.0.94 to 1.0.95 by @dependabot in https://github.com/containers/youki/pull/1734\n- (auto merged) chore(deps): bump serde from 1.0.158 to 1.0.159 by @dependabot in https://github.com/containers/youki/pull/1736\n- (auto merged) chore(deps): bump cmake from 0.1.49 to 0.1.50 by @dependabot in https://github.com/containers/youki/pull/1737\n- (auto merged) chore(deps): bump cap-rand from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1738\n- (auto merged) chore(deps): bump cap-primitives from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1739\n- v0.0.5 by @utam0k in https://github.com/containers/youki/pull/1740\n- CI: Comment out featuretest. by @utam0k in https://github.com/containers/youki/pull/1741\n\n## [v0.0.4](https://github.com/containers/youki/compare/v0.0.3...v0.0.4) - 2022-12-13\n- chore(deps): bump slab from 0.4.5 to 0.4.6 in /crates by @dependabot in https://github.com/containers/youki/pull/819\n- chore(deps): bump clap from 3.1.7 to 3.1.8 in /crates by @dependabot in https://github.com/containers/youki/pull/818\n- chore(deps): bump tracing-core from 0.1.23 to 0.1.24 in /crates by @dependabot in https://github.com/containers/youki/pull/817\n- chore(deps): bump enumset from 1.0.8 to 1.0.9 in /crates by @dependabot in https://github.com/containers/youki/pull/822\n- chore(deps): bump enumset_derive from 0.5.6 to 0.5.7 in /crates by @dependabot in https://github.com/containers/youki/pull/823\n- chore(deps): bump enumset from 1.0.9 to 1.0.10 in /crates by @dependabot in https://github.com/containers/youki/pull/825\n- chore(deps): bump sysinfo from 0.23.6 to 0.23.7 in /crates by @dependabot in https://github.com/containers/youki/pull/826\n- chore(deps): bump syn from 1.0.90 to 1.0.91 in /crates by @dependabot in https://github.com/containers/youki/pull/827\n- chore(deps): bump wat from 1.0.41 to 1.0.42 in /crates by @dependabot in https://github.com/containers/youki/pull/824\n- chore(deps): bump sysinfo from 0.23.7 to 0.23.8 in /crates by @dependabot in https://github.com/containers/youki/pull/830\n- chore(deps): bump libc from 0.2.121 to 0.2.122 in /crates by @dependabot in https://github.com/containers/youki/pull/829\n- chore(deps): bump proc-macro2 from 1.0.36 to 1.0.37 in /crates by @dependabot in https://github.com/containers/youki/pull/828\n- chore(deps): bump js-sys from 0.3.56 to 0.3.57 in /crates by @dependabot in https://github.com/containers/youki/pull/832\n- chore(deps): bump wasm-bindgen from 0.2.79 to 0.2.80 in /crates by @dependabot in https://github.com/containers/youki/pull/831\n- chore(rustdoc): Fix `rustdoc` warnings by @adoerr in https://github.com/containers/youki/pull/833\n- Support rust 1.60.0 by @Furisto in https://github.com/containers/youki/pull/835\n- Add support for CFS bandwith burst by @Furisto in https://github.com/containers/youki/pull/834\n- chore(deps): bump tracing from 0.1.32 to 0.1.33 in /crates by @dependabot in https://github.com/containers/youki/pull/836\n- chore(deps): bump sysinfo from 0.23.8 to 0.23.9 in /crates by @dependabot in https://github.com/containers/youki/pull/837\n- chore(deps): bump quote from 1.0.17 to 1.0.18 in /crates by @dependabot in https://github.com/containers/youki/pull/841\n- chore(deps): bump flate2 from 1.0.22 to 1.0.23 in /crates by @dependabot in https://github.com/containers/youki/pull/840\n- chore(deps): bump libbpf-sys from 0.6.1-2 to 0.7.0+v0.7.0 in /crates by @dependabot in https://github.com/containers/youki/pull/839\n- chore(deps): bump libc from 0.2.122 to 0.2.123 in /crates by @dependabot in https://github.com/containers/youki/pull/842\n- chore(deps): bump enumset from 1.0.10 to 1.0.11 in /crates by @dependabot in https://github.com/containers/youki/pull/843\n- chore(deps): bump tracing-core from 0.1.24 to 0.1.25 in /crates by @dependabot in https://github.com/containers/youki/pull/844\n- Update the docs for the directory structure changes by @YJDoc2 in https://github.com/containers/youki/pull/813\n- chore(deps): bump rayon-core from 1.9.1 to 1.9.2 in /crates by @dependabot in https://github.com/containers/youki/pull/848\n- chore(deps): bump sysinfo from 0.23.9 to 0.23.10 in /crates by @dependabot in https://github.com/containers/youki/pull/846\n- chore(deps): bump tracing from 0.1.33 to 0.1.34 in /crates by @dependabot in https://github.com/containers/youki/pull/850\n- chore(deps): bump tracing-core from 0.1.25 to 0.1.26 in /crates by @dependabot in https://github.com/containers/youki/pull/849\n- chore(deps): bump rayon from 1.5.1 to 1.5.2 in /crates by @dependabot in https://github.com/containers/youki/pull/847\n- chore(deps): bump clap from 3.1.8 to 3.1.9 in /crates by @dependabot in https://github.com/containers/youki/pull/852\n- Ensure pid and root path are canonicalized by @Furisto in https://github.com/containers/youki/pull/851\n- chore(deps): bump backtrace from 0.3.64 to 0.3.65 in /crates by @dependabot in https://github.com/containers/youki/pull/853\n- chore(deps): bump libc from 0.2.123 to 0.2.124 in /crates by @dependabot in https://github.com/containers/youki/pull/854\n- chore(deps): bump clap from 3.1.9 to 3.1.10 in /crates by @dependabot in https://github.com/containers/youki/pull/856\n- chore(deps): bump libbpf-sys from 0.7.0+v0.7.0 to 0.7.1+v0.7.0 in /crates by @dependabot in https://github.com/containers/youki/pull/857\n- chore(deps): bump clap_complete from 3.1.1 to 3.1.2 in /crates by @dependabot in https://github.com/containers/youki/pull/858\n- chore(deps): bump derive_builder from 0.11.1 to 0.11.2 in /crates by @dependabot in https://github.com/containers/youki/pull/859\n- chore(deps): bump anyhow from 1.0.56 to 1.0.57 in /crates by @dependabot in https://github.com/containers/youki/pull/862\n- chore(deps): bump darling from 0.13.1 to 0.13.4 in /crates by @dependabot in https://github.com/containers/youki/pull/860\n- Follow the breaking changes of nix by @utam0k in https://github.com/containers/youki/pull/863\n- chore(deps): bump tinyvec from 1.5.1 to 1.6.0 in /crates by @dependabot in https://github.com/containers/youki/pull/864\n- Convenient use of rust-analyzer and other tools by placing Cargo.toml in the root. by @utam0k in https://github.com/containers/youki/pull/855\n- chore(deps): bump uuid from 0.8.2 to 1.0.0 by @dependabot in https://github.com/containers/youki/pull/866\n- Bump nix from 0.24.0 to 0.24.1 by @Furisto in https://github.com/containers/youki/pull/867\n- chore(deps): bump unicode-bidi from 0.3.7 to 0.3.8 by @dependabot in https://github.com/containers/youki/pull/870\n- chore(deps): bump tracing-attributes from 0.1.20 to 0.1.21 by @dependabot in https://github.com/containers/youki/pull/869\n- Add a missing instruction in the readme by @RMPR in https://github.com/containers/youki/pull/871\n- chore(deps): bump libz-sys from 1.1.5 to 1.1.6 by @dependabot in https://github.com/containers/youki/pull/872\n- chore(deps): bump pin-project-lite from 0.2.8 to 0.2.9 by @dependabot in https://github.com/containers/youki/pull/868\n- Add flat logos to docs folder by @scary4cat in https://github.com/containers/youki/pull/873\n- chore(deps): bump clap from 3.1.12 to 3.1.14 by @dependabot in https://github.com/containers/youki/pull/884\n- chore(deps): bump clap_complete from 3.1.2 to 3.1.3 by @dependabot in https://github.com/containers/youki/pull/883\n- chore(deps): bump serde_json from 1.0.79 to 1.0.80 by @dependabot in https://github.com/containers/youki/pull/882\n- chore(deps): bump syn from 1.0.91 to 1.0.92 by @dependabot in https://github.com/containers/youki/pull/885\n- chore(deps): bump num-integer from 0.1.44 to 0.1.45 by @dependabot in https://github.com/containers/youki/pull/881\n- chore(deps): bump thiserror from 1.0.30 to 1.0.31 by @dependabot in https://github.com/containers/youki/pull/880\n- chore(deps): bump serde_bytes from 0.11.5 to 0.11.6 by @dependabot in https://github.com/containers/youki/pull/879\n- chore(deps): bump memchr from 2.4.1 to 2.5.0 by @dependabot in https://github.com/containers/youki/pull/878\n- chore(deps): bump serde from 1.0.136 to 1.0.137 by @dependabot in https://github.com/containers/youki/pull/877\n- chore(deps): bump libc from 0.2.124 to 0.2.125 by @dependabot in https://github.com/containers/youki/pull/876\n- chore(deps): bump sysinfo from 0.23.10 to 0.23.11 by @dependabot in https://github.com/containers/youki/pull/875\n- chore(deps): bump clap from 3.1.14 to 3.1.15 by @dependabot in https://github.com/containers/youki/pull/886\n- chore(deps): bump log from 0.4.16 to 0.4.17 by @dependabot in https://github.com/containers/youki/pull/887\n- chore(deps): bump unicode-xid from 0.2.2 to 0.2.3 by @dependabot in https://github.com/containers/youki/pull/888\n- chore(deps): bump serde_json from 1.0.80 to 1.0.81 by @dependabot in https://github.com/containers/youki/pull/893\n- chore(deps): bump bytecheck from 0.6.7 to 0.6.8 by @dependabot in https://github.com/containers/youki/pull/892\n- chore(deps): bump num-traits from 0.2.14 to 0.2.15 by @dependabot in https://github.com/containers/youki/pull/891\n- chore(deps): bump rkyv from 0.7.37 to 0.7.38 by @dependabot in https://github.com/containers/youki/pull/890\n- chore(deps): bump xattr from 0.2.2 to 0.2.3 by @dependabot in https://github.com/containers/youki/pull/889\n- chore(deps): bump num_threads from 0.1.5 to 0.1.6 by @dependabot in https://github.com/containers/youki/pull/894\n- Update README by @utam0k in https://github.com/containers/youki/pull/895\n- How many times in my life do I have to repeat forgetting close? by @utam0k in https://github.com/containers/youki/pull/896\n- resize page to fit svg image by @scary4cat in https://github.com/containers/youki/pull/901\n- chore(deps): bump rust-criu from `6df3057` to `3f1340b` by @dependabot in https://github.com/containers/youki/pull/906\n- chore(deps): bump clap_complete from 3.1.3 to 3.1.4 by @dependabot in https://github.com/containers/youki/pull/903\n- chore(deps): bump clap from 3.1.15 to 3.1.17 by @dependabot in https://github.com/containers/youki/pull/904\n- chore(deps): bump mio from 0.8.2 to 0.8.3 by @dependabot in https://github.com/containers/youki/pull/907\n- chore(deps): bump proc-macro2 from 1.0.37 to 1.0.38 by @dependabot in https://github.com/containers/youki/pull/905\n- chore(deps): bump object from 0.28.3 to 0.28.4 by @dependabot in https://github.com/containers/youki/pull/910\n- chore(deps): bump syn from 1.0.92 to 1.0.93 by @dependabot in https://github.com/containers/youki/pull/913\n- chore(deps): bump clap from 3.1.17 to 3.1.18 by @dependabot in https://github.com/containers/youki/pull/912\n- chore(deps): bump sysinfo from 0.23.11 to 0.23.12 by @dependabot in https://github.com/containers/youki/pull/911\n- support configure cpu.idle by Cgroupfs by @wineway in https://github.com/containers/youki/pull/908\n- chore(deps): bump mockall from 0.11.0 to 0.11.1 by @dependabot in https://github.com/containers/youki/pull/918\n- chore(deps): bump itoa from 1.0.1 to 1.0.2 by @dependabot in https://github.com/containers/youki/pull/922\n- chore(deps): bump rayon from 1.5.2 to 1.5.3 by @dependabot in https://github.com/containers/youki/pull/921\n- chore(deps): bump rayon-core from 1.9.2 to 1.9.3 by @dependabot in https://github.com/containers/youki/pull/920\n- chore(deps): bump os_str_bytes from 6.0.0 to 6.0.1 by @dependabot in https://github.com/containers/youki/pull/919\n- chore(deps): bump ryu from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/917\n- chore(deps): bump syn from 1.0.93 to 1.0.94 by @dependabot in https://github.com/containers/youki/pull/916\n- put the runtime feature into the oci-spec-rs crate. by @utam0k in https://github.com/containers/youki/pull/923\n- chore(deps): bump proc-macro2 from 1.0.38 to 1.0.39 by @dependabot in https://github.com/containers/youki/pull/924\n- chore(deps): bump vergen from 7.0.0 to 7.1.0 by @dependabot in https://github.com/containers/youki/pull/927\n- chore(deps): bump once_cell from 1.10.0 to 1.11.0 by @dependabot in https://github.com/containers/youki/pull/929\n- chore(deps): bump wat from 1.0.42 to 1.0.43 by @dependabot in https://github.com/containers/youki/pull/928\n- chore(deps): bump libc from 0.2.125 to 0.2.126 by @dependabot in https://github.com/containers/youki/pull/926\n- chore(deps): bump syn from 1.0.94 to 1.0.95 by @dependabot in https://github.com/containers/youki/pull/925\n- chore(deps): bump regex-syntax from 0.6.25 to 0.6.26 by @dependabot in https://github.com/containers/youki/pull/934\n- chore(deps): bump regex from 1.5.5 to 1.5.6 by @dependabot in https://github.com/containers/youki/pull/932\n- Rust 1.61.0 by @utam0k in https://github.com/containers/youki/pull/931\n- chore(deps): bump sysinfo from 0.23.12 to 0.23.13 by @dependabot in https://github.com/containers/youki/pull/933\n- chore(deps): bump once_cell from 1.11.0 to 1.12.0 by @dependabot in https://github.com/containers/youki/pull/937\n- chore(deps): bump target-lexicon from 0.12.3 to 0.12.4 by @dependabot in https://github.com/containers/youki/pull/940\n- Remove the build dependency from some tests. by @utam0k in https://github.com/containers/youki/pull/909\n- Update cargo-llvm-cov and use rust 1.60.0 for coverage by @taiki-e in https://github.com/containers/youki/pull/898\n- Fix CI compilation issues by @Furisto in https://github.com/containers/youki/pull/945\n- chore(deps): bump os_str_bytes from 6.0.1 to 6.1.0 by @dependabot in https://github.com/containers/youki/pull/944\n- chore(deps): bump uuid from 1.0.0 to 1.1.0 by @dependabot in https://github.com/containers/youki/pull/943\n- bump git2 from 0.14.2 to 0.14.4 by @utam0k in https://github.com/containers/youki/pull/946\n- support the all option in the kill command. by @utam0k in https://github.com/containers/youki/pull/935\n- chore(deps): bump flate2 from 1.0.23 to 1.0.24 by @dependabot in https://github.com/containers/youki/pull/948\n- chore(deps): bump indexmap from 1.8.1 to 1.8.2 by @dependabot in https://github.com/containers/youki/pull/949\n- chore(deps): bump libz-sys from 1.1.6 to 1.1.8 by @dependabot in https://github.com/containers/youki/pull/950\n- bump syscalls from 0.5.0 to 0.6.0  by @utam0k in https://github.com/containers/youki/pull/947\n- chore(deps): bump miniz_oxide from 0.5.1 to 0.5.3 by @dependabot in https://github.com/containers/youki/pull/952\n- chore(deps): bump uuid from 1.1.0 to 1.1.1 by @dependabot in https://github.com/containers/youki/pull/954\n- chore(deps): bump syn from 1.0.95 to 1.0.96 by @dependabot in https://github.com/containers/youki/pull/957\n- chore(deps): bump bumpalo from 3.9.1 to 3.10.0 by @dependabot in https://github.com/containers/youki/pull/956\n- chore(deps): bump serial_test from 0.6.0 to 0.7.0 by @dependabot in https://github.com/containers/youki/pull/962\n- chore(deps): bump memmap2 from 0.5.3 to 0.5.4 by @dependabot in https://github.com/containers/youki/pull/961\n- chore(deps): bump libbpf-sys from 0.7.1+v0.7.0 to 0.8.0+v0.8.0 by @dependabot in https://github.com/containers/youki/pull/960\n- chore: update .gitignore by @tony84727 in https://github.com/containers/youki/pull/964\n- Use pnet_datalink instead of pnet. by @utam0k in https://github.com/containers/youki/pull/963\n- Prepare containerd integration test environment using youki by @guni1192 in https://github.com/containers/youki/pull/914\n- chore(deps): bump tracing from 0.1.34 to 0.1.35 by @dependabot in https://github.com/containers/youki/pull/969\n- chore(deps): bump wasmer-wasi from 2.2.1 to 2.3.0 by @dependabot in https://github.com/containers/youki/pull/967\n- chore(deps): bump tracing-core from 0.1.26 to 0.1.27 by @dependabot in https://github.com/containers/youki/pull/966\n- chore(deps): bump vergen from 7.2.0 to 7.2.1 by @dependabot in https://github.com/containers/youki/pull/965\n- chore(deps): bump fragile from 1.2.0 to 1.2.1 by @dependabot in https://github.com/containers/youki/pull/972\n- chore(deps): bump syscalls from 0.6.0 to 0.6.1 by @dependabot in https://github.com/containers/youki/pull/971\n- chore(deps): bump wat from 1.0.43 to 1.0.44 by @dependabot in https://github.com/containers/youki/pull/970\n- chore(deps): bump uuid from 1.1.1 to 1.1.2 by @dependabot in https://github.com/containers/youki/pull/973\n- chore(deps): bump getrandom from 0.2.6 to 0.2.7 by @dependabot in https://github.com/containers/youki/pull/978\n- chore(deps): bump clap_lex from 0.2.0 to 0.2.2 by @dependabot in https://github.com/containers/youki/pull/977\n- chore(deps): bump unicode-ident from 1.0.0 to 1.0.1 by @dependabot in https://github.com/containers/youki/pull/976\n- chore(deps): bump js-sys from 0.3.57 to 0.3.58 by @dependabot in https://github.com/containers/youki/pull/982\n- chore(deps): bump wasm-bindgen from 0.2.80 to 0.2.81 by @dependabot in https://github.com/containers/youki/pull/983\n- chore(deps): bump clap from 3.1.18 to 3.2.4 by @dependabot in https://github.com/containers/youki/pull/984\n- chore(deps): bump clap_complete from 3.1.4 to 3.2.1 by @dependabot in https://github.com/containers/youki/pull/985\n- chore(deps): bump time from 0.1.43 to 0.1.44 by @dependabot in https://github.com/containers/youki/pull/986\n- chore: a separate target directory for runtimetest by @tony84727 in https://github.com/containers/youki/pull/981\n- chore(deps): bump clap from 3.2.4 to 3.2.5 by @dependabot in https://github.com/containers/youki/pull/988\n- chore(deps): bump crossbeam-epoch from 0.9.8 to 0.9.9 by @dependabot in https://github.com/containers/youki/pull/992\n- chore(deps): bump crossbeam-channel from 0.5.4 to 0.5.5 by @dependabot in https://github.com/containers/youki/pull/991\n- chore(deps): bump crossbeam-utils from 0.8.8 to 0.8.9 by @dependabot in https://github.com/containers/youki/pull/990\n- chore(deps): bump indexmap from 1.8.2 to 1.9.0 by @dependabot in https://github.com/containers/youki/pull/989\n- test: hooks integration test by @tony84727 in https://github.com/containers/youki/pull/959\n- Remove duplicated assignment by @cyyzero in https://github.com/containers/youki/pull/993\n- chore(deps): bump mio from 0.8.3 to 0.8.4 by @dependabot in https://github.com/containers/youki/pull/995\n- chore(deps): bump anyhow from 1.0.57 to 1.0.58 by @dependabot in https://github.com/containers/youki/pull/996\n- chore(deps): bump syn from 1.0.96 to 1.0.98 by @dependabot in https://github.com/containers/youki/pull/994\n- chore(deps): bump quote from 1.0.18 to 1.0.19 by @dependabot in https://github.com/containers/youki/pull/997\n- chore(deps): bump rustversion from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/998\n- chore(deps): bump proc-macro2 from 1.0.39 to 1.0.40 by @dependabot in https://github.com/containers/youki/pull/999\n- chore(deps): bump clap_lex from 0.2.2 to 0.2.3 by @dependabot in https://github.com/containers/youki/pull/1001\n- chore(deps): bump indexmap from 1.9.0 to 1.9.1 by @dependabot in https://github.com/containers/youki/pull/1003\n- chore(deps): bump clap_complete from 3.2.1 to 3.2.2 by @dependabot in https://github.com/containers/youki/pull/1004\n- chore(deps): bump quote from 1.0.19 to 1.0.20 by @dependabot in https://github.com/containers/youki/pull/1005\n- chore(deps): bump clap from 3.2.5 to 3.2.6 by @dependabot in https://github.com/containers/youki/pull/1006\n- chore(deps): bump tracing-core from 0.1.27 to 0.1.28 by @dependabot in https://github.com/containers/youki/pull/1007\n- chore(deps): bump crossbeam-utils from 0.8.9 to 0.8.10 by @dependabot in https://github.com/containers/youki/pull/1008\n- chore(deps): bump smallvec from 1.8.0 to 1.8.1 by @dependabot in https://github.com/containers/youki/pull/1014\n- chore(deps): bump serial_test from 0.7.0 to 0.8.0 by @dependabot in https://github.com/containers/youki/pull/1013\n- chore(deps): bump base-x from 0.2.10 to 0.2.11 by @dependabot in https://github.com/containers/youki/pull/1012\n- chore(deps): bump rkyv from 0.7.38 to 0.7.39 by @dependabot in https://github.com/containers/youki/pull/1010\n- chore(deps): bump unicode-normalization from 0.1.19 to 0.1.20 by @dependabot in https://github.com/containers/youki/pull/1011\n- chore(deps): bump clap_lex from 0.2.3 to 0.2.4 by @dependabot in https://github.com/containers/youki/pull/1015\n- chore(deps): bump clap_complete from 3.2.2 to 3.2.3 by @dependabot in https://github.com/containers/youki/pull/1017\n- Added podman local system tests by @stefins in https://github.com/containers/youki/pull/1009\n- chore(deps): bump serde_json from 1.0.81 to 1.0.82 by @dependabot in https://github.com/containers/youki/pull/1021\n- chore(deps): bump either from 1.6.1 to 1.7.0 by @dependabot in https://github.com/containers/youki/pull/1020\n- chore(deps): bump smallvec from 1.8.1 to 1.9.0 by @dependabot in https://github.com/containers/youki/pull/1019\n- chore(deps): bump clap from 3.2.6 to 3.2.7 by @dependabot in https://github.com/containers/youki/pull/1016\n- chore(deps): bump tracing-attributes from 0.1.21 to 0.1.22 by @dependabot in https://github.com/containers/youki/pull/1024\n- chore(deps): bump fixedbitset from 0.4.1 to 0.4.2 by @dependabot in https://github.com/containers/youki/pull/1025\n- chore(deps): bump filetime from 0.2.16 to 0.2.17 by @dependabot in https://github.com/containers/youki/pull/1026\n- chore(deps): bump unicode-normalization from 0.1.20 to 0.1.21 by @dependabot in https://github.com/containers/youki/pull/1027\n- chore(deps): bump corosensei from 0.1.2 to 0.1.3 by @dependabot in https://github.com/containers/youki/pull/1029\n- chore(deps): bump serde from 1.0.137 to 1.0.138 by @dependabot in https://github.com/containers/youki/pull/1030\n- chore(deps): bump once_cell from 1.12.0 to 1.12.1 by @dependabot in https://github.com/containers/youki/pull/1031\n- chore(deps): bump clap from 3.2.7 to 3.2.8 by @dependabot in https://github.com/containers/youki/pull/1023\n- chore(deps): bump regex-syntax from 0.6.26 to 0.6.27 by @dependabot in https://github.com/containers/youki/pull/1033\n- chore(deps): bump wat from 1.0.44 to 1.0.45 by @dependabot in https://github.com/containers/youki/pull/1034\n- chore(deps): bump regex from 1.5.6 to 1.6.0 by @dependabot in https://github.com/containers/youki/pull/1035\n- chore(deps): bump once_cell from 1.12.1 to 1.13.0 by @dependabot in https://github.com/containers/youki/pull/1032\n- chore(deps): bump syscalls from 0.6.1 to 0.6.2 by @dependabot in https://github.com/containers/youki/pull/1022\n- chore(deps): bump procfs from 0.12.0 to 0.13.0 by @dependabot in https://github.com/containers/youki/pull/1028\n- chore(deps): bump backtrace from 0.3.65 to 0.3.66 by @dependabot in https://github.com/containers/youki/pull/1036\n- chore(deps): bump syscalls from 0.6.2 to 0.6.3 by @dependabot in https://github.com/containers/youki/pull/1037\n- Automatically publish packages by @MostlyAmiable in https://github.com/containers/youki/pull/1000\n- chore(deps): bump libbpf-sys from 0.8.0+v0.8.0 to 0.8.1+v0.8.0 by @dependabot in https://github.com/containers/youki/pull/1041\n- chore(deps): bump rust-criu from 0.1.0 to 0.2.0 by @dependabot in https://github.com/containers/youki/pull/1040\n- chore(deps): bump memmap2 from 0.5.4 to 0.5.5 by @dependabot in https://github.com/containers/youki/pull/1039\n- reduce the number of args. by @utam0k in https://github.com/containers/youki/pull/1042\n- chore(deps): bump clap from 3.2.8 to 3.2.10 by @dependabot in https://github.com/containers/youki/pull/1046\n- chore(deps): bump wat from 1.0.45 to 1.0.46 by @dependabot in https://github.com/containers/youki/pull/1048\n- chore(deps): bump serde from 1.0.138 to 1.0.139 by @dependabot in https://github.com/containers/youki/pull/1045\n- chore(deps): bump clap from 3.2.10 to 3.2.11 by @dependabot in https://github.com/containers/youki/pull/1047\n- chore(deps): bump syscalls from 0.6.3 to 0.6.5 by @dependabot in https://github.com/containers/youki/pull/1050\n- chore(deps): bump clap from 3.2.11 to 3.2.12 by @dependabot in https://github.com/containers/youki/pull/1049\n- chore(deps): bump libbpf-sys from 0.8.1+v0.8.0 to 0.8.2+v0.8.1 by @dependabot in https://github.com/containers/youki/pull/1056\n- chore(deps): bump dbus from 0.9.5 to 0.9.6 by @dependabot in https://github.com/containers/youki/pull/1043\n- chore(deps): bump os_str_bytes from 6.1.0 to 6.2.0 by @dependabot in https://github.com/containers/youki/pull/1055\n- chore(deps): bump rustversion from 1.0.7 to 1.0.8 by @dependabot in https://github.com/containers/youki/pull/1054\n- chore(deps): bump gimli from 0.26.1 to 0.26.2 by @dependabot in https://github.com/containers/youki/pull/1053\n- chore(deps): bump nix from 0.24.1 to 0.24.2 by @dependabot in https://github.com/containers/youki/pull/1052\n- chore(deps): bump unicode-ident from 1.0.1 to 1.0.2 by @dependabot in https://github.com/containers/youki/pull/1051\n- chore(deps): bump procfs from 0.13.0 to 0.13.2 by @dependabot in https://github.com/containers/youki/pull/1044\n- Fix some typos by @z1cheng in https://github.com/containers/youki/pull/1057\n- chore(deps): bump slab from 0.4.6 to 0.4.7 by @dependabot in https://github.com/containers/youki/pull/1058\n- chore(deps): bump libbpf-sys from 0.8.2+v0.8.1 to 0.8.3+v0.8.1 by @dependabot in https://github.com/containers/youki/pull/1060\n- chore(deps): bump clap from 3.2.12 to 3.2.13 by @dependabot in https://github.com/containers/youki/pull/1059\n- chore(deps): bump serde from 1.0.139 to 1.0.140 by @dependabot in https://github.com/containers/youki/pull/1061\n- chore(deps): bump clap from 3.2.13 to 3.2.14 by @dependabot in https://github.com/containers/youki/pull/1062\n- chore(deps): bump crossbeam-epoch from 0.9.9 to 0.9.10 by @dependabot in https://github.com/containers/youki/pull/1072\n- chore(deps): bump crossbeam from 0.8.1 to 0.8.2 by @dependabot in https://github.com/containers/youki/pull/1071\n- chore(deps): bump bytecheck from 0.6.8 to 0.6.9 by @dependabot in https://github.com/containers/youki/pull/1070\n- chore(deps): bump mockall from 0.11.1 to 0.11.2 by @dependabot in https://github.com/containers/youki/pull/1069\n- chore(deps): bump crossbeam-channel from 0.5.5 to 0.5.6 by @dependabot in https://github.com/containers/youki/pull/1065\n- chore(deps): bump fastrand from 1.7.0 to 1.8.0 by @dependabot in https://github.com/containers/youki/pull/1073\n- chore(deps): bump proc-macro2 from 1.0.40 to 1.0.41 by @dependabot in https://github.com/containers/youki/pull/1074\n- chore(deps): bump clap from 3.2.14 to 3.2.15 by @dependabot in https://github.com/containers/youki/pull/1078\n- chore(deps): bump js-sys from 0.3.58 to 0.3.59 by @dependabot in https://github.com/containers/youki/pull/1077\n- chore(deps): bump redox_syscall from 0.2.13 to 0.2.15 by @dependabot in https://github.com/containers/youki/pull/1066\n- chore(deps): bump crossbeam-queue from 0.3.5 to 0.3.6 by @dependabot in https://github.com/containers/youki/pull/1064\n- chore(deps): bump crossbeam-deque from 0.8.1 to 0.8.2 by @dependabot in https://github.com/containers/youki/pull/1063\n- chore(deps): bump crossbeam-utils from 0.8.10 to 0.8.11 by @dependabot in https://github.com/containers/youki/pull/1068\n- chore(deps): bump proc-macro2 from 1.0.41 to 1.0.42 by @dependabot in https://github.com/containers/youki/pull/1075\n- chore(deps): bump redox_syscall from 0.2.15 to 0.2.16 by @dependabot in https://github.com/containers/youki/pull/1079\n- chore(deps): bump wat from 1.0.46 to 1.0.47 by @dependabot in https://github.com/containers/youki/pull/1080\n- chore(deps): bump git2 from 0.14.4 to 0.15.0 by @dependabot in https://github.com/containers/youki/pull/1081\n- Update to rust 1.62.1 by @Furisto in https://github.com/containers/youki/pull/1082\n- chore(deps): bump clap from 3.2.15 to 3.2.16 by @dependabot in https://github.com/containers/youki/pull/1085\n- chore(deps): bump tracing from 0.1.35 to 0.1.36 by @dependabot in https://github.com/containers/youki/pull/1083\n- chore(deps): bump vergen from 7.2.1 to 7.3.2 by @dependabot in https://github.com/containers/youki/pull/1084\n- chore(deps): bump serde from 1.0.140 to 1.0.141 by @dependabot in https://github.com/containers/youki/pull/1089\n- chore(deps): bump itoa from 1.0.2 to 1.0.3 by @dependabot in https://github.com/containers/youki/pull/1091\n- chore(deps): bump serde from 1.0.141 to 1.0.142 by @dependabot in https://github.com/containers/youki/pull/1092\n- chore(deps): bump serde_repr from 0.1.8 to 0.1.9 by @dependabot in https://github.com/containers/youki/pull/1098\n- chore(deps): bump unicode-ident from 1.0.2 to 1.0.3 by @dependabot in https://github.com/containers/youki/pull/1093\n- chore(deps): bump serde_bytes from 0.11.6 to 0.11.7 by @dependabot in https://github.com/containers/youki/pull/1097\n- chore(deps): bump thiserror from 1.0.31 to 1.0.32 by @dependabot in https://github.com/containers/youki/pull/1094\n- chore(deps): bump ryu from 1.0.10 to 1.0.11 by @dependabot in https://github.com/containers/youki/pull/1103\n- chore(deps): bump syn from 1.0.98 to 1.0.99 by @dependabot in https://github.com/containers/youki/pull/1102\n- chore(deps): bump proc-macro2 from 1.0.42 to 1.0.43 by @dependabot in https://github.com/containers/youki/pull/1096\n- chore(deps): bump serde_json from 1.0.82 to 1.0.83 by @dependabot in https://github.com/containers/youki/pull/1100\n- chore(deps): bump rustversion from 1.0.8 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/1095\n- chore(deps): bump quote from 1.0.20 to 1.0.21 by @dependabot in https://github.com/containers/youki/pull/1099\n- Bump procfs from 0.13.2 to 0.14.0 by @Furisto in https://github.com/containers/youki/pull/1106\n- chore(deps): bump anyhow from 1.0.58 to 1.0.60 by @dependabot in https://github.com/containers/youki/pull/1090\n- chore(deps): bump libc from 0.2.126 to 0.2.127 by @dependabot in https://github.com/containers/youki/pull/1101\n- chore(deps): bump chrono from 0.4.19 to 0.4.21 by @dependabot in https://github.com/containers/youki/pull/1108\n- Changed bats installation script to apt package manager by @stefins in https://github.com/containers/youki/pull/1125\n- chore(deps): bump iana-time-zone from 0.1.41 to 0.1.45 by @dependabot in https://github.com/containers/youki/pull/1124\n- chore(deps): bump io-lifetimes from 0.7.2 to 0.7.3 by @dependabot in https://github.com/containers/youki/pull/1126\n- chore(deps): bump rustix from 0.35.7 to 0.35.9 by @dependabot in https://github.com/containers/youki/pull/1127\n- chore(deps): bump vergen from 7.3.2 to 7.4.0 by @dependabot in https://github.com/containers/youki/pull/1130\n- chore(deps): bump iana-time-zone from 0.1.45 to 0.1.46 by @dependabot in https://github.com/containers/youki/pull/1131\n- chore(deps): bump clap_complete from 3.2.3 to 3.2.4 by @dependabot in https://github.com/containers/youki/pull/1132\n- chore(deps): bump clap from 3.2.16 to 3.2.17 by @dependabot in https://github.com/containers/youki/pull/1117\n- chore(deps): bump serial_test from 0.8.0 to 0.9.0 by @dependabot in https://github.com/containers/youki/pull/1113\n- chore(deps): bump serde from 1.0.142 to 1.0.143 by @dependabot in https://github.com/containers/youki/pull/1110\n- chore(deps): bump memmap2 from 0.5.5 to 0.5.7 by @dependabot in https://github.com/containers/youki/pull/1120\n- chore(deps): bump chrono from 0.4.21 to 0.4.22 by @dependabot in https://github.com/containers/youki/pull/1135\n- chore(deps): bump futures-core from 0.3.21 to 0.3.23 by @dependabot in https://github.com/containers/youki/pull/1136\n- chore(deps): bump os_str_bytes from 6.2.0 to 6.3.0 by @dependabot in https://github.com/containers/youki/pull/1134\n- chore(deps): bump futures-channel from 0.3.21 to 0.3.23 by @dependabot in https://github.com/containers/youki/pull/1141\n- chore(deps): bump procfs from 0.14.0 to 0.14.1 by @dependabot in https://github.com/containers/youki/pull/1139\n- chore(deps): bump bumpalo from 3.10.0 to 3.11.0 by @dependabot in https://github.com/containers/youki/pull/1138\n- Fix bug that attempts is always 0 in delete_with_retry by @cyyzero in https://github.com/containers/youki/pull/1128\n- Upgrade dependencies by @utam0k in https://github.com/containers/youki/pull/1137\n- chore(deps): bump nix from 0.24.2 to 0.25.0 by @dependabot in https://github.com/containers/youki/pull/1147\n- chore(deps): bump git2 from 0.14.4 to 0.15.0 by @dependabot in https://github.com/containers/youki/pull/1148\n- build.sh enhancement for feature flags by @orimanabu in https://github.com/containers/youki/pull/1150\n- chore(deps): bump lock_api from 0.4.7 to 0.4.8 by @dependabot in https://github.com/containers/youki/pull/1151\n- Canonicalize the bundle path when storing in the new container data by @YJDoc2 in https://github.com/containers/youki/pull/1154\n- chore(deps): bump futures-core from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1162\n- chore(deps): bump futures from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1161\n- chore(deps): bump futures-task from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1160\n- chore(deps): bump futures-channel from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1159\n- chore(deps): bump futures-io from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1158\n- chore(deps): bump futures-sink from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/1157\n- chore(deps): bump clap from 3.2.17 to 3.2.18 by @dependabot in https://github.com/containers/youki/pull/1156\n- Fix whitespaces: replace TABs to SPACEs by @orimanabu in https://github.com/containers/youki/pull/1167\n- Add instructions for using podman and buildah to webassembly.md by @orimanabu in https://github.com/containers/youki/pull/1155\n- chore(deps): bump which from 4.2.5 to 4.3.0 by @dependabot in https://github.com/containers/youki/pull/1170\n- chore(deps): bump thiserror from 1.0.32 to 1.0.33 by @dependabot in https://github.com/containers/youki/pull/1169\n- chore(deps): bump clap from 3.2.18 to 3.2.19 by @dependabot in https://github.com/containers/youki/pull/1166\n- chore(deps): bump iana-time-zone from 0.1.46 to 0.1.47 by @dependabot in https://github.com/containers/youki/pull/1165\n- chore(deps): bump android_system_properties from 0.1.4 to 0.1.5 by @dependabot in https://github.com/containers/youki/pull/1164\n- chore(deps): bump dashmap from 5.3.4 to 5.4.0 by @dependabot in https://github.com/containers/youki/pull/1163\n- Upgrade dependencies by @utam0k in https://github.com/containers/youki/pull/1172\n- chore(deps): bump thiserror from 1.0.33 to 1.0.34 by @dependabot in https://github.com/containers/youki/pull/1175\n- chore(deps): bump miniz_oxide from 0.5.3 to 0.5.4 by @dependabot in https://github.com/containers/youki/pull/1176\n- chore(deps): bump oci-spec from 0.5.7 to 0.5.8 by @dependabot in https://github.com/containers/youki/pull/1179\n- chore(deps): bump url from 2.2.2 to 2.3.0 by @dependabot in https://github.com/containers/youki/pull/1180\n- chore(deps): bump percent-encoding from 2.1.0 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/1182\n- chore(deps): bump url from 2.3.0 to 2.3.1 by @dependabot in https://github.com/containers/youki/pull/1181\n- chore(deps): bump anyhow from 1.0.63 to 1.0.65 by @dependabot in https://github.com/containers/youki/pull/1192\n- chore(deps): bump iana-time-zone from 0.1.47 to 0.1.48 by @dependabot in https://github.com/containers/youki/pull/1185\n- chore(deps): bump clap from 3.2.20 to 3.2.21 by @dependabot in https://github.com/containers/youki/pull/1184\n- Fix README issue links by @LeoColomb in https://github.com/containers/youki/pull/1183\n- chore(deps): bump unicode-ident from 1.0.3 to 1.0.4 by @dependabot in https://github.com/containers/youki/pull/1193\n- chore(deps): bump wasm-bindgen from 0.2.82 to 0.2.83 by @dependabot in https://github.com/containers/youki/pull/1186\n- chore(deps): bump js-sys from 0.3.59 to 0.3.60 by @dependabot in https://github.com/containers/youki/pull/1188\n- chore(deps): bump clap_complete from 3.2.4 to 3.2.5 by @dependabot in https://github.com/containers/youki/pull/1187\n- chore(deps): bump syn from 1.0.99 to 1.0.100 by @dependabot in https://github.com/containers/youki/pull/1196\n- chore(deps): bump unicode-normalization from 0.1.21 to 0.1.22 by @dependabot in https://github.com/containers/youki/pull/1199\n- chore(deps): bump env_logger from 0.9.0 to 0.9.1 by @dependabot in https://github.com/containers/youki/pull/1198\n- chore(deps): bump clap from 3.2.21 to 3.2.22 by @dependabot in https://github.com/containers/youki/pull/1197\n- Get the result of exec command by @utam0k in https://github.com/containers/youki/pull/1018\n- chore(deps): bump lock_api from 0.4.8 to 0.4.9 by @dependabot in https://github.com/containers/youki/pull/1203\n- chore(deps): bump once_cell from 1.14.0 to 1.15.0 by @dependabot in https://github.com/containers/youki/pull/1201\n- chore(deps): bump thiserror from 1.0.34 to 1.0.35 by @dependabot in https://github.com/containers/youki/pull/1191\n- chore(deps): bump wat from 1.0.48 to 1.0.49 by @dependabot in https://github.com/containers/youki/pull/1205\n- chore(deps): bump iana-time-zone from 0.1.48 to 0.1.49 by @dependabot in https://github.com/containers/youki/pull/1208\n- chore(deps): bump serde from 1.0.144 to 1.0.145 by @dependabot in https://github.com/containers/youki/pull/1207\n- chore(deps): bump rand_core from 0.6.3 to 0.6.4 by @dependabot in https://github.com/containers/youki/pull/1194\n- chore(deps): bump unicode-width from 0.1.9 to 0.1.10 by @dependabot in https://github.com/containers/youki/pull/1189\n- chore(deps): bump itertools from 0.10.3 to 0.10.5 by @dependabot in https://github.com/containers/youki/pull/1202\n- chore(deps): bump jobserver from 0.1.24 to 0.1.25 by @dependabot in https://github.com/containers/youki/pull/1215\n- chore(deps): bump proc-macro2 from 1.0.43 to 1.0.44 by @dependabot in https://github.com/containers/youki/pull/1216\n- chore(deps): bump thiserror from 1.0.35 to 1.0.36 by @dependabot in https://github.com/containers/youki/pull/1218\n- chore(deps): bump sysinfo from 0.26.2 to 0.26.3 by @dependabot in https://github.com/containers/youki/pull/1219\n- chore(deps): bump iana-time-zone from 0.1.49 to 0.1.50 by @dependabot in https://github.com/containers/youki/pull/1220\n- Rust 1.64.0 by @utam0k in https://github.com/containers/youki/pull/1211\n- chore(deps): bump sysinfo from 0.26.3 to 0.26.4 by @dependabot in https://github.com/containers/youki/pull/1221\n- chore(deps): bump syn from 1.0.100 to 1.0.101 by @dependabot in https://github.com/containers/youki/pull/1222\n- Support domainname by @higuruchi in https://github.com/containers/youki/pull/1214\n- chore(deps): bump rustix from 0.35.9 to 0.35.10 by @dependabot in https://github.com/containers/youki/pull/1206\n- chore(deps): bump rustix from 0.35.10 to 0.35.11 by @dependabot in https://github.com/containers/youki/pull/1224\n- chore(deps): bump thiserror from 1.0.36 to 1.0.37 by @dependabot in https://github.com/containers/youki/pull/1223\n- chore(deps): bump proc-macro2 from 1.0.44 to 1.0.45 by @dependabot in https://github.com/containers/youki/pull/1226\n- chore(deps): bump clap_derive from 3.2.18 to 4.0.1 by @dependabot in https://github.com/containers/youki/pull/1228\n- chore(deps): bump crossbeam-utils from 0.8.11 to 0.8.12 by @dependabot in https://github.com/containers/youki/pull/1233\n- chore(deps): bump proc-macro2 from 1.0.45 to 1.0.46 by @dependabot in https://github.com/containers/youki/pull/1230\n- chore(deps): bump crossbeam-epoch from 0.9.10 to 0.9.11 by @dependabot in https://github.com/containers/youki/pull/1232\n- chore(deps): bump smallvec from 1.9.0 to 1.10.0 by @dependabot in https://github.com/containers/youki/pull/1239\n- chore(deps): bump clap_derive from 4.0.1 to 4.0.8 by @dependabot in https://github.com/containers/youki/pull/1237\n- chore(deps): bump libseccomp from 0.2.3 to 0.3.0 by @dependabot in https://github.com/containers/youki/pull/1240\n- chore(deps): bump enumset from 1.0.11 to 1.0.12 by @dependabot in https://github.com/containers/youki/pull/1244\n- chore(deps): bump clap_derive from 4.0.8 to 4.0.9 by @dependabot in https://github.com/containers/youki/pull/1242\n- chore(deps): bump wast from 47.0.0 to 47.0.1 by @dependabot in https://github.com/containers/youki/pull/1243\n- chore(deps): bump enumset_derive from 0.6.0 to 0.6.1 by @dependabot in https://github.com/containers/youki/pull/1241\n- chore(deps): bump clap_derive from 4.0.9 to 4.0.10 by @dependabot in https://github.com/containers/youki/pull/1245\n- chore(deps): bump tracing-core from 0.1.29 to 0.1.30 by @dependabot in https://github.com/containers/youki/pull/1250\n- chore(deps): bump tracing from 0.1.36 to 0.1.37 by @dependabot in https://github.com/containers/youki/pull/1249\n- chore(deps): bump itoa from 1.0.3 to 1.0.4 by @dependabot in https://github.com/containers/youki/pull/1248\n- Add git commit sha placeholder if .git not found by @YJDoc2 in https://github.com/containers/youki/pull/1251\n- chore(deps): bump syn from 1.0.101 to 1.0.102 by @dependabot in https://github.com/containers/youki/pull/1247\n- chore(deps): bump unicode-ident from 1.0.4 to 1.0.5 by @dependabot in https://github.com/containers/youki/pull/1253\n- chore(deps): bump iana-time-zone from 0.1.50 to 0.1.51 by @dependabot in https://github.com/containers/youki/pull/1254\n- chore(deps): bump uuid from 1.1.2 to 1.2.1 by @dependabot in https://github.com/containers/youki/pull/1255\n- chore(deps): bump serde_json from 1.0.85 to 1.0.86 by @dependabot in https://github.com/containers/youki/pull/1256\n- fix a typo by @DriedYellowPeach in https://github.com/containers/youki/pull/1257\n- chore(deps): bump clap_derive from 4.0.10 to 4.0.13 by @dependabot in https://github.com/containers/youki/pull/1261\n- chore(actions): upgrade to checkout action to v3 by @fsmiamoto in https://github.com/containers/youki/pull/1263\n- chore(deps): bump syscalls from 0.6.6 to 0.6.7 by @dependabot in https://github.com/containers/youki/pull/1264\n- chore(deps): bump iana-time-zone-haiku from 0.1.0 to 0.1.1 by @dependabot in https://github.com/containers/youki/pull/1271\n- chore(deps): bump cxx-build from 1.0.78 to 1.0.79 by @dependabot in https://github.com/containers/youki/pull/1270\n- chore(deps): bump proc-macro2 from 1.0.46 to 1.0.47 by @dependabot in https://github.com/containers/youki/pull/1268\n- chore(deps): bump cxx from 1.0.78 to 1.0.79 by @dependabot in https://github.com/containers/youki/pull/1269\n- Update libbpf to 1.0.3+v1.0.1 by @YJDoc2 in https://github.com/containers/youki/pull/1265\n- chore(deps): bump bumpalo from 3.11.0 to 3.11.1 by @dependabot in https://github.com/containers/youki/pull/1276\n- chore(deps): bump fragile from 1.2.1 to 1.2.2 by @dependabot in https://github.com/containers/youki/pull/1275\n- chore(deps): bump parking_lot_core from 0.9.3 to 0.9.4 by @dependabot in https://github.com/containers/youki/pull/1274\n- chore(deps): bump sysinfo from 0.26.4 to 0.26.5 by @dependabot in https://github.com/containers/youki/pull/1272\n- chore(deps): bump mockall from 0.11.2 to 0.11.3 by @dependabot in https://github.com/containers/youki/pull/1273\n- chore(deps): bump clap_derive from 4.0.13 to 4.0.18 by @dependabot in https://github.com/containers/youki/pull/1287\n- chore(deps): bump futures from 0.3.24 to 0.3.25 by @dependabot in https://github.com/containers/youki/pull/1285\n- chore(deps): bump futures-core from 0.3.24 to 0.3.25 by @dependabot in https://github.com/containers/youki/pull/1284\n- chore(deps): bump futures-task from 0.3.24 to 0.3.25 by @dependabot in https://github.com/containers/youki/pull/1283\n- chore(deps): bump syn from 1.0.102 to 1.0.103 by @dependabot in https://github.com/containers/youki/pull/1281\n- chore(deps): bump getrandom from 0.2.7 to 0.2.8 by @dependabot in https://github.com/containers/youki/pull/1282\n- chore(deps): bump futures-channel from 0.3.24 to 0.3.25 by @dependabot in https://github.com/containers/youki/pull/1280\n- Make exec behaviour consistent with runc's exec by @YJDoc2 in https://github.com/containers/youki/pull/1252\n- Fix how cgroup manager is created based on cgroups path by @YJDoc2 in https://github.com/containers/youki/pull/1288\n- chore(deps): bump serde from 1.0.145 to 1.0.147 by @dependabot in https://github.com/containers/youki/pull/1290\n- chore(deps): bump io-lifetimes from 0.7.3 to 0.7.4 by @dependabot in https://github.com/containers/youki/pull/1291\n- chore(deps): bump filetime from 0.2.17 to 0.2.18 by @dependabot in https://github.com/containers/youki/pull/1292\n- chore(deps): bump rustix from 0.35.11 to 0.35.12 by @dependabot in https://github.com/containers/youki/pull/1293\n- chore(deps): bump cxx-build from 1.0.79 to 1.0.80 by @dependabot in https://github.com/containers/youki/pull/1294\n- chore(deps): bump cxx from 1.0.79 to 1.0.80 by @dependabot in https://github.com/containers/youki/pull/1295\n- chore(deps): bump clap from 3.2.22 to 3.2.23 by @dependabot in https://github.com/containers/youki/pull/1298\n- chore(deps): bump mio from 0.8.4 to 0.8.5 by @dependabot in https://github.com/containers/youki/pull/1299\n- chore(deps): bump libbpf-sys from 1.0.3+v1.0.1 to 1.0.4+v1.0.1 by @dependabot in https://github.com/containers/youki/pull/1301\n- Log result of the command before returning from main by @YJDoc2 in https://github.com/containers/youki/pull/1302\n- chore(deps): bump wat from 1.0.49 to 1.0.50 by @dependabot in https://github.com/containers/youki/pull/1303\n- chore(deps): bump darling from 0.14.1 to 0.14.2 by @dependabot in https://github.com/containers/youki/pull/1306\n- chore(deps): bump sysinfo from 0.26.5 to 0.26.6 by @dependabot in https://github.com/containers/youki/pull/1307\n- chore(deps): bump pkg-config from 0.3.25 to 0.3.26 by @dependabot in https://github.com/containers/youki/pull/1308\n- chore(deps): bump serde_json from 1.0.86 to 1.0.87 by @dependabot in https://github.com/containers/youki/pull/1279\n- Thaw a paused container in cgroup v1 when it is forcely deleted. by @cyyzero in https://github.com/containers/youki/pull/1204\n- chore(deps): bump once_cell from 1.15.0 to 1.16.0 by @dependabot in https://github.com/containers/youki/pull/1309\n- chore(deps): bump io-lifetimes from 0.7.4 to 0.7.5 by @dependabot in https://github.com/containers/youki/pull/1317\n- chore(deps): bump rustix from 0.35.12 to 0.35.13 by @dependabot in https://github.com/containers/youki/pull/1316\n- chore(deps): bump caps from 0.5.4 to 0.5.5 by @dependabot in https://github.com/containers/youki/pull/1315\n- chore(deps): bump sysinfo from 0.26.6 to 0.26.7 by @dependabot in https://github.com/containers/youki/pull/1313\n- chore(deps): bump os_str_bytes from 6.3.0 to 6.3.1 by @dependabot in https://github.com/containers/youki/pull/1312\n- chore(deps): bump iana-time-zone from 0.1.51 to 0.1.53 by @dependabot in https://github.com/containers/youki/pull/1310\n- Add TestContainerKill required error message in kill command by @YJDoc2 in https://github.com/containers/youki/pull/1319\n- chore(deps): bump predicates-core from 1.0.3 to 1.0.4 by @dependabot in https://github.com/containers/youki/pull/1322\n- chore(deps): bump regex-syntax from 0.6.27 to 0.6.28 by @dependabot in https://github.com/containers/youki/pull/1328\n- chore(deps): bump predicates-tree from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/1323\n- chore(deps): bump target-lexicon from 0.12.4 to 0.12.5 by @dependabot in https://github.com/containers/youki/pull/1324\n- chore(deps): bump regex from 1.6.0 to 1.7.0 by @dependabot in https://github.com/containers/youki/pull/1325\n- chore(deps): bump libloading from 0.7.3 to 0.7.4 by @dependabot in https://github.com/containers/youki/pull/1327\n- Upgrade to 1.65 and fix lint errors by @YJDoc2 in https://github.com/containers/youki/pull/1321\n- chore(deps): bump predicates from 2.1.1 to 2.1.2 by @dependabot in https://github.com/containers/youki/pull/1326\n- chore(deps): bump cc from 1.0.73 to 1.0.74 by @dependabot in https://github.com/containers/youki/pull/1311\n- Add the logo with the name by @utam0k in https://github.com/containers/youki/pull/1329\n- chore(deps): bump wat from 1.0.50 to 1.0.51 by @dependabot in https://github.com/containers/youki/pull/1338\n- chore(deps): bump num_cpus from 1.13.1 to 1.14.0 by @dependabot in https://github.com/containers/youki/pull/1337\n- chore(deps): bump env_logger from 0.9.1 to 0.9.3 by @dependabot in https://github.com/containers/youki/pull/1335\n- chore(deps): bump cxx from 1.0.80 to 1.0.81 by @dependabot in https://github.com/containers/youki/pull/1334\n- chore(deps): bump ppv-lite86 from 0.2.16 to 0.2.17 by @dependabot in https://github.com/containers/youki/pull/1333\n- chore(deps): bump memmap2 from 0.5.7 to 0.5.8 by @dependabot in https://github.com/containers/youki/pull/1340\n- chore(deps): bump cc from 1.0.74 to 1.0.76 by @dependabot in https://github.com/containers/youki/pull/1336\n- chore(deps): bump clap_derive from 4.0.18 to 4.0.21 by @dependabot in https://github.com/containers/youki/pull/1332\n- chore(deps): bump cxx-build from 1.0.80 to 1.0.81 by @dependabot in https://github.com/containers/youki/pull/1331\n- chore(deps): bump predicates from 2.1.2 to 2.1.3 by @dependabot in https://github.com/containers/youki/pull/1341\n- chore(deps): bump predicates-tree from 1.0.6 to 1.0.7 by @dependabot in https://github.com/containers/youki/pull/1342\n- chore(deps): bump predicates-core from 1.0.4 to 1.0.5 by @dependabot in https://github.com/containers/youki/pull/1343\n- Ignore error when killing, if error is 'process does not exist' by @YJDoc2 in https://github.com/containers/youki/pull/1339\n- chore(deps): bump chrono from 0.4.22 to 0.4.23 by @dependabot in https://github.com/containers/youki/pull/1344\n- chore(deps): bump os_str_bytes from 6.3.1 to 6.4.0 by @dependabot in https://github.com/containers/youki/pull/1345\n- chore(deps): bump uuid from 1.2.1 to 1.2.2 by @dependabot in https://github.com/containers/youki/pull/1346\n- Fixed set capability fail. by @higuruchi in https://github.com/containers/youki/pull/1349\n- Pass features across crates by @Silcet in https://github.com/containers/youki/pull/1330\n- Detect architecture by uname command by @udzura in https://github.com/containers/youki/pull/1352\n- chore(deps): bump indexmap from 1.9.1 to 1.9.2 by @dependabot in https://github.com/containers/youki/pull/1350\n- Small fix and refine documents by @udzura in https://github.com/containers/youki/pull/1351\n- chore(deps): bump rayon-core from 1.9.3 to 1.10.1 by @dependabot in https://github.com/containers/youki/pull/1360\n- chore(deps): bump cc from 1.0.76 to 1.0.77 by @dependabot in https://github.com/containers/youki/pull/1353\n- chore(deps): bump cxx-build from 1.0.81 to 1.0.82 by @dependabot in https://github.com/containers/youki/pull/1354\n- chore(deps): bump crossbeam-epoch from 0.9.11 to 0.9.13 by @dependabot in https://github.com/containers/youki/pull/1362\n- chore(deps): bump crossbeam-utils from 0.8.12 to 0.8.14 by @dependabot in https://github.com/containers/youki/pull/1364\n- chore(deps): bump os_str_bytes from 6.4.0 to 6.4.1 by @dependabot in https://github.com/containers/youki/pull/1361\n- chore(deps): bump cxx from 1.0.81 to 1.0.82 by @dependabot in https://github.com/containers/youki/pull/1355\n- chore(deps): bump crossbeam-queue from 0.3.6 to 0.3.8 by @dependabot in https://github.com/containers/youki/pull/1363\n- chore(deps): bump aho-corasick from 0.7.19 to 0.7.20 by @dependabot in https://github.com/containers/youki/pull/1370\n- chore(deps): bump sysinfo from 0.26.7 to 0.26.8 by @dependabot in https://github.com/containers/youki/pull/1368\n- chore(deps): bump serde_json from 1.0.87 to 1.0.89 by @dependabot in https://github.com/containers/youki/pull/1367\n- chore(deps): bump wat from 1.0.51 to 1.0.52 by @dependabot in https://github.com/containers/youki/pull/1365\n- chore(deps): bump rayon from 1.5.3 to 1.6.0 by @dependabot in https://github.com/containers/youki/pull/1366\n- chore(deps): bump env_logger from 0.9.3 to 0.10.0 by @dependabot in https://github.com/containers/youki/pull/1374\n- chore(deps): bump flate2 from 1.0.24 to 1.0.25 by @dependabot in https://github.com/containers/youki/pull/1373\n- Improve the flow of the containerd test with youki by @utam0k in https://github.com/containers/youki/pull/1297\n- Fix TestContainerNoBinaryExists test, by making create behaviour similar to runc by @YJDoc2 in https://github.com/containers/youki/pull/1347\n- chore(deps): bump time from 0.1.44 to 0.1.45 by @dependabot in https://github.com/containers/youki/pull/1378\n- chore(deps): bump syn from 1.0.103 to 1.0.104 by @dependabot in https://github.com/containers/youki/pull/1382\n- chore(deps): bump serde from 1.0.147 to 1.0.148 by @dependabot in https://github.com/containers/youki/pull/1381\n- chore(deps): bump is-terminal from 0.4.0 to 0.4.1 by @dependabot in https://github.com/containers/youki/pull/1380\n- [libcontainer] Integrate WasmEdge Runtime by @apepkuss in https://github.com/containers/youki/pull/1320\n- [actions] add workflow file for containerd integration testing by @guni1192 in https://github.com/containers/youki/pull/968\n- Change targets in the makefile by @YJDoc2 in https://github.com/containers/youki/pull/1383\n- Update makefile changes in containerd ci by @YJDoc2 in https://github.com/containers/youki/pull/1386\n- chore(deps): bump parking_lot_core from 0.9.4 to 0.9.5 by @dependabot in https://github.com/containers/youki/pull/1384\n- chore(deps): bump protobuf from 2.27.1 to 2.28.0 by @dependabot in https://github.com/containers/youki/pull/1387\n- chore(deps): bump cmake from 0.1.48 to 0.1.49 by @dependabot in https://github.com/containers/youki/pull/1388\n- chore(deps): bump syn from 1.0.104 to 1.0.105 by @dependabot in https://github.com/containers/youki/pull/1390\n- Add hostname test by @chermehdi in https://github.com/containers/youki/pull/1376\n- chore(deps): bump cxx from 1.0.82 to 1.0.83 by @dependabot in https://github.com/containers/youki/pull/1394\n- chore(deps): bump cxx-build from 1.0.82 to 1.0.83 by @dependabot in https://github.com/containers/youki/pull/1393\n- Check capabilities in youki info subcommand by @udzura in https://github.com/containers/youki/pull/1389\n- chore(deps): bump serde from 1.0.148 to 1.0.149 by @dependabot in https://github.com/containers/youki/pull/1396\n- Pin github action image by @Furisto in https://github.com/containers/youki/pull/1401\n- Support pressure stall information by @Furisto in https://github.com/containers/youki/pull/1400\n- chore(deps): bump rayon from 1.6.0 to 1.6.1 by @dependabot in https://github.com/containers/youki/pull/1405\n- chore(deps): bump serde from 1.0.149 to 1.0.150 by @dependabot in https://github.com/containers/youki/pull/1404\n- chore(deps): bump paste from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/1408\n- Fix release script and prepare for release by @YJDoc2 in https://github.com/containers/youki/pull/1397\n\n## [v0.0.3](https://github.com/containers/youki/compare/v0.0.2...v0.0.3) - 2022-04-03\n- Use /dev/null inside of the container by @adrianreber in https://github.com/containers/youki/pull/630\n- Fix some typos and align formatting by @Szymongib in https://github.com/containers/youki/pull/631\n- chore(deps): bump serde from 1.0.133 to 1.0.135 by @dependabot in https://github.com/containers/youki/pull/635\n- chore(deps): bump serde_json from 1.0.75 to 1.0.78 by @dependabot in https://github.com/containers/youki/pull/637\n- chore(deps): bump anyhow from 1.0.52 to 1.0.53 by @dependabot in https://github.com/containers/youki/pull/636\n- chore(deps): bump crc32fast from 1.3.0 to 1.3.1 by @dependabot in https://github.com/containers/youki/pull/638\n- chore(deps): bump quote from 1.0.14 to 1.0.15 by @dependabot in https://github.com/containers/youki/pull/639\n- chore(deps): bump fastrand from 1.6.0 to 1.7.0 by @dependabot in https://github.com/containers/youki/pull/640\n- Suppport executing wasm workloads with wasmer by @Furisto in https://github.com/containers/youki/pull/548\n- chore(deps): bump libloading from 0.7.2 to 0.7.3 by @dependabot in https://github.com/containers/youki/pull/652\n- chore(deps): bump wasm-bindgen from 0.2.78 to 0.2.79 by @dependabot in https://github.com/containers/youki/pull/651\n- chore(deps): bump wasmer-types from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/650\n- chore(deps): bump rkyv from 0.7.28 to 0.7.29 by @dependabot in https://github.com/containers/youki/pull/648\n- chore(deps): bump wasmer-vfs from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/656\n- chore(deps): bump js-sys from 0.3.55 to 0.3.56 by @dependabot in https://github.com/containers/youki/pull/655\n- chore(deps): bump wat from 1.0.40 to 1.0.41 by @dependabot in https://github.com/containers/youki/pull/653\n- Always call setsid by @Furisto in https://github.com/containers/youki/pull/632\n- chore(deps): bump bumpalo from 3.8.0 to 3.9.1 by @dependabot in https://github.com/containers/youki/pull/649\n- chore(deps): bump memmap2 from 0.5.0 to 0.5.2 by @dependabot in https://github.com/containers/youki/pull/643\n- chore(deps): bump wasmer-compiler from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/644\n- chore(deps): bump vergen from 6.0.0 to 6.0.1 by @dependabot in https://github.com/containers/youki/pull/645\n- chore(deps): bump wasmer-vm from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/659\n- chore(deps): bump sha1 from 0.6.0 to 0.6.1 by @dependabot in https://github.com/containers/youki/pull/646\n- chore(deps): bump wasmer-derive from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/658\n- chore(deps): bump const_fn from 0.4.8 to 0.4.9 by @dependabot in https://github.com/containers/youki/pull/647\n- chore(deps): bump libc from 0.2.113 to 0.2.114 by @dependabot in https://github.com/containers/youki/pull/654\n- chore(deps): bump serde from 1.0.135 to 1.0.136 by @dependabot in https://github.com/containers/youki/pull/657\n- chore(deps): bump wasmer-engine from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/669\n- chore(deps): bump wasmer-compiler-cranelift from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/668\n- chore(deps): bump rkyv from 0.7.29 to 0.7.31 by @dependabot in https://github.com/containers/youki/pull/667\n- chore(deps): bump wasmer-object from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/666\n- chore(deps): bump wasmer-wasi-types from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/660\n- chore(deps): bump tempfile from 3.2.0 to 3.3.0 by @dependabot in https://github.com/containers/youki/pull/661\n- chore(deps): bump which from 4.2.2 to 4.2.4 by @dependabot in https://github.com/containers/youki/pull/664\n- chore(deps): bump libbpf-sys from 0.6.1-1 to 0.6.1-2 by @dependabot in https://github.com/containers/youki/pull/663\n- chore(deps): bump wasmer from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/665\n- Support umask by @Furisto in https://github.com/containers/youki/pull/642\n- chore(deps): bump wasmer-wasi from 2.1.0 to 2.1.1 by @dependabot in https://github.com/containers/youki/pull/672\n- chore(deps): bump vergen from 6.0.1 to 6.0.2 by @dependabot in https://github.com/containers/youki/pull/671\n- chore(deps): bump libc from 0.2.114 to 0.2.115 by @dependabot in https://github.com/containers/youki/pull/670\n- ready for integration test for the exec command. by @utam0k in https://github.com/containers/youki/pull/622\n- Ensure namespaces are entered in correct order by @Furisto in https://github.com/containers/youki/pull/674\n- Remove duplication from commands execution in Integration tests by @Szymongib in https://github.com/containers/youki/pull/673\n- chore(deps): bump lock_api from 0.4.5 to 0.4.6 by @dependabot in https://github.com/containers/youki/pull/675\n- chore(deps): bump libc from 0.2.115 to 0.2.116 by @dependabot in https://github.com/containers/youki/pull/676\n- chore(deps): bump backtrace from 0.3.63 to 0.3.64 by @dependabot in https://github.com/containers/youki/pull/677\n- chore(deps): bump target-lexicon from 0.12.2 to 0.12.3 by @dependabot in https://github.com/containers/youki/pull/678\n- chore(deps): bump libc from 0.2.116 to 0.2.117 by @dependabot in https://github.com/containers/youki/pull/679\n- make sure test_make_parent_mount_private() passes even when root is not a slave. by @utam0k in https://github.com/containers/youki/pull/682\n- chore(deps): bump tracing-core from 0.1.21 to 0.1.22 by @dependabot in https://github.com/containers/youki/pull/693\n- chore(deps): bump crossbeam-utils from 0.8.6 to 0.8.7 by @dependabot in https://github.com/containers/youki/pull/684\n- chore(deps): bump crossbeam-epoch from 0.9.6 to 0.9.7 by @dependabot in https://github.com/containers/youki/pull/685\n- chore(deps): bump futures-sink from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/686\n- chore(deps): bump tracing-attributes from 0.1.18 to 0.1.19 by @dependabot in https://github.com/containers/youki/pull/687\n- chore(deps): bump futures-channel from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/688\n- chore(deps): bump futures-core from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/689\n- chore(deps): bump crossbeam-queue from 0.3.3 to 0.3.4 by @dependabot in https://github.com/containers/youki/pull/691\n- chore(deps): bump futures-io from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/692\n- chore(deps): bump unicode-segmentation from 1.8.0 to 1.9.0 by @dependabot in https://github.com/containers/youki/pull/697\n- chore(deps): bump futures from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/690\n- chore(deps): bump tracing from 0.1.29 to 0.1.30 by @dependabot in https://github.com/containers/youki/pull/695\n- chore(deps): bump futures-task from 0.3.19 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/696\n- Use buffered io for reading state file by @Furisto in https://github.com/containers/youki/pull/683\n- chore(deps): bump crc32fast from 1.3.1 to 1.3.2 by @dependabot in https://github.com/containers/youki/pull/700\n- chore(deps): bump autocfg from 1.0.1 to 1.1.0 by @dependabot in https://github.com/containers/youki/pull/698\n- chore(deps): bump sysinfo from 0.23.0 to 0.23.1 by @dependabot in https://github.com/containers/youki/pull/699\n- adding HOME into envs when init containers by @mitnk in https://github.com/containers/youki/pull/681\n- make the rootless code testable by @utam0k in https://github.com/containers/youki/pull/634\n- chore(deps): bump memmap2 from 0.5.2 to 0.5.3 by @dependabot in https://github.com/containers/youki/pull/701\n- Add tests to libcgroups/src/v2/devices/emulator.rs by @128f in https://github.com/containers/youki/pull/704\n- Add gitpod as development option by @Furisto in https://github.com/containers/youki/pull/576\n- Add a description to 'create daemon.json' in Basic Usage in docs by @kobotomorrow in https://github.com/containers/youki/pull/707\n- chore(deps): bump sysinfo from 0.23.1 to 0.23.2 by @dependabot in https://github.com/containers/youki/pull/710\n- chore(deps): bump ntapi from 0.3.6 to 0.3.7 by @dependabot in https://github.com/containers/youki/pull/709\n- chore(deps): bump serde_json from 1.0.78 to 1.0.79 by @dependabot in https://github.com/containers/youki/pull/708\n- remove cargo config by @junnplus in https://github.com/containers/youki/pull/712\n- chore(deps): bump rand from 0.8.4 to 0.8.5 by @dependabot in https://github.com/containers/youki/pull/713\n- Always use the same permissions for youki dir by @Szymongib in https://github.com/containers/youki/pull/705\n- chore(deps): bump libc from 0.2.117 to 0.2.118 by @dependabot in https://github.com/containers/youki/pull/714\n- chore(deps): bump cc from 1.0.72 to 1.0.73 by @dependabot in https://github.com/containers/youki/pull/716\n- chore(deps): bump rkyv from 0.7.31 to 0.7.32 by @dependabot in https://github.com/containers/youki/pull/715\n- chore(deps): bump tracing from 0.1.30 to 0.1.31 by @dependabot in https://github.com/containers/youki/pull/718\n- add the rust-analyzer for gitpod. by @utam0k in https://github.com/containers/youki/pull/717\n- chore(deps): bump anyhow from 1.0.53 to 1.0.54 by @dependabot in https://github.com/containers/youki/pull/723\n- chore(deps): bump libc from 0.2.118 to 0.2.119 by @dependabot in https://github.com/containers/youki/pull/725\n- chore(deps): bump rkyv from 0.7.32 to 0.7.33 by @dependabot in https://github.com/containers/youki/pull/724\n- chore(deps): bump serial_test from 0.5.1 to 0.6.0 by @dependabot in https://github.com/containers/youki/pull/726\n- chore(deps): bump anyhow from 1.0.54 to 1.0.55 by @dependabot in https://github.com/containers/youki/pull/728\n- chore(deps): bump getrandom from 0.2.4 to 0.2.5 by @dependabot in https://github.com/containers/youki/pull/729\n- Use cgroup.kill file if available by @Furisto in https://github.com/containers/youki/pull/722\n- Add tests to crates/libcgroups/src/v2/devices/controller.rs by @128f in https://github.com/containers/youki/pull/706\n- Remove caching of OCI tests in CI by @YJDoc2 in https://github.com/containers/youki/pull/727\n- Add support for seccomp filter flags by @saschagrunert in https://github.com/containers/youki/pull/733\n- Fix Cargo.lock file that gets generated after build by @harche in https://github.com/containers/youki/pull/734\n- chore(deps): bump sysinfo from 0.23.2 to 0.23.3 by @dependabot in https://github.com/containers/youki/pull/736\n- chore(deps): bump serial_test from 0.5.1 to 0.6.0 by @dependabot in https://github.com/containers/youki/pull/735\n- chore(deps): bump sysinfo from 0.23.3 to 0.23.4 by @dependabot in https://github.com/containers/youki/pull/737\n- chore(deps): bump git2 from 0.13.25 to 0.14.0 by @dependabot in https://github.com/containers/youki/pull/738\n- Initial checkpoint support by @adrianreber in https://github.com/containers/youki/pull/641\n- Add the metadates for publishing a crate by @utam0k in https://github.com/containers/youki/pull/732\n- Bring back architecture diagrams to README. by @utam0k in https://github.com/containers/youki/pull/739\n- Handle relative paths by @Szymongib in https://github.com/containers/youki/pull/740\n- chore(deps): bump serial_test_derive from 0.5.1 to 0.6.0 by @dependabot in https://github.com/containers/youki/pull/742\n- Support rust 1.59.0 by @utam0k in https://github.com/containers/youki/pull/745\n- chore(deps): bump libgit2-sys from 0.13.0+1.4.1 to 0.13.1+1.4.2 by @dependabot in https://github.com/containers/youki/pull/747\n- chore(deps): bump redox_syscall from 0.2.10 to 0.2.11 by @dependabot in https://github.com/containers/youki/pull/746\n- chore(deps): bump git2 from 0.14.0 to 0.14.1 by @dependabot in https://github.com/containers/youki/pull/750\n- chore(deps): bump sysinfo from 0.23.4 to 0.23.5 by @dependabot in https://github.com/containers/youki/pull/749\n- chore(deps): bump wasmer-derive from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/748\n- chore(deps): bump wasmer-types from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/752\n- chore(deps): bump wasmer-vfs from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/751\n- chore(deps): bump wasmer-vm from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/755\n- chore(deps): bump wasmer-wasi-types from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/756\n- chore(deps): bump termcolor from 1.1.2 to 1.1.3 by @dependabot in https://github.com/containers/youki/pull/759\n- chore(deps): bump once_cell from 1.9.0 to 1.10.0 by @dependabot in https://github.com/containers/youki/pull/761\n- chore(deps): bump rkyv from 0.7.33 to 0.7.35 by @dependabot in https://github.com/containers/youki/pull/760\n- Create the pid file with integration test by @utam0k in https://github.com/containers/youki/pull/762\n- chore(deps): bump wasmer from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/754\n- Use the libseccomp-rs/libseccomp-rs crate instead of youki original libseccomp-rs. by @utam0k in https://github.com/containers/youki/pull/741\n- chore(deps): bump wasmer-wasi from 2.1.1 to 2.2.0 by @dependabot in https://github.com/containers/youki/pull/764\n- chore(deps): bump libz-sys from 1.1.3 to 1.1.4 by @dependabot in https://github.com/containers/youki/pull/765\n- Adds more mocked tests by @128f in https://github.com/containers/youki/pull/711\n- Use close_range where possible  by @Furisto in https://github.com/containers/youki/pull/758\n- Update oci-spec-rs to v0.5.5 by @saschagrunert in https://github.com/containers/youki/pull/744\n- Bump anyhow from 1.0.55 to 1.0.56 and fix warnings by @Furisto in https://github.com/containers/youki/pull/767\n- chore(deps): bump regex from 1.5.4 to 1.5.5 by @dependabot in https://github.com/containers/youki/pull/770\n- chore(deps): bump tracing-attributes from 0.1.19 to 0.1.20 by @dependabot in https://github.com/containers/youki/pull/769\n- chore(deps): bump libz-sys from 1.1.4 to 1.1.5 by @dependabot in https://github.com/containers/youki/pull/768\n- Refactor Directory structure by @YJDoc2 in https://github.com/containers/youki/pull/694\n- Fix a comment explaining that `seccom_rule_add` requires multiple args to be broken into multiple rules. by @yihuaf in https://github.com/containers/youki/pull/775\n- introduce the timeout for github actions by @utam0k in https://github.com/containers/youki/pull/777\n- fix log control env val not passing properly. by @utam0k in https://github.com/containers/youki/pull/778\n- Introduce the root Makefile. by @utam0k in https://github.com/containers/youki/pull/774\n- update README.md for makefile. by @utam0k in https://github.com/containers/youki/pull/779\n- Organize the workflows to adapt Makefile. by @utam0k in https://github.com/containers/youki/pull/780\n- fix the release workflow. by @utam0k in https://github.com/containers/youki/pull/781\n- make dependabot work again. by @utam0k in https://github.com/containers/youki/pull/782\n- chore(deps): bump mio from 0.8.0 to 0.8.2 in /crates by @dependabot in https://github.com/containers/youki/pull/783\n- chore(deps): bump log from 0.4.14 to 0.4.16 in /crates by @dependabot in https://github.com/containers/youki/pull/791\n- chore(deps): bump rkyv from 0.7.35 to 0.7.36 in /crates by @dependabot in https://github.com/containers/youki/pull/787\n- chore(deps): bump crossbeam-epoch from 0.9.7 to 0.9.8 in /crates by @dependabot in https://github.com/containers/youki/pull/795\n- chore(deps): bump libc from 0.2.119 to 0.2.121 in /crates by @dependabot in https://github.com/containers/youki/pull/793\n- chore(deps): bump quote from 1.0.15 to 1.0.16 in /crates by @dependabot in https://github.com/containers/youki/pull/790\n- chore(deps): bump tracing from 0.1.31 to 0.1.32 in /crates by @dependabot in https://github.com/containers/youki/pull/785\n- chore(deps): bump syn from 1.0.86 to 1.0.89 in /crates by @dependabot in https://github.com/containers/youki/pull/786\n- chore(deps): bump crossbeam-utils from 0.8.7 to 0.8.8 in /crates by @dependabot in https://github.com/containers/youki/pull/784\n- chore(deps): bump git2 from 0.14.1 to 0.14.2 in /crates by @dependabot in https://github.com/containers/youki/pull/796\n- chore(deps): bump crossbeam-channel from 0.5.2 to 0.5.4 in /crates by @dependabot in https://github.com/containers/youki/pull/792\n- chore(deps): bump libgit2-sys from 0.13.1+1.4.2 to 0.13.2+1.4.2 in /crates by @dependabot in https://github.com/containers/youki/pull/788\n- chore(deps): bump redox_syscall from 0.2.11 to 0.2.12 in /crates by @dependabot in https://github.com/containers/youki/pull/794\n- chore(deps): bump which from 4.2.4 to 4.2.5 in /crates by @dependabot in https://github.com/containers/youki/pull/799\n- chore(deps): bump paste from 1.0.6 to 1.0.7 in /crates by @dependabot in https://github.com/containers/youki/pull/800\n- chore(deps): bump quote from 1.0.16 to 1.0.17 in /crates by @dependabot in https://github.com/containers/youki/pull/801\n- chore(deps): bump sysinfo from 0.23.5 to 0.23.6 in /crates by @dependabot in https://github.com/containers/youki/pull/806\n- chore(deps): bump indexmap from 1.8.0 to 1.8.1 in /crates by @dependabot in https://github.com/containers/youki/pull/805\n- chore(deps): bump fragile from 1.1.0 to 1.2.0 in /crates by @dependabot in https://github.com/containers/youki/pull/804\n- chore(deps): bump getrandom from 0.2.5 to 0.2.6 in /crates by @dependabot in https://github.com/containers/youki/pull/802\n- chore(deps): bump syn from 1.0.89 to 1.0.90 in /crates by @dependabot in https://github.com/containers/youki/pull/803\n- Resolve deprecation warnings from clap by @YJDoc2 in https://github.com/containers/youki/pull/798\n- chore(deps): bump redox_syscall from 0.2.12 to 0.2.13 in /crates by @dependabot in https://github.com/containers/youki/pull/808\n- chore(deps): bump lock_api from 0.4.6 to 0.4.7 in /crates by @dependabot in https://github.com/containers/youki/pull/809\n- chore(deps): bump rkyv from 0.7.36 to 0.7.37 in /crates by @dependabot in https://github.com/containers/youki/pull/807\n- chore(deps): bump vergen from 6.0.2 to 7.0.0 in /crates by @dependabot in https://github.com/containers/youki/pull/789\n- chore(deps): bump clap from 3.1.6 to 3.1.7 in /crates by @dependabot in https://github.com/containers/youki/pull/812\n- chore(deps): bump pkg-config from 0.3.24 to 0.3.25 in /crates by @dependabot in https://github.com/containers/youki/pull/811\n- Release v0.0.3 by @utam0k in https://github.com/containers/youki/pull/816\n\n## [v0.0.2](https://github.com/containers/youki/compare/v0.0.1...v0.0.2) - 2022-01-22\n- Bump syn from 1.0.83 to 1.0.84 by @dependabot in https://github.com/containers/youki/pull/566\n- Bump proc-macro2 from 1.0.34 to 1.0.35 by @dependabot in https://github.com/containers/youki/pull/567\n- Resolved `needs_to_handle` TODO's by @SarthakSingh31 in https://github.com/containers/youki/pull/568\n- Fix typo in docs by @inductor in https://github.com/containers/youki/pull/572\n- Bump proc-macro2 from 1.0.35 to 1.0.36 by @dependabot in https://github.com/containers/youki/pull/571\n- Bump quote from 1.0.10 to 1.0.13 by @dependabot in https://github.com/containers/youki/pull/570\n- Interpret a cpu quota of zero as default value by @Furisto in https://github.com/containers/youki/pull/569\n- Add option to list test groups by @Furisto in https://github.com/containers/youki/pull/573\n- fix the name of build artifacts at release. by @utam0k in https://github.com/containers/youki/pull/574\n- Bump quote from 1.0.13 to 1.0.14 by @dependabot in https://github.com/containers/youki/pull/575\n- Bump version_check from 0.9.3 to 0.9.4 by @dependabot in https://github.com/containers/youki/pull/577\n- align the version of crates. by @utam0k in https://github.com/containers/youki/pull/578\n- Bump ppv-lite86 from 0.2.15 to 0.2.16 by @dependabot in https://github.com/containers/youki/pull/581\n- Use correct hugetlb interface file name by @Furisto in https://github.com/containers/youki/pull/579\n- Bump serde_json from 1.0.73 to 1.0.74 by @dependabot in https://github.com/containers/youki/pull/590\n- Bump serde from 1.0.132 to 1.0.133 by @dependabot in https://github.com/containers/youki/pull/589\n- Bump pin-project-lite from 0.2.7 to 0.2.8 by @dependabot in https://github.com/containers/youki/pull/588\n- Bump fixedbitset from 0.4.0 to 0.4.1 by @dependabot in https://github.com/containers/youki/pull/587\n- style(spellcheck): Run a spellchecker and fix typos by @em- in https://github.com/containers/youki/pull/580\n- Ignore autogenerated code by @Furisto in https://github.com/containers/youki/pull/592\n- Add commit id to info cmd by @Furisto in https://github.com/containers/youki/pull/593\n- Bump oci-spec to 0.5.3 to prevent specifying commit hash. by @utam0k in https://github.com/containers/youki/pull/596\n- chore(deps): bump libbpf-sys from 0.6.0-1 to 0.6.1-1 by @dependabot in https://github.com/containers/youki/pull/599\n- chore(deps): bump syn from 1.0.84 to 1.0.85 by @dependabot in https://github.com/containers/youki/pull/598\n- Readonly paths by @YJDoc2 in https://github.com/containers/youki/pull/582\n- clean up the shell script for youki integration test. by @utam0k in https://github.com/containers/youki/pull/600\n- chore(deps): bump crossbeam-channel from 0.5.1 to 0.5.2 by @dependabot in https://github.com/containers/youki/pull/602\n- chore(deps): bump crossbeam-epoch from 0.9.5 to 0.9.6 by @dependabot in https://github.com/containers/youki/pull/604\n- chore(deps): bump indexmap from 1.7.0 to 1.8.0 by @dependabot in https://github.com/containers/youki/pull/603\n- chore(deps): bump crossbeam-queue from 0.3.2 to 0.3.3 by @dependabot in https://github.com/containers/youki/pull/605\n- chore(deps): bump crossbeam-utils from 0.8.5 to 0.8.6 by @dependabot in https://github.com/containers/youki/pull/606\n- Improve cgroup path handling for rootless containers by @Furisto in https://github.com/containers/youki/pull/597\n- chore(deps): bump getrandom from 0.2.3 to 0.2.4 by @dependabot in https://github.com/containers/youki/pull/611\n- chore(deps): bump smallvec from 1.7.0 to 1.8.0 by @dependabot in https://github.com/containers/youki/pull/612\n- chore(deps): bump pnet from 0.28.0 to 0.29.0 by @dependabot in https://github.com/containers/youki/pull/610\n- Pin nightly version in CI as temporary fix to coverage issue by @YJDoc2 in https://github.com/containers/youki/pull/619\n- Ensure youki runs under podman by @Furisto in https://github.com/containers/youki/pull/613\n- chore(deps): bump serde_json from 1.0.74 to 1.0.75 by @dependabot in https://github.com/containers/youki/pull/618\n- make the runtime-tools directory one level deeper. by @utam0k in https://github.com/containers/youki/pull/614\n- support rust 1.58.0 by @utam0k in https://github.com/containers/youki/pull/615\n- Ensure exec command can find config.json by @Furisto in https://github.com/containers/youki/pull/616\n- chore(deps): bump syn from 1.0.85 to 1.0.86 by @dependabot in https://github.com/containers/youki/pull/625\n- chore(deps): bump libc from 0.2.112 to 0.2.113 by @dependabot in https://github.com/containers/youki/pull/624\n- Create device as 0666 and not 066 by @adrianreber in https://github.com/containers/youki/pull/627\n- integration test: move `config.json` to the code. by @utam0k in https://github.com/containers/youki/pull/621\n- Add minimum rust version requirement to libcgroups and libcontainers by @YJDoc2 in https://github.com/containers/youki/pull/626\n\n## [v0.0.1](https://github.com/containers/youki/commits/v0.0.1) - 2021-12-26\n- Add for local to README by @succie in https://github.com/containers/youki/pull/1\n- Update README.md by @aoki in https://github.com/containers/youki/pull/3\n- traial implementation of async/await. by @utam0k in https://github.com/containers/youki/pull/4\n- introduce ci for checkinng codes by github actions. by @utam0k in https://github.com/containers/youki/pull/5\n- implementation the controller of devices in cgroups. by @utam0k in https://github.com/containers/youki/pull/6\n- Improvement to make easier to write tests by @utam0k in https://github.com/containers/youki/pull/7\n- organize and add to the list of features. by @utam0k in https://github.com/containers/youki/pull/18\n- add information about the pronunciation and the etymology of youki by @yuchiki in https://github.com/containers/youki/pull/19\n- Support hugetlb cgroup by @Furisto in https://github.com/containers/youki/pull/15\n- refactor a code of github actions. by @utam0k in https://github.com/containers/youki/pull/22\n- add pids cgroup controller by @kmpzr in https://github.com/containers/youki/pull/26\n- Change logo by @128f in https://github.com/containers/youki/pull/29\n- add memory cgroup controller by @tsturzl in https://github.com/containers/youki/pull/16\n- adjust the size of the logo. by @utam0k in https://github.com/containers/youki/pull/30\n- Blkio cgroup support by @Furisto in https://github.com/containers/youki/pull/31\n- Fix typo in README.md regarding opencontainers/runtime-tools by @akluth in https://github.com/containers/youki/pull/35\n- cgroup v1 networking by @tsturzl in https://github.com/containers/youki/pull/34\n- fix a memory subsystem by @utam0k in https://github.com/containers/youki/pull/36\n- Add comments to main.rs by @YJDoc2 in https://github.com/containers/youki/pull/38\n- Update Rust-Analyzer in Dockerfile by @nalpine in https://github.com/containers/youki/pull/40\n- get oci_spec in separate crate by @ferrell-code in https://github.com/containers/youki/pull/42\n- extract the integration tests written in the ci file as a script file. by @utam0k in https://github.com/containers/youki/pull/37\n- revert asynchronous devices mounting. by @utam0k in https://github.com/containers/youki/pull/41\n- organize the logger. by @utam0k in https://github.com/containers/youki/pull/47\n- add default handling when there isn't cgroup path in config.json. by @utam0k in https://github.com/containers/youki/pull/45\n- add the tutorial on using youki. by @utam0k in https://github.com/containers/youki/pull/49\n- update README. by @utam0k in https://github.com/containers/youki/pull/50\n- Initial support for cgroups v2 by @Furisto in https://github.com/containers/youki/pull/48\n- make log level debug to get more information when ci failed. 4b260b0 by @utam0k in https://github.com/containers/youki/pull/53\n- cargo clippy. by @utam0k in https://github.com/containers/youki/pull/52\n- Align cgroup controller implementations by @Furisto in https://github.com/containers/youki/pull/54\n- Consolidate cgroup test methods by @Furisto in https://github.com/containers/youki/pull/57\n- Add 'Community' section to README.md by @nimrodshn in https://github.com/containers/youki/pull/59\n- Update README.md by @aoki in https://github.com/containers/youki/pull/58\n- Add comments to create.rs by @YJDoc2 in https://github.com/containers/youki/pull/43\n- utam0k -> containers by @smorimoto in https://github.com/containers/youki/pull/61\n- Support for cgroup v1 cpu and cpuset subsystem by @Furisto in https://github.com/containers/youki/pull/63\n- Add comments to process module and minor refactoring by @YJDoc2 in https://github.com/containers/youki/pull/64\n- Added install command for prerequisite in README by @PeterYordanov in https://github.com/containers/youki/pull/66\n- Fixed spelling mistake in src/rootfs.rs by @PeterYordanov in https://github.com/containers/youki/pull/67\n- add handling of WouldBlock error. by @utam0k in https://github.com/containers/youki/pull/68\n- Change execution path and fix CI by @minakawa-daiki in https://github.com/containers/youki/pull/73\n- Fix issues with cgroup v1 and v2 by @Furisto in https://github.com/containers/youki/pull/69\n- Added Integration test template by @minakawa-daiki in https://github.com/containers/youki/pull/71\n- Added doc comments modules by @PeterYordanov in https://github.com/containers/youki/pull/70\n- add some widgets to README.md by @utam0k in https://github.com/containers/youki/pull/76\n- Handle relative cgroup paths by @Furisto in https://github.com/containers/youki/pull/74\n- Improved testing, property testing, device tests by @tsturzl in https://github.com/containers/youki/pull/75\n- Document Container and Command modules by @YJDoc2 in https://github.com/containers/youki/pull/79\n- Fix badges in README by @tsturzl in https://github.com/containers/youki/pull/80\n- add create kill delete state in integration test by @lizhemingi in https://github.com/containers/youki/pull/81\n- Provide better error messages by @Furisto in https://github.com/containers/youki/pull/84\n- Clean up use of unsafe by @tsturzl in https://github.com/containers/youki/pull/85\n- Add info command by @Furisto in https://github.com/containers/youki/pull/83\n- Fix README link typo by @sasurau4 in https://github.com/containers/youki/pull/88\n- Add CODE-OF-CONDUCT.md and SECURITY.md by @utam0k in https://github.com/containers/youki/pull/86\n- clean up around the tty. by @utam0k in https://github.com/containers/youki/pull/89\n- Rename Cond to Pipe by @YJDoc2 in https://github.com/containers/youki/pull/90\n- make sure to log any unimplemented controllers. by @utam0k in https://github.com/containers/youki/pull/91\n- [WIP] Add support for cpuacct in cgroup v1. by @yjuba in https://github.com/containers/youki/pull/92\n- use bail! insted of anyhow by @utam0k in https://github.com/containers/youki/pull/94\n- Add a test for applying CpuAcct. by @yjuba in https://github.com/containers/youki/pull/96\n- Add cgroup v1 freezer controller by @lizhemingi in https://github.com/containers/youki/pull/93\n- Experimental support for rootless containers by @Furisto in https://github.com/containers/youki/pull/98\n- Add unit tests for tty module in https://github.com/containers/youki/pull/102\n- Extend info cmd with version and os by @Furisto in https://github.com/containers/youki/pull/101\n- Use `assert!` instead of `assert_eq!` when comparing a boolean. by @utam0k in https://github.com/containers/youki/pull/104\n- Add support for systemd managed cgroups by @nimrodshn in https://github.com/containers/youki/pull/46\n- update README.md by @utam0k in https://github.com/containers/youki/pull/105\n- Fix README.md Fedora & Centos instructions by @nimrodshn in https://github.com/containers/youki/pull/107\n- Add list command by @Furisto in https://github.com/containers/youki/pull/108\n- improve build time in CI by @utam0k in https://github.com/containers/youki/pull/97\n- split the subcommands into their own files. by @utam0k in https://github.com/containers/youki/pull/110\n- Update README.md by @bkochendorfer in https://github.com/containers/youki/pull/112\n- Separate adding tasks to cgroups and applying resource restrictions by @Furisto in https://github.com/containers/youki/pull/111\n- Require only requested cgroups to be present by @Furisto in https://github.com/containers/youki/pull/114\n- force delete container if it is running or created by @bobsongplus in https://github.com/containers/youki/pull/115\n- add comments in intergration_test.sh about test case that runc no paas by @bobsongplus in https://github.com/containers/youki/pull/116\n- remove unnecessary clone() in create.rs by @utam0k in https://github.com/containers/youki/pull/117\n- add cgroup v2 pids controller by @bobsongplus in https://github.com/containers/youki/pull/119\n- make String to signal conversion more simplify by using a Trait. by @utam0k in https://github.com/containers/youki/pull/122\n- Reduce size of binary by @Furisto in https://github.com/containers/youki/pull/124\n- Add cgroup v2 freezer controller by @lizhemingi in https://github.com/containers/youki/pull/123\n- Modularize container creation by @Furisto in https://github.com/containers/youki/pull/121\n- Cgroupv2 io controller by @bobsongplus in https://github.com/containers/youki/pull/128\n- fix the warnings shown by cargo clippy by @utam0k in https://github.com/containers/youki/pull/127\n- Add format check ci by @lizhemingi in https://github.com/containers/youki/pull/129\n- Fix spec path in delete by @lizhemingi in https://github.com/containers/youki/pull/130\n- Fix same tmp dir in freezer v2 tests by @lizhemingi in https://github.com/containers/youki/pull/133\n- Document capabilities rs and refactor its drop_privileges function by @YJDoc2 in https://github.com/containers/youki/pull/131\n- Document Info module by @YJDoc2 in https://github.com/containers/youki/pull/136\n- cgroupsv2 hugetlb by @kmpzr in https://github.com/containers/youki/pull/135\n- Document list and logger modules by @YJDoc2 in https://github.com/containers/youki/pull/137\n- Implement exec command by @Furisto in https://github.com/containers/youki/pull/138\n- Add pause and resume command by @lizhemingi in https://github.com/containers/youki/pull/139\n- Adds spec cli command by @ferrell-code in https://github.com/containers/youki/pull/55\n- memory cgv2 subsystem implemented by @tsturzl in https://github.com/containers/youki/pull/141\n- add serde_support to caps by @ferrell-code in https://github.com/containers/youki/pull/151\n- Correctly handle the rootfs path with bundle by @yihuaf in https://github.com/containers/youki/pull/153\n- Refactor the container creation to use `clone(2)` instead of fork. by @yihuaf in https://github.com/containers/youki/pull/143\n- prepare Vagrant instead of devcontainer for platforms other than linux. by @utam0k in https://github.com/containers/youki/pull/100\n- Document namespace.rs by @YJDoc2 in https://github.com/containers/youki/pull/154\n- Move commands into dedicated module by @Furisto in https://github.com/containers/youki/pull/155\n- Fix alignment of cgroups info by @Furisto in https://github.com/containers/youki/pull/157\n- Document Pause and Resume by @YJDoc2 in https://github.com/containers/youki/pull/156\n- Converted linux in spec from Option<Linux> to Linux by @YJDoc2 in https://github.com/containers/youki/pull/158\n- add implementation of run command by @zidoshare in https://github.com/containers/youki/pull/160\n- Cleanup state file path construction by @saschagrunert in https://github.com/containers/youki/pull/161\n- bump up to nix-0.22.0 by @utam0k in https://github.com/containers/youki/pull/164\n- Add integration tests for life cycle by @minakawa-daiki in https://github.com/containers/youki/pull/113\n- Use `remove_dir` instead of `remove_dir_all` because youki doesn't have permission to delete contents in cgroup directory. by @utam0k in https://github.com/containers/youki/pull/165\n- add perf_event to cgroups v1 by @fbrv in https://github.com/containers/youki/pull/166\n- Refactor clone(2) child stack creation. by @yihuaf in https://github.com/containers/youki/pull/167\n- make the builder pattern more flowing and code readable. by @utam0k in https://github.com/containers/youki/pull/169\n- Generalize OCI spec root by @saschagrunert in https://github.com/containers/youki/pull/174\n- Fix how closure is transferred to the clone call. by @yihuaf in https://github.com/containers/youki/pull/173\n- ci with release build by @utam0k in https://github.com/containers/youki/pull/175\n- Implement events command for cgroup v1 stats by @Furisto in https://github.com/containers/youki/pull/171\n- Implementation of ps commmand by @zidoshare in https://github.com/containers/youki/pull/172\n- Implement --preserve-fds flag by @yihuaf in https://github.com/containers/youki/pull/177\n- Add `Hooks` to OCI spec by @saschagrunert in https://github.com/containers/youki/pull/178\n- implemented LISTEN_FDS by @yihuaf in https://github.com/containers/youki/pull/180\n- Add Windows, VM and Solaris types by @saschagrunert in https://github.com/containers/youki/pull/181\n- make ci fail even with clippy warning level. by @utam0k in https://github.com/containers/youki/pull/176\n- Update README to reflect completion of features by @tsturzl in https://github.com/containers/youki/pull/190\n- Implement events command for cgroup v2 stats by @Furisto in https://github.com/containers/youki/pull/191\n- adjust author and version to current status. by @utam0k in https://github.com/containers/youki/pull/192\n- Improve looking up the root directory by @Furisto in https://github.com/containers/youki/pull/193\n- reduce the number of clones by introducing lifetime to rootless. by @utam0k in https://github.com/containers/youki/pull/194\n- delete the original FileDescriptor. by @utam0k in https://github.com/containers/youki/pull/195\n- Make optional types optional by @saschagrunert in https://github.com/containers/youki/pull/183\n- support readonly path by @lizhemingi in https://github.com/containers/youki/pull/196\n- reduce the number of clones by introducing lifetime to namespaces. by @utam0k in https://github.com/containers/youki/pull/197\n- Move cgroups into own crate by @Furisto in https://github.com/containers/youki/pull/198\n- Support sysctl by @Furisto in https://github.com/containers/youki/pull/199\n- Change the license from MIT to Apache 2.0 by @utam0k in https://github.com/containers/youki/pull/200\n- Implemented hooks by @yihuaf in https://github.com/containers/youki/pull/187\n- Reflected that oci_spec has been moved to a separate repository by @utam0k in https://github.com/containers/youki/pull/202\n- Support unified resource section by @Furisto in https://github.com/containers/youki/pull/203\n- Organize integration tests and add current status to README by @utam0k in https://github.com/containers/youki/pull/204\n- make sure integration tests complete in ubuntu 20.04 enviroment. by @utam0k in https://github.com/containers/youki/pull/206\n- Fail fast to create a container if bundle path is illegal by @tiqwab in https://github.com/containers/youki/pull/210\n- ensure theat read only paths work properly. by @utam0k in https://github.com/containers/youki/pull/212\n- fork: use 8MB stack if rlimit returns unlimited  by @MoZhonghua in https://github.com/containers/youki/pull/214\n- Chdir to process.cwd before starting container to pass integration test by @guni1192 in https://github.com/containers/youki/pull/215\n- increment as we pass the `process` case. by @utam0k in https://github.com/containers/youki/pull/216\n- Add necessary libraries to build youki in Vagrant provision by @tiqwab in https://github.com/containers/youki/pull/219\n- Fix clone(2) with double fork by @yihuaf in https://github.com/containers/youki/pull/217\n- Fix integration_test script for go env by @chenyukang in https://github.com/containers/youki/pull/222\n- Show error log only when error happens by @chenyukang in https://github.com/containers/youki/pull/223\n- exclude blkio test case in runtime-tools bacause it doesn't support linux kernel 5.0 or later. by @utam0k in https://github.com/containers/youki/pull/211\n- Fix #209, pass root-readonly by @chenyukang in https://github.com/containers/youki/pull/224\n- Tweak document by @chenyukang in https://github.com/containers/youki/pull/220\n- introduction to sequence diagrams using vscode's draw.io by @utam0k in https://github.com/containers/youki/pull/231\n- Fix user namespace for integration tests by @yihuaf in https://github.com/containers/youki/pull/233\n- Fix tutorial in readme by @chenyukang in https://github.com/containers/youki/pull/229\n- Fix graceful shutdown when intermediate or init process errors or panic by @yihuaf in https://github.com/containers/youki/pull/238\n- [WIP] cgroups v2: PoC of devices controller by @MoZhonghua in https://github.com/containers/youki/pull/208\n- Pass misc props test by @Furisto in https://github.com/containers/youki/pull/245\n- Use chroot when not entering into mount namespace by @yihuaf in https://github.com/containers/youki/pull/242\n- [Trivial] Include 3 more passed integration test by @yihuaf in https://github.com/containers/youki/pull/247\n- distinguish channels more clearly between each process by @utam0k in https://github.com/containers/youki/pull/244\n- [Trivial] Fix a typo where gid should be uid. by @yihuaf in https://github.com/containers/youki/pull/253\n- enable oom_score_adj test by @yihuaf in https://github.com/containers/youki/pull/251\n- Add codecov by @chenyukang in https://github.com/containers/youki/pull/232\n- Pass process user integration test by @Furisto in https://github.com/containers/youki/pull/243\n- Minor improvements to Container Struct by @utam0k in https://github.com/containers/youki/pull/257\n- Add namespace information to info command by @Furisto in https://github.com/containers/youki/pull/258\n- add tests of ContainerStatus. by @utam0k in https://github.com/containers/youki/pull/264\n- The `.grcov.yml` moves under the `.github/` because `.github/workflows` is recognized as GitHub actions files. by @utam0k in https://github.com/containers/youki/pull/263\n- Print logfile when test case crash by @chenyukang in https://github.com/containers/youki/pull/265\n- Create test framework and setup initial integration tests by @YJDoc2 in https://github.com/containers/youki/pull/186\n- fix unstable the channel tests. by @utam0k in https://github.com/containers/youki/pull/267\n- fix a failure because it is running before checkout. by @utam0k in https://github.com/containers/youki/pull/270\n- disable the code coverage because of unstable. by @utam0k in https://github.com/containers/youki/pull/272\n- Rework cgroup detection by @Furisto in https://github.com/containers/youki/pull/269\n- Pass mounts/mounts in testing by @chenyukang in https://github.com/containers/youki/pull/268\n- bump clap and use crate_version macro by @humancalico in https://github.com/containers/youki/pull/259\n- CI Code Coverage Fix by @YJDoc2 in https://github.com/containers/youki/pull/273\n- Bump procfs by @Furisto in https://github.com/containers/youki/pull/274\n- Pass linux_masked_paths by @chenyukang in https://github.com/containers/youki/pull/276\n- Fixing and stabilizing github actions by @utam0k in https://github.com/containers/youki/pull/275\n- Stablize cargo test by @yihuaf in https://github.com/containers/youki/pull/277\n- Change cache action from default to Swatinem/rust-cache@v1  by @YJDoc2 in https://github.com/containers/youki/pull/278\n- cache runtime-tools. by @utam0k in https://github.com/containers/youki/pull/280\n- fix cargo clippy warning in cgroups. by @utam0k in https://github.com/containers/youki/pull/281\n- fix: Mismatch of PWD in tutorial by @kenoss in https://github.com/containers/youki/pull/283\n- Convert memory swap values by @Furisto in https://github.com/containers/youki/pull/285\n- add Rust 1.55.0 by @utam0k in https://github.com/containers/youki/pull/288\n- fix cargo clippy warning in cgroups by @utam0k in https://github.com/containers/youki/pull/291\n- check if commands used in the unit test exists. by @utam0k in https://github.com/containers/youki/pull/290\n- Pass uid_mapping test by @tommady in https://github.com/containers/youki/pull/289\n- fix a failure when dirs is empty at changes job. by @utam0k in https://github.com/containers/youki/pull/294\n- Upgrade oci-spec-rs to 0.4.0 by @guni1192 in https://github.com/containers/youki/pull/266\n- update oci compliance in README. by @utam0k in https://github.com/containers/youki/pull/293\n- 279 increate the code coverage of src capabilities by @tommady in https://github.com/containers/youki/pull/296\n- Extend info cmd with status of cgroup controllers by @Furisto in https://github.com/containers/youki/pull/286\n- sipliy split init.rs into several files. by @utam0k in https://github.com/containers/youki/pull/297\n- Introduce a workspace to enable execution of commands in bulk. by @utam0k in https://github.com/containers/youki/pull/287\n- Don't skip the hook timeout test, to incease coverage by @yihuaf in https://github.com/containers/youki/pull/298\n- Implemented seccomp and pass the integration test by @yihuaf in https://github.com/containers/youki/pull/292\n- update the README about seccomp. by @utam0k in https://github.com/containers/youki/pull/301\n- fix doc comment of with_preserved_fds by @shorii in https://github.com/containers/youki/pull/302\n- Forbid empty string values for container id in commands by @YJDoc2 in https://github.com/containers/youki/pull/305\n- Fix Changes Job in CI by @YJDoc2 in https://github.com/containers/youki/pull/306\n- prepare to use system call mocks in unit tests by @utam0k in https://github.com/containers/youki/pull/304\n- handle name as a str instead of a String. by @utam0k in https://github.com/containers/youki/pull/308\n- Add `new` method to instantiate Delete command by @rosds in https://github.com/containers/youki/pull/262\n- Support 'shared' and 'unbindable' rootfs propagations by @tiqwab in https://github.com/containers/youki/pull/309\n- Add integration test utils necessary for implementing rest integration tests by @YJDoc2 in https://github.com/containers/youki/pull/310\n- Implement apparmor support by @Furisto in https://github.com/containers/youki/pull/312\n- add unit tests for gid and uid mapping in `builder_impl()` by @utam0k in https://github.com/containers/youki/pull/311\n- Fix error message(`LinuixIdMapping` to `uid_mappings`) by @shorii in https://github.com/containers/youki/pull/318\n- style: Fix indentation by @kenoss in https://github.com/containers/youki/pull/319\n- avoid cloning LinuxResources because it is a large structure. by @utam0k in https://github.com/containers/youki/pull/320\n- fix vagrant errors #321 by @zidoshare in https://github.com/containers/youki/pull/322\n- fix build error in vagrant by @zidoshare in https://github.com/containers/youki/pull/323\n- rootful mode for vagrant by @zidoshare in https://github.com/containers/youki/pull/324\n- fix flaky unit tests by @utam0k in https://github.com/containers/youki/pull/326\n- Make container commands more suitable for use as a library by @Furisto in https://github.com/containers/youki/pull/314\n- add a unit test for applying cgroup in builder_impl(). by @utam0k in https://github.com/containers/youki/pull/325\n- Complete command help information by @Furisto in https://github.com/containers/youki/pull/334\n- Improve readme and docs by @Furisto in https://github.com/containers/youki/pull/335\n- Adds a note why `pidfile` integration test doesn't work by @yihuaf in https://github.com/containers/youki/pull/315\n- add to README that all runtime_tools tests have been covered. by @utam0k in https://github.com/containers/youki/pull/336\n- Updated oci-spec-rs to 0.5.1 or later by @guni1192 in https://github.com/containers/youki/pull/303\n- Ensure cgroup error behavior is consistent with runc by @Furisto in https://github.com/containers/youki/pull/333\n- fix inaccessiblity of private field. by @utam0k in https://github.com/containers/youki/pull/338\n- Add various refactorings by @tranzystorekk in https://github.com/containers/youki/pull/341\n- Implement seccomp notify by @yihuaf in https://github.com/containers/youki/pull/330\n- Add HugeTLB tests by @YJDoc2 in https://github.com/containers/youki/pull/339\n- README edits for clarity and correctness by @oldaccountdeadname in https://github.com/containers/youki/pull/348\n- Implemented util function to test in child process by @yihuaf in https://github.com/containers/youki/pull/345\n- Seal /proc/self/exe to protect against CVE-2019-5736 by @oblique in https://github.com/containers/youki/pull/343\n- remove dead code in src/utils.rs by @hle0 in https://github.com/containers/youki/pull/352\n- Support cgroup namespaces for cgroup v1 by @Furisto in https://github.com/containers/youki/pull/349\n- Add pid to newuidmap/newgidmap as argument by @shorii in https://github.com/containers/youki/pull/353\n- Add cgroup namespace to info command by @Furisto in https://github.com/containers/youki/pull/355\n- Add rootless option for spec by @chenyukang in https://github.com/containers/youki/pull/350\n- part of PR 340 - adding syscalls by @tommady in https://github.com/containers/youki/pull/356\n- part of PR 340 moving syscalls into structure by @tommady in https://github.com/containers/youki/pull/357\n- part of PR 340 adding two testcases test_to_sflag and test_parse_mount by @tommady in https://github.com/containers/youki/pull/358\n- part of PR 340 adding test_setup_ptmx and test_setup_default_symlinks by @tommady in https://github.com/containers/youki/pull/359\n- Implement secure_join for path by @Ian-Yy in https://github.com/containers/youki/pull/354\n- part of PR 340 adding test_bind_dev,test_mknod_dev and test_create_devices by @tommady in https://github.com/containers/youki/pull/362\n- use the console for code blocks. by @utam0k in https://github.com/containers/youki/pull/368\n- Check libseccomp is available at correct version on build by @tsturzl in https://github.com/containers/youki/pull/367\n- Use generic for signal argument in container_kill by @rosds in https://github.com/containers/youki/pull/363\n- Combine test_framework and  add README and guide for integration tests by @YJDoc2 in https://github.com/containers/youki/pull/360\n- Update Youki with latest oci-spec-rs by @yihuaf in https://github.com/containers/youki/pull/364\n- remove a unnecessary clone method. by @utam0k in https://github.com/containers/youki/pull/370\n- part of PR 340 adding test_mount_to_container and separate rootfs file by @tommady in https://github.com/containers/youki/pull/365\n- Restructure the channel code once again by @yihuaf in https://github.com/containers/youki/pull/372\n- organize the process around the namespace in init by @utam0k in https://github.com/containers/youki/pull/371\n- Add integration tests validation workflow by @YJDoc2 in https://github.com/containers/youki/pull/375\n- part of PR 340 adding test_make_parent_mount_private by @tommady in https://github.com/containers/youki/pull/374\n- Support systemd named hierarchy and emulate cgroup namespaces for v1 control cgroups by @Furisto in https://github.com/containers/youki/pull/373\n- Improve integration test readme by @Furisto in https://github.com/containers/youki/pull/377\n- 279 increate the code coverage of src container by @tommady in https://github.com/containers/youki/pull/376\n- Support cgroup v2 mounts by @Furisto in https://github.com/containers/youki/pull/378\n- Add pidfile test by @YJDoc2 in https://github.com/containers/youki/pull/379\n- remove a GitHub commit activeity. by @utam0k in https://github.com/containers/youki/pull/383\n- Fix multi mapping for rootless containers by @Furisto in https://github.com/containers/youki/pull/381\n- Update procfs by @Furisto in https://github.com/containers/youki/pull/387\n- Fix path issues by @Furisto in https://github.com/containers/youki/pull/386\n- fix running unit tests multiple times will cause a rare failed by @tommady in https://github.com/containers/youki/pull/380\n- Refactor process and channel code by @yihuaf in https://github.com/containers/youki/pull/388\n- remove a unnecessary calls to clone() by limiting the lifetime. by @utam0k in https://github.com/containers/youki/pull/390\n- implement seccomp notify by @yihuaf in https://github.com/containers/youki/pull/384\n- turning the sequnce diagram. by @utam0k in https://github.com/containers/youki/pull/394\n- add a unit test for mounting cgroup v1 by @utam0k in https://github.com/containers/youki/pull/392\n- Add readme for rootless by @chenyukang in https://github.com/containers/youki/pull/395\n- Cgroup v1 pid integration tests by @Furisto in https://github.com/containers/youki/pull/391\n- small improvement by @utam0k in https://github.com/containers/youki/pull/399\n- Add ns_itype test by @YJDoc2 in https://github.com/containers/youki/pull/389\n- add a unit test for fork. by @utam0k in https://github.com/containers/youki/pull/401\n- add a unit test for the failed case of fork. by @utam0k in https://github.com/containers/youki/pull/402\n- Make youki a library crate by @Furisto in https://github.com/containers/youki/pull/403\n- [Trivial] minor fixes by @yihuaf in https://github.com/containers/youki/pull/406\n- increate the code coverage of src process part1 by @tommady in https://github.com/containers/youki/pull/397\n- add a config about the dependabot. by @utam0k in https://github.com/containers/youki/pull/407\n- Bump instant from 0.1.10 to 0.1.12 by @dependabot in https://github.com/containers/youki/pull/408\n- Bump syn from 1.0.76 to 1.0.80 by @dependabot in https://github.com/containers/youki/pull/409\n- Bump smallvec from 1.6.1 to 1.7.0 by @dependabot in https://github.com/containers/youki/pull/412\n- Bump cstr-argument from 0.1.1 to 0.1.2 by @dependabot in https://github.com/containers/youki/pull/414\n- Bump anyhow from 1.0.43 to 1.0.44 by @dependabot in https://github.com/containers/youki/pull/417\n- Bump dbus from 0.9.3 to 0.9.5 by @dependabot in https://github.com/containers/youki/pull/410\n- Bump libc from 0.2.101 to 0.2.105 by @dependabot in https://github.com/containers/youki/pull/419\n- Bump serde_json from 1.0.67 to 1.0.68 by @dependabot in https://github.com/containers/youki/pull/418\n- Bump thiserror from 1.0.29 to 1.0.30 by @dependabot in https://github.com/containers/youki/pull/421\n- Bump unicode-width from 0.1.8 to 0.1.9 by @dependabot in https://github.com/containers/youki/pull/420\n- Bump cc from 1.0.70 to 1.0.71 by @dependabot in https://github.com/containers/youki/pull/422\n- Bump pkg-config from 0.3.20 to 0.3.21 by @dependabot in https://github.com/containers/youki/pull/413\n- Bump libbpf-sys from 0.4.0-2 to 0.5.0-1 by @dependabot in https://github.com/containers/youki/pull/411\n- Bump nix from 0.22.1 to 0.23.0 by @dependabot in https://github.com/containers/youki/pull/425\n- Bump proc-macro2 from 1.0.29 to 1.0.30 by @dependabot in https://github.com/containers/youki/pull/426\n- Bump systemd from 0.8.2 to 0.9.0 by @dependabot in https://github.com/containers/youki/pull/429\n- Bump slab from 0.4.4 to 0.4.5 by @dependabot in https://github.com/containers/youki/pull/427\n- Bump mio from 0.7.13 to 0.7.14 by @dependabot in https://github.com/containers/youki/pull/424\n- Bump errno-dragonfly from 0.1.1 to 0.1.2 by @dependabot in https://github.com/containers/youki/pull/433\n- Bump ppv-lite86 from 0.2.14 to 0.2.15 by @dependabot in https://github.com/containers/youki/pull/432\n- Bump quote from 1.0.9 to 1.0.10 by @dependabot in https://github.com/containers/youki/pull/430\n- Bump pkg-config from 0.3.21 to 0.3.22 by @dependabot in https://github.com/containers/youki/pull/428\n- Bump flate2 from 1.0.21 to 1.0.22 by @dependabot in https://github.com/containers/youki/pull/431\n- implemented seccomp notify integration tests by @yihuaf in https://github.com/containers/youki/pull/435\n- make the table of features in README more accurate. by @utam0k in https://github.com/containers/youki/pull/434\n- Bump proc-macro2 from 1.0.30 to 1.0.32 by @dependabot in https://github.com/containers/youki/pull/437\n- Update caps and clap by @YJDoc2 in https://github.com/containers/youki/pull/438\n- Bump errno from 0.2.7 to 0.2.8 by @dependabot in https://github.com/containers/youki/pull/439\n- Bump nix from 0.22.0 to 0.23.0 by @dependabot in https://github.com/containers/youki/pull/440\n- add support for missing executable file. by @utam0k in https://github.com/containers/youki/pull/441\n- increate the code coverage of src process part2 by @tommady in https://github.com/containers/youki/pull/436\n- refactoring the syscall test by @tommady in https://github.com/containers/youki/pull/445\n- Bump libc from 0.2.105 to 0.2.106 by @dependabot in https://github.com/containers/youki/pull/446\n- Implement json log format by @yihuaf in https://github.com/containers/youki/pull/448\n- Bump libbpf-sys from 0.5.0-1 to 0.5.0-2 by @dependabot in https://github.com/containers/youki/pull/449\n- Bump anyhow from 1.0.44 to 1.0.45 by @dependabot in https://github.com/containers/youki/pull/450\n- adding test_sync_seccomp for process/container_main_process by @tommady in https://github.com/containers/youki/pull/452\n- Support resource control via systemd by @Furisto in https://github.com/containers/youki/pull/451\n- Move to 2021 by @chenyukang in https://github.com/containers/youki/pull/405\n- clearly state the feedback address in the README. by @utam0k in https://github.com/containers/youki/pull/456\n- Integration test linux cgroups cpus by @tsturzl in https://github.com/containers/youki/pull/462\n- Bump serde_json from 1.0.68 to 1.0.69 by @dependabot in https://github.com/containers/youki/pull/459\n- Bump libc from 0.2.106 to 0.2.107 by @dependabot in https://github.com/containers/youki/pull/460\n- `cgroup` should not be capitalized. by @utam0k in https://github.com/containers/youki/pull/463\n- Bump cc from 1.0.71 to 1.0.72 by @dependabot in https://github.com/containers/youki/pull/466\n- add a ci for the first release. by @utam0k in https://github.com/containers/youki/pull/458\n- enable default error code for seccomp by @yihuaf in https://github.com/containers/youki/pull/470\n- cgroups v1 memory integration test by @tsturzl in https://github.com/containers/youki/pull/473\n- Fix test_make_parent_mount_private by @tsturzl in https://github.com/containers/youki/pull/472\n- Bump mio from 0.7.14 to 0.8.0 by @dependabot in https://github.com/containers/youki/pull/477\n- Bump serde_json from 1.0.69 to 1.0.70 by @dependabot in https://github.com/containers/youki/pull/476\n- style: adjusting the position of parameters and flag declarations by @unknowndevQwQ in https://github.com/containers/youki/pull/474\n- adding benchmark execution time github action by @tommady in https://github.com/containers/youki/pull/478\n- Add debug flag by @unknowndevQwQ in https://github.com/containers/youki/pull/465\n- Bump serde_json from 1.0.70 to 1.0.71 by @dependabot in https://github.com/containers/youki/pull/480\n- youki original config by @utam0k in https://github.com/containers/youki/pull/447\n- Systemd support for memory and unified restrictions by @Furisto in https://github.com/containers/youki/pull/479\n- use a command instead of label to run benchmark. by @utam0k in https://github.com/containers/youki/pull/483\n- Bump anyhow from 1.0.45 to 1.0.47 by @dependabot in https://github.com/containers/youki/pull/485\n- Bump libc from 0.2.107 to 0.2.108 by @dependabot in https://github.com/containers/youki/pull/484\n- Bump anyhow from 1.0.47 to 1.0.48 by @dependabot in https://github.com/containers/youki/pull/486\n- Create a subdirectory under XDG_RUNTIME_DIR by @dgibson in https://github.com/containers/youki/pull/488\n- Bump futures-io from 0.3.17 to 0.3.18 by @dependabot in https://github.com/containers/youki/pull/489\n- Bump futures-core from 0.3.17 to 0.3.18 by @dependabot in https://github.com/containers/youki/pull/490\n- Bump futures-channel from 0.3.17 to 0.3.18 by @dependabot in https://github.com/containers/youki/pull/493\n- Bump crc32fast from 1.2.1 to 1.2.2 by @dependabot in https://github.com/containers/youki/pull/491\n- Bump futures from 0.3.17 to 0.3.18 by @dependabot in https://github.com/containers/youki/pull/495\n- Bump futures-task from 0.3.17 to 0.3.18 by @dependabot in https://github.com/containers/youki/pull/492\n- Use /tmp/youki-<uid> rather than /tmp/youki/<uid> in determine_root_path by @dgibson in https://github.com/containers/youki/pull/497\n- Bump syn from 1.0.81 to 1.0.82 by @dependabot in https://github.com/containers/youki/pull/501\n- Bump serde_json from 1.0.71 to 1.0.72 by @dependabot in https://github.com/containers/youki/pull/500\n- make complex loglevel decision easy to understand. by @utam0k in https://github.com/containers/youki/pull/482\n- Support resource restrictions for rootless containers by @Furisto in https://github.com/containers/youki/pull/499\n- Bump procfs from 0.11.0 to 0.11.1 by @dependabot in https://github.com/containers/youki/pull/505\n- Bump ryu from 1.0.5 to 1.0.6 by @dependabot in https://github.com/containers/youki/pull/503\n- Bump anyhow from 1.0.48 to 1.0.50 by @dependabot in https://github.com/containers/youki/pull/502\n- Bump getset from 0.1.1 to 0.1.2 by @dependabot in https://github.com/containers/youki/pull/504\n- Bump anyhow from 1.0.50 to 1.0.51 by @dependabot in https://github.com/containers/youki/pull/507\n- Bump crc32fast from 1.2.2 to 1.3.0 by @dependabot in https://github.com/containers/youki/pull/510\n- Fix log files and remove env_logger by @yihuaf in https://github.com/containers/youki/pull/511\n- Split CLI parsing front end into a separate crate by @dgibson in https://github.com/containers/youki/pull/509\n- Improvements to cgroup support by @Furisto in https://github.com/containers/youki/pull/513\n- ignore integration test crate for code coverage by @tsturzl in https://github.com/containers/youki/pull/517\n- Add shell completion by @theoparis in https://github.com/containers/youki/pull/515\n- Bump memoffset from 0.6.4 to 0.6.5 by @dependabot in https://github.com/containers/youki/pull/522\n- Bump libbpf-sys from 0.5.0-2 to 0.6.0-1 by @dependabot in https://github.com/containers/youki/pull/521\n- Bump libc from 0.2.108 to 0.2.109 by @dependabot in https://github.com/containers/youki/pull/520\n- Bump proc-macro2 from 1.0.32 to 1.0.33 by @dependabot in https://github.com/containers/youki/pull/519\n- Bump pkg-config from 0.3.22 to 0.3.23 by @dependabot in https://github.com/containers/youki/pull/523\n- Integration test: cgroup v1 network tests, fix to memory tests by @tsturzl in https://github.com/containers/youki/pull/516\n- bump rust from 1.56.1 to 1.57.0 by @utam0k in https://github.com/containers/youki/pull/524\n- Bump hermit-abi from 0.1.19 to 0.1.20 by @dependabot in https://github.com/containers/youki/pull/526\n- remove unneede `impl Default` by @utam0k in https://github.com/containers/youki/pull/527\n- Bump ryu from 1.0.6 to 1.0.9 by @dependabot in https://github.com/containers/youki/pull/535\n- Bump libc from 0.2.109 to 0.2.111 by @dependabot in https://github.com/containers/youki/pull/533\n- Bump pkg-config from 0.3.23 to 0.3.24 by @dependabot in https://github.com/containers/youki/pull/532\n- Bump serde_json from 1.0.72 to 1.0.73 by @dependabot in https://github.com/containers/youki/pull/539\n- Update version for runc compatibility for Moby by @jhult in https://github.com/containers/youki/pull/530\n- Implement integration tests for cgroup v2 cpu by @Furisto in https://github.com/containers/youki/pull/528\n- Bump libc from 0.2.111 to 0.2.112 by @dependabot in https://github.com/containers/youki/pull/538\n- Bump procfs from 0.11.1 to 0.12.0 by @dependabot in https://github.com/containers/youki/pull/534\n- Bump proc-macro2 from 1.0.33 to 1.0.34 by @dependabot in https://github.com/containers/youki/pull/542\n- Bump tar from 0.4.37 to 0.4.38 by @dependabot in https://github.com/containers/youki/pull/541\n- Bump once_cell from 1.8.0 to 1.9.0 by @dependabot in https://github.com/containers/youki/pull/540\n- implement the update subcommand(partially) by @knight42 in https://github.com/containers/youki/pull/536\n- add the benchmark result to README. by @utam0k in https://github.com/containers/youki/pull/544\n- Bump serde from 1.0.131 to 1.0.132 by @dependabot in https://github.com/containers/youki/pull/545\n- fix(libcgroup): make cgroup manager be able to set blkio weight by @knight42 in https://github.com/containers/youki/pull/543\n- Add Cgroup V1 block IO integration test by @YJDoc2 in https://github.com/containers/youki/pull/537\n- feat: add --resource option to update subcommand by @knight42 in https://github.com/containers/youki/pull/546\n- Bump futures from 0.3.18 to 0.3.19 by @dependabot in https://github.com/containers/youki/pull/556\n- Bump futures-io from 0.3.18 to 0.3.19 by @dependabot in https://github.com/containers/youki/pull/558\n- Bump nix from 0.23.0 to 0.23.1 by @dependabot in https://github.com/containers/youki/pull/551\n- Bump fastrand from 1.5.0 to 1.6.0 by @dependabot in https://github.com/containers/youki/pull/555\n- Bump num_cpus from 1.13.0 to 1.13.1 by @dependabot in https://github.com/containers/youki/pull/559\n- update README.md about the table of features. by @utam0k in https://github.com/containers/youki/pull/547\n- Bump syn from 1.0.82 to 1.0.83 by @dependabot in https://github.com/containers/youki/pull/561\n- Bump anyhow from 1.0.51 to 1.0.52 by @dependabot in https://github.com/containers/youki/pull/563\n- Log value that is written to cgroup file by @Furisto in https://github.com/containers/youki/pull/562\n- Add Mdbook documentation by @YJDoc2 in https://github.com/containers/youki/pull/560\n- Update mdbook docs and Add doc link in the Readme by @YJDoc2 in https://github.com/containers/youki/pull/565\n- The release tag generally begins with v by @utam0k in https://github.com/containers/youki/pull/564\n"
  },
  {
    "path": "CODE-OF-CONDUCT.md",
    "content": "Move to youki-dev.github.io/youki/community/governance.html#code-of-conduct\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace]\nresolver = \"2\"\nmembers = [\"crates/*\", \"tests/contest/*\", \"tools/*\"]\nexclude = [\"experiment/seccomp\", \"experiment/selinux\"]\n\n[profile.release]\nlto = true\n"
  },
  {
    "path": "Cross.toml",
    "content": "[build]\ndefault-target = \"x86_64-unknown-linux-gnu\"\nenv.passthrough = [\"XDG_RUNTIME_DIR\"]\n\n[target.aarch64-unknown-linux-gnu]\ndockerfile = \"cross/Dockerfile.gnu\"\n\n[target.x86_64-unknown-linux-gnu]\ndockerfile = \"cross/Dockerfile.gnu\"\n\n[target.aarch64-unknown-linux-musl]\ndockerfile = \"cross/Dockerfile.musl\"\n\n[target.x86_64-unknown-linux-musl]\ndockerfile = \"cross/Dockerfile.musl\"\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 [2021] [youki team]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   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": "MigrationGuide.md",
    "content": "# Migration Guide\n\nThis contains information for migrating library versions.\n\n## v0.2.0 -> v0.3.0\n\n### libcgroups\n- Switched from dbus-rs to a native dbus implementation see [#2208](https://github.com/youki-dev/youki/issues/2208) for motivation behind this. This replaces the `dbus` module with `dbus_native` module. However, As this is not in public interface for the crate, the users of this crate should not need any code changes. As this removes the dependency on the `libdbus` system library, you can uninstall it if desired.\n\n## v0.1.0 -> v0.2.0\n\n### libcontainer\n\n- The `Rootless` struct has been re-named as `UserNamespaceConfig` , `RootlessIDMapper` has been re-named to `UserNamespaceIDMapper` , and correspondingly the `RootlessError` has been re-named to `UserNamespaceError` . This is due to the fact that the structure was to be used for containers when a new user namespace is to be created, and that is not strictly only for rootless uses. Accordingly, the fields of various structs has been updated to reflect this change :\n  - rootless (module name) -> user_ns\n  - Rootless.rootless_id_mapper -> UserNamespaceConfig.id_mapper\n  - LibcontainerError::Rootless -> LibcontainerError::UserNamespace\n  - ContainerBuilderImpl.rootless -> ContainerBuilderImpl.user_ns_config\n  - ContainerArgs.rootless -> ContainerArgs.user_ns_config\n\n- Executor now contains 2 methods for implementation. We introduce a `validate` step in addition to execute. The `validate` should validate the input OCI spec. The step runs after all the namespaces are entered and rootfs is pivoted.\n\n- Executor is now composible instead of an array of executor. To implement multiple executor, create a new executor that runs all the executor. The users are now in control of how multiple executor are run.\n"
  },
  {
    "path": "README.md",
    "content": "# youki: A container runtime in Rust\n\n[![Discord](https://img.shields.io/discord/849943000770412575.svg?logo=discord)](https://discord.gg/zHnyXKSQFD)\n[![GitHub contributors](https://img.shields.io/github/contributors/youki-dev/youki)](https://github.com/youki-dev/youki/graphs/contributors)\n[![Github CI](https://github.com/youki-dev/youki/actions/workflows/basic.yml/badge.svg?branch=main)](https://github.com/youki-dev/youki/actions)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Fyouki.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Fyouki?ref=badge_shield)\n\n<p align=\"center\">\n  <img src=\"docs/youki.png\" width=\"450\">\n</p>\n\n**youki** is an implementation of the [OCI runtime-spec](https://github.com/opencontainers/runtime-spec) in Rust, similar to [runc](https://github.com/opencontainers/runc).  \nYour ideas are welcome [here](https://github.com/youki-dev/youki/issues/10).\n\n# 🏷️ About the name\n\nyouki is pronounced as /joʊki/ or yoh-key.\nyouki is named after the Japanese word 'youki', which means 'a container'. In Japanese language, youki also means 'cheerful', 'merry', or 'hilarious'.\n\n# 🚀 Quick Start\n\n> [!TIP]\n> You can immediately set up your environment with youki on GitHub Codespaces and try it out.  \n>\n> [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/youki-dev/youki?quickstart=1)\n> ```console\n> just build\n> docker run --runtime youki hello-world\n> sudo podman run --cgroup-manager=cgroupfs --runtime /workspaces/youki/youki hello-world\n> ```\n\n[User Documentation](https://youki-dev.github.io/youki/user/basic_setup.html#quick-install)\n\n# 🎯 Motivation\n\nHere is why we are writing a new container runtime in Rust.\n\n- Rust is one of the best languages to implement the oci-runtime spec. Many very nice container tools are currently written in Go. However, the container runtime requires the use of system calls, which requires a bit of special handling when implemented in Go. This is tricky (e.g. _namespaces(7)_, _fork(2)_); with Rust too, but it's not that tricky. And, unlike in C, Rust provides the benefit of memory safety. While Rust is not yet a major player in the container field, it has the potential to contribute a lot: something this project attempts to exemplify.\n- youki has the potential to be faster and use less memory than runc, and therefore work in environments with tight memory usage requirements. Here is a simple benchmark of a container from creation to deletion.\n  |  Runtime | Time (mean ± σ) | \tRange (min … max) | vs youki(mean) | Version | \n  | -------- | -------- | -------- | -------- | -------- |\n  | youki     | 111.5 ms ± 11.6 ms  | 84.0 ms ± 142.5 ms   | 100% | 0.3.3 |\n  | runc     | 224.6 ms ± 12.0 ms  | 190.5 ms ± 255.4 ms   | 200% | 1.1.7 |\n  | crun     | 47.3 ms ± 2.8 ms  | 42.4 ms ± 56.2 ms   | 42% | 1.15 |\n    <details>\n  <summary>Details about the benchmark</summary>\n\n  - A command used for the benchmark\n\n    ```bash\n    hyperfine --prepare 'sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' --warmup 10 --min-runs 100 'sudo ./youki create -b tutorial a && sudo ./youki start a && sudo ./youki delete -f a'\n    ```\n\n  - Environment\n\n    ```console\n    $ ./youki info\n    Version           0.3.3\n    Commit            4f3c8307\n    Kernel-Release    6.5.0-35-generic\n    Kernel-Version    #35~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue May  7 09:00:52 UTC 2\n    Architecture      x86_64\n    Operating System  Ubuntu 22.04.4 LTS\n    Cores             16\n    Total Memory      63870\n    Cgroup setup      unified\n    Cgroup mounts\n    Namespaces        enabled\n      mount           enabled\n      uts             enabled\n      ipc             enabled\n      user            enabled\n      pid             enabled\n      network         enabled\n      cgroup          enabled\n    Capabilities\n    CAP_BPF           available\n    CAP_PERFMON       available\n    CAP_CHECKPOINT_RESTORE available\n    ```\n\n  </details>\n\n- I have fun implementing this. In fact, this may be the most important.\n\n# 📍 Status of youki\n\n**youki** has aced real-world use cases, including containerd's e2e test, and is now adopted by several production environments. See [Adopters and Use Cases](https://youki-dev.github.io/youki/community/adopters_and_use_cases.html) for public examples.\nWe have [our roadmap](https://github.com/orgs/containers/projects/15).\n\n![youki demo](docs/demo.gif)\n\n# 🔗 Related project\n\n- [youki-dev/oci-spec-rs](https://github.com/youki-dev/oci-spec-rs) - OCI Runtime and Image Spec in Rust\n\n# 🎨 Design and implementation of youki\n\nThe User and Developer Documentation for youki is hosted at [https://youki-dev.github.io/youki/](https://youki-dev.github.io/youki/)\n\n![Architecture](docs/.drawio.svg)\n\n# 🎬 Getting Started\n\nLocal build is only supported on Linux.\nFor other platforms, please use the [Vagrantfile](#setting-up-vagrant) that we have prepared. You can also spin up a fully preconfigured development environment in the cloud with [GitHub Codespaces](https://docs.github.com/en/codespaces/getting-started/quickstart).\n\n## Requires\n\n- Rust(See [here](https://www.rust-lang.org/tools/install)), edition 2024\n- linux kernel ≥ 5.3\n\n## Dependencies\n\nTo install `just`, follow the instruction [here](https://github.com/casey/just#installation).\n\n### Debian, Ubuntu and related distributions\n\n```console\nsudo apt-get install    \\\n      pkg-config        \\\n      libsystemd-dev    \\\n      build-essential   \\\n      libelf-dev        \\\n      libseccomp-dev    \\\n      libclang-dev      \\\n      libssl-dev\n```\n\n### Fedora, CentOS, RHEL and related distributions\n\n```console\nsudo dnf install            \\\n      pkg-config            \\\n      systemd-devel         \\\n      elfutils-libelf-devel \\\n      libseccomp-devel      \\\n      clang-devel           \\\n      openssl-devel\n```\n\n## Build\n\n```bash\ngit clone git@github.com:youki-dev/youki.git\ncd youki\njust youki-dev # or youki-release\n./youki -h # you can get information about youki command\n```\n\n## Tutorial\n\n### Requires\n\n- Docker(See [here](https://docs.docker.com/engine/install))\n\n### Create and run a container\n\nLet's try to run a container that executes `sleep 30` with youki. This tutorial may need root permission.\n\n```bash\ngit clone git@github.com:youki-dev/youki.git\ncd youki\njust youki-dev # or youki-release\n\nmkdir -p tutorial/rootfs\ncd tutorial\n# use docker to export busybox into the rootfs directory\ndocker export $(docker create busybox) | tar -C rootfs -xvf -\n```\n\nThen, we need to prepare a configuration file. This file contains metadata and specs for a container, such as the process to run, environment variables to inject, sandboxing features to use, etc.\n\n```bash\n../youki spec  # will generate a spec file named config.json\n```\n\nWe can edit the `config.json` to add customized behaviors for container. Here, we modify the `process` field to run `sleep 30`.\n\n```json\n  \"process\": {\n    ...\n    \"args\": [\n      \"sleep\", \"30\"\n    ],\n\n  ...\n  }\n```\n\nThen we can explore the lifecycle of a container:\n\n```bash\ncd ..                                                # go back to the repository root\nsudo ./youki create -b tutorial tutorial_container   # create a container with name `tutorial_container`\nsudo ./youki state tutorial_container                # you can see the state the container is `created`\nsudo ./youki start tutorial_container                # start the container\nsudo ./youki list                                    # will show the list of containers, the container is `running`\nsudo ./youki delete tutorial_container               # delete the container\n```\n\nChange the command to be executed in `config.json` and try something other than `sleep 30`.\n\n### Rootless container\n\n`youki` provides the ability to run containers as non-root user([rootless mode](https://docs.docker.com/engine/security/rootless/)). To run a container in rootless mode, we need to add some extra options in `config.json`, other steps are same with above:\n\n```bash\nmkdir -p tutorial/rootfs\ncd tutorial\n# use docker to export busybox into the rootfs directory\ndocker export $(docker create busybox) | tar -C rootfs -xvf -\n\n../youki spec --rootless          # will generate a spec file named config.json with rootless mode\n## Modify the `args` field as you like\n\n../youki run rootless-container   # will create and run a container with rootless mode\n```\n\n## Usage\n\nStart the docker daemon.\n\n```bash\ndockerd --experimental --add-runtime=\"youki=$(pwd)/youki\"\n```\n\nIf you get an error like the below, that means your normal Docker daemon is running, and it needs to be stopped. Do that with your init system (i.e., with systemd, run `sudo systemctl stop docker`, as root if necessary).\n\n```console\nfailed to start daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid\n```\n\nNow repeat the command, which should start the docker daemon.\n\nYou can use youki in a different terminal to start the container.\n\n```bash\ndocker run -it --rm --runtime youki busybox\n```\n\nAfterwards, you can close the docker daemon process in other the other terminal. To restart normal docker daemon (if you had stopped it before), run:\n\n```bash\nsudo systemctl start docker\n```\n\n### Integration Tests\n\nGo and node-tap are required to run integration tests. See the [opencontainers/runtime-tools](https://github.com/opencontainers/runtime-tools) README for details.\n\n```bash\ngit submodule update --init --recursive\njust test-oci\n```\n\n### Setting up Vagrant\n\nYou can try youki on platforms other than Linux by using the Vagrantfile we have prepared. We have prepared two environments for vagrant, namely rootless mode and rootful mode\n\n```bash\ngit clone git@github.com:youki-dev/youki.git\ncd youki\n\n# If you want to develop in rootless mode, and this is the default mode\nvagrant up default\nvagrant ssh default\n\n# or if you want to develop in rootful mode\nvagrant up rootful\nvagrant ssh rootful\n\n# in virtual machine\ncd youki\njust youki-dev # or youki-release\n```\n\n# 👥 Community and Contributing\n\nPlease refer to [our community page](https://youki-dev.github.io/youki/community/introduction.html).\n\nThanks to all the people who already contributed!\n\n<a href=\"https://github.com/youki-dev/youki/graphs/contributors\">\n  <img src=\"https://contributors-img.web.app/image?repo=youki-dev/youki\" />\n</a>\n\n- - -\n**We are a [Cloud Native Computing Foundation](https://cncf.io/) sandbox project.**\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://www.cncf.io/wp-content/uploads/2022/07/cncf-white-logo.svg\">\n  <img src=\"https://www.cncf.io/wp-content/uploads/2022/07/cncf-color-bg.svg\" width=300 />\n</picture>\n\nThe Linux Foundation® (TLF) has registered trademarks and uses trademarks. For a list of TLF trademarks, see [Trademark Usage](https://www.linuxfoundation.org/legal/trademark-usage).\n\n\n## License\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Fyouki.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Fyouki?ref=badge_large)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "## Security and Disclosure Information Policy for the Youki Project\n\nThe Youki Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects.\n"
  },
  {
    "path": "Vagrantfile",
    "content": "# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\nCONTAINERD_2_YOUKI_GO_VERSION = \"1.20.12\"\nCONTAINERD_2_YOUKI_CONTAINERD_VERSION = \"1.7.11\"\n\nPODMAN_E2E_GO_VERSION = \"1.22.0\"\nPODMAN_E2E_PODMAN_BRANCH = \"main\"\nPODMAN_E2E_SKOPEO_VERSION = \"1.13.1\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.define \"default\" do |m|\n    m.vm.box = \"bento/fedora-42\"\n    m.vm.synced_folder '.', '/vagrant', disabled: true\n\n    m.vm.provider \"virtualbox\" do |v|\n      v.memory = 2048\n      v.cpus = 2\n    end\n    m.vm.provision \"shell\", inline: <<-SHELL\n      set -e -u -o pipefail\n      yum update -y\n      yum install -y git gcc docker wget pkg-config systemd-devel dbus-devel elfutils-libelf-devel libseccomp-devel clang-devel openssl-devel just\n      grubby --update-kernel=ALL --args=\"systemd.unified_cgroup_hierarchy=0\"\n      service docker start\n    SHELL\n\n    m.vm.provision \"shell\", privileged: false, inline: <<-SHELL\n      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n      echo \"export PATH=$PATH:$HOME/.cargo/bin\" >> ~/.bashrc\n\n      git clone https://github.com/youki-dev/youki\n    SHELL\n  end\n\n  config.vm.define \"rootful\" do |m|\n    m.vm.box = \"bento/fedora-42\"\n    m.vm.synced_folder '.', '/vagrant', disabled: true\n\n    m.vm.provider \"virtualbox\" do |v|\n      v.memory = 2048\n      v.cpus = 2\n    end\n    m.vm.provision \"shell\", path: \"./hack/set_root_login_for_vagrant.sh\"\n    m.vm.provision \"shell\", inline: <<-SHELL\n      set -e -u -o pipefail\n      yum update -y\n      yum install -y git gcc docker wget pkg-config systemd-devel dbus-devel elfutils-libelf-devel libseccomp-devel clang-devel openssl-devel just\n      grubby --update-kernel=ALL --args=\"systemd.unified_cgroup_hierarchy=0\"\n      service docker start\n      curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n      echo \"export PATH=$PATH:$HOME/.cargo/bin\" >> ~/.bashrc\n    SHELL\n    m.ssh.username = 'root'\n    m.ssh.insert_key = 'true'\n  end\n\n  config.vm.define \"containerd2youki\" do |m|\n    m.vm.box = \"bento/ubuntu-24.04\"\n    m.vm.synced_folder '.', '/vagrant/youki', disabled: false\n\n    m.vm.provider \"virtualbox\" do |v|\n      v.memory = 4096\n      v.cpus = 4\n    end\n\n    m.vm.provision \"bootstrap\", type: \"shell\" do |s|\n      s.inline = <<-SHELL\n        set -e -u -o pipefail\n        export DEBIAN_FRONTEND=noninteractive\n        apt-get update && apt-get install -y \\\n          make \\\n          pkg-config         \\\n          libsystemd-dev     \\\n          libdbus-glib-1-dev \\\n          build-essential    \\\n          libelf-dev \\\n          libseccomp-dev \\\n          libbtrfs-dev \\\n          btrfs-progs\n        \n        ARCH=$(uname -m)\n        case \"$ARCH\" in\n          x86_64)\n            GOARCH=\"amd64\"\n            ;;\n          aarch64)\n            GOARCH=\"arm64\"\n            ;;\n          *)\n            echo \"Unsupported architecture: $ARCH\"\n            exit 1\n            ;;\n        esac\n        \n        wget --quiet https://go.dev/dl/go#{CONTAINERD_2_YOUKI_GO_VERSION}.linux-${GOARCH}.tar.gz -O /tmp/go#{CONTAINERD_2_YOUKI_GO_VERSION}.linux-${GOARCH}.tar.gz\n        rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go#{CONTAINERD_2_YOUKI_GO_VERSION}.linux-${GOARCH}.tar.gz\n        echo \"export PATH=$PATH:/usr/local/go/bin\" >> ~/.bashrc\n        echo \"export GOPATH=$HOME/go\" >> ~/.bashrc\n        export PATH=$PATH:$HOME/.cargo/bin:/usr/local/go/bin\n        export GOPATH=$HOME/go\n\n        git clone https://github.com/containerd/containerd \\\n          /root/go/src/github.com/containerd/containerd -b v#{CONTAINERD_2_YOUKI_CONTAINERD_VERSION}\n\n        cd /root/go/src/github.com/containerd/containerd\n        make\n        make binaries\n        make install\n        ./script/setup/install-cni\n        ./script/setup/install-critools\n        rm -rf /bin/runc /sbin/runc /usr/sbin/runc /usr/bin/runc\n        ln -s /vagrant/youki/youki /usr/bin/runc\n      SHELL\n    end\n\n    m.vm.provision \"test\", type: \"shell\", run: \"never\" do |s|\n        s.inline = <<-SHELL\n          export RUNC_FLAVOR=crun\n          cd /root/go/src/github.com/containerd/containerd/\n          export PATH=$PATH:$HOME/.cargo/bin:/usr/local/go/bin\n          make TEST_RUNTIME=io.containerd.runc.v2 TESTFLAGS=\"-timeout 120m\" integration | tee result.txt\n          grep \"FAIL: \" result.txt || true\n        SHELL\n    end\n  end\n\n  config.vm.define \"podmane2e\" do |m|\n    m.vm.box = \"bento/ubuntu-24.04\"\n    m.vm.synced_folder '.', '/vagrant/youki', disabled: false\n\n    m.vm.provider \"virtualbox\" do |v|\n      v.memory = 8192\n      v.cpus = 8\n    end\n\n    m.vm.provision \"bootstrap\", type: \"shell\" do |s|\n      s.inline = <<-SHELL\n        set -e -u -o pipefail\n        export DEBIAN_FRONTEND=noninteractive\n        apt-get update && apt-get install -y \\\n          make \\\n          pkg-config         \\\n          libsystemd-dev     \\\n          libdbus-glib-1-dev \\\n          build-essential    \\\n          libelf-dev \\\n          libseccomp-dev \\\n          libbtrfs-dev \\\n          btrfs-progs \\\n          libgpgme-dev \\\n          libassuan-dev \\\n          libdevmapper-dev \\\n          bats \\\n          socat \\\n          jq \\\n          conmon \\\n          protobuf-compiler\n\n        ARCH=$(uname -m)\n        case \"$ARCH\" in\n          x86_64)\n            GOARCH=\"amd64\"\n            ;;\n          aarch64)\n            GOARCH=\"arm64\"\n            ;;\n          *)\n            echo \"Unsupported architecture: $ARCH\"\n            exit 1\n            ;;\n        esac\n\n        wget --quiet https://go.dev/dl/go#{PODMAN_E2E_GO_VERSION}.linux-${GOARCH}.tar.gz -O /tmp/go#{PODMAN_E2E_GO_VERSION}.linux-${GOARCH}.tar.gz\n        rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go#{PODMAN_E2E_GO_VERSION}.linux-${GOARCH}.tar.gz\n        echo \"export PATH=$PATH:/usr/local/go/bin\" >> ~/.bashrc\n        echo \"export GOPATH=$HOME/go\" >> ~/.bashrc\n        export PATH=$PATH:$HOME/.cargo/bin:/usr/local/go/bin\n        export GOPATH=$HOME/go\n\n        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n        source \"$HOME/.cargo/env\"\n        cargo install netavark aardvark-dns\n        mkdir -p /usr/local/lib/podman\n        sudo cp $(which netavark) /usr/local/lib/podman/\n        sudo cp $(which netavark)-dhcp-proxy-client /usr/local/lib/podman/\n        sudo cp $(which aardvark-dns) /usr/local/lib/podman/\n\n        mkdir /tmp/skopeo \n        curl -fsSL \"https://github.com/containers/skopeo/archive/v#{PODMAN_E2E_SKOPEO_VERSION}.tar.gz\" | tar -xzf - -C /tmp/skopeo --strip-components=1\n        cd /tmp/skopeo && DISABLE_DOCS=1 make\n        sudo mkdir /etc/containers && sudo cp /tmp/skopeo/bin/skopeo /usr/local/bin/skopeo && sudo cp /tmp/skopeo/default-policy.json /etc/containers/policy.json\n\n        git clone https://github.com/containers/podman /vagrant/podman -b #{PODMAN_E2E_PODMAN_BRANCH}\n        \n        cd /vagrant/podman && make binaries install.tools\n\n        rm -rf /bin/runc /sbin/runc /usr/sbin/runc /usr/bin/runc\n\n        cp /vagrant/youki/youki /usr/bin/runc\n      SHELL\n    end\n  end\nend\n"
  },
  {
    "path": "crates/.gitignore",
    "content": "*_bin"
  },
  {
    "path": "crates/libcgroups/.gitignore",
    "content": "debug/\ntarget/\n**/*.rs.bk\n\n"
  },
  {
    "path": "crates/libcgroups/Cargo.toml",
    "content": "[package]\nname = \"libcgroups\"\nversion = \"0.6.0\" # MARK: Version\ndescription = \"Library for cgroup\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nrust-version = \"1.85.0\"\nautoexamples = true\nkeywords = [\"youki\", \"container\", \"cgroups\"]\n\n[features]\ndefault = [\"v1\", \"v2\", \"systemd\"]\nv1 = []\nv2 = []\nsystemd = [\"v2\", \"nix/socket\", \"nix/uio\"]\ncgroupsv2_devices = [\"rbpf\", \"libbpf-sys\", \"errno\", \"libc\", \"nix/dir\"]\n\n[dependencies]\nnix = { version = \"0.29.0\", features = [\"signal\", \"user\", \"fs\"] }\nprocfs = \"0.17.0\"\npathrs = \"0.2.4\"\noci-spec = { version = \"~0.9.0\", features = [\"runtime\"] }\nfixedbitset = \"0.5.7\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nrbpf = { version = \"0.4.1\", optional = true }\nlibbpf-sys = { version = \"1.6.4\", optional = true }\nerrno = { version = \"0.3.14\", optional = true }\nlibc = { version = \"0.2.180\", optional = true }\nthiserror = \"2.0.18\"\ntracing = { version = \"0.1.44\", features = [\"attributes\"] }\n\n[dev-dependencies]\nanyhow = \"1.0\"\noci-spec = { version = \"~0.9.0\", features = [\"proptests\", \"runtime\"] }\nquickcheck = \"1\"\nmockall = { version = \"0.14.0\", features = [] }\nclap = \"4.5.13\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\nenv_logger = \"0.11\"\nserial_test = \"3.4.0\"\ntempfile = \"3\"\n"
  },
  {
    "path": "crates/libcgroups/README.md",
    "content": "# libcgroups\n"
  },
  {
    "path": "crates/libcgroups/examples/bpf.rs",
    "content": "use anyhow::Result;\n\n#[cfg(feature = \"cgroupsv2_devices\")]\nmod bpf {\n    use std::os::unix::io::AsRawFd;\n    use std::path::Path;\n\n    use anyhow::{Result, bail};\n    use clap::{Arg, Command};\n    use libcgroups::v2::devices::{bpf, emulator, program};\n    use nix::fcntl::OFlag;\n    use nix::sys::stat::Mode;\n    use oci_spec::runtime::LinuxDeviceCgroup;\n\n    const LICENSE: &str = \"Apache\";\n    fn cli() -> Command {\n        clap::Command::new(\"bpf\")\n            .version(\"0.1\")\n            .about(\"tools to test BPF program for cgroups v2 devices\")\n            .arg(Arg::new(\"cgroup_dir\").short('c').value_name(\"CGROUP_DIR\"))\n            .subcommand(\n                Command::new(\"query\").about(\"query list of BPF programs attached to cgroup dir\"),\n            )\n            .subcommand(\n                Command::new(\"detach\")\n                    .about(\"detach BPF program by id\")\n                    .arg(\n                        Arg::new(\"id\")\n                            .value_name(\"PROG_ID\")\n                            .required(true)\n                            .help(\"ID of BPF program returned by query command\"),\n                    ),\n            )\n            .subcommand(\n                Command::new(\"attach\")\n                    .about(\"compile rules to BPF and attach to cgroup dir\")\n                    .arg(\n                        Arg::new(\"input_file\")\n                            .value_name(\"INPUT_FILE\")\n                            .required(true)\n                            .help(\"File contains Vec<LinuxDeviceCgroup> in json format\"),\n                    ),\n            )\n    }\n\n    fn parse_cgroupv1_device_rules<P: AsRef<Path>>(path: P) -> Result<Vec<LinuxDeviceCgroup>> {\n        let content = std::fs::read_to_string(path)?;\n        let devices = serde_json::from_str(&content)?;\n        Ok(devices)\n    }\n\n    pub fn run() -> Result<()> {\n        let matches = cli().get_matches();\n        let cgroup_dir = matches.get_one::<String>(\"cgroup_dir\").unwrap();\n        let cgroup_fd = nix::dir::Dir::open(\n            cgroup_dir.as_str(),\n            OFlag::O_RDONLY | OFlag::O_DIRECTORY,\n            Mode::from_bits(0o600).unwrap(),\n        )?;\n        match matches.subcommand() {\n            Some((\"query\", _)) => {\n                let progs = bpf::prog::query(cgroup_fd.as_raw_fd())?;\n                for prog in &progs {\n                    println!(\"prog: id={}, fd={}\", prog.id, prog.fd);\n                }\n            }\n            Some((\"detach\", submatch)) => {\n                let prog_id = submatch.get_one::<String>(\"id\").unwrap().parse::<u32>()?;\n                let progs = bpf::prog::query(cgroup_fd.as_raw_fd())?;\n                let prog = progs.iter().find(|v| v.id == prog_id);\n                if prog.is_none() {\n                    bail!(\"can't get prog fd by prog id\");\n                }\n\n                bpf::prog::detach2(prog.unwrap().fd, cgroup_fd.as_raw_fd())?;\n                println!(\"detach ok\");\n            }\n            Some((\"attach\", submatch)) => {\n                let input_file = submatch.get_one::<String>(\"input_file\").unwrap();\n                let rules = parse_cgroupv1_device_rules(input_file)?;\n                let mut emulator = emulator::Emulator::with_default_allow(false);\n                emulator.add_rules(&rules);\n                let prog = program::Program::from_rules(&emulator.rules, emulator.default_allow)?;\n                let prog_fd = bpf::prog::load(LICENSE, prog.bytecodes())?;\n                bpf::prog::attach(prog_fd, cgroup_fd.as_raw_fd())?;\n                println!(\"attach ok\");\n            }\n\n            _ => unreachable!(),\n        };\n        Ok(())\n    }\n}\n\n#[cfg(not(feature = \"cgroupsv2_devices\"))]\nmod bpf {\n    use anyhow::{Result, bail};\n\n    pub fn run() -> Result<()> {\n        if !cfg!(feature = \"cgroupsv2_devices\") {\n            bail!(\"cgroupsv2_devices feature is not enabled\");\n        }\n\n        unreachable!()\n    }\n}\n\nfn main() -> Result<()> {\n    env_logger::init();\n    bpf::run()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/libcgroups/examples/rules.json",
    "content": "[\n    {\n        \"allow\": false,\n        \"access\": \"rwm\"\n    },\n    {\n        \"allow\": true,\n        \"type\": \"c\",\n        \"access\": \"rwm\"\n    },\n    {\n        \"allow\": true,\n        \"type\": \"b\",\n        \"major\": 8,\n        \"access\": \"rm\"\n    }\n]\n"
  },
  {
    "path": "crates/libcgroups/examples/systemd_io.rs",
    "content": "use std::path::PathBuf;\nuse std::process::{Command, Stdio};\n\nuse anyhow::Result;\nuse libcgroups::common::{CgroupConfig, CgroupManager, ControllerOpt, create_cgroup_manager};\nuse nix::unistd::Pid;\nuse oci_spec::runtime::{\n    LinuxBlockIoBuilder, LinuxMemoryBuilder, LinuxResourcesBuilder, LinuxThrottleDeviceBuilder,\n};\nfn main() -> Result<()> {\n    let cfg = CgroupConfig {\n        cgroup_path: PathBuf::from(\"system.slice:youki:test\"),\n        systemd_cgroup: true,\n        container_name: \"test\".to_owned(),\n    };\n    let manager = create_cgroup_manager(cfg)?;\n    let mem_limit = 256 * 1024 * 1024;\n    // Throttle rate in bytes per second (Bps) for the block IO device\n    const THROTTLE_RATE_BPS: u64 = 1000;\n    let rate: u64 = THROTTLE_RATE_BPS;\n    let memory_resource = LinuxMemoryBuilder::default().limit(mem_limit).build()?;\n    let device = LinuxThrottleDeviceBuilder::default()\n        .major(259)\n        .minor(0)\n        .rate(rate)\n        .build()?;\n    let blkio = LinuxBlockIoBuilder::default()\n        .throttle_read_bps_device(vec![device])\n        .build()?;\n    let resources = LinuxResourcesBuilder::default()\n        .memory(memory_resource)\n        .block_io(blkio)\n        .build()?;\n    let opts = ControllerOpt {\n        resources: &resources,\n        disable_oom_killer: false,\n        oom_score_adj: None,\n        freezer_state: None,\n    };\n\n    let pid = Pid::from_raw(std::process::id() as i32);\n    manager.add_task(pid)?;\n    manager.apply(&opts)?;\n\n    println!(\"Cgroup created and properties applied.\");\n    println!(\"Launching interactive shell... (type exit to continue)\");\n    // use 'dd if=/dev/zero of=/tmp/test.bin bs=1M count=1024 oflag=direct' to test the read write speed\n    // or check using 'systemctl --show youki-test.scope'\n    Command::new(\"sh\")\n        .stdin(Stdio::inherit())\n        .stdout(Stdio::inherit())\n        .stderr(Stdio::inherit())\n        .spawn()\n        .expect(\"Failed to spawn shell\")\n        .wait()\n        .expect(\"Shell process failed\");\n\n    println!(\"Shell exited. Removing cgroup...\");\n\n    manager.remove()?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/libcgroups/src/common.rs",
    "content": "use std::fmt::{Debug, Display};\nuse std::fs::{self, File};\nuse std::io::{BufRead, BufReader, Write};\nuse std::path::{Path, PathBuf, StripPrefixError};\nuse std::time::Duration;\n\nuse nix::sys::statfs::{CGROUP2_SUPER_MAGIC, TMPFS_MAGIC, statfs};\nuse nix::unistd::Pid;\nuse oci_spec::runtime::LinuxResources;\n#[cfg(any(feature = \"cgroupsv2_devices\", feature = \"v1\"))]\nuse oci_spec::runtime::{\n    LinuxDevice, LinuxDeviceBuilder, LinuxDeviceCgroup, LinuxDeviceCgroupBuilder, LinuxDeviceType,\n};\n\nuse super::stats::Stats;\nuse super::{systemd, v1, v2};\n\npub const CGROUP_PROCS: &str = \"cgroup.procs\";\npub const DEFAULT_CGROUP_ROOT: &str = \"/sys/fs/cgroup\";\n\n#[cfg(feature = \"systemd\")]\n#[inline]\nfn is_true_root() -> Result<bool, WrappedIoError> {\n    if !nix::unistd::geteuid().is_root() {\n        return Ok(false);\n    }\n    let uid_map_path = \"/proc/self/uid_map\";\n    let content = std::fs::read_to_string(uid_map_path).map_err(|e| WrappedIoError::Read {\n        err: e,\n        path: uid_map_path.into(),\n    })?;\n    Ok(content.contains(\"4294967295\"))\n}\npub trait CgroupManager {\n    type Error;\n\n    /// Adds a task specified by its pid to the cgroup\n    fn add_task(&self, pid: Pid) -> Result<(), Self::Error>;\n\n    /// Applies resource restrictions to the cgroup\n    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error>;\n\n    /// Removes the cgroup\n    fn remove(&self) -> Result<(), Self::Error>;\n\n    /// Sets the freezer cgroup to the specified state\n    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error>;\n\n    /// Retrieve statistics for the cgroup\n    fn stats(&self) -> Result<Stats, Self::Error>;\n\n    /// Gets the PIDs inside the cgroup\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error>;\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum AnyManagerError {\n    #[error(transparent)]\n    Systemd(#[from] systemd::manager::SystemdManagerError),\n    #[error(transparent)]\n    V1(#[from] v1::manager::V1ManagerError),\n    #[error(transparent)]\n    V2(#[from] v2::manager::V2ManagerError),\n}\n\n// systemd is boxed due to size lint https://rust-lang.github.io/rust-clippy/master/index.html#/large_enum_variant\npub enum AnyCgroupManager {\n    Systemd(Box<systemd::manager::Manager>),\n    V1(v1::manager::Manager),\n    V2(v2::manager::Manager),\n}\n\nimpl CgroupManager for AnyCgroupManager {\n    type Error = AnyManagerError;\n\n    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.add_task(pid)?),\n            AnyCgroupManager::V1(m) => Ok(m.add_task(pid)?),\n            AnyCgroupManager::V2(m) => Ok(m.add_task(pid)?),\n        }\n    }\n\n    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.apply(controller_opt)?),\n            AnyCgroupManager::V1(m) => Ok(m.apply(controller_opt)?),\n            AnyCgroupManager::V2(m) => Ok(m.apply(controller_opt)?),\n        }\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.remove()?),\n            AnyCgroupManager::V1(m) => Ok(m.remove()?),\n            AnyCgroupManager::V2(m) => Ok(m.remove()?),\n        }\n    }\n\n    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.freeze(state)?),\n            AnyCgroupManager::V1(m) => Ok(m.freeze(state)?),\n            AnyCgroupManager::V2(m) => Ok(m.freeze(state)?),\n        }\n    }\n\n    fn stats(&self) -> Result<Stats, Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.stats()?),\n            AnyCgroupManager::V1(m) => Ok(m.stats()?),\n            AnyCgroupManager::V2(m) => Ok(m.stats()?),\n        }\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {\n        match self {\n            AnyCgroupManager::Systemd(m) => Ok(m.get_all_pids()?),\n            AnyCgroupManager::V1(m) => Ok(m.get_all_pids()?),\n            AnyCgroupManager::V2(m) => Ok(m.get_all_pids()?),\n        }\n    }\n}\n\n#[derive(Debug)]\npub enum CgroupSetup {\n    Hybrid,\n    Legacy,\n    Unified,\n}\n\nimpl Display for CgroupSetup {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            CgroupSetup::Hybrid => \"hybrid\",\n            CgroupSetup::Legacy => \"legacy\",\n            CgroupSetup::Unified => \"unified\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\n/// FreezerState is given freezer controller\n#[derive(Clone, Copy, Debug, Eq, PartialEq)]\npub enum FreezerState {\n    /// Tasks in cgroup are undefined\n    Undefined,\n    /// Tasks in cgroup are suspended.\n    Frozen,\n    /// Tasks in cgroup are resuming.\n    Thawed,\n}\n\n/// ControllerOpt is given all cgroup controller for applying cgroup configuration.\n#[derive(Clone, Debug)]\npub struct ControllerOpt<'a> {\n    /// Resources contain cgroup information for handling resource constraints for the container.\n    pub resources: &'a LinuxResources,\n    /// Disables the OOM killer for out of memory conditions.\n    pub disable_oom_killer: bool,\n    /// Specify an oom_score_adj for container.\n    pub oom_score_adj: Option<i32>,\n    /// FreezerState is given to freezer controller for suspending process.\n    pub freezer_state: Option<FreezerState>,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum WrappedIoError {\n    #[error(\"failed to open {path}: {err}\")]\n    Open { err: std::io::Error, path: PathBuf },\n    #[error(\"failed to write {data} to {path}: {err}\")]\n    Write {\n        err: std::io::Error,\n        path: PathBuf,\n        data: String,\n    },\n    #[error(\"failed to read {path}: {err}\")]\n    Read { err: std::io::Error, path: PathBuf },\n    #[error(\"failed to create dir {path}: {err}\")]\n    CreateDir { err: std::io::Error, path: PathBuf },\n    #[error(\"at {path}: {err}\")]\n    Other { err: std::io::Error, path: PathBuf },\n}\n\nimpl WrappedIoError {\n    pub fn inner(&self) -> &std::io::Error {\n        match self {\n            WrappedIoError::Open { err, .. } => err,\n            WrappedIoError::Write { err, .. } => err,\n            WrappedIoError::Read { err, .. } => err,\n            WrappedIoError::CreateDir { err, .. } => err,\n            WrappedIoError::Other { err, .. } => err,\n        }\n    }\n}\n\n#[inline]\npub fn write_cgroup_file_str<P: AsRef<Path>>(path: P, data: &str) -> Result<(), WrappedIoError> {\n    let path = path.as_ref();\n\n    fs::OpenOptions::new()\n        .create(false)\n        .write(true)\n        .truncate(false)\n        .open(path)\n        .map_err(|err| WrappedIoError::Open {\n            err,\n            path: path.to_path_buf(),\n        })?\n        .write_all(data.as_bytes())\n        .map_err(|err| WrappedIoError::Write {\n            err,\n            path: path.to_path_buf(),\n            data: data.into(),\n        })?;\n\n    Ok(())\n}\n\n#[inline]\npub fn write_cgroup_file<P: AsRef<Path>, T: ToString>(\n    path: P,\n    data: T,\n) -> Result<(), WrappedIoError> {\n    let path = path.as_ref();\n    let data = data.to_string();\n\n    fs::OpenOptions::new()\n        .create(false)\n        .write(true)\n        .truncate(false)\n        .open(path)\n        .map_err(|err| WrappedIoError::Open {\n            err,\n            path: path.to_path_buf(),\n        })?\n        .write_all(data.as_bytes())\n        .map_err(|err| WrappedIoError::Write {\n            err,\n            path: path.to_path_buf(),\n            data,\n        })?;\n\n    Ok(())\n}\n\n#[inline]\npub fn read_cgroup_file<P: AsRef<Path>>(path: P) -> Result<String, WrappedIoError> {\n    let path = path.as_ref();\n    fs::read_to_string(path).map_err(|err| WrappedIoError::Read {\n        err,\n        path: path.to_path_buf(),\n    })\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum GetCgroupSetupError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"non default cgroup root not supported\")]\n    NonDefault,\n    #[error(\"failed to detect cgroup setup\")]\n    FailedToDetect,\n}\n\n/// Determines the cgroup setup of the system. Systems typically have one of\n/// three setups:\n/// - Unified: Pure cgroup v2 system.\n/// - Legacy: Pure cgroup v1 system.\n/// - Hybrid: Hybrid is basically a cgroup v1 system, except for\n///   an additional unified hierarchy which doesn't have any\n///   controllers attached. Resource control can purely be achieved\n///   through the cgroup v1 hierarchy, not through the cgroup v2 hierarchy.\npub fn get_cgroup_setup_with_root(root_path: &Path) -> Result<CgroupSetup, GetCgroupSetupError> {\n    match root_path.exists() {\n        true => {\n            // If the filesystem is of type cgroup2, the system is in unified mode.\n            // If the filesystem is tmpfs instead the system is either in legacy or\n            // hybrid mode. If a cgroup2 filesystem has been mounted under the \"unified\"\n            // folder we are in hybrid mode, otherwise we are in legacy mode.\n            let stat = statfs(root_path)\n                .map_err(std::io::Error::other)\n                .wrap_other(root_path)?;\n            if stat.filesystem_type() == CGROUP2_SUPER_MAGIC {\n                return Ok(CgroupSetup::Unified);\n            }\n\n            if stat.filesystem_type() == TMPFS_MAGIC {\n                let unified = &Path::new(root_path).join(\"unified\");\n                if Path::new(unified).exists() {\n                    let stat = statfs(unified)\n                        .map_err(std::io::Error::other)\n                        .wrap_other(unified)?;\n                    if stat.filesystem_type() == CGROUP2_SUPER_MAGIC {\n                        return Ok(CgroupSetup::Hybrid);\n                    }\n                }\n\n                return Ok(CgroupSetup::Legacy);\n            }\n        }\n        false => return Err(GetCgroupSetupError::NonDefault),\n    }\n\n    Err(GetCgroupSetupError::FailedToDetect)\n}\n\npub fn get_cgroup_setup() -> Result<CgroupSetup, GetCgroupSetupError> {\n    get_cgroup_setup_with_root(Path::new(DEFAULT_CGROUP_ROOT))\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum CreateCgroupSetupError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"non default cgroup root not supported\")]\n    NonDefault,\n    #[error(\"failed to detect cgroup setup\")]\n    FailedToDetect,\n    #[error(\"v1 error: {0}\")]\n    V1(#[from] v1::manager::V1ManagerError),\n    #[error(\"v2 error: {0}\")]\n    V2(#[from] v2::manager::V2ManagerError),\n    #[error(\"systemd error: {0}\")]\n    Systemd(#[from] systemd::manager::SystemdManagerError),\n}\n\n#[derive(Clone)]\npub struct CgroupConfig {\n    pub cgroup_path: PathBuf,\n    pub systemd_cgroup: bool,\n    pub container_name: String,\n}\n\n// Create any cgroup manager with customize root path. If root_path provided\n// is None, then it defaults to /sys/fs/cgroup.\npub fn create_cgroup_manager_with_root(\n    root_path: Option<&Path>,\n    config: CgroupConfig,\n) -> Result<AnyCgroupManager, CreateCgroupSetupError> {\n    let root = match root_path {\n        Some(p) => p,\n        None => Path::new(DEFAULT_CGROUP_ROOT),\n    };\n\n    let cgroup_setup = get_cgroup_setup_with_root(root).map_err(|err| match err {\n        GetCgroupSetupError::WrappedIo(err) => CreateCgroupSetupError::WrappedIo(err),\n        GetCgroupSetupError::NonDefault => CreateCgroupSetupError::NonDefault,\n        GetCgroupSetupError::FailedToDetect => CreateCgroupSetupError::FailedToDetect,\n    })?;\n    let cgroup_path = config.cgroup_path.as_path();\n\n    match cgroup_setup {\n        CgroupSetup::Legacy | CgroupSetup::Hybrid => {\n            Ok(create_v1_cgroup_manager(cgroup_path)?.any())\n        }\n        CgroupSetup::Unified => {\n            // ref https://github.com/opencontainers/runtime-spec/blob/main/config-linux.md#cgroups-path\n            if cgroup_path.is_absolute() || !config.systemd_cgroup {\n                return Ok(create_v2_cgroup_manager(root, cgroup_path)?.any());\n            }\n            Ok(\n                create_systemd_cgroup_manager(root, cgroup_path, config.container_name.as_str())?\n                    .any(),\n            )\n        }\n    }\n}\n\npub fn create_cgroup_manager(\n    config: CgroupConfig,\n) -> Result<AnyCgroupManager, CreateCgroupSetupError> {\n    create_cgroup_manager_with_root(Some(Path::new(DEFAULT_CGROUP_ROOT)), config)\n}\n\n#[cfg(feature = \"v1\")]\nfn create_v1_cgroup_manager(\n    cgroup_path: &Path,\n) -> Result<v1::manager::Manager, v1::manager::V1ManagerError> {\n    tracing::info!(\"cgroup manager V1 will be used\");\n    v1::manager::Manager::new(cgroup_path)\n}\n\n#[cfg(not(feature = \"v1\"))]\nfn create_v1_cgroup_manager(\n    _cgroup_path: &Path,\n) -> Result<v1::manager::Manager, v1::manager::V1ManagerError> {\n    Err(v1::manager::V1ManagerError::NotEnabled)\n}\n\n#[cfg(feature = \"v2\")]\nfn create_v2_cgroup_manager(\n    root_path: &Path,\n    cgroup_path: &Path,\n) -> Result<v2::manager::Manager, v2::manager::V2ManagerError> {\n    tracing::info!(\"cgroup manager V2 will be used\");\n    v2::manager::Manager::new(root_path.to_path_buf(), cgroup_path.to_owned())\n}\n\n#[cfg(not(feature = \"v2\"))]\nfn create_v2_cgroup_manager(\n    _root_path: &Path,\n    _cgroup_path: &Path,\n) -> Result<v2::manager::Manager, v2::manager::V2ManagerError> {\n    Err(v2::manager::V2ManagerError::NotEnabled)\n}\n\n#[cfg(feature = \"systemd\")]\nfn create_systemd_cgroup_manager(\n    root_path: &Path,\n    cgroup_path: &Path,\n    container_name: &str,\n) -> Result<systemd::manager::Manager, systemd::manager::SystemdManagerError> {\n    use crate::systemd::manager::PROCESS_IN_CGROUP_TIMEOUT_DURATION;\n\n    if !systemd::booted() {\n        panic!(\n            \"systemd cgroup flag passed, but systemd support for managing cgroups is not available\"\n        );\n    }\n\n    let use_system = is_true_root().map_err(systemd::manager::SystemdManagerError::WrappedIo)?;\n\n    tracing::info!(\n        \"systemd cgroup manager with system bus {} will be used\",\n        use_system\n    );\n    systemd::manager::Manager::new(\n        root_path.into(),\n        cgroup_path.to_owned(),\n        container_name.into(),\n        use_system,\n        PROCESS_IN_CGROUP_TIMEOUT_DURATION,\n    )\n}\n\n#[cfg(not(feature = \"systemd\"))]\nfn create_systemd_cgroup_manager(\n    _root_path: &Path,\n    _cgroup_path: &Path,\n    _container_name: &str,\n) -> Result<systemd::manager::Manager, systemd::manager::SystemdManagerError> {\n    Err(systemd::manager::SystemdManagerError::NotEnabled)\n}\n\npub fn get_all_pids(path: &Path) -> Result<Vec<Pid>, WrappedIoError> {\n    tracing::debug!(\"scan pids in folder: {:?}\", path);\n    let mut result = vec![];\n    walk_dir(path, &mut |p| {\n        let file_path = p.join(CGROUP_PROCS);\n        if file_path.exists() {\n            let file = File::open(&file_path).wrap_open(&file_path)?;\n            for line in BufReader::new(file).lines().map_while(Result::ok) {\n                result.push(Pid::from_raw(\n                    line.parse::<i32>()\n                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))\n                        .wrap_other(&file_path)?,\n                ))\n            }\n        }\n        Ok::<(), WrappedIoError>(())\n    })?;\n    Ok(result)\n}\n\nfn walk_dir<F, E>(path: &Path, c: &mut F) -> Result<(), E>\nwhere\n    F: FnMut(&Path) -> Result<(), E>,\n    E: From<WrappedIoError>,\n{\n    c(path)?;\n    for entry in fs::read_dir(path).wrap_read(path)? {\n        let entry = entry.wrap_open(path)?;\n        let path = entry.path();\n\n        if path.is_dir() {\n            walk_dir(&path, c)?;\n        }\n    }\n    Ok(())\n}\n\npub(crate) trait PathBufExt {\n    fn join_safely<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, JoinSafelyError>;\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum JoinSafelyError {\n    #[error(\"failed to strip prefix from {path}: {err}\")]\n    StripPrefix {\n        err: StripPrefixError,\n        path: PathBuf,\n    },\n}\n\nimpl PathBufExt for PathBuf {\n    fn join_safely<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, JoinSafelyError> {\n        let path = path.as_ref();\n        if path.is_relative() {\n            return Ok(self.join(path));\n        }\n\n        let stripped = path\n            .strip_prefix(\"/\")\n            .map_err(|err| JoinSafelyError::StripPrefix {\n                err,\n                path: path.to_path_buf(),\n            })?;\n        Ok(self.join(stripped))\n    }\n}\n\n#[cfg(any(feature = \"cgroupsv2_devices\", feature = \"v1\"))]\npub(crate) fn default_allow_devices() -> Vec<LinuxDeviceCgroup> {\n    vec![\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::C)\n            .access(\"m\")\n            .build()\n            .unwrap(),\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::B)\n            .access(\"m\")\n            .build()\n            .unwrap(),\n        // /dev/console\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::C)\n            .major(5)\n            .minor(1)\n            .access(\"rwm\")\n            .build()\n            .unwrap(),\n        // /dev/pts\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::C)\n            .major(136)\n            .access(\"rwm\")\n            .build()\n            .unwrap(),\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::C)\n            .major(5)\n            .minor(2)\n            .access(\"rwm\")\n            .build()\n            .unwrap(),\n        // tun/tap\n        LinuxDeviceCgroupBuilder::default()\n            .allow(true)\n            .typ(LinuxDeviceType::C)\n            .major(10)\n            .minor(200)\n            .access(\"rwm\")\n            .build()\n            .unwrap(),\n    ]\n}\n\n#[cfg(any(feature = \"cgroupsv2_devices\", feature = \"v1\"))]\npub(crate) fn default_devices() -> Vec<LinuxDevice> {\n    vec![\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/null\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(3)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/zero\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(5)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/full\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(7)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/tty\"))\n            .typ(LinuxDeviceType::C)\n            .major(5)\n            .minor(0)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/urandom\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(9)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/random\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(8)\n            .file_mode(0o066u32)\n            .build()\n            .unwrap(),\n    ]\n}\n\n/// Attempts to delete the path the requested number of times.\npub(crate) fn delete_with_retry<P: AsRef<Path>, L: Into<Option<Duration>>>(\n    path: P,\n    retries: u32,\n    limit_backoff: L,\n) -> Result<(), WrappedIoError> {\n    let mut attempts = 0;\n    let mut delay = Duration::from_millis(10);\n    let path = path.as_ref();\n    let limit = limit_backoff.into().unwrap_or(Duration::MAX);\n\n    while attempts < retries {\n        if fs::remove_dir(path).is_ok() {\n            return Ok(());\n        }\n\n        std::thread::sleep(delay);\n        attempts += 1;\n        delay *= attempts;\n        if delay > limit {\n            delay = limit;\n        }\n    }\n\n    Err(std::io::Error::new(\n        std::io::ErrorKind::TimedOut,\n        \"could not delete\".to_string(),\n    ))\n    .wrap_other(path)?\n}\n\npub(crate) trait WrapIoResult {\n    type Target;\n\n    fn wrap_create_dir<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError>;\n    fn wrap_read<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError>;\n    fn wrap_open<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError>;\n    fn wrap_write<P: Into<PathBuf>, D: Into<String>>(\n        self,\n        path: P,\n        data: D,\n    ) -> Result<Self::Target, WrappedIoError>;\n    fn wrap_other<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError>;\n}\n\nimpl<T> WrapIoResult for Result<T, std::io::Error> {\n    type Target = T;\n\n    fn wrap_create_dir<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError> {\n        self.map_err(|err| WrappedIoError::CreateDir {\n            err,\n            path: path.into(),\n        })\n    }\n\n    fn wrap_read<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError> {\n        self.map_err(|err| WrappedIoError::Read {\n            err,\n            path: path.into(),\n        })\n    }\n\n    fn wrap_open<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError> {\n        self.map_err(|err| WrappedIoError::Open {\n            err,\n            path: path.into(),\n        })\n    }\n\n    fn wrap_write<P: Into<PathBuf>, D: Into<String>>(\n        self,\n        path: P,\n        data: D,\n    ) -> Result<Self::Target, WrappedIoError> {\n        self.map_err(|err| WrappedIoError::Write {\n            err,\n            path: path.into(),\n            data: data.into(),\n        })\n    }\n\n    fn wrap_other<P: Into<PathBuf>>(self, path: P) -> Result<Self::Target, WrappedIoError> {\n        self.map_err(|err| WrappedIoError::Other {\n            err,\n            path: path.into(),\n        })\n    }\n}\n\n#[derive(Debug)]\npub enum EitherError<L, R> {\n    Left(L),\n    Right(R),\n}\n\nimpl<L: Display, R: Display> Display for EitherError<L, R> {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            EitherError::Left(left) => <L as Display>::fmt(left, f),\n            EitherError::Right(right) => <R as Display>::fmt(right, f),\n        }\n    }\n}\n\nimpl<L: Debug + Display, R: Debug + Display> std::error::Error for EitherError<L, R> {}\n\n#[derive(Debug)]\npub struct MustBePowerOfTwo;\n\nimpl Display for MustBePowerOfTwo {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(\"page size must be in the format of 2^(integer)\")\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/lib.rs",
    "content": "//! Control groups provide a way of controlling groups of processes.\n//! Examples: controlling resource limits, execution priority, measuring resource usage,\n//! freezing, checkpointing and restarting groups of processes.\n#[cfg(test)]\n#[macro_use]\nextern crate quickcheck;\n\n#[cfg(test)]\n#[allow(unused_imports)]\n#[macro_use]\nextern crate mockall;\n\nmod test;\n\npub mod common;\npub mod stats;\n#[cfg(feature = \"systemd\")]\npub mod systemd;\n#[cfg(not(feature = \"systemd\"))]\n#[path = \"stub/systemd/mod.rs\"]\npub mod systemd;\npub mod test_manager;\n#[cfg(feature = \"v1\")]\npub mod v1;\n#[cfg(not(feature = \"v1\"))]\n#[path = \"stub/v1/mod.rs\"]\npub mod v1;\n#[cfg(feature = \"v2\")]\npub mod v2;\n#[cfg(not(feature = \"v2\"))]\n#[path = \"stub/v2/mod.rs\"]\npub mod v2;\n"
  },
  {
    "path": "crates/libcgroups/src/stats.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt::Display;\nuse std::fs;\nuse std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse serde::Serialize;\n\nuse super::common;\nuse crate::common::{WrapIoResult, WrappedIoError};\n\npub(crate) trait StatsProvider {\n    type Error;\n    type Stats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error>;\n}\n\n/// Reports the statistics for a cgroup\n#[derive(Debug, Serialize, Default)]\npub struct Stats {\n    /// Cpu statistics for the cgroup\n    pub cpu: CpuStats,\n    /// Pid statistics for the cgroup\n    pub pids: PidStats,\n    /// Hugetlb statistics for the cgroup\n    pub hugetlb: HashMap<String, HugeTlbStats>,\n    /// Blkio statistics for the cgroup\n    pub blkio: BlkioStats,\n    /// Memory statistics for the cgroup\n    pub memory: MemoryStats,\n}\n\n/// Reports the cpu statistics for a cgroup\n#[derive(Debug, Default, Serialize)]\npub struct CpuStats {\n    /// Cpu usage statistics for the cgroup\n    pub usage: CpuUsage,\n    /// Cpu Throttling statistics for the cgroup\n    pub throttling: CpuThrottling,\n    /// Pressure Stall Information\n    pub psi: PSIStats,\n}\n\n/// Reports the cpu usage for a cgroup\n#[derive(Debug, Default, PartialEq, Eq, Serialize)]\npub struct CpuUsage {\n    /// Cpu time consumed by tasks in total\n    pub usage_total: u64,\n    /// Cpu time consumed by tasks in user mode\n    pub usage_user: u64,\n    /// Cpu time consumed by tasks in kernel mode\n    pub usage_kernel: u64,\n    /// Cpu time consumed by tasks itemized per core\n    pub per_core_usage_total: Vec<u64>,\n    /// Cpu time consumed by tasks in user mode itemized per core\n    pub per_core_usage_user: Vec<u64>,\n    /// Cpu time consumed by tasks in kernel mode itemized per core\n    pub per_core_usage_kernel: Vec<u64>,\n}\n\n/// Reports the cpu throttling for a cgroup\n#[derive(Debug, Default, PartialEq, Eq, Serialize)]\npub struct CpuThrottling {\n    /// Number of period intervals (as specified in cpu.cfs_period_us) that have elapsed\n    pub periods: u64,\n    /// Number of period intervals where tasks have been throttled because they exhausted their quota\n    pub throttled_periods: u64,\n    /// Total time duration for which tasks have been throttled\n    pub throttled_time: u64,\n}\n\n/// Reports memory stats for a cgroup\n#[derive(Debug, Default, Serialize)]\npub struct MemoryStats {\n    /// Usage of memory\n    pub memory: MemoryData,\n    /// Usage of memory and swap\n    pub memswap: MemoryData,\n    /// Usage of kernel memory\n    pub kernel: MemoryData,\n    /// Usage of kernel tcp memory\n    pub kernel_tcp: MemoryData,\n    /// Page cache in bytes\n    pub cache: u64,\n    /// Returns true if hierarchical accounting is enabled\n    pub hierarchy: bool,\n    /// Various memory statistics\n    pub stats: HashMap<String, u64>,\n    /// Pressure Stall Information\n    pub psi: PSIStats,\n}\n\n/// Reports memory stats for one type of memory\n#[derive(Debug, Default, PartialEq, Eq, Serialize)]\npub struct MemoryData {\n    /// Usage in bytes\n    pub usage: u64,\n    /// Maximum recorded usage in bytes\n    pub max_usage: u64,\n    /// Number of times memory usage hit limits\n    pub fail_count: u64,\n    /// Memory usage limit\n    pub limit: u64,\n}\n\n/// Reports pid stats for a cgroup\n#[derive(Debug, Default, PartialEq, Eq, Serialize)]\npub struct PidStats {\n    /// Current number of active pids\n    pub current: u64,\n    /// Allowed number of active pids (0 means no limit)\n    pub limit: u64,\n}\n\n/// Reports block io stats for a cgroup\n#[derive(Debug, Default, PartialEq, Serialize)]\npub struct BlkioStats {\n    // Number of bytes transferred to/from a device by the cgroup\n    pub service_bytes: Vec<BlkioDeviceStat>,\n    // Number of I/O operations performed on a device by the cgroup\n    pub serviced: Vec<BlkioDeviceStat>,\n    // Time in milliseconds that the cgroup had access to a device\n    pub time: Vec<BlkioDeviceStat>,\n    // Number of sectors transferred to/from a device by the cgroup\n    pub sectors: Vec<BlkioDeviceStat>,\n    // Total time between request dispatch and request completion\n    pub service_time: Vec<BlkioDeviceStat>,\n    // Total time spend waiting in the scheduler queues for service\n    pub wait_time: Vec<BlkioDeviceStat>,\n    // Number of requests queued for I/O operations\n    pub queued: Vec<BlkioDeviceStat>,\n    // Number of requests merged into requests for I/O operations\n    pub merged: Vec<BlkioDeviceStat>,\n    /// Pressure Stall Information\n    pub psi: PSIStats,\n}\n\n/// Reports single stat value for a specific device\n#[derive(Debug, PartialEq, Eq, Clone, Serialize, PartialOrd, Ord)]\npub struct BlkioDeviceStat {\n    /// Major device number\n    pub major: u64,\n    /// Minor device number\n    pub minor: u64,\n    /// Operation type\n    pub op_type: Option<String>,\n    /// Stat value\n    pub value: u64,\n}\n\nimpl Display for BlkioDeviceStat {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        if let Some(op_type) = &self.op_type {\n            write!(\n                f,\n                \"{}:{} {} {}\",\n                self.major, self.minor, op_type, self.value\n            )\n        } else {\n            write!(f, \"{}:{} {}\", self.major, self.minor, self.value)\n        }\n    }\n}\n\n/// Reports hugetlb stats for a cgroup\n#[derive(Debug, Default, PartialEq, Eq, Serialize)]\npub struct HugeTlbStats {\n    /// Current usage in bytes\n    pub usage: u64,\n    /// Maximum recorded usage in bytes\n    pub max_usage: u64,\n    /// Number of allocation failures due to HugeTlb usage limit\n    pub fail_count: u64,\n}\n\n/// Reports Pressure Stall Information for a cgroup\n#[derive(Debug, Default, PartialEq, Serialize)]\npub struct PSIStats {\n    /// Percentage of walltime that some (one or more) tasks were delayed due to lack of resources\n    pub some: PSIData,\n    /// Percentage of walltime in which all tasks were delayed by lack of resources\n    pub full: PSIData,\n}\n\n#[derive(Debug, Default, PartialEq, Serialize)]\npub struct PSIData {\n    /// Running average over the last 10 seconds\n    pub avg10: f64,\n    /// Running average over the last 60 seconds\n    pub avg60: f64,\n    /// Running average over the last 300 seconds\n    pub avg300: f64,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum SupportedPageSizesError {\n    #[error(\"io error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"failed to parse value {value}: {err}\")]\n    Parse { value: String, err: ParseIntError },\n    #[error(\"failed to determine page size from {dir_name}\")]\n    Failed { dir_name: String },\n}\n\n/// Reports which hugepage sizes are supported by the system\npub fn supported_page_sizes() -> Result<Vec<String>, SupportedPageSizesError> {\n    let mut sizes = Vec::new();\n    for hugetlb_entry in fs::read_dir(\"/sys/kernel/mm/hugepages\")? {\n        let hugetlb_entry = hugetlb_entry?;\n        if !hugetlb_entry.path().is_dir() {\n            continue;\n        }\n\n        let dir_name = hugetlb_entry.file_name();\n        // this name should always be valid utf-8,\n        // so can unwrap without any checks\n        let dir_name = dir_name.to_str().unwrap();\n\n        sizes.push(extract_page_size(dir_name)?);\n    }\n\n    Ok(sizes)\n}\n\nfn extract_page_size(dir_name: &str) -> Result<String, SupportedPageSizesError> {\n    if let Some(size) = dir_name\n        .strip_prefix(\"hugepages-\")\n        .and_then(|name_stripped| name_stripped.strip_suffix(\"kB\"))\n    {\n        let size: u64 = size.parse().map_err(|err| SupportedPageSizesError::Parse {\n            value: size.into(),\n            err,\n        })?;\n\n        let size_moniker = if size >= (1 << 20) {\n            (size >> 20).to_string() + \"GB\"\n        } else if size >= (1 << 10) {\n            (size >> 10).to_string() + \"MB\"\n        } else {\n            size.to_string() + \"KB\"\n        };\n\n        return Ok(size_moniker);\n    }\n\n    Err(SupportedPageSizesError::Failed {\n        dir_name: dir_name.into(),\n    })\n}\n\n/// Parses this string slice into an u64\n/// # Example\n/// ```\n/// use libcgroups::stats::parse_value;\n///\n/// let value = parse_value(\"32\").unwrap();\n/// assert_eq!(value, 32);\n/// ```\npub fn parse_value(value: &str) -> Result<u64, ParseIntError> {\n    value.parse()\n}\n\n/// Parses a single valued file to an u64\n/// # Example\n/// ```no_run\n/// use std::path::Path;\n/// use libcgroups::stats::parse_single_value;\n///\n/// let value = parse_single_value(&Path::new(\"memory.current\")).unwrap();\n/// assert_eq!(value, 32);\n/// ```\npub fn parse_single_value(file_path: &Path) -> Result<u64, WrappedIoError> {\n    let value = common::read_cgroup_file(file_path)?;\n    let value = value.trim();\n    if value == \"max\" {\n        return Ok(u64::MAX);\n    }\n\n    value\n        .parse()\n        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))\n        .wrap_other(file_path)\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum ParseFlatKeyedDataError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"flat keyed data at {path} contains entries that do not conform to 'key value'\")]\n    DoesNotConform { path: PathBuf },\n    #[error(\"failed to parse value {value} from {path}\")]\n    FailedToParse {\n        value: String,\n        path: PathBuf,\n        err: ParseIntError,\n    },\n}\n\n/// Parses a file that is structured according to the flat keyed format\npub(crate) fn parse_flat_keyed_data(\n    file_path: &Path,\n) -> Result<HashMap<String, u64>, ParseFlatKeyedDataError> {\n    let mut stats = HashMap::new();\n    let keyed_data = common::read_cgroup_file(file_path)?;\n    for entry in keyed_data.lines() {\n        let entry_fields: Vec<&str> = entry.split_ascii_whitespace().collect();\n        if entry_fields.len() != 2 {\n            return Err(ParseFlatKeyedDataError::DoesNotConform {\n                path: file_path.to_path_buf(),\n            });\n        }\n\n        stats.insert(\n            entry_fields[0].to_owned(),\n            entry_fields[1]\n                .parse()\n                .map_err(|err| ParseFlatKeyedDataError::FailedToParse {\n                    value: entry_fields[0].into(),\n                    path: file_path.to_path_buf(),\n                    err,\n                })?,\n        );\n    }\n\n    Ok(stats)\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum ParseNestedKeyedDataError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"nested keyed data at {path} contains entries that do not conform to key format\")]\n    DoesNotConform { path: PathBuf },\n}\n\n/// Parses a file that is structured according to the nested keyed format\npub fn parse_nested_keyed_data(\n    file_path: &Path,\n) -> Result<HashMap<String, Vec<String>>, ParseNestedKeyedDataError> {\n    let mut stats: HashMap<String, Vec<String>> = HashMap::new();\n    let keyed_data = common::read_cgroup_file(file_path)?;\n    for entry in keyed_data.lines() {\n        let entry_fields: Vec<&str> = entry.split_ascii_whitespace().collect();\n        if entry_fields.len() < 2 || !entry_fields[1..].iter().all(|p| p.contains('=')) {\n            return Err(ParseNestedKeyedDataError::DoesNotConform {\n                path: file_path.to_path_buf(),\n            });\n        }\n\n        stats.insert(\n            entry_fields[0].to_owned(),\n            entry_fields[1..]\n                .iter()\n                .copied()\n                .map(|p| p.to_owned())\n                .collect(),\n        );\n    }\n\n    Ok(stats)\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum ParseDeviceNumberError {\n    #[error(\"failed to parse device number from {device}: expected 2 parts, found {numbers}\")]\n    TooManyNumbers { device: String, numbers: usize },\n    #[error(\"failed to parse device number from {device}: {err}\")]\n    MalformedNumber { device: String, err: ParseIntError },\n}\n\npub(crate) fn parse_device_number(device: &str) -> Result<(u64, u64), ParseDeviceNumberError> {\n    let numbers: Vec<&str> = device.split_terminator(':').collect();\n    if numbers.len() != 2 {\n        return Err(ParseDeviceNumberError::TooManyNumbers {\n            device: device.into(),\n            numbers: numbers.len(),\n        });\n    }\n\n    Ok((\n        numbers[0]\n            .parse()\n            .map_err(|err| ParseDeviceNumberError::MalformedNumber {\n                device: device.into(),\n                err,\n            })?,\n        numbers[1]\n            .parse()\n            .map_err(|err| ParseDeviceNumberError::MalformedNumber {\n                device: device.into(),\n                err,\n            })?,\n    ))\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum PidStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"failed to parse current pids: {0}\")]\n    ParseCurrent(ParseIntError),\n    #[error(\"failed to parse pids limit: {0}\")]\n    ParseLimit(ParseIntError),\n}\n\n/// Returns cgroup pid statistics\npub fn pid_stats(cgroup_path: &Path) -> Result<PidStats, PidStatsError> {\n    let mut stats = PidStats::default();\n\n    let current = common::read_cgroup_file(cgroup_path.join(\"pids.current\"))?;\n    stats.current = current\n        .trim()\n        .parse()\n        .map_err(PidStatsError::ParseCurrent)?;\n\n    let limit =\n        common::read_cgroup_file(cgroup_path.join(\"pids.max\")).map(|l| l.trim().to_owned())?;\n    if limit != \"max\" {\n        stats.limit = limit.parse().map_err(PidStatsError::ParseLimit)?;\n    }\n\n    Ok(stats)\n}\n\npub fn psi_stats(psi_file: &Path) -> Result<PSIStats, WrappedIoError> {\n    let mut stats = PSIStats::default();\n\n    let psi = common::read_cgroup_file(psi_file)?;\n    for line in psi.lines() {\n        match &line[0..4] {\n            \"some\" => stats.some = parse_psi(&line[4..], psi_file)?,\n            \"full\" => stats.full = parse_psi(&line[4..], psi_file)?,\n            _ => continue,\n        }\n    }\n\n    Ok(stats)\n}\n\nfn parse_psi(stat_line: &str, path: &Path) -> Result<PSIData, WrappedIoError> {\n    use std::io::{Error, ErrorKind};\n\n    let mut psi_data = PSIData::default();\n\n    for kv in stat_line.split_ascii_whitespace() {\n        match kv.split_once('=') {\n            Some((\"avg10\", v)) => {\n                psi_data.avg10 = v\n                    .parse()\n                    .map_err(|err| Error::new(ErrorKind::InvalidData, err))\n                    .wrap_other(path)?\n            }\n            Some((\"avg60\", v)) => {\n                psi_data.avg60 = v\n                    .parse()\n                    .map_err(|err| Error::new(ErrorKind::InvalidData, err))\n                    .wrap_other(path)?\n            }\n            Some((\"avg300\", v)) => {\n                psi_data.avg300 = v\n                    .parse()\n                    .map_err(|err| Error::new(ErrorKind::InvalidData, err))\n                    .wrap_other(path)?\n            }\n            _ => continue,\n        }\n    }\n\n    Ok(psi_data)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_supported_page_sizes_gigabyte() {\n        let page_size = extract_page_size(\"hugepages-1048576kB\").unwrap();\n        assert_eq!(page_size, \"1GB\");\n    }\n\n    #[test]\n    fn test_supported_page_sizes_megabyte() {\n        let page_size = extract_page_size(\"hugepages-2048kB\").unwrap();\n        assert_eq!(page_size, \"2MB\");\n    }\n\n    #[test]\n    fn test_supported_page_sizes_kilobyte() {\n        let page_size = extract_page_size(\"hugepages-512kB\").unwrap();\n        assert_eq!(page_size, \"512KB\");\n    }\n\n    #[test]\n    fn test_parse_single_value_valid() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_path = set_fixture(tmp.path(), \"single_valued_file\", \"1200\\n\").unwrap();\n\n        let value = parse_single_value(&file_path).unwrap();\n        assert_eq!(value, 1200);\n    }\n\n    #[test]\n    fn test_parse_single_value_invalid_number() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_path = set_fixture(tmp.path(), \"single_invalid_file\", \"noop\\n\").unwrap();\n\n        let value = parse_single_value(&file_path);\n        assert!(value.is_err());\n    }\n\n    #[test]\n    fn test_parse_single_value_multiple_entries() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_path = set_fixture(tmp.path(), \"multi_valued_file\", \"1200\\n1400\\n1600\").unwrap();\n\n        let value = parse_single_value(&file_path);\n        assert!(value.is_err());\n    }\n\n    #[test]\n    fn test_parse_flat_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1 1\", \"key2 2\", \"key3 3\"].join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"flat_keyed_data\", &file_content).unwrap();\n\n        let actual = parse_flat_keyed_data(&file_path).unwrap();\n        let mut expected = HashMap::with_capacity(3);\n        expected.insert(\"key1\".to_owned(), 1);\n        expected.insert(\"key2\".to_owned(), 2);\n        expected.insert(\"key3\".to_owned(), 3);\n\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_parse_flat_keyed_data_with_characters() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1 1\", \"key2 a\", \"key3 b\"].join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"flat_keyed_data\", &file_content).unwrap();\n\n        let result = parse_flat_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_space_separated_as_flat_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1\", \"key2\", \"key3\", \"key4\"].join(\" \");\n        let file_path = set_fixture(tmp.path(), \"space_separated\", &file_content).unwrap();\n\n        let result = parse_flat_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_newline_separated_as_flat_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1\", \"key2\", \"key3\", \"key4\"].join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"newline_separated\", &file_content).unwrap();\n\n        let result = parse_flat_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_nested_keyed_data_as_flat_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\n            \"key1 subkey1=value1 subkey2=value2 subkey3=value3\",\n            \"key2 subkey1=value1 subkey2=value2 subkey3=value3\",\n            \"key3 subkey1=value1 subkey2=value2 subkey3=value3\",\n        ]\n        .join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"nested_keyed_data\", &file_content).unwrap();\n\n        let result = parse_flat_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_nested_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\n            \"key1 subkey1=value1 subkey2=value2 subkey3=value3\",\n            \"key2 subkey1=value1 subkey2=value2 subkey3=value3\",\n            \"key3 subkey1=value1 subkey2=value2 subkey3=value3\",\n        ]\n        .join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"nested_keyed_data\", &file_content).unwrap();\n\n        let actual = parse_nested_keyed_data(&file_path).unwrap();\n        let mut expected = HashMap::with_capacity(3);\n        expected.insert(\n            \"key1\".to_owned(),\n            vec![\n                \"subkey1=value1\".to_owned(),\n                \"subkey2=value2\".to_owned(),\n                \"subkey3=value3\".to_owned(),\n            ],\n        );\n        expected.insert(\n            \"key2\".to_owned(),\n            vec![\n                \"subkey1=value1\".to_owned(),\n                \"subkey2=value2\".to_owned(),\n                \"subkey3=value3\".to_owned(),\n            ],\n        );\n        expected.insert(\n            \"key3\".to_owned(),\n            vec![\n                \"subkey1=value1\".to_owned(),\n                \"subkey2=value2\".to_owned(),\n                \"subkey3=value3\".to_owned(),\n            ],\n        );\n\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_parse_space_separated_as_nested_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1\", \"key2\", \"key3\", \"key4\"].join(\" \");\n        let file_path = set_fixture(tmp.path(), \"space_separated\", &file_content).unwrap();\n\n        let result = parse_nested_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_newline_separated_as_nested_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1\", \"key2\", \"key3\", \"key4\"].join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"newline_separated\", &file_content).unwrap();\n\n        let result = parse_nested_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_flat_keyed_as_nested_keyed_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"key1 1\", \"key2 2\", \"key3 3\"].join(\"\\n\");\n        let file_path = set_fixture(tmp.path(), \"newline_separated\", &file_content).unwrap();\n\n        let result = parse_nested_keyed_data(&file_path);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_device_number() {\n        let (major, minor) = parse_device_number(\"8:0\").unwrap();\n        assert_eq!((major, minor), (8, 0));\n    }\n\n    #[test]\n    fn test_parse_invalid_device_number() {\n        let result = parse_device_number(\"a:b\");\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_parse_psi_full_stats() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\n            \"some avg10=80.00 avg60=50.00 avg300=90.00 total=0\",\n            \"full avg10=10.00 avg60=30.00 avg300=50.00 total=0\",\n        ]\n        .join(\"\\n\");\n        let psi_file = set_fixture(tmp.path(), \"psi.pressure\", &file_content).unwrap();\n\n        let result = psi_stats(&psi_file).unwrap();\n        assert_eq!(\n            result,\n            PSIStats {\n                some: PSIData {\n                    avg10: 80.0,\n                    avg60: 50.0,\n                    avg300: 90.0\n                },\n                full: PSIData {\n                    avg10: 10.0,\n                    avg60: 30.0,\n                    avg300: 50.0\n                },\n            }\n        )\n    }\n\n    #[test]\n    fn test_parse_psi_only_some() {\n        let tmp = tempfile::tempdir().unwrap();\n        let file_content = [\"some avg10=80.00 avg60=50.00 avg300=90.00 total=0\"].join(\"\\n\");\n        let psi_file = set_fixture(tmp.path(), \"psi.pressure\", &file_content).unwrap();\n\n        let result = psi_stats(&psi_file).unwrap();\n        assert_eq!(\n            result,\n            PSIStats {\n                some: PSIData {\n                    avg10: 80.0,\n                    avg60: 50.0,\n                    avg300: 90.0\n                },\n                full: PSIData::default(),\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/stub/systemd/manager.rs",
    "content": "use crate::common::{AnyCgroupManager, CgroupManager};\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdManagerError {\n    #[error(\"systemd cgroup feature is required, but was not enabled during compile time\")]\n    NotEnabled,\n}\n\npub struct Manager {}\n\nimpl Manager {\n    pub fn any(self) -> AnyCgroupManager {\n        AnyCgroupManager::Systemd(Box::new(self))\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = SystemdManagerError;\n\n    fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n\n    fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n\n    fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n\n    fn stats(&self) -> Result<crate::stats::Stats, Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<nix::unistd::Pid>, Self::Error> {\n        Err(SystemdManagerError::NotEnabled)\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/stub/systemd/mod.rs",
    "content": "pub mod manager;\n"
  },
  {
    "path": "crates/libcgroups/src/stub/v1/manager.rs",
    "content": "use crate::common::{AnyCgroupManager, CgroupManager};\n\n#[derive(thiserror::Error, Debug)]\npub enum V1ManagerError {\n    #[error(\"v1 cgroup feature is required, but was not enabled during compile time\")]\n    NotEnabled,\n}\n\npub struct Manager {}\n\nimpl Manager {\n    pub fn any(self) -> AnyCgroupManager {\n        crate::common::AnyCgroupManager::V1(self)\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = V1ManagerError;\n\n    fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n\n    fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n\n    fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n\n    fn stats(&self) -> Result<crate::stats::Stats, Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<nix::unistd::Pid>, Self::Error> {\n        Err(V1ManagerError::NotEnabled)\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/stub/v1/mod.rs",
    "content": "pub mod manager;\n"
  },
  {
    "path": "crates/libcgroups/src/stub/v2/manager.rs",
    "content": "use crate::common::{AnyCgroupManager, CgroupManager};\n\n#[derive(thiserror::Error, Debug)]\npub enum V2ManagerError {\n    #[error(\"v2 cgroup feature is required, but was not enabled during compile time\")]\n    NotEnabled,\n}\n\npub struct Manager {}\n\nimpl Manager {\n    pub fn any(self) -> AnyCgroupManager {\n        crate::common::AnyCgroupManager::V2(self)\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = V2ManagerError;\n\n    fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n\n    fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n\n    fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n\n    fn stats(&self) -> Result<crate::stats::Stats, Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<nix::unistd::Pid>, Self::Error> {\n        Err(V2ManagerError::NotEnabled)\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/stub/v2/mod.rs",
    "content": "pub mod manager;\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/controller.rs",
    "content": "use std::collections::HashMap;\n\nuse super::dbus_native::serialize::Variant;\nuse crate::common::ControllerOpt;\n\npub(super) trait Controller {\n    type Error;\n\n    fn apply(\n        options: &ControllerOpt,\n        systemd_version: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error>;\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/controller_type.rs",
    "content": "use std::fmt::Display;\n\npub enum ControllerType {\n    Cpu,\n    CpuSet,\n    Io,\n    Memory,\n    Pids,\n}\n\nimpl Display for ControllerType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            ControllerType::Cpu => \"cpu\",\n            ControllerType::CpuSet => \"cpuset\",\n            ControllerType::Io => \"io\",\n            ControllerType::Memory => \"memory\",\n            ControllerType::Pids => \"pids\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\nimpl AsRef<str> for ControllerType {\n    fn as_ref(&self) -> &str {\n        match self {\n            ControllerType::Cpu => \"cpu\",\n            ControllerType::CpuSet => \"cpuset\",\n            ControllerType::Io => \"io\",\n            ControllerType::Memory => \"memory\",\n            ControllerType::Pids => \"pids\",\n        }\n    }\n}\n\npub const CONTROLLER_TYPES: &[ControllerType] = &[\n    ControllerType::Cpu,\n    ControllerType::CpuSet,\n    ControllerType::Io,\n    ControllerType::Memory,\n    ControllerType::Pids,\n];\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/cpu.rs",
    "content": "use std::collections::HashMap;\n\nuse oci_spec::runtime::LinuxCpu;\n\nuse super::controller::Controller;\nuse super::dbus_native::serialize::Variant;\nuse crate::common::ControllerOpt;\n\npub const CPU_WEIGHT: &str = \"CPUWeight\";\npub const CPU_QUOTA: &str = \"CPUQuotaPerSecUSec\";\npub const CPU_PERIOD: &str = \"CPUQuotaPeriodUSec\";\nconst MICROSECS_PER_SEC: u64 = 1_000_000;\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdCpuError {\n    #[error(\"realtime is not supported on systemd v2 yet\")]\n    RealtimeSystemd,\n}\n\npub(crate) struct Cpu {}\n\nimpl Controller for Cpu {\n    type Error = SystemdCpuError;\n\n    fn apply(\n        options: &ControllerOpt,\n        _: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(cpu) = options.resources.cpu() {\n            tracing::debug!(\"Applying cpu resource restrictions\");\n            Self::apply(cpu, properties)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl Cpu {\n    fn apply(\n        cpu: &LinuxCpu,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdCpuError> {\n        if Self::is_realtime_requested(cpu) {\n            let runtime = cpu.realtime_runtime().unwrap_or(0);\n            let period = cpu.realtime_period().unwrap_or(0);\n\n            if runtime > 0 || period > 0 {\n                return Err(SystemdCpuError::RealtimeSystemd);\n            }\n        }\n\n        if let Some(mut shares) = cpu.shares() {\n            shares = convert_shares_to_cgroup2(shares);\n            if shares != 0 {\n                properties.insert(CPU_WEIGHT, Variant::U64(shares));\n            }\n        }\n\n        // if quota is unrestricted set to 'max'\n        let mut quota = u64::MAX;\n        if let Some(specified_quota) = cpu.quota() {\n            if specified_quota > 0 {\n                let period = cpu.period().unwrap_or(100_000);\n\n                // cpu quota in systemd must be specified as number of\n                // microseconds per second of cpu time.\n                quota = specified_quota as u64 * MICROSECS_PER_SEC / period;\n            }\n        }\n        properties.insert(CPU_QUOTA, Variant::U64(quota));\n\n        let mut period: u64 = 100_000;\n        if let Some(specified_period) = cpu.period() {\n            if specified_period > 0 {\n                period = specified_period;\n            }\n        }\n        properties.insert(CPU_PERIOD, Variant::U64(period));\n\n        Ok(())\n    }\n\n    fn is_realtime_requested(cpu: &LinuxCpu) -> bool {\n        cpu.realtime_period().is_some() || cpu.realtime_runtime().is_some()\n    }\n}\n\n// Convert CPU shares (cgroup v1) into CPU weight (cgroup v2).\n// cgroup v1 shares span [2, 262_144] with a default of 1_024.\n// cgroup v2 weight spans [1, 10_000] with a default of 100.\n// A shares value of 0 keeps the field unset.\n// The quadratic fit matches runc's mapping and preserves the defaults.\n// For reference, see:\n// https://github.com/opencontainers/runc/releases/tag/v1.3.2\n// https://github.com/opencontainers/cgroups/pull/20\npub fn convert_shares_to_cgroup2(shares: u64) -> u64 {\n    if shares == 0 {\n        return 0;\n    }\n\n    const MIN_SHARES: u64 = 2;\n    const MAX_SHARES: u64 = 262_144;\n    const MAX_WEIGHT: u64 = 10_000;\n\n    if shares <= MIN_SHARES {\n        return 1;\n    }\n\n    if shares >= MAX_SHARES {\n        return MAX_WEIGHT;\n    }\n\n    let log_shares = (shares as f64).log2();\n    let exponent = (log_shares * log_shares + 125.0 * log_shares) / 612.0 - 7.0 / 34.0;\n\n    (10f64.powf(exponent)).ceil() as u64\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::super::dbus_native::serialize::DbusSerialize;\n    use super::*;\n    use crate::recast;\n\n    #[test]\n    fn test_set_shares() -> Result<()> {\n        // arrange\n        let cpu = LinuxCpuBuilder::default()\n            .shares(22000u64)\n            .build()\n            .context(\"build cpu spec\")?;\n        let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n        // act\n        Cpu::apply(&cpu, &mut properties)?;\n\n        // assert\n        assert!(properties.contains_key(CPU_WEIGHT));\n\n        let cpu_weight = &properties[CPU_WEIGHT];\n        let val = recast!(cpu_weight, Variant)?;\n        assert_eq!(val, Variant::U64(1204));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_set_quota() -> Result<()> {\n        let quotas: Vec<(i64, u64)> = vec![(200_000, 2_000_000), (0, u64::MAX), (-50000, u64::MAX)];\n\n        for quota in quotas {\n            // arrange\n            let cpu = LinuxCpuBuilder::default().quota(quota.0).build().unwrap();\n            let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n            // act\n            Cpu::apply(&cpu, &mut properties)?;\n\n            // assert\n            assert!(properties.contains_key(CPU_QUOTA));\n            let cpu_quota = &properties[CPU_QUOTA];\n            let val = recast!(cpu_quota, Variant)?;\n            assert_eq!(val, Variant::U64(quota.1));\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_set_period() -> Result<()> {\n        let periods: Vec<(u64, u64)> = vec![(200_000, 200_000), (0, 100_000)];\n\n        for period in periods {\n            let cpu = LinuxCpuBuilder::default()\n                .period(period.0)\n                .build()\n                .context(\"build cpu spec\")?;\n            let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n            // act\n            Cpu::apply(&cpu, &mut properties)?;\n\n            // assert\n            assert!(properties.contains_key(CPU_PERIOD));\n            let cpu_quota = &properties[CPU_PERIOD];\n            let val = recast!(cpu_quota, Variant)?;\n            assert_eq!(val, Variant::U64(period.1));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/cpuset.rs",
    "content": "use std::collections::HashMap;\n\nuse fixedbitset::FixedBitSet;\nuse oci_spec::runtime::LinuxCpu;\n\nuse super::controller::Controller;\nuse super::dbus_native::serialize::Variant;\nuse crate::common::ControllerOpt;\n\npub const ALLOWED_CPUS: &str = \"AllowedCPUs\";\npub const ALLOWED_NODES: &str = \"AllowedMemoryNodes\";\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdCpuSetError {\n    #[error(\"setting cpuset restrictions requires systemd version greater than 243\")]\n    OldSystemd,\n    #[error(\"could not create bitmask for cpus: {0}\")]\n    CpusBitmask(BitmaskError),\n    #[error(\"could not create bitmask for memory nodes: {0}\")]\n    MemoryNodesBitmask(BitmaskError),\n}\n\npub struct CpuSet {}\n\nimpl Controller for CpuSet {\n    type Error = SystemdCpuSetError;\n\n    fn apply(\n        options: &ControllerOpt,\n        systemd_version: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(cpu) = options.resources.cpu() {\n            tracing::debug!(\"Applying cpuset resource restrictions\");\n            return Self::apply(cpu, systemd_version, properties);\n        }\n\n        Ok(())\n    }\n}\n\nimpl CpuSet {\n    fn apply(\n        cpu: &LinuxCpu,\n        systemd_version: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdCpuSetError> {\n        if systemd_version <= 243 {\n            return Err(SystemdCpuSetError::OldSystemd);\n        }\n\n        if let Some(cpus) = cpu.cpus() {\n            let cpu_mask: Vec<_> = to_bitmask(cpus)\n                .map_err(SystemdCpuSetError::CpusBitmask)?\n                .into_iter()\n                .map(|v| v as u64)\n                .collect();\n            properties.insert(ALLOWED_CPUS, Variant::ArrayU64(cpu_mask));\n        }\n\n        if let Some(mems) = cpu.mems() {\n            let mems_mask: Vec<_> = to_bitmask(mems)\n                .map_err(SystemdCpuSetError::MemoryNodesBitmask)?\n                .into_iter()\n                .map(|v| v as u64)\n                .collect();\n            properties.insert(ALLOWED_NODES, Variant::ArrayU64(mems_mask));\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum BitmaskError {\n    #[error(\"invalid index {index}: {err}\")]\n    InvalidIndex {\n        err: std::num::ParseIntError,\n        index: String,\n    },\n    #[error(\"invalid cpu range {0}\")]\n    InvalidRange(String),\n}\n\npub fn to_bitmask(range: &str) -> Result<Vec<u8>, BitmaskError> {\n    let mut bitset = FixedBitSet::with_capacity(8);\n\n    for cpu_set in range.split_terminator(',') {\n        let cpu_set = cpu_set.trim();\n        if cpu_set.is_empty() {\n            continue;\n        }\n\n        let cpus: Vec<&str> = cpu_set.split('-').map(|s| s.trim()).collect();\n        if cpus.len() == 1 {\n            let cpu_index: usize = cpus[0].parse().map_err(|err| BitmaskError::InvalidIndex {\n                err,\n                index: cpus[0].into(),\n            })?;\n            if cpu_index >= bitset.len() {\n                bitset.grow(bitset.len() + 8);\n            }\n            bitset.set(cpu_index, true);\n        } else {\n            let start_index = cpus[0].parse().map_err(|err| BitmaskError::InvalidIndex {\n                err,\n                index: cpus[0].into(),\n            })?;\n            let end_index = cpus[1].parse().map_err(|err| BitmaskError::InvalidIndex {\n                err,\n                index: cpus[1].into(),\n            })?;\n            if start_index > end_index {\n                return Err(BitmaskError::InvalidRange(cpu_set.into()));\n            }\n\n            if end_index >= bitset.len() {\n                bitset.grow(end_index + 1);\n            }\n\n            bitset.set_range(start_index..end_index + 1, true);\n        }\n    }\n\n    // systemd expects a sequence of bytes with no leading zeros, otherwise the values will not be set\n    // with no error message\n    Ok(bitset\n        .as_slice()\n        .iter()\n        .flat_map(|b| b.to_be_bytes())\n        .skip_while(|b| *b == 0u8)\n        .collect())\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::super::dbus_native::serialize::DbusSerialize;\n    use super::*;\n    use crate::recast;\n\n    #[test]\n    fn to_bitmask_single_value() -> Result<()> {\n        let cpus = \"0\"; // 0000 0001\n\n        let bitmask = to_bitmask(cpus).context(\"to bitmask\")?;\n\n        assert_eq!(bitmask.len(), 1);\n        assert_eq!(bitmask[0], 1);\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_multiple_single_values() -> Result<()> {\n        let cpus = \"0,1,2\"; // 0000 0111\n\n        let bitmask = to_bitmask(cpus).context(\"to bitmask\")?;\n\n        assert_eq!(bitmask.len(), 1);\n        assert_eq!(bitmask[0], 7);\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_range_value() -> Result<()> {\n        let cpus = \"0-2\"; // 0000 0111\n\n        let bitmask = to_bitmask(cpus).context(\"to bitmask\")?;\n\n        assert_eq!(bitmask.len(), 1);\n        assert_eq!(bitmask[0], 7);\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_interchanged_range() -> Result<()> {\n        let cpus = \"2-0\";\n\n        let result = to_bitmask(cpus).context(\"to bitmask\");\n        assert!(result.is_err());\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_incomplete_range() -> Result<()> {\n        let cpus = vec![\"2-\", \"-2\"];\n\n        for c in cpus {\n            let result = to_bitmask(c).context(\"to bitmask\");\n            assert!(result.is_err());\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_mixed() -> Result<()> {\n        let cpus = \"0,2-4,7,9-10\"; // 0000 0110 1001 1101\n\n        let bitmask = to_bitmask(cpus).context(\"to bitmask\")?;\n\n        assert_eq!(bitmask.len(), 2);\n        assert_eq!(bitmask[0], 6);\n        assert_eq!(bitmask[1], 157);\n        Ok(())\n    }\n\n    #[test]\n    fn to_bitmask_extra_characters() -> Result<()> {\n        let cpus = \"0, 2- 4,,7   ,,9-10\"; // 0000 0110 1001 1101\n\n        let bitmask = to_bitmask(cpus).context(\"to bitmask\")?;\n        assert_eq!(bitmask.len(), 2);\n        assert_eq!(bitmask[0], 6);\n        assert_eq!(bitmask[1], 157);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_cpuset_systemd_too_old() -> Result<()> {\n        let systemd_version = 235;\n        let cpu = LinuxCpuBuilder::default()\n            .build()\n            .context(\"build cpu spec\")?;\n        let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n        let result = CpuSet::apply(&cpu, systemd_version, &mut properties);\n\n        assert!(result.is_err());\n        Ok(())\n    }\n\n    #[test]\n    fn test_cpuset_set() -> Result<()> {\n        let systemd_version = 245;\n        let cpu = LinuxCpuBuilder::default()\n            .cpus(\"0-3\")\n            .mems(\"0-3\")\n            .build()\n            .context(\"build cpu spec\")?;\n        let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n        CpuSet::apply(&cpu, systemd_version, &mut properties).context(\"apply cpuset\")?;\n\n        assert_eq!(properties.len(), 2);\n        assert!(properties.contains_key(ALLOWED_CPUS));\n        let cpus = properties.get(ALLOWED_CPUS).unwrap();\n        let v = recast!(cpus, Variant)?;\n        assert!(matches!(v, Variant::ArrayU64(_)));\n\n        assert!(properties.contains_key(ALLOWED_NODES));\n        let mems = properties.get(ALLOWED_NODES).unwrap();\n        let v = recast!(mems, Variant)?;\n        assert!(matches!(v, Variant::ArrayU64(_)));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/client.rs",
    "content": "use std::collections::HashMap;\nuse std::path::PathBuf;\n\nuse super::serialize::Variant;\nuse super::utils::SystemdClientError;\n\npub trait SystemdClient {\n    #[allow(dead_code)]\n    fn is_system(&self) -> bool;\n\n    fn transient_unit_exists(&self, unit_name: &str) -> bool;\n\n    fn start_transient_unit(\n        &self,\n        container_name: &str,\n        pid: u32,\n        parent: &str,\n        unit_name: &str,\n    ) -> Result<(), SystemdClientError>;\n\n    fn stop_transient_unit(&self, unit_name: &str) -> Result<(), SystemdClientError>;\n\n    fn set_unit_properties(\n        &self,\n        unit_name: &str,\n        properties: &HashMap<&str, Variant>,\n    ) -> Result<(), SystemdClientError>;\n\n    fn systemd_version(&self) -> Result<u32, SystemdClientError>;\n\n    fn control_cgroup_root(&self) -> Result<PathBuf, SystemdClientError>;\n\n    fn add_process_to_unit(\n        &self,\n        unit_name: &str,\n        subcgroup: &str,\n        pid: u32,\n    ) -> Result<(), SystemdClientError>;\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/dbus.rs",
    "content": "use std::collections::HashMap;\nuse std::io::{IoSlice, IoSliceMut};\nuse std::os::fd::AsRawFd;\nuse std::path::PathBuf;\nuse std::sync::atomic::{AtomicU32, Ordering};\n\nuse nix::errno::Errno;\nuse nix::sys::socket;\n\nuse super::client::SystemdClient;\nuse super::message::*;\nuse super::proxy::Proxy;\nuse super::utils::{DbusError, Result, SystemdClientError};\nuse crate::systemd::dbus_native::serialize::{DbusSerialize, Structure, Variant};\n\nconst REPLY_BUF_SIZE: usize = 128; // seems good enough tradeoff between extra size and repeated calls\n\n/// NOTE that this is meant for a single-threaded use, and concurrent\n/// usage can cause errors, primarily because then the message received over\n/// socket can be out of order and we need to manager buffer and check with message counter\n/// which message is for which request etc etc\n// Client is a wrapper providing higher level API and abatraction around dbus.\n// For more information see https://www.freedesktop.org/wiki/Software/systemd/dbus/\npub struct DbusConnection {\n    /// Is the socket system level or session specific\n    #[allow(dead_code)]\n    system: bool,\n    /// socket fd\n    socket: i32,\n    /// name id assigned by dbus for the connection\n    id: Option<String>,\n    /// counter for messages\n    // This must be atomic, so that we can take non-mutable reference to self\n    // and still increment this\n    msg_ctr: AtomicU32,\n}\n\n#[inline(always)]\nfn uid_to_hex_str(uid: u32) -> String {\n    let temp: Vec<_> = uid\n        .to_string()\n        .chars()\n        .map(|c| format!(\"{:x}\", c as u8))\n        .collect();\n    temp.join(\"\")\n}\n\nfn parse_dbus_address(env_value: String) -> Result<String> {\n    // as per spec, the env var can have multiple addresses separated by ;\n    let addr_list: Vec<_> = env_value.split(';').collect();\n    for addr in addr_list {\n        if let Some(s) = addr.strip_prefix(\"unix:path=\") {\n            if !std::path::PathBuf::from(s).exists() {\n                continue;\n            }\n            return Ok(s.to_owned());\n        }\n\n        if let Some(s) = addr.strip_prefix(\"unix:abstract=\") {\n            return Ok(s.to_owned());\n        }\n    }\n    // we do not support unix:runtime=\n    Err(DbusError::BusAddressError(format!(\"no valid bus path found in list {}\", env_value)).into())\n}\n\nfn get_session_bus_address() -> Result<String> {\n    if let Ok(s) = std::env::var(\"DBUS_SESSION_BUS_ADDRESS\") {\n        return parse_dbus_address(s);\n    }\n\n    if let Ok(mut s) = std::env::var(\"XDG_RUNTIME_DIR\") {\n        s.push_str(\"/bus\");\n        if !std::path::PathBuf::from(&s).exists() {\n            return Err(DbusError::BusAddressError(format!(\n                \"session bus address {} does not exist\",\n                s\n            ))\n            .into());\n        }\n        return Ok(s);\n    }\n\n    Err(\n        DbusError::BusAddressError(\"could not find dbus session bus address from env\".into())\n            .into(),\n    )\n}\n\nfn get_system_bus_address() -> Result<String> {\n    if let Ok(s) = std::env::var(\"DBUS_SYSTEM_BUS_ADDRESS\") {\n        return parse_dbus_address(s);\n    }\n    // as per dbus spec https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-types-system\n    // there are multiple service files which we should try searching and finding bus address from\n    // but we will instead just support the following, which is supposed to be\n    // well known anyways according to spec\n    Ok(\"/var/run/dbus/system_bus_socket\".into())\n}\n\nfn get_actual_uid() -> Result<u32> {\n    let output = std::process::Command::new(\"busctl\")\n        .arg(\"--user\")\n        .arg(\"--no-pager\")\n        .arg(\"status\")\n        .stdin(std::process::Stdio::null())\n        .stdout(std::process::Stdio::piped())\n        .spawn()\n        .map_err(|e| DbusError::BusctlError(format!(\"error in running busctl {:?}\", e)))?\n        .wait_with_output()\n        .map_err(|e| DbusError::BusctlError(format!(\"error from busctl execution {:?}\", e)))?;\n\n    let stdout = String::from_utf8_lossy(&output.stdout);\n    let found =\n        stdout\n            .lines()\n            .find(|s| s.starts_with(\"OwnerUID=\"))\n            .ok_or(DbusError::BusctlError(\n                \"could not find OwnerUID from busctl\".into(),\n            ))?;\n\n    let uid = found\n        .trim_start_matches(\"OwnerUID=\")\n        .parse::<u32>()\n        .map_err(DbusError::UidError)?;\n    Ok(uid)\n}\n\nimpl DbusConnection {\n    /// Open a new dbus connection to given address\n    /// authenticating as user with given uid\n    pub fn new(addr: &str, uid: u32, system: bool) -> Result<Self> {\n        // Use ManuallyDrop to keep the socket open.\n        let socket = std::mem::ManuallyDrop::new(socket::socket(\n            socket::AddressFamily::Unix,\n            socket::SockType::Stream,\n            socket::SockFlag::empty(),\n            None,\n        )?);\n\n        let addr = socket::UnixAddr::new(addr)?;\n        socket::connect(socket.as_raw_fd(), &addr)?;\n        let mut dbus = Self {\n            socket: socket.as_raw_fd(),\n            msg_ctr: AtomicU32::new(0),\n            id: None,\n            system,\n        };\n        dbus.authenticate(uid)?;\n        Ok(dbus)\n    }\n\n    pub fn new_system() -> Result<Self> {\n        let addr = get_system_bus_address()?;\n        Self::new(&addr, 0, true)\n    }\n\n    pub fn new_session() -> Result<Self> {\n        let addr = get_session_bus_address()?;\n        let uid = get_actual_uid()?;\n        Self::new(&addr, uid, false)\n    }\n\n    /// Authenticates with dbus using given uid via external strategy\n    /// Must be called on any connection before doing any other communication\n    fn authenticate(&mut self, uid: u32) -> Result<()> {\n        let mut buf = [0; 64];\n\n        // dbus connection always start with a 0 byte sent as first thing\n        socket::send(self.socket, &[0], socket::MsgFlags::empty())?;\n\n        let msg = format!(\"AUTH EXTERNAL {}\\r\\n\", uid_to_hex_str(uid));\n\n        // then we send our auth with uid\n        socket::send(self.socket, msg.as_bytes(), socket::MsgFlags::empty())?;\n\n        // we get the reply and check if all went well or not\n        socket::recv(self.socket, &mut buf, socket::MsgFlags::empty())?;\n\n        let reply: Vec<u8> = buf.iter().filter(|v| **v != 0).copied().collect();\n\n        // we can use _lossy as we know dbus communication is always ascii\n        let reply = String::from_utf8_lossy(&reply);\n\n        // successful auth reply starts with 'ok'\n        if !reply.starts_with(\"OK\") {\n            return Err(DbusError::AuthenticationErr(format!(\n                \"Authentication failed, got message : {}\",\n                reply\n            ))\n            .into());\n        }\n\n        // we must send the BEGIN before starting any actual communication\n        // we can also send AGREE_UNIX_FD before this if we need to deal with sending/receiving\n        // fds over the connection, but because youki doesn't need it, we can skip that\n        socket::send(\n            self.socket,\n            \"BEGIN\\r\\n\".as_bytes(),\n            socket::MsgFlags::empty(),\n        )?;\n\n        // First thing any dbus client must do after authentication\n        // is to do a hello method call, in order to get a name allocated\n        // if we do any other method call, the connection is assumed to be\n        // invalid and auto disconnected\n        let headers = vec![\n            Header {\n                kind: HeaderKind::Path,\n                value: HeaderValue::String(\"/org/freedesktop/DBus\".to_string()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".to_string()),\n            },\n            Header {\n                kind: HeaderKind::Interface,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".to_string()),\n            },\n            Header {\n                kind: HeaderKind::Member,\n                value: HeaderValue::String(\"Hello\".to_string()),\n            },\n        ];\n\n        let res = self.send_message(MessageType::MethodCall, headers, vec![])?;\n\n        let res: Vec<_> = res\n            .into_iter()\n            .filter(|m| m.preamble.mtype == MessageType::MethodReturn)\n            .collect();\n\n        let res = res.first().ok_or(DbusError::AuthenticationErr(format!(\n            \"expected Hello call to have reply, found no reply message, got {:?} instead\",\n            res\n        )))?;\n        let mut ctr = 0;\n        let id = String::deserialize(&res.body, &mut ctr)?;\n        self.id = Some(id);\n\n        Ok(())\n    }\n\n    /// Helper function to get complete message in chunks\n    /// over the socket. This will loop and collect all of the message\n    /// chunks into a single vector\n    fn receive_complete_response(&self) -> Result<Vec<u8>> {\n        let mut ret = Vec::with_capacity(512);\n        loop {\n            let mut reply: [u8; REPLY_BUF_SIZE] = [0_u8; REPLY_BUF_SIZE];\n            let mut reply_buffer = [IoSliceMut::new(&mut reply[0..])];\n\n            let reply_res = socket::recvmsg::<()>(\n                self.socket,\n                &mut reply_buffer,\n                None,\n                socket::MsgFlags::empty(),\n            );\n\n            let reply_rcvd = match reply_res {\n                Ok(msg) => msg,\n                Err(Errno::EAGAIN) => continue,\n                Err(e) => return Err(e.into()),\n            };\n            let received_byte_count = reply_rcvd.bytes;\n\n            ret.extend_from_slice(&reply[0..received_byte_count]);\n\n            if received_byte_count < REPLY_BUF_SIZE {\n                // if received byte count is less than buffer size, then we got all\n                break;\n            }\n        }\n        Ok(ret)\n    }\n\n    /// function to send message of given type with given headers and body\n    /// over the dbus connection. The caller must specify the destination, interface etc.etc.\n    /// in the headers, this function will only take care of sending the message and\n    /// returning the received messages. Note that the caller must check if any error\n    /// message was returned or not, this will not check that, the returned Err\n    /// indicates error in sending/receiving message\n    pub fn send_message(\n        &self,\n        mtype: MessageType,\n        mut headers: Vec<Header>,\n        body: Vec<u8>,\n    ) -> Result<Vec<Message>> {\n        if let Some(s) = &self.id {\n            headers.push(Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(s.clone()),\n            });\n        }\n\n        let message = Message::new(mtype, self.get_msg_id(), headers, body);\n        let serialized = message.serialize();\n\n        socket::sendmsg::<()>(\n            self.socket,\n            &[IoSlice::new(&serialized)],\n            &[],\n            socket::MsgFlags::empty(),\n            None,\n        )?;\n\n        let mut ret = Vec::new();\n\n        // it is possible that while receiving messages, we get some extra/previous message\n        // for method calls, we need to have an error or method return type message, so\n        // we keep looping until we get either of these. see https://github.com/youki-dev/youki/issues/2826\n        // for more detailed analysis.\n        loop {\n            let reply = self.receive_complete_response()?;\n\n            // note that a single received response can contain multiple\n            // messages, so we must deserialize it piece by piece\n            let mut buf = &reply[..];\n\n            while !buf.is_empty() {\n                let mut ctr = 0;\n                let msg = Message::deserialize(&buf[ctr..], &mut ctr)?;\n                // we reset the buf, because I couldn't figure out how the adjust_counter function\n                // should should be changed to work correctly with non-zero start counter, and this solved that issue\n                buf = &buf[ctr..];\n                ret.push(msg);\n            }\n\n            // in Youki, we only ever do method call apart from initial auth\n            // in case it is, we don't really have a specific message to look\n            // out of, so we take the buffer and break\n            if mtype != MessageType::MethodCall {\n                break;\n            }\n\n            // check if any of the received message is method return or error type\n            let return_message_count = ret\n                .iter()\n                .filter(|m| {\n                    m.preamble.mtype == MessageType::MethodReturn\n                        || m.preamble.mtype == MessageType::Error\n                })\n                .count();\n\n            if return_message_count > 0 {\n                break;\n            }\n        }\n        Ok(ret)\n    }\n\n    /// function to manage the message counter\n    fn get_msg_id(&self) -> u32 {\n        let old_ctr = self.msg_ctr.fetch_add(1, Ordering::SeqCst);\n        old_ctr + 1\n    }\n\n    /// Create a proxy for given destination and path\n    pub fn proxy(&self, destination: &str, path: &str) -> Proxy<'_> {\n        Proxy::new(self, destination, path)\n    }\n\n    fn create_proxy(&self) -> Proxy<'_> {\n        self.proxy(\"org.freedesktop.systemd1\", \"/org/freedesktop/systemd1\")\n    }\n}\n\nimpl SystemdClient for DbusConnection {\n    fn is_system(&self) -> bool {\n        self.system\n    }\n\n    fn transient_unit_exists(&self, unit_name: &str) -> bool {\n        let mut proxy = self.create_proxy();\n        proxy.get_unit(unit_name).is_ok()\n    }\n\n    /// start_transient_unit is a higher level API for starting a unit\n    /// for a specific container under systemd.\n    /// See https://www.freedesktop.org/wiki/Software/systemd/dbus for more details.\n    fn start_transient_unit(\n        &self,\n        container_name: &str,\n        pid: u32,\n        parent: &str,\n        unit_name: &str,\n    ) -> Result<()> {\n        // To view and introspect the methods under the 'org.freedesktop.systemd1' destination\n        // and object path under it use the following command:\n        // `gdbus introspect --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1`\n        let proxy = self.create_proxy();\n\n        // To align with runc, youki will always add the following properties to its container units:\n        // - CPUAccounting=true\n        // - IOAccounting=true (BlockIOAccounting for cgroup v1)\n        // - MemoryAccounting=true\n        // - TasksAccounting=true\n        // see https://github.com/opencontainers/runc/blob/6023d635d725a74c6eaa11ab7f3c870c073badd2/docs/systemd.md#systemd-cgroup-driver\n        // for more details.\n        let mut properties: Vec<(&str, Variant)> = Vec::with_capacity(6);\n        properties.push((\n            \"Description\",\n            Variant::String(format!(\"youki container {container_name}\")),\n        ));\n\n        // if we create a slice, the parent is defined via a Wants=\n        // otherwise, we use Slice=\n        if unit_name.ends_with(\"slice\") {\n            properties.push((\"Wants\", Variant::String(parent.to_owned())));\n        } else {\n            properties.push((\"Slice\", Variant::String(parent.to_owned())));\n            properties.push((\"Delegate\", Variant::Bool(true)));\n        }\n\n        properties.push((\"MemoryAccounting\", Variant::Bool(true)));\n        properties.push((\"CPUAccounting\", Variant::Bool(true)));\n        properties.push((\"IOAccounting\", Variant::Bool(true)));\n        properties.push((\"TasksAccounting\", Variant::Bool(true)));\n\n        properties.push((\"DefaultDependencies\", Variant::Bool(false)));\n        properties.push((\"PIDs\", Variant::ArrayU32(vec![pid])));\n\n        tracing::debug!(\"Starting transient unit: {:?}\", properties);\n        let props = properties\n            .into_iter()\n            .map(|(k, v)| Structure::new(k.into(), v))\n            .collect();\n        proxy\n            .start_transient_unit(unit_name, \"replace\", props, vec![])\n            .map_err(|err| SystemdClientError::FailedTransient {\n                err: Box::new(err),\n                unit_name: unit_name.into(),\n                parent: parent.into(),\n            })?;\n        Ok(())\n    }\n\n    fn stop_transient_unit(&self, unit_name: &str) -> Result<()> {\n        let proxy = self.create_proxy();\n\n        proxy\n            .stop_unit(unit_name, \"replace\")\n            .map_err(|err| SystemdClientError::FailedStop {\n                err: Box::new(err),\n                unit_name: unit_name.into(),\n            })?;\n        Ok(())\n    }\n\n    fn set_unit_properties(\n        &self,\n        unit_name: &str,\n        properties: &HashMap<&str, Variant>,\n    ) -> Result<()> {\n        let proxy = self.create_proxy();\n\n        let props: Vec<Structure<Variant>> = properties\n            .iter()\n            .map(|(k, v)| Structure::new(k.to_string(), v.clone()))\n            .collect();\n\n        proxy\n            .set_unit_properties(unit_name, true, props)\n            .map_err(|err| SystemdClientError::FailedProperties {\n                err: Box::new(err),\n                unit_name: unit_name.into(),\n            })?;\n        Ok(())\n    }\n\n    fn systemd_version(&self) -> std::result::Result<u32, SystemdClientError> {\n        let proxy = self.create_proxy();\n\n        let version = proxy\n            .version()?\n            .chars()\n            .skip_while(|c| c.is_alphabetic())\n            .take_while(|c| c.is_numeric())\n            .collect::<String>()\n            .parse::<u32>()\n            .map_err(SystemdClientError::SystemdVersion)?;\n\n        Ok(version)\n    }\n\n    fn control_cgroup_root(&self) -> std::result::Result<PathBuf, SystemdClientError> {\n        let proxy = self.create_proxy();\n\n        let cgroup_root = proxy.control_group()?;\n        Ok(PathBuf::from(&cgroup_root))\n    }\n    fn add_process_to_unit(&self, unit_name: &str, subcgroup: &str, pid: u32) -> Result<()> {\n        let proxy = self.create_proxy();\n        proxy.attach_process(unit_name, subcgroup, pid)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use nix::unistd::getuid;\n\n    use super::super::utils::Result;\n    use super::{DbusConnection, SystemdClientError, uid_to_hex_str};\n\n    #[test]\n    fn test_uid_to_hex_str() {\n        let uid0 = uid_to_hex_str(0);\n        assert_eq!(uid0, \"30\");\n        let uid1000 = uid_to_hex_str(1000);\n        assert_eq!(uid1000, \"31303030\");\n    }\n\n    #[test]\n    #[cfg(feature = \"systemd\")]\n    fn test_dbus_connection_auth() {\n        let uid: u32 = getuid().into();\n\n        let dbus_pipe_path = format!(\"/run/user/{}/bus\", uid);\n\n        let conn = DbusConnection::new(&dbus_pipe_path, uid, false);\n        assert!(conn.is_ok());\n\n        let invalid_conn = DbusConnection::new(&dbus_pipe_path, uid.wrapping_add(1), false);\n        assert!(invalid_conn.is_err());\n    }\n\n    #[test]\n    #[cfg(feature = \"systemd\")]\n    fn test_dbus_function_calls() -> Result<()> {\n        use crate::systemd::dbus_native::serialize::Variant;\n\n        let uid: u32 = getuid().into();\n\n        let dbus_pipe_path = format!(\"/run/user/{}/bus\", uid);\n\n        let conn = DbusConnection::new(&dbus_pipe_path, uid, false)?;\n\n        let proxy = conn.proxy(\"org.freedesktop.systemd1\", \"/org/freedesktop/systemd1\");\n\n        let body = (\n            \"org.freedesktop.systemd1.Manager\".to_string(),\n            \"Version\".to_string(),\n        );\n        let t = proxy.method_call::<_, Variant>(\n            \"org.freedesktop.DBus.Properties\",\n            \"Get\",\n            Some(body),\n        )?;\n        assert!(matches!(t, Variant::String(_)));\n\n        let body = (\n            \"org.freedesktop.systemd1.Manager\".to_string(),\n            \"ControlGroup\".to_string(),\n        );\n        let t = proxy.method_call::<_, Variant>(\n            \"org.freedesktop.DBus.Properties\",\n            \"Get\",\n            Some(body),\n        )?;\n        assert!(matches!(t, Variant::String(_)));\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"systemd\")]\n    fn test_dbus_function_calls_errors() {\n        use crate::systemd::dbus_native::utils::DbusError;\n\n        let uid: u32 = getuid().into();\n\n        let dbus_pipe_path = format!(\"/run/user/{}/bus\", uid);\n\n        let conn = DbusConnection::new(&dbus_pipe_path, uid, false).unwrap();\n\n        let proxy = conn.proxy(\"org.freedesktop.systemd1\", \"/org/freedesktop/systemd1\");\n        let body = (\n            \"org.freedesktop.systemd1.Manager\".to_string(),\n            \"ControlGroup\".to_string(),\n        );\n\n        // invalid return type, this call returns variant<String>\n        let res = proxy.method_call::<_, u16>(\"org.freedesktop.DBus.Properties\", \"Get\", Some(body));\n        assert!(res.is_err());\n        assert!(matches!(\n            res,\n            Err(SystemdClientError::DBus(DbusError::DeserializationError(_)))\n        ));\n\n        let body = (\n            \"org.freedesktop.systemd1.Manager\".to_string(),\n            \"ControlGroup\".to_string(),\n        );\n\n        // invalid interface\n        let res = proxy.method_call::<_, u16>(\"org.freedesktop.DBus.Property_\", \"Get\", Some(body));\n        assert!(res.is_err());\n        assert!(matches!(\n            res,\n            Err(SystemdClientError::DBus(DbusError::MethodCallErr(_)))\n        ))\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/message.rs",
    "content": "use super::utils::{DbusError, Result, adjust_padding, align_counter};\n\n#[derive(Debug)]\n/// Indicates the endian of message\npub enum Endian {\n    Little,\n    Big, // we do not support this unless explicitly requested in youki's issues\n}\n\nimpl Endian {\n    fn to_byte(&self) -> u8 {\n        match self {\n            Self::Big => b'b',\n            Self::Little => b'l',\n        }\n    }\n    fn from_byte(byte: u8) -> Self {\n        match byte {\n            b'l' => Self::Little,\n            b'b' => Self::Big,\n            _ => panic!(\"invalid endian {}\", byte),\n        }\n    }\n}\n\n/// Represents the type of header data\n// there are others, but these are the only once we need\n#[derive(Debug, PartialEq, Eq)]\npub enum HeaderSignature {\n    Object,\n    U32,\n    String,\n    Signature,\n}\n\nimpl HeaderSignature {\n    fn to_byte(&self) -> u8 {\n        match self {\n            Self::Object => b'o',\n            Self::Signature => b'g',\n            Self::String => b's',\n            Self::U32 => b'u',\n        }\n    }\n    fn from_byte(byte: u8) -> Self {\n        match byte {\n            b'o' => Self::Object,\n            b'g' => Self::Signature,\n            b's' => Self::String,\n            b'u' => Self::U32,\n            _ => panic!(\"unexpected signature {}\", byte),\n        }\n    }\n}\n\n/// Type of message\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\npub enum MessageType {\n    MethodCall,\n    MethodReturn,\n    Error,\n    Signal, // we will ignore this for all intents and purposes\n}\n\n/// Represents the kind of header\n#[derive(Debug, PartialEq, Eq)]\npub enum HeaderKind {\n    Path,\n    Interface,\n    Member,\n    ErrorName,\n    ReplySerial,\n    Destination,\n    Sender,\n    BodySignature,\n    UnixFd, // we will not use this, just for the sake of completion\n}\n\nimpl HeaderKind {\n    fn signature(&self) -> HeaderSignature {\n        match &self {\n            Self::Path => HeaderSignature::Object,\n            Self::ReplySerial => HeaderSignature::U32,\n            Self::BodySignature => HeaderSignature::Signature, // this is also encoded as string, but we need special handling for how its length is encoded\n            Self::UnixFd => HeaderSignature::U32,\n            _ => HeaderSignature::String, // rest all are encoded as string\n        }\n    }\n}\n\n// This is separated from header kind, because I wanted\n// HeaderKind to be u8 like directly comparable, passable thing\n#[derive(Debug, PartialEq, Eq)]\npub enum HeaderValue {\n    String(String),\n    U32(u32),\n}\n\nimpl HeaderValue {\n    fn as_bytes(&self) -> Vec<u8> {\n        match self {\n            Self::String(s) => {\n                let mut t: Vec<u8> = s.as_bytes().into();\n                t.push(0); // null byte terminator\n                t\n            }\n            Self::U32(v) => v.to_le_bytes().into(),\n        }\n    }\n\n    fn len(&self) -> usize {\n        match self {\n            Self::String(s) => s.len(), // we don't consider terminating null byte in length\n            Self::U32(_) => 4,          // u32 is encoded as 4 bytes\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Eq)]\npub struct Header {\n    pub kind: HeaderKind,\n    pub value: HeaderValue,\n}\n\nimpl Header {\n    /// Parses a single header from given u8 vec,\n    /// assuming the header to start from given counter point\n    fn parse(buf: &[u8], ctr: &mut usize) -> Result<Self> {\n        let header_kind = match buf[*ctr] {\n            1 => HeaderKind::Path,\n            2 => HeaderKind::Interface,\n            3 => HeaderKind::Member,\n            4 => HeaderKind::ErrorName,\n            5 => HeaderKind::ReplySerial,\n            6 => HeaderKind::Destination,\n            7 => HeaderKind::Sender,\n            8 => HeaderKind::BodySignature,\n            9 => HeaderKind::UnixFd,\n            v => {\n                // should not occur unless we mess up parsing somewhere else\n                return Err(DbusError::DeserializationError(format!(\n                    \"found invalid header kind value : {}\",\n                    v\n                ))\n                .into());\n            }\n        };\n\n        // account for the header_kind byte\n        *ctr += 1;\n\n        // length of signature is always < 255, i.e. stored in 1 byte\n        let signature_length = buf[*ctr] as usize;\n        *ctr += 1;\n\n        // we only support string, u32 signature and object,\n        // all of which have signature length of 1 byte\n        if signature_length != 1 {\n            return Err(DbusError::IncompleteImplementation(\n                \"complex header type not supported\".into(),\n            )\n            .into());\n        }\n\n        let actual_signature = HeaderSignature::from_byte(buf[*ctr]);\n\n        // we can simply += 1, but I think this is more sensible\n        *ctr += signature_length;\n\n        let expected_signature = header_kind.signature();\n\n        if actual_signature != expected_signature {\n            return Err(DbusError::DeserializationError(format!(\n                \"header signature mismatch, expected {:?}, found {:?}\",\n                expected_signature, actual_signature\n            ))\n            .into());\n        }\n\n        *ctr += 1; // accounting for extra null byte that is always there\n\n        let value = match expected_signature {\n            HeaderSignature::U32 => {\n                if buf.len() < *ctr + 4 {\n                    return Err(DbusError::DeserializationError(\n                        \"incomplete response : partial header value\".into(),\n                    )\n                    .into());\n                }\n                let ret = HeaderValue::U32(u32::from_le_bytes(\n                    buf[*ctr..*ctr + 4].try_into().unwrap(), // we can unwrap here as we know 4 byte buffer will satisfy [u8;4]\n                ));\n                *ctr += 4;\n                ret\n            }\n            // both are encoded as string\n            HeaderSignature::Object | HeaderSignature::String => {\n                if buf.len() < *ctr + 4 {\n                    return Err(DbusError::DeserializationError(\n                        \"incomplete response : partial header value length\".into(),\n                    )\n                    .into());\n                }\n                let len = u32::from_le_bytes(buf[*ctr..*ctr + 4].try_into().unwrap()) as usize;\n                *ctr += 4;\n                if buf.len() < *ctr + len {\n                    return Err(DbusError::DeserializationError(\n                        \"incomplete response : partial header value\".into(),\n                    )\n                    .into());\n                }\n                let string = String::from_utf8(buf[*ctr..*ctr + len].into()).unwrap();\n                *ctr += len + 1; // +1 to account for null\n                HeaderValue::String(string)\n            }\n            // only difference here is that length is 1 byte, not 4 bytes\n            HeaderSignature::Signature => {\n                let len = buf[*ctr] as usize;\n                *ctr += 1;\n                if buf.len() < *ctr + len {\n                    return Err(DbusError::DeserializationError(\n                        \"incomplete response : partial header value\".into(),\n                    )\n                    .into());\n                }\n                let signature = String::from_utf8(buf[*ctr..*ctr + len].into()).unwrap();\n                *ctr += len + 1; //+1 to account for null byte\n                HeaderValue::String(signature)\n            }\n        };\n        Ok(Self {\n            kind: header_kind,\n            value,\n        })\n    }\n}\n\n/// Message preamble of initial 4 bytes\n#[derive(Debug)]\npub struct Preamble {\n    endian: Endian,\n    pub mtype: MessageType,\n    flags: u8,\n    version: u8,\n}\n\nimpl Preamble {\n    fn new(mtype: MessageType) -> Self {\n        Self {\n            endian: Endian::Little, // we don't support big endian until requested\n            mtype,\n            flags: 0,   // until we need some flags to be used, this is fixed\n            version: 1, // this is fixed until dbus releases a new major version\n        }\n    }\n}\n\n/// Represents a complete message transported over dbus connection\n#[derive(Debug)]\npub struct Message {\n    /// Initial 4 byte preamble needed for all messages\n    pub preamble: Preamble,\n    /// Serial ID of message\n    pub serial: u32,\n    // Message headers\n    pub headers: Vec<Header>,\n    /// Actual body, serialized\n    pub body: Vec<u8>,\n}\n\nimpl Message {\n    /// create a new message structure\n    pub fn new(mtype: MessageType, serial: u32, headers: Vec<Header>, body: Vec<u8>) -> Self {\n        let preamble = Preamble::new(mtype);\n        Self {\n            preamble,\n            serial,\n            headers,\n            body,\n        }\n    }\n}\n\n// NOTE that this does not add padding after last header, because we need\n// non-padded header length\n// the 8-byte alignment must be done separately after this\nfn serialize_headers(headers: &[Header]) -> Vec<u8> {\n    let mut ret = vec![];\n\n    for header in headers {\n        // all headers are always 8 byte aligned\n        adjust_padding(&mut ret, 8);\n\n        let header_kind: u8 = match &header.kind {\n            HeaderKind::Path => 1,\n            HeaderKind::Interface => 2,\n            HeaderKind::Member => 3,\n            HeaderKind::ErrorName => 4,\n            HeaderKind::ReplySerial => 5,\n            HeaderKind::Destination => 6,\n            HeaderKind::Sender => 7,\n            HeaderKind::BodySignature => 8,\n            HeaderKind::UnixFd => 9,\n        };\n\n        let header_signature: u8 = header.kind.signature().to_byte();\n\n        let signature_length = 1; // signature length is always 1 byte, and for all our headers, it is going to be 1\n\n        // header preamble\n        ret.extend_from_slice(&[header_kind, signature_length, header_signature, 0]);\n\n        let header_value_length = header.value.len() as u32;\n\n        // add header value length\n        match &header.kind {\n            HeaderKind::BodySignature => {\n                // signature length is always 1 byte\n                ret.push(header_value_length as u8);\n            }\n            HeaderKind::ReplySerial | HeaderKind::UnixFd => {\n                /* do nothing as u32 does not need length appended*/\n            }\n            _ => {\n                ret.extend_from_slice(&header_value_length.to_le_bytes());\n            }\n        }\n\n        ret.extend_from_slice(&header.value.as_bytes());\n    }\n\n    ret\n}\n\n/// deserializes multiple headers from given array\nfn deserialize_headers(buf: &[u8]) -> Result<Vec<Header>> {\n    let mut ret = Vec::new();\n\n    let mut ctr = 0;\n    // headers are always aligned at 8 byte boundary\n    align_counter(&mut ctr, 8);\n    while ctr < buf.len() {\n        let header = Header::parse(buf, &mut ctr)?;\n        align_counter(&mut ctr, 8);\n        ret.push(header);\n    }\n    Ok(ret)\n}\n\nimpl Message {\n    /// Serialize the given message into u8 vec\n    pub fn serialize(mut self) -> Vec<u8> {\n        let mtype = match self.preamble.mtype {\n            MessageType::MethodCall => 1,\n            MessageType::MethodReturn => 2,\n            MessageType::Error => 3,\n            MessageType::Signal => 4,\n        };\n\n        // preamble\n        // Endian, message type, flags, dbus spec version\n        let mut message: Vec<u8> = vec![\n            self.preamble.endian.to_byte(),\n            mtype,\n            self.preamble.flags,\n            self.preamble.version,\n        ];\n\n        // set body length\n        message.extend_from_slice(&(self.body.len() as u32).to_le_bytes());\n\n        // set id\n        message.extend_from_slice(&self.serial.to_le_bytes());\n\n        let serialized_headers = serialize_headers(&self.headers);\n\n        // header length -  to be calculated without padding\n        message.extend_from_slice(&(serialized_headers.len() as u32).to_le_bytes());\n        // actual headers\n        message.extend_from_slice(&serialized_headers);\n\n        // padding to 8 byte boundary\n        adjust_padding(&mut message, 8);\n\n        // body\n        message.append(&mut self.body);\n\n        // no padding after body\n\n        message\n    }\n\n    /// deserialize a single message from given buffer, assumed to start from given counter value\n    pub fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        let endian = Endian::from_byte(buf[*counter]);\n\n        if !matches!(endian, Endian::Little) {\n            return Err(\n                DbusError::IncompleteImplementation(\"big endian not supported\".into()).into(),\n            );\n        }\n\n        let mtype = match buf[*counter + 1] {\n            1 => MessageType::MethodCall,\n            2 => MessageType::MethodReturn,\n            3 => MessageType::Error,\n            4 => MessageType::Signal,\n            v => {\n                return Err(\n                    DbusError::DeserializationError(format!(\"invalid message type {}\", v)).into(),\n                );\n            }\n        };\n\n        let _flags = buf[*counter + 2]; // we basically ignore flags\n        let version = buf[*counter + 3];\n\n        if version != 1 {\n            return Err(DbusError::IncompleteImplementation(\n                \"only dbus protocol v1 is supported\".into(),\n            )\n            .into());\n        }\n\n        *counter += 4; // account for preamble bytes\n\n        let preamble = Preamble::new(mtype);\n\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete response : partial body length\".into(),\n            )\n            .into());\n        }\n        let body_length =\n            u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap()) as usize;\n        *counter += 4;\n\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete response : partial header serial\".into(),\n            )\n            .into());\n        }\n\n        let serial = u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap());\n        *counter += 4;\n\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete response : partial header header array length\".into(),\n            )\n            .into());\n        }\n        let header_array_length =\n            u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap()) as usize;\n        *counter += 4;\n\n        if buf.len() < *counter + header_array_length {\n            return Err(DbusError::DeserializationError(\n                \"incomplete response : partial header array\".into(),\n            )\n            .into());\n        }\n        let headers = deserialize_headers(&buf[*counter..*counter + header_array_length])?;\n        *counter += header_array_length;\n        align_counter(counter, 8);\n\n        if buf.len() < *counter + body_length {\n            return Err(DbusError::DeserializationError(\n                \"incomplete response : partial body value\".into(),\n            )\n            .into());\n        }\n\n        // we do not deserialize body here, and instead let the caller do it as needed\n        // that way we don't have do deal with checking if the message sent id error or validating the body signature etc\n        let body = Vec::from(&buf[*counter..*counter + body_length]);\n        *counter += body_length;\n\n        Ok(Self {\n            preamble,\n            serial,\n            headers,\n            body,\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::super::serialize::DbusSerialize;\n    use super::{Header, HeaderKind, HeaderValue, Message, MessageType, Result};\n    use crate::systemd::dbus_native::serialize::{Structure, Variant};\n    // The hardcoded serialized values are captured from\n    // original dbus library communication\n    // and manually decoded.\n    // see https://github.com/YJDoc2/dbus_native/tree/2d0dbc78d067c508ccc96343673b122f0a0cb48a/raw-decoded\n    #[test]\n    fn test_method_call_deserialize() -> Result<()> {\n        let serialized = b\"l\\x01\\x00\\x019\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\xa0\\x00\\x00\\x00\\x01\\x01o\\x00\\x19\\x00\\x00\\x00/org/freedesktop/systemd1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x01s\\x00\\x03\\x00\\x00\\x00Get\\x00\\x00\\x00\\x00\\x00\\x07\\x01s\\x00\\x07\\x00\\x00\\x00:1.1309\\x00\\x06\\x01s\\x00\\x18\\x00\\x00\\x00org.freedesktop.systemd1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x01s\\x00\\x1f\\x00\\x00\\x00org.freedesktop.DBus.Properties\\x00\\x08\\x01g\\x00\\x02ss\\x00 \\x00\\x00\\x00org.freedesktop.systemd1.Manager\\x00\\x00\\x00\\x00\\x0c\\x00\\x00\\x00ControlGroup\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::MethodCall);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::Path,\n                value: HeaderValue::String(\"/org/freedesktop/systemd1\".into()),\n            },\n            Header {\n                kind: HeaderKind::Member,\n                value: HeaderValue::String(\"Get\".into()),\n            },\n            Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(\":1.1309\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\"org.freedesktop.systemd1\".into()),\n            },\n            Header {\n                kind: HeaderKind::Interface,\n                value: HeaderValue::String(\"org.freedesktop.DBus.Properties\".into()),\n            },\n            Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(\"ss\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        let mut counter = 0;\n        let body = <(String, String)>::deserialize(&res.body, &mut counter)?;\n        assert_eq!(body.0, \"org.freedesktop.systemd1.Manager\");\n        assert_eq!(body.1, \"ControlGroup\");\n\n        let mut body = vec![];\n        (\n            \"org.freedesktop.systemd1.Manager\".to_string(),\n            \"ControlGroup\".to_string(),\n        )\n            .serialize(&mut body);\n\n        let msg = Message::new(MessageType::MethodCall, 2, expected_headers, body);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_method_reply_deserialize() -> Result<()> {\n        let serialized = b\"l\\x02\\x00\\x01\\x0c\\x00\\x00\\x00\\xff\\xff\\xff\\xff?\\x00\\x00\\x00\\x05\\x01u\\x00\\x01\\x00\\x00\\x00\\x07\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x06\\x01s\\x00\\x07\\x00\\x00\\x00:1.2072\\x00\\x08\\x01g\\x00\\x01s\\x00\\x00\\x07\\x00\\x00\\x00:1.2072\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::MethodReturn);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::ReplySerial,\n                value: HeaderValue::U32(1),\n            },\n            Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\":1.2072\".into()),\n            },\n            Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(\"s\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        let mut counter = 0;\n        let body = String::deserialize(&res.body, &mut counter)?;\n        assert_eq!(body, \":1.2072\");\n\n        let mut body = vec![];\n        String::from(\":1.2072\").serialize(&mut body);\n\n        let msg = Message::new(MessageType::MethodReturn, u32::MAX, expected_headers, body);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n\n    // we don't support signals, but just checking if serialize-deserialize works\n    #[test]\n    fn test_signal_deserialize() -> Result<()> {\n        let serialized = b\"l\\x04\\x00\\x01\\x0c\\x00\\x00\\x00\\xff\\xff\\xff\\xff\\x8f\\x00\\x00\\x00\\x07\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x06\\x01s\\x00\\x07\\x00\\x00\\x00:1.2072\\x00\\x01\\x01o\\x00\\x15\\x00\\x00\\x00/org/freedesktop/DBus\\x00\\x00\\x00\\x02\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x03\\x01s\\x00\\x0c\\x00\\x00\\x00NameAcquired\\x00\\x00\\x00\\x00\\x08\\x01g\\x00\\x01s\\x00\\x00\\x07\\x00\\x00\\x00:1.2072\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::Signal);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\":1.2072\".into()),\n            },\n            Header {\n                kind: HeaderKind::Path,\n                value: HeaderValue::String(\"/org/freedesktop/DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Interface,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Member,\n                value: HeaderValue::String(\"NameAcquired\".into()),\n            },\n            Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(\"s\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        let mut counter = 0;\n        let body = String::deserialize(&res.body, &mut counter)?;\n        assert_eq!(body, \":1.2072\");\n\n        let mut body = vec![];\n        String::from(\":1.2072\").serialize(&mut body);\n\n        let msg = Message::new(MessageType::Signal, u32::MAX, expected_headers, body);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_no_body_deserialize() -> Result<()> {\n        let serialized = b\"l\\x01\\x00\\x01\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00n\\x00\\x00\\x00\\x01\\x01o\\x00\\x15\\x00\\x00\\x00/org/freedesktop/DBus\\x00\\x00\\x00\\x06\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x02\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x03\\x01s\\x00\\x05\\x00\\x00\\x00Hello\\x00\\x00\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::MethodCall);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::Path,\n                value: HeaderValue::String(\"/org/freedesktop/DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Interface,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::Member,\n                value: HeaderValue::String(\"Hello\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        assert!(res.body.is_empty());\n\n        let msg = Message::new(MessageType::MethodCall, 1, expected_headers, vec![]);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_error_message_deserialize() -> Result<()> {\n        let serialized = b\"l\\x03\\x00\\x01\\x16\\x00\\x00\\x00\\xff\\xff\\xff\\xffw\\x00\\x00\\x00\\x05\\x01u\\x00\\x03\\x00\\x00\\x00\\x07\\x01s\\x00\\x14\\x00\\x00\\x00org.freedesktop.DBus\\x00\\x00\\x00\\x00\\x04\\x01s\\x00+\\x00\\x00\\x00org.freedesktop.DBus.Error.UnknownInterface\\x00\\x00\\x00\\x00\\x00\\x08\\x01g\\x00\\x01s\\x00\\x00\\x06\\x01s\\x00\\x06\\x00\\x00\\x00:1.868\\x00\\x00\\x11\\x00\\x00\\x00Invalid interface\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::Error);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::ReplySerial,\n                value: HeaderValue::U32(3),\n            },\n            Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(\"org.freedesktop.DBus\".into()),\n            },\n            Header {\n                kind: HeaderKind::ErrorName,\n                value: HeaderValue::String(\"org.freedesktop.DBus.Error.UnknownInterface\".into()),\n            },\n            Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(\"s\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\":1.868\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        let mut counter = 0;\n        let body = String::deserialize(&res.body, &mut counter)?;\n        assert_eq!(body, \"Invalid interface\");\n\n        let mut body = vec![];\n        String::from(\"Invalid interface\").serialize(&mut body);\n\n        let msg = Message::new(MessageType::Error, u32::MAX, expected_headers, body);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_vector_payload_deserialize() -> Result<()> {\n        let serialized = b\"l\\x01\\x00\\x01\\xc8\\x01\\x00\\x00\\x03\\x00\\x00\\x00\\xc6\\x00\\x00\\x00\\x01\\x01o\\x00\\x19\\x00\\x00\\x00\\x2forg\\x2ffreedesktop\\x2fsystemd1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x01s\\x00\\x12\\x00\\x00\\x00StartTransientUnit\\x00\\x00\\x00\\x00\\x00\\x00\\x07\\x01s\\x00\\x07\\x00\\x00\\x00\\x3a1\\x2e1021\\x00\\x06\\x01s\\x00\\x18\\x00\\x00\\x00org\\x2efreedesktop\\x2esystemd1\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x02\\x01s\\x00\\x20\\x00\\x00\\x00org\\x2efreedesktop\\x2esystemd1\\x2eManager\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08\\x01g\\x00\\x10ssa\\x28sv\\x29a\\x28sa\\x28sv\\x29\\x29\\x00\\x00\\x00M\\x00\\x00\\x00libpod\\x2d57f5869eaf80cee986095eebd1e0fbbbd148527f67d94f1fbe958f5bab8112f7\\x2escope\\x00\\x00\\x00\\x07\\x00\\x00\\x00replace\\x00X\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x0b\\x00\\x00\\x00Description\\x00\\x01s\\x00\\x00P\\x00\\x00\\x00youki\\x20container\\x2057f5869eaf80cee986095eebd1e0fbbbd148527f67d94f1fbe958f5bab8112f7\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x05\\x00\\x00\\x00Slice\\x00\\x01s\\x00\\x00\\x00\\x00\\x0a\\x00\\x00\\x00user\\x2eslice\\x00\\x00\\x08\\x00\\x00\\x00Delegate\\x00\\x01b\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00\\x00\\x00MemoryAccounting\\x00\\x01b\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0d\\x00\\x00\\x00CPUAccounting\\x00\\x01b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x0c\\x00\\x00\\x00IOAccounting\\x00\\x01b\\x00\\x01\\x00\\x00\\x00\\x0f\\x00\\x00\\x00TasksAccounting\\x00\\x01b\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x13\\x00\\x00\\x00DefaultDependencies\\x00\\x01b\\x00\\x00\\x00\\x00\\x00\\x00\\x04\\x00\\x00\\x00PIDs\\x00\\x02au\\x00\\x00\\x00\\x00\\x04\\x00\\x00\\x007g\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\";\n\n        let mut counter = 0;\n\n        let res = Message::deserialize(serialized, &mut counter)?;\n        assert_eq!(res.preamble.mtype, MessageType::MethodCall);\n\n        let expected_headers = vec![\n            Header {\n                kind: HeaderKind::Path,\n                value: HeaderValue::String(\"/org/freedesktop/systemd1\".into()),\n            },\n            Header {\n                kind: HeaderKind::Member,\n                value: HeaderValue::String(\"StartTransientUnit\".into()),\n            },\n            Header {\n                kind: HeaderKind::Sender,\n                value: HeaderValue::String(\":1.1021\".into()),\n            },\n            Header {\n                kind: HeaderKind::Destination,\n                value: HeaderValue::String(\"org.freedesktop.systemd1\".into()),\n            },\n            Header {\n                kind: HeaderKind::Interface,\n                value: HeaderValue::String(\"org.freedesktop.systemd1.Manager\".into()),\n            },\n            Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(\"ssa(sv)a(sa(sv))\".into()),\n            },\n        ];\n        assert_eq!(res.headers, expected_headers);\n\n        let mut counter = 0;\n\n        let (name, mode, props, aux) = <(\n            String,\n            String,\n            Vec<Structure<Variant>>,\n            Vec<Structure<Vec<Structure<Variant>>>>,\n        )>::deserialize(&res.body, &mut counter)?;\n\n        let expected_name =\n            \"libpod-57f5869eaf80cee986095eebd1e0fbbbd148527f67d94f1fbe958f5bab8112f7.scope\"\n                .to_string();\n\n        assert_eq!(name, expected_name);\n\n        let expected_mode = \"replace\".to_string();\n        assert_eq!(mode, expected_mode);\n\n        let expected_aux = vec![];\n        assert_eq!(aux, expected_aux);\n\n        let expected_props = vec![Structure::new(\n            \"Description\".into(),\n            Variant::String(\n                \"youki container 57f5869eaf80cee986095eebd1e0fbbbd148527f67d94f1fbe958f5bab8112f7\"\n                    .into(),\n            ),\n        ),\n        Structure::new(\"Slice\".into(), Variant::String(\"user.slice\".into())),\n        Structure::new(\"Delegate\".into(), Variant::Bool(true)),\n        Structure::new(\"MemoryAccounting\".into(), Variant::Bool(true)),\n        Structure::new(\"CPUAccounting\".into(), Variant::Bool(true)),\n        Structure::new(\"IOAccounting\".into(), Variant::Bool(true)),\n        Structure::new(\"TasksAccounting\".into(), Variant::Bool(true)),\n        Structure::new(\"DefaultDependencies\".into(), Variant::Bool(false)),\n        Structure::new(\"PIDs\".into(),Variant::ArrayU32(vec![26423]))\n        ];\n\n        assert_eq!(props, expected_props);\n\n        let mut body = vec![];\n        (expected_name, expected_mode, expected_props, expected_aux).serialize(&mut body);\n\n        let msg = Message::new(MessageType::MethodCall, 3, expected_headers, body);\n        let actual_serialized = msg.serialize();\n\n        assert_eq!(\n            Vec::from_iter(serialized.iter().copied()),\n            actual_serialized\n        );\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/mod.rs",
    "content": "// see https://dbus.freedesktop.org/doc/dbus-specification.html and\n// https://dbus.freedesktop.org/doc/api/html/structDBusHeader.html\n\npub mod client;\npub mod dbus;\npub mod message;\npub mod proxy;\npub mod serialize;\npub mod utils;\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/proxy.rs",
    "content": "use super::dbus::DbusConnection;\nuse super::message::*;\nuse super::serialize::{DbusSerialize, Structure, Variant};\nuse super::utils::{DbusError, Result};\n\n/// Structure to conveniently communicate with\n/// given destination and path for method calls\npub struct Proxy<'conn> {\n    conn: &'conn DbusConnection,\n    dest: String,\n    path: String,\n}\n\n// helper method to check compatibility between\n// actual signature of received reply and expected signature\n// we have to do this, as we don't have dedicated type\n// for object path\nfn check_signature_compatibility(actual: &str, expected: &str) -> bool {\n    if actual == expected {\n        return true;\n    }\n    // we don't consider signature (g) here as :\n    // 1. length encoding is different than string, so cannot be deserialized by String::deserialize\n    // 2. currently we don't expect any method to return signature, so we can get away with this\n    if expected == \"s\" && matches!(actual, \"s\" | \"o\") {\n        return true;\n    }\n\n    false\n}\n\nimpl<'conn> Proxy<'conn> {\n    /// create a new proxy for given destination and path over given connection\n    pub fn new(conn: &'conn DbusConnection, dest: &str, path: &str) -> Self {\n        Self {\n            conn,\n            dest: dest.into(),\n            path: path.into(),\n        }\n    }\n\n    /// Do a method call for given interface and member by sending given body\n    /// If no body is to be sent, set it as `None`\n    pub fn method_call<Body: DbusSerialize, Output: DbusSerialize>(\n        &self,\n        interface: &str,\n        member: &str,\n        body: Option<Body>,\n    ) -> Result<Output> {\n        tracing::trace!(\"dbus call at interface {} member {}\", interface, member);\n        let mut headers = Vec::with_capacity(4);\n\n        // create necessary headers\n        headers.push(Header {\n            kind: HeaderKind::Path,\n            value: HeaderValue::String(self.path.clone()),\n        });\n        headers.push(Header {\n            kind: HeaderKind::Destination,\n            value: HeaderValue::String(self.dest.clone()),\n        });\n        headers.push(Header {\n            kind: HeaderKind::Interface,\n            value: HeaderValue::String(interface.to_string()),\n        });\n        headers.push(Header {\n            kind: HeaderKind::Member,\n            value: HeaderValue::String(member.to_string()),\n        });\n\n        let mut serialized_body = vec![];\n\n        // if there is some body, serialize it, and set the\n        // body signature header accordingly\n        if let Some(v) = body {\n            headers.push(Header {\n                kind: HeaderKind::BodySignature,\n                value: HeaderValue::String(Body::get_signature()),\n            });\n            v.serialize(&mut serialized_body);\n        }\n\n        // send the message and get response\n        let reply_messages =\n            self.conn\n                .send_message(MessageType::MethodCall, headers, serialized_body)?;\n\n        // check if there is any error message\n        let error_message: Vec<_> = reply_messages\n            .iter()\n            .filter(|m| m.preamble.mtype == MessageType::Error)\n            .collect();\n\n        // if any error, return error\n        if !error_message.is_empty() {\n            let msg = error_message[0];\n            if msg.body.is_empty() {\n                // this should rarely be the case\n                return Err(DbusError::MethodCallErr(\"Unknown Dbus Error\".into()).into());\n            } else {\n                // in error message, first item of the body (if present) is always a string\n                // indicating the error\n                let mut ctr = 0;\n                let msg = String::deserialize(&msg.body, &mut ctr)?;\n                return Err(DbusError::MethodCallErr(msg).into());\n            }\n        }\n\n        // we basically ignore all type of messages apart from method return\n        let reply: Vec<_> = reply_messages\n            .iter()\n            .filter(|m| m.preamble.mtype == MessageType::MethodReturn)\n            .collect();\n\n        // we are only going to consider first reply, cause... so.\n        // realistically there should only be at most one method return type of message\n        // for a method call\n        let reply = reply.first().ok_or(DbusError::MethodCallErr(format!(\n            \"expected to get a reply for method call, got {:?} instead\",\n            reply_messages\n        )))?;\n\n        let headers = &reply.headers;\n        let expected_signature = Output::get_signature();\n\n        // get the signature header\n        let signature_header: Vec<_> = headers\n            .iter()\n            .filter(|h| h.kind == HeaderKind::BodySignature)\n            .collect();\n\n        // This is also something that should never happen\n        // we just check this defensively\n        if signature_header.is_empty() && !reply.body.is_empty() {\n            return Err(DbusError::MethodCallErr(\n                \"Body non empty, but body signature header missing\".to_string(),\n            )\n            .into());\n        }\n\n        if expected_signature == *\"\" {\n            // This is for the case when there is no body, i.e. Output = ()\n            // we must do this as the signature header will be\n            // absent in that case, so instead we choose to\n            // parse and return early\n            // This is a bit hacky, but works\n            let mut ctr = 0;\n            return Output::deserialize(&[], &mut ctr);\n        }\n\n        let actual_signature = match &signature_header[0].value {\n            HeaderValue::String(s) => s,\n            _ => unreachable!(\"body signature header will always be string type\"),\n        };\n\n        // check that signature returned and type we are trying to deserialize\n        // match as expected\n        if !check_signature_compatibility(actual_signature, &expected_signature) {\n            return Err(DbusError::DeserializationError(format!(\n                \"reply signature mismatch : expected {}, found {} : \\n{:?}\",\n                expected_signature, actual_signature, reply.body\n            ))\n            .into());\n        }\n\n        let mut ctr = 0;\n        Output::deserialize(&reply.body, &mut ctr)\n    }\n\n    pub fn get_unit(&mut self, name: &str) -> Result<String> {\n        self.method_call(\n            \"org.freedesktop.systemd1.Manager\",\n            \"GetUnit\",\n            Some(name.to_string()),\n        )\n    }\n\n    // Note that because we do not listen to the jobRemoved signal\n    // similar to runc, it is possible that when this method returns\n    // the unit is not yet started, however, because we do expect a reply\n    // (according to message flags we send), it is possible that dbus only returns\n    // after unit is started. Need to investigate more\n    pub fn start_transient_unit(\n        &self,\n        name: &str,\n        mode: &str,\n        properties: Vec<Structure<Variant>>,\n        aux: Vec<Structure<Vec<Structure<Variant>>>>,\n    ) -> Result<String> {\n        self.method_call(\n            \"org.freedesktop.systemd1.Manager\",\n            \"StartTransientUnit\",\n            Some((name, mode, properties, aux)),\n        )\n    }\n\n    pub fn stop_unit(&self, name: &str, mode: &str) -> Result<String> {\n        self.method_call(\n            \"org.freedesktop.systemd1.Manager\",\n            \"StopUnit\",\n            Some((name, mode)),\n        )\n    }\n\n    pub fn set_unit_properties(\n        &self,\n        name: &str,\n        runtime: bool,\n        properties: Vec<Structure<Variant>>,\n    ) -> Result<()> {\n        self.method_call(\n            \"org.freedesktop.systemd1.Manager\",\n            \"SetUnitProperties\",\n            Some((name, runtime, properties)),\n        )\n    }\n\n    pub fn version(&self) -> Result<String> {\n        let t = self.method_call::<_, Variant>(\n            \"org.freedesktop.DBus.Properties\",\n            \"Get\",\n            Some((\"org.freedesktop.systemd1.Manager\", \"Version\")),\n        )?;\n        match t {\n            Variant::String(s) => Ok(s),\n            v => panic!(\"version expected string variant, got {:?} instead\", v),\n        }\n    }\n\n    pub fn control_group(&self) -> Result<String> {\n        let t = self.method_call::<_, Variant>(\n            \"org.freedesktop.DBus.Properties\",\n            \"Get\",\n            Some((\n                \"org.freedesktop.systemd1.Manager\".to_string(),\n                \"ControlGroup\".to_string(),\n            )),\n        )?;\n        match t {\n            Variant::String(s) => Ok(s),\n            v => panic!(\"control group expected string variant, got {:?} instead\", v),\n        }\n    }\n    pub fn attach_process(&self, name: &str, cgroup: &str, pid: u32) -> Result<()> {\n        self.method_call::<_, ()>(\n            \"org.freedesktop.systemd1.Manager\",\n            \"AttachProcessesToUnit\",\n            Some((name, cgroup, vec![pid])),\n        )\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/serialize.rs",
    "content": "use super::utils::{DbusError, Result, adjust_padding, align_counter};\n\n/// This indicates that given type can be serialized as dbus\n/// message body, and has methods needed for that\npub trait DbusSerialize: std::fmt::Debug {\n    /// Provide signature for the given type in the dbus signature format\n    fn get_signature() -> String\n    where\n        Self: Sized;\n\n    fn get_alignment() -> usize;\n    /// Serialize the given type into given buffer\n    /// This needs to adjust padding before starting serialization, but must not\n    /// pad after last byte of serialized value\n    fn serialize(&self, buf: &mut Vec<u8>);\n    /// Deserialize the type from given buffer\n    /// The trait implementation must adjust the counter to required padding boundary\n    /// before starting deserialization, as the counter can be unaligned for the given type.\n    /// The caller must have verified that the buffer actually\n    /// contains the given type's value, so this method does not need to do that.\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self>\n    where\n        Self: Sized;\n}\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum Variant {\n    // D-Bus data type\n    // s\n    String(String),\n    // b\n    Bool(bool),\n    // t\n    U64(u64),\n    // au\n    ArrayU32(Vec<u32>),\n    // at\n    ArrayU64(Vec<u64>),\n    // a(st)\n    ArrayStructU64(Vec<Structure<u64>>),\n}\n\n#[derive(Debug, PartialEq, Eq, Clone)]\npub struct Structure<T: DbusSerialize> {\n    key: String,\n    val: T,\n}\n\nimpl<T: DbusSerialize> Structure<T> {\n    pub fn new(key: String, val: T) -> Self {\n        Self { key, val }\n    }\n}\n\nimpl DbusSerialize for () {\n    fn get_signature() -> String {\n        String::new()\n    }\n    fn get_alignment() -> usize {\n        1\n    }\n    fn serialize(&self, _: &mut Vec<u8>) {}\n    // for (), we have to ignore body , so we simply clear it out\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        *counter = buf.len();\n        Ok(())\n    }\n}\n\nimpl<T1: DbusSerialize, T2: DbusSerialize> DbusSerialize for (T1, T2) {\n    fn get_signature() -> String {\n        format!(\"{}{}\", T1::get_signature(), T2::get_signature())\n    }\n    fn get_alignment() -> usize {\n        T1::get_alignment()\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        self.0.serialize(buf);\n        self.1.serialize(buf);\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        let t1 = T1::deserialize(buf, counter)?;\n        let t2 = T2::deserialize(buf, counter)?;\n        Ok((t1, t2))\n    }\n}\n\nimpl<T1: DbusSerialize, T2: DbusSerialize, T3: DbusSerialize, T4: DbusSerialize> DbusSerialize\n    for (T1, T2, T3, T4)\n{\n    fn get_signature() -> String {\n        format!(\n            \"{}{}{}{}\",\n            T1::get_signature(),\n            T2::get_signature(),\n            T3::get_signature(),\n            T4::get_signature()\n        )\n    }\n    fn get_alignment() -> usize {\n        T1::get_alignment()\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        self.0.serialize(buf);\n        self.1.serialize(buf);\n        self.2.serialize(buf);\n        self.3.serialize(buf);\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        let t1 = T1::deserialize(buf, counter)?;\n        let t2 = T2::deserialize(buf, counter)?;\n        let t3 = T3::deserialize(buf, counter)?;\n        let t4 = T4::deserialize(buf, counter)?;\n        Ok((t1, t2, t3, t4))\n    }\n}\n\nimpl<T1: DbusSerialize, T2: DbusSerialize, T3: DbusSerialize> DbusSerialize for (T1, T2, T3) {\n    fn get_signature() -> String {\n        format!(\n            \"{}{}{}\",\n            T1::get_signature(),\n            T2::get_signature(),\n            T3::get_signature(),\n        )\n    }\n    fn get_alignment() -> usize {\n        T1::get_alignment()\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        self.0.serialize(buf);\n        self.1.serialize(buf);\n        self.2.serialize(buf);\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        let t1 = T1::deserialize(buf, counter)?;\n        let t2 = T2::deserialize(buf, counter)?;\n        let t3 = T3::deserialize(buf, counter)?;\n        Ok((t1, t2, t3))\n    }\n}\n\nimpl DbusSerialize for &str {\n    fn get_signature() -> String {\n        \"s\".to_string()\n    }\n    fn get_alignment() -> usize {\n        String::get_alignment()\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 4);\n        let length = self.len() as u32;\n        buf.extend_from_slice(&length.to_le_bytes());\n\n        buf.extend_from_slice(self.as_bytes());\n        buf.push(0); // needs to be null terminated\n    }\n    fn deserialize(_: &[u8], _: &mut usize) -> Result<Self> {\n        Err(\n            DbusError::IncompleteImplementation(\"&str does not support deserialization\".into())\n                .into(),\n        )\n    }\n}\n\nimpl DbusSerialize for String {\n    fn get_signature() -> String {\n        \"s\".to_string()\n    }\n    fn get_alignment() -> usize {\n        4 // string length is u32\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 4);\n        let length = self.len() as u32;\n        buf.extend_from_slice(&length.to_le_bytes());\n\n        buf.extend_from_slice(self.as_bytes());\n        buf.push(0); // needs to be null terminated\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 4);\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete string response : missing length\".into(),\n            )\n            .into());\n        }\n        let length = u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap()) as usize;\n        *counter += 4;\n        if buf.len() < *counter + length {\n            return Err(DbusError::DeserializationError(\n                \"incomplete string response : missing partial string\".into(),\n            )\n            .into());\n        }\n        let ret = String::from_utf8((&buf[*counter..*counter + length]).into()).unwrap();\n        *counter += length + 1; // +1 accounting for null\n        Ok(ret)\n    }\n}\n\nimpl DbusSerialize for bool {\n    fn get_signature() -> String {\n        \"b\".to_string()\n    }\n    fn get_alignment() -> usize {\n        4\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 4);\n        let val: u32 = match self {\n            true => 1,\n            false => 0,\n        };\n        buf.extend_from_slice(&val.to_le_bytes());\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 4);\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete bool response : partial response\".into(),\n            )\n            .into());\n        }\n        let ret = u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap());\n        *counter += 4;\n        Ok(ret != 0)\n    }\n}\n\nimpl DbusSerialize for u8 {\n    fn get_signature() -> String {\n        \"y\".to_string()\n    }\n    fn get_alignment() -> usize {\n        1\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 1);\n        buf.extend_from_slice(&self.to_le_bytes());\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 1);\n        if buf.len() < *counter + 1 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete u8 response : partial response\".into(),\n            )\n            .into());\n        }\n        let ret = u8::from_le_bytes(buf[*counter..*counter + 1].try_into().unwrap());\n        *counter += 1;\n        Ok(ret)\n    }\n}\n\nimpl DbusSerialize for u16 {\n    fn get_signature() -> String {\n        \"q\".to_string()\n    }\n    fn get_alignment() -> usize {\n        2\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 2);\n        buf.extend_from_slice(&self.to_le_bytes());\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 2);\n        if buf.len() < *counter + 2 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete u16 response : partial response\".into(),\n            )\n            .into());\n        }\n        let ret = u16::from_le_bytes(buf[*counter..*counter + 2].try_into().unwrap());\n        *counter += 2;\n        Ok(ret)\n    }\n}\n\nimpl DbusSerialize for u32 {\n    fn get_signature() -> String {\n        \"u\".to_string()\n    }\n    fn get_alignment() -> usize {\n        4\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 4);\n        buf.extend_from_slice(&self.to_le_bytes());\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 4);\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete u32 response : partial response\".into(),\n            )\n            .into());\n        }\n        let ret = u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap());\n        *counter += 4;\n        Ok(ret)\n    }\n}\n\nimpl DbusSerialize for u64 {\n    fn get_signature() -> String {\n        \"t\".to_string()\n    }\n    fn get_alignment() -> usize {\n        8\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 8);\n        buf.extend_from_slice(&self.to_le_bytes());\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 8);\n        if buf.len() < *counter + 8 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete u64 response : partial response\".into(),\n            )\n            .into());\n        }\n        let ret = u64::from_le_bytes(buf[*counter..*counter + 8].try_into().unwrap());\n        *counter += 8;\n        Ok(ret)\n    }\n}\n\nimpl<T: DbusSerialize> DbusSerialize for Vec<T> {\n    fn get_signature() -> String {\n        let sub_type = T::get_signature();\n        format!(\"a{}\", sub_type)\n    }\n    fn get_alignment() -> usize {\n        4 // for the length u32\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 4);\n\n        let mut temp_buf = Vec::new();\n        for elem in self.iter() {\n            elem.serialize(&mut temp_buf);\n        }\n        let len = temp_buf.len() as u32;\n        buf.extend_from_slice(&len.to_le_bytes());\n        let align = T::get_alignment();\n        adjust_padding(buf, align);\n        buf.extend_from_slice(&temp_buf);\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 4);\n\n        if buf.len() < *counter + 4 {\n            return Err(DbusError::DeserializationError(\n                \"incomplete array response : partial length\".into(),\n            )\n            .into());\n        }\n\n        let length_in_bytes =\n            u32::from_le_bytes(buf[*counter..*counter + 4].try_into().unwrap()) as usize;\n        *counter += 4;\n\n        let end = *counter + length_in_bytes;\n\n        if buf.len() < end {\n            return Err(DbusError::DeserializationError(\n                \"incomplete array response : partial elements\".into(),\n            )\n            .into());\n        }\n\n        let mut ret = Vec::new();\n\n        while *counter < end {\n            let elem = T::deserialize(buf, counter)?;\n            ret.push(elem);\n        }\n        Ok(ret)\n    }\n}\n\nimpl<T: DbusSerialize> DbusSerialize for Structure<T> {\n    fn get_signature() -> String {\n        let val_sign = T::get_signature();\n        format!(\"(s{})\", val_sign)\n    }\n    fn get_alignment() -> usize {\n        8\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        adjust_padding(buf, 8);\n        self.key.serialize(buf);\n        self.val.serialize(buf);\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 8);\n        let key = String::deserialize(buf, counter)?;\n        let val = T::deserialize(buf, counter)?;\n        Ok(Self { key, val })\n    }\n}\n\nimpl DbusSerialize for Variant {\n    fn get_signature() -> String {\n        \"v\".to_string()\n    }\n    fn get_alignment() -> usize {\n        1 // the signature comes first, which is 1 aligned\n    }\n    fn serialize(&self, buf: &mut Vec<u8>) {\n        // no alignment needed, as variant is 1-align\n        match self {\n            Self::String(s) => {\n                let sub_type = String::get_signature();\n                let signature_length = sub_type.len() as u8; // signature length must be < 256\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                s.serialize(buf);\n            }\n            Self::ArrayU32(v) => {\n                let sub_type = <Vec<u32>>::get_signature();\n                let signature_length = sub_type.len() as u8; // signature length must be < 256\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                v.serialize(buf);\n            }\n            Self::ArrayU64(v) => {\n                let sub_type = <Vec<u64>>::get_signature();\n                let signature_length = sub_type.len() as u8; // signature length must be < 256\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                v.serialize(buf);\n            }\n            Self::Bool(b) => {\n                let sub_type = bool::get_signature();\n                let signature_length = sub_type.len() as u8; // signature length must be < 256\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                b.serialize(buf);\n            }\n            Self::U64(v) => {\n                let sub_type = u64::get_signature();\n                let signature_length = sub_type.len() as u8; // signature length must be < 256\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                v.serialize(buf);\n            }\n            Self::ArrayStructU64(s) => {\n                let sub_type = <Vec<Structure<u64>>>::get_signature();\n                let signature_length = sub_type.len() as u8;\n                buf.push(signature_length);\n                buf.extend_from_slice(sub_type.as_bytes());\n                buf.push(0);\n                s.serialize(buf);\n            }\n        }\n    }\n    fn deserialize(buf: &[u8], counter: &mut usize) -> Result<Self> {\n        align_counter(counter, 1);\n\n        let signature_length = buf[*counter] as usize;\n        *counter += 1;\n\n        if buf.len() < *counter + signature_length {\n            return Err(DbusError::DeserializationError(\n                \"incomplete variant response : partial signature\".into(),\n            )\n            .into());\n        }\n        let signature =\n            String::from_utf8(buf[*counter..*counter + signature_length].into()).unwrap();\n\n        *counter += signature_length + 1; // +1 for null byte\n\n        let string_signature = String::get_signature();\n        let bool_signature = bool::get_signature();\n        let vec32_signature = <Vec<u32>>::get_signature();\n        let vec64_signature = <Vec<u64>>::get_signature();\n        let u64_signature = u64::get_signature();\n        let vec_struct_u64_signature = <Vec<Structure<u64>>>::get_signature();\n        if signature == string_signature {\n            Ok(Self::String(String::deserialize(buf, counter)?))\n        } else if signature == bool_signature {\n            Ok(Self::Bool(bool::deserialize(buf, counter)?))\n        } else if signature == vec32_signature {\n            Ok(Self::ArrayU32(<Vec<u32>>::deserialize(buf, counter)?))\n        } else if signature == vec64_signature {\n            Ok(Self::ArrayU64(<Vec<u64>>::deserialize(buf, counter)?))\n        } else if signature == u64_signature {\n            Ok(Self::U64(u64::deserialize(buf, counter)?))\n        } else if signature == vec_struct_u64_signature {\n            Ok(Self::ArrayStructU64(<Vec<Structure<u64>>>::deserialize(\n                buf, counter,\n            )?))\n        } else {\n            Err(DbusError::IncompleteImplementation(format!(\n                \"unsupported value signature {}\",\n                signature\n            ))\n            .into())\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/dbus_native/utils.rs",
    "content": "use std::num::ParseIntError;\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdClientError {\n    #[error(\"dbus error: {0}\")]\n    DBus(#[from] DbusError),\n    #[error(\"failed to start transient unit {unit_name}, parent is {parent}: {err}\")]\n    FailedTransient {\n        err: Box<SystemdClientError>,\n        unit_name: String,\n        parent: String,\n    },\n    #[error(\"failed to stop unit {unit_name}: {err}\")]\n    FailedStop {\n        err: Box<SystemdClientError>,\n        unit_name: String,\n    },\n    #[error(\"failed to set properties for unit {unit_name}: {err}\")]\n    FailedProperties {\n        err: Box<SystemdClientError>,\n        unit_name: String,\n    },\n    #[error(\"could not parse systemd version: {0}\")]\n    SystemdVersion(ParseIntError),\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum DbusError {\n    #[error(\"dbus authentication error: {0}\")]\n    AuthenticationErr(String),\n    #[error(\"dbus implementation is incomplete: {0}\")]\n    IncompleteImplementation(String),\n    #[error(\"dbus incorrect message: {0}\")]\n    IncorrectMessage(String),\n    #[error(\"dbus connection error: {0}\")]\n    ConnectionError(String),\n    #[error(\"dbus deserialization error: {0}\")]\n    DeserializationError(String),\n    #[error(\"dbus function call error: {0}\")]\n    MethodCallErr(String),\n    #[error(\"dbus bus address error: {0}\")]\n    BusAddressError(String),\n    #[error(\"dbus busctl error\")]\n    BusctlError(String),\n    #[error(\"could not parse uid from busctl: {0}\")]\n    UidError(ParseIntError),\n}\n\npub type Result<T> = std::result::Result<T, SystemdClientError>;\n\nimpl From<nix::Error> for SystemdClientError {\n    fn from(err: nix::Error) -> SystemdClientError {\n        DbusError::ConnectionError(err.to_string()).into()\n    }\n}\n\n/// adjusts the padding in buffer to given alignment\n/// by appending 0 to the buffer\npub fn adjust_padding(buf: &mut Vec<u8>, align: usize) {\n    if align == 1 {\n        return; // no padding is required for 1-alignment\n    }\n    let len = buf.len();\n    let required_padding = (align - (len % align)) % align;\n    for _ in 0..required_padding {\n        buf.push(0);\n    }\n}\n\n/// aligns the counter to given alignment\npub fn align_counter(ctr: &mut usize, align: usize) {\n    if *ctr % align != 0 {\n        // adjust counter for align\n        *ctr += (align - (*ctr % align)) % align;\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    #[test]\n    fn test_adjust_padding() {\n        let mut buf = vec![];\n        // we need to specifically define this,\n        // assert_eq gives error with vec![]\n        let empty_buf: Vec<u8> = vec![];\n\n        // empty buffer is all aligned\n        adjust_padding(&mut buf, 1);\n        assert_eq!(buf.len(), 0);\n        assert_eq!(buf, empty_buf);\n        adjust_padding(&mut buf, 3);\n        assert_eq!(buf.len(), 0);\n        assert_eq!(buf, empty_buf);\n        adjust_padding(&mut buf, 8);\n        assert_eq!(buf.len(), 0);\n        assert_eq!(buf, empty_buf);\n\n        let mut buf = vec![1, 2, 3, 4];\n\n        // align 1 should never change buffer, as everything is 1 byte aligned\n        adjust_padding(&mut buf, 1);\n        assert_eq!(buf.len(), 4);\n        assert_eq!(buf, vec![1, 2, 3, 4]);\n\n        // 4 aligned buffer should not have any changes\n        adjust_padding(&mut buf, 4);\n        assert_eq!(buf.len(), 4);\n        assert_eq!(buf, vec![1, 2, 3, 4]);\n\n        adjust_padding(&mut buf, 3);\n        assert_eq!(buf.len(), 6);\n        assert_eq!(buf, vec![1, 2, 3, 4, 0, 0]);\n\n        let mut buf = vec![1, 2, 3, 4];\n        adjust_padding(&mut buf, 8);\n        assert_eq!(buf.len(), 8);\n        assert_eq!(buf, vec![1, 2, 3, 4, 0, 0, 0, 0]);\n\n        let mut buf = vec![1, 2, 3];\n        adjust_padding(&mut buf, 4);\n        assert_eq!(buf.len(), 4);\n        assert_eq!(buf, vec![1, 2, 3, 0]);\n    }\n\n    #[test]\n    fn test_align_counter() {\n        let mut ctr = 0;\n\n        // 0 counter is always aligned\n        align_counter(&mut ctr, 1);\n        assert_eq!(ctr, 0);\n        align_counter(&mut ctr, 2);\n        assert_eq!(ctr, 0);\n        align_counter(&mut ctr, 4);\n        assert_eq!(ctr, 0);\n\n        ctr = 3;\n        align_counter(&mut ctr, 2);\n        assert_eq!(ctr, 4);\n        ctr = 3;\n        align_counter(&mut ctr, 4);\n        assert_eq!(ctr, 4);\n        ctr = 3;\n        align_counter(&mut ctr, 8);\n        assert_eq!(ctr, 8);\n\n        ctr = 4;\n        align_counter(&mut ctr, 4);\n        assert_eq!(ctr, 4);\n\n        ctr = 4;\n        align_counter(&mut ctr, 2);\n        assert_eq!(ctr, 4);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/io.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::path::Path;\n\nuse oci_spec::runtime::{LinuxBlockIo, LinuxThrottleDevice};\n\nuse crate::systemd::controller::Controller;\nuse crate::systemd::dbus_native::serialize::{Structure, Variant};\npub struct Io {}\n\npub const IO_READ_BANDWIDTH_MAX: &str = \"IOReadBandwidthMax\";\npub const IO_WRITE_BANDWIDTH_MAX: &str = \"IOWriteBandwidthMax\";\npub const IO_READ_IOPS_MAX: &str = \"IOReadIOPSMax\";\npub const IO_WRITE_IOPS_MAX: &str = \"IOWriteIOPSMax\";\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdIoError {\n    #[error(\"File path for specified device with major:{0} , minor:{1} not found\")]\n    DeviceNotFound(i64, i64),\n}\nimpl Controller for Io {\n    type Error = SystemdIoError;\n    fn apply(\n        options: &crate::common::ControllerOpt,\n        _: u32,\n        properties: &mut HashMap<&str, super::dbus_native::serialize::Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(blkio) = options.resources.block_io() {\n            tracing::debug!(\"applying blkio resource restrictions\");\n            Self::apply(blkio, properties)?;\n        }\n        Ok(())\n    }\n}\nimpl Io {\n    fn apply(\n        blkio: &LinuxBlockIo,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdIoError> {\n        let mut apply_limits = |devices: &[LinuxThrottleDevice],\n                                key|\n         -> Result<(), SystemdIoError> {\n            let limits = devices\n                .iter()\n                .map(|d| {\n                    let rate = d.rate();\n                    let dev = match dev_path_from_major_minor(d.major(), d.minor()) {\n                        Some(path) => path,\n                        None => return Err(SystemdIoError::DeviceNotFound(d.major(), d.minor())),\n                    };\n                    Ok(Structure::new(dev, rate))\n                })\n                .collect::<Result<Vec<Structure<u64>>, SystemdIoError>>()?;\n            if !limits.is_empty() {\n                properties.insert(key, Variant::ArrayStructU64(limits));\n            }\n            Ok(())\n        };\n        if let Some(devices) = blkio.throttle_read_bps_device() {\n            apply_limits(devices, IO_READ_BANDWIDTH_MAX)?;\n        }\n\n        if let Some(devices) = blkio.throttle_write_bps_device() {\n            apply_limits(devices, IO_WRITE_BANDWIDTH_MAX)?;\n        }\n\n        if let Some(devices) = blkio.throttle_read_iops_device() {\n            apply_limits(devices, IO_READ_IOPS_MAX)?;\n        }\n\n        if let Some(devices) = blkio.throttle_write_iops_device() {\n            apply_limits(devices, IO_WRITE_IOPS_MAX)?;\n        }\n        Ok(())\n    }\n}\n\nfn dev_path_from_major_minor(major: i64, minor: i64) -> Option<String> {\n    // Try block devices first: /sys/dev/block/<major>:<minor> -> .../block/<name>[/<part>]\n    let block_path = format!(\"/sys/dev/block/{}:{}\", major, minor);\n    if let Ok(target) = fs::read_link(Path::new(&block_path)) {\n        if let Some(name) = target.file_name() {\n            return Some(format!(\"/dev/{}\", name.to_string_lossy()));\n        }\n    }\n\n    // Fallback to char devices if applicable: /sys/dev/char/<major>:<minor>\n    let char_path = format!(\"/sys/dev/char/{}:{}\", major, minor);\n    if let Ok(target) = fs::read_link(Path::new(&char_path)) {\n        if let Some(name) = target.file_name() {\n            return Some(format!(\"/dev/{}\", name.to_string_lossy()));\n        }\n    }\n\n    None\n}\n\n#[cfg(test)]\nmod tests {\n    use nix::sys::stat::{major, minor, stat};\n    use oci_spec::runtime::{LinuxBlockIoBuilder, LinuxThrottleDeviceBuilder};\n\n    use super::*;\n\n    #[test]\n    fn dev_path_from_char_device_null() {\n        let st = stat(\"/dev/null\").expect(\"stat /dev/null\");\n        let maj = major(st.st_rdev) as i64;\n        let min = minor(st.st_rdev) as i64;\n        let path = dev_path_from_major_minor(maj, min).expect(\"resolve char device path\");\n        assert_eq!(path, \"/dev/null\");\n    }\n\n    #[test]\n    fn apply_inserts_structs_for_positive_rates() {\n        let st = stat(\"/dev/null\").expect(\"stat /dev/null\");\n        let maj = major(st.st_rdev) as i64;\n        let min = minor(st.st_rdev) as i64;\n        // set random bytes\n        let read_bps = 111u64;\n        let write_bps = 222u64;\n        let read_iops = 333u64;\n        let write_iops = 444u64;\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(maj)\n                    .minor(min)\n                    .rate(read_bps)\n                    .build()\n                    .unwrap(),\n            ])\n            .throttle_write_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(maj)\n                    .minor(min)\n                    .rate(write_bps)\n                    .build()\n                    .unwrap(),\n            ])\n            .throttle_read_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(maj)\n                    .minor(min)\n                    .rate(read_iops)\n                    .build()\n                    .unwrap(),\n            ])\n            .throttle_write_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(maj)\n                    .minor(min)\n                    .rate(write_iops)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n        let mut props: HashMap<&str, Variant> = HashMap::new();\n        Io::apply(&blkio, &mut props).expect(\"apply blkio to props\");\n\n        assert_eq!(\n            props.get(IO_READ_BANDWIDTH_MAX),\n            Some(&Variant::ArrayStructU64(vec![Structure::new(\n                \"/dev/null\".into(),\n                read_bps\n            )]))\n        );\n        assert_eq!(\n            props.get(IO_WRITE_BANDWIDTH_MAX),\n            Some(&Variant::ArrayStructU64(vec![Structure::new(\n                \"/dev/null\".into(),\n                write_bps\n            )]))\n        );\n        assert_eq!(\n            props.get(IO_READ_IOPS_MAX),\n            Some(&Variant::ArrayStructU64(vec![Structure::new(\n                \"/dev/null\".into(),\n                read_iops\n            )]))\n        );\n        assert_eq!(\n            props.get(IO_WRITE_IOPS_MAX),\n            Some(&Variant::ArrayStructU64(vec![Structure::new(\n                \"/dev/null\".into(),\n                write_iops\n            )]))\n        );\n    }\n\n    #[test]\n    fn test_io_apply() {\n        let st = stat(\"/dev/null\").expect(\"stat /dev/null\");\n        let maj = major(st.st_rdev) as i64;\n        let min = minor(st.st_rdev) as i64;\n        let rate = 000u64;\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(maj)\n                    .minor(min)\n                    .rate(rate)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        let mut props: HashMap<&str, Variant> = HashMap::new();\n        Io::apply(&blkio, &mut props).expect(\"apply blkio with zero rate\");\n        assert_eq!(props.len(), 1);\n        assert!(props.contains_key(IO_READ_BANDWIDTH_MAX));\n        let dbus_struct = props.get(IO_READ_BANDWIDTH_MAX).unwrap();\n        assert!(matches!(dbus_struct, Variant::ArrayStructU64(_)))\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/manager.rs",
    "content": "use std::collections::HashMap;\nuse std::convert::Infallible;\nuse std::fmt::{Debug, Display};\nuse std::fs::{self};\nuse std::path::Component::RootDir;\nuse std::path::{Path, PathBuf};\nuse std::time::{Duration, Instant};\n\nuse nix::NixPath;\nuse nix::unistd::Pid;\n\nuse super::controller::Controller;\nuse super::controller_type::{CONTROLLER_TYPES, ControllerType};\nuse super::cpu::Cpu;\nuse super::cpuset::CpuSet;\nuse super::dbus_native::client::SystemdClient;\nuse super::dbus_native::dbus::DbusConnection;\nuse super::dbus_native::utils::SystemdClientError;\nuse super::memory::Memory;\nuse super::pids::Pids;\nuse crate::common::{\n    self, AnyCgroupManager, CgroupManager, ControllerOpt, FreezerState, JoinSafelyError,\n    PathBufExt, WrapIoResult, WrappedIoError,\n};\nuse crate::stats::Stats;\nuse crate::systemd::dbus_native::serialize::Variant;\nuse crate::systemd::io::Io;\nuse crate::systemd::unified::Unified;\nuse crate::v2::manager::{Manager as FsManager, V2ManagerError};\n\nconst CGROUP_CONTROLLERS: &str = \"cgroup.controllers\";\nconst CGROUP_SUBTREE_CONTROL: &str = \"cgroup.subtree_control\";\npub const PROCESS_IN_CGROUP_TIMEOUT_DURATION: Duration = Duration::from_secs(5);\n\npub struct Manager {\n    /// Root path of the cgroup hierarchy e.g. /sys/fs/cgroup\n    root_path: PathBuf,\n    /// Path relative to the root path e.g. /system.slice/youki-569d5ce3afe1074769f67.scope for rootfull containers\n    /// and e.g. /user.slice/user-1000/user@1000.service/youki-569d5ce3afe1074769f67.scope for rootless containers\n    cgroups_path: PathBuf,\n    /// Combination of root path and cgroups path\n    full_path: PathBuf,\n    /// Destructured cgroups path as specified in the runtime spec e.g. system.slice:youki:569d5ce3afe1074769f67\n    destructured_path: CgroupsPath,\n    /// Name of the container e.g. 569d5ce3afe1074769f67\n    container_name: String,\n    /// Name of the systemd unit e.g. youki-569d5ce3afe1074769f67.scope\n    unit_name: String,\n    /// Client for communicating with systemd\n    client: DbusConnection,\n    /// Cgroup manager for the created transient unit\n    fs_manager: FsManager,\n    /// Last control group which is managed by systemd, e.g. /user.slice/user-1000/user@1000.service\n    delegation_boundary: PathBuf,\n    /// Duration to wait for a specific PID to be added to a cgroup\n    cgroup_wait_timeout_duration: Duration,\n}\n\n/// Represents the systemd cgroups path:\n/// It should be of the form [slice]:[scope_prefix]:[name].\n/// The slice is the \"parent\" and should be expanded properly,\n/// see expand_slice below.\n#[derive(Debug)]\nstruct CgroupsPath {\n    parent: String,\n    prefix: String,\n    name: String,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum CgroupsPathError {\n    #[error(\"no cgroups path has been provided\")]\n    NoPath,\n    #[error(\"cgroups path does not contain valid utf8\")]\n    InvalidUtf8(PathBuf),\n    #[error(\"cgroups path is malformed: {0}\")]\n    MalformedPath(PathBuf),\n}\n\nimpl TryFrom<&Path> for CgroupsPath {\n    type Error = CgroupsPathError;\n\n    fn try_from(cgroups_path: &Path) -> Result<Self, Self::Error> {\n        // if cgroups_path was provided it should be of the form [slice]:[prefix]:[name],\n        // for example: \"system.slice:docker:1234\".\n        if cgroups_path.len() == 0 {\n            return Err(CgroupsPathError::NoPath);\n        }\n\n        let parts = cgroups_path\n            .to_str()\n            .ok_or_else(|| CgroupsPathError::InvalidUtf8(cgroups_path.to_path_buf()))?\n            .split(':')\n            .collect::<Vec<&str>>();\n\n        let destructured_path = match parts.len() {\n            2 => CgroupsPath {\n                parent: \"\".to_owned(),\n                prefix: parts[0].to_owned(),\n                name: parts[1].to_owned(),\n            },\n            3 => CgroupsPath {\n                parent: parts[0].to_owned(),\n                prefix: parts[1].to_owned(),\n                name: parts[2].to_owned(),\n            },\n            _ => return Err(CgroupsPathError::MalformedPath(cgroups_path.to_path_buf())),\n        };\n\n        Ok(destructured_path)\n    }\n}\n\nimpl Display for CgroupsPath {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}:{}:{}\", self.parent, self.prefix, self.name)\n    }\n}\n\n/// ensures that a parent unit for the current unit is specified\nfn ensure_parent_unit(cgroups_path: &mut CgroupsPath, use_system: bool) {\n    if cgroups_path.parent.is_empty() {\n        cgroups_path.parent = match use_system {\n            true => \"system.slice\".to_owned(),\n            false => \"user.slice\".to_owned(),\n        }\n    }\n}\n\n// custom debug impl as Manager contains fields that do not implement Debug\n// and therefore Debug cannot be derived\nimpl Debug for Manager {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"Manager\")\n            .field(\"root_path\", &self.root_path)\n            .field(\"cgroups_path\", &self.cgroups_path)\n            .field(\"full_path\", &self.full_path)\n            .field(\"destructured_path\", &self.destructured_path)\n            .field(\"container_name\", &self.container_name)\n            .field(\"unit_name\", &self.unit_name)\n            .finish()\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdManagerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"failed to destructure cgroups path: {0}\")]\n    CgroupsPath(#[from] CgroupsPathError),\n    #[error(\"invalid slice name: {0}\")]\n    InvalidSliceName(String),\n    #[error(transparent)]\n    SystemdClient(#[from] SystemdClientError),\n    #[error(\"failed to join safely: {0}\")]\n    JoinSafely(#[from] JoinSafelyError),\n    #[error(\"file not found: {0}\")]\n    FileNotFound(PathBuf),\n    #[error(\"bad delegation boundary {boundary} for cgroups path {cgroup}\")]\n    BadDelegationBoundary { boundary: PathBuf, cgroup: PathBuf },\n    #[error(\"in v2 manager: {0}\")]\n    V2Manager(#[from] V2ManagerError),\n\n    #[error(\"Timeout waiting for pid {0} to be added to cgroup\")]\n    WaitForProcessInCgroupTimeout(String),\n\n    #[error(\"in cpu controller: {0}\")]\n    Cpu(#[from] super::cpu::SystemdCpuError),\n    #[error(\"in cpuset controller: {0}\")]\n    CpuSet(#[from] super::cpuset::SystemdCpuSetError),\n    #[error(\"in io controller: {0}\")]\n    Io(#[from] super::io::SystemdIoError),\n    #[error(\"in memory controller: {0}\")]\n    Memory(#[from] super::memory::SystemdMemoryError),\n    #[error(\"in pids controller: {0}\")]\n    Pids(Infallible),\n    #[error(\"in pids unified controller: {0}\")]\n    Unified(#[from] super::unified::SystemdUnifiedError),\n}\n\nimpl Manager {\n    pub fn new(\n        root_path: PathBuf,\n        cgroups_path: PathBuf,\n        container_name: String,\n        use_system: bool,\n        cgroup_wait_timeout_duration: Duration,\n    ) -> Result<Self, SystemdManagerError> {\n        let mut destructured_path: CgroupsPath = cgroups_path.as_path().try_into()?;\n        ensure_parent_unit(&mut destructured_path, use_system);\n\n        let client = match use_system {\n            true => DbusConnection::new_system()?,\n            false => DbusConnection::new_session()?,\n        };\n\n        let (cgroups_path, delegation_boundary) =\n            Self::construct_cgroups_path(&destructured_path, &client)?;\n        let full_path = root_path.join_safely(&cgroups_path)?;\n        let fs_manager = FsManager::new(root_path.clone(), cgroups_path.clone())?;\n\n        Ok(Manager {\n            root_path,\n            cgroups_path,\n            full_path,\n            container_name,\n            unit_name: Self::get_unit_name(&destructured_path),\n            destructured_path,\n            client,\n            fs_manager,\n            delegation_boundary,\n            cgroup_wait_timeout_duration,\n        })\n    }\n\n    /// get_unit_name returns the unit (scope) name from the path provided by the user\n    /// for example: foo:docker:bar returns in '/docker-bar.scope'\n    fn get_unit_name(cgroups_path: &CgroupsPath) -> String {\n        // By default we create a scope unless specified explicitly.\n        if !cgroups_path.name.ends_with(\".slice\") {\n            return format!(\"{}-{}.scope\", cgroups_path.prefix, cgroups_path.name);\n        }\n        cgroups_path.name.clone()\n    }\n\n    // get_cgroups_path generates a cgroups path from the one provided by the user via cgroupsPath.\n    // an example of the final path: \"/system.slice/youki-569d5ce3afe1074769f67.scope\" or if we are\n    // not running as root /user.slice/user-1000/user@1000.service/youki-569d5ce3afe1074769f67.scope\n    fn construct_cgroups_path(\n        cgroups_path: &CgroupsPath,\n        client: &dyn SystemdClient,\n    ) -> Result<(PathBuf, PathBuf), SystemdManagerError> {\n        // if the user provided a '.slice' (as in a branch of a tree)\n        // we need to convert it to a filesystem path.\n\n        let parent = Self::expand_slice(&cgroups_path.parent)?;\n        let systemd_root = client.control_cgroup_root()?;\n        let unit_name = Self::get_unit_name(cgroups_path);\n\n        let cgroups_path = systemd_root.join_safely(parent)?.join_safely(unit_name)?;\n        Ok((cgroups_path, systemd_root))\n    }\n\n    // systemd represents slice hierarchy using `-`, so we need to follow suit when\n    // generating the path of slice. For example, 'test-a-b.slice' becomes\n    // '/test.slice/test-a.slice/test-a-b.slice'.\n    fn expand_slice(slice: &str) -> Result<PathBuf, SystemdManagerError> {\n        let suffix = \".slice\";\n        if slice.len() <= suffix.len() || !slice.ends_with(suffix) {\n            return Err(SystemdManagerError::InvalidSliceName(slice.into()));\n        }\n        if slice.contains('/') {\n            return Err(SystemdManagerError::InvalidSliceName(slice.into()));\n        }\n        let mut path = \"\".to_owned();\n        let mut prefix = \"\".to_owned();\n        let slice_name = slice.trim_end_matches(suffix);\n        // if input was -.slice, we should just return root now\n        if slice_name == \"-\" {\n            return Ok(Path::new(\"/\").to_path_buf());\n        }\n        for component in slice_name.split('-') {\n            if component.is_empty() {\n                return Err(SystemdManagerError::InvalidSliceName(slice.into()));\n            }\n            // Append the component to the path and to the prefix.\n            path = format!(\"{path}/{prefix}{component}{suffix}\");\n            prefix = format!(\"{prefix}{component}-\");\n        }\n        Ok(Path::new(&path).to_path_buf())\n    }\n\n    /// ensures that each level in the downward path from the delegation boundary down to\n    /// the scope or slice of the transient unit has all available controllers enabled\n    fn ensure_controllers_attached(&self) -> Result<(), SystemdManagerError> {\n        let full_boundary_path = self.root_path.join_safely(&self.delegation_boundary)?;\n\n        let controllers: Vec<String> = self\n            .get_available_controllers(&full_boundary_path)?\n            .into_iter()\n            .map(|c| format!(\"{}{}\", \"+\", c))\n            .collect();\n\n        Self::write_controllers(&full_boundary_path, &controllers)?;\n\n        let mut current_path = full_boundary_path;\n        let mut components = self\n            .cgroups_path\n            .strip_prefix(&self.delegation_boundary)\n            .map_err(|_| SystemdManagerError::BadDelegationBoundary {\n                boundary: self.delegation_boundary.clone(),\n                cgroup: self.cgroups_path.clone(),\n            })?\n            .components()\n            .filter(|c| c.ne(&RootDir))\n            .peekable();\n        // Verify that *each level* in the downward path from the root cgroup\n        // down to the cgroup_path provided by the user is a valid cgroup hierarchy.\n        // containing the attached controllers.\n        while let Some(component) = components.next() {\n            current_path = current_path.join(component);\n            if !current_path.exists() {\n                tracing::warn!(\n                    \"{:?} does not exist. Resource restrictions might not work correctly\",\n                    current_path\n                );\n                return Ok(());\n            }\n\n            // last component cannot have subtree_control enabled due to internal process constraint\n            // if this were set, writing to the cgroups.procs file will fail with Erno 16 (device or resource busy)\n            if components.peek().is_some() {\n                Self::write_controllers(&current_path, &controllers)?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn wait_for_process_in_cgroup(&self, pid: Pid) -> Result<(), SystemdManagerError> {\n        let start = Instant::now();\n        while start.elapsed() < self.cgroup_wait_timeout_duration {\n            // If it fails, it most likely means that the cgroup hasn't been set up yet.\n            let result = self.fs_manager.get_all_pids();\n            if let Ok(pids) = result {\n                if pids.contains(&pid) {\n                    tracing::info!(\"Process {} successfully added to cgroup\", pid);\n                    return Ok(());\n                }\n            } else if let Err(e) = result {\n                if let V2ManagerError::WrappedIo(ref wrapped_io_error) = e {\n                    if !matches!(wrapped_io_error, WrappedIoError::Read { .. }) {\n                        return Err(e.into());\n                    }\n                } else {\n                    return Err(e.into());\n                }\n            }\n\n            std::thread::sleep(Duration::from_millis(20));\n        }\n        Err(SystemdManagerError::WaitForProcessInCgroupTimeout(\n            pid.to_string(),\n        ))\n    }\n\n    fn get_available_controllers<P: AsRef<Path>>(\n        &self,\n        cgroups_path: P,\n    ) -> Result<Vec<ControllerType>, SystemdManagerError> {\n        let controllers_path = self.root_path.join(cgroups_path).join(CGROUP_CONTROLLERS);\n        if !controllers_path.exists() {\n            return Err(SystemdManagerError::FileNotFound(controllers_path));\n        }\n\n        let mut controllers = Vec::new();\n        for controller in fs::read_to_string(&controllers_path)\n            .wrap_read(controllers_path)?\n            .split_whitespace()\n        {\n            match controller {\n                \"cpu\" => controllers.push(ControllerType::Cpu),\n                \"memory\" => controllers.push(ControllerType::Memory),\n                \"pids\" => controllers.push(ControllerType::Pids),\n                _ => continue,\n            }\n        }\n\n        Ok(controllers)\n    }\n\n    fn write_controllers(path: &Path, controllers: &[String]) -> Result<(), SystemdManagerError> {\n        for controller in controllers {\n            common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn any(self) -> AnyCgroupManager {\n        AnyCgroupManager::Systemd(Box::new(self))\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = SystemdManagerError;\n\n    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {\n        // Dont attach any pid to the cgroup if -1 is specified as a pid\n        if pid.as_raw() == -1 {\n            return Ok(());\n        }\n        if self.client.transient_unit_exists(&self.unit_name) {\n            tracing::debug!(\"Transient unit {:?} already exists\", self.unit_name);\n            self.client\n                .add_process_to_unit(&self.unit_name, \"\", pid.as_raw() as u32)?;\n            return Ok(());\n        }\n\n        tracing::debug!(\"Starting {:?}\", self.unit_name);\n        self.client.start_transient_unit(\n            &self.container_name,\n            pid.as_raw() as u32,\n            &self.destructured_path.parent,\n            &self.unit_name,\n        )?;\n\n        // There is a chance that the intermediate process ends before systemd gets the dbus message to add it to transit unit.\n        self.wait_for_process_in_cgroup(pid)?;\n\n        Ok(())\n    }\n\n    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {\n        let mut properties: HashMap<&str, Variant> = HashMap::new();\n        let systemd_version = self.client.systemd_version()?;\n\n        for controller in CONTROLLER_TYPES {\n            match controller {\n                ControllerType::Cpu => {\n                    Cpu::apply(controller_opt, systemd_version, &mut properties)?;\n                }\n\n                ControllerType::CpuSet => {\n                    CpuSet::apply(controller_opt, systemd_version, &mut properties)?;\n                }\n\n                ControllerType::Pids => {\n                    Pids::apply(controller_opt, systemd_version, &mut properties)\n                        .map_err(SystemdManagerError::Pids)?;\n                }\n                ControllerType::Memory => {\n                    Memory::apply(controller_opt, systemd_version, &mut properties)?;\n                }\n                ControllerType::Io => {\n                    Io::apply(controller_opt, systemd_version, &mut properties)?;\n                }\n            };\n        }\n\n        tracing::debug!(\"applying properties {:?}\", properties);\n        Unified::apply(controller_opt, systemd_version, &mut properties)?;\n\n        if !properties.is_empty() {\n            self.ensure_controllers_attached()?;\n            self.client\n                .set_unit_properties(&self.unit_name, &properties)?;\n        }\n\n        Ok(())\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        tracing::debug!(\"remove {}\", self.unit_name);\n        if self.client.transient_unit_exists(&self.unit_name) {\n            self.client.stop_transient_unit(&self.unit_name)?;\n        }\n\n        Ok(())\n    }\n\n    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {\n        Ok(self.fs_manager.freeze(state)?)\n    }\n\n    fn stats(&self) -> Result<Stats, Self::Error> {\n        Ok(self.fs_manager.stats()?)\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {\n        Ok(common::get_all_pids(&self.full_path)?)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n\n    use super::*;\n    use crate::common::DEFAULT_CGROUP_ROOT;\n    use crate::systemd::dbus_native::client::SystemdClient;\n    use crate::systemd::dbus_native::serialize::Variant;\n    use crate::systemd::dbus_native::utils::SystemdClientError;\n\n    struct TestSystemdClient {}\n\n    impl SystemdClient for TestSystemdClient {\n        fn is_system(&self) -> bool {\n            true\n        }\n\n        fn transient_unit_exists(&self, _: &str) -> bool {\n            true\n        }\n\n        fn start_transient_unit(\n            &self,\n            _container_name: &str,\n            _pid: u32,\n            _parent: &str,\n            _unit_name: &str,\n        ) -> Result<(), SystemdClientError> {\n            Ok(())\n        }\n\n        fn stop_transient_unit(&self, _unit_name: &str) -> Result<(), SystemdClientError> {\n            Ok(())\n        }\n\n        fn set_unit_properties(\n            &self,\n            _unit_name: &str,\n            _properties: &HashMap<&str, Variant>,\n        ) -> Result<(), SystemdClientError> {\n            Ok(())\n        }\n\n        fn systemd_version(&self) -> Result<u32, SystemdClientError> {\n            Ok(245)\n        }\n\n        fn control_cgroup_root(&self) -> Result<PathBuf, SystemdClientError> {\n            Ok(PathBuf::from(\"/\"))\n        }\n\n        fn add_process_to_unit(\n            &self,\n            _unit_name: &str,\n            _subcgroup: &str,\n            _pid: u32,\n        ) -> Result<(), SystemdClientError> {\n            Ok(())\n        }\n    }\n\n    #[test]\n    fn expand_slice_works() -> Result<()> {\n        assert_eq!(\n            Manager::expand_slice(\"test-a-b.slice\")?,\n            PathBuf::from(\"/test.slice/test-a.slice/test-a-b.slice\"),\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn get_cgroups_path_works_with_a_complex_slice() -> Result<()> {\n        let cgroups_path = Path::new(\"test-a-b.slice:docker:foo\")\n            .try_into()\n            .context(\"construct path\")?;\n\n        assert_eq!(\n            Manager::construct_cgroups_path(&cgroups_path, &TestSystemdClient {})?.0,\n            PathBuf::from(\"/test.slice/test-a.slice/test-a-b.slice/docker-foo.scope\"),\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn get_cgroups_path_works_with_a_simple_slice() -> Result<()> {\n        let cgroups_path = Path::new(\"machine.slice:libpod:foo\")\n            .try_into()\n            .context(\"construct path\")?;\n\n        assert_eq!(\n            Manager::construct_cgroups_path(&cgroups_path, &TestSystemdClient {})?.0,\n            PathBuf::from(\"/machine.slice/libpod-foo.scope\"),\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn get_cgroups_path_works_without_parent() -> Result<()> {\n        let mut cgroups_path = Path::new(\":docker:foo\")\n            .try_into()\n            .context(\"construct path\")?;\n        ensure_parent_unit(&mut cgroups_path, true);\n\n        assert_eq!(\n            Manager::construct_cgroups_path(&cgroups_path, &TestSystemdClient {})?.0,\n            PathBuf::from(\"/system.slice/docker-foo.scope\"),\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_task_addition() {\n        let manager = Manager::new(\n            DEFAULT_CGROUP_ROOT.into(),\n            \":youki:test\".into(),\n            \"youki_test_container\".into(),\n            false,\n            PROCESS_IN_CGROUP_TIMEOUT_DURATION,\n        )\n        .unwrap();\n        let mut p1 = std::process::Command::new(\"sleep\")\n            .arg(\"1s\")\n            .spawn()\n            .unwrap();\n        let p1_id = nix::unistd::Pid::from_raw(p1.id() as i32);\n        let mut p2 = std::process::Command::new(\"sleep\")\n            .arg(\"1s\")\n            .spawn()\n            .unwrap();\n        let p2_id = nix::unistd::Pid::from_raw(p2.id() as i32);\n        manager.add_task(p1_id).unwrap();\n        manager.add_task(p2_id).unwrap();\n        let all_pids = manager.get_all_pids().unwrap();\n        assert!(all_pids.contains(&p1_id));\n        assert!(all_pids.contains(&p2_id));\n        // wait till both processes are finished so we can cleanup the cgroup\n        let _ = p1.wait();\n        let _ = p2.wait();\n        manager.remove().unwrap();\n        // the remove call above should remove the dir, we just do this again\n        // for contingency, and thus ignore the result\n        let _ = fs::remove_dir(&manager.full_path);\n    }\n\n    #[test]\n    fn test_error_thrown_if_process_never_added_to_cgroup() -> Result<()> {\n        let manager = Manager::new(\n            DEFAULT_CGROUP_ROOT.into(),\n            \":youki:test\".into(),\n            \"youki_test_container\".into(),\n            false,\n            Duration::from_secs(1),\n        )\n        .unwrap();\n\n        // Bogus Pid\n        let p1_id = nix::unistd::Pid::from_raw(-1_i32);\n\n        let result = manager.wait_for_process_in_cgroup(p1_id);\n\n        assert!(matches!(\n            result,\n            Err(SystemdManagerError::WaitForProcessInCgroupTimeout(..))\n        ));\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/memory.rs",
    "content": "use std::collections::HashMap;\n\nuse oci_spec::runtime::LinuxMemory;\n\nuse super::controller::Controller;\nuse super::dbus_native::serialize::Variant;\nuse crate::common::ControllerOpt;\n\npub const MEMORY_MIN: &str = \"MemoryMin\";\npub const MEMORY_LOW: &str = \"MemoryLow\";\npub const MEMORY_HIGH: &str = \"MemoryHigh\";\npub const MEMORY_MAX: &str = \"MemoryMax\";\npub const MEMORY_SWAP: &str = \"MemorySwapMax\";\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdMemoryError {\n    #[error(\"invalid memory reservation value: {0}\")]\n    ReservationValue(i64),\n    #[error(\"invalid memory limit value: {0}\")]\n    MemoryLimit(i64),\n    #[error(\"cgroup v2 swap value cannot be calculated from swap of {swap} and limit of {limit}\")]\n    SwapValue { swap: i64, limit: String },\n}\n\npub struct Memory {}\n\nimpl Controller for Memory {\n    type Error = SystemdMemoryError;\n\n    fn apply(\n        options: &ControllerOpt,\n        _: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(memory) = options.resources.memory() {\n            tracing::debug!(\"applying memory resource restrictions\");\n            return Self::apply(memory, properties);\n        }\n\n        Ok(())\n    }\n}\n\nimpl Memory {\n    fn apply(\n        memory: &LinuxMemory,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdMemoryError> {\n        if let Some(reservation) = memory.reservation() {\n            match reservation {\n                1..=i64::MAX => {\n                    properties.insert(MEMORY_LOW, Variant::U64(reservation as u64));\n                }\n                -1 => {\n                    properties.insert(MEMORY_LOW, Variant::U64(u64::MAX));\n                }\n                _ => return Err(SystemdMemoryError::ReservationValue(reservation)),\n            }\n        }\n\n        if let Some(limit) = memory.limit() {\n            match limit {\n                1..=i64::MAX => {\n                    properties.insert(MEMORY_MAX, Variant::U64(limit as u64));\n                }\n                -1 => {\n                    properties.insert(MEMORY_MAX, Variant::U64(u64::MAX));\n                }\n                _ => return Err(SystemdMemoryError::MemoryLimit(limit)),\n            }\n        }\n\n        Self::apply_swap(memory.swap(), memory.limit(), properties)?;\n        Ok(())\n    }\n\n    // Swap needs to be converted as the runtime spec defines swap as the total of memory + swap,\n    // which corresponds to memory.memsw.limit_in_bytes in cgroup v1. In v2 however swap is a\n    // separate value (memory.swap.max). Therefore swap needs to be calculated from memory limit\n    // and swap. Specified values could be None (no value specified), -1 (unlimited), zero or a\n    // positive value. Swap needs to be bigger than the memory limit (due to swap being memory + swap)\n    fn apply_swap(\n        swap: Option<i64>,\n        limit: Option<i64>,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdMemoryError> {\n        let value: Variant = match (limit, swap) {\n            // memory is unlimited and swap not specified -> assume swap unlimited\n            (Some(-1), None) => Variant::U64(u64::MAX),\n            // if swap is unlimited it can be set to unlimited regardless of memory limit value\n            (_, Some(-1)) => Variant::U64(u64::MAX),\n            // if swap is zero, then it needs to be rejected regardless of memory limit value\n            // as memory limit would be either bigger (invariant violation) or zero which would\n            // leave the container with no memory and no swap.\n            // if swap is greater than zero and memory limit is unspecified swap cannot be\n            // calculated. If memory limit is zero the container would have only swap. If\n            // memory is unlimited it would be bigger than swap.\n            (_, Some(0)) | (None | Some(0) | Some(-1), Some(1..=i64::MAX)) => {\n                return Err(SystemdMemoryError::SwapValue {\n                    swap: swap.unwrap(),\n                    limit: limit.map_or(\"none\".to_owned(), |v| v.to_string()),\n                });\n            }\n\n            (Some(l), Some(s)) if l < s => Variant::U64((s - l) as u64),\n            _ => return Ok(()),\n        };\n\n        properties.insert(MEMORY_SWAP, value);\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n    use oci_spec::runtime::LinuxMemoryBuilder;\n\n    use super::super::dbus_native::serialize::DbusSerialize;\n    use super::*;\n    use crate::recast;\n\n    #[test]\n    fn test_set_valid_memory_low() -> Result<()> {\n        let values = vec![(536870912, 536870912u64), (-1, u64::MAX)];\n\n        for (reservation, expected) in values {\n            // arrange\n            let memory = LinuxMemoryBuilder::default()\n                .reservation(reservation)\n                .build()\n                .context(\"build memory spec\")?;\n            let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n            // act\n            Memory::apply(&memory, &mut properties).context(\"apply memory\")?;\n\n            // assert\n            assert_eq!(properties.len(), 1);\n            assert!(properties.contains_key(MEMORY_LOW));\n            let memory_low = &properties[MEMORY_LOW];\n            let val = recast!(memory_low, Variant)?;\n            assert_eq!(val, Variant::U64(expected));\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_set_valid_memory_max() -> Result<()> {\n        let values = vec![(536870912, 536870912u64, 1), (-1, u64::MAX, 2)];\n\n        for (reservation, mem_low, prop_count) in values {\n            // arrange\n            let memory = LinuxMemoryBuilder::default()\n                .limit(reservation)\n                .build()\n                .context(\"build memory spec\")?;\n            let mut properties: HashMap<&str, Variant> = HashMap::new();\n\n            // act\n            Memory::apply(&memory, &mut properties).context(\"apply memory\")?;\n\n            // assert\n            assert_eq!(properties.len(), prop_count);\n            assert!(properties.contains_key(MEMORY_MAX));\n            let actual = &properties[MEMORY_MAX];\n            let val = recast!(actual, Variant)?;\n            assert_eq!(val, Variant::U64(mem_low));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/mod.rs",
    "content": "use std::fs;\n\nmod controller;\npub mod controller_type;\nmod cpu;\nmod cpuset;\nmod dbus_native;\nmod io;\npub mod manager;\nmod memory;\nmod pids;\nmod unified;\n\n/// Checks if the system was booted with systemd\npub fn booted() -> bool {\n    fs::symlink_metadata(\"/run/systemd/system\")\n        .map(|p| p.is_dir())\n        .unwrap_or_default()\n}\n\n#[macro_export]\nmacro_rules! recast {\n    ($v:ident, $t:ty) => {{\n        let mut buf = Vec::new();\n        $v.serialize(&mut buf);\n        let mut ctr = 0;\n        let ret = <$t>::deserialize(&buf, &mut ctr);\n        ret\n    }};\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/pids.rs",
    "content": "use std::collections::HashMap;\nuse std::convert::Infallible;\n\nuse oci_spec::runtime::LinuxPids;\n\nuse super::controller::Controller;\nuse super::dbus_native::serialize::Variant;\nuse crate::common::ControllerOpt;\n\npub const TASKS_MAX: &str = \"TasksMax\";\n\npub struct Pids {}\n\nimpl Controller for Pids {\n    type Error = Infallible;\n\n    fn apply(\n        options: &ControllerOpt,\n        _: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(pids) = options.resources.pids() {\n            tracing::debug!(\"Applying pids resource restrictions\");\n            Self::apply(pids, properties);\n        }\n\n        Ok(())\n    }\n}\n\nimpl Pids {\n    fn apply(pids: &LinuxPids, properties: &mut HashMap<&str, Variant>) {\n        let limit = if pids.limit() > 0 {\n            pids.limit() as u64\n        } else {\n            u64::MAX\n        };\n\n        properties.insert(TASKS_MAX, Variant::U64(limit));\n    }\n}\n\n#[cfg(test)]\nmod tests {\n\n    use anyhow::{Context, Result, anyhow};\n    use oci_spec::runtime::{LinuxPidsBuilder, LinuxResources, LinuxResourcesBuilder};\n\n    use super::super::dbus_native::serialize::DbusSerialize;\n    use super::*;\n    use crate::recast;\n\n    fn setup(resources: &LinuxResources) -> (ControllerOpt<'_>, HashMap<&str, Variant>) {\n        let properties = HashMap::new();\n        let options = ControllerOpt {\n            resources,\n            disable_oom_killer: false,\n            oom_score_adj: None,\n            freezer_state: None,\n        };\n\n        (options, properties)\n    }\n\n    #[test]\n    fn test_pids_positive_limit() -> Result<()> {\n        let resources = LinuxResourcesBuilder::default()\n            .pids(LinuxPidsBuilder::default().limit(10).build()?)\n            .build()?;\n        let (options, mut properties) = setup(&resources);\n\n        <Pids as Controller>::apply(&options, 245, &mut properties)\n            .map_err(|err| anyhow!(err))\n            .context(\"apply pids\")?;\n\n        assert_eq!(properties.len(), 1);\n        assert!(properties.contains_key(TASKS_MAX));\n\n        let task_max = properties.get(TASKS_MAX).unwrap();\n        let val = recast!(task_max, Variant)?;\n        assert_eq!(val, Variant::U64(10));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_pids_zero_limit() -> Result<()> {\n        let resources = LinuxResourcesBuilder::default()\n            .pids(LinuxPidsBuilder::default().limit(0).build()?)\n            .build()?;\n        let (options, mut properties) = setup(&resources);\n\n        <Pids as Controller>::apply(&options, 245, &mut properties)\n            .map_err(|err| anyhow!(err))\n            .context(\"apply pids\")?;\n\n        assert_eq!(properties.len(), 1);\n        assert!(properties.contains_key(TASKS_MAX));\n\n        let task_max = properties.get(TASKS_MAX).unwrap();\n        let val = recast!(task_max, Variant)?;\n        assert_eq!(val, Variant::U64(u64::MAX));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_pids_negative_limit() -> Result<()> {\n        let resources = LinuxResourcesBuilder::default()\n            .pids(LinuxPidsBuilder::default().limit(-500).build()?)\n            .build()?;\n        let (options, mut properties) = setup(&resources);\n\n        <Pids as Controller>::apply(&options, 245, &mut properties)\n            .map_err(|err| anyhow!(err))\n            .context(\"apply pids\")?;\n\n        assert_eq!(properties.len(), 1);\n        assert!(properties.contains_key(TASKS_MAX));\n\n        let task_max = properties.get(TASKS_MAX).unwrap();\n        let val = recast!(task_max, Variant)?;\n        assert_eq!(val, Variant::U64(u64::MAX));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/systemd/unified.rs",
    "content": "use std::collections::HashMap;\nuse std::num::ParseIntError;\n\nuse super::controller::Controller;\nuse super::cpu::{self, convert_shares_to_cgroup2};\nuse super::cpuset::{self, BitmaskError, to_bitmask};\nuse super::dbus_native::serialize::Variant;\nuse super::{memory, pids};\nuse crate::common::ControllerOpt;\n\n#[derive(thiserror::Error, Debug)]\npub enum SystemdUnifiedError {\n    #[error(\"failed to parse cpu weight {value}: {err}\")]\n    CpuWeight { err: ParseIntError, value: String },\n    #[error(\"invalid format for cpu.max: {0}\")]\n    CpuMax(String),\n    #[error(\"failed to to parse cpu quota {value}: {err}\")]\n    CpuQuota { err: ParseIntError, value: String },\n    #[error(\"failed to to parse cpu period {value}: {err}\")]\n    CpuPeriod { err: ParseIntError, value: String },\n    #[error(\"setting {0} requires systemd version greater than 243\")]\n    OldSystemd(String),\n    #[error(\"invalid value for cpuset.cpus {0}\")]\n    CpuSetCpu(BitmaskError),\n    #[error(\"failed to parse {name} {value}: {err}\")]\n    Memory {\n        err: ParseIntError,\n        name: String,\n        value: String,\n    },\n    #[error(\"failed to to parse pids.max {value}: {err}\")]\n    PidsMax { err: ParseIntError, value: String },\n}\n\npub struct Unified {}\n\nimpl Controller for Unified {\n    type Error = SystemdUnifiedError;\n\n    fn apply(\n        options: &ControllerOpt,\n        systemd_version: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), Self::Error> {\n        if let Some(unified) = options.resources.unified() {\n            tracing::debug!(\"applying unified resource restrictions\");\n            Self::apply(unified, systemd_version, properties)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl Unified {\n    fn apply(\n        unified: &HashMap<String, String>,\n        systemd_version: u32,\n        properties: &mut HashMap<&str, Variant>,\n    ) -> Result<(), SystemdUnifiedError> {\n        for (key, value) in unified {\n            match key.as_str() {\n                \"cpu.weight\" => {\n                    let shares =\n                        value\n                            .parse::<u64>()\n                            .map_err(|err| SystemdUnifiedError::CpuWeight {\n                                err,\n                                value: value.into(),\n                            })?;\n                    properties.insert(\n                        cpu::CPU_WEIGHT,\n                        Variant::U64(convert_shares_to_cgroup2(shares)),\n                    );\n                }\n                \"cpu.max\" => {\n                    let parts: Vec<&str> = value.split_whitespace().collect();\n                    if parts.is_empty() || parts.len() > 2 {\n                        return Err(SystemdUnifiedError::CpuMax(value.into()));\n                    }\n\n                    let quota =\n                        parts[0]\n                            .parse::<u64>()\n                            .map_err(|err| SystemdUnifiedError::CpuQuota {\n                                err,\n                                value: parts[0].into(),\n                            })?;\n                    properties.insert(cpu::CPU_QUOTA, Variant::U64(quota));\n\n                    if parts.len() == 2 {\n                        let period = parts[1].parse::<u64>().map_err(|err| {\n                            SystemdUnifiedError::CpuPeriod {\n                                err,\n                                value: parts[1].into(),\n                            }\n                        })?;\n                        properties.insert(cpu::CPU_PERIOD, Variant::U64(period));\n                    }\n                }\n                cpuset @ (\"cpuset.cpus\" | \"cpuset.mems\") => {\n                    if systemd_version <= 243 {\n                        return Err(SystemdUnifiedError::OldSystemd(cpuset.into()));\n                    }\n\n                    let bitmask: Vec<u64> = to_bitmask(value)\n                        .map_err(SystemdUnifiedError::CpuSetCpu)?\n                        .into_iter()\n                        .map(|v| v as u64)\n                        .collect();\n\n                    let systemd_cpuset = match cpuset {\n                        \"cpuset.cpus\" => cpuset::ALLOWED_CPUS,\n                        \"cpuset.mems\" => cpuset::ALLOWED_NODES,\n                        file_name => unreachable!(\"{} was not matched\", file_name),\n                    };\n\n                    properties.insert(systemd_cpuset, Variant::ArrayU64(bitmask));\n                }\n                memory @ (\"memory.min\" | \"memory.low\" | \"memory.high\" | \"memory.max\") => {\n                    let value =\n                        value\n                            .parse::<u64>()\n                            .map_err(|err| SystemdUnifiedError::Memory {\n                                err,\n                                name: memory.into(),\n                                value: value.into(),\n                            })?;\n                    let systemd_memory = match memory {\n                        \"memory.min\" => memory::MEMORY_MIN,\n                        \"memory.low\" => memory::MEMORY_LOW,\n                        \"memory.high\" => memory::MEMORY_HIGH,\n                        \"memory.max\" => memory::MEMORY_MAX,\n                        file_name => unreachable!(\"{} was not matched\", file_name),\n                    };\n                    properties.insert(systemd_memory, Variant::U64(value));\n                }\n                \"pids.max\" => {\n                    let pids = value.trim().parse::<i64>().map_err(|err| {\n                        SystemdUnifiedError::PidsMax {\n                            err,\n                            value: value.into(),\n                        }\n                    })?;\n                    properties.insert(pids::TASKS_MAX, Variant::U64(pids as u64));\n                }\n\n                unknown => tracing::warn!(\"could not apply {}. Unknown property.\", unknown),\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n\n    use super::super::dbus_native::serialize::DbusSerialize;\n    use super::*;\n    use crate::recast;\n\n    #[test]\n    fn test_set() -> Result<()> {\n        // arrange\n        let unified: HashMap<String, String> = [\n            (\"cpu.weight\", \"22000\"),\n            (\"cpuset.cpus\", \"0-3\"),\n            (\"cpuset.mems\", \"0-3\"),\n            (\"memory.min\", \"100000\"),\n            (\"memory.low\", \"200000\"),\n            (\"memory.high\", \"300000\"),\n            (\"memory.max\", \"400000\"),\n            (\"pids.max\", \"100\"),\n        ]\n        .into_iter()\n        .map(|(k, v)| (k.to_owned(), v.to_owned()))\n        .collect();\n\n        let mut expected: HashMap<&str, Variant> = HashMap::new();\n        expected.insert(cpu::CPU_WEIGHT, Variant::U64(1204));\n        expected.insert(cpuset::ALLOWED_CPUS, Variant::ArrayU64(vec![15u64]));\n        expected.insert(cpuset::ALLOWED_NODES, Variant::ArrayU64(vec![15u64]));\n        expected.insert(memory::MEMORY_MIN, Variant::U64(100000u64));\n        expected.insert(memory::MEMORY_LOW, Variant::U64(200000u64));\n        expected.insert(memory::MEMORY_HIGH, Variant::U64(300000u64));\n        expected.insert(memory::MEMORY_MAX, Variant::U64(400000u64));\n        expected.insert(pids::TASKS_MAX, Variant::U64(100u64));\n\n        // act\n        let mut actual: HashMap<&str, Variant> = HashMap::new();\n        Unified::apply(&unified, 245, &mut actual).context(\"apply unified\")?;\n\n        // assert\n        for (setting, value) in expected {\n            assert!(actual.contains_key(setting));\n            let mut value_buf = Vec::new();\n            let mut actual_buf = Vec::new();\n            value.serialize(&mut value_buf);\n            actual[setting].serialize(&mut actual_buf);\n            assert_eq!(value_buf, actual_buf, \"{setting}\");\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_cpu_max_quota_and_period() -> Result<()> {\n        // arrange\n        let unified: HashMap<String, String> = [(\"cpu.max\", \"500000 250000\")]\n            .into_iter()\n            .map(|(k, v)| (k.to_owned(), v.to_owned()))\n            .collect();\n        let mut actual: HashMap<&str, Variant> = HashMap::new();\n\n        // act\n        Unified::apply(&unified, 245, &mut actual).context(\"apply unified\")?;\n\n        // assert\n        assert!(actual.contains_key(cpu::CPU_PERIOD));\n        assert!(actual.contains_key(cpu::CPU_QUOTA));\n\n        let cpu_period = &actual[cpu::CPU_PERIOD];\n        let cpu_quota = &actual[cpu::CPU_QUOTA];\n        assert_eq!(recast!(cpu_period, Variant)?, Variant::U64(250000));\n        assert_eq!(recast!(cpu_quota, Variant)?, Variant::U64(500000));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_cpu_max_quota_only() -> Result<()> {\n        // arrange\n        let unified: HashMap<String, String> = [(\"cpu.max\", \"500000\")]\n            .into_iter()\n            .map(|(k, v)| (k.to_owned(), v.to_owned()))\n            .collect();\n        let mut actual: HashMap<&str, Variant> = HashMap::new();\n\n        // act\n        Unified::apply(&unified, 245, &mut actual).context(\"apply unified\")?;\n\n        // assert\n        assert!(!actual.contains_key(cpu::CPU_PERIOD));\n        assert!(actual.contains_key(cpu::CPU_QUOTA));\n\n        let cpu_quota = &actual[cpu::CPU_QUOTA];\n        assert_eq!(recast!(cpu_quota, Variant)?, Variant::U64(500000));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/test.rs",
    "content": "#![cfg(test)]\n\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result};\n\npub fn setup(cgroup_file: &str) -> (tempfile::TempDir, PathBuf) {\n    let tmp = tempfile::tempdir().expect(\"create temp directory for test\");\n    let cgroup_file = set_fixture(tmp.path(), cgroup_file, \"\")\n        .unwrap_or_else(|_| panic!(\"set test fixture for {cgroup_file}\"));\n\n    (tmp, cgroup_file)\n}\n\npub fn set_fixture(temp_dir: &Path, filename: &str, val: &str) -> Result<PathBuf> {\n    let full_path = temp_dir.join(filename);\n\n    std::fs::OpenOptions::new()\n        .create(true)\n        .write(true)\n        .truncate(true)\n        .open(&full_path)\n        .with_context(|| format!(\"failed to open {full_path:?}\"))?\n        .write_all(val.as_bytes())\n        .with_context(|| format!(\"failed to write to {full_path:?}\"))?;\n\n    Ok(full_path)\n}\n"
  },
  {
    "path": "crates/libcgroups/src/test_manager.rs",
    "content": "use std::cell::RefCell;\nuse std::convert::Infallible;\n\nuse nix::unistd::Pid;\n\nuse crate::common::{CgroupManager, ControllerOpt, FreezerState};\nuse crate::stats::Stats;\n\n#[derive(Debug)]\npub struct TestManager {\n    add_task_args: RefCell<Vec<Pid>>,\n    pub apply_called: RefCell<bool>,\n}\n\nimpl Default for TestManager {\n    fn default() -> Self {\n        Self {\n            add_task_args: RefCell::new(vec![]),\n            apply_called: RefCell::new(false),\n        }\n    }\n}\n\nimpl CgroupManager for TestManager {\n    type Error = Infallible;\n\n    fn add_task(&self, pid: Pid) -> Result<(), Infallible> {\n        self.add_task_args.borrow_mut().push(pid);\n        Ok(())\n    }\n\n    // NOTE: The argument cannot be stored due to lifetime.\n    fn apply(&self, _controller_opt: &ControllerOpt) -> Result<(), Infallible> {\n        *self.apply_called.borrow_mut() = true;\n        Ok(())\n    }\n\n    fn remove(&self) -> Result<(), Infallible> {\n        unimplemented!()\n    }\n\n    fn freeze(&self, _state: FreezerState) -> Result<(), Infallible> {\n        unimplemented!()\n    }\n\n    fn stats(&self) -> Result<Stats, Infallible> {\n        unimplemented!()\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Infallible> {\n        unimplemented!()\n    }\n}\n\nimpl TestManager {\n    pub fn get_add_task_args(&self) -> Vec<Pid> {\n        self.add_task_args.borrow_mut().clone()\n    }\n\n    pub fn apply_called(&self) -> bool {\n        *self.apply_called.borrow_mut()\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/blkio.rs",
    "content": "use std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::LinuxBlockIo;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{self, BlkioDeviceStat, BlkioStats, ParseDeviceNumberError, StatsProvider};\n\n// Throttling/upper limit policy\n// ---------------------------------------\n// Upper limit on the number of read operations a device can perform specified in bytes\n// Format: Major:Minor Bytes\nconst BLKIO_THROTTLE_READ_BPS: &str = \"blkio.throttle.read_bps_device\";\n// Upper limit on the number of write operations a device can perform specified in bytes\n// Format: Major:Minor Bytes\nconst BLKIO_THROTTLE_WRITE_BPS: &str = \"blkio.throttle.write_bps_device\";\n// Upper limit on the number of read operations a device can perform specified in operations per second\n// Format: Major:Minor Ops\nconst BLKIO_THROTTLE_READ_IOPS: &str = \"blkio.throttle.read_iops_device\";\n// Upper limit on the number of write operations a device can perform specified in operations per second\n// Format: Major:Minor Ops\nconst BLKIO_THROTTLE_WRITE_IOPS: &str = \"blkio.throttle.write_iops_device\";\n// Number of I/O operations performed on a device by the cgroup\n// Format: Major:Minor Type Ops\nconst BLKIO_THROTTLE_IO_SERVICED: &str = \"blkio.throttle.io_serviced\";\n// Number of bytes transferred to/from a device by the cgroup\n// Format: Major:Minor Type Bytes\nconst BLKIO_THROTTLE_IO_SERVICE_BYTES: &str = \"blkio.throttle.io_service_bytes\";\n\n// Proportional weight division policy\n// ---------------------------------------\n// Specifies the relative proportion of block I/O access available to the cgroup\n// Format: weight (weight can range from 10 to 1000)\nconst BLKIO_WEIGHT: &str = \"blkio.weight\";\n// Similar to BLKIO_WEIGHT, but is only available in kernels starting with version 5.0\n// with blk-mq and when using BFQ I/O scheduler\n// Format: weight (weight can range from 1 to 10000)\nconst BLKIO_BFQ_WEIGHT: &str = \"blkio.bfq.weight\";\n// Specifies the relative proportion of block I/O access for specific devices available\n// to the cgroup. This overrides the the blkio.weight value for the specified device\n// Format: Major:Minor weight (weight can range from 100 to 1000)\n#[allow(dead_code)]\nconst BLKIO_WEIGHT_DEVICE: &str = \"blkio.weight_device\";\n\n// Common parameters which may be used for either policy but seem to be used only for\n// proportional weight division policy in practice\n// ---------------------------------------\n// Time in milliseconds that the cgroup had access to a device\n// Format: Major:Minor Time(ms)\nconst BLKIO_TIME: &str = \"blkio.time_recursive\";\n// Number of sectors transferred to/from a device by the cgroup\n// Format: Major:Minor Sectors\nconst BLKIO_SECTORS: &str = \"blkio.sectors_recursive\";\n// Number of bytes transferred to/from a device by the cgroup\n/// Format: Major:Minor Type Bytes\nconst BLKIO_IO_SERVICE_BYTES: &str = \"blkio.io_service_bytes_recursive\";\n// Number of I/O operations performed on a device by the cgroup\n// Format: Major:Minor Type Ops\nconst BLKIO_IO_SERVICED: &str = \"blkio.io_serviced_recursive\";\n// Total time between request dispatch and request completion\n/// Format: Major:Minor Type Time(ns)\nconst BLKIO_IO_SERVICE_TIME: &str = \"blkio.io_service_time_recursive\";\n// Total time spend waiting in the scheduler queues for service\n// Format: Major:Minor Type Time(ns)\nconst BLKIO_WAIT_TIME: &str = \"blkio.io_wait_time_recursive\";\n// Number of requests queued for I/O operations\n// Format: Requests Type\nconst BLKIO_QUEUED: &str = \"blkio.io_queued_recursive\";\n// Number of requests merged into requests for I/O operations\n// Format: Requests Type\nconst BLKIO_MERGED: &str = \"blkio.io_merged_recursive\";\n\npub struct Blkio {}\n\nimpl Controller for Blkio {\n    type Error = WrappedIoError;\n    type Resource = LinuxBlockIo;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply blkio cgroup config\");\n\n        if let Some(blkio) = Self::needs_to_handle(controller_opt) {\n            Self::apply(cgroup_root, blkio)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.resources.block_io().as_ref()\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1BlkioStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"failed to parse device value {value} in {path}: {err}\")]\n    FailedParseValue {\n        value: String,\n        path: PathBuf,\n        err: ParseIntError,\n    },\n    #[error(\"failed to parse device number: {0}\")]\n    FailedParseNumber(#[from] ParseDeviceNumberError),\n}\n\nimpl StatsProvider for Blkio {\n    type Error = V1BlkioStatsError;\n    type Stats = BlkioStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        if cgroup_path.join(BLKIO_WEIGHT).exists() {\n            return Self::get_weight_division_policy_stats(cgroup_path);\n        }\n\n        Self::get_throttling_policy_stats(cgroup_path)\n    }\n}\n\nimpl Blkio {\n    fn apply(root_path: &Path, blkio: &LinuxBlockIo) -> Result<(), WrappedIoError> {\n        if let Some(blkio_weight) = blkio.weight() {\n            // be aligned with what runc does\n            // See also: https://github.com/opencontainers/runc/blob/81044ad7c902f3fc153cb8ffadaf4da62855193f/libcontainer/cgroups/fs/blkio.go#L28-L33\n            if blkio_weight != 0 {\n                let cgroup_file = root_path.join(BLKIO_WEIGHT);\n                if cgroup_file.exists() {\n                    common::write_cgroup_file(&cgroup_file, blkio_weight)?;\n                } else {\n                    common::write_cgroup_file(root_path.join(BLKIO_BFQ_WEIGHT), blkio_weight)?;\n                }\n            }\n        }\n\n        if let Some(throttle_read_bps_device) = blkio.throttle_read_bps_device().as_ref() {\n            for trbd in throttle_read_bps_device {\n                common::write_cgroup_file_str(\n                    root_path.join(BLKIO_THROTTLE_READ_BPS),\n                    &format!(\"{}:{} {}\", trbd.major(), trbd.minor(), trbd.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_write_bps_device) = blkio.throttle_write_bps_device().as_ref() {\n            for twbd in throttle_write_bps_device {\n                common::write_cgroup_file_str(\n                    root_path.join(BLKIO_THROTTLE_WRITE_BPS),\n                    &format!(\"{}:{} {}\", twbd.major(), twbd.minor(), twbd.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_read_iops_device) = blkio.throttle_read_iops_device().as_ref() {\n            for trid in throttle_read_iops_device {\n                common::write_cgroup_file_str(\n                    root_path.join(BLKIO_THROTTLE_READ_IOPS),\n                    &format!(\"{}:{} {}\", trid.major(), trid.minor(), trid.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_write_iops_device) = blkio.throttle_write_iops_device().as_ref() {\n            for twid in throttle_write_iops_device {\n                common::write_cgroup_file_str(\n                    root_path.join(BLKIO_THROTTLE_WRITE_IOPS),\n                    &format!(\"{}:{} {}\", twid.major(), twid.minor(), twid.rate()),\n                )?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn get_throttling_policy_stats(cgroup_path: &Path) -> Result<BlkioStats, V1BlkioStatsError> {\n        let stats = BlkioStats {\n            service_bytes: Self::parse_blkio_file(\n                &cgroup_path.join(BLKIO_THROTTLE_IO_SERVICE_BYTES),\n            )?,\n            serviced: Self::parse_blkio_file(&cgroup_path.join(BLKIO_THROTTLE_IO_SERVICED))?,\n            ..Default::default()\n        };\n\n        Ok(stats)\n    }\n\n    fn get_weight_division_policy_stats(\n        cgroup_path: &Path,\n    ) -> Result<BlkioStats, V1BlkioStatsError> {\n        let stats = BlkioStats {\n            time: Self::parse_blkio_file(&cgroup_path.join(BLKIO_TIME))?,\n            sectors: Self::parse_blkio_file(&cgroup_path.join(BLKIO_SECTORS))?,\n            service_bytes: Self::parse_blkio_file(&cgroup_path.join(BLKIO_IO_SERVICE_BYTES))?,\n            serviced: Self::parse_blkio_file(&cgroup_path.join(BLKIO_IO_SERVICED))?,\n            service_time: Self::parse_blkio_file(&cgroup_path.join(BLKIO_IO_SERVICE_TIME))?,\n            wait_time: Self::parse_blkio_file(&cgroup_path.join(BLKIO_WAIT_TIME))?,\n            queued: Self::parse_blkio_file(&cgroup_path.join(BLKIO_QUEUED))?,\n            merged: Self::parse_blkio_file(&cgroup_path.join(BLKIO_MERGED))?,\n            ..Default::default()\n        };\n\n        Ok(stats)\n    }\n\n    fn parse_blkio_file(blkio_file: &Path) -> Result<Vec<BlkioDeviceStat>, V1BlkioStatsError> {\n        let content = common::read_cgroup_file(blkio_file)?;\n        let mut stats = Vec::new();\n        for entry in content.lines() {\n            let entry_fields: Vec<&str> = entry.split_ascii_whitespace().collect();\n            if entry_fields.len() <= 2 {\n                continue;\n            }\n\n            let (major, minor) = stats::parse_device_number(entry_fields[0])?;\n            let op_type = if entry_fields.len() == 3 {\n                Some(entry_fields[1].to_owned())\n            } else {\n                None\n            };\n            let value = if entry_fields.len() == 3 {\n                entry_fields[2]\n                    .parse()\n                    .map_err(|err| V1BlkioStatsError::FailedParseValue {\n                        value: entry_fields[2].into(),\n                        path: blkio_file.to_path_buf(),\n                        err,\n                    })?\n            } else {\n                entry_fields[1]\n                    .parse()\n                    .map_err(|err| V1BlkioStatsError::FailedParseValue {\n                        value: entry_fields[1].into(),\n                        path: blkio_file.to_path_buf(),\n                        err,\n                    })?\n            };\n\n            let stat = BlkioDeviceStat {\n                major,\n                minor,\n                op_type,\n                value,\n            };\n\n            stats.push(stat);\n        }\n\n        Ok(stats)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use oci_spec::runtime::{LinuxBlockIoBuilder, LinuxThrottleDeviceBuilder};\n\n    use super::*;\n    use crate::test::{set_fixture, setup};\n\n    #[test]\n    fn test_set_blkio_weight() {\n        for cgroup_file in &[BLKIO_WEIGHT, BLKIO_BFQ_WEIGHT] {\n            let (tmp, weight_file) = setup(cgroup_file);\n            let blkio = LinuxBlockIoBuilder::default()\n                .weight(200_u16)\n                .build()\n                .unwrap();\n\n            Blkio::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n            let content = fs::read_to_string(weight_file).expect(\"read blkio weight\");\n            assert_eq!(\"200\", content);\n        }\n    }\n\n    #[test]\n    fn test_set_blkio_read_bps() {\n        let (tmp, throttle) = setup(BLKIO_THROTTLE_READ_BPS);\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Blkio::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle)\n            .unwrap_or_else(|_| panic!(\"read {BLKIO_THROTTLE_READ_BPS} content\"));\n\n        assert_eq!(\"8:0 102400\", content);\n    }\n\n    #[test]\n    fn test_set_blkio_write_bps() {\n        let (tmp, throttle) = setup(BLKIO_THROTTLE_WRITE_BPS);\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_write_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Blkio::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle)\n            .unwrap_or_else(|_| panic!(\"read {BLKIO_THROTTLE_WRITE_BPS} content\"));\n\n        assert_eq!(\"8:0 102400\", content);\n    }\n\n    #[test]\n    fn test_set_blkio_read_iops() {\n        let (tmp, throttle) = setup(BLKIO_THROTTLE_READ_IOPS);\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Blkio::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle)\n            .unwrap_or_else(|_| panic!(\"read {BLKIO_THROTTLE_READ_IOPS} content\"));\n\n        assert_eq!(\"8:0 102400\", content);\n    }\n\n    #[test]\n    fn test_set_blkio_write_iops() {\n        let (tmp, throttle) = setup(BLKIO_THROTTLE_WRITE_IOPS);\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_write_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Blkio::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle)\n            .unwrap_or_else(|_| panic!(\"read {BLKIO_THROTTLE_WRITE_IOPS} content\"));\n\n        assert_eq!(\"8:0 102400\", content);\n    }\n\n    #[test]\n    fn test_stat_throttling_policy() -> Result<(), Box<dyn std::error::Error>> {\n        let tmp = tempfile::tempdir().unwrap();\n        let content = &[\n            \"8:0 Read 20\",\n            \"8:0 Write 20\",\n            \"8:0 Sync 20\",\n            \"8:0 Async 20\",\n            \"8:0 Discard 20\",\n            \"8:0 Total 20\",\n            \"Total 0\",\n        ]\n        .join(\"\\n\");\n        set_fixture(tmp.path(), BLKIO_THROTTLE_IO_SERVICE_BYTES, content).unwrap();\n        set_fixture(tmp.path(), BLKIO_THROTTLE_IO_SERVICED, content).unwrap();\n\n        let actual = Blkio::stats(tmp.path()).expect(\"get cgroup stats\");\n        let mut expected = BlkioStats::default();\n        let devices: Vec<BlkioDeviceStat> = [\"Read\", \"Write\", \"Sync\", \"Async\", \"Discard\", \"Total\"]\n            .iter()\n            .copied()\n            .map(|op| BlkioDeviceStat {\n                major: 8,\n                minor: 0,\n                op_type: Some(op.to_owned()),\n                value: 20,\n            })\n            .collect();\n\n        expected.service_bytes = devices.clone();\n        expected.serviced = devices;\n\n        assert_eq!(expected, actual);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/controller.rs",
    "content": "use std::fs;\nuse std::path::Path;\n\nuse nix::unistd::Pid;\n\nuse crate::common::{self, CGROUP_PROCS, ControllerOpt, WrapIoResult, WrappedIoError};\n\npub(super) trait Controller {\n    type Error: From<WrappedIoError>;\n    type Resource;\n\n    /// Adds a new task specified by its pid to the cgroup\n    fn add_task(pid: Pid, cgroup_path: &Path) -> Result<(), Self::Error> {\n        fs::create_dir_all(cgroup_path).wrap_create_dir(cgroup_path)?;\n        common::write_cgroup_file(cgroup_path.join(CGROUP_PROCS), pid)?;\n        Ok(())\n    }\n\n    /// Applies resource restrictions to the cgroup\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error>;\n\n    /// Checks if the controller needs to handle this request\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource>;\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/controller_type.rs",
    "content": "use std::fmt::Display;\n\n#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]\npub enum ControllerType {\n    Cpu,\n    CpuAcct,\n    CpuSet,\n    Devices,\n    HugeTlb,\n    Pids,\n    PerfEvent,\n    Memory,\n    Blkio,\n    NetworkPriority,\n    NetworkClassifier,\n    Freezer,\n}\n\nimpl Display for ControllerType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match *self {\n            Self::Cpu => \"cpu\",\n            Self::CpuAcct => \"cpuacct\",\n            Self::CpuSet => \"cpuset\",\n            Self::Devices => \"devices\",\n            Self::HugeTlb => \"hugetlb\",\n            Self::Pids => \"pids\",\n            Self::PerfEvent => \"perf_event\",\n            Self::Memory => \"memory\",\n            Self::Blkio => \"blkio\",\n            Self::NetworkPriority => \"net_prio\",\n            Self::NetworkClassifier => \"net_cls\",\n            Self::Freezer => \"freezer\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\nimpl AsRef<str> for ControllerType {\n    fn as_ref(&self) -> &str {\n        match *self {\n            Self::Cpu => \"cpu\",\n            Self::CpuAcct => \"cpuacct\",\n            Self::CpuSet => \"cpuset\",\n            Self::Devices => \"devices\",\n            Self::HugeTlb => \"hugetlb\",\n            Self::Pids => \"pids\",\n            Self::PerfEvent => \"perf_event\",\n            Self::Memory => \"memory\",\n            Self::Blkio => \"blkio\",\n            Self::NetworkPriority => \"net_prio\",\n            Self::NetworkClassifier => \"net_cls\",\n            Self::Freezer => \"freezer\",\n        }\n    }\n}\n\npub const CONTROLLERS: &[ControllerType] = &[\n    ControllerType::Cpu,\n    ControllerType::CpuAcct,\n    ControllerType::CpuSet,\n    ControllerType::Devices,\n    ControllerType::HugeTlb,\n    ControllerType::Memory,\n    ControllerType::Pids,\n    ControllerType::PerfEvent,\n    ControllerType::Blkio,\n    ControllerType::NetworkPriority,\n    ControllerType::NetworkClassifier,\n    ControllerType::Freezer,\n];\n"
  },
  {
    "path": "crates/libcgroups/src/v1/cpu.rs",
    "content": "use std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::LinuxCpu;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{CpuThrottling, ParseFlatKeyedDataError, StatsProvider, parse_flat_keyed_data};\n\nconst CGROUP_CPU_SHARES: &str = \"cpu.shares\";\nconst CGROUP_CPU_QUOTA: &str = \"cpu.cfs_quota_us\";\nconst CGROUP_CPU_PERIOD: &str = \"cpu.cfs_period_us\";\nconst CGROUP_CPU_BURST: &str = \"cpu.cfs_burst_us\";\nconst CGROUP_CPU_RT_RUNTIME: &str = \"cpu.rt_runtime_us\";\nconst CGROUP_CPU_RT_PERIOD: &str = \"cpu.rt_period_us\";\nconst CGROUP_CPU_STAT: &str = \"cpu.stat\";\nconst CGROUP_CPU_IDLE: &str = \"cpu.idle\";\n\npub struct Cpu {}\n\nimpl Controller for Cpu {\n    type Error = WrappedIoError;\n    type Resource = LinuxCpu;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply Cpu cgroup config\");\n\n        if let Some(cpu) = Self::needs_to_handle(controller_opt) {\n            Self::apply(cgroup_root, cpu)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        if let Some(cpu) = &controller_opt.resources.cpu() {\n            if cpu.shares().is_some()\n                || cpu.period().is_some()\n                || cpu.quota().is_some()\n                || cpu.realtime_period().is_some()\n                || cpu.realtime_runtime().is_some()\n                || cpu.idle().is_some()\n            {\n                return Some(cpu);\n            }\n        }\n\n        None\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1CpuStatsError {\n    #[error(\"error parsing data: {0}\")]\n    ParseData(#[from] ParseFlatKeyedDataError),\n    #[error(\"missing field {field} from {path}\")]\n    MissingField { field: &'static str, path: PathBuf },\n}\n\nimpl StatsProvider for Cpu {\n    type Error = V1CpuStatsError;\n    type Stats = CpuThrottling;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let mut stats = CpuThrottling::default();\n        let stat_path = cgroup_path.join(CGROUP_CPU_STAT);\n\n        let stat_table = parse_flat_keyed_data(&stat_path)?;\n\n        macro_rules! get {\n            ($name: expr => $field: ident) => {\n                stats.$field =\n                    *stat_table\n                        .get($name)\n                        .ok_or_else(|| V1CpuStatsError::MissingField {\n                            field: $name,\n                            path: stat_path.clone(),\n                        })?;\n            };\n        }\n\n        get!(\"nr_periods\" => periods);\n        get!(\"nr_throttled\" => throttled_periods);\n        get!(\"throttled_time\" => throttled_time);\n\n        Ok(stats)\n    }\n}\n\nimpl Cpu {\n    fn apply(root_path: &Path, cpu: &LinuxCpu) -> Result<(), WrappedIoError> {\n        if let Some(cpu_shares) = cpu.shares() {\n            if cpu_shares != 0 {\n                common::write_cgroup_file(root_path.join(CGROUP_CPU_SHARES), cpu_shares)?;\n            }\n        }\n\n        if let Some(cpu_period) = cpu.period() {\n            if cpu_period != 0 {\n                common::write_cgroup_file(root_path.join(CGROUP_CPU_PERIOD), cpu_period)?;\n            }\n        }\n\n        if let Some(cpu_quota) = cpu.quota() {\n            if cpu_quota != 0 {\n                common::write_cgroup_file(root_path.join(CGROUP_CPU_QUOTA), cpu_quota)?;\n            }\n        }\n\n        if let Some(cpu_burst) = cpu.burst() {\n            common::write_cgroup_file(root_path.join(CGROUP_CPU_BURST), cpu_burst)?;\n        }\n\n        if let Some(rt_runtime) = cpu.realtime_runtime() {\n            if rt_runtime != 0 {\n                common::write_cgroup_file(root_path.join(CGROUP_CPU_RT_RUNTIME), rt_runtime)?;\n            }\n        }\n\n        if let Some(rt_period) = cpu.realtime_period() {\n            if rt_period != 0 {\n                common::write_cgroup_file(root_path.join(CGROUP_CPU_RT_PERIOD), rt_period)?;\n            }\n        }\n\n        if let Some(idle) = cpu.idle() {\n            common::write_cgroup_file(root_path.join(CGROUP_CPU_IDLE), idle)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::*;\n    use crate::test::{set_fixture, setup};\n\n    #[test]\n    fn test_set_shares() {\n        // arrange\n        let (tmp, shares) = setup(CGROUP_CPU_SHARES);\n        let _ = set_fixture(tmp.path(), CGROUP_CPU_SHARES, \"\")\n            .unwrap_or_else(|_| panic!(\"set test fixture for {CGROUP_CPU_SHARES}\"));\n        let cpu = LinuxCpuBuilder::default().shares(2048u64).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(shares)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_SHARES} file content\"));\n        assert_eq!(content, 2048.to_string());\n    }\n\n    #[test]\n    fn test_set_quota() {\n        // arrange\n        const QUOTA: i64 = 200000;\n        let (tmp, max) = setup(CGROUP_CPU_QUOTA);\n        let cpu = LinuxCpuBuilder::default().quota(QUOTA).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_QUOTA} file content\"));\n        assert_eq!(content, QUOTA.to_string());\n    }\n\n    #[test]\n    fn test_set_period() {\n        // arrange\n        const PERIOD: u64 = 100000;\n        let (tmp, max) = setup(CGROUP_CPU_PERIOD);\n        let cpu = LinuxCpuBuilder::default().period(PERIOD).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_PERIOD} file content\"));\n        assert_eq!(content, PERIOD.to_string());\n    }\n\n    #[test]\n    fn test_set_rt_runtime() {\n        // arrange\n        const RUNTIME: i64 = 100000;\n        let (tmp, max) = setup(CGROUP_CPU_RT_RUNTIME);\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_runtime(RUNTIME)\n            .build()\n            .unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_RT_RUNTIME} file content\"));\n        assert_eq!(content, RUNTIME.to_string());\n    }\n\n    #[test]\n    fn test_set_cpu_idle() {\n        // arrange\n        const IDLE: i64 = 1;\n        const CPU: &str = \"cpu\";\n\n        if !Path::new(common::DEFAULT_CGROUP_ROOT)\n            .join(CPU)\n            .join(CGROUP_CPU_IDLE)\n            .exists()\n        {\n            // skip test_set_cpu_idle due to not found cpu.idle, maybe due to old kernel version\n            return;\n        }\n\n        let (tmp, max) = setup(CGROUP_CPU_IDLE);\n        let cpu = LinuxCpuBuilder::default().idle(IDLE).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_IDLE} file content\"));\n        assert_eq!(content, IDLE.to_string());\n    }\n\n    #[test]\n    fn test_set_rt_period() {\n        // arrange\n        const PERIOD: u64 = 100000;\n        let (tmp, max) = setup(CGROUP_CPU_RT_PERIOD);\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_period(PERIOD)\n            .build()\n            .unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_RT_PERIOD} file content\"));\n        assert_eq!(content, PERIOD.to_string());\n    }\n\n    #[test]\n    fn test_stat_cpu_throttling() {\n        let tmp = tempfile::tempdir().unwrap();\n        let stat_content = &[\n            \"nr_periods 165000\",\n            \"nr_throttled 27\",\n            \"throttled_time 1080\",\n        ]\n        .join(\"\\n\");\n        set_fixture(tmp.path(), CGROUP_CPU_STAT, stat_content).expect(\"create stat file\");\n\n        let actual = Cpu::stats(tmp.path()).expect(\"get cgroup stats\");\n        let expected = CpuThrottling {\n            periods: 165000,\n            throttled_periods: 27,\n            throttled_time: 1080,\n        };\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_set_burst() {\n        // arrange\n        let expected_burst: u64 = 100_000;\n        let (tmp, max) = setup(CGROUP_CPU_BURST);\n        let cpu = LinuxCpuBuilder::default()\n            .burst(expected_burst)\n            .build()\n            .unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let actual_burst = fs::read_to_string(max).expect(\"read burst\");\n        assert_eq!(actual_burst, expected_burst.to_string());\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/cpuacct.rs",
    "content": "use std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{CpuUsage, ParseFlatKeyedDataError, StatsProvider, parse_flat_keyed_data};\n\n// Contains user mode and kernel mode cpu consumption\nconst CGROUP_CPUACCT_STAT: &str = \"cpuacct.stat\";\n// Contains overall cpu consumption\nconst CGROUP_CPUACCT_USAGE: &str = \"cpuacct.usage\";\n// Contains user mode and kernel mode cpu consumption differentiated by core\nconst CGROUP_CPUACCT_USAGE_ALL: &str = \"cpuacct.usage_all\";\n// Contains overall cpu consumption differentiated by core\nconst CGROUP_CPUACCT_PERCPU: &str = \"cpuacct.usage_percpu\";\n\npub struct CpuAcct {}\n\nimpl Controller for CpuAcct {\n    type Error = WrappedIoError;\n    type Resource = ();\n\n    fn apply(_controller_opt: &ControllerOpt, _cgroup_path: &Path) -> Result<(), Self::Error> {\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(_controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        None\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1CpuAcctStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"error parsing data: {0}\")]\n    ParseData(#[from] ParseFlatKeyedDataError),\n    #[error(\"missing field {field} from {path}\")]\n    MissingField { field: &'static str, path: PathBuf },\n    #[error(\"failed to parse total cpu usage: {0}\")]\n    ParseTotalCpu(ParseIntError),\n    #[error(\"failed to parse per core {mode} mode cpu usage in {path}: {err}\")]\n    FailedToParseField {\n        mode: &'static str,\n        path: PathBuf,\n        err: ParseIntError,\n    },\n    #[error(\"failed to parse per core cpu usage: {0}\")]\n    ParsePerCore(ParseIntError),\n}\n\nimpl StatsProvider for CpuAcct {\n    type Error = V1CpuAcctStatsError;\n    type Stats = CpuUsage;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, V1CpuAcctStatsError> {\n        let mut stats = CpuUsage::default();\n        Self::get_total_cpu_usage(cgroup_path, &mut stats)?;\n        Self::get_per_core_usage(cgroup_path, &mut stats)?;\n\n        Ok(stats)\n    }\n}\n\nimpl CpuAcct {\n    fn get_total_cpu_usage(\n        cgroup_path: &Path,\n        stats: &mut CpuUsage,\n    ) -> Result<(), V1CpuAcctStatsError> {\n        let stat_file_path = cgroup_path.join(CGROUP_CPUACCT_STAT);\n        let stat_table = parse_flat_keyed_data(&stat_file_path)?;\n\n        macro_rules! get {\n            ($name: expr => $field: ident) => {\n                stats.$field =\n                    *stat_table\n                        .get($name)\n                        .ok_or_else(|| V1CpuAcctStatsError::MissingField {\n                            field: $name,\n                            path: stat_file_path.clone(),\n                        })?;\n            };\n        }\n\n        get!(\"user\" => usage_user);\n        get!(\"system\" => usage_kernel);\n\n        let total = common::read_cgroup_file(cgroup_path.join(CGROUP_CPUACCT_USAGE))?;\n        stats.usage_total = total\n            .trim()\n            .parse()\n            .map_err(V1CpuAcctStatsError::ParseTotalCpu)?;\n\n        Ok(())\n    }\n\n    fn get_per_core_usage(\n        cgroup_path: &Path,\n        stats: &mut CpuUsage,\n    ) -> Result<(), V1CpuAcctStatsError> {\n        let path = cgroup_path.join(CGROUP_CPUACCT_USAGE_ALL);\n        let all_content = common::read_cgroup_file(&path)?;\n        // first line is header, skip it\n        for entry in all_content.lines().skip(1) {\n            let entry_parts: Vec<&str> = entry.split_ascii_whitespace().collect();\n            if entry_parts.len() != 3 {\n                continue;\n            }\n\n            stats\n                .per_core_usage_user\n                .push(entry_parts[1].parse().map_err(|err| {\n                    V1CpuAcctStatsError::FailedToParseField {\n                        mode: \"user\",\n                        path: path.clone(),\n                        err,\n                    }\n                })?);\n            stats\n                .per_core_usage_kernel\n                .push(entry_parts[2].parse().map_err(|err| {\n                    V1CpuAcctStatsError::FailedToParseField {\n                        mode: \"kernel\",\n                        path: path.clone(),\n                        err,\n                    }\n                })?);\n        }\n\n        let percpu_content = common::read_cgroup_file(cgroup_path.join(CGROUP_CPUACCT_PERCPU))?;\n        stats.per_core_usage_total = percpu_content\n            .split_ascii_whitespace()\n            .map(|v| v.parse())\n            .collect::<Result<Vec<_>, _>>()\n            .map_err(V1CpuAcctStatsError::ParsePerCore)?;\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use nix::unistd::Pid;\n    use tempfile::TempDir;\n\n    use super::*;\n    use crate::common::CGROUP_PROCS;\n    use crate::test::{set_fixture, setup};\n\n    fn setup_total_cpu(stat_content: &str, usage_content: &str) -> TempDir {\n        let tmp = tempfile::tempdir().unwrap();\n\n        let _ = set_fixture(tmp.path(), CGROUP_CPUACCT_STAT, stat_content)\n            .unwrap_or_else(|_| panic!(\"create {CGROUP_CPUACCT_STAT} file\"));\n        let _ = set_fixture(tmp.path(), CGROUP_CPUACCT_USAGE, usage_content)\n            .unwrap_or_else(|_| panic!(\"create {CGROUP_CPUACCT_USAGE} file\"));\n\n        tmp\n    }\n\n    fn setup_per_core(percpu_content: &str, usage_all_content: &str) -> TempDir {\n        let tmp = tempfile::tempdir().unwrap();\n\n        let _ = set_fixture(tmp.path(), CGROUP_CPUACCT_PERCPU, percpu_content)\n            .unwrap_or_else(|_| panic!(\"create {CGROUP_CPUACCT_PERCPU} file\"));\n        let _ = set_fixture(tmp.path(), CGROUP_CPUACCT_USAGE_ALL, usage_all_content)\n            .unwrap_or_else(|_| panic!(\"create {CGROUP_CPUACCT_USAGE_ALL} file\"));\n\n        tmp\n    }\n\n    #[test]\n    fn test_add_task() {\n        let (tmp, procs) = setup(CGROUP_PROCS);\n        let pid = Pid::from_raw(1000);\n\n        CpuAcct::add_task(pid, tmp.path()).expect(\"apply cpuacct\");\n\n        let content = fs::read_to_string(procs)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_PROCS} file content\"));\n        assert_eq!(content, \"1000\");\n    }\n\n    #[test]\n    fn test_stat_total_cpu_usage() {\n        let stat_content = &[\"user 1300888\", \"system 364592\"].join(\"\\n\");\n        let usage_content = \"18198092369681\";\n        let tmp = setup_total_cpu(stat_content, usage_content);\n\n        let mut stats = CpuUsage::default();\n        CpuAcct::get_total_cpu_usage(tmp.path(), &mut stats).expect(\"get cgroup stats\");\n\n        assert_eq!(stats.usage_user, 1300888);\n        assert_eq!(stats.usage_kernel, 364592);\n        assert_eq!(stats.usage_total, 18198092369681);\n    }\n\n    #[test]\n    fn test_stat_per_cpu_usage() {\n        let percpu_content = \"989683000640 4409567860144 4439880333849 4273328034121\";\n        let usage_all_content = &[\n            \"cpu user system\",\n            \"0 5838999815217 295316023007\",\n            \"1 4139072325517 325194619244\",\n            \"2 4175712075766 323435639997\",\n            \"3 4021385867300 304269989810\",\n        ]\n        .join(\"\\n\");\n        let tmp = setup_per_core(percpu_content, usage_all_content);\n\n        let mut stats = CpuUsage::default();\n        CpuAcct::get_per_core_usage(tmp.path(), &mut stats).expect(\"get cgroup stats\");\n\n        assert_eq!(\n            stats.per_core_usage_user,\n            [5838999815217, 4139072325517, 4175712075766, 4021385867300]\n        );\n\n        assert_eq!(\n            stats.per_core_usage_kernel,\n            [295316023007, 325194619244, 323435639997, 304269989810]\n        );\n\n        assert_eq!(\n            stats.per_core_usage_total,\n            [989683000640, 4409567860144, 4439880333849, 4273328034121]\n        );\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/cpuset.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf, StripPrefixError};\n\nuse nix::unistd;\nuse oci_spec::runtime::LinuxCpu;\nuse unistd::Pid;\n\nuse super::ControllerType;\nuse super::controller::Controller;\nuse super::util::{self, V1MountPointError};\nuse crate::common::{self, CGROUP_PROCS, ControllerOpt, WrapIoResult, WrappedIoError};\n\nconst CGROUP_CPUSET_CPUS: &str = \"cpuset.cpus\";\nconst CGROUP_CPUSET_MEMS: &str = \"cpuset.mems\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V1CpuSetControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"bad cgroup path {path}: {err}\")]\n    BadCgroupPath {\n        err: StripPrefixError,\n        path: PathBuf,\n    },\n    #[error(\"cpuset parent value is empty\")]\n    EmptyParent,\n    #[error(\"mount point error: {0}\")]\n    MountPoint(#[from] V1MountPointError),\n}\n\npub struct CpuSet {}\n\nimpl Controller for CpuSet {\n    type Error = V1CpuSetControllerError;\n    type Resource = LinuxCpu;\n\n    fn add_task(pid: Pid, cgroup_path: &Path) -> Result<(), Self::Error> {\n        fs::create_dir_all(cgroup_path).wrap_create_dir(cgroup_path)?;\n\n        Self::ensure_not_empty(cgroup_path, CGROUP_CPUSET_CPUS)?;\n        Self::ensure_not_empty(cgroup_path, CGROUP_CPUSET_MEMS)?;\n\n        common::write_cgroup_file(cgroup_path.join(CGROUP_PROCS), pid)?;\n        Ok(())\n    }\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply CpuSet cgroup config\");\n\n        if let Some(cpuset) = Self::needs_to_handle(controller_opt) {\n            Self::apply(cgroup_path, cpuset)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        if let Some(cpuset) = &controller_opt.resources.cpu() {\n            if cpuset.cpus().is_some() || cpuset.mems().is_some() {\n                return Some(cpuset);\n            }\n        }\n\n        None\n    }\n}\n\nimpl CpuSet {\n    fn apply(cgroup_path: &Path, cpuset: &LinuxCpu) -> Result<(), V1CpuSetControllerError> {\n        if let Some(cpus) = &cpuset.cpus() {\n            common::write_cgroup_file_str(cgroup_path.join(CGROUP_CPUSET_CPUS), cpus)?;\n        }\n\n        if let Some(mems) = &cpuset.mems() {\n            common::write_cgroup_file_str(cgroup_path.join(CGROUP_CPUSET_MEMS), mems)?;\n        }\n\n        Ok(())\n    }\n\n    // if a task is moved into the cgroup and a value has not been set for cpus and mems\n    // Errno 28 (no space left on device) will be returned. Therefore we set the value from the parent if required.\n    fn ensure_not_empty(\n        cgroup_path: &Path,\n        interface_file: &str,\n    ) -> Result<(), V1CpuSetControllerError> {\n        let mut current = util::get_subsystem_mount_point(&ControllerType::CpuSet)?;\n        let relative_cgroup_path = cgroup_path.strip_prefix(&current).map_err(|err| {\n            V1CpuSetControllerError::BadCgroupPath {\n                err,\n                path: cgroup_path.to_path_buf(),\n            }\n        })?;\n\n        for component in relative_cgroup_path.components() {\n            let parent_value =\n                fs::read_to_string(current.join(interface_file)).wrap_read(cgroup_path)?;\n            if parent_value.trim().is_empty() {\n                return Err(V1CpuSetControllerError::EmptyParent);\n            }\n\n            current.push(component);\n            let child_path = current.join(interface_file);\n            let child_value = fs::read_to_string(&child_path).wrap_read(&child_path)?;\n            // the file can contain a newline character. Need to trim it away,\n            // otherwise it is not considered empty and value will not be written\n            if child_value.trim().is_empty() {\n                common::write_cgroup_file_str(&child_path, &parent_value)?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::*;\n    use crate::test::setup;\n\n    #[test]\n    fn test_set_cpus() {\n        // arrange\n        let (tmp, cpus) = setup(CGROUP_CPUSET_CPUS);\n        let cpuset = LinuxCpuBuilder::default()\n            .cpus(\"1-3\".to_owned())\n            .build()\n            .unwrap();\n\n        // act\n        CpuSet::apply(tmp.path(), &cpuset).expect(\"apply cpuset\");\n\n        // assert\n        let content = fs::read_to_string(cpus)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPUSET_CPUS} file content\"));\n        assert_eq!(content, \"1-3\");\n    }\n\n    #[test]\n    fn test_set_mems() {\n        // arrange\n        let (tmp, mems) = setup(CGROUP_CPUSET_MEMS);\n        let cpuset = LinuxCpuBuilder::default()\n            .mems(\"1-3\".to_owned())\n            .build()\n            .unwrap();\n\n        // act\n        CpuSet::apply(tmp.path(), &cpuset).expect(\"apply cpuset\");\n\n        // assert\n        let content = fs::read_to_string(mems)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPUSET_MEMS} file content\"));\n        assert_eq!(content, \"1-3\");\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/devices.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxDeviceCgroup;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError, default_allow_devices, default_devices};\n\npub struct Devices {}\n\nimpl Controller for Devices {\n    type Error = WrappedIoError;\n    type Resource = ();\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply Devices cgroup config\");\n\n        if let Some(devices) = controller_opt.resources.devices().as_ref() {\n            for d in devices {\n                Self::apply_device(d, cgroup_root)?;\n            }\n        }\n\n        for d in [\n            default_devices().iter().map(|d| d.into()).collect(),\n            default_allow_devices(),\n        ]\n        .concat()\n        {\n            Self::apply_device(&d, cgroup_root)?;\n        }\n\n        Ok(())\n    }\n\n    // always needs to be called due to default devices\n    fn needs_to_handle<'a>(_controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        Some(&())\n    }\n}\n\nimpl Devices {\n    fn apply_device(device: &LinuxDeviceCgroup, cgroup_root: &Path) -> Result<(), WrappedIoError> {\n        let path = if device.allow() {\n            cgroup_root.join(\"devices.allow\")\n        } else {\n            cgroup_root.join(\"devices.deny\")\n        };\n\n        common::write_cgroup_file_str(path, &device.to_string())?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs::read_to_string;\n\n    use oci_spec::runtime::{LinuxDeviceCgroupBuilder, LinuxDeviceType};\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_default_devices() {\n        let tmp = tempfile::tempdir().unwrap();\n\n        default_allow_devices().iter().for_each(|d| {\n            // NOTE: We reset the fixtures every iteration because files aren't appended\n            // so what happens in the tests is you get strange overwrites which can contain\n            // remaining bytes from the last iteration. Resetting the files more appropriately\n            // mocks the behavior of cgroup files.\n            set_fixture(tmp.path(), \"devices.allow\", \"\").expect(\"create allowed devices list\");\n            set_fixture(tmp.path(), \"devices.deny\", \"\").expect(\"create denied devices list\");\n\n            Devices::apply_device(d, tmp.path()).expect(\"Apply default device\");\n            println!(\"Device: {}\", d);\n            if d.allow() {\n                let allowed_content =\n                    read_to_string(tmp.path().join(\"devices.allow\")).expect(\"read to string\");\n                assert_eq!(allowed_content, d.to_string());\n            } else {\n                let denied_content =\n                    read_to_string(tmp.path().join(\"devices.deny\")).expect(\"read to string\");\n                assert_eq!(denied_content, d.to_string());\n            }\n        });\n    }\n\n    #[test]\n    fn test_set_mock_devices() {\n        let tmp = tempfile::tempdir().unwrap();\n        [\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::C)\n                .major(10)\n                .access(\"rwm\")\n                .build()\n                .unwrap(),\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::A)\n                .minor(200)\n                .access(\"rwm\")\n                .build()\n                .unwrap(),\n            LinuxDeviceCgroupBuilder::default()\n                .allow(false)\n                .typ(LinuxDeviceType::P)\n                .major(10)\n                .minor(200)\n                .access(\"m\")\n                .build()\n                .unwrap(),\n            LinuxDeviceCgroupBuilder::default()\n                .allow(false)\n                .typ(LinuxDeviceType::U)\n                .access(\"rw\")\n                .build()\n                .unwrap(),\n        ]\n        .iter()\n        .for_each(|d| {\n            set_fixture(tmp.path(), \"devices.allow\", \"\").expect(\"create allowed devices list\");\n            set_fixture(tmp.path(), \"devices.deny\", \"\").expect(\"create denied devices list\");\n\n            Devices::apply_device(d, tmp.path()).expect(\"Apply default device\");\n            println!(\"Device: {}\", d);\n            if d.allow() {\n                let allowed_content =\n                    read_to_string(tmp.path().join(\"devices.allow\")).expect(\"read to string\");\n                assert_eq!(allowed_content, d.to_string());\n            } else {\n                let denied_content =\n                    read_to_string(tmp.path().join(\"devices.deny\")).expect(\"read to string\");\n                assert_eq!(denied_content, d.to_string());\n            }\n        });\n    }\n\n    quickcheck! {\n        fn property_test_apply_device(device: LinuxDeviceCgroup) -> bool {\n            let tmp = tempfile::tempdir().unwrap();\n            set_fixture(tmp.path(), \"devices.allow\", \"\").expect(\"create allowed devices list\");\n            set_fixture(tmp.path(), \"devices.deny\", \"\").expect(\"create denied devices list\");\n            Devices::apply_device(&device, tmp.path()).expect(\"Apply default device\");\n            if device.allow() {\n                let allowed_content =\n                    read_to_string(tmp.path().join(\"devices.allow\")).expect(\"read to string\");\n                allowed_content == device.to_string()\n            } else {\n                let denied_content =\n                    read_to_string(tmp.path().join(\"devices.deny\")).expect(\"read to string\");\n                denied_content == device.to_string()\n            }\n        }\n\n        fn property_test_apply_multiple_devices(devices: Vec<LinuxDeviceCgroup>) -> bool {\n            let tmp = tempfile::tempdir().unwrap();\n            devices.iter()\n                .all(|device| {\n                    set_fixture(tmp.path(), \"devices.allow\", \"\").expect(\"create allowed devices list\");\n                    set_fixture(tmp.path(), \"devices.deny\", \"\").expect(\"create denied devices list\");\n                    Devices::apply_device(device, tmp.path()).expect(\"Apply default device\");\n                    if device.allow() {\n                        let allowed_content =\n                            read_to_string(tmp.path().join(\"devices.allow\")).expect(\"read to string\");\n                        allowed_content == device.to_string()\n                    } else {\n                        let denied_content =\n                            read_to_string(tmp.path().join(\"devices.deny\")).expect(\"read to string\");\n                        denied_content == device.to_string()\n                    }\n                })\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/freezer.rs",
    "content": "use std::fs::OpenOptions;\nuse std::io::Read;\nuse std::path::Path;\nuse std::{thread, time};\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, FreezerState, WrapIoResult, WrappedIoError};\n\nconst CGROUP_FREEZER_STATE: &str = \"freezer.state\";\nconst FREEZER_STATE_THAWED: &str = \"THAWED\";\nconst FREEZER_STATE_FROZEN: &str = \"FROZEN\";\nconst FREEZER_STATE_FREEZING: &str = \"FREEZING\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V1FreezerControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"unexpected state {state} while freezing\")]\n    UnexpectedState { state: String },\n    #[error(\"unable to freeze\")]\n    UnableToFreeze,\n}\n\npub struct Freezer {}\n\nimpl Controller for Freezer {\n    type Error = V1FreezerControllerError;\n    type Resource = FreezerState;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply Freezer cgroup config\");\n        std::fs::create_dir_all(cgroup_root).wrap_create_dir(cgroup_root)?;\n\n        if let Some(freezer_state) = Self::needs_to_handle(controller_opt) {\n            Self::apply(freezer_state, cgroup_root)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.freezer_state.as_ref()\n    }\n}\n\nimpl Freezer {\n    fn apply(\n        freezer_state: &FreezerState,\n        cgroup_root: &Path,\n    ) -> Result<(), V1FreezerControllerError> {\n        match freezer_state {\n            FreezerState::Undefined => {}\n            FreezerState::Thawed => {\n                common::write_cgroup_file(\n                    cgroup_root.join(CGROUP_FREEZER_STATE),\n                    FREEZER_STATE_THAWED,\n                )?;\n            }\n            FreezerState::Frozen => {\n                let r = || -> Result<(), V1FreezerControllerError> {\n                    // We should do our best to retry if FREEZING is seen until it becomes FROZEN.\n                    // Add sleep between retries occasionally helped when system is extremely slow.\n                    // see:\n                    // https://github.com/opencontainers/runc/blob/b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7/libcontainer/cgroups/fs/freezer.go#L42\n                    for i in 0..1000 {\n                        if i % 50 == 49 {\n                            let _ = common::write_cgroup_file(\n                                cgroup_root.join(CGROUP_FREEZER_STATE),\n                                FREEZER_STATE_THAWED,\n                            );\n                            thread::sleep(time::Duration::from_millis(10));\n                        }\n\n                        common::write_cgroup_file(\n                            cgroup_root.join(CGROUP_FREEZER_STATE),\n                            FREEZER_STATE_FROZEN,\n                        )?;\n\n                        if i % 25 == 24 {\n                            thread::sleep(time::Duration::from_millis(10));\n                        }\n\n                        let r = Self::read_freezer_state(cgroup_root)?;\n                        match r.trim() {\n                            FREEZER_STATE_FREEZING => {\n                                continue;\n                            }\n                            FREEZER_STATE_FROZEN => {\n                                if i > 1 {\n                                    tracing::debug!(\"frozen after {} retries\", i)\n                                }\n                                return Ok(());\n                            }\n                            _ => {\n                                // should not reach here.\n                                return Err(V1FreezerControllerError::UnexpectedState { state: r });\n                            }\n                        }\n                    }\n                    Err(V1FreezerControllerError::UnableToFreeze)\n                }();\n\n                if r.is_err() {\n                    // Freezing failed, and it is bad and dangerous to leave the cgroup in FROZEN or\n                    // FREEZING, so try to thaw it back.\n                    let _ = common::write_cgroup_file(\n                        cgroup_root.join(CGROUP_FREEZER_STATE),\n                        FREEZER_STATE_THAWED,\n                    );\n                }\n                return r;\n            }\n        }\n        Ok(())\n    }\n\n    fn read_freezer_state(cgroup_root: &Path) -> Result<String, WrappedIoError> {\n        let path = cgroup_root.join(CGROUP_FREEZER_STATE);\n        let mut content = String::new();\n        OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(path)\n            .wrap_open(cgroup_root)?\n            .read_to_string(&mut content)\n            .wrap_read(cgroup_root)?;\n        Ok(content)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use nix::unistd::Pid;\n    use oci_spec::runtime::LinuxResourcesBuilder;\n\n    use super::*;\n    use crate::common::{CGROUP_PROCS, FreezerState};\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_freezer_state() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_FREEZER_STATE, \"\").expect(\"Set fixure for freezer state\");\n\n        // set Frozen state.\n        {\n            let freezer_state = FreezerState::Frozen;\n            Freezer::apply(&freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"Read to string\");\n            assert_eq!(FREEZER_STATE_FROZEN, state_content);\n        }\n\n        // set Thawed state.\n        {\n            let freezer_state = FreezerState::Thawed;\n            Freezer::apply(&freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"Read to string\");\n            assert_eq!(FREEZER_STATE_THAWED, state_content);\n        }\n\n        // set Undefined state.\n        {\n            let old_state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"Read to string\");\n            let freezer_state = FreezerState::Undefined;\n            Freezer::apply(&freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"Read to string\");\n            assert_eq!(old_state_content, state_content);\n        }\n    }\n\n    #[test]\n    fn test_add_and_apply() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_FREEZER_STATE, \"\").expect(\"set fixure for freezer state\");\n        set_fixture(tmp.path(), CGROUP_PROCS, \"\").expect(\"set fixture for proc file\");\n\n        // set Thawed state.\n        {\n            let linux_resources = LinuxResourcesBuilder::default()\n                .devices(vec![])\n                .hugepage_limits(vec![])\n                .build()\n                .unwrap();\n            let state = FreezerState::Thawed;\n\n            let controller_opt = ControllerOpt {\n                resources: &linux_resources,\n                freezer_state: Some(state),\n                oom_score_adj: None,\n                disable_oom_killer: false,\n            };\n\n            let pid = Pid::from_raw(1000);\n            Freezer::add_task(pid, tmp.path()).expect(\"freezer add task\");\n            <Freezer as Controller>::apply(&controller_opt, tmp.path()).expect(\"freezer apply\");\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"read to string\");\n            assert_eq!(FREEZER_STATE_THAWED, state_content);\n            let pid_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_PROCS)).expect(\"read to string\");\n            assert_eq!(pid_content, \"1000\");\n        }\n\n        // set Frozen state.\n        {\n            let linux_resources = LinuxResourcesBuilder::default()\n                .devices(vec![])\n                .hugepage_limits(vec![])\n                .build()\n                .unwrap();\n            let state = FreezerState::Frozen;\n\n            let controller_opt = ControllerOpt {\n                resources: &linux_resources,\n                freezer_state: Some(state),\n                oom_score_adj: None,\n                disable_oom_killer: false,\n            };\n\n            let pid = Pid::from_raw(1001);\n            Freezer::add_task(pid, tmp.path()).expect(\"freezer add task\");\n            <Freezer as Controller>::apply(&controller_opt, tmp.path()).expect(\"freezer apply\");\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"read to string\");\n            assert_eq!(FREEZER_STATE_FROZEN, state_content);\n            let pid_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_PROCS)).expect(\"read to string\");\n            assert_eq!(pid_content, \"1001\");\n        }\n\n        // set Undefined state.\n        {\n            let linux_resources = LinuxResourcesBuilder::default()\n                .devices(vec![])\n                .hugepage_limits(vec![])\n                .build()\n                .unwrap();\n\n            let state = FreezerState::Undefined;\n\n            let controller_opt = ControllerOpt {\n                resources: &linux_resources,\n                freezer_state: Some(state),\n                oom_score_adj: None,\n                disable_oom_killer: false,\n            };\n\n            let pid = Pid::from_raw(1002);\n            let old_state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"read to string\");\n            Freezer::add_task(pid, tmp.path()).expect(\"freezer add task\");\n            <Freezer as Controller>::apply(&controller_opt, tmp.path()).expect(\"freezer apply\");\n            let state_content = std::fs::read_to_string(tmp.path().join(CGROUP_FREEZER_STATE))\n                .expect(\"read to string\");\n            assert_eq!(old_state_content, state_content);\n            let pid_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_PROCS)).expect(\"read to string\");\n            assert_eq!(pid_content, \"1002\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/hugetlb.rs",
    "content": "use std::collections::HashMap;\nuse std::num::ParseIntError;\nuse std::path::Path;\n\nuse oci_spec::runtime::LinuxHugepageLimit;\n\nuse super::controller::Controller;\nuse crate::common::{\n    self, ControllerOpt, EitherError, MustBePowerOfTwo, WrappedIoError, read_cgroup_file,\n};\nuse crate::stats::{HugeTlbStats, StatsProvider, SupportedPageSizesError, supported_page_sizes};\n\n#[derive(thiserror::Error, Debug)]\npub enum V1HugeTlbControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"malformed page size {page_size}: {err}\")]\n    MalformedPageSize {\n        page_size: String,\n        err: EitherError<ParseIntError, MustBePowerOfTwo>,\n    },\n}\n\npub struct HugeTlb {}\n\nimpl Controller for HugeTlb {\n    type Error = V1HugeTlbControllerError;\n    type Resource = Vec<LinuxHugepageLimit>;\n\n    fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_root: &std::path::Path,\n    ) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply Hugetlb cgroup config\");\n\n        if let Some(hugepage_limits) = Self::needs_to_handle(controller_opt) {\n            for hugetlb in hugepage_limits {\n                Self::apply(cgroup_root, hugetlb)?\n            }\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        if let Some(hugepage_limits) = controller_opt.resources.hugepage_limits() {\n            if !hugepage_limits.is_empty() {\n                return controller_opt.resources.hugepage_limits().as_ref();\n            }\n        }\n\n        None\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1HugeTlbStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"error getting supported page sizes: {0}\")]\n    SupportedPageSizes(#[from] SupportedPageSizesError),\n    #[error(\"error parsing value: {0}\")]\n    Parse(#[from] ParseIntError),\n}\n\nimpl StatsProvider for HugeTlb {\n    type Error = V1HugeTlbStatsError;\n    type Stats = HashMap<String, HugeTlbStats>;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let page_sizes = supported_page_sizes()?;\n        let mut hugetlb_stats = HashMap::with_capacity(page_sizes.len());\n\n        for page_size in &page_sizes {\n            let stats = Self::stats_for_page_size(cgroup_path, page_size)?;\n            hugetlb_stats.insert(page_size.to_owned(), stats);\n        }\n\n        Ok(hugetlb_stats)\n    }\n}\n\nimpl HugeTlb {\n    fn apply(\n        root_path: &Path,\n        hugetlb: &LinuxHugepageLimit,\n    ) -> Result<(), V1HugeTlbControllerError> {\n        let raw_page_size: String = hugetlb\n            .page_size()\n            .chars()\n            .take_while(|c| c.is_ascii_digit())\n            .collect();\n        let page_size: u64 = match raw_page_size.parse() {\n            Ok(page_size) => page_size,\n            Err(err) => {\n                return Err(V1HugeTlbControllerError::MalformedPageSize {\n                    page_size: raw_page_size,\n                    err: EitherError::Left(err),\n                });\n            }\n        };\n        if !Self::is_power_of_two(page_size) {\n            return Err(V1HugeTlbControllerError::MalformedPageSize {\n                page_size: raw_page_size,\n                err: EitherError::Right(MustBePowerOfTwo),\n            });\n        }\n\n        common::write_cgroup_file(\n            root_path.join(format!(\"hugetlb.{}.limit_in_bytes\", hugetlb.page_size())),\n            hugetlb.limit(),\n        )?;\n\n        let rsvd_file_path = root_path.join(format!(\n            \"hugetlb.{}.rsvd.limit_in_bytes\",\n            hugetlb.page_size()\n        ));\n        if rsvd_file_path.exists() {\n            common::write_cgroup_file(rsvd_file_path, hugetlb.limit())?;\n        }\n\n        Ok(())\n    }\n\n    fn is_power_of_two(number: u64) -> bool {\n        (number != 0) && (number & (number.saturating_sub(1))) == 0\n    }\n\n    fn stats_for_page_size(\n        cgroup_path: &Path,\n        page_size: &str,\n    ) -> Result<HugeTlbStats, V1HugeTlbStatsError> {\n        let mut stats = HugeTlbStats::default();\n        let mut file_prefix = format!(\"hugetlb.{page_size}.rsvd\");\n        let mut usage_file = format!(\"{file_prefix}.usage_in_bytes\");\n        let usage_content = read_cgroup_file(cgroup_path.join(&usage_file)).or_else(|_| {\n            file_prefix = format!(\"hugetlb.{page_size}\");\n            usage_file = format!(\"{file_prefix}.usage_in_bytes\");\n            read_cgroup_file(cgroup_path.join(&usage_file))\n        })?;\n        stats.usage = usage_content.trim().parse()?;\n\n        let max_file = format!(\"{file_prefix}.max_usage_in_bytes\");\n        let max_content = common::read_cgroup_file(cgroup_path.join(max_file))?;\n        stats.max_usage = max_content.trim().parse()?;\n\n        let failcnt_file = format!(\"{file_prefix}.failcnt\");\n        let failcnt_content = common::read_cgroup_file(cgroup_path.join(failcnt_file))?;\n        stats.fail_count = failcnt_content.trim().parse()?;\n\n        Ok(stats)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs::read_to_string;\n\n    use oci_spec::runtime::LinuxHugepageLimitBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_hugetlb() {\n        let page_file_name = \"hugetlb.2MB.limit_in_bytes\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), page_file_name, \"0\").expect(\"Set fixture for 2 MB page size\");\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"2MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n\n        HugeTlb::apply(tmp.path(), &hugetlb).expect(\"apply hugetlb\");\n        let content =\n            read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n        assert_eq!(hugetlb.limit().to_string(), content);\n    }\n\n    #[test]\n    fn test_set_rsvd_hugetlb() {\n        let page_file_name = \"hugetlb.2MB.limit_in_bytes\";\n        let rsvd_page_file_name = \"hugetlb.2MB.rsvd.limit_in_bytes\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), page_file_name, \"0\").expect(\"Set fixture for 2 MB page size\");\n        set_fixture(tmp.path(), rsvd_page_file_name, \"0\")\n            .expect(\"Set fixture for 2 MB rsvd page size\");\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"2MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n\n        HugeTlb::apply(tmp.path(), &hugetlb).expect(\"apply hugetlb\");\n        let content =\n            read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n        let rsvd_content = read_to_string(tmp.path().join(rsvd_page_file_name))\n            .expect(\"Read rsvd hugetlb file content\");\n\n        // Both files should have been written to\n        assert_eq!(hugetlb.limit().to_string(), content);\n        assert_eq!(hugetlb.limit().to_string(), rsvd_content);\n    }\n\n    #[test]\n    fn test_set_hugetlb_with_invalid_page_size() {\n        let tmp = tempfile::tempdir().unwrap();\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"3MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n\n        let result = HugeTlb::apply(tmp.path(), &hugetlb);\n        assert!(\n            result.is_err(),\n            \"page size that is not a power of two should be an error\"\n        );\n    }\n\n    quickcheck! {\n        fn property_test_set_hugetlb(hugetlb: LinuxHugepageLimit) -> bool {\n            let page_file_name = format!(\"hugetlb.{:?}.limit_in_bytes\", hugetlb.page_size());\n            let tmp = tempfile::tempdir().unwrap();\n            set_fixture(tmp.path(), &page_file_name, \"0\").expect(\"Set fixture for page size\");\n\n            let result = HugeTlb::apply(tmp.path(), &hugetlb);\n\n            let page_size: String = hugetlb\n            .page_size()\n            .chars()\n            .take_while(|c| c.is_ascii_digit())\n            .collect();\n            let page_size: u64 = page_size.parse().expect(\"parse page size\");\n\n            if HugeTlb::is_power_of_two(page_size) && page_size != 1 {\n                let content =\n                    read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n                hugetlb.limit().to_string() == content\n            } else {\n                result.is_err()\n            }\n        }\n    }\n\n    #[test]\n    fn test_stat_hugetlb() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"hugetlb.2MB.usage_in_bytes\", \"1024\\n\").expect(\"set hugetlb usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.max_usage_in_bytes\", \"4096\\n\")\n            .expect(\"set hugetlb max usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.failcnt\", \"5\").expect(\"set hugetlb fail count\");\n\n        let actual = HugeTlb::stats_for_page_size(tmp.path(), \"2MB\").expect(\"get cgroup stats\");\n\n        let expected = HugeTlbStats {\n            usage: 1024,\n            max_usage: 4096,\n            fail_count: 5,\n        };\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_stat_rsvd_hugetlb() {\n        let tmp = tempfile::tempdir().unwrap();\n\n        set_fixture(tmp.path(), \"hugetlb.2MB.rsvd.usage_in_bytes\", \"1024\\n\")\n            .expect(\"set hugetlb usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.rsvd.max_usage_in_bytes\", \"4096\\n\")\n            .expect(\"set hugetlb max usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.rsvd.failcnt\", \"5\").expect(\"set hugetlb fail count\");\n\n        set_fixture(tmp.path(), \"hugetlb.2MB.usage_in_bytes\", \"2048\\n\").expect(\"set hugetlb usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.max_usage_in_bytes\", \"8192\\n\")\n            .expect(\"set hugetlb max usage\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.failcnt\", \"10\").expect(\"set hugetlb fail count\");\n\n        let actual = HugeTlb::stats_for_page_size(tmp.path(), \"2MB\").expect(\"get cgroup stats\");\n\n        // Should prefer rsvd stats over non-rsvd stats\n        let expected = HugeTlbStats {\n            usage: 1024,\n            max_usage: 4096,\n            fail_count: 5,\n        };\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/manager.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::path::{Path, PathBuf};\nuse std::time::Duration;\n\nuse nix::unistd::Pid;\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\nuse procfs::{FromRead, ProcError, ProcessCGroups};\n\nuse super::blkio::{Blkio, V1BlkioStatsError};\nuse super::controller::Controller;\nuse super::controller_type::CONTROLLERS;\nuse super::cpu::{Cpu, V1CpuStatsError};\nuse super::cpuacct::{CpuAcct, V1CpuAcctStatsError};\nuse super::cpuset::{CpuSet, V1CpuSetControllerError};\nuse super::devices::Devices;\nuse super::freezer::{Freezer, V1FreezerControllerError};\nuse super::hugetlb::{HugeTlb, V1HugeTlbControllerError, V1HugeTlbStatsError};\nuse super::memory::{Memory, V1MemoryControllerError, V1MemoryStatsError};\nuse super::network_classifier::NetworkClassifier;\nuse super::network_priority::NetworkPriority;\nuse super::perf_event::PerfEvent;\nuse super::pids::Pids;\nuse super::util::V1MountPointError;\nuse super::{ControllerType as CtrlType, util};\nuse crate::common::{\n    self, AnyCgroupManager, CGROUP_PROCS, CgroupManager, ControllerOpt, FreezerState,\n    JoinSafelyError, PathBufExt, WrapIoResult, WrappedIoError,\n};\nuse crate::stats::{PidStatsError, Stats, StatsProvider};\n\npub struct Manager {\n    subsystems: HashMap<CtrlType, PathBuf>,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1ManagerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"mount point error: {0}\")]\n    MountPoint(#[from] V1MountPointError),\n    #[error(\"proc error: {0}\")]\n    Proc(#[from] ProcError),\n    #[error(\"while joining paths: {0}\")]\n    JoinSafely(#[from] JoinSafelyError),\n    #[error(\"cgroup {0} is required to fulfill the request, but is not supported by this system\")]\n    CGroupRequired(CtrlType),\n    #[error(\"subsystem does not exist\")]\n    SubsystemDoesNotExist,\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n\n    #[error(transparent)]\n    BlkioController(WrappedIoError),\n    #[error(transparent)]\n    CpuController(WrappedIoError),\n    #[error(transparent)]\n    CpuAcctController(WrappedIoError),\n    #[error(transparent)]\n    CpuSetController(#[from] V1CpuSetControllerError),\n    #[error(transparent)]\n    FreezerController(#[from] V1FreezerControllerError),\n    #[error(transparent)]\n    HugeTlbController(#[from] V1HugeTlbControllerError),\n    #[error(transparent)]\n    MemoryController(#[from] V1MemoryControllerError),\n    #[error(transparent)]\n    PidsController(WrappedIoError),\n\n    #[error(transparent)]\n    BlkioStats(#[from] V1BlkioStatsError),\n    #[error(transparent)]\n    CpuStats(#[from] V1CpuStatsError),\n    #[error(transparent)]\n    CpuAcctStats(#[from] V1CpuAcctStatsError),\n    #[error(transparent)]\n    PidsStats(#[from] PidStatsError),\n    #[error(transparent)]\n    HugeTlbStats(#[from] V1HugeTlbStatsError),\n    #[error(transparent)]\n    MemoryStats(#[from] V1MemoryStatsError),\n}\n\nimpl Manager {\n    /// Constructs a new cgroup manager with cgroups_path being relative to the root of the subsystem\n    pub fn new(cgroup_path: &Path) -> Result<Self, V1ManagerError> {\n        let mut subsystems = HashMap::new();\n        for subsystem in CONTROLLERS {\n            if let Ok(subsystem_path) = Self::get_subsystem_path(cgroup_path, subsystem) {\n                subsystems.insert(*subsystem, subsystem_path);\n            } else {\n                tracing::warn!(\"cgroup {} not supported on this system\", subsystem);\n            }\n        }\n\n        Ok(Manager { subsystems })\n    }\n\n    fn get_subsystem_path(\n        cgroup_path: &Path,\n        subsystem: &CtrlType,\n    ) -> Result<PathBuf, V1ManagerError> {\n        tracing::debug!(\"Get path for subsystem: {}\", subsystem);\n        let mount_point = util::get_subsystem_mount_point(subsystem)?;\n\n        let cgroup = ProcessCGroups::from_read(ProcfsHandle::new()?.open(\n            ProcfsBase::ProcSelf,\n            \"cgroup\",\n            OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n        )?)?\n        .into_iter()\n        .find(|c| c.controllers.contains(&subsystem.to_string()))\n        .ok_or(V1ManagerError::SubsystemDoesNotExist)?;\n\n        let p = if cgroup_path.as_os_str().is_empty() {\n            mount_point.join_safely(Path::new(&cgroup.pathname))?\n        } else {\n            mount_point.join_safely(cgroup_path)?\n        };\n\n        Ok(p)\n    }\n\n    fn get_required_controllers(\n        &self,\n        controller_opt: &ControllerOpt,\n    ) -> Result<HashMap<&CtrlType, &PathBuf>, V1ManagerError> {\n        let mut required_controllers = HashMap::new();\n\n        for controller in CONTROLLERS {\n            let required = match controller {\n                CtrlType::Cpu => Cpu::needs_to_handle(controller_opt).is_some(),\n                CtrlType::CpuAcct => CpuAcct::needs_to_handle(controller_opt).is_some(),\n                CtrlType::CpuSet => CpuSet::needs_to_handle(controller_opt).is_some(),\n                CtrlType::Devices => Devices::needs_to_handle(controller_opt).is_some(),\n                CtrlType::HugeTlb => HugeTlb::needs_to_handle(controller_opt).is_some(),\n                CtrlType::Memory => Memory::needs_to_handle(controller_opt).is_some(),\n                CtrlType::Pids => Pids::needs_to_handle(controller_opt).is_some(),\n                CtrlType::PerfEvent => PerfEvent::needs_to_handle(controller_opt).is_some(),\n                CtrlType::Blkio => Blkio::needs_to_handle(controller_opt).is_some(),\n                CtrlType::NetworkPriority => {\n                    NetworkPriority::needs_to_handle(controller_opt).is_some()\n                }\n                CtrlType::NetworkClassifier => {\n                    NetworkClassifier::needs_to_handle(controller_opt).is_some()\n                }\n                CtrlType::Freezer => Freezer::needs_to_handle(controller_opt).is_some(),\n            };\n\n            if required {\n                if let Some(subsystem_path) = self.subsystems.get(controller) {\n                    required_controllers.insert(controller, subsystem_path);\n                } else {\n                    return Err(V1ManagerError::CGroupRequired(*controller));\n                }\n            }\n        }\n\n        Ok(required_controllers)\n    }\n\n    pub fn any(self) -> AnyCgroupManager {\n        AnyCgroupManager::V1(self)\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = V1ManagerError;\n\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {\n        let devices = self.subsystems.get(&CtrlType::Devices);\n        if let Some(p) = devices {\n            Ok(common::get_all_pids(p)?)\n        } else {\n            Err(V1ManagerError::SubsystemDoesNotExist)\n        }\n    }\n\n    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {\n        for (ctrl_type, cgroup_path) in &self.subsystems {\n            match ctrl_type {\n                CtrlType::Cpu => Cpu::add_task(pid, cgroup_path)?,\n                CtrlType::CpuAcct => CpuAcct::add_task(pid, cgroup_path)?,\n                CtrlType::CpuSet => CpuSet::add_task(pid, cgroup_path)?,\n                CtrlType::Devices => Devices::add_task(pid, cgroup_path)?,\n                CtrlType::HugeTlb => HugeTlb::add_task(pid, cgroup_path)?,\n                CtrlType::Memory => Memory::add_task(pid, cgroup_path)?,\n                CtrlType::Pids => Pids::add_task(pid, cgroup_path)?,\n                CtrlType::PerfEvent => PerfEvent::add_task(pid, cgroup_path)?,\n                CtrlType::Blkio => Blkio::add_task(pid, cgroup_path)?,\n                CtrlType::NetworkPriority => NetworkPriority::add_task(pid, cgroup_path)?,\n                CtrlType::NetworkClassifier => NetworkClassifier::add_task(pid, cgroup_path)?,\n                CtrlType::Freezer => Freezer::add_task(pid, cgroup_path)?,\n            }\n        }\n\n        Ok(())\n    }\n\n    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {\n        for (ctrl_type, cgroup_path) in self.get_required_controllers(controller_opt)? {\n            match ctrl_type {\n                CtrlType::Cpu => Cpu::apply(controller_opt, cgroup_path)?,\n                CtrlType::CpuAcct => CpuAcct::apply(controller_opt, cgroup_path)?,\n                CtrlType::CpuSet => CpuSet::apply(controller_opt, cgroup_path)?,\n                CtrlType::Devices => Devices::apply(controller_opt, cgroup_path)?,\n                CtrlType::HugeTlb => HugeTlb::apply(controller_opt, cgroup_path)?,\n                CtrlType::Memory => Memory::apply(controller_opt, cgroup_path)?,\n                CtrlType::Pids => Pids::apply(controller_opt, cgroup_path)?,\n                CtrlType::PerfEvent => PerfEvent::apply(controller_opt, cgroup_path)?,\n                CtrlType::Blkio => Blkio::apply(controller_opt, cgroup_path)?,\n                CtrlType::NetworkPriority => NetworkPriority::apply(controller_opt, cgroup_path)?,\n                CtrlType::NetworkClassifier => {\n                    NetworkClassifier::apply(controller_opt, cgroup_path)?\n                }\n                CtrlType::Freezer => Freezer::apply(controller_opt, cgroup_path)?,\n            }\n        }\n\n        Ok(())\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        for cgroup_path in self.subsystems.values() {\n            if cgroup_path.exists() {\n                tracing::debug!(\"remove cgroup {:?}\", cgroup_path);\n                let procs_path = cgroup_path.join(CGROUP_PROCS);\n                let procs = fs::read_to_string(&procs_path).wrap_read(&procs_path)?;\n\n                for line in procs.lines() {\n                    let pid: i32 = line\n                        .parse()\n                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))\n                        .wrap_other(&procs_path)?;\n                    let _ = nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL);\n                }\n\n                common::delete_with_retry(cgroup_path, 4, Duration::from_millis(100))?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {\n        let controller_opt = ControllerOpt {\n            resources: &Default::default(),\n            freezer_state: Some(state),\n            oom_score_adj: None,\n            disable_oom_killer: false,\n        };\n        Ok(Freezer::apply(\n            &controller_opt,\n            self.subsystems\n                .get(&CtrlType::Freezer)\n                .ok_or(V1ManagerError::SubsystemDoesNotExist)?,\n        )?)\n    }\n\n    fn stats(&self) -> Result<Stats, Self::Error> {\n        let mut stats = Stats::default();\n\n        for (ctrl_type, cgroup_path) in &self.subsystems {\n            match ctrl_type {\n                CtrlType::Cpu => stats.cpu.throttling = Cpu::stats(cgroup_path)?,\n                CtrlType::CpuAcct => stats.cpu.usage = CpuAcct::stats(cgroup_path)?,\n                CtrlType::Pids => stats.pids = Pids::stats(cgroup_path)?,\n                CtrlType::HugeTlb => stats.hugetlb = HugeTlb::stats(cgroup_path)?,\n                CtrlType::Blkio => stats.blkio = Blkio::stats(cgroup_path)?,\n                CtrlType::Memory => stats.memory = Memory::stats(cgroup_path)?,\n                _ => continue,\n            }\n        }\n\n        Ok(stats)\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/memory.rs",
    "content": "use std::collections::HashMap;\nuse std::fmt::Display;\nuse std::fs::OpenOptions;\nuse std::io::Write;\nuse std::io::prelude::*;\nuse std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse nix::errno::Errno;\nuse oci_spec::runtime::LinuxMemory;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrapIoResult, WrappedIoError};\nuse crate::stats::{\n    self, MemoryData, MemoryStats, ParseFlatKeyedDataError, StatsProvider, parse_single_value,\n};\n\nconst CGROUP_MEMORY_SWAP_LIMIT: &str = \"memory.memsw.limit_in_bytes\";\nconst CGROUP_MEMORY_LIMIT: &str = \"memory.limit_in_bytes\";\nconst CGROUP_MEMORY_USAGE: &str = \"memory.usage_in_bytes\";\nconst CGROUP_MEMORY_MAX_USAGE: &str = \"memory.max_usage_in_bytes\";\nconst CGROUP_MEMORY_SWAPPINESS: &str = \"memory.swappiness\";\nconst CGROUP_MEMORY_RESERVATION: &str = \"memory.soft_limit_in_bytes\";\nconst CGROUP_MEMORY_OOM_CONTROL: &str = \"memory.oom_control\";\n\nconst CGROUP_KERNEL_MEMORY_LIMIT: &str = \"memory.kmem.limit_in_bytes\";\nconst CGROUP_KERNEL_TCP_MEMORY_LIMIT: &str = \"memory.kmem.tcp.limit_in_bytes\";\n\n// Shows various memory statistics\nconst MEMORY_STAT: &str = \"memory.stat\";\n//\nconst MEMORY_USE_HIERARCHY: &str = \"memory.use_hierarchy\";\n// Prefix for memory cgroup files\nconst MEMORY_PREFIX: &str = \"memory\";\n// Prefix for memory and swap cgroup files\nconst MEMORY_AND_SWAP_PREFIX: &str = \"memory.memsw\";\n// Prefix for kernel memory cgroup files\nconst MEMORY_KERNEL_PREFIX: &str = \"memory.kmem\";\n// Prefix for kernel tcp memory cgroup files\nconst MEMORY_KERNEL_TCP_PREFIX: &str = \"memory.kmem.tcp\";\n// Memory usage in bytes\nconst MEMORY_USAGE_IN_BYTES: &str = \".usage_in_bytes\";\n// Maximum recorded memory usage\nconst MEMORY_MAX_USAGE_IN_BYTES: &str = \".max_usage_in_bytes\";\n// Memory usage limit in bytes\nconst MEMORY_LIMIT_IN_BYTES: &str = \".limit_in_bytes\";\n// Number of times memory usage hit limits\nconst MEMORY_FAIL_COUNT: &str = \".failcnt\";\n\n#[derive(Debug)]\npub enum MalformedThing {\n    Limit,\n    Usage,\n    MaxUsage,\n}\n\nimpl Display for MalformedThing {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            MalformedThing::Limit => f.write_str(\"memory limit\"),\n            MalformedThing::Usage => f.write_str(\"memory usage\"),\n            MalformedThing::MaxUsage => f.write_str(\"memory max usage\"),\n        }\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V1MemoryControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"invalid swappiness value: {supplied}. valid range is 0-100\")]\n    SwappinessOutOfRange { supplied: u64 },\n    #[error(\"read malformed {thing} {limit} from {path}: {err}\")]\n    MalformedValue {\n        thing: MalformedThing,\n        limit: String,\n        path: PathBuf,\n        err: ParseIntError,\n    },\n    #[error(\n        \"unable to set memory limit to {target} (current usage: {current}, peak usage: {peak})\"\n    )]\n    UnableToSet {\n        target: i64,\n        current: u64,\n        peak: u64,\n    },\n}\n\npub struct Memory {}\n\nimpl Controller for Memory {\n    type Error = V1MemoryControllerError;\n    type Resource = LinuxMemory;\n\n    fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_root: &Path,\n    ) -> Result<(), V1MemoryControllerError> {\n        tracing::debug!(\"Apply Memory cgroup config\");\n\n        if let Some(memory) = &controller_opt.resources.memory() {\n            let reservation = memory.reservation().unwrap_or(0);\n\n            Self::apply(memory, cgroup_root)?;\n\n            if reservation != 0 {\n                common::write_cgroup_file(\n                    cgroup_root.join(CGROUP_MEMORY_RESERVATION),\n                    reservation,\n                )?;\n            }\n\n            Self::set_oom_control(cgroup_root, controller_opt.disable_oom_killer)?;\n\n            if let Some(swappiness) = memory.swappiness() {\n                if swappiness <= 100 {\n                    common::write_cgroup_file(\n                        cgroup_root.join(CGROUP_MEMORY_SWAPPINESS),\n                        swappiness,\n                    )?;\n                } else {\n                    // invalid swappiness value\n                    return Err(V1MemoryControllerError::SwappinessOutOfRange {\n                        supplied: swappiness,\n                    });\n                }\n            }\n\n            // NOTE: Seems as though kernel and kernelTCP are both deprecated\n            // neither are implemented by runc. Tests pass without this, but\n            // kept in per the spec.\n            if let Some(kmem) = memory.kernel() {\n                common::write_cgroup_file(cgroup_root.join(CGROUP_KERNEL_MEMORY_LIMIT), kmem)?;\n            }\n            if let Some(tcp_mem) = memory.kernel_tcp() {\n                common::write_cgroup_file(\n                    cgroup_root.join(CGROUP_KERNEL_TCP_MEMORY_LIMIT),\n                    tcp_mem,\n                )?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.resources.memory().as_ref()\n    }\n}\n#[derive(thiserror::Error, Debug)]\npub enum V1MemoryStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"error parsing stat data: {0}\")]\n    Parse(#[from] ParseFlatKeyedDataError),\n}\n\nimpl StatsProvider for Memory {\n    type Error = V1MemoryStatsError;\n    type Stats = MemoryStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let memory = Self::get_memory_data(cgroup_path, MEMORY_PREFIX)?;\n        let memswap = Self::get_memory_data(cgroup_path, MEMORY_AND_SWAP_PREFIX)?;\n        let kernel = Self::get_memory_data(cgroup_path, MEMORY_KERNEL_PREFIX)?;\n        let kernel_tcp = Self::get_memory_data(cgroup_path, MEMORY_KERNEL_TCP_PREFIX)?;\n        let hierarchy = Self::hierarchy_enabled(cgroup_path)?;\n        let stats = Self::get_stat_data(cgroup_path)?;\n\n        Ok(MemoryStats {\n            memory,\n            memswap,\n            kernel,\n            kernel_tcp,\n            cache: stats[\"cache\"],\n            hierarchy,\n            stats,\n            ..Default::default()\n        })\n    }\n}\n\nimpl Memory {\n    fn get_memory_data(\n        cgroup_path: &Path,\n        file_prefix: &str,\n    ) -> Result<MemoryData, WrappedIoError> {\n        let memory_data = MemoryData {\n            usage: parse_single_value(\n                &cgroup_path.join(format!(\"{file_prefix}{MEMORY_USAGE_IN_BYTES}\")),\n            )?,\n            max_usage: parse_single_value(\n                &cgroup_path.join(format!(\"{file_prefix}{MEMORY_MAX_USAGE_IN_BYTES}\")),\n            )?,\n            limit: parse_single_value(\n                &cgroup_path.join(format!(\"{file_prefix}{MEMORY_LIMIT_IN_BYTES}\")),\n            )?,\n            fail_count: parse_single_value(\n                &cgroup_path.join(format!(\"{file_prefix}{MEMORY_FAIL_COUNT}\")),\n            )?,\n        };\n\n        Ok(memory_data)\n    }\n\n    fn set_oom_control(cgroup_root: &Path, disable_oom_killer: bool) -> Result<(), WrappedIoError> {\n        if disable_oom_killer {\n            common::write_cgroup_file(cgroup_root.join(CGROUP_MEMORY_OOM_CONTROL), 1)\n        } else {\n            common::write_cgroup_file(cgroup_root.join(CGROUP_MEMORY_OOM_CONTROL), 0)\n        }\n    }\n\n    fn hierarchy_enabled(cgroup_path: &Path) -> Result<bool, WrappedIoError> {\n        let hierarchy_path = cgroup_path.join(MEMORY_USE_HIERARCHY);\n        let hierarchy = common::read_cgroup_file(hierarchy_path)?;\n        let enabled = matches!(hierarchy.trim(), \"1\");\n\n        Ok(enabled)\n    }\n\n    fn get_stat_data(cgroup_path: &Path) -> Result<HashMap<String, u64>, ParseFlatKeyedDataError> {\n        stats::parse_flat_keyed_data(&cgroup_path.join(MEMORY_STAT))\n    }\n\n    fn get_memory_usage(cgroup_root: &Path) -> Result<u64, V1MemoryControllerError> {\n        let path = cgroup_root.join(CGROUP_MEMORY_USAGE);\n        let mut contents = String::new();\n        OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(&path)\n            .wrap_open(&path)?\n            .read_to_string(&mut contents)\n            .wrap_read(&path)?;\n\n        contents = contents.trim().to_string();\n\n        if contents == \"max\" {\n            return Ok(u64::MAX);\n        }\n\n        let val =\n            contents\n                .parse::<u64>()\n                .map_err(|err| V1MemoryControllerError::MalformedValue {\n                    thing: MalformedThing::Usage,\n                    limit: contents,\n                    path,\n                    err,\n                })?;\n        Ok(val)\n    }\n\n    fn get_memory_max_usage(cgroup_root: &Path) -> Result<u64, V1MemoryControllerError> {\n        let path = cgroup_root.join(CGROUP_MEMORY_MAX_USAGE);\n        let mut contents = String::new();\n        OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(&path)\n            .wrap_open(&path)?\n            .read_to_string(&mut contents)\n            .wrap_read(&path)?;\n\n        contents = contents.trim().to_string();\n\n        if contents == \"max\" {\n            return Ok(u64::MAX);\n        }\n\n        let val =\n            contents\n                .parse::<u64>()\n                .map_err(|err| V1MemoryControllerError::MalformedValue {\n                    thing: MalformedThing::MaxUsage,\n                    limit: contents,\n                    path,\n                    err,\n                })?;\n        Ok(val)\n    }\n\n    fn get_memory_limit(cgroup_root: &Path) -> Result<i64, V1MemoryControllerError> {\n        let path = cgroup_root.join(CGROUP_MEMORY_LIMIT);\n        let mut contents = String::new();\n        OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(&path)\n            .wrap_open(&path)?\n            .read_to_string(&mut contents)\n            .wrap_read(&path)?;\n\n        contents = contents.trim().to_string();\n\n        if contents == \"max\" {\n            return Ok(i64::MAX);\n        }\n\n        let val =\n            contents\n                .parse::<i64>()\n                .map_err(|err| V1MemoryControllerError::MalformedValue {\n                    thing: MalformedThing::Limit,\n                    limit: contents,\n                    path,\n                    err,\n                })?;\n        Ok(val)\n    }\n\n    fn set<T: ToString>(val: T, path: &Path) -> Result<(), WrappedIoError> {\n        let data = val.to_string();\n        OpenOptions::new()\n            .create(false)\n            .write(true)\n            .truncate(true)\n            .open(path)\n            .wrap_open(path)?\n            .write_all(data.as_bytes())\n            .wrap_write(path, data)?;\n        Ok(())\n    }\n\n    fn set_memory(val: i64, cgroup_root: &Path) -> Result<(), V1MemoryControllerError> {\n        if val == 0 {\n            return Ok(());\n        }\n        let path = cgroup_root.join(CGROUP_MEMORY_LIMIT);\n\n        match Self::set(val, &path) {\n            Ok(_) => Ok(()),\n            Err(e) => {\n                // we need to look into the raw OS error for an EBUSY status\n                match e.inner().raw_os_error() {\n                    Some(code) => match Errno::from_raw(code) {\n                        Errno::EBUSY => {\n                            let usage = Self::get_memory_usage(cgroup_root)?;\n                            let max_usage = Self::get_memory_max_usage(cgroup_root)?;\n                            Err(V1MemoryControllerError::UnableToSet {\n                                target: val,\n                                current: usage,\n                                peak: max_usage,\n                            })\n                        }\n                        _ => Err(e)?,\n                    },\n                    None => Err(e)?,\n                }\n            }\n        }\n    }\n\n    fn set_swap(swap: i64, cgroup_root: &Path) -> Result<(), V1MemoryControllerError> {\n        if swap == 0 {\n            return Ok(());\n        }\n\n        common::write_cgroup_file(cgroup_root.join(CGROUP_MEMORY_SWAP_LIMIT), swap)?;\n        Ok(())\n    }\n\n    fn set_memory_and_swap(\n        limit: i64,\n        swap: i64,\n        is_updated: bool,\n        cgroup_root: &Path,\n    ) -> Result<(), V1MemoryControllerError> {\n        // According to runc we need to change the write sequence of\n        // limit and swap so it won't fail, because the new and old\n        // values don't fit the kernel's validation\n        // see:\n        // https://github.com/opencontainers/runc/blob/3f6594675675d4e88901c782462f56497260b1d2/libcontainer/cgroups/fs/memory.go#L89\n        if is_updated {\n            Self::set_swap(swap, cgroup_root)?;\n            Self::set_memory(limit, cgroup_root)?;\n        }\n        Self::set_memory(limit, cgroup_root)?;\n        Self::set_swap(swap, cgroup_root)?;\n        Ok(())\n    }\n\n    fn apply(resource: &LinuxMemory, cgroup_root: &Path) -> Result<(), V1MemoryControllerError> {\n        match resource.limit() {\n            Some(limit) => {\n                let current_limit = Self::get_memory_limit(cgroup_root)?;\n                match resource.swap() {\n                    Some(swap) => {\n                        let is_updated = swap == -1 || current_limit < swap;\n                        Self::set_memory_and_swap(limit, swap, is_updated, cgroup_root)?;\n                    }\n                    None => {\n                        if limit == -1 {\n                            Self::set_memory_and_swap(limit, -1, true, cgroup_root)?;\n                        } else {\n                            let is_updated = current_limit < 0;\n                            Self::set_memory_and_swap(limit, 0, is_updated, cgroup_root)?;\n                        }\n                    }\n                }\n            }\n            None => match resource.swap() {\n                Some(swap) => Self::set_memory_and_swap(0, swap, false, cgroup_root)?,\n                None => Self::set_memory_and_swap(0, 0, false, cgroup_root)?,\n            },\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::{LinuxMemoryBuilder, LinuxResourcesBuilder};\n\n    use super::*;\n    use crate::common::CGROUP_PROCS;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_memory() {\n        let limit = 1024;\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_USAGE, \"0\").expect(\"Set fixure for memory usage\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX_USAGE, \"0\")\n            .expect(\"Set fixure for max memory usage\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LIMIT, \"0\").expect(\"Set fixure for memory limit\");\n        Memory::set_memory(limit, tmp.path()).expect(\"Set memory limit\");\n        let content =\n            std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_LIMIT)).expect(\"Read to string\");\n        assert_eq!(limit.to_string(), content)\n    }\n\n    #[test]\n    fn test_disable_oom_kill() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_OOM_CONTROL, \"0\")\n            .expect(\"Set fixure for oom control\");\n        Memory::set_oom_control(tmp.path(), true).expect(\"Set oom control\");\n        let content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_OOM_CONTROL))\n            .expect(\"Read to string\");\n        assert_eq!(\"1\", content);\n\n        Memory::set_oom_control(tmp.path(), false).expect(\"Set oom control\");\n        let content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_OOM_CONTROL))\n            .expect(\"Read to string\");\n        assert_eq!(\"0\", content);\n    }\n\n    #[test]\n    fn pass_set_memory_if_limit_is_zero() {\n        let sample_val = \"1024\";\n        let limit = 0;\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_LIMIT, sample_val)\n            .expect(\"Set fixure for memory limit\");\n        Memory::set_memory(limit, tmp.path()).expect(\"Set memory limit\");\n        let content =\n            std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_LIMIT)).expect(\"Read to string\");\n        assert_eq!(content, sample_val)\n    }\n\n    #[test]\n    fn test_set_swap() {\n        let limit = 512;\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP_LIMIT, \"0\").expect(\"Set fixure for swap limit\");\n        Memory::set_swap(limit, tmp.path()).expect(\"Set swap limit\");\n        let content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP_LIMIT))\n            .expect(\"Read to string\");\n        assert_eq!(limit.to_string(), content)\n    }\n\n    #[test]\n    fn test_set_memory_and_swap() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_USAGE, \"0\").expect(\"Set fixure for memory usage\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX_USAGE, \"0\")\n            .expect(\"Set fixure for max memory usage\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LIMIT, \"0\").expect(\"Set fixure for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP_LIMIT, \"0\").expect(\"Set fixure for swap limit\");\n\n        // test unlimited memory with no set swap\n        {\n            let limit = -1;\n            let linux_memory = LinuxMemoryBuilder::default().limit(limit).build().unwrap();\n            Memory::apply(&linux_memory, tmp.path()).expect(\"Set memory and swap\");\n\n            let limit_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_LIMIT))\n                .expect(\"Read to string\");\n            assert_eq!(limit.to_string(), limit_content);\n\n            let swap_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP_LIMIT))\n                .expect(\"Read to string\");\n            // swap should be set to -1 also\n            assert_eq!(limit.to_string(), swap_content);\n        }\n\n        // test setting swap and memory to arbitrary values\n        {\n            let limit = 1024 * 1024 * 1024;\n            let swap = 1024;\n            let linux_memory = LinuxMemoryBuilder::default()\n                .limit(limit)\n                .swap(swap)\n                .build()\n                .unwrap();\n            Memory::apply(&linux_memory, tmp.path()).expect(\"Set memory and swap\");\n\n            let limit_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_LIMIT))\n                .expect(\"Read to string\");\n            assert_eq!(limit.to_string(), limit_content);\n\n            let swap_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP_LIMIT))\n                .expect(\"Read to string\");\n            assert_eq!(swap.to_string(), swap_content);\n        }\n    }\n\n    quickcheck! {\n            fn property_test_set_memory(linux_memory: LinuxMemory, disable_oom_killer: bool) -> bool {\n                let tmp = tempfile::tempdir().unwrap();\n                set_fixture(tmp.path(), CGROUP_MEMORY_USAGE, \"0\").expect(\"Set fixure for memory usage\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_MAX_USAGE, \"0\").expect(\"Set fixure for max memory usage\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_LIMIT, \"0\").expect(\"Set fixure for memory limit\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_SWAP_LIMIT, \"0\").expect(\"Set fixure for swap limit\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_SWAPPINESS, \"0\").expect(\"Set fixure for swappiness\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_RESERVATION, \"0\").expect(\"Set fixture for memory reservation\");\n                set_fixture(tmp.path(), CGROUP_MEMORY_OOM_CONTROL, \"0\").expect(\"Set fixture for oom control\");\n                set_fixture(tmp.path(), CGROUP_KERNEL_MEMORY_LIMIT, \"0\").expect(\"Set fixture for kernel memory limit\");\n                set_fixture(tmp.path(), CGROUP_KERNEL_TCP_MEMORY_LIMIT, \"0\").expect(\"Set fixture for kernel tcp memory limit\");\n                set_fixture(tmp.path(), CGROUP_PROCS, \"\").expect(\"set fixture for proc file\");\n\n\n                // clone to avoid use of moved value later on\n                let memory_limits = linux_memory;\n\n                let linux_resources = LinuxResourcesBuilder::default().devices(vec![]).memory(linux_memory).hugepage_limits(vec![]).build().unwrap();\n\n                let controller_opt = ControllerOpt {\n                    resources: &linux_resources,\n                    disable_oom_killer,\n                    oom_score_adj: None,\n                    freezer_state: None,\n                };\n\n                let result = <Memory as Controller>::apply(&controller_opt, tmp.path());\n\n\n                if result.is_err() {\n                    if let Some(swappiness) = memory_limits.swappiness() {\n                        // error is expected if swappiness is greater than 100\n                        if swappiness > 100 {\n                            return true;\n                        }\n                    } else {\n                        // useful for debugging\n                        println!(\"Some unexpected error: {:?}\", result.unwrap_err());\n                        // any other error should be considered unexpected\n                        return false;\n                    }\n                }\n\n                // check memory reservation\n                let reservation_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_RESERVATION)).expect(\"read memory reservation\");\n                let reservation_check = match memory_limits.reservation() {\n                    Some(reservation) => {\n                        reservation_content == reservation.to_string()\n                    }\n                    None => reservation_content == \"0\",\n                };\n\n                // check kernel memory limit\n                let kernel_content = std::fs::read_to_string(tmp.path().join(CGROUP_KERNEL_MEMORY_LIMIT)).expect(\"read kernel memory limit\");\n                let kernel_check = match memory_limits.kernel() {\n                    Some(kernel) => {\n                        kernel_content == kernel.to_string()\n                    }\n                    None => kernel_content == \"0\",\n                };\n\n                // check kernel tcp memory limit\n                let kernel_tcp_content = std::fs::read_to_string(tmp.path().join(CGROUP_KERNEL_TCP_MEMORY_LIMIT)).expect(\"read kernel tcp memory limit\");\n                let kernel_tcp_check = match memory_limits.kernel_tcp() {\n                    Some(kernel_tcp) => {\n                        kernel_tcp_content == kernel_tcp.to_string()\n                    }\n                    None => kernel_tcp_content == \"0\",\n                };\n\n                // check swappiness\n                let swappiness_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_SWAPPINESS)).expect(\"read swappiness\");\n                let swappiness_check = match memory_limits.swappiness() {\n                    Some(swappiness) if swappiness <= 100 => {\n                        swappiness_content == swappiness.to_string()\n                    }\n                    None => swappiness_content == \"0\",\n                    // everything else is a failure\n                    _ => false,\n                };\n\n                // check limit and swap\n                let limit_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_LIMIT)).expect(\"read memory limit\");\n                let swap_content = std::fs::read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP_LIMIT)).expect(\"read swap memory limit\");\n                let limit_swap_check = match memory_limits.limit() {\n                    Some(limit) => {\n                        match memory_limits.swap() {\n                            Some(swap) => {\n                                limit_content == limit.to_string()\n                                    && swap_content == swap.to_string()\n                            }\n                            None => {\n                                if limit == -1 {\n                                    limit_content == limit.to_string()\n                                        && swap_content == \"-1\"\n                                } else {\n                                    limit_content == limit.to_string()\n                                        && swap_content == \"0\"\n                                }\n                            }\n                        }\n                    }\n                    None => {\n                        match memory_limits.swap() {\n                            Some(swap) => {\n                                limit_content == \"0\"\n                                    && swap_content == swap.to_string()\n                            }\n                            None => limit_content == \"0\" && swap_content == \"0\"\n                        }\n                    }\n                };\n\n                // useful for debugging\n                println!(\"reservation_check: {reservation_check:?}\");\n                println!(\"kernel_check: {kernel_check:?}\");\n                println!(\"kernel_tcp_check: {kernel_tcp_check:?}\");\n                println!(\"swappiness_check: {swappiness_check:?}\");\n                println!(\"limit_swap_check: {limit_swap_check:?}\");\n\n                // combine all the checks\n                reservation_check && kernel_check && kernel_tcp_check && swappiness_check && limit_swap_check\n            }\n    }\n\n    #[test]\n    fn test_stat_memory_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(\n            tmp.path(),\n            &format!(\"{MEMORY_PREFIX}{MEMORY_USAGE_IN_BYTES}\"),\n            \"1024\\n\",\n        )\n        .unwrap();\n        set_fixture(\n            tmp.path(),\n            &format!(\"{MEMORY_PREFIX}{MEMORY_MAX_USAGE_IN_BYTES}\"),\n            \"2048\\n\",\n        )\n        .unwrap();\n        set_fixture(\n            tmp.path(),\n            &format!(\"{MEMORY_PREFIX}{MEMORY_LIMIT_IN_BYTES}\"),\n            \"4096\\n\",\n        )\n        .unwrap();\n        set_fixture(\n            tmp.path(),\n            &format!(\"{MEMORY_PREFIX}{MEMORY_FAIL_COUNT}\"),\n            \"5\\n\",\n        )\n        .unwrap();\n\n        let actual = Memory::get_memory_data(tmp.path(), MEMORY_PREFIX).expect(\"get cgroup stats\");\n        let expected = MemoryData {\n            usage: 1024,\n            max_usage: 2048,\n            limit: 4096,\n            fail_count: 5,\n        };\n\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_stat_hierarchy_enabled() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), MEMORY_USE_HIERARCHY, \"1\").unwrap();\n\n        let enabled = Memory::hierarchy_enabled(tmp.path()).expect(\"get cgroup stats\");\n        assert!(enabled)\n    }\n\n    #[test]\n    fn test_stat_hierarchy_disabled() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), MEMORY_USE_HIERARCHY, \"0\").unwrap();\n\n        let enabled = Memory::hierarchy_enabled(tmp.path()).expect(\"get cgroup stats\");\n        assert!(!enabled)\n    }\n\n    #[test]\n    fn test_stat_memory_stats() {\n        let tmp = tempfile::tempdir().unwrap();\n        let content = [\n            \"cache 0\",\n            \"rss 0\",\n            \"rss_huge 0\",\n            \"shmem 0\",\n            \"pgpgout 0\",\n            \"unevictable 0\",\n            \"hierarchical_memory_limit 9223372036854771712\",\n            \"hierarchical_memsw_limit 9223372036854771712\",\n        ]\n        .join(\"\\n\");\n        set_fixture(tmp.path(), MEMORY_STAT, &content).unwrap();\n\n        let actual = Memory::get_stat_data(tmp.path()).expect(\"get cgroup data\");\n        let expected: HashMap<String, u64> = [\n            (\"cache\".to_owned(), 0),\n            (\"rss\".to_owned(), 0),\n            (\"rss_huge\".to_owned(), 0),\n            (\"shmem\".to_owned(), 0),\n            (\"pgpgout\".to_owned(), 0),\n            (\"unevictable\".to_owned(), 0),\n            (\"hierarchical_memory_limit\".to_owned(), 9223372036854771712),\n            (\"hierarchical_memsw_limit\".to_owned(), 9223372036854771712),\n        ]\n        .iter()\n        .cloned()\n        .collect();\n\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/mod.rs",
    "content": "mod blkio;\nmod controller;\nmod controller_type;\nmod cpu;\nmod cpuacct;\nmod cpuset;\nmod devices;\nmod freezer;\nmod hugetlb;\npub mod manager;\nmod memory;\nmod network_classifier;\nmod network_priority;\npub mod perf_event;\nmod pids;\npub mod util;\npub use controller_type::ControllerType;\npub use manager::Manager;\n"
  },
  {
    "path": "crates/libcgroups/src/v1/network_classifier.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxNetwork;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\n\npub struct NetworkClassifier {}\n\nimpl Controller for NetworkClassifier {\n    type Error = WrappedIoError;\n    type Resource = LinuxNetwork;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply NetworkClassifier cgroup config\");\n\n        if let Some(network) = Self::needs_to_handle(controller_opt) {\n            Self::apply(cgroup_root, network)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.resources.network().as_ref()\n    }\n}\n\nimpl NetworkClassifier {\n    fn apply(root_path: &Path, network: &LinuxNetwork) -> Result<(), WrappedIoError> {\n        if let Some(class_id) = network.class_id() {\n            common::write_cgroup_file(root_path.join(\"net_cls.classid\"), class_id)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::LinuxNetworkBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_apply_network_classifier() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"net_cls.classid\", \"0\").expect(\"set fixture for classID\");\n\n        let id = 0x100001u32;\n        let network = LinuxNetworkBuilder::default()\n            .class_id(id)\n            .priorities(vec![])\n            .build()\n            .unwrap();\n\n        NetworkClassifier::apply(tmp.path(), &network).expect(\"apply network classID\");\n\n        let content = std::fs::read_to_string(tmp.path().join(\"net_cls.classid\"))\n            .expect(\"Read classID contents\");\n        assert_eq!(id.to_string(), content);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/network_priority.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxNetwork;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\n\npub struct NetworkPriority {}\n\nimpl Controller for NetworkPriority {\n    type Error = WrappedIoError;\n    type Resource = LinuxNetwork;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply NetworkPriority cgroup config\");\n\n        if let Some(network) = Self::needs_to_handle(controller_opt) {\n            Self::apply(cgroup_root, network)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.resources.network().as_ref()\n    }\n}\n\nimpl NetworkPriority {\n    fn apply(root_path: &Path, network: &LinuxNetwork) -> Result<(), WrappedIoError> {\n        if let Some(ni_priorities) = network.priorities() {\n            let priorities: String = ni_priorities.iter().map(|p| p.to_string()).collect();\n            common::write_cgroup_file_str(root_path.join(\"net_prio.ifpriomap\"), priorities.trim())?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::{LinuxInterfacePriorityBuilder, LinuxNetworkBuilder};\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_apply_network_priorities() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"net_prio.ifpriomap\", \"\").expect(\"set fixture for priority map\");\n        let priorities = vec![\n            LinuxInterfacePriorityBuilder::default()\n                .name(\"a\")\n                .priority(1u32)\n                .build()\n                .unwrap(),\n            LinuxInterfacePriorityBuilder::default()\n                .name(\"b\")\n                .priority(2u32)\n                .build()\n                .unwrap(),\n        ];\n        let priorities_string = priorities.iter().map(|p| p.to_string()).collect::<String>();\n        let network = LinuxNetworkBuilder::default()\n            .priorities(priorities)\n            .build()\n            .unwrap();\n\n        NetworkPriority::apply(tmp.path(), &network).expect(\"apply network priorities\");\n\n        let content = std::fs::read_to_string(tmp.path().join(\"net_prio.ifpriomap\"))\n            .expect(\"Read classID contents\");\n        assert_eq!(priorities_string.trim(), content);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/perf_event.rs",
    "content": "use std::path::Path;\n\nuse super::controller::Controller;\nuse crate::common::{ControllerOpt, WrappedIoError};\n\npub struct PerfEvent {}\n\nimpl Controller for PerfEvent {\n    type Error = WrappedIoError;\n    type Resource = ();\n\n    fn apply(_controller_opt: &ControllerOpt, _cgroup_root: &Path) -> Result<(), Self::Error> {\n        Ok(())\n    }\n    //no need to handle any case\n    fn needs_to_handle<'a>(_controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        None\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use nix::unistd::Pid;\n\n    use super::*;\n    use crate::common::CGROUP_PROCS;\n    use crate::test::setup;\n\n    #[test]\n    fn test_add_task() {\n        let (tmp, procs) = setup(CGROUP_PROCS);\n        let pid = Pid::from_raw(1000);\n\n        PerfEvent::add_task(pid, tmp.path()).expect(\"apply perf_event\");\n\n        let content = fs::read_to_string(procs)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_PROCS} file content\"));\n        assert_eq!(content, \"1000\");\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/pids.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxPids;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{self, PidStats, PidStatsError, StatsProvider};\n\n// Contains the maximum allowed number of active pids\nconst CGROUP_PIDS_MAX: &str = \"pids.max\";\n\npub struct Pids {}\n\nimpl Controller for Pids {\n    type Error = WrappedIoError;\n    type Resource = LinuxPids;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply pids cgroup config\");\n\n        if let Some(pids) = &controller_opt.resources.pids() {\n            Self::apply(cgroup_root, pids)?;\n        }\n\n        Ok(())\n    }\n\n    fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> {\n        controller_opt.resources.pids().as_ref()\n    }\n}\n\nimpl StatsProvider for Pids {\n    type Error = PidStatsError;\n    type Stats = PidStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        stats::pid_stats(cgroup_path)\n    }\n}\n\nimpl Pids {\n    fn apply(root_path: &Path, pids: &LinuxPids) -> Result<(), WrappedIoError> {\n        let limit = if pids.limit() > 0 {\n            pids.limit().to_string()\n        } else {\n            \"max\".to_string()\n        };\n\n        common::write_cgroup_file_str(root_path.join(CGROUP_PIDS_MAX), &limit)?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::LinuxPidsBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    // Contains the current number of active pids\n    const CGROUP_PIDS_CURRENT: &str = \"pids.current\";\n\n    #[test]\n    fn test_set_pids() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_MAX, \"1000\").expect(\"Set fixture for 1000 pids\");\n\n        let pids = LinuxPidsBuilder::default().limit(1000).build().unwrap();\n\n        Pids::apply(tmp.path(), &pids).expect(\"apply pids\");\n        let content =\n            std::fs::read_to_string(tmp.path().join(CGROUP_PIDS_MAX)).expect(\"Read pids contents\");\n        assert_eq!(pids.limit().to_string(), content);\n    }\n\n    #[test]\n    fn test_set_pids_max() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_MAX, \"0\").expect(\"set fixture for 0 pids\");\n\n        let pids = LinuxPidsBuilder::default().limit(0).build().unwrap();\n\n        Pids::apply(tmp.path(), &pids).expect(\"apply pids\");\n\n        let content =\n            std::fs::read_to_string(tmp.path().join(CGROUP_PIDS_MAX)).expect(\"Read pids contents\");\n        assert_eq!(\"max\".to_string(), content);\n    }\n\n    #[test]\n    fn test_stat_pids() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_CURRENT, \"5\\n\").unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_MAX, \"30\\n\").unwrap();\n\n        let stats = Pids::stats(tmp.path()).expect(\"get cgroup stats\");\n\n        assert_eq!(stats.current, 5);\n        assert_eq!(stats.limit, 30);\n    }\n\n    #[test]\n    fn test_stat_pids_max() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_CURRENT, \"5\\n\").unwrap();\n        set_fixture(tmp.path(), CGROUP_PIDS_MAX, \"max\\n\").unwrap();\n\n        let stats = Pids::stats(tmp.path()).expect(\"get cgroup stats\");\n\n        assert_eq!(stats.current, 5);\n        assert_eq!(stats.limit, 0);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v1/util.rs",
    "content": "use std::collections::HashMap;\nuse std::io::{BufRead, BufReader};\nuse std::path::PathBuf;\n\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\nuse procfs::ProcError;\nuse procfs::process::MountInfo;\n\nuse super::ControllerType;\nuse super::controller_type::CONTROLLERS;\n\n#[derive(thiserror::Error, Debug)]\npub enum V1MountPointError {\n    #[error(\"io error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"failed to get mountinfo: {0}\")]\n    MountInfo(ProcError),\n    #[error(\"could not find mountpoint for {subsystem}\")]\n    NotFound { subsystem: ControllerType },\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n}\n\n/// List all cgroup v1 subsystem mount points on the system. This can include unsupported\n/// subsystems, comounted controllers and named hierarchies.\npub fn list_subsystem_mount_points() -> Result<Vec<PathBuf>, V1MountPointError> {\n    let reader = BufReader::new(ProcfsHandle::new()?.open(\n        ProcfsBase::ProcSelf,\n        \"mountinfo\",\n        OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n    )?);\n\n    reader\n        .lines()\n        .map(|lr| {\n            lr.map_err(V1MountPointError::Io)\n                .and_then(|line| MountInfo::from_line(&line).map_err(V1MountPointError::MountInfo))\n        })\n        .try_fold(Vec::new(), |mut mount_points, r| {\n            r.map(|m| {\n                if m.fs_type == \"cgroup\" {\n                    mount_points.push(m.mount_point);\n                }\n                mount_points\n            })\n        })\n}\n\n/// List the mount points of all currently supported cgroup subsystems.\npub fn list_supported_mount_points() -> Result<HashMap<ControllerType, PathBuf>, V1MountPointError>\n{\n    let mut mount_paths = HashMap::with_capacity(CONTROLLERS.len());\n\n    for controller in CONTROLLERS {\n        if let Ok(mount_point) = get_subsystem_mount_point(controller) {\n            mount_paths.insert(controller.to_owned(), mount_point);\n        }\n    }\n\n    Ok(mount_paths)\n}\n\npub fn get_subsystem_mount_point(subsystem: &ControllerType) -> Result<PathBuf, V1MountPointError> {\n    let subsystem_name = subsystem.to_string();\n    let reader = BufReader::new(ProcfsHandle::new()?.open(\n        ProcfsBase::ProcSelf,\n        \"mountinfo\",\n        OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n    )?);\n\n    reader\n        .lines()\n        .map(|lr| {\n            lr.map_err(V1MountPointError::Io)\n                .and_then(|line| MountInfo::from_line(&line).map_err(V1MountPointError::MountInfo))\n        })\n        .find_map(|r| match r {\n            Err(e) => Some(Err(e)),\n            Ok(m) if m.fs_type == \"cgroup\" => {\n                // Some systems mount net_prio and net_cls in the same directory\n                // other systems mount them in their own directories. This\n                // should handle both cases.\n                let ok = match subsystem_name.as_str() {\n                    \"net_cls\" => [\"net_cls,net_prio\", \"net_prio,net_cls\", \"net_cls\"]\n                        .iter()\n                        .any(|s| m.mount_point.ends_with(s)),\n                    \"net_prio\" => [\"net_cls,net_prio\", \"net_prio,net_cls\", \"net_prio\"]\n                        .iter()\n                        .any(|s| m.mount_point.ends_with(s)),\n                    \"cpu\" => [\"cpu,cpuacct\", \"cpu\"]\n                        .iter()\n                        .any(|s| m.mount_point.ends_with(s)),\n                    \"cpuacct\" => [\"cpu,cpuacct\", \"cpuacct\"]\n                        .iter()\n                        .any(|s| m.mount_point.ends_with(s)),\n                    _ => m.mount_point.ends_with(&subsystem_name),\n                };\n                if ok { Some(Ok(m.mount_point)) } else { None }\n            }\n            Ok(_) => None,\n        })\n        .transpose()?\n        .ok_or(V1MountPointError::NotFound {\n            subsystem: *subsystem,\n        })\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/controller.rs",
    "content": "use std::path::Path;\n\nuse crate::common::ControllerOpt;\n\npub(super) trait Controller {\n    type Error;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error>;\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/controller_type.rs",
    "content": "use std::fmt::Display;\n\n#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]\npub enum ControllerType {\n    Cpu,\n    CpuSet,\n    Io,\n    Memory,\n    HugeTlb,\n    Pids,\n}\n\nimpl Display for ControllerType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            Self::Cpu => \"cpu\",\n            Self::CpuSet => \"cpuset\",\n            Self::Io => \"io\",\n            Self::Memory => \"memory\",\n            Self::HugeTlb => \"hugetlb\",\n            Self::Pids => \"pids\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\npub const CONTROLLER_TYPES: &[ControllerType] = &[\n    ControllerType::Cpu,\n    ControllerType::CpuSet,\n    ControllerType::HugeTlb,\n    ControllerType::Io,\n    ControllerType::Memory,\n    ControllerType::Pids,\n];\n\n#[derive(Clone, Copy, PartialEq, Eq, Hash)]\npub enum PseudoControllerType {\n    Devices,\n    Freezer,\n    Unified,\n}\n\nimpl Display for PseudoControllerType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            Self::Devices => \"devices\",\n            Self::Freezer => \"freezer\",\n            Self::Unified => \"unified\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\npub const PSEUDO_CONTROLLER_TYPES: &[PseudoControllerType] = &[\n    PseudoControllerType::Devices,\n    PseudoControllerType::Freezer,\n    PseudoControllerType::Unified,\n];\n"
  },
  {
    "path": "crates/libcgroups/src/v2/cpu.rs",
    "content": "use std::borrow::Cow;\nuse std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::LinuxCpu;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{self, CpuStats, ParseFlatKeyedDataError, StatsProvider};\n\nconst CGROUP_CPU_WEIGHT: &str = \"cpu.weight\";\nconst CGROUP_CPU_MAX: &str = \"cpu.max\";\nconst CGROUP_CPU_BURST: &str = \"cpu.max.burst\";\nconst CGROUP_CPU_IDLE: &str = \"cpu.idle\";\nconst UNRESTRICTED_QUOTA: &str = \"max\";\nconst MAX_CPU_WEIGHT: u64 = 10000;\n\nconst CPU_STAT: &str = \"cpu.stat\";\nconst CPU_PSI: &str = \"cpu.pressure\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2CpuControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"realtime is not supported on v2 yet\")]\n    RealtimeV2,\n}\n\npub struct Cpu {}\n\nimpl Controller for Cpu {\n    type Error = V2CpuControllerError;\n\n    fn apply(controller_opt: &ControllerOpt, path: &Path) -> Result<(), Self::Error> {\n        if let Some(cpu) = &controller_opt.resources.cpu() {\n            Self::apply(path, cpu)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V2CpuStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"while parsing stat table: {0}\")]\n    ParseNestedKeyedData(#[from] ParseFlatKeyedDataError),\n    #[error(\"missing field {field} from {path}\")]\n    MissingField { field: &'static str, path: PathBuf },\n}\n\nimpl StatsProvider for Cpu {\n    type Error = V2CpuStatsError;\n    type Stats = CpuStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let mut stats = CpuStats::default();\n        let stats_path = cgroup_path.join(CPU_STAT);\n\n        let stats_table = stats::parse_flat_keyed_data(&stats_path)?;\n\n        macro_rules! get {\n            ($name: expr => $field1:ident.$field2:ident) => {\n                stats.$field1.$field2 =\n                    *stats_table\n                        .get($name)\n                        .ok_or_else(|| V2CpuStatsError::MissingField {\n                            field: $name,\n                            path: stats_path.clone(),\n                        })?;\n            };\n        }\n\n        get!(\"usage_usec\" => usage.usage_total);\n        get!(\"user_usec\" => usage.usage_user);\n        get!(\"system_usec\" => usage.usage_kernel);\n        get!(\"nr_periods\" => throttling.periods);\n        get!(\"nr_throttled\" => throttling.throttled_periods);\n        get!(\"throttled_usec\" => throttling.throttled_time);\n\n        stats.psi = stats::psi_stats(&cgroup_path.join(CPU_PSI))?;\n        Ok(stats)\n    }\n}\n\nimpl Cpu {\n    fn apply(path: &Path, cpu: &LinuxCpu) -> Result<(), V2CpuControllerError> {\n        if Self::is_realtime_requested(cpu) {\n            let runtime = cpu.realtime_runtime().unwrap_or(0);\n            let period = cpu.realtime_period().unwrap_or(0);\n\n            if runtime > 0 || period > 0 {\n                return Err(V2CpuControllerError::RealtimeV2);\n            }\n        }\n\n        if let Some(mut shares) = cpu.shares() {\n            shares = Self::convert_shares_to_cgroup2(shares);\n            if shares != 0 {\n                // will result in Erno 34 (numerical result out of range) otherwise\n                common::write_cgroup_file(path.join(CGROUP_CPU_WEIGHT), shares)?;\n            }\n        }\n\n        let cpu_max_file = path.join(CGROUP_CPU_MAX);\n        let new_cpu_max: Option<Cow<str>> = match (cpu.quota(), cpu.period()) {\n            (None, Some(period)) => Self::create_period_only_value(&cpu_max_file, period)?,\n            (Some(quota), None) if quota > 0 => Some(quota.to_string().into()),\n            (Some(quota), None) if quota <= 0 => Some(UNRESTRICTED_QUOTA.into()),\n            (Some(quota), Some(period)) if quota > 0 => Some(format!(\"{quota} {period}\").into()),\n            (Some(quota), Some(period)) if quota <= 0 => {\n                Some(format!(\"{UNRESTRICTED_QUOTA} {period}\").into())\n            }\n            _ => None,\n        };\n\n        // format is 'quota period'\n        // the kernel default is 'max 100000'\n        // 250000 250000 -> 1 CPU worth of runtime every 250ms\n        // 10000 50000 -> 20% of one CPU every 50ms\n        if let Some(cpu_max) = new_cpu_max {\n            common::write_cgroup_file_str(&cpu_max_file, &cpu_max)?;\n        }\n\n        if let Some(burst) = cpu.burst() {\n            common::write_cgroup_file(path.join(CGROUP_CPU_BURST), burst)?;\n        }\n\n        if let Some(idle) = cpu.idle() {\n            common::write_cgroup_file(path.join(CGROUP_CPU_IDLE), idle)?;\n        }\n\n        Ok(())\n    }\n\n    // Convert CPU shares (cgroup v1) into CPU weight (cgroup v2).\n    // cgroup v1 shares span [2, 262_144] with a default of 1_024.\n    // cgroup v2 weight spans [1, 10_000] with a default of 100.\n    // A shares value of 0 keeps the field unset.\n    // The quadratic fit mirrors runc's mapping to keep extrema and defaults.\n    // For reference, see:\n    // https://github.com/opencontainers/runc/releases/tag/v1.3.2\n    // https://github.com/opencontainers/cgroups/pull/20\n    fn convert_shares_to_cgroup2(shares: u64) -> u64 {\n        if shares == 0 {\n            return 0;\n        }\n\n        const MIN_SHARES: u64 = 2;\n        const MAX_SHARES: u64 = 262_144;\n\n        if shares <= MIN_SHARES {\n            return 1;\n        }\n\n        if shares >= MAX_SHARES {\n            return MAX_CPU_WEIGHT;\n        }\n\n        let log_shares = (shares as f64).log2();\n        let exponent = (log_shares * log_shares + 125.0 * log_shares) / 612.0 - 7.0 / 34.0;\n        let weight = (10f64.powf(exponent)).ceil() as u64;\n\n        weight.clamp(1, MAX_CPU_WEIGHT)\n    }\n\n    fn is_realtime_requested(cpu: &LinuxCpu) -> bool {\n        if cpu.realtime_period().is_some() {\n            return true;\n        }\n\n        if cpu.realtime_runtime().is_some() {\n            return true;\n        }\n\n        false\n    }\n\n    fn create_period_only_value(\n        cpu_max_file: &Path,\n        period: u64,\n    ) -> Result<Option<Cow<'_, str>>, V2CpuControllerError> {\n        let old_cpu_max = common::read_cgroup_file(cpu_max_file)?;\n        if let Some(old_quota) = old_cpu_max.split_whitespace().next() {\n            return Ok(Some(format!(\"{old_quota} {period}\").into()));\n        }\n        Ok(None)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::*;\n    use crate::stats::{CpuThrottling, CpuUsage};\n    use crate::test::{set_fixture, setup};\n\n    #[test]\n    fn test_set_valid_shares() {\n        // arrange\n        let (tmp, weight) = setup(CGROUP_CPU_WEIGHT);\n        let _ = set_fixture(tmp.path(), CGROUP_CPU_MAX, \"\")\n            .unwrap_or_else(|_| panic!(\"set test fixture for {CGROUP_CPU_MAX}\"));\n        let cpu = LinuxCpuBuilder::default().shares(22000u64).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(weight)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_WEIGHT} file content\"));\n        assert_eq!(content, 1204.to_string());\n    }\n\n    #[test]\n    fn test_set_cpu_idle() {\n        // arrange\n        const IDLE: i64 = 1;\n        const CPU: &str = \"cpu\";\n\n        if !Path::new(common::DEFAULT_CGROUP_ROOT)\n            .join(CPU)\n            .join(CGROUP_CPU_IDLE)\n            .exists()\n        {\n            // skip test_set_cpu_idle due to not found cpu.idle, maybe due to old kernel version\n            return;\n        }\n\n        let (tmp, max) = setup(CGROUP_CPU_IDLE);\n        let cpu = LinuxCpuBuilder::default().idle(IDLE).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_IDLE} file content\"));\n        assert_eq!(content, format!(\"{IDLE}\"))\n    }\n\n    #[test]\n    fn test_set_positive_quota() {\n        // arrange\n        const QUOTA: i64 = 200000;\n        let (tmp, max) = setup(CGROUP_CPU_MAX);\n        let cpu = LinuxCpuBuilder::default().quota(QUOTA).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_MAX} file content\"));\n        assert_eq!(content, format!(\"{QUOTA}\"))\n    }\n\n    #[test]\n    fn test_set_negative_quota() {\n        // arrange\n        let (tmp, max) = setup(CGROUP_CPU_MAX);\n        let cpu = LinuxCpuBuilder::default().quota(-500).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_MAX} file content\"));\n        assert_eq!(content, UNRESTRICTED_QUOTA)\n    }\n\n    #[test]\n    fn test_set_positive_period() {\n        // arrange\n        const QUOTA: u64 = 50000;\n        const PERIOD: u64 = 100000;\n        let (tmp, max) = setup(CGROUP_CPU_MAX);\n        common::write_cgroup_file(&max, QUOTA).unwrap();\n        let cpu = LinuxCpuBuilder::default().period(PERIOD).build().unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_MAX} file content\"));\n        assert_eq!(content, format!(\"{QUOTA} {PERIOD}\"))\n    }\n\n    #[test]\n    fn test_set_quota_and_period() {\n        // arrange\n        const QUOTA: i64 = 200000;\n        const PERIOD: u64 = 100000;\n        let (tmp, max) = setup(CGROUP_CPU_MAX);\n        let cpu = LinuxCpuBuilder::default()\n            .quota(QUOTA)\n            .period(PERIOD)\n            .build()\n            .unwrap();\n\n        // act\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        // assert\n        let content = fs::read_to_string(max)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPU_MAX} file content\"));\n        assert_eq!(content, format!(\"{QUOTA} {PERIOD}\"));\n    }\n\n    #[test]\n    fn test_realtime_runtime_not_supported() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_runtime(5)\n            .build()\n            .unwrap();\n\n        // act\n        let result = Cpu::apply(tmp.path(), &cpu);\n\n        // assert\n        assert!(\n            result.is_err(),\n            \"realtime runtime is not supported and should return an error\"\n        );\n    }\n\n    #[test]\n    fn test_realtime_period_not_supported() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_period(5u64)\n            .build()\n            .unwrap();\n\n        // act\n        let result = Cpu::apply(tmp.path(), &cpu);\n\n        // assert\n        assert!(\n            result.is_err(),\n            \"realtime period is not supported and should return an error\"\n        );\n    }\n\n    #[test]\n    fn test_stat_usage() {\n        let tmp = tempfile::tempdir().unwrap();\n        let content = [\n            \"usage_usec 7730\",\n            \"user_usec 4387\",\n            \"system_usec 3498\",\n            \"nr_periods 400\",\n            \"nr_throttled 20\",\n            \"throttled_usec 5000\",\n        ]\n        .join(\"\\n\");\n        set_fixture(tmp.path(), CPU_STAT, &content).expect(\"create stat file\");\n        set_fixture(tmp.path(), CPU_PSI, \"\").expect(\"create psi file\");\n\n        let actual = Cpu::stats(tmp.path()).expect(\"get cgroup stats\");\n        let expected = CpuStats {\n            usage: CpuUsage {\n                usage_total: 7730,\n                usage_user: 4387,\n                usage_kernel: 3498,\n                ..Default::default()\n            },\n            throttling: CpuThrottling {\n                periods: 400,\n                throttled_periods: 20,\n                throttled_time: 5000,\n            },\n            ..Default::default()\n        };\n\n        assert_eq!(actual.usage, expected.usage);\n        assert_eq!(actual.throttling, expected.throttling);\n    }\n\n    #[test]\n    fn test_burst() {\n        let expected = 100000u64;\n        let (tmp, burst_file) = setup(CGROUP_CPU_BURST);\n        let cpu = LinuxCpuBuilder::default().burst(expected).build().unwrap();\n\n        Cpu::apply(tmp.path(), &cpu).expect(\"apply cpu\");\n\n        let actual = fs::read_to_string(burst_file).expect(\"read burst file\");\n        assert_eq!(actual, expected.to_string());\n    }\n\n    #[test]\n    fn test_cgroupsv2_but_runtime_set_to_zero() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_runtime(0i64)\n            .build()\n            .unwrap();\n\n        // act\n        let result = Cpu::apply(tmp.path(), &cpu);\n\n        // assert\n        assert!(result.is_ok())\n    }\n\n    #[test]\n    fn test_cgroupsv2_but_period_set_to_zero() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_period(0u64)\n            .build()\n            .unwrap();\n\n        // act\n        let result = Cpu::apply(tmp.path(), &cpu);\n\n        // assert\n        assert!(result.is_ok())\n    }\n\n    #[test]\n    fn test_cgroupsv2_but_period_and_runtime_set_to_zero() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = LinuxCpuBuilder::default()\n            .realtime_period(0u64)\n            .realtime_runtime(0i64)\n            .build()\n            .unwrap();\n\n        // act\n        let result = Cpu::apply(tmp.path(), &cpu);\n\n        // assert\n        assert!(result.is_ok())\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/cpuset.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxCpu;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\n\nconst CGROUP_CPUSET_CPUS: &str = \"cpuset.cpus\";\nconst CGROUP_CPUSET_MEMS: &str = \"cpuset.mems\";\n\npub struct CpuSet {}\n\nimpl Controller for CpuSet {\n    type Error = WrappedIoError;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error> {\n        if let Some(cpuset) = &controller_opt.resources.cpu() {\n            Self::apply(cgroup_path, cpuset)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl CpuSet {\n    fn apply(path: &Path, cpuset: &LinuxCpu) -> Result<(), WrappedIoError> {\n        if let Some(cpus) = &cpuset.cpus() {\n            common::write_cgroup_file_str(path.join(CGROUP_CPUSET_CPUS), cpus)?;\n        }\n\n        if let Some(mems) = &cpuset.mems() {\n            common::write_cgroup_file_str(path.join(CGROUP_CPUSET_MEMS), mems)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use oci_spec::runtime::LinuxCpuBuilder;\n\n    use super::*;\n    use crate::test::setup;\n\n    #[test]\n    fn test_set_cpus() {\n        // arrange\n        let (tmp, cpus) = setup(CGROUP_CPUSET_CPUS);\n        let cpuset = LinuxCpuBuilder::default()\n            .cpus(\"1-3\".to_owned())\n            .build()\n            .unwrap();\n\n        // act\n        CpuSet::apply(tmp.path(), &cpuset).expect(\"apply cpuset\");\n\n        // assert\n        let content = fs::read_to_string(cpus)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPUSET_CPUS} file content\"));\n        assert_eq!(content, \"1-3\");\n    }\n\n    #[test]\n    fn test_set_mems() {\n        // arrange\n        let (tmp, mems) = setup(CGROUP_CPUSET_MEMS);\n        let cpuset = LinuxCpuBuilder::default()\n            .mems(\"1-3\".to_owned())\n            .build()\n            .unwrap();\n\n        // act\n        CpuSet::apply(tmp.path(), &cpuset).expect(\"apply cpuset\");\n\n        // assert\n        let content = fs::read_to_string(mems)\n            .unwrap_or_else(|_| panic!(\"read {CGROUP_CPUSET_MEMS} file content\"));\n        assert_eq!(content, \"1-3\");\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/bpf.rs",
    "content": "#[derive(Clone)]\npub struct ProgramInfo {\n    pub id: u32,\n    pub fd: i32,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum BpfError {\n    #[error(transparent)]\n    Errno(#[from] errno::Errno),\n    #[error(\"Failed to increase rlimit\")]\n    FailedToIncreaseRLimit,\n}\n\n#[cfg_attr(test, automock)]\npub mod prog {\n    use std::os::unix::io::RawFd;\n    use std::ptr;\n\n    use libbpf_sys::{BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI, BPF_PROG_TYPE_CGROUP_DEVICE, bpf_insn};\n    #[cfg(not(test))]\n    use libbpf_sys::{\n        bpf_prog_attach, bpf_prog_detach2, bpf_prog_get_fd_by_id, bpf_prog_load, bpf_prog_query,\n    };\n    #[cfg(not(test))]\n    use libc::setrlimit;\n    use libc::{ENOSPC, RLIMIT_MEMLOCK, rlimit};\n\n    use super::ProgramInfo;\n    // TODO: consider use of #[mockall_double]\n    #[cfg(test)]\n    use crate::v2::devices::mocks::mock_libbpf_sys::{\n        bpf_prog_attach, bpf_prog_detach2, bpf_prog_get_fd_by_id, bpf_prog_load, bpf_prog_query,\n    };\n    // mocks\n    // TODO: consider use of #[mockall_double]\n    #[cfg(test)]\n    use crate::v2::devices::mocks::mock_libc::setrlimit;\n\n    pub fn load(license: &str, insns: &[u8]) -> Result<RawFd, super::BpfError> {\n        let insns_cnt = insns.len() / std::mem::size_of::<bpf_insn>();\n        let insns = insns as *const _ as *const bpf_insn;\n        let mut opts = libbpf_sys::bpf_prog_load_opts {\n            sz: std::mem::size_of::<libbpf_sys::bpf_prog_load_opts>() as libbpf_sys::size_t,\n            kern_version: 0,\n            log_buf: ptr::null_mut::<::std::os::raw::c_char>(),\n            log_size: 0,\n            ..Default::default()\n        };\n        #[allow(unused_unsafe)]\n        let prog_fd = unsafe {\n            bpf_prog_load(\n                BPF_PROG_TYPE_CGROUP_DEVICE,\n                ptr::null::<::std::os::raw::c_char>(),\n                license as *const _ as *const ::std::os::raw::c_char,\n                insns,\n                insns_cnt as u64,\n                &mut opts as *mut libbpf_sys::bpf_prog_load_opts,\n            )\n        };\n\n        if prog_fd < 0 {\n            return Err(errno::errno().into());\n        }\n        Ok(prog_fd)\n    }\n\n    /// Given a fd for a cgroup, collect the programs associated with it\n    pub fn query(cgroup_fd: RawFd) -> Result<Vec<ProgramInfo>, super::BpfError> {\n        let mut prog_ids: Vec<u32> = vec![0_u32; 64];\n        let mut attach_flags = 0_u32;\n        for _ in 0..10 {\n            let mut prog_cnt = prog_ids.len() as u32;\n            #[allow(unused_unsafe)]\n            let ret = unsafe {\n                // collect ids for bpf programs\n                bpf_prog_query(\n                    cgroup_fd,\n                    BPF_CGROUP_DEVICE,\n                    0,\n                    &mut attach_flags,\n                    &prog_ids[0] as *const u32 as *mut u32,\n                    &mut prog_cnt,\n                )\n            };\n            if ret != 0 {\n                let err = errno::errno();\n                if err.0 == ENOSPC {\n                    assert!(prog_cnt as usize > prog_ids.len());\n\n                    // allocate more space and try again\n                    prog_ids.resize(prog_cnt as usize, 0);\n                    continue;\n                }\n\n                return Err(err.into());\n            }\n\n            prog_ids.resize(prog_cnt as usize, 0);\n            break;\n        }\n\n        let mut prog_fds = Vec::with_capacity(prog_ids.len());\n        for prog_id in &prog_ids {\n            // collect fds for programs by getting their ids\n            #[allow(unused_unsafe)]\n            let prog_fd = unsafe { bpf_prog_get_fd_by_id(*prog_id) };\n            if prog_fd < 0 {\n                tracing::debug!(\"bpf_prog_get_fd_by_id failed: {}\", errno::errno());\n                continue;\n            }\n            prog_fds.push(ProgramInfo {\n                id: *prog_id,\n                fd: prog_fd,\n            });\n        }\n        Ok(prog_fds)\n    }\n\n    pub fn detach2(prog_fd: RawFd, cgroup_fd: RawFd) -> Result<(), super::BpfError> {\n        #[allow(unused_unsafe)]\n        let ret = unsafe { bpf_prog_detach2(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE) };\n        if ret != 0 {\n            return Err(errno::errno().into());\n        }\n        Ok(())\n    }\n\n    pub fn attach(prog_fd: RawFd, cgroup_fd: RawFd) -> Result<(), super::BpfError> {\n        #[allow(unused_unsafe)]\n        let ret =\n            unsafe { bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_DEVICE, BPF_F_ALLOW_MULTI) };\n\n        if ret != 0 {\n            return Err(errno::errno().into());\n        }\n        Ok(())\n    }\n\n    pub fn bump_memlock_rlimit() -> Result<(), super::BpfError> {\n        let rlimit = rlimit {\n            rlim_cur: 128 << 20,\n            rlim_max: 128 << 20,\n        };\n\n        #[allow(unused_unsafe)]\n        if unsafe { setrlimit(RLIMIT_MEMLOCK, &rlimit) } != 0 {\n            return Err(super::BpfError::FailedToIncreaseRLimit);\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use errno::{Errno, set_errno};\n    use libc::{ENOSPC, ENOSYS};\n    use serial_test::serial;\n\n    use super::prog;\n    use crate::v2::devices::mocks::{mock_libbpf_sys, mock_libc};\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_load() {\n        // eBPF uses 64-bit instructions\n        let instruction_zero: &[u8] = &[0x0, 0x0, 0x0, 0x0];\n        let instruction_one: &[u8] = &[0xF, 0xF, 0xF, 0xF];\n\n        // arrange\n        let license = \"Apache\";\n        let instructions = [instruction_zero, instruction_one].concat();\n        let load = mock_libbpf_sys::bpf_prog_load_context();\n\n        // expect\n        load.expect().once().returning(|_, _, _, _, _, _| 32);\n\n        // act\n        let fd = prog::load(license, &instructions).expect(\"successfully calls load\");\n\n        // assert\n        assert_eq!(fd, 32);\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_attach() {\n        // arrange\n        let attach = mock_libbpf_sys::bpf_prog_attach_context();\n\n        // expect\n        attach.expect().once().returning(|_, _, _, _| 0);\n\n        // act\n        let r = prog::attach(0, 0);\n\n        // assert\n        assert!(r.is_ok());\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_load_error() {\n        // eBPF uses 64-bit instructions\n        let instruction_zero: &[u8] = &[0x0, 0x0, 0x0, 0x0];\n        let instruction_one: &[u8] = &[0xF, 0xF, 0xF, 0xF];\n\n        // arrange\n        let license = \"Apache\";\n        let instructions = [instruction_zero, instruction_one].concat();\n        let load = mock_libbpf_sys::bpf_prog_load_context();\n\n        // expect\n        load.expect().once().returning(|_, _, _, _, _, _| -1);\n\n        // act\n        let error_result = prog::load(license, &instructions);\n\n        // assert\n        assert!(error_result.is_err());\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_query() {\n        // arrange\n        let query = mock_libbpf_sys::bpf_prog_query_context();\n        let get_fd_by_id = mock_libbpf_sys::bpf_prog_get_fd_by_id_context();\n\n        // expect\n        query.expect().once().returning(\n            |_target_fd: std::os::raw::c_int,\n             _type_: libbpf_sys::bpf_attach_type,\n             _query_flags: libbpf_sys::__u32,\n             _attach_flags: *mut libbpf_sys::__u32,\n             prog_ids: *mut libbpf_sys::__u32,\n             prog_cnt: *mut libbpf_sys::__u32|\n             -> ::std::os::raw::c_int {\n                // deref the ptr and fill it with some \"ids\"\n                // also set the prog_cnt to 4\n                set_errno(Errno(0));\n                unsafe {\n                    *prog_cnt = 4;\n                    let id_array = std::slice::from_raw_parts_mut(prog_ids, 4_usize);\n                    id_array[0] = 1;\n                    id_array[1] = 2;\n                    id_array[2] = 3;\n                    id_array[3] = 4;\n                }\n                0\n            },\n        );\n        get_fd_by_id.expect().times(4).returning(|fd| {\n            // return the same fd if it's not 0\n            if fd > 0 {\n                return fd as std::os::raw::c_int;\n            }\n            -1\n        });\n\n        // act\n        let info = prog::query(0).expect(\"Able to successfully query\");\n\n        // assert\n        assert_eq!(info.first().unwrap().id, 1);\n        assert_eq!(info.len(), 4);\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_query_recoverable_error() {\n        // arrange\n        let query = mock_libbpf_sys::bpf_prog_query_context();\n        let get_fd_by_id = mock_libbpf_sys::bpf_prog_get_fd_by_id_context();\n\n        // expect\n        query.expect().times(2).returning(\n            |_target_fd: std::os::raw::c_int,\n             _type_: libbpf_sys::bpf_attach_type,\n             _query_flags: libbpf_sys::__u32,\n             _attach_flags: *mut libbpf_sys::__u32,\n             prog_ids: *mut libbpf_sys::__u32,\n             prog_cnt: *mut libbpf_sys::__u32|\n             -> ::std::os::raw::c_int {\n                unsafe {\n                    if *prog_cnt == 64 {\n                        set_errno(Errno(ENOSPC));\n                        *prog_cnt = 128;\n                        return 1;\n                    }\n                    let id_array = std::slice::from_raw_parts_mut(prog_ids, 128_usize);\n                    for (i, item) in id_array.iter_mut().enumerate() {\n                        *item = (i + 1) as u32;\n                    }\n                }\n                0\n            },\n        );\n        get_fd_by_id.expect().times(128).returning(|fd| {\n            // return the same fd if it's not 0\n            if fd > 0 {\n                return fd as std::os::raw::c_int;\n            }\n            -1\n        });\n\n        // act\n        let info = prog::query(0).expect(\"Able to successfully query\");\n\n        // assert\n        assert_eq!(info.first().unwrap().id, 1);\n        assert_eq!(info.len(), 128);\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_query_other_error() {\n        // arrange\n        let query = mock_libbpf_sys::bpf_prog_query_context();\n        let get_fd_by_id = mock_libbpf_sys::bpf_prog_get_fd_by_id_context();\n\n        // expect\n        query.expect().times(1).returning(\n            |_target_fd: std::os::raw::c_int,\n             _type_: libbpf_sys::bpf_attach_type,\n             _query_flags: libbpf_sys::__u32,\n             _attach_flags: *mut libbpf_sys::__u32,\n             _prog_ids: *mut libbpf_sys::__u32,\n             _prog_cnt: *mut libbpf_sys::__u32|\n             -> ::std::os::raw::c_int {\n                set_errno(Errno(ENOSYS));\n                1\n            },\n        );\n        get_fd_by_id.expect().never();\n\n        // act\n        let error = prog::query(0);\n\n        // assert\n        assert!(error.is_err());\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_detach2() {\n        // arrange\n        let detach2 = mock_libbpf_sys::bpf_prog_detach2_context();\n\n        // expect\n        detach2.expect().once().returning(|_, _, _| 0);\n\n        // act\n        let r = prog::detach2(0, 0);\n\n        // assert\n        assert!(r.is_ok());\n    }\n\n    #[test]\n    #[serial(libbpf_sys)] // mock contexts are shared\n    fn test_bpf_detach2_error() {\n        // arrange\n        let detach2 = mock_libbpf_sys::bpf_prog_detach2_context();\n\n        // expect\n        detach2.expect().once().returning(|_, _, _| 1);\n\n        // act\n        let r = prog::detach2(0, 0);\n\n        // assert\n        assert!(r.is_err());\n    }\n\n    #[test]\n    #[serial(libc)] // mock contexts are shared\n    fn test_bump_memlock_rlimit() {\n        // arrange\n        let setrlimit = mock_libc::setrlimit_context();\n\n        // expect\n        setrlimit.expect().once().returning(|_, _| 0);\n\n        // act\n        let r = prog::bump_memlock_rlimit();\n\n        // assert\n        assert!(r.is_ok());\n    }\n\n    #[test]\n    #[serial(libc)] // mock contexts are shared\n    fn test_bump_memlock_rlimit_error() {\n        // arrange\n        let setrlimit = mock_libc::setrlimit_context();\n\n        // expect\n        setrlimit.expect().once().returning(|_, _| 1);\n\n        // act\n        let r = prog::bump_memlock_rlimit();\n\n        // assert\n        assert!(r.is_err());\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/controller.rs",
    "content": "use std::os::unix::io::AsRawFd;\nuse std::path::Path;\n\n#[cfg(test)]\nuse bpf::mock_prog as bpf_prog;\n#[cfg(not(test))]\nuse bpf::prog as bpf_prog;\nuse nix::fcntl::OFlag;\nuse nix::sys::stat::Mode;\nuse oci_spec::runtime::LinuxDeviceCgroup;\n\nuse super::bpf::BpfError;\nuse super::program::ProgramError;\nuse super::*;\nuse crate::common::{ControllerOpt, default_allow_devices, default_devices};\nuse crate::v2::controller::Controller;\n\nconst LICENSE: &str = \"Apache\";\n\npub struct Devices {}\n\n#[derive(thiserror::Error, Debug)]\npub enum DevicesControllerError {\n    #[error(\"bpf error: {0}\")]\n    Bpf(#[from] BpfError),\n    #[error(\"nix error: {0}\")]\n    Nix(#[from] nix::Error),\n    #[error(\"program error: {0}\")]\n    Program(#[from] ProgramError),\n}\n\nimpl Controller for Devices {\n    type Error = DevicesControllerError;\n\n    fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_root: &Path,\n    ) -> Result<(), DevicesControllerError> {\n        #[cfg(not(feature = \"cgroupsv2_devices\"))]\n        return Ok(());\n\n        #[cfg(feature = \"cgroupsv2_devices\")]\n        return Self::apply_devices(cgroup_root, controller_opt.resources.devices());\n    }\n}\n\nimpl Devices {\n    pub fn apply_devices(\n        cgroup_root: &Path,\n        linux_devices: &Option<Vec<LinuxDeviceCgroup>>,\n    ) -> Result<(), DevicesControllerError> {\n        tracing::debug!(\"Apply Devices cgroup config\");\n\n        // FIXME: should we start as \"deny all\"?\n        let mut emulator = emulator::Emulator::with_default_allow(false);\n\n        // FIXME: apply user-defined and default rules in which order?\n        if let Some(devices) = linux_devices {\n            for d in devices {\n                tracing::debug!(\"apply user defined rule: {:?}\", d);\n                emulator.add_rule(d);\n            }\n        }\n\n        for d in [\n            default_devices().iter().map(|d| d.into()).collect(),\n            default_allow_devices(),\n        ]\n        .concat()\n        {\n            tracing::debug!(\"apply default rule: {:?}\", d);\n            emulator.add_rule(&d);\n        }\n\n        let prog = program::Program::from_rules(&emulator.rules, emulator.default_allow)?;\n\n        // Increase `ulimit -l` limit to avoid BPF_PROG_LOAD error (#2167).\n        // This limit is not inherited into the container.\n        bpf_prog::bump_memlock_rlimit()?;\n        let prog_fd = bpf_prog::load(LICENSE, prog.bytecodes())?;\n\n        // FIXME: simple way to attach BPF program\n        //  1. get list of existing attached programs\n        //  2. attach this program (not use BPF_F_REPLACE, see below)\n        //  3. detach all programs of 1\n        //\n        // runc will use BPF_F_REPLACE to replace currently attached program if:\n        //   1. BPF_F_REPLACE is supported by kernel\n        //   2. there is exactly one attached program\n        // https://github.com/opencontainers/runc/blob/8e6871a3b14bb74e0ef358aca3b9f8f9cb80f041/libcontainer/cgroups/ebpf/ebpf_linux.go#L165\n        //\n        // IMHO, this is too complicated, and in most cases, we just attach program once without\n        // already attached programs.\n\n        // get the fd of the cgroup root\n        let fd = nix::dir::Dir::open(\n            cgroup_root.as_os_str(),\n            OFlag::O_RDONLY | OFlag::O_DIRECTORY,\n            Mode::from_bits(0o600).unwrap(),\n        )?;\n\n        // collect the programs attached to this cgroup\n        let old_progs = bpf_prog::query(fd.as_raw_fd())?;\n        // attach our new program\n        bpf_prog::attach(prog_fd, fd.as_raw_fd())?;\n        // detach all previous programs\n        for old_prog in old_progs {\n            bpf_prog::detach2(old_prog.fd, fd.as_raw_fd())?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::os::unix::io::RawFd;\n\n    use bpf::mock_prog;\n    use oci_spec::runtime::{LinuxDeviceCgroupBuilder, LinuxDeviceType};\n    use serial_test::serial;\n\n    use super::*;\n    use crate::test::setup;\n\n    #[test]\n    #[serial(bpf)] // mock contexts are shared\n    fn test_apply_devices() {\n        // arrange\n        let (tmp, _) = setup(\"some.value\");\n        let a_type = LinuxDeviceCgroupBuilder::default()\n            .typ(LinuxDeviceType::A)\n            .build()\n            .unwrap();\n        let file_descriptor: RawFd = 6;\n\n        // expect\n        let bump_memlock_rlimit = mock_prog::bump_memlock_rlimit_context();\n        let load = mock_prog::load_context();\n        let query = mock_prog::query_context();\n        let attach = mock_prog::attach_context();\n        let detach2 = mock_prog::detach2_context();\n        bump_memlock_rlimit.expect().once().returning(|| Ok(()));\n        load.expect()\n            .once()\n            .returning(move |_, _| Ok(file_descriptor));\n        query.expect().once().returning(|_| Ok(vec![]));\n        attach.expect().once().returning(|_, _| Ok(()));\n        detach2.expect().never();\n\n        // act\n        Devices::apply_devices(tmp.path(), &Some(vec![a_type])).expect(\"Could not apply devices\");\n    }\n\n    #[test]\n    #[serial(bpf)] // mock contexts are shared\n    fn test_existing_programs() {\n        // arrange\n        let (tmp, _) = setup(\"some.value\");\n        let a_type = LinuxDeviceCgroupBuilder::default()\n            .typ(LinuxDeviceType::A)\n            .build()\n            .unwrap();\n        let file_descriptor: RawFd = 6;\n        let existing_program_1 = bpf::ProgramInfo {\n            id: u32::default(),\n            fd: i32::default(),\n        };\n\n        // expect\n        let bump_memlock_rlimit = mock_prog::bump_memlock_rlimit_context();\n        let load = mock_prog::load_context();\n        let query = mock_prog::query_context();\n        let attach = mock_prog::attach_context();\n        let detach2 = mock_prog::detach2_context();\n        bump_memlock_rlimit.expect().once().returning(|| Ok(()));\n        load.expect()\n            .once()\n            .returning(move |_, _| Ok(file_descriptor));\n        query\n            .expect()\n            .once()\n            .returning(move |_| Ok(vec![existing_program_1.clone()]));\n        attach.expect().once().returning(|_, _| Ok(()));\n        detach2.expect().once().returning(|_, _| Ok(()));\n\n        // act\n        Devices::apply_devices(tmp.path(), &Some(vec![a_type])).expect(\"Could not apply devices\");\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/emulator.rs",
    "content": "use oci_spec::runtime::{LinuxDeviceCgroup, LinuxDeviceType};\n\n// For cgroup v1 compatibility, runc implements a device emulator to calculate the final rules given\n// a list of user-defined rules.\n// https://github.com/opencontainers/runc/commit/2353ffec2bb670a200009dc7a54a56b93145f141\n//\n// I chose to implement a very simple algorithm, which will just work in most cases, but with\n// diversion from cgroupv1 in some cases:\n//  1. just add used-defined rules one by one\n//  2. discard existing rules when encountering a rule with type='a', and change to deny/allow all\n//     list according the 'allow' of the rule\n//  3. bpf program will check rule one by one in *reversed* order, return action of first rule\n//     which matches device access operation\n//\n\n// FIXME: should we use runc's implementation?\npub struct Emulator {\n    pub default_allow: bool,\n    pub rules: Vec<LinuxDeviceCgroup>,\n}\n\nimpl Emulator {\n    pub fn with_default_allow(default_allow: bool) -> Self {\n        Emulator {\n            default_allow,\n            rules: Vec::new(),\n        }\n    }\n\n    pub fn add_rules(&mut self, rules: &[LinuxDeviceCgroup]) {\n        for rule in rules {\n            self.add_rule(rule);\n        }\n    }\n\n    pub fn add_rule(&mut self, rule: &LinuxDeviceCgroup) {\n        // special case, switch to blacklist or whitelist and clear all existing rules\n        // NOTE: we ignore other fields when type='a', this is same as cgroup v1 and runc\n        if rule.typ().unwrap_or_default() == LinuxDeviceType::A {\n            self.default_allow = rule.allow();\n            self.rules.clear();\n            return;\n        }\n\n        // empty access match nothing, just discard this rule\n        if rule.access().is_none() {\n            return;\n        }\n\n        self.rules.push(rule.clone());\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::LinuxDeviceCgroupBuilder;\n\n    use super::*;\n\n    #[test]\n    fn test_with_default_allow() {\n        // act\n        let emulator = Emulator::with_default_allow(true);\n\n        // assert\n        assert_eq!(emulator.rules.len(), 0);\n        assert!(emulator.default_allow);\n    }\n\n    #[test]\n    fn test_type_a_rule() {\n        // arrange\n        let mut emulator = Emulator::with_default_allow(false);\n        let cgroup = LinuxDeviceCgroupBuilder::default()\n            .typ(LinuxDeviceType::A)\n            .build()\n            .unwrap();\n\n        // act\n        emulator.add_rule(&cgroup);\n\n        // assert\n        assert_eq!(emulator.rules.len(), 0);\n        assert!(!emulator.default_allow);\n    }\n\n    #[test]\n    fn test_add_empty_rule() {\n        // arrange\n        let mut emulator = Emulator::with_default_allow(false);\n        let cgroup = LinuxDeviceCgroupBuilder::default().build().unwrap();\n\n        // act\n        emulator.add_rule(&cgroup);\n\n        // assert\n        assert_eq!(emulator.rules.len(), 0);\n        assert!(!emulator.default_allow);\n    }\n\n    #[test]\n    fn test_add_some_rule() {\n        // arrange\n        let mut emulator = Emulator::with_default_allow(false);\n        let permission: &str = \"PERMISSION\";\n        let cgroup = LinuxDeviceCgroupBuilder::default()\n            .typ(LinuxDeviceType::B)\n            .access(permission)\n            .build()\n            .unwrap();\n\n        // act\n        emulator.add_rule(&cgroup);\n\n        // assert\n        let top_rule = emulator.rules.first().unwrap();\n        assert_eq!(top_rule.access(), &Some(permission.to_string()));\n        assert!(!emulator.default_allow);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/mocks.rs",
    "content": "// Here we duplicate the signatures of external functions and apply\n// the mockall::automock macro to generate mock modules for use\n// in tests, allowing us to exercise code paths without eg making syscalls\n\n#[cfg_attr(test, automock())]\npub mod libc {\n    #[cfg(target_env = \"musl\")]\n    #[allow(non_camel_case_types)]\n    pub type __rlimit_resource_t = libc::c_int;\n\n    #[cfg(not(target_env = \"musl\"))]\n    #[allow(non_camel_case_types)]\n    pub type __rlimit_resource_t = libc::__rlimit_resource_t;\n\n    pub fn setrlimit(_resource: __rlimit_resource_t, _rlim: *const libc::rlimit) -> libc::c_int {\n        unimplemented!();\n    }\n}\n\n#[cfg_attr(test, automock())]\npub mod libbpf_sys {\n    pub fn bpf_prog_load(\n        _type_: libbpf_sys::bpf_prog_type,\n        _name: *const ::std::os::raw::c_char,\n        _license: *const ::std::os::raw::c_char,\n        _insns: *const libbpf_sys::bpf_insn,\n        _insns_cnt: libbpf_sys::size_t,\n        _opts: *const libbpf_sys::bpf_prog_load_opts,\n    ) -> ::std::os::raw::c_int {\n        unimplemented!();\n    }\n\n    pub fn bpf_prog_query(\n        _target_fd: ::std::os::raw::c_int,\n        _type_: libbpf_sys::bpf_attach_type,\n        _query_flags: libbpf_sys::__u32,\n        _attach_flags: *mut libbpf_sys::__u32,\n        _prog_ids: *mut libbpf_sys::__u32,\n        _prog_cnt: *mut libbpf_sys::__u32,\n    ) -> ::std::os::raw::c_int {\n        unimplemented!();\n    }\n\n    pub fn bpf_prog_get_fd_by_id(_id: libbpf_sys::__u32) -> ::std::os::raw::c_int {\n        unimplemented!();\n    }\n\n    pub fn bpf_prog_detach2(\n        _prog_fd: ::std::os::raw::c_int,\n        _attachable_fd: ::std::os::raw::c_int,\n        _type_: libbpf_sys::bpf_attach_type,\n    ) -> ::std::os::raw::c_int {\n        unimplemented!();\n    }\n\n    pub fn bpf_prog_attach(\n        _prog_fd: ::std::os::raw::c_int,\n        _attachable_fd: ::std::os::raw::c_int,\n        _type_: libbpf_sys::bpf_attach_type,\n        _flags: ::std::os::raw::c_uint,\n    ) -> ::std::os::raw::c_int {\n        unimplemented!();\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/mod.rs",
    "content": "pub mod bpf;\npub mod controller;\npub mod emulator;\npub mod program;\n\n#[cfg(test)]\n#[allow(clippy::too_many_arguments)]\npub mod mocks;\n\npub use controller::Devices;\n"
  },
  {
    "path": "crates/libcgroups/src/v2/devices/program.rs",
    "content": "use oci_spec::runtime::*;\nuse rbpf::disassembler::disassemble;\nuse rbpf::insn_builder::{Arch as RbpfArch, *};\n\npub struct Program {\n    prog: BpfCode,\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum ProgramError {\n    #[error(\"io error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"invalid access: {0}\")]\n    InvalidAccess(char),\n    #[error(\"{0} device not supported\")]\n    DeviceNotSupported(&'static str),\n    #[error(\"wildcard device type should be removed when cleaning rules\")]\n    WildcardDevice,\n}\n\nimpl Program {\n    pub fn from_rules(\n        rules: &[LinuxDeviceCgroup],\n        default_allow: bool,\n    ) -> Result<Self, ProgramError> {\n        let mut prog = Program {\n            prog: BpfCode::new(),\n        };\n        prog.init();\n\n        for rule in rules.iter().rev() {\n            prog.add_rule(rule)?;\n        }\n        prog.finalize(default_allow);\n        Ok(prog)\n    }\n\n    pub fn bytecodes(&self) -> &[u8] {\n        self.prog.into_bytes()\n    }\n\n    fn finalize(&mut self, default_allow: bool) {\n        self.prog\n            .mov(Source::Imm, RbpfArch::X32)\n            .set_dst(0)\n            .set_imm(default_allow as i32)\n            .push();\n\n        self.prog.exit().push();\n    }\n\n    // struct bpf_cgroup_dev_ctx: https://elixir.bootlin.com/linux/v5.3.6/source/include/uapi/linux/bpf.h#L3423\n    /*\n    u32 access_type\n    u32 major\n    u32 minor\n    */\n    // R2 <- type (lower 16 bit of u32 access_type at R1[0])\n    // R3 <- access (upper 16 bit of u32 access_type at R1[0])\n    // R4 <- major (u32 major at R1[4])\n    // R5 <- minor (u32 minor at R1[8])\n    fn init(&mut self) {\n        self.prog\n            .load_x(MemSize::Word)\n            .set_src(1)\n            .set_off(0)\n            .set_dst(2)\n            .push();\n\n        self.prog\n            .bit_and(Source::Imm, RbpfArch::X32)\n            .set_dst(2)\n            .set_imm(0xFFFF)\n            .push();\n\n        self.prog\n            .load_x(MemSize::Word)\n            .set_src(1)\n            .set_off(0)\n            .set_dst(3)\n            .push();\n\n        self.prog\n            .right_shift(Source::Imm, RbpfArch::X32)\n            .set_imm(16)\n            .set_dst(3)\n            .push();\n\n        self.prog\n            .load_x(MemSize::Word)\n            .set_src(1)\n            .set_off(4)\n            .set_dst(4)\n            .push();\n\n        self.prog\n            .load_x(MemSize::Word)\n            .set_src(1)\n            .set_off(8)\n            .set_dst(5)\n            .push();\n    }\n\n    fn add_rule(&mut self, rule: &LinuxDeviceCgroup) -> Result<(), ProgramError> {\n        let dev_type = bpf_dev_type(rule.typ().unwrap_or_default())?;\n        let access = bpf_access(rule.access().clone().unwrap_or_default())?;\n        let has_access = access\n            != (libbpf_sys::BPF_DEVCG_ACC_READ\n                | libbpf_sys::BPF_DEVCG_ACC_WRITE\n                | libbpf_sys::BPF_DEVCG_ACC_MKNOD);\n\n        let has_major = rule.major().is_some() && rule.major().unwrap() >= 0;\n        let has_minor = rule.minor().is_some() && rule.minor().unwrap() >= 0;\n\n        // count of instructions of this rule\n        let mut instruction_count = 1; // execute dev_type\n        if has_access {\n            instruction_count += 3;\n        }\n        if has_major {\n            instruction_count += 1;\n        }\n        if has_minor {\n            instruction_count += 1;\n        }\n        instruction_count += 2;\n\n        // if (R2 != dev_type) goto next rule\n        let mut next_rule_offset = instruction_count - 1;\n        self.prog\n            .jump_conditional(Cond::NotEquals, Source::Imm)\n            .set_dst(2)\n            .set_imm(dev_type as i32)\n            .set_off(next_rule_offset)\n            .push();\n\n        if has_access {\n            next_rule_offset -= 3;\n            // if (R3 & access != R3 /* use R1 as a temp var */) goto next rule\n            self.prog\n                .mov(Source::Reg, RbpfArch::X32)\n                .set_dst(1)\n                .set_src(3)\n                .push();\n\n            self.prog\n                .bit_and(Source::Imm, RbpfArch::X32)\n                .set_dst(1)\n                .set_imm(access as i32)\n                .push();\n\n            self.prog\n                .jump_conditional(Cond::NotEquals, Source::Reg)\n                .set_dst(1)\n                .set_src(3)\n                .set_off(next_rule_offset)\n                .push();\n        }\n\n        if has_major {\n            next_rule_offset -= 1;\n            // if (R4 != major) goto next rule\n            self.prog\n                .jump_conditional(Cond::NotEquals, Source::Imm)\n                .set_dst(4)\n                .set_imm(rule.major().unwrap() as i32)\n                .set_off(next_rule_offset)\n                .push();\n        }\n\n        if has_minor {\n            next_rule_offset -= 1;\n            // if (R5 != minor) goto next rule\n            self.prog\n                .jump_conditional(Cond::NotEquals, Source::Imm)\n                .set_dst(5)\n                .set_imm(rule.minor().unwrap() as i32)\n                .set_off(next_rule_offset)\n                .push();\n        }\n\n        // matched, return rule.allow\n        self.prog\n            .mov(Source::Imm, RbpfArch::X32)\n            .set_dst(0)\n            .set_imm(rule.allow() as i32)\n            .push();\n        self.prog.exit().push();\n\n        Ok(())\n    }\n\n    pub fn dump(&self) {\n        disassemble(self.prog.into_bytes());\n    }\n\n    pub fn execute(\n        &self,\n        typ: LinuxDeviceType,\n        major: u32,\n        minor: u32,\n        access: String,\n    ) -> Result<u64, ProgramError> {\n        let mut mem = bpf_cgroup_dev_ctx(typ, major, minor, access)?;\n        let vm = rbpf::EbpfVmRaw::new(Some(self.prog.into_bytes()))?;\n        let result = vm.execute_program(&mut mem[..])?;\n        Ok(result)\n    }\n}\n\nfn bpf_dev_type(typ: LinuxDeviceType) -> Result<u32, ProgramError> {\n    let dev_type: u32 = match typ {\n        LinuxDeviceType::C => libbpf_sys::BPF_DEVCG_DEV_CHAR,\n        LinuxDeviceType::U => return Err(ProgramError::DeviceNotSupported(\"unbuffered char\")),\n        LinuxDeviceType::B => libbpf_sys::BPF_DEVCG_DEV_BLOCK,\n        LinuxDeviceType::P => return Err(ProgramError::DeviceNotSupported(\"pipe device\")),\n        LinuxDeviceType::A => return Err(ProgramError::WildcardDevice),\n    };\n    Ok(dev_type)\n}\n\nfn bpf_access(access: String) -> Result<u32, ProgramError> {\n    let mut v = 0_u32;\n    for c in access.chars() {\n        let cur_access = match c {\n            'r' => libbpf_sys::BPF_DEVCG_ACC_READ,\n            'w' => libbpf_sys::BPF_DEVCG_ACC_WRITE,\n            'm' => libbpf_sys::BPF_DEVCG_ACC_MKNOD,\n            _ => return Err(ProgramError::InvalidAccess(c)),\n        };\n        v |= cur_access;\n    }\n    Ok(v)\n}\n\nfn bpf_cgroup_dev_ctx(\n    typ: LinuxDeviceType,\n    major: u32,\n    minor: u32,\n    access: String,\n) -> Result<Vec<u8>, ProgramError> {\n    let mut mem = Vec::with_capacity(12);\n\n    let mut type_access = 0_u32;\n    if let Ok(t) = bpf_dev_type(typ) {\n        type_access = t & 0xFFFF;\n    }\n\n    type_access |= bpf_access(access)? << 16;\n\n    mem.extend_from_slice(&type_access.to_ne_bytes());\n    mem.extend_from_slice(&major.to_ne_bytes());\n    mem.extend_from_slice(&minor.to_ne_bytes());\n\n    Ok(mem)\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::Result;\n    use oci_spec::runtime::LinuxDeviceCgroupBuilder;\n\n    use super::*;\n\n    fn build_bpf_program(rules: &Option<Vec<LinuxDeviceCgroup>>) -> Result<Program> {\n        let mut em = crate::v2::devices::emulator::Emulator::with_default_allow(false);\n        if let Some(rules) = rules {\n            em.add_rules(rules);\n        }\n\n        Ok(Program::from_rules(&em.rules, em.default_allow)?)\n    }\n\n    #[test]\n    fn test_devices_allow_single() {\n        let rules = vec![\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::C)\n                .major(10)\n                .minor(20)\n                .access(\"r\")\n                .build()\n                .unwrap(),\n        ];\n\n        let prog = build_bpf_program(&Some(rules)).unwrap();\n        let ty_list = vec![\n            LinuxDeviceType::C,\n            LinuxDeviceType::U,\n            LinuxDeviceType::P,\n            LinuxDeviceType::B,\n        ];\n        let major_list = vec![10_u32, 99_u32];\n        let minor_list = vec![20_u32, 00_u32];\n        let access_list = vec![\"r\", \"w\", \"m\"];\n        for ty in &ty_list {\n            for major in &major_list {\n                for minor in &minor_list {\n                    for access in &access_list {\n                        let ret = prog.execute(*ty, *major, *minor, access.to_string());\n                        assert!(ret.is_ok());\n\n                        println!(\"execute {ty:?} {major} {minor} {access} -> {ret:?}\");\n                        if *ty == LinuxDeviceType::C  // only this is allowed\n                            && *major == 10\n                                && *minor == 20\n                                && access.eq(&\"r\")\n                        {\n                            assert_eq!(ret.unwrap(), 1);\n                        } else {\n                            assert_eq!(ret.unwrap(), 0);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_devices_deny_all() {\n        let rules = vec![];\n\n        let prog = build_bpf_program(&Some(rules)).unwrap();\n        let ty_list = vec![\n            LinuxDeviceType::C,\n            LinuxDeviceType::U,\n            LinuxDeviceType::P,\n            LinuxDeviceType::B,\n        ];\n        let major_list = vec![10_u32, 99_u32];\n        let minor_list = vec![20_u32, 00_u32];\n        let access_list = vec![\"r\", \"w\", \"m\"];\n        for ty in &ty_list {\n            for major in &major_list {\n                for minor in &minor_list {\n                    for access in &access_list {\n                        let ret = prog.execute(*ty, *major, *minor, access.to_string());\n                        assert!(ret.is_ok());\n                        assert_eq!(ret.unwrap(), 0);\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_devices_allow_all() {\n        let rules = vec![\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::A)\n                .build()\n                .unwrap(),\n        ];\n\n        let prog = build_bpf_program(&Some(rules)).unwrap();\n        let ty_list = vec![\n            LinuxDeviceType::C,\n            LinuxDeviceType::U,\n            LinuxDeviceType::P,\n            LinuxDeviceType::B,\n        ];\n        let major_list = vec![10_u32, 99_u32];\n        let minor_list = vec![20_u32, 00_u32];\n        let access_list = vec![\"r\", \"w\", \"m\"];\n        for ty in &ty_list {\n            for major in &major_list {\n                for minor in &minor_list {\n                    for access in &access_list {\n                        let ret = prog.execute(*ty, *major, *minor, access.to_string());\n                        assert!(ret.is_ok());\n\n                        println!(\"execute {ty:?} {major} {minor} {access} -> {ret:?}\");\n                        assert_eq!(ret.unwrap(), 1);\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_devices_allow_wildcard() {\n        let rules = vec![\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::C)\n                .minor(20)\n                .access(\"r\")\n                .build()\n                .unwrap(),\n        ];\n\n        let prog = build_bpf_program(&Some(rules)).unwrap();\n        let ty_list = vec![\n            LinuxDeviceType::C,\n            LinuxDeviceType::U,\n            LinuxDeviceType::P,\n            LinuxDeviceType::B,\n        ];\n        let major_list = vec![10_u32, 99_u32];\n        let minor_list = vec![20_u32, 00_u32];\n        let access_list = vec![\"r\", \"w\", \"m\"];\n        for ty in &ty_list {\n            for major in &major_list {\n                for minor in &minor_list {\n                    for access in &access_list {\n                        let ret = prog.execute(*ty, *major, *minor, access.to_string());\n                        assert!(ret.is_ok());\n\n                        println!(\"execute {ty:?} {major} {minor} {access} -> {ret:?}\");\n                        if *ty == LinuxDeviceType::C && *minor == 20 && access.eq(&\"r\") {\n                            assert_eq!(ret.unwrap(), 1);\n                        } else {\n                            assert_eq!(ret.unwrap(), 0);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    #[test]\n    fn test_devices_allow_and_deny() {\n        let rules = vec![\n            LinuxDeviceCgroupBuilder::default()\n                .allow(true)\n                .typ(LinuxDeviceType::C)\n                .minor(20)\n                .access(\"rw\")\n                .build()\n                .unwrap(),\n            LinuxDeviceCgroupBuilder::default()\n                .allow(false)\n                .typ(LinuxDeviceType::C)\n                .major(10)\n                .access(\"r\")\n                .build()\n                .unwrap(),\n        ];\n\n        let prog = build_bpf_program(&Some(rules)).unwrap();\n        let ty_list = vec![\n            LinuxDeviceType::C,\n            LinuxDeviceType::U,\n            LinuxDeviceType::P,\n            LinuxDeviceType::B,\n        ];\n        let major_list = vec![10_u32, 99_u32];\n        let minor_list = vec![20_u32, 00_u32];\n        let access_list = vec![\"r\", \"w\", \"m\"];\n        for ty in &ty_list {\n            for major in &major_list {\n                for minor in &minor_list {\n                    for access in &access_list {\n                        let ret = prog.execute(*ty, *major, *minor, access.to_string());\n                        assert!(ret.is_ok());\n\n                        println!(\"execute {ty:?} {major} {minor} {access} -> {ret:?}\");\n                        if *ty == LinuxDeviceType::C && *major == 10 && access.eq(&\"r\") {\n                            assert_eq!(ret.unwrap(), 0);\n                        } else if *ty == LinuxDeviceType::C\n                            && *minor == 20\n                            && (access.eq(&\"r\") || access.eq(&\"w\"))\n                        {\n                            assert_eq!(ret.unwrap(), 1);\n                        } else {\n                            assert_eq!(ret.unwrap(), 0);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/freezer.rs",
    "content": "use std::fs::OpenOptions;\nuse std::io::{BufRead, BufReader, Read, Seek, Write};\nuse std::path::Path;\nuse std::str::{self, Utf8Error};\nuse std::thread;\nuse std::time::Duration;\n\nuse super::controller::Controller;\nuse crate::common::{ControllerOpt, FreezerState, WrapIoResult, WrappedIoError};\n\nconst CGROUP_FREEZE: &str = \"cgroup.freeze\";\nconst CGROUP_EVENTS: &str = \"cgroup.events\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2FreezerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"freezer not supported: {0}\")]\n    NotSupported(WrappedIoError),\n    #[error(\"expected \\\"cgroup.freeze\\\" to be in state {expected:?} but was in {actual:?}\")]\n    ExpectedToBe {\n        expected: FreezerState,\n        actual: FreezerState,\n    },\n    #[error(\"unexpected \\\"cgroup.freeze\\\" state: {state}\")]\n    UnknownState { state: String },\n    #[error(\"timeout of {0} ms reached waiting for the cgroup to freeze\")]\n    Timeout(u128),\n    #[error(\"invalid utf8: {0}\")]\n    InvalidUtf8(#[from] Utf8Error),\n}\n\npub struct Freezer {}\n\nimpl Controller for Freezer {\n    type Error = V2FreezerError;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error> {\n        if let Some(freezer_state) = controller_opt.freezer_state {\n            Self::apply(freezer_state, cgroup_path)?;\n        }\n\n        Ok(())\n    }\n}\n\nimpl Freezer {\n    fn apply(freezer_state: FreezerState, path: &Path) -> Result<(), V2FreezerError> {\n        let state_str = match freezer_state {\n            FreezerState::Undefined => return Ok(()),\n            FreezerState::Frozen => \"1\",\n            FreezerState::Thawed => \"0\",\n        };\n\n        let target = path.join(CGROUP_FREEZE);\n        match OpenOptions::new().create(false).write(true).open(&target) {\n            Err(err) => {\n                if freezer_state == FreezerState::Frozen {\n                    return Err(V2FreezerError::NotSupported(WrappedIoError::Open {\n                        err,\n                        path: target,\n                    }));\n                }\n                return Ok(());\n            }\n            Ok(mut file) => file\n                .write_all(state_str.as_bytes())\n                .wrap_write(target, state_str)?,\n        };\n\n        // confirm that the cgroup did actually change states.\n        let actual_state = Self::read_freezer_state(path)?;\n        if !actual_state.eq(&freezer_state) {\n            return Err(V2FreezerError::ExpectedToBe {\n                expected: freezer_state,\n                actual: actual_state,\n            });\n        }\n\n        Ok(())\n    }\n\n    fn read_freezer_state(path: &Path) -> Result<FreezerState, V2FreezerError> {\n        let target = path.join(CGROUP_FREEZE);\n        let mut buf = [0; 1];\n        OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(&target)\n            .wrap_open(&target)?\n            .read_exact(&mut buf)\n            .wrap_read(&target)?;\n\n        let state = str::from_utf8(&buf)?;\n        match state {\n            \"0\" => Ok(FreezerState::Thawed),\n            \"1\" => Self::wait_frozen(path),\n            _ => Err(V2FreezerError::UnknownState {\n                state: state.into(),\n            }),\n        }\n    }\n\n    // wait_frozen polls cgroup.events until it sees \"frozen 1\" in it.\n    fn wait_frozen(path: &Path) -> Result<FreezerState, V2FreezerError> {\n        let path = path.join(CGROUP_EVENTS);\n        let f = OpenOptions::new()\n            .create(false)\n            .read(true)\n            .open(&path)\n            .wrap_open(&path)?;\n        let mut f = BufReader::new(f);\n\n        let wait_time = Duration::from_millis(10);\n        let max_iter = 1000;\n        let mut iter = 0;\n        let mut line = String::new();\n\n        loop {\n            if iter == max_iter {\n                return Err(V2FreezerError::Timeout(wait_time.as_millis() * max_iter));\n            }\n            line.clear();\n            let num_bytes = f.read_line(&mut line).wrap_read(&path)?;\n            if num_bytes == 0 {\n                break;\n            }\n            if line.starts_with(\"frozen \") {\n                if line.starts_with(\"frozen 1\") {\n                    if iter > 1 {\n                        tracing::debug!(\"frozen after {} retries\", iter)\n                    }\n                    return Ok(FreezerState::Frozen);\n                }\n                iter += 1;\n                thread::sleep(wait_time);\n                f.rewind().wrap_other(&path)?;\n            }\n        }\n\n        Ok(FreezerState::Undefined)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::sync::Arc;\n\n    use super::*;\n    use crate::common::FreezerState;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_freezer_state() {\n        let tmp = Arc::new(tempfile::tempdir().unwrap());\n        set_fixture(tmp.path(), CGROUP_FREEZE, \"\").expect(\"Set fixure for freezer state\");\n        set_fixture(tmp.path(), CGROUP_EVENTS, \"populated 0\\nfrozen 0\")\n            .expect(\"Set fixure for freezer state\");\n\n        // set Frozen state.\n        {\n            // use another thread to update events file async.\n            let p = Arc::clone(&tmp);\n            thread::spawn(move || {\n                thread::sleep(Duration::from_millis(100));\n                set_fixture(p.path(), CGROUP_EVENTS, \"populated 0\\nfrozen 1\")\n                    .expect(\"Set fixure for freezer state\");\n            });\n            let freezer_state = FreezerState::Frozen;\n            Freezer::apply(freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_FREEZE)).expect(\"Read to string\");\n            assert_eq!(\"1\", state_content);\n        }\n\n        // set Thawed state.\n        {\n            let freezer_state = FreezerState::Thawed;\n            Freezer::apply(freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_FREEZE)).expect(\"Read to string\");\n            assert_eq!(\"0\", state_content);\n        }\n\n        // set Undefined state.\n        {\n            let old_state_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_FREEZE)).expect(\"Read to string\");\n            let freezer_state = FreezerState::Undefined;\n            Freezer::apply(freezer_state, tmp.path()).expect(\"Set freezer state\");\n\n            let state_content =\n                std::fs::read_to_string(tmp.path().join(CGROUP_FREEZE)).expect(\"Read to string\");\n            assert_eq!(old_state_content, state_content);\n        }\n    }\n\n    #[test]\n    fn test_set_freezer_state_error() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_FREEZE, \"\").expect(\"Set fixure for freezer state\");\n        set_fixture(tmp.path(), CGROUP_EVENTS, \"\").expect(\"Set fixure for freezer state\");\n\n        // events file does not contain \"frozen 1\"\n        {\n            let freezer_state = FreezerState::Frozen;\n            let r = Freezer::apply(freezer_state, tmp.path());\n            assert!(r.is_err());\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/hugetlb.rs",
    "content": "use std::collections::HashMap;\nuse std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::LinuxHugepageLimit;\n\nuse super::controller::Controller;\nuse crate::common::{\n    self, ControllerOpt, EitherError, MustBePowerOfTwo, WrappedIoError, read_cgroup_file,\n};\nuse crate::stats::{\n    HugeTlbStats, StatsProvider, SupportedPageSizesError, parse_single_value, supported_page_sizes,\n};\n\n#[derive(thiserror::Error, Debug)]\npub enum V2HugeTlbControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"malformed page size {page_size}: {err}\")]\n    MalformedPageSize {\n        page_size: String,\n        err: EitherError<ParseIntError, MustBePowerOfTwo>,\n    },\n}\n\npub struct HugeTlb {}\n\nimpl Controller for HugeTlb {\n    type Error = V2HugeTlbControllerError;\n\n    fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_root: &std::path::Path,\n    ) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply hugetlb cgroup v2 config\");\n        if let Some(hugepage_limits) = controller_opt.resources.hugepage_limits() {\n            for hugetlb in hugepage_limits {\n                Self::apply(cgroup_root, hugetlb)?\n            }\n        }\n        Ok(())\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V2HugeTlbStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"getting supported huge page sizes: {0}\")]\n    SupportedPageSizes(#[from] SupportedPageSizesError),\n    #[error(\"failed to parse max value for {path}: {err}\")]\n    ParseMax { path: PathBuf, err: ParseIntError },\n}\n\nimpl StatsProvider for HugeTlb {\n    type Error = V2HugeTlbStatsError;\n    type Stats = HashMap<String, HugeTlbStats>;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let page_sizes = supported_page_sizes()?;\n        let mut hugetlb_stats = HashMap::with_capacity(page_sizes.len());\n\n        for page_size in page_sizes {\n            hugetlb_stats.insert(\n                page_size.clone(),\n                Self::stats_for_page_size(cgroup_path, &page_size)?,\n            );\n        }\n\n        Ok(hugetlb_stats)\n    }\n}\n\nimpl HugeTlb {\n    fn apply(\n        root_path: &Path,\n        hugetlb: &LinuxHugepageLimit,\n    ) -> Result<(), V2HugeTlbControllerError> {\n        let page_size_raw: String = hugetlb\n            .page_size()\n            .chars()\n            .take_while(|c| c.is_ascii_digit())\n            .collect();\n        let page_size: u64 = match page_size_raw.parse() {\n            Ok(page_size) => page_size,\n            Err(err) => {\n                return Err(V2HugeTlbControllerError::MalformedPageSize {\n                    page_size: page_size_raw,\n                    err: EitherError::Left(err),\n                });\n            }\n        };\n        if !Self::is_power_of_two(page_size) {\n            return Err(V2HugeTlbControllerError::MalformedPageSize {\n                page_size: page_size_raw,\n                err: EitherError::Right(MustBePowerOfTwo),\n            });\n        }\n\n        common::write_cgroup_file(\n            root_path.join(format!(\"hugetlb.{}.max\", hugetlb.page_size())),\n            hugetlb.limit(),\n        )?;\n\n        let rsvd_file_path = root_path.join(format!(\"hugetlb.{}.rsvd.max\", hugetlb.page_size()));\n        if rsvd_file_path.exists() {\n            common::write_cgroup_file(rsvd_file_path, hugetlb.limit())?;\n        }\n\n        Ok(())\n    }\n\n    fn is_power_of_two(number: u64) -> bool {\n        (number != 0) && (number & (number.saturating_sub(1))) == 0\n    }\n\n    fn stats_for_page_size(\n        cgroup_path: &Path,\n        page_size: &str,\n    ) -> Result<HugeTlbStats, V2HugeTlbStatsError> {\n        let mut file_prefix = format!(\"hugetlb.{page_size}.rsvd\");\n        let mut path = cgroup_path.join(format!(\"{file_prefix}.events\"));\n        let events = read_cgroup_file(&path).or_else(|_| {\n            file_prefix = format!(\"hugetlb.{page_size}\");\n            path = cgroup_path.join(format!(\"{file_prefix}.events\"));\n            read_cgroup_file(&path)\n        })?;\n\n        let fail_count: u64 = events\n            .lines()\n            .find(|l| l.starts_with(\"max\"))\n            .map(|l| l[3..].trim().parse())\n            .transpose()\n            .map_err(|err| V2HugeTlbStatsError::ParseMax {\n                path: path.clone(),\n                err,\n            })?\n            .unwrap_or_default();\n\n        Ok(HugeTlbStats {\n            usage: parse_single_value(&cgroup_path.join(format!(\"{file_prefix}.current\")))?,\n            fail_count,\n            ..Default::default()\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs::read_to_string;\n\n    use oci_spec::runtime::LinuxHugepageLimitBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_hugetlb() {\n        let page_file_name = \"hugetlb.2MB.max\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), page_file_name, \"0\").expect(\"Set fixture for 2 MB page size\");\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"2MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n        HugeTlb::apply(tmp.path(), &hugetlb).expect(\"apply hugetlb\");\n        let content =\n            read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n        assert_eq!(hugetlb.limit().to_string(), content);\n    }\n\n    #[test]\n    fn test_set_hugetlb_with_invalid_page_size() {\n        let tmp = tempfile::tempdir().unwrap();\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"3MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n\n        let result = HugeTlb::apply(tmp.path(), &hugetlb);\n        assert!(\n            result.is_err(),\n            \"page size that is not a power of two should be an error\"\n        );\n    }\n\n    #[test]\n    fn test_set_rsvd_hugetlb() {\n        let page_file_name = \"hugetlb.2MB.max\";\n        let rsvd_page_file_name = \"hugetlb.2MB.rsvd.max\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), page_file_name, \"0\").expect(\"Set fixture for 2 MB page size\");\n        set_fixture(tmp.path(), rsvd_page_file_name, \"0\")\n            .expect(\"Set fixture for 2 MB rsvd page size\");\n\n        let hugetlb = LinuxHugepageLimitBuilder::default()\n            .page_size(\"2MB\")\n            .limit(16384)\n            .build()\n            .unwrap();\n        HugeTlb::apply(tmp.path(), &hugetlb).expect(\"apply hugetlb\");\n\n        let content =\n            read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n        let rsvd_content = read_to_string(tmp.path().join(rsvd_page_file_name))\n            .expect(\"Read hugetlb file content\");\n\n        assert_eq!(hugetlb.limit().to_string(), content);\n        assert_eq!(hugetlb.limit().to_string(), rsvd_content);\n    }\n\n    quickcheck! {\n        fn property_test_set_hugetlb(hugetlb: LinuxHugepageLimit) -> bool {\n            let page_file_name = format!(\"hugetlb.{:?}.max\", hugetlb.page_size());\n            let tmp = tempfile::tempdir().unwrap();\n            set_fixture(tmp.path(), &page_file_name, \"0\").expect(\"Set fixture for page size\");\n            let result = HugeTlb::apply(tmp.path(), &hugetlb);\n\n            let page_size: String = hugetlb\n            .page_size()\n            .chars()\n            .take_while(|c| c.is_ascii_digit())\n            .collect();\n            let page_size: u64 = page_size.parse().expect(\"parse page size\");\n\n            if HugeTlb::is_power_of_two(page_size) && page_size != 1 {\n                let content =\n                    read_to_string(tmp.path().join(page_file_name)).expect(\"Read hugetlb file content\");\n                hugetlb.limit().to_string() == content\n            } else {\n                result.is_err()\n            }\n        }\n    }\n\n    #[test]\n    fn test_stat_hugetbl() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"hugetlb.2MB.current\", \"1024\\n\").expect(\"set hugetlb current\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.events\", \"max 5\\n\").expect(\"set hugetlb events\");\n\n        let actual = HugeTlb::stats_for_page_size(tmp.path(), \"2MB\").expect(\"get cgroup stats\");\n\n        let expected = HugeTlbStats {\n            usage: 1024,\n            max_usage: 0,\n            fail_count: 5,\n        };\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_stat_rsvd_hugetbl() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"hugetlb.2MB.current\", \"2048\\n\").expect(\"set hugetlb current\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.events\", \"max 5\\n\").expect(\"set hugetlb events\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.rsvd.current\", \"1024\\n\")\n            .expect(\"set hugetlb rsvd current\");\n        set_fixture(tmp.path(), \"hugetlb.2MB.rsvd.events\", \"max 5\\n\")\n            .expect(\"set hugetlb rsvd events\");\n\n        let actual = HugeTlb::stats_for_page_size(tmp.path(), \"2MB\").expect(\"get cgroup stats\");\n\n        // Should prefer rsvd stats over non-rsvd stats if available\n        let expected = HugeTlbStats {\n            usage: 1024,\n            max_usage: 0,\n            fail_count: 5,\n        };\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/io.rs",
    "content": "use std::num::ParseIntError;\nuse std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::LinuxBlockIo;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{\n    self, BlkioDeviceStat, BlkioStats, ParseDeviceNumberError, ParseNestedKeyedDataError,\n    StatsProvider, psi_stats,\n};\n\nconst CGROUP_BFQ_IO_WEIGHT: &str = \"io.bfq.weight\";\nconst CGROUP_IO_WEIGHT: &str = \"io.weight\";\nconst CGROUP_IO_STAT: &str = \"io.stat\";\nconst CGROUP_IO_PSI: &str = \"io.pressure\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2IoControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"cannot set leaf_weight with cgroupv2\")]\n    LeafWeight,\n}\n\npub struct Io {}\n\nimpl Controller for Io {\n    type Error = V2IoControllerError;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply io cgroup v2 config\");\n        if let Some(io) = &controller_opt.resources.block_io() {\n            Self::apply(cgroup_root, io)?;\n        }\n        Ok(())\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum V2IoStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"while parsing stat table: {0}\")]\n    ParseNestedKeyedData(#[from] ParseNestedKeyedDataError),\n    #[error(\"while parsing device number: {0}\")]\n    ParseDeviceNumber(#[from] ParseDeviceNumberError),\n    #[error(\"while parsing table value: {0}\")]\n    ParseInt(#[from] ParseIntError),\n}\n\nimpl StatsProvider for Io {\n    type Error = V2IoStatsError;\n    type Stats = BlkioStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let keyed_data = stats::parse_nested_keyed_data(&cgroup_path.join(CGROUP_IO_STAT))?;\n        let mut service_bytes = Vec::with_capacity(keyed_data.len());\n        let mut serviced = Vec::with_capacity(keyed_data.len());\n        for entry in keyed_data {\n            let (major, minor) = stats::parse_device_number(&entry.0)?;\n            for value in entry.1 {\n                if value.starts_with(\"rbytes\") {\n                    service_bytes.push(BlkioDeviceStat {\n                        major,\n                        minor,\n                        op_type: Some(\"read\".to_owned()),\n                        value: stats::parse_value(&value[7..])?,\n                    });\n                } else if value.starts_with(\"wbytes\") {\n                    service_bytes.push(BlkioDeviceStat {\n                        major,\n                        minor,\n                        op_type: Some(\"write\".to_owned()),\n                        value: stats::parse_value(&value[7..])?,\n                    });\n                } else if value.starts_with(\"rios\") {\n                    serviced.push(BlkioDeviceStat {\n                        major,\n                        minor,\n                        op_type: Some(\"read\".to_owned()),\n                        value: stats::parse_value(&value[5..])?,\n                    });\n                } else if value.starts_with(\"wios\") {\n                    serviced.push(BlkioDeviceStat {\n                        major,\n                        minor,\n                        op_type: Some(\"write\".to_owned()),\n                        value: stats::parse_value(&value[5..])?,\n                    });\n                }\n            }\n        }\n\n        let stats = BlkioStats {\n            service_bytes,\n            serviced,\n            psi: psi_stats(&cgroup_path.join(CGROUP_IO_PSI))?,\n            ..Default::default()\n        };\n\n        Ok(stats)\n    }\n}\n\nimpl Io {\n    // Since the OCI spec is designed for cgroup v1, in some cases\n    // there is need to convert from the cgroup v1 configuration to cgroup v2\n    // the formula for BlkIOWeight to IOWeight is y = (1 + (x - 10) * 9999 / 990)\n    // convert linearly from [10-1000] to [1-10000]\n    fn convert_cfq_io_weight_to_bfq(v: u16) -> u16 {\n        if v == 0 {\n            return 0;\n        }\n        1 + (v.saturating_sub(10)) * 9999 / 990\n    }\n\n    fn io_max_path(path: &Path) -> PathBuf {\n        path.join(\"io.max\")\n    }\n\n    // linux kernel doc: https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#io\n    fn apply(root_path: &Path, blkio: &LinuxBlockIo) -> Result<(), V2IoControllerError> {\n        if let Some(weight_device) = blkio.weight_device() {\n            for wd in weight_device {\n                if let Some(weight) = wd.weight() {\n                    common::write_cgroup_file(\n                        root_path.join(CGROUP_BFQ_IO_WEIGHT),\n                        format!(\"{}:{} {}\", wd.major(), wd.minor(), weight),\n                    )?;\n                }\n            }\n        }\n        if let Some(leaf_weight) = blkio.leaf_weight() {\n            if leaf_weight > 0 {\n                return Err(V2IoControllerError::LeafWeight);\n            }\n        }\n        if let Some(io_weight) = blkio.weight() {\n            // be aligned with what runc does\n            // See also: https://github.com/opencontainers/runc/blob/81044ad7c902f3fc153cb8ffadaf4da62855193f/libcontainer/cgroups/fs2/io.go#L57-L69\n            if io_weight > 0 {\n                let cgroup_file = root_path.join(CGROUP_BFQ_IO_WEIGHT);\n                if cgroup_file.exists() {\n                    common::write_cgroup_file(cgroup_file, io_weight)?;\n                } else {\n                    common::write_cgroup_file(\n                        root_path.join(CGROUP_IO_WEIGHT),\n                        Self::convert_cfq_io_weight_to_bfq(io_weight),\n                    )?;\n                }\n            }\n        }\n\n        if let Some(throttle_read_bps_device) = blkio.throttle_read_bps_device() {\n            for trbd in throttle_read_bps_device {\n                common::write_cgroup_file(\n                    Self::io_max_path(root_path),\n                    format!(\"{}:{} rbps={}\", trbd.major(), trbd.minor(), trbd.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_write_bps_device) = blkio.throttle_write_bps_device() {\n            for twbd in throttle_write_bps_device {\n                common::write_cgroup_file(\n                    Self::io_max_path(root_path),\n                    format!(\"{}:{} wbps={}\", twbd.major(), twbd.minor(), twbd.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_read_iops_device) = blkio.throttle_read_iops_device() {\n            for trid in throttle_read_iops_device {\n                common::write_cgroup_file(\n                    Self::io_max_path(root_path),\n                    format!(\"{}:{} riops={}\", trid.major(), trid.minor(), trid.rate()),\n                )?;\n            }\n        }\n\n        if let Some(throttle_write_iops_device) = blkio.throttle_write_iops_device() {\n            for twid in throttle_write_iops_device {\n                common::write_cgroup_file(\n                    Self::io_max_path(root_path),\n                    format!(\"{}:{} wiops={}\", twid.major(), twid.minor(), twid.rate()),\n                )?;\n            }\n        }\n\n        Ok(())\n    }\n}\n#[cfg(test)]\nmod test {\n    use std::fs;\n\n    use oci_spec::runtime::{\n        LinuxBlockIoBuilder, LinuxThrottleDeviceBuilder, LinuxWeightDeviceBuilder,\n    };\n\n    use super::*;\n    use crate::test::{set_fixture, setup};\n\n    #[test]\n    fn test_set_io_read_bps() {\n        let (tmp, throttle) = setup(\"io.max\");\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle).unwrap_or_else(|_| panic!(\"read rbps content\"));\n\n        assert_eq!(\"8:0 rbps=102400\", content);\n    }\n\n    #[test]\n    fn test_set_io_write_bps() {\n        let (tmp, throttle) = setup(\"io.max\");\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_write_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle).unwrap_or_else(|_| panic!(\"read rbps content\"));\n\n        assert_eq!(\"8:0 wbps=102400\", content);\n    }\n\n    #[test]\n    fn test_set_io_read_iops() {\n        let (tmp, throttle) = setup(\"io.max\");\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_read_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle).unwrap_or_else(|_| panic!(\"read riops content\"));\n\n        assert_eq!(\"8:0 riops=102400\", content);\n    }\n\n    #[test]\n    fn test_set_io_write_iops() {\n        let (tmp, throttle) = setup(\"io.max\");\n\n        let blkio = LinuxBlockIoBuilder::default()\n            .throttle_write_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .rate(102400u64)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content = fs::read_to_string(throttle).unwrap_or_else(|_| panic!(\"read wiops content\"));\n\n        assert_eq!(\"8:0 wiops=102400\", content);\n    }\n\n    #[test]\n    fn test_set_ioweight_device() {\n        let (tmp, throttle) = setup(CGROUP_BFQ_IO_WEIGHT);\n        let blkio = LinuxBlockIoBuilder::default()\n            .weight_device(vec![\n                LinuxWeightDeviceBuilder::default()\n                    .major(8)\n                    .minor(0)\n                    .weight(80u16)\n                    .leaf_weight(0u16)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap();\n\n        Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n        let content =\n            fs::read_to_string(throttle).unwrap_or_else(|_| panic!(\"read bfq_io_weight content\"));\n\n        assert_eq!(\"8:0 80\", content);\n    }\n\n    #[test]\n    fn test_set_ioweight() {\n        struct TestCase {\n            cgroup_file: &'static str,\n            weight: u16,\n            expected_weight: String,\n        }\n        for case in &[\n            TestCase {\n                cgroup_file: CGROUP_BFQ_IO_WEIGHT,\n                weight: 100,\n                expected_weight: String::from(\"100\"),\n            },\n            TestCase {\n                cgroup_file: CGROUP_IO_WEIGHT,\n                weight: 10,\n                expected_weight: String::from(\"1\"),\n            },\n        ] {\n            let (tmp, weight_file) = setup(case.cgroup_file);\n            let blkio = LinuxBlockIoBuilder::default()\n                .weight(case.weight)\n                .build()\n                .unwrap();\n\n            Io::apply(tmp.path(), &blkio).expect(\"apply blkio\");\n            let content = fs::read_to_string(weight_file).expect(\"read blkio weight\");\n            assert_eq!(case.expected_weight, content);\n        }\n    }\n\n    #[test]\n    fn test_stat_io() {\n        let tmp = tempfile::tempdir().unwrap();\n        let stat_content = [\n            \"7:10 rbytes=18432 wbytes=16842 rios=12 wios=0 dbytes=0 dios=0\",\n            \"7:9 rbytes=34629632 wbytes=274965 rios=1066 wios=319 dbytes=0 dios=0\",\n        ]\n        .join(\"\\n\");\n        set_fixture(tmp.path(), \"io.stat\", &stat_content).unwrap();\n        set_fixture(tmp.path(), CGROUP_IO_PSI, \"\").expect(\"create psi file\");\n\n        let mut actual = Io::stats(tmp.path()).expect(\"get cgroup stats\");\n        let expected = BlkioStats {\n            service_bytes: vec![\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 9,\n                    op_type: Some(\"read\".to_owned()),\n                    value: 34629632,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 9,\n                    op_type: Some(\"write\".to_owned()),\n                    value: 274965,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 10,\n                    op_type: Some(\"read\".to_owned()),\n                    value: 18432,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 10,\n                    op_type: Some(\"write\".to_owned()),\n                    value: 16842,\n                },\n            ],\n            serviced: vec![\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 9,\n                    op_type: Some(\"read\".to_owned()),\n                    value: 1066,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 9,\n                    op_type: Some(\"write\".to_owned()),\n                    value: 319,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 10,\n                    op_type: Some(\"read\".to_owned()),\n                    value: 12,\n                },\n                BlkioDeviceStat {\n                    major: 7,\n                    minor: 10,\n                    op_type: Some(\"write\".to_owned()),\n                    value: 0,\n                },\n            ],\n            ..Default::default()\n        };\n\n        actual.service_bytes.sort();\n        actual.serviced.sort();\n\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/manager.rs",
    "content": "use std::fs::{self};\nuse std::os::unix::fs::PermissionsExt;\nuse std::path::Component::RootDir;\nuse std::path::{Path, PathBuf};\nuse std::time::Duration;\n\nuse nix::unistd::Pid;\n\nuse super::controller::Controller;\nuse super::controller_type::{\n    CONTROLLER_TYPES, ControllerType, PSEUDO_CONTROLLER_TYPES, PseudoControllerType,\n};\nuse super::cpu::{Cpu, V2CpuControllerError, V2CpuStatsError};\nuse super::cpuset::CpuSet;\n#[cfg(feature = \"cgroupsv2_devices\")]\nuse super::devices::Devices;\nuse super::freezer::{Freezer, V2FreezerError};\nuse super::hugetlb::{HugeTlb, V2HugeTlbControllerError, V2HugeTlbStatsError};\nuse super::io::{Io, V2IoControllerError, V2IoStatsError};\nuse super::memory::{Memory, V2MemoryControllerError, V2MemoryStatsError};\nuse super::pids::Pids;\nuse super::unified::{Unified, V2UnifiedError};\nuse super::util::{self, CGROUP_SUBTREE_CONTROL, V2UtilError};\nuse crate::common::{\n    self, AnyCgroupManager, CGROUP_PROCS, CgroupManager, ControllerOpt, FreezerState,\n    JoinSafelyError, PathBufExt, WrapIoResult, WrappedIoError,\n};\nuse crate::stats::{PidStatsError, Stats, StatsProvider};\n\npub const CGROUP_KILL: &str = \"cgroup.kill\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2ManagerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"while joining paths: {0}\")]\n    JoinSafely(#[from] JoinSafelyError),\n    #[error(transparent)]\n    Util(#[from] V2UtilError),\n\n    #[error(transparent)]\n    CpuController(#[from] V2CpuControllerError),\n    #[error(transparent)]\n    CpuSetController(WrappedIoError),\n    #[error(transparent)]\n    HugeTlbController(#[from] V2HugeTlbControllerError),\n    #[error(transparent)]\n    IoController(#[from] V2IoControllerError),\n    #[error(transparent)]\n    MemoryController(#[from] V2MemoryControllerError),\n    #[error(transparent)]\n    PidsController(WrappedIoError),\n    #[error(transparent)]\n    UnifiedController(#[from] V2UnifiedError),\n    #[error(transparent)]\n    FreezerController(#[from] V2FreezerError),\n    #[cfg(feature = \"cgroupsv2_devices\")]\n    #[error(transparent)]\n    DevicesController(#[from] super::devices::controller::DevicesControllerError),\n\n    #[error(transparent)]\n    CpuStats(#[from] V2CpuStatsError),\n    #[error(transparent)]\n    HugeTlbStats(#[from] V2HugeTlbStatsError),\n    #[error(transparent)]\n    PidsStats(PidStatsError),\n    #[error(transparent)]\n    MemoryStats(#[from] V2MemoryStatsError),\n    #[error(transparent)]\n    IoStats(#[from] V2IoStatsError),\n}\n\n/// Represents a management interface for a cgroup located at `{root_path}/{cgroup_path}`\n///\n/// This struct does not have ownership of the cgroup\npub struct Manager {\n    root_path: PathBuf,\n    cgroup_path: PathBuf,\n    full_path: PathBuf,\n}\n\nimpl Manager {\n    /// Constructs a new cgroup manager with root path being the mount point\n    /// of a cgroup v2 fs and cgroup path being a relative path from the root\n    pub fn new(root_path: PathBuf, cgroup_path: PathBuf) -> Result<Self, V2ManagerError> {\n        let full_path = root_path.join_safely(&cgroup_path)?;\n\n        Ok(Self {\n            root_path,\n            cgroup_path,\n            full_path,\n        })\n    }\n\n    /// Creates a unified cgroup at `self.full_path` and attaches a process to it\n    fn create_unified_cgroup(&self, pid: Pid) -> Result<(), V2ManagerError> {\n        let controllers: Vec<String> = util::get_available_controllers(&self.root_path)?\n            .iter()\n            .map(|c| format!(\"+{c}\"))\n            .collect();\n\n        Self::write_controllers(&self.root_path, &controllers)?;\n\n        let mut current_path = self.root_path.clone();\n        let mut components = self\n            .cgroup_path\n            .components()\n            .filter(|c| c.ne(&RootDir))\n            .peekable();\n        while let Some(component) = components.next() {\n            current_path = current_path.join(component);\n            if !current_path.exists() {\n                fs::create_dir(&current_path).wrap_create_dir(&current_path)?;\n                fs::metadata(&current_path)\n                    .wrap_other(&current_path)?\n                    .permissions()\n                    .set_mode(0o755);\n            }\n\n            // last component cannot have subtree_control enabled due to internal process constraint\n            // if this were set, writing to the cgroups.procs file will fail with Erno 16 (device or resource busy)\n            if components.peek().is_some() {\n                Self::write_controllers(&current_path, &controllers)?;\n            }\n        }\n\n        common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;\n        Ok(())\n    }\n\n    /// Writes a list of controllers to the `{path}/cgroup.subtree_control` file\n    fn write_controllers(path: &Path, controllers: &[String]) -> Result<(), WrappedIoError> {\n        for controller in controllers {\n            common::write_cgroup_file_str(path.join(CGROUP_SUBTREE_CONTROL), controller)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn any(self) -> AnyCgroupManager {\n        AnyCgroupManager::V2(self)\n    }\n}\n\nimpl CgroupManager for Manager {\n    type Error = V2ManagerError;\n\n    fn add_task(&self, pid: Pid) -> Result<(), Self::Error> {\n        if self.full_path.exists() {\n            common::write_cgroup_file(self.full_path.join(CGROUP_PROCS), pid)?;\n            return Ok(());\n        }\n        self.create_unified_cgroup(pid)?;\n        Ok(())\n    }\n\n    fn apply(&self, controller_opt: &ControllerOpt) -> Result<(), Self::Error> {\n        for controller in CONTROLLER_TYPES {\n            match controller {\n                ControllerType::Cpu => Cpu::apply(controller_opt, &self.full_path)?,\n                ControllerType::CpuSet => CpuSet::apply(controller_opt, &self.full_path)?,\n                ControllerType::HugeTlb => HugeTlb::apply(controller_opt, &self.full_path)?,\n                ControllerType::Io => Io::apply(controller_opt, &self.full_path)?,\n                ControllerType::Memory => Memory::apply(controller_opt, &self.full_path)?,\n                ControllerType::Pids => Pids::apply(controller_opt, &self.full_path)?,\n            }\n        }\n\n        #[cfg(feature = \"cgroupsv2_devices\")]\n        Devices::apply(controller_opt, &self.full_path)?;\n\n        for pseudoctlr in PSEUDO_CONTROLLER_TYPES {\n            if let PseudoControllerType::Unified = pseudoctlr {\n                Unified::apply(\n                    controller_opt,\n                    &self.full_path,\n                    util::get_available_controllers(&self.root_path)?,\n                )?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn remove(&self) -> Result<(), Self::Error> {\n        if self.full_path.exists() {\n            tracing::debug!(\"remove cgroup {:?}\", self.full_path);\n            let kill_file = self.full_path.join(CGROUP_KILL);\n            if kill_file.exists() {\n                fs::write(&kill_file, \"1\").wrap_write(&kill_file, \"1\")?;\n            } else {\n                let procs_path = self.full_path.join(CGROUP_PROCS);\n                let procs = fs::read_to_string(&procs_path).wrap_read(&procs_path)?;\n\n                for line in procs.lines() {\n                    let pid: i32 = line\n                        .parse()\n                        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))\n                        .wrap_other(&procs_path)?;\n                    let _ = nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL);\n                }\n            }\n\n            common::delete_with_retry(&self.full_path, 4, Duration::from_millis(100))?;\n        }\n\n        Ok(())\n    }\n\n    fn freeze(&self, state: FreezerState) -> Result<(), Self::Error> {\n        let controller_opt = ControllerOpt {\n            resources: &Default::default(),\n            freezer_state: Some(state),\n            oom_score_adj: None,\n            disable_oom_killer: false,\n        };\n        Ok(Freezer::apply(&controller_opt, &self.full_path)?)\n    }\n\n    fn stats(&self) -> Result<Stats, Self::Error> {\n        let mut stats = Stats::default();\n\n        for subsystem in CONTROLLER_TYPES {\n            match subsystem {\n                ControllerType::Cpu => stats.cpu = Cpu::stats(&self.full_path)?,\n                ControllerType::HugeTlb => stats.hugetlb = HugeTlb::stats(&self.full_path)?,\n                ControllerType::Pids => {\n                    stats.pids = Pids::stats(&self.full_path).map_err(V2ManagerError::PidsStats)?\n                }\n                ControllerType::Memory => stats.memory = Memory::stats(&self.full_path)?,\n                ControllerType::Io => stats.blkio = Io::stats(&self.full_path)?,\n                _ => continue,\n            }\n        }\n\n        Ok(stats)\n    }\n\n    fn get_all_pids(&self) -> Result<Vec<Pid>, Self::Error> {\n        Ok(common::get_all_pids(&self.full_path)?)\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/memory.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxMemory;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{self, MemoryData, MemoryStats, ParseFlatKeyedDataError, StatsProvider};\n\nconst CGROUP_MEMORY_SWAP: &str = \"memory.swap.max\";\nconst CGROUP_MEMORY_MAX: &str = \"memory.max\";\nconst CGROUP_MEMORY_LOW: &str = \"memory.low\";\nconst MEMORY_STAT: &str = \"memory.stat\";\nconst MEMORY_PSI: &str = \"memory.pressure\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2MemoryControllerError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"invalid memory value {0}\")]\n    MemoryValue(i64),\n    #[error(\"invalid swap value {0}\")]\n    SwapValue(i64),\n    #[error(\"swap memory ({swap}) should be bigger than memory limit ({limit})\")]\n    SwapTooSmall { swap: i64, limit: i64 },\n    #[error(\"unable to set swap limit without memory limit\")]\n    SwapWithoutLimit,\n    #[error(\"invalid memory reservation value: {0}\")]\n    MemoryReservation(i64),\n}\n\npub struct Memory {}\n\nimpl Controller for Memory {\n    type Error = V2MemoryControllerError;\n\n    fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error> {\n        if let Some(memory) = &controller_opt.resources.memory() {\n            Self::apply(cgroup_path, memory)?;\n        }\n\n        Ok(())\n    }\n}\n#[derive(thiserror::Error, Debug)]\npub enum V2MemoryStatsError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"while parsing stat table: {0}\")]\n    ParseNestedKeyedData(#[from] ParseFlatKeyedDataError),\n}\n\nimpl StatsProvider for Memory {\n    type Error = V2MemoryStatsError;\n    type Stats = MemoryStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        let stats = MemoryStats {\n            memory: Self::get_memory_data(cgroup_path, \"memory\", \"oom\")?,\n            memswap: Self::get_memory_data(cgroup_path, \"memory.swap\", \"fail\")?,\n            hierarchy: true,\n            stats: stats::parse_flat_keyed_data(&cgroup_path.join(MEMORY_STAT))?,\n            psi: stats::psi_stats(&cgroup_path.join(MEMORY_PSI))?,\n            ..Default::default()\n        };\n\n        Ok(stats)\n    }\n}\n\nimpl Memory {\n    fn get_memory_data(\n        cgroup_path: &Path,\n        file_prefix: &str,\n        fail_event: &str,\n    ) -> Result<MemoryData, V2MemoryStatsError> {\n        let usage =\n            stats::parse_single_value(&cgroup_path.join(format!(\"{}.{}\", file_prefix, \"current\")))?;\n        let limit =\n            stats::parse_single_value(&cgroup_path.join(format!(\"{}.{}\", file_prefix, \"max\")))?;\n        let max_usage =\n            stats::parse_single_value(&cgroup_path.join(format!(\"{}.{}\", file_prefix, \"peak\")))\n                .unwrap_or(0);\n\n        let events = stats::parse_flat_keyed_data(\n            &cgroup_path.join(format!(\"{}.{}\", file_prefix, \"events\")),\n        )?;\n        let fail_count = if let Some((_, v)) = events.get_key_value(fail_event) {\n            *v\n        } else {\n            Default::default()\n        };\n\n        Ok(MemoryData {\n            usage,\n            max_usage,\n            fail_count,\n            limit,\n        })\n    }\n\n    fn set<P: AsRef<Path>>(path: P, val: i64) -> Result<(), WrappedIoError> {\n        if val == 0 {\n            Ok(())\n        } else if val == -1 {\n            Ok(common::write_cgroup_file_str(path, \"max\")?)\n        } else {\n            Ok(common::write_cgroup_file(path, val)?)\n        }\n    }\n\n    fn apply(path: &Path, memory: &LinuxMemory) -> Result<(), V2MemoryControllerError> {\n        // if nothing is set just exit right away\n        if memory.reservation().is_none() && memory.limit().is_none() && memory.swap().is_none() {\n            return Ok(());\n        }\n\n        match memory.limit() {\n            Some(limit) if limit < -1 => {\n                return Err(V2MemoryControllerError::MemoryValue(limit));\n            }\n            Some(limit) => match memory.swap() {\n                Some(swap) if swap < -1 => {\n                    return Err(V2MemoryControllerError::SwapValue(swap));\n                }\n                Some(swap) => {\n                    // -1 means max\n                    if swap == -1 || limit == -1 {\n                        Memory::set(path.join(CGROUP_MEMORY_SWAP), swap)?;\n                    } else {\n                        if swap < limit {\n                            return Err(V2MemoryControllerError::SwapTooSmall { swap, limit });\n                        }\n\n                        // In cgroup v1 swap is memory+swap, but in cgroup v2 swap is\n                        // a separate value, so the swap value in the runtime spec needs\n                        // to be converted from the cgroup v1 value to the cgroup v2 value\n                        // by subtracting limit from swap\n                        Memory::set(path.join(CGROUP_MEMORY_SWAP), swap - limit)?;\n                    }\n                    Memory::set(path.join(CGROUP_MEMORY_MAX), limit)?;\n                }\n                None => {\n                    if limit == -1 {\n                        Memory::set(path.join(CGROUP_MEMORY_SWAP), -1)?;\n                    }\n                    Memory::set(path.join(CGROUP_MEMORY_MAX), limit)?;\n                }\n            },\n            None => {\n                if memory.swap().is_some() {\n                    return Err(V2MemoryControllerError::SwapWithoutLimit);\n                }\n            }\n        };\n\n        if let Some(reservation) = memory.reservation() {\n            if reservation < -1 {\n                return Err(V2MemoryControllerError::MemoryReservation(reservation));\n            }\n            Memory::set(path.join(CGROUP_MEMORY_LOW), reservation)?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs::read_to_string;\n\n    use oci_spec::runtime::LinuxMemoryBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_memory() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\")\n            .expect(\"set fixture for memory reservation\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n        let limit = 1024;\n        let reservation = 512;\n        let swap = 2048;\n\n        let memory_limits = LinuxMemoryBuilder::default()\n            .limit(limit)\n            .reservation(reservation)\n            .swap(swap)\n            .build()\n            .unwrap();\n\n        Memory::apply(tmp.path(), &memory_limits).expect(\"apply memory limits\");\n\n        let limit_content =\n            read_to_string(tmp.path().join(CGROUP_MEMORY_MAX)).expect(\"read memory limit\");\n        assert_eq!(limit_content, limit.to_string());\n\n        let swap_content =\n            read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP)).expect(\"read swap limit\");\n        assert_eq!(swap_content, (swap - limit).to_string());\n\n        let reservation_content =\n            read_to_string(tmp.path().join(CGROUP_MEMORY_LOW)).expect(\"read memory reservation\");\n        assert_eq!(reservation_content, reservation.to_string());\n    }\n\n    #[test]\n    fn test_set_memory_unlimited() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\")\n            .expect(\"set fixture for memory reservation\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n        let memory_limits = LinuxMemoryBuilder::default().limit(-1).build().unwrap();\n\n        Memory::apply(tmp.path(), &memory_limits).expect(\"apply memory limits\");\n\n        let limit_content =\n            read_to_string(tmp.path().join(CGROUP_MEMORY_MAX)).expect(\"read memory limit\");\n        assert_eq!(limit_content, \"max\");\n\n        let swap_content =\n            read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP)).expect(\"read swap limit\");\n        assert_eq!(swap_content, \"max\");\n    }\n\n    #[test]\n    fn test_err_swap_no_memory() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\")\n            .expect(\"set fixture for memory reservation\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n        let memory_limits = LinuxMemoryBuilder::default().swap(512).build().unwrap();\n\n        let result = Memory::apply(tmp.path(), &memory_limits);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_err_bad_limit() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\")\n            .expect(\"set fixture for memory reservation\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n        let memory_limits = LinuxMemoryBuilder::default().limit(-2).build().unwrap();\n\n        let result = Memory::apply(tmp.path(), &memory_limits);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_err_bad_swap() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\")\n            .expect(\"set fixture for memory reservation\");\n        set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n        let memory_limits = LinuxMemoryBuilder::default()\n            .limit(512)\n            .swap(-3)\n            .build()\n            .unwrap();\n\n        let result = Memory::apply(tmp.path(), &memory_limits);\n\n        assert!(result.is_err());\n    }\n\n    quickcheck! {\n        fn property_test_set_memory(linux_memory: LinuxMemory) -> bool {\n            let tmp = tempfile::tempdir().unwrap();\n            set_fixture(tmp.path(), CGROUP_MEMORY_MAX, \"0\").expect(\"set fixture for memory limit\");\n            set_fixture(tmp.path(), CGROUP_MEMORY_LOW, \"0\").expect(\"set fixture for memory reservation\");\n            set_fixture(tmp.path(), CGROUP_MEMORY_SWAP, \"0\").expect(\"set fixture for swap limit\");\n\n            let result = Memory::apply(tmp.path(), &linux_memory);\n\n            // we need to check for expected errors first and foremost or we'll get false negatives\n            // later\n            if let Some(limit) = linux_memory.limit() {\n                if limit < -1 {\n                    return result.is_err();\n                }\n            }\n\n            if let Some(swap) = linux_memory.swap() {\n                if swap < -1 {\n                    return result.is_err();\n                }\n                if linux_memory.limit().is_none() {\n                    return result.is_err();\n                }\n                if let Some(limit) = linux_memory.limit() {\n                    if limit != -1 && swap != -1 && swap < limit {\n                        return result.is_err();\n                    }\n                }\n            }\n\n            if let Some(reservation) = linux_memory.reservation() {\n                if reservation < -1 {\n                    return result.is_err();\n                }\n            }\n\n            // check the limit file is set as expected\n            let limit_content = read_to_string(tmp.path().join(CGROUP_MEMORY_MAX)).expect(\"read memory limit to string\");\n            let limit_check = match linux_memory.limit() {\n                Some(-1) => limit_content == \"max\",\n                Some(limit) => limit_content == limit.to_string(),\n                None => limit_content == \"0\",\n            };\n\n            // check the swap file is set as expected\n            let swap_content = read_to_string(tmp.path().join(CGROUP_MEMORY_SWAP)).expect(\"read swap limit to string\");\n            let swap_check = match linux_memory.swap() {\n                Some(-1)=> swap_content == \"max\",\n                Some(swap) => {\n                    if let Some(limit) = linux_memory.limit() {\n                        if limit == -1 {\n                            swap_content == swap.to_string()\n                        } else {\n                            swap_content == (swap - linux_memory.limit().unwrap()).to_string()\n                        }\n                    } else {\n                        false\n                    }\n                }\n                None => {\n                    match linux_memory.limit() {\n                        Some(-1) => swap_content == \"max\",\n                        _ => swap_content == \"0\",\n                    }\n                }\n            };\n\n\n            // check the reservation file is set as expected\n            let reservation_content = read_to_string(tmp.path().join(CGROUP_MEMORY_LOW)).expect(\"read memory reservation to string\");\n            let reservation_check = match linux_memory.reservation() {\n                Some(-1) => reservation_content == \"max\",\n                Some(reservation) => reservation_content == reservation.to_string(),\n                None => reservation_content == \"0\",\n            };\n\n            println!(\"limit_check: {limit_check}\");\n            println!(\"swap_check: {swap_check}\");\n            println!(\"reservation_check: {reservation_check}\");\n            limit_check && swap_check && reservation_check\n        }\n    }\n\n    #[test]\n    fn test_get_memory_data() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"memory.current\", \"12500\\n\").unwrap();\n        set_fixture(tmp.path(), \"memory.max\", \"25000\\n\").unwrap();\n        let events = [\"slab 5\", \"anon 13\", \"oom 3\"].join(\"\\n\");\n        set_fixture(tmp.path(), \"memory.events\", &events).unwrap();\n\n        let actual =\n            Memory::get_memory_data(tmp.path(), \"memory\", \"oom\").expect(\"get cgroup stats\");\n        let expected = MemoryData {\n            usage: 12500,\n            limit: 25000,\n            fail_count: 3,\n            ..Default::default()\n        };\n\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_get_memory_data_with_peak() {\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), \"memory.current\", \"12500\\n\").unwrap();\n        set_fixture(tmp.path(), \"memory.max\", \"25000\\n\").unwrap();\n        set_fixture(tmp.path(), \"memory.peak\", \"20000\\n\").unwrap();\n        let events = [\"slab 5\", \"anon 13\", \"oom 3\"].join(\"\\n\");\n        set_fixture(tmp.path(), \"memory.events\", &events).unwrap();\n\n        let actual =\n            Memory::get_memory_data(tmp.path(), \"memory\", \"oom\").expect(\"get cgroup stats\");\n        let expected = MemoryData {\n            usage: 12500,\n            max_usage: 20000,\n            limit: 25000,\n            fail_count: 3,\n        };\n\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/mod.rs",
    "content": "mod controller;\npub mod controller_type;\nmod cpu;\nmod cpuset;\n#[cfg(feature = \"cgroupsv2_devices\")]\npub mod devices;\nmod freezer;\nmod hugetlb;\nmod io;\npub mod manager;\nmod memory;\nmod pids;\nmod unified;\npub mod util;\n"
  },
  {
    "path": "crates/libcgroups/src/v2/pids.rs",
    "content": "use std::path::Path;\n\nuse oci_spec::runtime::LinuxPids;\n\nuse super::controller::Controller;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\nuse crate::stats::{self, PidStats, PidStatsError, StatsProvider};\n\npub struct Pids {}\n\nimpl Controller for Pids {\n    type Error = WrappedIoError;\n\n    fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_root: &std::path::Path,\n    ) -> Result<(), Self::Error> {\n        tracing::debug!(\"Apply pids cgroup v2 config\");\n        if let Some(pids) = &controller_opt.resources.pids() {\n            Self::apply(cgroup_root, pids)?;\n        }\n        Ok(())\n    }\n}\n\nimpl StatsProvider for Pids {\n    type Error = PidStatsError;\n    type Stats = PidStats;\n\n    fn stats(cgroup_path: &Path) -> Result<Self::Stats, Self::Error> {\n        stats::pid_stats(cgroup_path)\n    }\n}\n\nimpl Pids {\n    fn apply(root_path: &Path, pids: &LinuxPids) -> Result<(), WrappedIoError> {\n        let limit = if pids.limit() > 0 {\n            pids.limit().to_string()\n        } else {\n            \"max\".to_string()\n        };\n        common::write_cgroup_file(root_path.join(\"pids.max\"), limit)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::LinuxPidsBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n\n    #[test]\n    fn test_set_pids() {\n        let pids_file_name = \"pids.max\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), pids_file_name, \"1000\").expect(\"Set fixture for 1000 pids\");\n\n        let pids = LinuxPidsBuilder::default().limit(1000).build().unwrap();\n\n        Pids::apply(tmp.path(), &pids).expect(\"apply pids\");\n        let content =\n            std::fs::read_to_string(tmp.path().join(pids_file_name)).expect(\"Read pids contents\");\n        assert_eq!(pids.limit().to_string(), content);\n    }\n\n    #[test]\n    fn test_set_pids_max() {\n        let pids_file_name = \"pids.max\";\n        let tmp = tempfile::tempdir().unwrap();\n        set_fixture(tmp.path(), pids_file_name, \"0\").expect(\"set fixture for 0 pids\");\n\n        let pids = LinuxPidsBuilder::default().limit(0).build().unwrap();\n\n        Pids::apply(tmp.path(), &pids).expect(\"apply pids\");\n\n        let content =\n            std::fs::read_to_string(tmp.path().join(pids_file_name)).expect(\"Read pids contents\");\n        assert_eq!(\"max\".to_string(), content);\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/unified.rs",
    "content": "use std::collections::HashMap;\nuse std::path::Path;\n\nuse super::controller_type::ControllerType;\nuse crate::common::{self, ControllerOpt, WrappedIoError};\n\n#[derive(thiserror::Error, Debug)]\npub enum V2UnifiedError {\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"subsystem {subsystem} is not available: {err}\")]\n    SubsystemNotAvailable {\n        subsystem: String,\n        err: WrappedIoError,\n    },\n}\n\npub struct Unified {}\n\nimpl Unified {\n    pub fn apply(\n        controller_opt: &ControllerOpt,\n        cgroup_path: &Path,\n        controllers: Vec<ControllerType>,\n    ) -> Result<(), V2UnifiedError> {\n        if let Some(unified) = &controller_opt.resources.unified() {\n            Self::apply_impl(unified, cgroup_path, &controllers)?;\n        }\n\n        Ok(())\n    }\n\n    fn apply_impl(\n        unified: &HashMap<String, String>,\n        cgroup_path: &Path,\n        controllers: &[ControllerType],\n    ) -> Result<(), V2UnifiedError> {\n        tracing::debug!(\"Apply unified cgroup config\");\n        for (cgroup_file, value) in unified {\n            if let Err(err) = common::write_cgroup_file_str(cgroup_path.join(cgroup_file), value) {\n                let (subsystem, _) = cgroup_file.split_once('.').unwrap_or((cgroup_file, \"\"));\n\n                if controllers.iter().any(|c| c.to_string() == subsystem) {\n                    Err(err)?;\n                } else {\n                    return Err(V2UnifiedError::SubsystemNotAvailable {\n                        subsystem: subsystem.into(),\n                        err,\n                    });\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashMap;\n    use std::fs;\n\n    use oci_spec::runtime::LinuxResourcesBuilder;\n\n    use super::*;\n    use crate::test::set_fixture;\n    use crate::v2::controller_type::ControllerType;\n\n    #[test]\n    fn test_set_unified() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let hugetlb_limit_path = set_fixture(tmp.path(), \"hugetlb.1GB.limit_in_bytes\", \"\").unwrap();\n        let cpu_weight_path = set_fixture(tmp.path(), \"cpu.weight\", \"\").unwrap();\n\n        let unified = {\n            let mut u = HashMap::new();\n            u.insert(\n                \"hugetlb.1GB.limit_in_bytes\".to_owned(),\n                \"72348034\".to_owned(),\n            );\n            u.insert(\"cpu.weight\".to_owned(), \"5000\".to_owned());\n            u\n        };\n\n        let resources = LinuxResourcesBuilder::default()\n            .unified(unified)\n            .build()\n            .unwrap();\n\n        let controller_opt = ControllerOpt {\n            resources: &resources,\n            freezer_state: None,\n            oom_score_adj: None,\n            disable_oom_killer: false,\n        };\n\n        // act\n        Unified::apply(&controller_opt, tmp.path(), vec![]).expect(\"apply unified\");\n\n        // assert\n        let hugetlb_limit = fs::read_to_string(hugetlb_limit_path).expect(\"read hugetlb limit\");\n        let cpu_weight = fs::read_to_string(cpu_weight_path).expect(\"read cpu weight\");\n        assert_eq!(hugetlb_limit, \"72348034\");\n        assert_eq!(cpu_weight, \"5000\");\n    }\n\n    #[test]\n    fn test_set_unified_failed_to_write_subsystem_not_enabled() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n\n        let unified = {\n            let mut u = HashMap::new();\n            u.insert(\n                \"hugetlb.1GB.limit_in_bytes\".to_owned(),\n                \"72348034\".to_owned(),\n            );\n            u.insert(\"cpu.weight\".to_owned(), \"5000\".to_owned());\n            u\n        };\n\n        let resources = LinuxResourcesBuilder::default()\n            .unified(unified)\n            .build()\n            .unwrap();\n\n        let controller_opt = ControllerOpt {\n            resources: &resources,\n            freezer_state: None,\n            oom_score_adj: None,\n            disable_oom_killer: false,\n        };\n\n        // act\n        let result = Unified::apply(&controller_opt, tmp.path(), vec![]);\n\n        // assert\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn test_set_unified_failed_to_write_subsystem_enabled() {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n\n        let unified = {\n            let mut u = HashMap::new();\n            u.insert(\n                \"hugetlb.1GB.limit_in_bytes\".to_owned(),\n                \"72348034\".to_owned(),\n            );\n            u.insert(\"cpu.weight\".to_owned(), \"5000\".to_owned());\n            u\n        };\n\n        let resources = LinuxResourcesBuilder::default()\n            .unified(unified)\n            .build()\n            .unwrap();\n\n        let controller_opt = ControllerOpt {\n            resources: &resources,\n            oom_score_adj: None,\n            disable_oom_killer: false,\n            freezer_state: None,\n        };\n\n        // act\n        let result = Unified::apply(\n            &controller_opt,\n            tmp.path(),\n            vec![ControllerType::HugeTlb, ControllerType::Cpu],\n        );\n\n        // assert\n        assert!(result.is_err());\n    }\n}\n"
  },
  {
    "path": "crates/libcgroups/src/v2/util.rs",
    "content": "use std::io::{BufRead, BufReader};\nuse std::path::{Path, PathBuf};\n\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\nuse procfs::ProcError;\nuse procfs::process::MountInfo;\n\nuse super::controller_type::ControllerType;\nuse crate::common::{self, WrappedIoError};\n\npub const CGROUP_CONTROLLERS: &str = \"cgroup.controllers\";\npub const CGROUP_SUBTREE_CONTROL: &str = \"cgroup.subtree_control\";\n\n#[derive(thiserror::Error, Debug)]\npub enum V2UtilError {\n    #[error(\"io error: {0}\")]\n    Io(#[from] std::io::Error),\n    #[error(\"io error: {0}\")]\n    WrappedIo(#[from] WrappedIoError),\n    #[error(\"proc error: {0}\")]\n    Proc(#[from] ProcError),\n    #[error(\"could not find mountpoint for unified\")]\n    CouldNotFind,\n    #[error(\"cannot get available controllers. {0} does not exist\")]\n    DoesNotExist(PathBuf),\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n}\n\n// Reads the `/proc/self/mountinfo` to get the mount point of this cgroup\npub fn get_unified_mount_point() -> Result<PathBuf, V2UtilError> {\n    let reader = BufReader::new(ProcfsHandle::new()?.open(\n        ProcfsBase::ProcSelf,\n        \"mountinfo\",\n        OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n    )?);\n\n    reader\n        .lines()\n        .map(|lr| {\n            lr.map_err(V2UtilError::Io)\n                .and_then(|s| MountInfo::from_line(&s).map_err(V2UtilError::from))\n        })\n        .find_map(|r| match r {\n            Ok(mi) if mi.fs_type == \"cgroup2\" => Some(Ok(mi.mount_point)),\n            Ok(_) => None,\n            Err(e) => Some(Err(e)),\n        })\n        .transpose()?\n        .ok_or(V2UtilError::CouldNotFind)\n}\n\n/// Reads the `{root_path}/cgroup.controllers` file to get the list of the controllers that are\n/// available in this cgroup\npub fn get_available_controllers<P: AsRef<Path>>(\n    root_path: P,\n) -> Result<Vec<ControllerType>, V2UtilError> {\n    let root_path = root_path.as_ref();\n    let controllers_path = root_path.join(CGROUP_CONTROLLERS);\n    if !controllers_path.exists() {\n        return Err(V2UtilError::DoesNotExist(controllers_path));\n    }\n\n    let mut controllers = Vec::new();\n    for controller in common::read_cgroup_file(controllers_path)?.split_whitespace() {\n        match controller {\n            \"cpu\" => controllers.push(ControllerType::Cpu),\n            \"cpuset\" => controllers.push(ControllerType::CpuSet),\n            \"hugetlb\" => controllers.push(ControllerType::HugeTlb),\n            \"io\" => controllers.push(ControllerType::Io),\n            \"memory\" => controllers.push(ControllerType::Memory),\n            \"pids\" => controllers.push(ControllerType::Pids),\n            tpe => tracing::warn!(\"Controller {} is not yet implemented.\", tpe),\n        }\n    }\n\n    Ok(controllers)\n}\n"
  },
  {
    "path": "crates/libcontainer/Cargo.toml",
    "content": "[package]\nname = \"libcontainer\"\nversion = \"0.6.0\" # MARK: Version\ndescription = \"Library for container control\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nrust-version = \"1.85.0\"\nkeywords = [\"youki\", \"container\", \"cgroups\"]\n\n[features]\ndefault = [\"systemd\", \"v2\", \"v1\", \"libseccomp\"]\nlibseccomp = [\"dep:libseccomp\"]\nsystemd = [\"libcgroups/systemd\", \"v2\"]\nv2 = [\"libcgroups/v2\"]\nv1 = [\"libcgroups/v1\"]\ncgroupsv2_devices = [\"libcgroups/cgroupsv2_devices\"]\n\n[dependencies]\ncaps = \"0.5.6\"\nchrono = { version = \"0.4\", default-features = false, features = [\n    \"clock\",\n    \"serde\",\n] }\nfastrand = \"^2.3.0\"\nlibc = \"0.2.180\"\nnix = { version = \"0.29.0\", features = [\n    \"socket\",\n    \"sched\",\n    \"mount\",\n    \"mman\",\n    \"resource\",\n    \"dir\",\n    \"term\",\n    \"hostname\",\n    \"personality\",\n] }\noci-spec = { version = \"0.9.0\", features = [\"runtime\"] }\nprocfs = \"0.17.0\"\nprctl = \"1.0.0\"\nlibcgroups = { path = \"../libcgroups\", default-features = false, version = \"0.6.0\" } # MARK: Version\nlibseccomp = { version = \"0.4.0\", optional = true }\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\nrust-criu = \"0.5.0\"\nregex = { version = \"1.12.3\", default-features = false, features = [\n    \"std\",\n    \"unicode-perl\",\n] }\nthiserror = \"2.0.18\"\ntracing = { version = \"0.1.44\", features = [\"attributes\"] }\nsafe-path = \"0.1.0\"\nnc = \"0.9.7\"\nnetlink-packet-route = \"0.26.0\"\nnetlink-sys = \"0.8.8\"\nnetlink-packet-core = \"0.8.1\"\npathrs = \"0.2.4\"\n\n[dev-dependencies]\noci-spec = { version = \"~0.9.0\", features = [\"proptests\", \"runtime\"] }\nquickcheck = \"1\"\nserial_test = \"3.4.0\"\ntempfile = \"3\"\nanyhow = \"1.0\"\nrand = \"0.10.0\"\nscopeguard = \"1\"\n"
  },
  {
    "path": "crates/libcontainer/README.md",
    "content": "# libcontainer\n\n### Building with musl\n\nIn order to build with musl you must first remove the libseccomp dependency as it will reference shared libraries (`libseccomp`) which cannot be built with musl.\n\nDo this by using adding flags to Cargo. Use the `--no-default-features` flag followed by `-F` and whatever features you intend to build with such as `v2` as defined in Cargo.toml under features section.\n\nNext you will also need the `+nightly` flags when building with `rustup` and `cargo`.\n\n```bash\n# Add rustup +nightly musl to toolchain\nrustup +nightly target add $(uname -m)-unknown-linux-musl\n\n# Build rustup +nightly stdlib with musl\nrustup +nightly toolchain install nightly-$(uname -m)-unknown-linux-musl\n\n# Build musl standard library\ncargo +nightly build -Zbuild-std --target $(uname -m)-unknown-linux-musl --no-default-features -F v2\n\ncargo +nightly build --target $(uname -m)-unknown-linux-musl --no-default-features -F v2\n```\n"
  },
  {
    "path": "crates/libcontainer/src/apparmor.rs",
    "content": "use std::fs;\nuse std::io::Write;\nuse std::path::{Path, PathBuf};\n\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\n\n#[derive(Debug, thiserror::Error)]\npub enum AppArmorError {\n    #[error(\"failed to apply AppArmor profile\")]\n    ActivateProfile {\n        path: PathBuf,\n        profile: String,\n        source: std::io::Error,\n    },\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n}\n\ntype Result<T> = std::result::Result<T, AppArmorError>;\n\nconst ENABLED_PARAMETER_PATH: &str = \"/sys/module/apparmor/parameters/enabled\";\n\n/// Checks if AppArmor has been enabled on the system.\npub fn is_enabled() -> std::result::Result<bool, std::io::Error> {\n    let aa_enabled = fs::read_to_string(ENABLED_PARAMETER_PATH)?;\n    Ok(aa_enabled.starts_with('Y'))\n}\n\n/// Applies an AppArmor profile to the container.\npub fn apply_profile(profile: &str) -> Result<()> {\n    if profile.is_empty() {\n        return Ok(());\n    }\n\n    // Try the module specific subdirectory. This is the recommended way to configure\n    // LSMs since Linux 5.1. AppArmor has such a directory since Linux 5.8.\n    activate_profile(Path::new(\"attr/apparmor/exec\"), profile)\n        // try the legacy interface\n        .or_else(|_| activate_profile(Path::new(\"attr/exec\"), profile))\n}\n\nfn activate_profile(subpath: &Path, profile: &str) -> Result<()> {\n    ProcfsHandle::new()?\n        .open(\n            ProcfsBase::ProcSelf,\n            subpath,\n            OpenFlags::O_WRONLY | OpenFlags::O_CLOEXEC,\n        )?\n        .write_all(format!(\"exec {profile}\").as_bytes())\n        .map_err(|err| AppArmorError::ActivateProfile {\n            path: PathBuf::from(\"/proc/self\").join(subpath),\n            profile: profile.to_owned(),\n            source: err,\n        })\n}\n"
  },
  {
    "path": "crates/libcontainer/src/capabilities.rs",
    "content": "//! Handles Management of Capabilities\nuse caps::{Capability as CapsCapability, *};\nuse oci_spec::runtime::{Capabilities, Capability as SpecCapability, LinuxCapabilities};\n\nuse crate::syscall::{Syscall, SyscallError};\n\n/// Converts a list of capability types to capabilities has set\nfn to_set(caps: &Capabilities) -> CapsHashSet {\n    let mut capabilities = CapsHashSet::new();\n\n    for c in caps {\n        let cap = c.to_cap();\n        capabilities.insert(cap);\n    }\n    capabilities\n}\n\npub trait CapabilityExt {\n    /// Convert self to caps::Capability\n    fn to_cap(&self) -> caps::Capability;\n    /// Convert caps::Capability to self\n    fn from_cap(c: CapsCapability) -> Self;\n}\n\nimpl CapabilityExt for SpecCapability {\n    /// Convert oci::runtime::Capability to caps::Capability\n    fn to_cap(&self) -> caps::Capability {\n        match self {\n            SpecCapability::AuditControl => CapsCapability::CAP_AUDIT_CONTROL,\n            SpecCapability::AuditRead => CapsCapability::CAP_AUDIT_READ,\n            SpecCapability::AuditWrite => CapsCapability::CAP_AUDIT_WRITE,\n            SpecCapability::BlockSuspend => CapsCapability::CAP_BLOCK_SUSPEND,\n            SpecCapability::Bpf => CapsCapability::CAP_BPF,\n            SpecCapability::CheckpointRestore => CapsCapability::CAP_CHECKPOINT_RESTORE,\n            SpecCapability::Chown => CapsCapability::CAP_CHOWN,\n            SpecCapability::DacOverride => CapsCapability::CAP_DAC_OVERRIDE,\n            SpecCapability::DacReadSearch => CapsCapability::CAP_DAC_READ_SEARCH,\n            SpecCapability::Fowner => CapsCapability::CAP_FOWNER,\n            SpecCapability::Fsetid => CapsCapability::CAP_FSETID,\n            SpecCapability::IpcLock => CapsCapability::CAP_IPC_LOCK,\n            SpecCapability::IpcOwner => CapsCapability::CAP_IPC_OWNER,\n            SpecCapability::Kill => CapsCapability::CAP_KILL,\n            SpecCapability::Lease => CapsCapability::CAP_LEASE,\n            SpecCapability::LinuxImmutable => CapsCapability::CAP_LINUX_IMMUTABLE,\n            SpecCapability::MacAdmin => CapsCapability::CAP_MAC_ADMIN,\n            SpecCapability::MacOverride => CapsCapability::CAP_MAC_OVERRIDE,\n            SpecCapability::Mknod => CapsCapability::CAP_MKNOD,\n            SpecCapability::NetAdmin => CapsCapability::CAP_NET_ADMIN,\n            SpecCapability::NetBindService => CapsCapability::CAP_NET_BIND_SERVICE,\n            SpecCapability::NetBroadcast => CapsCapability::CAP_NET_BROADCAST,\n            SpecCapability::NetRaw => CapsCapability::CAP_NET_RAW,\n            SpecCapability::Perfmon => CapsCapability::CAP_PERFMON,\n            SpecCapability::Setgid => CapsCapability::CAP_SETGID,\n            SpecCapability::Setfcap => CapsCapability::CAP_SETFCAP,\n            SpecCapability::Setpcap => CapsCapability::CAP_SETPCAP,\n            SpecCapability::Setuid => CapsCapability::CAP_SETUID,\n            SpecCapability::SysAdmin => CapsCapability::CAP_SYS_ADMIN,\n            SpecCapability::SysBoot => CapsCapability::CAP_SYS_BOOT,\n            SpecCapability::SysChroot => CapsCapability::CAP_SYS_CHROOT,\n            SpecCapability::SysModule => CapsCapability::CAP_SYS_MODULE,\n            SpecCapability::SysNice => CapsCapability::CAP_SYS_NICE,\n            SpecCapability::SysPacct => CapsCapability::CAP_SYS_PACCT,\n            SpecCapability::SysPtrace => CapsCapability::CAP_SYS_PTRACE,\n            SpecCapability::SysRawio => CapsCapability::CAP_SYS_RAWIO,\n            SpecCapability::SysResource => CapsCapability::CAP_SYS_RESOURCE,\n            SpecCapability::SysTime => CapsCapability::CAP_SYS_TIME,\n            SpecCapability::SysTtyConfig => CapsCapability::CAP_SYS_TTY_CONFIG,\n            SpecCapability::Syslog => CapsCapability::CAP_SYSLOG,\n            SpecCapability::WakeAlarm => CapsCapability::CAP_WAKE_ALARM,\n        }\n    }\n\n    /// Convert caps::Capability to oci::runtime::Capability\n    fn from_cap(c: CapsCapability) -> SpecCapability {\n        match c {\n            CapsCapability::CAP_AUDIT_CONTROL => SpecCapability::AuditControl,\n            CapsCapability::CAP_AUDIT_READ => SpecCapability::AuditRead,\n            CapsCapability::CAP_AUDIT_WRITE => SpecCapability::AuditWrite,\n            CapsCapability::CAP_BLOCK_SUSPEND => SpecCapability::BlockSuspend,\n            CapsCapability::CAP_BPF => SpecCapability::Bpf,\n            CapsCapability::CAP_CHECKPOINT_RESTORE => SpecCapability::CheckpointRestore,\n            CapsCapability::CAP_CHOWN => SpecCapability::Chown,\n            CapsCapability::CAP_DAC_OVERRIDE => SpecCapability::DacOverride,\n            CapsCapability::CAP_DAC_READ_SEARCH => SpecCapability::DacReadSearch,\n            CapsCapability::CAP_FOWNER => SpecCapability::Fowner,\n            CapsCapability::CAP_FSETID => SpecCapability::Fsetid,\n            CapsCapability::CAP_IPC_LOCK => SpecCapability::IpcLock,\n            CapsCapability::CAP_IPC_OWNER => SpecCapability::IpcOwner,\n            CapsCapability::CAP_KILL => SpecCapability::Kill,\n            CapsCapability::CAP_LEASE => SpecCapability::Lease,\n            CapsCapability::CAP_LINUX_IMMUTABLE => SpecCapability::LinuxImmutable,\n            CapsCapability::CAP_MAC_ADMIN => SpecCapability::MacAdmin,\n            CapsCapability::CAP_MAC_OVERRIDE => SpecCapability::MacOverride,\n            CapsCapability::CAP_MKNOD => SpecCapability::Mknod,\n            CapsCapability::CAP_NET_ADMIN => SpecCapability::NetAdmin,\n            CapsCapability::CAP_NET_BIND_SERVICE => SpecCapability::NetBindService,\n            CapsCapability::CAP_NET_BROADCAST => SpecCapability::NetBroadcast,\n            CapsCapability::CAP_NET_RAW => SpecCapability::NetRaw,\n            CapsCapability::CAP_PERFMON => SpecCapability::Perfmon,\n            CapsCapability::CAP_SETGID => SpecCapability::Setgid,\n            CapsCapability::CAP_SETFCAP => SpecCapability::Setfcap,\n            CapsCapability::CAP_SETPCAP => SpecCapability::Setpcap,\n            CapsCapability::CAP_SETUID => SpecCapability::Setuid,\n            CapsCapability::CAP_SYS_ADMIN => SpecCapability::SysAdmin,\n            CapsCapability::CAP_SYS_BOOT => SpecCapability::SysBoot,\n            CapsCapability::CAP_SYS_CHROOT => SpecCapability::SysChroot,\n            CapsCapability::CAP_SYS_MODULE => SpecCapability::SysModule,\n            CapsCapability::CAP_SYS_NICE => SpecCapability::SysNice,\n            CapsCapability::CAP_SYS_PACCT => SpecCapability::SysPacct,\n            CapsCapability::CAP_SYS_PTRACE => SpecCapability::SysPtrace,\n            CapsCapability::CAP_SYS_RAWIO => SpecCapability::SysRawio,\n            CapsCapability::CAP_SYS_RESOURCE => SpecCapability::SysResource,\n            CapsCapability::CAP_SYS_TIME => SpecCapability::SysTime,\n            CapsCapability::CAP_SYS_TTY_CONFIG => SpecCapability::SysTtyConfig,\n            CapsCapability::CAP_SYSLOG => SpecCapability::Syslog,\n            CapsCapability::CAP_WAKE_ALARM => SpecCapability::WakeAlarm,\n            CapsCapability::__Nonexhaustive => unreachable!(\"invalid capability\"),\n        }\n    }\n}\n\n/// reset capabilities of process calling this to effective capabilities\n/// effective capability set is set of capabilities used by kernel to perform checks\n/// see <https://man7.org/linux/man-pages/man7/capabilities.7.html> for more information\npub fn reset_effective<S: Syscall + ?Sized>(syscall: &S) -> Result<(), SyscallError> {\n    tracing::debug!(\"reset all caps\");\n    // permitted capabilities are all the capabilities that we are allowed to acquire\n    let permitted = caps::read(None, CapSet::Permitted)?;\n    syscall.set_capability(CapSet::Effective, &permitted)?;\n    Ok(())\n}\n\n/// Drop any extra granted capabilities, and reset to defaults which are in oci specification\npub fn drop_privileges<S: Syscall + ?Sized>(\n    cs: &LinuxCapabilities,\n    syscall: &S,\n) -> Result<(), SyscallError> {\n    tracing::debug!(\"dropping bounding capabilities to {:?}\", cs.bounding());\n    if let Some(bounding) = cs.bounding() {\n        syscall.set_capability(CapSet::Bounding, &to_set(bounding))?;\n    }\n\n    if let Some(effective) = cs.effective() {\n        syscall.set_capability(CapSet::Effective, &to_set(effective))?;\n    }\n\n    if let Some(permitted) = cs.permitted() {\n        syscall.set_capability(CapSet::Permitted, &to_set(permitted))?;\n    }\n\n    if let Some(inheritable) = cs.inheritable() {\n        syscall.set_capability(CapSet::Inheritable, &to_set(inheritable))?;\n    }\n\n    if let Some(ambient) = cs.ambient() {\n        syscall.set_capability(CapSet::Ambient, &to_set(ambient))?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashSet;\n\n    use oci_spec::runtime::LinuxCapabilitiesBuilder;\n\n    use super::*;\n    use crate::syscall::test::TestHelperSyscall;\n\n    #[test]\n    fn test_reset_effective() {\n        let test_command = TestHelperSyscall::default();\n        let permitted_caps = caps::read(None, CapSet::Permitted).unwrap();\n        assert!(reset_effective(&test_command).is_ok());\n        let set_capability_args: Vec<_> = test_command\n            .get_set_capability_args()\n            .into_iter()\n            .map(|(_capset, caps)| caps)\n            .collect();\n        assert_eq!(set_capability_args, vec![permitted_caps]);\n    }\n\n    #[test]\n    fn test_convert_oci_spec_to_caps_type() {\n        struct Testcase {\n            input: SpecCapability,\n            want: CapsCapability,\n        }\n\n        let tests = vec![\n            Testcase {\n                input: SpecCapability::AuditControl,\n                want: CapsCapability::CAP_AUDIT_CONTROL,\n            },\n            Testcase {\n                input: SpecCapability::AuditRead,\n                want: CapsCapability::CAP_AUDIT_READ,\n            },\n            Testcase {\n                input: SpecCapability::AuditWrite,\n                want: CapsCapability::CAP_AUDIT_WRITE,\n            },\n            Testcase {\n                input: SpecCapability::BlockSuspend,\n                want: CapsCapability::CAP_BLOCK_SUSPEND,\n            },\n            Testcase {\n                input: SpecCapability::Bpf,\n                want: CapsCapability::CAP_BPF,\n            },\n            Testcase {\n                input: SpecCapability::CheckpointRestore,\n                want: CapsCapability::CAP_CHECKPOINT_RESTORE,\n            },\n            Testcase {\n                input: SpecCapability::Chown,\n                want: Capability::CAP_CHOWN,\n            },\n            Testcase {\n                input: SpecCapability::DacOverride,\n                want: CapsCapability::CAP_DAC_OVERRIDE,\n            },\n            Testcase {\n                input: SpecCapability::DacReadSearch,\n                want: CapsCapability::CAP_DAC_READ_SEARCH,\n            },\n            Testcase {\n                input: SpecCapability::Fowner,\n                want: CapsCapability::CAP_FOWNER,\n            },\n            Testcase {\n                input: SpecCapability::Fsetid,\n                want: CapsCapability::CAP_FSETID,\n            },\n            Testcase {\n                input: SpecCapability::IpcLock,\n                want: CapsCapability::CAP_IPC_LOCK,\n            },\n            Testcase {\n                input: SpecCapability::IpcOwner,\n                want: CapsCapability::CAP_IPC_OWNER,\n            },\n            Testcase {\n                input: SpecCapability::Kill,\n                want: CapsCapability::CAP_KILL,\n            },\n            Testcase {\n                input: SpecCapability::Lease,\n                want: CapsCapability::CAP_LEASE,\n            },\n            Testcase {\n                input: SpecCapability::LinuxImmutable,\n                want: CapsCapability::CAP_LINUX_IMMUTABLE,\n            },\n            Testcase {\n                input: SpecCapability::MacAdmin,\n                want: CapsCapability::CAP_MAC_ADMIN,\n            },\n            Testcase {\n                input: SpecCapability::MacOverride,\n                want: CapsCapability::CAP_MAC_OVERRIDE,\n            },\n            Testcase {\n                input: SpecCapability::Mknod,\n                want: CapsCapability::CAP_MKNOD,\n            },\n            Testcase {\n                input: SpecCapability::NetAdmin,\n                want: CapsCapability::CAP_NET_ADMIN,\n            },\n            Testcase {\n                input: SpecCapability::NetBindService,\n                want: CapsCapability::CAP_NET_BIND_SERVICE,\n            },\n            Testcase {\n                input: SpecCapability::NetBroadcast,\n                want: CapsCapability::CAP_NET_BROADCAST,\n            },\n            Testcase {\n                input: SpecCapability::NetRaw,\n                want: CapsCapability::CAP_NET_RAW,\n            },\n            Testcase {\n                input: SpecCapability::Perfmon,\n                want: CapsCapability::CAP_PERFMON,\n            },\n            Testcase {\n                input: SpecCapability::Setgid,\n                want: CapsCapability::CAP_SETGID,\n            },\n            Testcase {\n                input: SpecCapability::Setfcap,\n                want: CapsCapability::CAP_SETFCAP,\n            },\n            Testcase {\n                input: SpecCapability::Setpcap,\n                want: CapsCapability::CAP_SETPCAP,\n            },\n            Testcase {\n                input: SpecCapability::Setuid,\n                want: CapsCapability::CAP_SETUID,\n            },\n            Testcase {\n                input: SpecCapability::SysAdmin,\n                want: CapsCapability::CAP_SYS_ADMIN,\n            },\n            Testcase {\n                input: SpecCapability::SysBoot,\n                want: CapsCapability::CAP_SYS_BOOT,\n            },\n            Testcase {\n                input: SpecCapability::SysChroot,\n                want: CapsCapability::CAP_SYS_CHROOT,\n            },\n            Testcase {\n                input: SpecCapability::SysModule,\n                want: CapsCapability::CAP_SYS_MODULE,\n            },\n            Testcase {\n                input: SpecCapability::SysNice,\n                want: CapsCapability::CAP_SYS_NICE,\n            },\n            Testcase {\n                input: SpecCapability::SysPacct,\n                want: CapsCapability::CAP_SYS_PACCT,\n            },\n            Testcase {\n                input: SpecCapability::SysPtrace,\n                want: CapsCapability::CAP_SYS_PTRACE,\n            },\n            Testcase {\n                input: SpecCapability::SysRawio,\n                want: CapsCapability::CAP_SYS_RAWIO,\n            },\n            Testcase {\n                input: SpecCapability::SysResource,\n                want: CapsCapability::CAP_SYS_RESOURCE,\n            },\n            Testcase {\n                input: SpecCapability::SysTime,\n                want: CapsCapability::CAP_SYS_TIME,\n            },\n            Testcase {\n                input: SpecCapability::SysTtyConfig,\n                want: CapsCapability::CAP_SYS_TTY_CONFIG,\n            },\n            Testcase {\n                input: SpecCapability::Syslog,\n                want: CapsCapability::CAP_SYSLOG,\n            },\n            Testcase {\n                input: SpecCapability::WakeAlarm,\n                want: CapsCapability::CAP_WAKE_ALARM,\n            },\n        ];\n\n        for test in tests {\n            let got = test.input.to_cap();\n            assert_eq!(got, test.want);\n        }\n    }\n\n    #[test]\n    fn test_convert_caps_type_to_oci_spec() {\n        struct Testcase {\n            input: CapsCapability,\n            want: SpecCapability,\n        }\n\n        let tests = vec![\n            Testcase {\n                input: CapsCapability::CAP_AUDIT_CONTROL,\n                want: SpecCapability::AuditControl,\n            },\n            Testcase {\n                input: CapsCapability::CAP_AUDIT_READ,\n                want: SpecCapability::AuditRead,\n            },\n            Testcase {\n                input: CapsCapability::CAP_AUDIT_WRITE,\n                want: SpecCapability::AuditWrite,\n            },\n            Testcase {\n                input: CapsCapability::CAP_BLOCK_SUSPEND,\n                want: SpecCapability::BlockSuspend,\n            },\n            Testcase {\n                input: CapsCapability::CAP_BPF,\n                want: SpecCapability::Bpf,\n            },\n            Testcase {\n                input: CapsCapability::CAP_CHECKPOINT_RESTORE,\n                want: SpecCapability::CheckpointRestore,\n            },\n            Testcase {\n                input: CapsCapability::CAP_CHOWN,\n                want: SpecCapability::Chown,\n            },\n            Testcase {\n                input: CapsCapability::CAP_DAC_OVERRIDE,\n                want: SpecCapability::DacOverride,\n            },\n            Testcase {\n                input: CapsCapability::CAP_DAC_READ_SEARCH,\n                want: SpecCapability::DacReadSearch,\n            },\n            Testcase {\n                input: CapsCapability::CAP_FOWNER,\n                want: SpecCapability::Fowner,\n            },\n            Testcase {\n                input: CapsCapability::CAP_FSETID,\n                want: SpecCapability::Fsetid,\n            },\n            Testcase {\n                input: CapsCapability::CAP_IPC_LOCK,\n                want: SpecCapability::IpcLock,\n            },\n            Testcase {\n                input: CapsCapability::CAP_IPC_OWNER,\n                want: SpecCapability::IpcOwner,\n            },\n            Testcase {\n                input: CapsCapability::CAP_KILL,\n                want: SpecCapability::Kill,\n            },\n            Testcase {\n                input: CapsCapability::CAP_LEASE,\n                want: SpecCapability::Lease,\n            },\n            Testcase {\n                input: CapsCapability::CAP_LINUX_IMMUTABLE,\n                want: SpecCapability::LinuxImmutable,\n            },\n            Testcase {\n                input: CapsCapability::CAP_MAC_ADMIN,\n                want: SpecCapability::MacAdmin,\n            },\n            Testcase {\n                input: CapsCapability::CAP_MAC_OVERRIDE,\n                want: SpecCapability::MacOverride,\n            },\n            Testcase {\n                input: CapsCapability::CAP_MKNOD,\n                want: SpecCapability::Mknod,\n            },\n            Testcase {\n                input: CapsCapability::CAP_NET_ADMIN,\n                want: SpecCapability::NetAdmin,\n            },\n            Testcase {\n                input: CapsCapability::CAP_NET_BIND_SERVICE,\n                want: SpecCapability::NetBindService,\n            },\n            Testcase {\n                input: CapsCapability::CAP_NET_BROADCAST,\n                want: SpecCapability::NetBroadcast,\n            },\n            Testcase {\n                input: CapsCapability::CAP_NET_RAW,\n                want: SpecCapability::NetRaw,\n            },\n            Testcase {\n                input: CapsCapability::CAP_PERFMON,\n                want: SpecCapability::Perfmon,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SETGID,\n                want: SpecCapability::Setgid,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SETFCAP,\n                want: SpecCapability::Setfcap,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SETPCAP,\n                want: SpecCapability::Setpcap,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SETUID,\n                want: SpecCapability::Setuid,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_ADMIN,\n                want: SpecCapability::SysAdmin,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_BOOT,\n                want: SpecCapability::SysBoot,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_CHROOT,\n                want: SpecCapability::SysChroot,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_MODULE,\n                want: SpecCapability::SysModule,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_NICE,\n                want: SpecCapability::SysNice,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_PACCT,\n                want: SpecCapability::SysPacct,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_PTRACE,\n                want: SpecCapability::SysPtrace,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_RAWIO,\n                want: SpecCapability::SysRawio,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_RESOURCE,\n                want: SpecCapability::SysResource,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_TIME,\n                want: SpecCapability::SysTime,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYS_TTY_CONFIG,\n                want: SpecCapability::SysTtyConfig,\n            },\n            Testcase {\n                input: CapsCapability::CAP_SYSLOG,\n                want: SpecCapability::Syslog,\n            },\n            Testcase {\n                input: CapsCapability::CAP_WAKE_ALARM,\n                want: SpecCapability::WakeAlarm,\n            },\n        ];\n\n        for test in tests {\n            let got = SpecCapability::from_cap(test.input);\n            assert_eq!(got, test.want);\n        }\n    }\n\n    #[test]\n    fn test_drop_privileges() {\n        struct Testcase {\n            name: String,\n            input: LinuxCapabilities,\n            // be aware that the calling sequence in the drop_privileges function\n            // will affect the output sequence from test_command.get_set_capability_args()\n            want: Vec<(CapSet, Vec<SpecCapability>)>,\n        }\n\n        let cps = vec![\n            SpecCapability::AuditWrite,\n            SpecCapability::Kill,\n            SpecCapability::NetBindService,\n        ];\n\n        let tests = vec![\n            Testcase {\n                name: format!(\"all LinuxCapabilities fields with caps: {cps:?}\"),\n                input: LinuxCapabilitiesBuilder::default()\n                    .bounding(cps.clone().into_iter().collect::<Capabilities>())\n                    .effective(cps.clone().into_iter().collect::<Capabilities>())\n                    .inheritable(cps.clone().into_iter().collect::<Capabilities>())\n                    .permitted(cps.clone().into_iter().collect::<Capabilities>())\n                    .ambient(cps.clone().into_iter().collect::<Capabilities>())\n                    .build()\n                    .unwrap(),\n                want: vec![\n                    (CapSet::Bounding, cps.clone()),\n                    (CapSet::Effective, cps.clone()),\n                    (CapSet::Permitted, cps.clone()),\n                    (CapSet::Inheritable, cps.clone()),\n                    (CapSet::Ambient, cps.clone()),\n                ],\n            },\n            Testcase {\n                name: format!(\"partial LinuxCapabilities fields with caps: {cps:?}\"),\n                input: LinuxCapabilitiesBuilder::default()\n                    .bounding(cps.clone().into_iter().collect::<Capabilities>())\n                    .effective(cps.clone().into_iter().collect::<Capabilities>())\n                    .permitted(cps.clone().into_iter().collect::<Capabilities>())\n                    .build()\n                    .unwrap(),\n                want: vec![\n                    (CapSet::Bounding, cps.clone()),\n                    (CapSet::Effective, cps.clone()),\n                    (CapSet::Permitted, cps.clone()),\n                    (CapSet::Inheritable, cps.clone()),\n                    (CapSet::Ambient, cps.clone()),\n                ],\n            },\n            Testcase {\n                name: format!(\"empty LinuxCapabilities fields with caps: {cps:?}\"),\n                input: LinuxCapabilitiesBuilder::default()\n                    .bounding(HashSet::new())\n                    .effective(HashSet::new())\n                    .inheritable(HashSet::new())\n                    .permitted(HashSet::new())\n                    .ambient(HashSet::new())\n                    .build()\n                    .unwrap(),\n                want: vec![\n                    (CapSet::Bounding, cps.clone()),\n                    (CapSet::Effective, cps.clone()),\n                    (CapSet::Permitted, cps.clone()),\n                    (CapSet::Inheritable, cps.clone()),\n                    (CapSet::Ambient, cps),\n                ],\n            },\n        ];\n\n        for test in tests {\n            let test_command = TestHelperSyscall::default();\n            assert!(\n                drop_privileges(&test.input, &test_command).is_ok(),\n                \"{}, drop_privileges is not ok\",\n                test.name\n            );\n\n            let got: Vec<(CapSet, Vec<_>)> = test_command\n                .get_set_capability_args()\n                .into_iter()\n                .map(|(capset, caps)| {\n                    (\n                        capset,\n                        caps.into_iter().map(SpecCapability::from_cap).collect(),\n                    )\n                })\n                .collect();\n            assert_eq!(\n                got.len(),\n                test.want.len(),\n                \"{}, len of got:{}, want:{}\",\n                test.name,\n                got.len(),\n                test.want.len(),\n            );\n\n            for (i, want) in test.want.iter().enumerate().take(test.want.len()) {\n                // because CapSet has no Eq, PartialEq attributes,\n                // so using String to do the comparison.\n                let want_cap_set = format!(\"{:?}\", want.0);\n                let got_cap_set = format!(\"{:?}\", got[i].0);\n                let want_caps = &want.1;\n                let got_caps = &got[i].1;\n\n                assert_eq!(\n                    got_cap_set, want_cap_set,\n                    \"{}, capset of got:{}, want:{}\",\n                    test.name, got_cap_set, want_cap_set,\n                );\n                // because get_set_capability_args returns a HasSet of capabilities,\n                // so the ordering is randomized.\n                assert!(\n                    got_caps.iter().all(|cap| want_caps.contains(cap)),\n                    \"{}, caps of got:{:?}, want:{:?}\",\n                    test.name,\n                    got_caps,\n                    want_caps\n                );\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/channel.rs",
    "content": "use std::io::{IoSlice, IoSliceMut};\nuse std::marker::PhantomData;\nuse std::os::fd::AsRawFd;\nuse std::os::unix::prelude::RawFd;\n\nuse nix::sys::socket::{self, UnixAddr};\nuse nix::unistd::{self};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, thiserror::Error)]\npub enum ChannelError {\n    #[error(\"failed unix syscalls\")]\n    Nix(#[from] nix::Error),\n    #[error(\"failed serde serialization\")]\n    Serde(#[from] serde_json::Error),\n    #[error(\"channel connection broken\")]\n    BrokenChannel,\n}\npub struct Receiver<T> {\n    receiver: RawFd,\n    phantom: PhantomData<T>,\n}\n\npub struct Sender<T> {\n    sender: RawFd,\n    phantom: PhantomData<T>,\n}\n\nimpl<T> Sender<T>\nwhere\n    T: Serialize,\n{\n    fn send_iovec(\n        &mut self,\n        iov: &[IoSlice],\n        fds: Option<&[RawFd]>,\n    ) -> Result<usize, ChannelError> {\n        let cmsgs = if let Some(fds) = fds {\n            vec![socket::ControlMessage::ScmRights(fds)]\n        } else {\n            vec![]\n        };\n        socket::sendmsg::<UnixAddr>(self.sender, iov, &cmsgs, socket::MsgFlags::empty(), None)\n            .map_err(|e| e.into())\n    }\n\n    fn send_slice_with_len(\n        &mut self,\n        data: &[u8],\n        fds: Option<&[RawFd]>,\n    ) -> Result<usize, ChannelError> {\n        let len = data.len() as u64;\n        // Here we prefix the length of the data onto the serialized data.\n        let iov = [\n            IoSlice::new(unsafe {\n                std::slice::from_raw_parts(\n                    (&len as *const u64) as *const u8,\n                    std::mem::size_of::<u64>(),\n                )\n            }),\n            IoSlice::new(data),\n        ];\n        self.send_iovec(&iov[..], fds)\n    }\n\n    pub fn send(&mut self, object: T) -> Result<(), ChannelError> {\n        let payload = serde_json::to_vec(&object)?;\n        self.send_slice_with_len(&payload, None)?;\n\n        Ok(())\n    }\n\n    pub fn send_fds(&mut self, object: T, fds: &[RawFd]) -> Result<(), ChannelError> {\n        let payload = serde_json::to_vec(&object)?;\n        self.send_slice_with_len(&payload, Some(fds))?;\n\n        Ok(())\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        Ok(unistd::close(self.sender)?)\n    }\n}\n\nimpl<T> Receiver<T>\nwhere\n    T: serde::de::DeserializeOwned,\n{\n    fn peek_size_iovec(&mut self) -> Result<u64, ChannelError> {\n        let mut len: u64 = 0;\n        let mut iov = [IoSliceMut::new(unsafe {\n            std::slice::from_raw_parts_mut(\n                (&mut len as *mut u64) as *mut u8,\n                std::mem::size_of::<u64>(),\n            )\n        })];\n        let _ =\n            socket::recvmsg::<UnixAddr>(self.receiver, &mut iov, None, socket::MsgFlags::MSG_PEEK)?;\n        match len {\n            0 => Err(ChannelError::BrokenChannel),\n            _ => Ok(len),\n        }\n    }\n\n    fn recv_into_iovec<F>(\n        &mut self,\n        iov: &mut [IoSliceMut],\n    ) -> Result<(usize, Option<F>), ChannelError>\n    where\n        F: Default + AsMut<[RawFd]>,\n    {\n        let mut cmsgspace = nix::cmsg_space!(F);\n        let msg = socket::recvmsg::<UnixAddr>(\n            self.receiver,\n            iov,\n            Some(&mut cmsgspace),\n            socket::MsgFlags::MSG_CMSG_CLOEXEC,\n        )?;\n\n        // Sending multiple SCM_RIGHTS message will led to platform dependent\n        // behavior, with some system choose to return EINVAL when sending or\n        // silently only process the first msg or send all of it. Here we assume\n        // there is only one SCM_RIGHTS message and will only process the first\n        // message.\n        let fds: Option<F> = msg\n            .cmsgs()?\n            .find_map(|cmsg| {\n                if let socket::ControlMessageOwned::ScmRights(fds) = cmsg {\n                    Some(fds)\n                } else {\n                    None\n                }\n            })\n            .map(|fds| {\n                let mut fds_array: F = Default::default();\n                <F as AsMut<[RawFd]>>::as_mut(&mut fds_array).clone_from_slice(&fds);\n                fds_array\n            });\n\n        Ok((msg.bytes, fds))\n    }\n\n    fn recv_into_buf_with_len<F>(&mut self) -> Result<(Vec<u8>, Option<F>), ChannelError>\n    where\n        F: Default + AsMut<[RawFd]>,\n    {\n        let msg_len = self.peek_size_iovec()?;\n        let mut len: u64 = 0;\n        let mut buf = vec![0u8; msg_len as usize];\n        let (bytes, fds) = {\n            let mut iov = [\n                IoSliceMut::new(unsafe {\n                    std::slice::from_raw_parts_mut(\n                        (&mut len as *mut u64) as *mut u8,\n                        std::mem::size_of::<u64>(),\n                    )\n                }),\n                IoSliceMut::new(&mut buf),\n            ];\n            self.recv_into_iovec(&mut iov)?\n        };\n\n        match bytes {\n            0 => Err(ChannelError::BrokenChannel),\n            _ => Ok((buf, fds)),\n        }\n    }\n\n    // Recv the next message of type T.\n    pub fn recv(&mut self) -> Result<T, ChannelError> {\n        let (buf, _) = self.recv_into_buf_with_len::<[RawFd; 0]>()?;\n        Ok(serde_json::from_slice(&buf[..])?)\n    }\n\n    // Works similar to `recv`, but will look for fds sent by SCM_RIGHTS\n    // message.  We use F as as `[RawFd; n]`, where `n` is the number of\n    // descriptors you want to receive.\n    pub fn recv_with_fds<F>(&mut self) -> Result<(T, Option<F>), ChannelError>\n    where\n        F: Default + AsMut<[RawFd]>,\n    {\n        let (buf, fds) = self.recv_into_buf_with_len::<F>()?;\n        Ok((serde_json::from_slice(&buf[..])?, fds))\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        Ok(unistd::close(self.receiver)?)\n    }\n}\n\npub fn channel<T>() -> Result<(Sender<T>, Receiver<T>), ChannelError>\nwhere\n    T: for<'de> Deserialize<'de> + Serialize,\n{\n    let (os_sender, os_receiver) = unix_channel()?;\n    let receiver = Receiver {\n        receiver: os_receiver,\n        phantom: PhantomData,\n    };\n    let sender = Sender {\n        sender: os_sender,\n        phantom: PhantomData,\n    };\n    Ok((sender, receiver))\n}\n\n// Use socketpair as the underlying pipe.\nfn unix_channel() -> Result<(RawFd, RawFd), ChannelError> {\n    let (f1, f2) = socket::socketpair(\n        socket::AddressFamily::Unix,\n        socket::SockType::SeqPacket,\n        None,\n        socket::SockFlag::SOCK_CLOEXEC,\n    )?;\n    // It is not straightforward to share the OwnedFd across forks, so we\n    // treat them as i32. We use ManuallyDrop to keep the connection open.\n    let f1 = std::mem::ManuallyDrop::new(f1);\n    let f2 = std::mem::ManuallyDrop::new(f2);\n\n    Ok((f1.as_raw_fd(), f2.as_raw_fd()))\n}\n"
  },
  {
    "path": "crates/libcontainer/src/config.rs",
    "content": "use std::fs;\nuse std::io::{BufReader, BufWriter, Write};\nuse std::path::{Path, PathBuf};\n\nuse oci_spec::runtime::{Hooks, Spec};\nuse serde::{Deserialize, Serialize};\n\nuse crate::utils;\n\npub enum PersonalityDomain {\n    Linux = 0x0000,\n    Linux32 = 0x0008,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ConfigError {\n    #[error(\"failed to save config\")]\n    SaveIO {\n        source: std::io::Error,\n        path: PathBuf,\n    },\n    #[error(\"failed to save config\")]\n    SaveEncode {\n        source: serde_json::Error,\n        path: PathBuf,\n    },\n    #[error(\"failed to parse config\")]\n    LoadIO {\n        source: std::io::Error,\n        path: PathBuf,\n    },\n    #[error(\"failed to parse config\")]\n    LoadParse {\n        source: serde_json::Error,\n        path: PathBuf,\n    },\n    #[error(\"missing linux in spec\")]\n    MissingLinux,\n}\n\ntype Result<T> = std::result::Result<T, ConfigError>;\n\nconst YOUKI_CONFIG_NAME: &str = \"youki_config.json\";\n\n/// A configuration for passing information obtained during container creation to other commands.\n/// Keeping the information to a minimum improves performance.\n#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]\n#[non_exhaustive]\npub struct YoukiConfig {\n    pub hooks: Option<Hooks>,\n    pub cgroup_path: PathBuf,\n}\n\nimpl YoukiConfig {\n    pub fn from_spec(spec: &Spec, container_id: &str) -> Result<Self> {\n        Ok(YoukiConfig {\n            hooks: spec.hooks().clone(),\n            cgroup_path: utils::get_cgroup_path(\n                spec.linux()\n                    .as_ref()\n                    .ok_or(ConfigError::MissingLinux)?\n                    .cgroups_path(),\n                container_id,\n            ),\n        })\n    }\n\n    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        let file = fs::File::create(path.as_ref().join(YOUKI_CONFIG_NAME)).map_err(|err| {\n            ConfigError::SaveIO {\n                source: err,\n                path: path.as_ref().to_owned(),\n            }\n        })?;\n        let mut writer = BufWriter::new(file);\n        serde_json::to_writer(&mut writer, self).map_err(|err| ConfigError::SaveEncode {\n            source: err,\n            path: path.as_ref().to_owned(),\n        })?;\n        writer.flush().map_err(|err| ConfigError::SaveIO {\n            source: err,\n            path: path.as_ref().to_owned(),\n        })?;\n\n        Ok(())\n    }\n\n    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {\n        let path = path.as_ref();\n        let file =\n            fs::File::open(path.join(YOUKI_CONFIG_NAME)).map_err(|err| ConfigError::LoadIO {\n                source: err,\n                path: path.to_owned(),\n            })?;\n        let reader = BufReader::new(file);\n        let config = serde_json::from_reader(reader).map_err(|err| ConfigError::LoadParse {\n            source: err,\n            path: path.to_owned(),\n        })?;\n        Ok(config)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::Result;\n\n    use super::*;\n\n    #[test]\n    fn test_config_from_spec() -> Result<()> {\n        let container_id = \"sample\";\n        let spec = Spec::default();\n        let config = YoukiConfig::from_spec(&spec, container_id)?;\n        assert_eq!(&config.hooks, spec.hooks());\n        dbg!(&config.cgroup_path);\n        assert_eq!(\n            config.cgroup_path,\n            PathBuf::from(format!(\":youki:{container_id}\"))\n        );\n        Ok(())\n    }\n\n    #[test]\n    fn test_config_save_and_load() -> Result<()> {\n        let container_id = \"sample\";\n        let tmp = tempfile::tempdir().expect(\"create temp dir\");\n        let spec = Spec::default();\n        let config = YoukiConfig::from_spec(&spec, container_id)?;\n        config.save(&tmp)?;\n        let act = YoukiConfig::load(&tmp)?;\n        assert_eq!(act, config);\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/builder.rs",
    "content": "use std::os::fd::OwnedFd;\nuse std::path::PathBuf;\n\nuse super::init_builder::InitContainerBuilder;\nuse super::tenant_builder::TenantContainerBuilder;\nuse crate::error::{ErrInvalidID, LibcontainerError};\nuse crate::syscall::syscall::SyscallType;\nuse crate::utils::PathBufExt;\nuse crate::workload::{self, Executor};\n\npub struct ContainerBuilder {\n    /// Id of the container\n    pub(super) container_id: String,\n    /// Root directory for container state\n    pub(super) root_path: PathBuf,\n    /// Interface to operating system primitives\n    pub(super) syscall: SyscallType,\n    /// File which will be used to communicate the pid of the\n    /// container process to the higher level runtime\n    pub(super) pid_file: Option<PathBuf>,\n    /// Socket to communicate the file descriptor of the ptty\n    pub(super) console_socket: Option<PathBuf>,\n    /// File descriptors to be passed into the container process\n    pub(super) preserve_fds: i32,\n    /// The function that actually runs on the container init process. Default\n    /// is to execute the specified command in the oci spec.\n    pub(super) executor: Box<dyn Executor>,\n    // RawFd set to stdin of the container init process.\n    pub stdin: Option<OwnedFd>,\n    // RawFd set to stdout of the container init process.\n    pub stdout: Option<OwnedFd>,\n    // RawFd set to stderr of the container init process.\n    pub stderr: Option<OwnedFd>,\n}\n\n/// Builder that can be used to configure the common properties of\n/// either a init or a tenant container\n///\n/// # Example\n///\n/// ```no_run\n/// use libcontainer::container::builder::ContainerBuilder;\n/// use libcontainer::syscall::syscall::SyscallType;\n///\n/// ContainerBuilder::new(\n///     \"74f1a4cb3801\".to_owned(),\n///     SyscallType::default(),\n/// )\n/// .with_root_path(\"/run/containers/youki\").expect(\"invalid root path\")\n/// .with_pid_file(Some(\"/var/run/docker.pid\")).expect(\"invalid pid file\")\n/// .with_console_socket(Some(\"/var/run/docker/sock.tty\"))\n/// .as_init(\"/var/run/docker/bundle\")\n/// .build();\n/// ```\nimpl ContainerBuilder {\n    /// Generates the base configuration for a container which can be\n    /// transformed into either a init container or a tenant container\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// let builder = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// );\n    /// ```\n    pub fn new(container_id: String, syscall: SyscallType) -> Self {\n        let root_path = PathBuf::from(\"/run/youki\");\n        Self {\n            container_id,\n            root_path,\n            syscall,\n            pid_file: None,\n            console_socket: None,\n            preserve_fds: 0,\n            executor: workload::default::get_executor(),\n            stdin: None,\n            stdout: None,\n            stderr: None,\n        }\n    }\n\n    /// validate_id checks if the supplied container ID is valid, returning\n    /// the ErrInvalidID in case it is not.\n    ///\n    /// The format of valid ID was never formally defined, instead the code\n    /// was modified to allow or disallow specific characters.\n    ///\n    /// Currently, a valid ID is a non-empty string consisting only of\n    /// the following characters:\n    /// - uppercase (A-Z) and lowercase (a-z) Latin letters;\n    /// - digits (0-9);\n    /// - underscore (_);\n    /// - plus sign (+);\n    /// - minus sign (-);\n    /// - period (.).\n    ///\n    /// In addition, IDs that can't be used to represent a file name\n    /// (such as . or ..) are rejected.\n    pub fn validate_id(self) -> Result<Self, LibcontainerError> {\n        let container_id = self.container_id.clone();\n        if container_id.is_empty() {\n            Err(ErrInvalidID::Empty)?;\n        }\n\n        if container_id == \".\" || container_id == \"..\" {\n            Err(ErrInvalidID::FileName)?;\n        }\n\n        for c in container_id.chars() {\n            match c {\n                'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '+' | '-' | '.' => (),\n                _ => Err(ErrInvalidID::InvalidChars(c))?,\n            }\n        }\n        Ok(self)\n    }\n\n    /// Transforms this builder into a tenant builder\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_tenant()\n    /// .with_container_args(vec![\"sleep\".to_owned(), \"9001\".to_owned()])\n    /// .build();\n    /// ```\n    #[allow(clippy::wrong_self_convention)]\n    pub fn as_tenant(self) -> TenantContainerBuilder {\n        TenantContainerBuilder::new(self)\n    }\n\n    /// Transforms this builder into an init builder\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .with_systemd(false)\n    /// .build();\n    /// ```\n    #[allow(clippy::wrong_self_convention)]\n    pub fn as_init<P: Into<PathBuf>>(self, bundle: P) -> InitContainerBuilder {\n        InitContainerBuilder::new(self, bundle.into())\n    }\n\n    /// Sets the root path which will be used to store the container state\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_root_path(\"/run/containers/youki\").expect(\"invalid root path\");\n    /// ```\n    pub fn with_root_path<P: Into<PathBuf>>(mut self, path: P) -> Result<Self, LibcontainerError> {\n        let path = path.into();\n        self.root_path = path.canonicalize_safely().map_err(|err| {\n            tracing::error!(?path, ?err, \"failed to canonicalize root path\");\n            LibcontainerError::InvalidInput(format!(\"invalid root path {path:?}: {err:?}\"))\n        })?;\n\n        Ok(self)\n    }\n\n    /// Sets the pid file which will be used to write the pid of the container\n    /// process\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_pid_file(Some(\"/var/run/docker.pid\")).expect(\"invalid pid file\");\n    /// ```\n    pub fn with_pid_file<P: Into<PathBuf>>(\n        mut self,\n        path: Option<P>,\n    ) -> Result<Self, LibcontainerError> {\n        self.pid_file = match path.map(|p| p.into()) {\n            Some(path) => Some(path.canonicalize_safely().map_err(|err| {\n                tracing::error!(?path, ?err, \"failed to canonicalize pid file\");\n                LibcontainerError::InvalidInput(format!(\"invalid pid file path {path:?}: {err:?}\"))\n            })?),\n            None => None,\n        };\n\n        Ok(self)\n    }\n\n    /// Sets the console socket, which will be used to send the file descriptor\n    /// of the pseudoterminal\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_console_socket(Some(\"/var/run/docker/sock.tty\"));\n    /// ```\n    pub fn with_console_socket<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {\n        self.console_socket = path.map(|p| p.into());\n        self\n    }\n\n    /// Sets the number of additional file descriptors which will be passed into\n    /// the container process.\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_preserved_fds(5);\n    /// ```\n    pub fn with_preserved_fds(mut self, preserved_fds: i32) -> Self {\n        self.preserve_fds = preserved_fds;\n        self\n    }\n\n    /// Sets the function that actually runs on the container init process.\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    /// # use libcontainer::workload::default::DefaultExecutor;\n    ///\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_executor(DefaultExecutor{});\n    /// ```\n    pub fn with_executor(mut self, executor: impl Executor + 'static) -> Self {\n        self.executor = Box::new(executor);\n        self\n    }\n\n    /// Sets the stdin of the container, for those who use libcontainer as a library,\n    /// the container stdin may have to be set to an opened file descriptor\n    /// rather than the stdin of the current process.\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    /// # use libcontainer::workload::default::DefaultExecutor;\n    /// # use nix::unistd::pipe;\n    ///\n    /// let (r, _w) = pipe().unwrap();\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_stdin(r);\n    /// ```\n    pub fn with_stdin(mut self, stdin: impl Into<OwnedFd>) -> Self {\n        self.stdin = Some(stdin.into());\n        self\n    }\n\n    /// Sets the stdout of the container, for those who use libcontainer as a library,\n    /// the container stdout may have to be set to an opened file descriptor\n    /// rather than the stdout of the current process.\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    /// # use libcontainer::workload::default::DefaultExecutor;\n    /// # use nix::unistd::pipe;\n    ///\n    /// let (_r, w) = pipe().unwrap();\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_stdout(w);\n    /// ```\n    pub fn with_stdout(mut self, stdout: impl Into<OwnedFd>) -> Self {\n        self.stdout = Some(stdout.into());\n        self\n    }\n\n    /// Sets the stderr of the container, for those who use libcontainer as a library,\n    /// the container stderr may have to be set to an opened file descriptor\n    /// rather than the stderr of the current process.\n    /// # Example\n    ///\n    /// ```no_run\n    /// # use libcontainer::container::builder::ContainerBuilder;\n    /// # use libcontainer::syscall::syscall::SyscallType;\n    /// # use libcontainer::workload::default::DefaultExecutor;\n    /// # use nix::unistd::pipe;\n    ///\n    /// let (_r, w) = pipe().unwrap();\n    /// ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .with_stderr(w);\n    /// ```\n    pub fn with_stderr(mut self, stderr: impl Into<OwnedFd>) -> Self {\n        self.stderr = Some(stderr.into());\n        self\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::os::fd::AsRawFd;\n    use std::path::PathBuf;\n\n    use anyhow::{Context, Result};\n    use nix::unistd::pipe;\n\n    use crate::container::builder::ContainerBuilder;\n    use crate::syscall::syscall::SyscallType;\n\n    #[test]\n    fn test_failable_functions() -> Result<()> {\n        let root_path_temp_dir = tempfile::tempdir().context(\"failed to create temp dir\")?;\n        let pid_file_temp_dir = tempfile::tempdir().context(\"failed to create temp dir\")?;\n        let syscall = SyscallType::default();\n\n        ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall)\n            .with_root_path(root_path_temp_dir.path())?\n            .with_pid_file(Some(pid_file_temp_dir.path().join(\"fake.pid\")))?\n            .with_console_socket(Some(\"/var/run/docker/sock.tty\"))\n            .as_init(\"/var/run/docker/bundle\");\n\n        // accept None pid file.\n        ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall).with_pid_file::<PathBuf>(None)?;\n\n        // accept absolute root path which does not exist\n        let abs_root_path = PathBuf::from(\"/not/existing/path\");\n        let path_builder = ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall)\n            .with_root_path(&abs_root_path)\n            .context(\"build container\")?;\n        assert_eq!(path_builder.root_path, abs_root_path);\n\n        // accept relative root path which does not exist\n        let cwd = std::env::current_dir().context(\"get current dir\")?;\n        let path_builder = ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall)\n            .with_root_path(\"./not/existing/path\")\n            .context(\"build container\")?;\n        assert_eq!(path_builder.root_path, cwd.join(\"not/existing/path\"));\n\n        // accept absolute pid path which does not exist\n        let abs_pid_path = PathBuf::from(\"/not/existing/path\");\n        let path_builder = ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall)\n            .with_pid_file(Some(&abs_pid_path))\n            .context(\"build container\")?;\n        assert_eq!(path_builder.pid_file, Some(abs_pid_path));\n\n        // accept relative pid path which does not exist\n        let cwd = std::env::current_dir().context(\"get current dir\")?;\n        let path_builder = ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall)\n            .with_pid_file(Some(\"./not/existing/path\"))\n            .context(\"build container\")?;\n        assert_eq!(path_builder.pid_file, Some(cwd.join(\"not/existing/path\")));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_validate_id() -> Result<()> {\n        let syscall = SyscallType::default();\n        // validate container_id\n        let result = ContainerBuilder::new(\"$#\".to_owned(), syscall).validate_id();\n        assert!(result.is_err());\n\n        let result = ContainerBuilder::new(\".\".to_owned(), syscall).validate_id();\n        assert!(result.is_err());\n\n        let result = ContainerBuilder::new(\"..\".to_owned(), syscall).validate_id();\n        assert!(result.is_err());\n\n        let result = ContainerBuilder::new(\"...\".to_owned(), syscall).validate_id();\n        assert!(result.is_ok());\n\n        let result = ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), syscall).validate_id();\n        assert!(result.is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn test_stdios() -> Result<()> {\n        let (r, _w) = pipe()?;\n        let stdin_raw = r.as_raw_fd();\n        let builder =\n            ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), SyscallType::default()).with_stdin(r);\n        assert_eq!(\n            builder.stdin.as_ref().map(|o| o.as_raw_fd()),\n            Some(stdin_raw)\n        );\n\n        let (_r, w) = pipe()?;\n        let stdout_raw = w.as_raw_fd();\n        let builder =\n            ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), SyscallType::default()).with_stdout(w);\n        assert_eq!(\n            builder.stdout.as_ref().map(|o| o.as_raw_fd()),\n            Some(stdout_raw)\n        );\n\n        let (_r, w) = pipe()?;\n        let stderr_raw = w.as_raw_fd();\n        let builder =\n            ContainerBuilder::new(\"74f1a4cb3801\".to_owned(), SyscallType::default()).with_stderr(w);\n        assert_eq!(\n            builder.stderr.as_ref().map(|o| o.as_raw_fd()),\n            Some(stderr_raw)\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/builder_impl.rs",
    "content": "use std::fs;\nuse std::io::Write;\nuse std::os::fd::{AsRawFd, OwnedFd};\nuse std::path::PathBuf;\nuse std::rc::Rc;\n\nuse libcgroups::common::CgroupManager;\nuse nix::unistd::Pid;\nuse oci_spec::runtime::Spec;\n\nuse super::{Container, ContainerStatus};\nuse crate::error::{CreateContainerError, LibcontainerError, MissingSpecError};\nuse crate::notify_socket::NotifyListener;\nuse crate::process::args::{ContainerArgs, ContainerType};\nuse crate::process::intel_rdt::delete_resctrl_subdirectory;\nuse crate::process::{self};\nuse crate::syscall::syscall::SyscallType;\nuse crate::user_ns::UserNamespaceConfig;\nuse crate::utils;\nuse crate::workload::Executor;\n\npub(super) struct ContainerBuilderImpl {\n    /// Flag indicating if an init or a tenant container should be created\n    pub container_type: ContainerType,\n    /// Interface to operating system primitives\n    pub syscall: SyscallType,\n    /// Flag indicating if systemd should be used for cgroup management\n    pub use_systemd: bool,\n    /// Id of the container\n    pub container_id: String,\n    /// OCI compliant runtime spec\n    pub spec: Rc<Spec>,\n    /// Root filesystem of the container\n    pub rootfs: PathBuf,\n    /// File which will be used to communicate the pid of the\n    /// container process to the higher level runtime\n    pub pid_file: Option<PathBuf>,\n    /// Socket to communicate the file descriptor of the ptty\n    pub console_socket: Option<OwnedFd>,\n    /// Options for new user namespace\n    pub user_ns_config: Option<UserNamespaceConfig>,\n    /// Path to the Unix Domain Socket to communicate container start\n    pub notify_path: PathBuf,\n    /// Container state\n    pub container: Option<Container>,\n    /// File descriptos preserved/passed to the container init process.\n    pub preserve_fds: i32,\n    /// If the container is to be run in detached mode\n    pub detached: bool,\n    /// Default executes the specified execution of a generic command\n    pub executor: Box<dyn Executor>,\n    /// If do not use pivot root to jail process inside rootfs\n    pub no_pivot: bool,\n    // RawFd set to stdin of the container init process.\n    pub stdin: Option<OwnedFd>,\n    // RawFd set to stdout of the container init process.\n    pub stdout: Option<OwnedFd>,\n    // RawFd set to stderr of the container init process.\n    pub stderr: Option<OwnedFd>,\n    // Indicate if the init process should be a sibling of the main process.\n    pub as_sibling: bool,\n}\n\nimpl ContainerBuilderImpl {\n    pub(super) fn create(&mut self) -> Result<Pid, LibcontainerError> {\n        match self.run_container() {\n            Ok(pid) => Ok(pid),\n            Err(outer) => {\n                // Only the init container should be cleaned up in the case of\n                // an error.\n                let cleanup_err = if self.is_init_container() {\n                    self.cleanup_container().err()\n                } else {\n                    None\n                };\n\n                Err(CreateContainerError::new(outer, cleanup_err).into())\n            }\n        }\n    }\n\n    fn is_init_container(&self) -> bool {\n        matches!(self.container_type, ContainerType::InitContainer)\n    }\n\n    fn run_container(&mut self) -> Result<Pid, LibcontainerError> {\n        let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n        let cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);\n        let cgroup_config = libcgroups::common::CgroupConfig {\n            cgroup_path: cgroups_path,\n            systemd_cgroup: self.use_systemd || self.user_ns_config.is_some(),\n            container_name: self.container_id.to_owned(),\n        };\n        let process = self\n            .spec\n            .process()\n            .as_ref()\n            .ok_or(MissingSpecError::Process)?;\n\n        // Need to create the notify socket before we pivot root, since the unix\n        // domain socket used here is outside of the rootfs of container. During\n        // exec, need to create the socket before we enter into existing mount\n        // namespace. We also need to create to socket before entering into the\n        // user namespace in the case that the path is located in paths only\n        // root can access.\n        let notify_listener = NotifyListener::new(&self.notify_path)?;\n\n        // If Out-of-memory score adjustment is set in specification.  set the score\n        // value for the current process check\n        // https://dev.to/rrampage/surviving-the-linux-oom-killer-2ki9 for some more\n        // information.\n        //\n        // This has to be done before !dumpable because /proc/self/oom_score_adj\n        // is not writeable unless you're an privileged user (if !dumpable is\n        // set). All children inherit their parent's oom_score_adj value on\n        // fork(2) so this will always be propagated properly.\n        if let Some(oom_score_adj) = process.oom_score_adj() {\n            tracing::debug!(\"Set OOM score to {}\", oom_score_adj);\n            let mut f = fs::File::create(\"/proc/self/oom_score_adj\").map_err(|err| {\n                tracing::error!(\"failed to open /proc/self/oom_score_adj: {}\", err);\n                LibcontainerError::OtherIO(err)\n            })?;\n            f.write_all(oom_score_adj.to_string().as_bytes())\n                .map_err(|err| {\n                    tracing::error!(\"failed to write to /proc/self/oom_score_adj: {}\", err);\n                    LibcontainerError::OtherIO(err)\n                })?;\n        }\n\n        // Make the process non-dumpable, to avoid various race conditions that\n        // could cause processes in namespaces we're joining to access host\n        // resources (or potentially execute code).\n        //\n        // However, if the number of namespaces we are joining is 0, we are not\n        // going to be switching to a different security context. Thus setting\n        // ourselves to be non-dumpable only breaks things (like rootless\n        // containers), which is the recommendation from the kernel folks.\n        if linux.namespaces().is_some() {\n            prctl::set_dumpable(false).map_err(|e| {\n                LibcontainerError::Other(format!(\n                    \"error in setting dumpable to false : {}\",\n                    nix::errno::Errno::from_raw(e)\n                ))\n            })?;\n        }\n\n        // This container_args will be passed to the container processes,\n        // therefore we will have to move all the variable by value. Since self\n        // is a shared reference, we have to clone these variables here.\n        let container_args = ContainerArgs {\n            container_type: self.container_type,\n            syscall: self.syscall,\n            spec: Rc::clone(&self.spec),\n            rootfs: self.rootfs.to_owned(),\n            console_socket: self.console_socket.as_ref().map(|c| c.as_raw_fd()),\n            notify_listener,\n            preserve_fds: self.preserve_fds,\n            container: self.container.to_owned(),\n            user_ns_config: self.user_ns_config.to_owned(),\n            cgroup_config,\n            detached: self.detached,\n            executor: self.executor.clone(),\n            no_pivot: self.no_pivot,\n            stdin: self.stdin.as_ref().map(|x| x.as_raw_fd()),\n            stdout: self.stdout.as_ref().map(|x| x.as_raw_fd()),\n            stderr: self.stderr.as_ref().map(|x| x.as_raw_fd()),\n            as_sibling: self.as_sibling,\n            pid_file: self.pid_file.to_owned(),\n        };\n\n        let (init_pid, need_to_clean_up_intel_rdt_dir) =\n            process::container_main_process::container_main_process(&container_args).map_err(\n                |err| {\n                    tracing::error!(\"failed to run container process {}\", err);\n                    LibcontainerError::MainProcess(err)\n                },\n            )?;\n\n        if let Some(container) = &mut self.container {\n            // update status and pid of the container process\n            container\n                .set_status(ContainerStatus::Created)\n                .set_creator(nix::unistd::geteuid().as_raw())\n                .set_pid(init_pid.as_raw())\n                .set_clean_up_intel_rdt_directory(need_to_clean_up_intel_rdt_dir)\n                .save()?;\n        }\n\n        Ok(init_pid)\n    }\n\n    fn cleanup_container(&self) -> Result<(), LibcontainerError> {\n        let linux = self.spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n        let cgroups_path = utils::get_cgroup_path(linux.cgroups_path(), &self.container_id);\n        let cmanager =\n            libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {\n                cgroup_path: cgroups_path,\n                systemd_cgroup: self.use_systemd || self.user_ns_config.is_some(),\n                container_name: self.container_id.to_string(),\n            })?;\n\n        let mut errors = Vec::new();\n\n        if let Err(e) = cmanager.remove() {\n            tracing::error!(error = ?e, \"failed to remove cgroup manager\");\n            errors.push(e.to_string());\n        }\n\n        if let Some(container) = &self.container {\n            if let Some(true) = container.clean_up_intel_rdt_subdirectory() {\n                if let Err(e) = delete_resctrl_subdirectory(container.id()) {\n                    tracing::error!(id = ?container.id(), error = ?e, \"failed to delete resctrl subdirectory\");\n                    errors.push(e.to_string());\n                }\n            }\n\n            if container.root.exists() {\n                if let Err(e) = fs::remove_dir_all(&container.root) {\n                    tracing::error!(container_root = ?container.root, error = ?e, \"failed to delete container root\");\n                    errors.push(e.to_string());\n                }\n            }\n        }\n\n        if !errors.is_empty() {\n            return Err(LibcontainerError::Other(format!(\n                \"failed to cleanup container: {}\",\n                errors.join(\";\")\n            )));\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container.rs",
    "content": "use std::collections::HashMap;\nuse std::ffi::OsString;\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nuse chrono::{DateTime, Utc};\nuse nix::unistd::Pid;\nuse procfs::process::Process;\n\nuse crate::config::YoukiConfig;\nuse crate::container::{ContainerStatus, State};\nuse crate::error::LibcontainerError;\nuse crate::syscall::syscall::create_syscall;\n\n/// Structure representing the container data\n#[derive(Debug, Clone)]\npub struct Container {\n    // State of the container\n    pub state: State,\n    // indicated the directory for the root path in the container\n    pub root: PathBuf,\n}\n\nimpl Default for Container {\n    fn default() -> Self {\n        Self {\n            state: State::default(),\n            root: PathBuf::from(\"/run/youki\"),\n        }\n    }\n}\n\nimpl Container {\n    pub fn new(\n        container_id: &str,\n        status: ContainerStatus,\n        pid: Option<i32>,\n        bundle: &Path,\n        container_root: &Path,\n    ) -> Result<Self, LibcontainerError> {\n        let container_root = fs::canonicalize(container_root).map_err(|err| {\n            LibcontainerError::InvalidInput(format!(\n                \"invalid container root {container_root:?}: {err:?}\"\n            ))\n        })?;\n        let bundle = fs::canonicalize(bundle).map_err(|err| {\n            LibcontainerError::InvalidInput(format!(\"invalid bundle {bundle:?}: {err:?}\"))\n        })?;\n        let state = State::new(container_id, status, pid, bundle);\n\n        Ok(Self {\n            state,\n            root: container_root,\n        })\n    }\n\n    pub fn id(&self) -> &str {\n        &self.state.id\n    }\n\n    pub fn can_start(&self) -> bool {\n        self.state.status.can_start()\n    }\n\n    pub fn can_kill(&self) -> bool {\n        self.state.status.can_kill()\n    }\n\n    pub fn can_delete(&self) -> bool {\n        self.state.status.can_delete()\n    }\n\n    pub fn can_exec(&self) -> bool {\n        self.state.status == ContainerStatus::Running\n    }\n\n    pub fn can_pause(&self) -> bool {\n        self.state.status.can_pause()\n    }\n\n    pub fn can_resume(&self) -> bool {\n        self.state.status.can_resume()\n    }\n\n    pub fn bundle(&self) -> &PathBuf {\n        &self.state.bundle\n    }\n\n    pub fn set_annotations(&mut self, annotations: Option<HashMap<String, String>>) -> &mut Self {\n        self.state.annotations = annotations;\n        self\n    }\n\n    pub fn pid(&self) -> Option<Pid> {\n        self.state.pid.map(Pid::from_raw)\n    }\n\n    pub fn set_pid(&mut self, pid: i32) -> &mut Self {\n        self.state.pid = Some(pid);\n        self\n    }\n\n    pub fn created(&self) -> Option<DateTime<Utc>> {\n        self.state.created\n    }\n\n    pub fn creator(&self) -> Option<OsString> {\n        if let Some(uid) = self.state.creator {\n            let command = create_syscall();\n            let user_name = command.get_pwuid(uid);\n            if let Some(user_name) = user_name {\n                return Some((*user_name).to_owned());\n            }\n        }\n\n        None\n    }\n\n    pub fn set_creator(&mut self, uid: u32) -> &mut Self {\n        self.state.creator = Some(uid);\n        self\n    }\n\n    pub fn systemd(&self) -> bool {\n        self.state.use_systemd\n    }\n\n    pub fn set_systemd(&mut self, should_use: bool) -> &mut Self {\n        self.state.use_systemd = should_use;\n        self\n    }\n\n    pub fn set_clean_up_intel_rdt_directory(&mut self, clean_up: bool) -> &mut Self {\n        self.state.clean_up_intel_rdt_subdirectory = Some(clean_up);\n        self\n    }\n\n    pub fn clean_up_intel_rdt_subdirectory(&self) -> Option<bool> {\n        self.state.clean_up_intel_rdt_subdirectory\n    }\n\n    pub fn status(&self) -> ContainerStatus {\n        self.state.status\n    }\n\n    pub fn set_status(&mut self, status: ContainerStatus) -> &mut Self {\n        let created = match (status, self.state.created) {\n            (ContainerStatus::Created, None) => Some(Utc::now()),\n            _ => self.state.created,\n        };\n\n        self.state.created = created;\n        self.state.status = status;\n\n        self\n    }\n\n    pub fn refresh_status(&mut self) -> Result<(), LibcontainerError> {\n        let new_status = match self.pid() {\n            Some(pid) => {\n                // Note that Process::new does not spawn a new process\n                // but instead creates a new Process structure, and fill\n                // it with information about the process with given pid\n                if let Ok(proc) = Process::new(pid.as_raw()) {\n                    use procfs::process::ProcState;\n\n                    match proc.stat()?.state()? {\n                        ProcState::Zombie | ProcState::Dead => ContainerStatus::Stopped,\n                        _ => match self.status() {\n                            ContainerStatus::Creating\n                            | ContainerStatus::Created\n                            | ContainerStatus::Paused => self.status(),\n                            _ => ContainerStatus::Running,\n                        },\n                    }\n                } else {\n                    ContainerStatus::Stopped\n                }\n            }\n            None => ContainerStatus::Stopped,\n        };\n\n        self.set_status(new_status);\n        Ok(())\n    }\n\n    pub fn refresh_state(&mut self) -> Result<&mut Self, LibcontainerError> {\n        let state = State::load(&self.root)?;\n        self.state = state;\n\n        Ok(self)\n    }\n\n    pub fn load(container_root: PathBuf) -> Result<Self, LibcontainerError> {\n        let state = State::load(&container_root)?;\n        let mut container = Self {\n            state,\n            root: container_root,\n        };\n        container.refresh_status()?;\n        Ok(container)\n    }\n\n    pub fn save(&self) -> Result<(), LibcontainerError> {\n        tracing::debug!(\"Save container status: {:?} in {:?}\", self, self.root);\n        self.state.save(&self.root)?;\n\n        Ok(())\n    }\n\n    pub fn spec(&self) -> Result<YoukiConfig, LibcontainerError> {\n        let spec = YoukiConfig::load(&self.root)?;\n        Ok(spec)\n    }\n}\n\n/// Checkpoint parameter structure\npub struct CheckpointOptions {\n    pub ext_unix_sk: bool,\n    pub file_locks: bool,\n    pub image_path: PathBuf,\n    pub leave_running: bool,\n    pub shell_job: bool,\n    pub tcp_established: bool,\n    pub work_path: Option<PathBuf>,\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n    use serial_test::serial;\n\n    use super::*;\n\n    #[test]\n    fn test_get_set_pid() {\n        let mut container = Container::default();\n\n        assert_eq!(container.pid(), None);\n        container.set_pid(1);\n        assert_eq!(container.pid(), Some(Pid::from_raw(1)));\n    }\n\n    #[test]\n    fn test_basic_getter() -> Result<()> {\n        let mut container = Container::new(\n            \"container_id\",\n            ContainerStatus::Creating,\n            None,\n            &PathBuf::from(\".\"),\n            &PathBuf::from(\".\"),\n        )?;\n\n        // testing id\n        assert_eq!(container.id(), \"container_id\");\n        // testing bundle path\n        assert_eq!(\n            container.bundle(),\n            &fs::canonicalize(PathBuf::from(\".\")).unwrap()\n        );\n        // testing root path\n        assert_eq!(container.root, fs::canonicalize(PathBuf::from(\".\"))?);\n        // testing created\n        assert_eq!(container.created(), None);\n        container.set_status(ContainerStatus::Created);\n        assert!(container.created().is_some());\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_set_annotations() {\n        let mut container = Container::default();\n        assert_eq!(container.state.annotations, None);\n\n        let mut annotations = std::collections::HashMap::with_capacity(1);\n        annotations.insert(\n            \"org.criu.config\".to_string(),\n            \"/etc/special-youki-criu-options\".to_string(),\n        );\n        container.set_annotations(Some(annotations.clone()));\n        assert_eq!(container.state.annotations, Some(annotations));\n    }\n\n    #[test]\n    fn test_get_set_systemd() {\n        let mut container = Container::default();\n        assert!(!container.systemd());\n        container.set_systemd(true);\n        assert!(container.systemd());\n        container.set_systemd(false);\n        assert!(!container.systemd());\n    }\n\n    #[test]\n    fn test_get_set_creator() {\n        let mut container = Container::default();\n        assert_eq!(container.creator(), None);\n        container.set_creator(1000);\n        assert_eq!(container.creator(), Some(OsString::from(\"youki\")));\n    }\n\n    #[test]\n    #[serial]\n    fn test_refresh_load_save_state() -> Result<()> {\n        let tmp_dir = tempfile::tempdir().unwrap();\n        let mut container_1 = Container::new(\n            \"container_id_1\",\n            ContainerStatus::Created,\n            None,\n            &PathBuf::from(\".\"),\n            tmp_dir.path(),\n        )?;\n\n        container_1.save()?;\n        let container_2 = Container::load(tmp_dir.path().to_path_buf())?;\n        assert_eq!(container_1.state.id, container_2.state.id);\n        assert_eq!(container_2.state.status, ContainerStatus::Stopped);\n\n        container_1.state.id = \"container_id_1_modified\".to_string();\n        container_1.save()?;\n        container_1.refresh_state()?;\n        assert_eq!(container_1.state.id, \"container_id_1_modified\".to_string());\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_get_spec() -> Result<()> {\n        let tmp_dir = tempfile::tempdir().unwrap();\n        use oci_spec::runtime::Spec;\n        let spec = Spec::default();\n        let config = YoukiConfig::from_spec(&spec, \"123\").context(\"convert spec to config\")?;\n        config.save(tmp_dir.path()).context(\"save config\")?;\n\n        let container = Container {\n            root: tmp_dir.path().to_path_buf(),\n            ..Default::default()\n        };\n        container.spec().context(\"get config\")?;\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_get_set_refresh_status() -> Result<()> {\n        // there already has a full and well-tested flow of status in state.rs\n        // so we just let the coverage run through those can_xxx functions.\n        let mut container = Container::default();\n        assert_eq!(container.status(), ContainerStatus::Creating);\n        assert!(!container.can_start());\n        assert!(!container.can_kill());\n        assert!(!container.can_delete());\n        assert!(!container.can_exec());\n        assert!(!container.can_pause());\n        assert!(!container.can_resume());\n\n        // no PID case\n        container.refresh_status()?;\n        assert_eq!(container.status(), ContainerStatus::Stopped);\n\n        // with PID case but PID not exists\n        container.set_pid(-1);\n        container.refresh_status()?;\n        assert_eq!(container.status(), ContainerStatus::Stopped);\n\n        // with PID case\n        container.set_pid(1);\n        container.set_status(ContainerStatus::Paused);\n        container.refresh_status()?;\n        assert_eq!(container.status(), ContainerStatus::Paused);\n        container.set_status(ContainerStatus::Running);\n        container.refresh_status()?;\n        assert_eq!(container.status(), ContainerStatus::Running);\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_checkpoint.rs",
    "content": "use std::fs::{DirBuilder, File, read_link};\nuse std::io::{ErrorKind, Write};\nuse std::os::unix::fs::DirBuilderExt;\nuse std::os::unix::io::AsRawFd;\n\nuse libcgroups::common::CgroupSetup::{Hybrid, Legacy};\n#[cfg(feature = \"v1\")]\nuse libcgroups::common::DEFAULT_CGROUP_ROOT;\nuse oci_spec::runtime::Spec;\n\nuse super::container_criu::{CRIU_VERSION_MINIMUM, check_criu_version};\nuse super::{Container, ContainerStatus};\nuse crate::container::container::CheckpointOptions;\nuse crate::error::LibcontainerError;\n\nconst CRIU_CHECKPOINT_LOG_FILE: &str = \"dump.log\";\nconst DESCRIPTORS_JSON: &str = \"descriptors.json\";\n\n#[derive(thiserror::Error, Debug)]\npub enum CheckpointError {\n    #[error(\"criu error: {0}\")]\n    CriuError(String),\n}\n\nimpl Container {\n    pub fn checkpoint(&mut self, opts: &CheckpointOptions) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n\n        // can_pause() checks if the container is running. That also works for\n        // checkpointing. is_running() would make more sense here, but let's\n        // just reuse existing functions.\n        if !self.can_pause() {\n            tracing::error!(status = ?self.status(), id = ?self.id(), \"cannot checkpoint container because it is not running\");\n            return Err(LibcontainerError::IncorrectStatus(self.status()));\n        }\n\n        // Require CRIU >= 3.15.0, matching crun's LIBCRIU_MIN_VERSION requirement.\n        check_criu_version(CRIU_VERSION_MINIMUM)?;\n\n        // Create checkpoint image directory if it doesn't exist (mode 0o700 like crun).\n        if let Err(err) = DirBuilder::new().mode(0o700).create(&opts.image_path) {\n            if err.kind() != ErrorKind::AlreadyExists {\n                tracing::error!(path = ?opts.image_path, ?err, \"failed to create checkpoint directory\");\n                return Err(LibcontainerError::OtherIO(err));\n            }\n        }\n\n        let mut criu = rust_criu::Criu::new().map_err(|e| {\n            LibcontainerError::Checkpoint(CheckpointError::CriuError(format!(\n                \"error in creating criu struct: {}\",\n                e\n            )))\n        })?;\n        // We need to tell CRIU that all bind mounts are external. CRIU will fail checkpointing\n        // if it does not know that these bind mounts are coming from the outside of the container.\n        // This information is needed during restore again. The external location of the bind\n        // mounts can change and CRIU will just mount whatever we tell it to mount based on\n        // information found in 'config.json'.\n        let source_spec_path = self.bundle().join(\"config.json\");\n        let spec = Spec::load(source_spec_path)?;\n        let mounts = spec.mounts().clone();\n        for m in mounts.unwrap_or_default() {\n            match m.typ().as_deref() {\n                Some(\"bind\") => {\n                    let dest = m\n                        .destination()\n                        .clone()\n                        .into_os_string()\n                        .into_string()\n                        .expect(\"failed to convert mount destination\");\n                    criu.set_external_mount(dest.clone(), dest);\n                }\n                Some(\"cgroup\") => {\n                    match libcgroups::common::get_cgroup_setup()? {\n                        // For v1 it is necessary to list all cgroup mounts as external mounts\n                        Legacy | Hybrid => {\n                            #[cfg(not(feature = \"v1\"))]\n                            panic!(\n                                \"libcontainer can't run in a Legacy or Hybrid cgroup setup without the v1 feature\"\n                            );\n                            #[cfg(feature = \"v1\")]\n                            for mp in libcgroups::v1::util::list_subsystem_mount_points().map_err(\n                                |err| {\n                                    tracing::error!(?err, \"failed to get subsystem mount points\");\n                                    LibcontainerError::OtherCgroup(err.to_string())\n                                },\n                            )? {\n                                let cgroup_mount = mp\n                                    .clone()\n                                    .into_os_string()\n                                    .into_string()\n                                    .expect(\"failed to convert mount point\");\n                                if cgroup_mount.starts_with(DEFAULT_CGROUP_ROOT) {\n                                    criu.set_external_mount(cgroup_mount.clone(), cgroup_mount);\n                                }\n                            }\n                        }\n                        _ => (),\n                    }\n                }\n                _ => (),\n            }\n        }\n\n        let directory = File::open(&opts.image_path).map_err(|err| {\n            tracing::error!(path = ?opts.image_path, ?err, \"failed to open checkpoint directory\");\n            LibcontainerError::OtherIO(err)\n        })?;\n        criu.set_images_dir_fd(directory.as_raw_fd());\n\n        // It seems to be necessary to be defined outside of 'if' to\n        // keep the FD open until CRIU uses it.\n        let work_dir: File;\n        if let Some(wp) = &opts.work_path {\n            // Create work directory if it doesn't exist (mode 0o700 like crun).\n            if let Err(err) = DirBuilder::new().mode(0o700).create(wp) {\n                if err.kind() != ErrorKind::AlreadyExists {\n                    tracing::error!(path = ?wp, ?err, \"failed to create work directory\");\n                    return Err(LibcontainerError::OtherIO(err));\n                }\n            }\n            work_dir = File::open(wp).map_err(LibcontainerError::OtherIO)?;\n            criu.set_work_dir_fd(work_dir.as_raw_fd());\n        }\n\n        let pid: i32 = self\n            .pid()\n            .ok_or(LibcontainerError::Other(\n                \"container process pid not found in state\".into(),\n            ))?\n            .into();\n\n        // Remember original stdin, stdout, stderr for container restore.\n        let mut descriptors = Vec::new();\n        for n in 0..3 {\n            let link_path = match read_link(format!(\"/proc/{pid}/fd/{n}\")) {\n                // it should not have any non utf-8 or non os safe path,\n                // as we are reading from os , so ok to unwrap\n                Ok(lp) => lp.into_os_string().into_string().unwrap(),\n                Err(..) => \"/dev/null\".to_string(),\n            };\n            descriptors.push(link_path);\n        }\n        let descriptors_json_path = opts.image_path.join(DESCRIPTORS_JSON);\n        let mut descriptors_json =\n            File::create(descriptors_json_path).map_err(LibcontainerError::OtherIO)?;\n        write!(\n            descriptors_json,\n            \"{}\",\n            serde_json::to_string(&descriptors).map_err(LibcontainerError::OtherSerialization)?\n        )\n        .map_err(LibcontainerError::OtherIO)?;\n\n        criu.set_log_file(CRIU_CHECKPOINT_LOG_FILE.to_string());\n        criu.set_log_level(4);\n        criu.set_pid(pid);\n        criu.set_leave_running(opts.leave_running);\n        criu.set_ext_unix_sk(opts.ext_unix_sk);\n        criu.set_shell_job(opts.shell_job);\n        criu.set_tcp_established(opts.tcp_established);\n        criu.set_file_locks(opts.file_locks);\n        criu.set_orphan_pts_master(true);\n        criu.set_manage_cgroups(true);\n        criu.set_root(\n            self.bundle()\n                .clone()\n                .into_os_string()\n                .into_string()\n                .unwrap(),\n        );\n\n        criu.dump().map_err(|err| {\n            tracing::error!(?err, id = ?self.id(), logfile = ?opts.image_path.join(CRIU_CHECKPOINT_LOG_FILE), \"checkpointing container failed\");\n            LibcontainerError::Other(err.to_string())\n        })?;\n\n        if !opts.leave_running {\n            self.set_status(ContainerStatus::Stopped).save()?;\n        }\n\n        tracing::debug!(\"container {} checkpointed\", self.id());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_criu.rs",
    "content": "//! Common CRIU utilities for checkpoint and restore operations.\n//!\n//! This module provides shared functionality between container checkpoint and restore,\n//! following the patterns established by runc's CRIU integration.\n\nuse crate::error::LibcontainerError;\n\n/// Minimum CRIU version required for checkpoint/restore functionality.\n/// This matches crun's LIBCRIU_MIN_VERSION requirement (3.15.0).\n/// Version format: MAJOR * 10000 + MINOR * 100 + PATCH\npub const CRIU_VERSION_MINIMUM: u32 = 31500; // 3.15.0\n\nfn compare_criu_version(version: u32, min_version: u32) -> Result<(), LibcontainerError> {\n    if version < min_version {\n        return Err(LibcontainerError::Other(format!(\n            \"CRIU version {} is below minimum required version {}\",\n            version, min_version,\n        )));\n    }\n    Ok(())\n}\n\n/// Check if CRIU version is greater than or equal to min_version.\npub fn check_criu_version(min_version: u32) -> Result<(), LibcontainerError> {\n    let mut criu = rust_criu::Criu::new()\n        .map_err(|e| LibcontainerError::Other(format!(\"failed to create CRIU instance: {}\", e)))?;\n\n    let version = criu\n        .get_criu_version()\n        .map_err(|e| LibcontainerError::Other(format!(\"CRIU version check failed: {}\", e)))?;\n\n    compare_criu_version(version, min_version)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_compare_criu_version_ok() {\n        assert!(compare_criu_version(31500, 31500).is_ok());\n        assert!(compare_criu_version(31600, 31500).is_ok());\n        assert!(compare_criu_version(40000, 31500).is_ok());\n    }\n\n    #[test]\n    fn test_compare_criu_version_too_low() {\n        assert!(compare_criu_version(31499, 31500).is_err());\n        assert!(compare_criu_version(30000, 31500).is_err());\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_delete.rs",
    "content": "use std::fs;\n\nuse libcgroups::common::CgroupManager;\nuse libcgroups::{self};\nuse nix::sys::signal;\n\nuse super::{Container, ContainerStatus};\nuse crate::config::YoukiConfig;\nuse crate::error::LibcontainerError;\nuse crate::hooks;\nuse crate::process::intel_rdt::delete_resctrl_subdirectory;\n\nimpl Container {\n    /// Deletes the container\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.delete(true)?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn delete(&mut self, force: bool) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n\n        tracing::debug!(\"container status: {:?}\", self.status());\n\n        // Check if container is allowed to be deleted based on container status.\n        match self.status() {\n            ContainerStatus::Stopped => {}\n            ContainerStatus::Created => {\n                // Here, we differ from the OCI spec, but matches the same\n                // behavior as `runc` and `crun`. The OCI spec does not allow\n                // deletion of status `created` without `force` flag. But both\n                // `runc` and `crun` allows deleting `created`. Therefore we\n                // decided to follow `runc` and `crun`.\n                self.do_kill(signal::Signal::SIGKILL, true)?;\n                self.set_status(ContainerStatus::Stopped).save()?;\n            }\n            ContainerStatus::Creating | ContainerStatus::Running | ContainerStatus::Paused => {\n                // Containers can't be deleted while in these status, unless\n                // force flag is set. In the force case, we need to clean up any\n                // processes associated with containers.\n                if force {\n                    self.do_kill(signal::Signal::SIGKILL, true)?;\n                    self.set_status(ContainerStatus::Stopped).save()?;\n                } else {\n                    tracing::error!(\n                        id = ?self.id(),\n                        status = ?self.status(),\n                        \"delete requires the container state to be stopped or created\",\n                    );\n                    return Err(LibcontainerError::IncorrectStatus(self.status()));\n                }\n            }\n        }\n\n        // Once reached here, the container is verified that it can be deleted.\n        debug_assert!(self.status().can_delete());\n\n        if let Some(true) = &self.clean_up_intel_rdt_subdirectory() {\n            if let Err(err) = delete_resctrl_subdirectory(self.id()) {\n                tracing::warn!(\n                    \"failed to delete resctrl subdirectory due to: {err:?}, continue to delete\"\n                );\n            }\n        }\n\n        if self.root.exists() {\n            match YoukiConfig::load(&self.root) {\n                Ok(config) => {\n                    tracing::debug!(\"config: {:?}\", config);\n\n                    // remove the cgroup created for the container\n                    // check https://man7.org/linux/man-pages/man7/cgroups.7.html\n                    // creating and removing cgroups section for more information on cgroups\n                    let cmanager = libcgroups::common::create_cgroup_manager(\n                        libcgroups::common::CgroupConfig {\n                            cgroup_path: config.cgroup_path.to_owned(),\n                            systemd_cgroup: self.systemd(),\n                            container_name: self.id().to_string(),\n                        },\n                    )?;\n                    cmanager.remove().map_err(|err| {\n                        tracing::error!(cgroup_path = ?config.cgroup_path, \"failed to remove cgroup due to: {err:?}\");\n                        err\n                    })?;\n\n                    if let Some(hooks) = config.hooks.as_ref() {\n                        hooks::run_hooks(hooks.poststop().as_ref(), Some(&self.state), None, None)\n                            .map_err(|err| {\n                                tracing::error!(err = ?err, \"failed to run post stop hooks\");\n                                err\n                            })?;\n                    }\n                }\n                Err(err) => {\n                    // There is a brief window where the container state is\n                    // created, but the container config is not yet generated\n                    // from the OCI spec. In this case, we assume as if we\n                    // successfully deleted the config and moving on.\n                    tracing::warn!(\n                        \"skipping loading youki config due to: {err:?}, continue to delete\"\n                    );\n                }\n            }\n\n            // remove the directory storing container state\n            tracing::debug!(\"remove dir {:?}\", self.root);\n            fs::remove_dir_all(&self.root).map_err(|err| {\n                tracing::error!(?err, path = ?self.root, \"failed to remove container dir\");\n                LibcontainerError::OtherIO(err)\n            })?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_events.rs",
    "content": "use std::thread;\nuse std::time::Duration;\n\nuse libcgroups::common::CgroupManager;\n\nuse super::{Container, ContainerStatus};\nuse crate::error::LibcontainerError;\n\nimpl Container {\n    /// Displays container events\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.events(5000, false)?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn events(&mut self, interval: u32, stats: bool) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n        if !self.state.status.eq(&ContainerStatus::Running) {\n            tracing::error!(id = ?self.id(), status = ?self.state.status, \"container is not running\");\n            return Err(LibcontainerError::IncorrectStatus(self.status()));\n        }\n\n        let cgroup_manager =\n            libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {\n                cgroup_path: self.spec()?.cgroup_path,\n                systemd_cgroup: self.systemd(),\n                container_name: self.id().to_string(),\n            })?;\n        match stats {\n            true => {\n                let stats = cgroup_manager.stats()?;\n                println!(\n                    \"{}\",\n                    serde_json::to_string_pretty(&stats)\n                        .map_err(LibcontainerError::OtherSerialization)?\n                );\n            }\n            false => loop {\n                let stats = cgroup_manager.stats()?;\n                println!(\n                    \"{}\",\n                    serde_json::to_string_pretty(&stats)\n                        .map_err(LibcontainerError::OtherSerialization)?\n                );\n                thread::sleep(Duration::from_secs(interval as u64));\n            },\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_kill.rs",
    "content": "use libcgroups::common::{CgroupManager, get_cgroup_setup};\nuse nix::sys::signal::{self};\n\nuse super::{Container, ContainerStatus};\nuse crate::error::LibcontainerError;\nuse crate::signal::Signal;\n\nimpl Container {\n    /// Sends the specified signal to the container init process\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    /// use nix::sys::signal::Signal;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.kill(Signal::SIGKILL, false)?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn kill<S: Into<Signal>>(&mut self, signal: S, all: bool) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n        match self.can_kill() {\n            true => {\n                self.do_kill(signal, all)?;\n            }\n            false if all && self.status() == ContainerStatus::Stopped => {\n                self.do_kill(signal, all)?;\n            }\n            false => {\n                tracing::error!(id = ?self.id(), status = ?self.status(), \"cannot kill container due to incorrect state\");\n                return Err(LibcontainerError::IncorrectStatus(self.status()));\n            }\n        }\n        self.set_status(ContainerStatus::Stopped).save()?;\n        Ok(())\n    }\n\n    pub(crate) fn do_kill<S: Into<Signal>>(\n        &self,\n        signal: S,\n        all: bool,\n    ) -> Result<(), LibcontainerError> {\n        if all {\n            self.kill_all_processes(signal)\n        } else {\n            self.kill_one_process(signal)\n        }\n    }\n\n    fn kill_one_process<S: Into<Signal>>(&self, signal: S) -> Result<(), LibcontainerError> {\n        let signal = signal.into().into_raw();\n        let pid = self.pid().ok_or(LibcontainerError::Other(\n            \"container process pid not found in state\".into(),\n        ))?;\n\n        tracing::debug!(\"kill signal {} to {}\", signal, pid);\n\n        match signal::kill(pid, signal) {\n            Ok(_) => {}\n            Err(nix::errno::Errno::ESRCH) => {\n                // the process does not exist, which is what we want\n            }\n            Err(err) => {\n                tracing::error!(id = ?self.id(), err = ?err, ?pid, ?signal, \"failed to kill process\");\n                return Err(LibcontainerError::OtherSyscall(err));\n            }\n        }\n\n        // For cgroup V1, a frozon process cannot respond to signals,\n        // so we need to thaw it. Only thaw the cgroup for SIGKILL.\n        if self.status() == ContainerStatus::Paused && signal == signal::Signal::SIGKILL {\n            match get_cgroup_setup()? {\n                libcgroups::common::CgroupSetup::Legacy\n                | libcgroups::common::CgroupSetup::Hybrid => {\n                    let cmanager = libcgroups::common::create_cgroup_manager(\n                        libcgroups::common::CgroupConfig {\n                            cgroup_path: self.spec()?.cgroup_path,\n                            systemd_cgroup: self.systemd(),\n                            container_name: self.id().to_string(),\n                        },\n                    )?;\n                    cmanager.freeze(libcgroups::common::FreezerState::Thawed)?;\n                }\n                libcgroups::common::CgroupSetup::Unified => {}\n            }\n        }\n        Ok(())\n    }\n\n    fn kill_all_processes<S: Into<Signal>>(&self, signal: S) -> Result<(), LibcontainerError> {\n        let signal = signal.into().into_raw();\n        let cmanager =\n            libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {\n                cgroup_path: self.spec()?.cgroup_path,\n                systemd_cgroup: self.systemd(),\n                container_name: self.id().to_string(),\n            })?;\n\n        if let Err(e) = cmanager.freeze(libcgroups::common::FreezerState::Frozen) {\n            tracing::warn!(\n                err = ?e,\n                id = ?self.id(),\n                \"failed to freeze container\",\n            );\n        }\n\n        let pids = cmanager.get_all_pids()?;\n        pids.iter()\n            .try_for_each(|&pid| {\n                tracing::debug!(\"kill signal {} to {}\", signal, pid);\n                let res = signal::kill(pid, signal);\n                match res {\n                    Err(nix::errno::Errno::ESRCH) => {\n                        // the process does not exist, which is what we want\n                        Ok(())\n                    }\n                    _ => res,\n                }\n            })\n            .map_err(LibcontainerError::OtherSyscall)?;\n        if let Err(err) = cmanager.freeze(libcgroups::common::FreezerState::Thawed) {\n            tracing::warn!(\n                err = ?err,\n                id = ?self.id(),\n                \"failed to thaw container\",\n            );\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_pause.rs",
    "content": "use libcgroups::common::{CgroupManager, FreezerState};\n\nuse super::{Container, ContainerStatus};\nuse crate::error::LibcontainerError;\n\nimpl Container {\n    /// Suspends all processes within the container\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.pause()?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn pause(&mut self) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n\n        if !self.can_pause() {\n            tracing::error!(status = ?self.status(), id = ?self.id(), \"cannot pause container\");\n            return Err(LibcontainerError::IncorrectStatus(self.status()));\n        }\n\n        let cmanager =\n            libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {\n                cgroup_path: self.spec()?.cgroup_path,\n                systemd_cgroup: self.systemd(),\n                container_name: self.id().to_string(),\n            })?;\n        cmanager.freeze(FreezerState::Frozen)?;\n\n        tracing::debug!(\"saving paused status\");\n        self.set_status(ContainerStatus::Paused).save()?;\n\n        tracing::debug!(\"container {} paused\", self.id());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_resume.rs",
    "content": "use libcgroups::common::{CgroupManager, FreezerState};\n\nuse super::{Container, ContainerStatus};\nuse crate::error::LibcontainerError;\n\nimpl Container {\n    /// Resumes all processes within the container\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.resume()?;\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn resume(&mut self) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n        // check if container can be resumed :\n        // for example, a running process cannot be resumed\n        if !self.can_resume() {\n            tracing::error!(status = ?self.status(), id = ?self.id(), \"cannot resume container\");\n            return Err(LibcontainerError::IncorrectStatus(self.status()));\n        }\n\n        let cmanager =\n            libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig {\n                cgroup_path: self.spec()?.cgroup_path,\n                systemd_cgroup: self.systemd(),\n                container_name: self.id().to_string(),\n            })?;\n        // resume the frozen container\n        cmanager.freeze(FreezerState::Thawed)?;\n\n        tracing::debug!(\"saving running status\");\n        self.set_status(ContainerStatus::Running).save()?;\n\n        tracing::debug!(\"container {} resumed\", self.id());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/container_start.rs",
    "content": "use super::{Container, ContainerStatus};\nuse crate::config::YoukiConfig;\nuse crate::error::LibcontainerError;\nuse crate::hooks;\nuse crate::notify_socket::{NOTIFY_FILE, NotifySocket};\n\nimpl Container {\n    /// Starts a previously created container\n    ///\n    /// # Example\n    ///\n    /// ```no_run\n    /// use libcontainer::container::builder::ContainerBuilder;\n    /// use libcontainer::syscall::syscall::SyscallType;\n    ///\n    /// # fn main() -> anyhow::Result<()> {\n    /// let mut container = ContainerBuilder::new(\n    ///     \"74f1a4cb3801\".to_owned(),\n    ///     SyscallType::default(),\n    /// )\n    /// .as_init(\"/var/run/docker/bundle\")\n    /// .build()?;\n    ///\n    /// container.start();\n    /// # Ok(())\n    /// # }\n    /// ```\n    pub fn start(&mut self) -> Result<(), LibcontainerError> {\n        self.refresh_status()?;\n\n        if !self.can_start() {\n            tracing::error!(status = ?self.status(), id = ?self.id(), \"cannot start container due to incorrect state\");\n            return Err(LibcontainerError::IncorrectStatus(self.status()));\n        }\n\n        let config = YoukiConfig::load(&self.root).map_err(|err| {\n            tracing::error!(\n                \"failed to load runtime spec for container {}: {}\",\n                self.id(),\n                err\n            );\n            err\n        })?;\n\n        let mut notify_socket = NotifySocket::new(self.root.join(NOTIFY_FILE));\n        notify_socket.notify_container_start()?;\n        self.set_status(ContainerStatus::Running)\n            .save()\n            .map_err(|err| {\n                tracing::error!(id = ?self.id(), ?err, \"failed to save state for container\");\n                err\n            })?;\n\n        // Run post start hooks. It runs after the container process is started.\n        // It is called in the runtime namespace.\n        if let Some(hooks) = config.hooks.as_ref() {\n            hooks::run_hooks(\n                hooks.poststart().as_ref(),\n                Some(&self.state),\n                Some(&self.root),\n                None,\n            )\n            .map_err(|err| {\n                tracing::error!(\"failed to run post start hooks: {}\", err);\n                err\n            })?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/init_builder.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\nuse std::rc::Rc;\n\nuse oci_spec::runtime::Spec;\nuse user_ns::UserNamespaceConfig;\n\nuse super::builder::ContainerBuilder;\nuse super::builder_impl::ContainerBuilderImpl;\nuse super::{Container, ContainerStatus};\nuse crate::config::YoukiConfig;\nuse crate::error::{ErrInvalidSpec, LibcontainerError, MissingSpecError};\nuse crate::notify_socket::NOTIFY_FILE;\nuse crate::process::args::ContainerType;\nuse crate::syscall::syscall::create_syscall;\nuse crate::{apparmor, tty, user_ns, utils};\n\n// Builder that can be used to configure the properties of a new container\npub struct InitContainerBuilder {\n    base: ContainerBuilder,\n    bundle: PathBuf,\n    use_systemd: bool,\n    detached: bool,\n    no_pivot: bool,\n    as_sibling: bool,\n}\n\nimpl InitContainerBuilder {\n    /// Generates the base configuration for a new container from which\n    /// configuration methods can be chained\n    pub(super) fn new(builder: ContainerBuilder, bundle: PathBuf) -> Self {\n        Self {\n            base: builder,\n            bundle,\n            use_systemd: true,\n            detached: true,\n            no_pivot: false,\n            as_sibling: false,\n        }\n    }\n\n    /// Sets if systemd should be used for managing cgroups\n    pub fn with_systemd(mut self, should_use: bool) -> Self {\n        self.use_systemd = should_use;\n        self\n    }\n\n    /// Sets if the init process should be run as a child or a sibling of\n    /// the calling process\n    pub fn as_sibling(mut self, as_sibling: bool) -> Self {\n        self.as_sibling = as_sibling;\n        self\n    }\n\n    pub fn with_detach(mut self, detached: bool) -> Self {\n        self.detached = detached;\n        self\n    }\n\n    pub fn with_no_pivot(mut self, no_pivot: bool) -> Self {\n        self.no_pivot = no_pivot;\n        self\n    }\n\n    /// Creates a new container\n    pub fn build(self) -> Result<Container, LibcontainerError> {\n        let spec = self.load_spec()?;\n        let container_dir = self.create_container_dir()?;\n\n        let mut container = self.create_container_state(&container_dir)?;\n        container\n            .set_systemd(self.use_systemd)\n            .set_annotations(spec.annotations().clone());\n\n        let notify_path = container_dir.join(NOTIFY_FILE);\n        // convert path of root file system of the container to absolute path\n        let rootfs = fs::canonicalize(spec.root().as_ref().ok_or(MissingSpecError::Root)?.path())\n            .map_err(LibcontainerError::OtherIO)?;\n\n        // if socket file path is given in commandline options,\n        // get file descriptors of console socket\n        let csocketfd = if let Some(console_socket) = &self.base.console_socket {\n            Some(tty::setup_console_socket(\n                &container_dir,\n                console_socket,\n                \"console-socket\",\n            )?)\n        } else {\n            None\n        };\n\n        let user_ns_config = UserNamespaceConfig::new(&spec)?;\n\n        let config = YoukiConfig::from_spec(&spec, container.id())?;\n        config.save(&container_dir).map_err(|err| {\n            tracing::error!(?container_dir, \"failed to save config: {}\", err);\n            err\n        })?;\n\n        let mut builder_impl = ContainerBuilderImpl {\n            container_type: ContainerType::InitContainer,\n            syscall: self.base.syscall,\n            container_id: self.base.container_id,\n            pid_file: self.base.pid_file,\n            console_socket: csocketfd,\n            use_systemd: self.use_systemd,\n            spec: Rc::new(spec),\n            rootfs,\n            user_ns_config,\n            notify_path,\n            container: Some(container.clone()),\n            preserve_fds: self.base.preserve_fds,\n            detached: self.detached,\n            executor: self.base.executor,\n            no_pivot: self.no_pivot,\n            stdin: self.base.stdin,\n            stdout: self.base.stdout,\n            stderr: self.base.stderr,\n            as_sibling: self.as_sibling,\n        };\n\n        builder_impl.create()?;\n\n        container.refresh_state()?;\n\n        Ok(container)\n    }\n\n    fn create_container_dir(&self) -> Result<PathBuf, LibcontainerError> {\n        let container_dir = self.base.root_path.join(&self.base.container_id);\n        tracing::debug!(\"container directory will be {:?}\", container_dir);\n\n        if container_dir.exists() {\n            tracing::error!(id = self.base.container_id, dir = ?container_dir, \"container already exists\");\n            return Err(LibcontainerError::Exist);\n        }\n\n        std::fs::create_dir_all(&container_dir).map_err(|err| {\n            tracing::error!(\n                ?container_dir,\n                \"failed to create container directory: {}\",\n                err\n            );\n            LibcontainerError::OtherIO(err)\n        })?;\n\n        Ok(container_dir)\n    }\n\n    fn load_spec(&self) -> Result<Spec, LibcontainerError> {\n        let source_spec_path = self.bundle.join(\"config.json\");\n        let mut spec = Spec::load(source_spec_path)?;\n        Self::validate_spec(&spec)?;\n\n        spec.canonicalize_rootfs(&self.bundle).map_err(|err| {\n            tracing::error!(bundle = ?self.bundle, \"failed to canonicalize rootfs: {}\", err);\n            err\n        })?;\n\n        Ok(spec)\n    }\n\n    fn validate_spec(spec: &Spec) -> Result<(), LibcontainerError> {\n        let version = spec.version();\n        if !version.starts_with(\"1.\") {\n            tracing::error!(\n                \"runtime spec has incompatible version '{}'. Only 1.X.Y is supported\",\n                spec.version()\n            );\n            Err(ErrInvalidSpec::UnsupportedVersion)?;\n        }\n\n        if let Some(process) = spec.process() {\n            if let Some(profile) = process.apparmor_profile() {\n                let apparmor_is_enabled = apparmor::is_enabled().map_err(|err| {\n                    tracing::error!(?err, \"failed to check if apparmor is enabled\");\n                    LibcontainerError::OtherIO(err)\n                })?;\n                if !apparmor_is_enabled {\n                    tracing::error!(\n                        ?profile,\n                        \"apparmor profile exists in the spec, but apparmor is not activated on this system\"\n                    );\n                    Err(ErrInvalidSpec::AppArmorNotEnabled)?;\n                }\n            }\n\n            if let Some(io_priority) = process.io_priority() {\n                let priority = io_priority.priority();\n                let iop_class_res = serde_json::to_string(&io_priority.class());\n                match iop_class_res {\n                    Ok(iop_class) => {\n                        if !(0..=7).contains(&priority) {\n                            tracing::error!(\n                                ?priority,\n                                \"io priority '{}' not between 0 and 7 (inclusive), class '{}' not in (IO_PRIO_CLASS_RT,IO_PRIO_CLASS_BE,IO_PRIO_CLASS_IDLE)\",\n                                priority,\n                                iop_class\n                            );\n                            Err(ErrInvalidSpec::IoPriority)?;\n                        }\n                    }\n                    Err(e) => {\n                        tracing::error!(?priority, ?e, \"failed to parse io priority class\");\n                        Err(ErrInvalidSpec::IoPriority)?;\n                    }\n                }\n            }\n        }\n\n        if let Some(mounts) = spec.mounts() {\n            utils::validate_mount_options(mounts)?;\n        }\n\n        let syscall = create_syscall();\n        utils::validate_spec_for_new_user_ns(spec, &*syscall)?;\n        utils::validate_spec_for_net_devices(spec, &*syscall)\n            .map_err(LibcontainerError::NetDevicesError)?;\n\n        Ok(())\n    }\n\n    fn create_container_state(&self, container_dir: &Path) -> Result<Container, LibcontainerError> {\n        let container = Container::new(\n            &self.base.container_id,\n            ContainerStatus::Creating,\n            None,\n            &self.bundle,\n            container_dir,\n        )?;\n        container.save()?;\n        Ok(container)\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/mod.rs",
    "content": "//! Container management\n/// This crate is responsible for the creation of containers. It provides a builder that can\n/// be used to configure and create containers. We distinguish between an init container for which\n/// namespaces and cgroups will be created (usually) and a tenant container process that will move\n/// into the existing namespaces and cgroups of the initial container process (e.g. used to implement\n/// the exec command).\npub mod builder;\nmod builder_impl;\n#[allow(clippy::module_inception)]\nmod container;\nmod container_checkpoint;\nmod container_criu;\nmod container_delete;\nmod container_events;\nmod container_kill;\nmod container_pause;\nmod container_resume;\nmod container_start;\npub mod init_builder;\npub mod state;\npub mod tenant_builder;\npub use container::{CheckpointOptions, Container};\npub use container_checkpoint::CheckpointError;\n#[allow(deprecated)]\npub use state::ContainerProcessState;\npub use state::{ContainerStatus, State, StateConversionError};\n"
  },
  {
    "path": "crates/libcontainer/src/container/state.rs",
    "content": "//! Information about status and state of the container\nuse std::collections::HashMap;\nuse std::fmt::Display;\nuse std::fs;\nuse std::fs::File;\nuse std::io::{BufReader, BufWriter, Write};\nuse std::path::{Path, PathBuf};\n\nuse chrono::{DateTime, Utc};\nuse oci_spec::OciSpecError;\nuse oci_spec::runtime::{\n    ContainerState as OciContainerState, State as OciState, StateBuilder as OciStateBuilder,\n    VERSION as OCI_RUNTIME_VERSION,\n};\nuse serde::{Deserialize, Serialize};\nuse tracing::instrument;\n\n/// Indicates status of the container\n#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Default)]\n#[serde(rename_all = \"camelCase\")]\npub enum ContainerStatus {\n    // The container is being created\n    #[default]\n    Creating,\n    // The runtime has finished the create operation\n    Created,\n    // The container process has executed the user-specified program but has not exited\n    Running,\n    // The container process has exited\n    Stopped,\n    // The container process has paused\n    Paused,\n}\n\nimpl ContainerStatus {\n    pub fn can_start(&self) -> bool {\n        matches!(self, ContainerStatus::Created)\n    }\n\n    pub fn can_kill(&self) -> bool {\n        use ContainerStatus::*;\n        match self {\n            Creating | Stopped => false,\n            Created | Running | Paused => true,\n        }\n    }\n\n    pub fn can_delete(&self) -> bool {\n        matches!(self, ContainerStatus::Stopped)\n    }\n\n    pub fn can_pause(&self) -> bool {\n        matches!(self, ContainerStatus::Running)\n    }\n\n    pub fn can_resume(&self) -> bool {\n        matches!(self, ContainerStatus::Paused)\n    }\n}\n\nimpl Display for ContainerStatus {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match *self {\n            Self::Creating => \"Creating\",\n            Self::Created => \"Created\",\n            Self::Running => \"Running\",\n            Self::Stopped => \"Stopped\",\n            Self::Paused => \"Paused\",\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum StateError {\n    #[error(\"failed to open container state file {state_file_path:?}\")]\n    OpenStateFile {\n        state_file_path: PathBuf,\n        source: std::io::Error,\n    },\n    #[error(\"failed to parse container state file {state_file_path:?}\")]\n    ParseStateFile {\n        state_file_path: PathBuf,\n        source: serde_json::Error,\n    },\n    #[error(\"failed to write container state file {state_file_path:?}\")]\n    WriteStateFile {\n        state_file_path: PathBuf,\n        source: std::io::Error,\n    },\n}\n\ntype Result<T> = std::result::Result<T, StateError>;\n\n/// Stores the state information of the container\n#[derive(Serialize, Deserialize, Debug, Clone, Default)]\n#[serde(rename_all = \"camelCase\")]\npub struct State {\n    // Version is the version of the specification that is supported.\n    pub oci_version: String,\n    // ID is the container ID\n    pub id: String,\n    // Status is the runtime status of the container.\n    pub status: ContainerStatus,\n    // Pid is the process ID for the container process.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub pid: Option<i32>,\n    // Bundle is the path to the container's bundle directory.\n    pub bundle: PathBuf,\n    // Annotations are key values associated with the container.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub annotations: Option<HashMap<String, String>>,\n    // Creation time of the container\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub created: Option<DateTime<Utc>>,\n    // User that created the container\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub creator: Option<u32>,\n    // Specifies if systemd should be used to manage cgroups\n    pub use_systemd: bool,\n    // Specifies if the Intel RDT subdirectory needs be cleaned up.\n    pub clean_up_intel_rdt_subdirectory: Option<bool>,\n}\n\nimpl State {\n    const STATE_FILE_PATH: &'static str = \"state.json\";\n\n    pub fn new(\n        container_id: &str,\n        status: ContainerStatus,\n        pid: Option<i32>,\n        bundle: PathBuf,\n    ) -> Self {\n        Self {\n            oci_version: OCI_RUNTIME_VERSION.to_string(),\n            id: container_id.to_string(),\n            status,\n            pid,\n            bundle,\n            annotations: Some(HashMap::default()),\n            created: None,\n            creator: None,\n            use_systemd: false,\n            clean_up_intel_rdt_subdirectory: None,\n        }\n    }\n\n    #[instrument(level = \"trace\")]\n    pub fn save(&self, container_root: &Path) -> Result<()> {\n        let state_file_path = Self::file_path(container_root);\n        let file = fs::OpenOptions::new()\n            .read(true)\n            .write(true)\n            .append(false)\n            .create(true)\n            .truncate(true)\n            .open(&state_file_path)\n            .map_err(|err| {\n                tracing::error!(\n                    state_file_path = ?state_file_path,\n                    err = %err,\n                    \"failed to open container state file\",\n                );\n                StateError::OpenStateFile {\n                    state_file_path: state_file_path.to_owned(),\n                    source: err,\n                }\n            })?;\n        let mut writer = BufWriter::new(file);\n        serde_json::to_writer(&mut writer, self).map_err(|err| {\n            tracing::error!(\n                ?state_file_path,\n                %err,\n                \"failed to parse container state file\",\n            );\n            StateError::ParseStateFile {\n                state_file_path: state_file_path.to_owned(),\n                source: err,\n            }\n        })?;\n        writer.flush().map_err(|err| {\n            tracing::error!(\n                ?state_file_path,\n                %err,\n                \"failed to write container state file\",\n            );\n            StateError::WriteStateFile {\n                state_file_path: state_file_path.to_owned(),\n                source: err,\n            }\n        })?;\n\n        Ok(())\n    }\n\n    pub fn load(container_root: &Path) -> Result<Self> {\n        let state_file_path = Self::file_path(container_root);\n        let state_file = File::open(&state_file_path).map_err(|err| {\n            tracing::error!(\n                ?state_file_path,\n                %err,\n                \"failed to open container state file\",\n            );\n            StateError::OpenStateFile {\n                state_file_path: state_file_path.to_owned(),\n                source: err,\n            }\n        })?;\n\n        let state: Self = serde_json::from_reader(BufReader::new(state_file)).map_err(|err| {\n            tracing::error!(\n                ?state_file_path,\n                %err,\n                \"failed to parse container state file\",\n            );\n            StateError::ParseStateFile {\n                state_file_path: state_file_path.to_owned(),\n                source: err,\n            }\n        })?;\n\n        Ok(state)\n    }\n\n    /// Returns the path to the state JSON file for the provided `container_root`.\n    ///\n    /// ```\n    /// # use std::path::Path;\n    /// # use libcontainer::container::State;\n    ///\n    /// let container_root = Path::new(\"/var/run/containers/container\");\n    /// let state_file = State::file_path(&container_root);\n    /// assert_eq!(state_file.to_str(), Some(\"/var/run/containers/container/state.json\"));\n    /// ```\n    pub fn file_path(container_root: &Path) -> PathBuf {\n        container_root.join(Self::STATE_FILE_PATH)\n    }\n}\n\n/// Error type for state conversion failures.\n#[derive(Debug, thiserror::Error)]\npub enum StateConversionError {\n    #[error(\"failed to build OCI state: {0}\")]\n    OciStateBuild(#[from] OciSpecError),\n    #[error(\"invalid container status for OCI conversion: {0}\")]\n    InvalidStatus(ContainerStatus),\n}\n\n/// Convert internal State to OCI-compliant State (by reference, cloning necessary fields).\n///\n/// Based on runc's implementation:\n/// https://github.com/opencontainers/runc/blob/v2.2.1/libcontainer/container_linux.go#L961\nimpl TryFrom<&State> for OciState {\n    type Error = StateConversionError;\n\n    fn try_from(state: &State) -> std::result::Result<Self, Self::Error> {\n        let status = OciContainerState::try_from(state.status)?;\n\n        let mut builder = OciStateBuilder::default()\n            .version(state.oci_version.clone())\n            .id(state.id.clone())\n            .status(status)\n            .bundle(state.bundle.clone());\n\n        // Preserve None vs empty map distinction per OCI spec\n        if let Some(annotations) = &state.annotations {\n            builder = builder.annotations(annotations.clone());\n        }\n        if let Some(pid) = state.pid {\n            builder = builder.pid(pid);\n        }\n\n        Ok(builder.build()?)\n    }\n}\n\nimpl TryFrom<ContainerStatus> for OciContainerState {\n    type Error = StateConversionError;\n\n    fn try_from(status: ContainerStatus) -> std::result::Result<Self, Self::Error> {\n        match status {\n            ContainerStatus::Creating => Ok(OciContainerState::Creating),\n            ContainerStatus::Created => Ok(OciContainerState::Created),\n            ContainerStatus::Running => Ok(OciContainerState::Running),\n            ContainerStatus::Stopped => Ok(OciContainerState::Stopped),\n            // Paused is not defined in the OCI spec.\n            ContainerStatus::Paused => Err(StateConversionError::InvalidStatus(status)),\n        }\n    }\n}\n\n/// Deprecated: Use [`oci_spec::runtime::ContainerProcessState`] instead.\n#[deprecated(\n    since = \"0.6.0\",\n    note = \"Use oci_spec::runtime::ContainerProcessState instead\"\n)]\n#[derive(Serialize, Deserialize, Debug, Default)]\n#[serde(rename_all = \"camelCase\")]\npub struct ContainerProcessState {\n    // Version is the version of the specification that is supported.\n    pub oci_version: String,\n    // Fds is a string array containing the names of the file descriptors passed.\n    // The index of the name in this array corresponds to index of the file\n    // descriptor in the `SCM_RIGHTS` array.\n    pub fds: Vec<String>,\n    // Pid is the process ID as seen by the runtime.\n    pub pid: i32,\n    // Opaque metadata.\n    pub metadata: String,\n    // State of the container.\n    pub state: State,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_creating_status() {\n        let cstatus = ContainerStatus::default();\n        assert!(!cstatus.can_start());\n        assert!(!cstatus.can_delete());\n        assert!(!cstatus.can_kill());\n        assert!(!cstatus.can_pause());\n        assert!(!cstatus.can_resume());\n    }\n\n    #[test]\n    fn test_create_status() {\n        let cstatus = ContainerStatus::Created;\n        assert!(cstatus.can_start());\n        assert!(!cstatus.can_delete());\n        assert!(cstatus.can_kill());\n        assert!(!cstatus.can_pause());\n        assert!(!cstatus.can_resume());\n    }\n\n    #[test]\n    fn test_running_status() {\n        let cstatus = ContainerStatus::Running;\n        assert!(!cstatus.can_start());\n        assert!(!cstatus.can_delete());\n        assert!(cstatus.can_kill());\n        assert!(cstatus.can_pause());\n        assert!(!cstatus.can_resume());\n    }\n\n    #[test]\n    fn test_stopped_status() {\n        let cstatus = ContainerStatus::Stopped;\n        assert!(!cstatus.can_start());\n        assert!(cstatus.can_delete());\n        assert!(!cstatus.can_kill());\n        assert!(!cstatus.can_pause());\n        assert!(!cstatus.can_resume());\n    }\n\n    #[test]\n    fn test_paused_status() {\n        let cstatus = ContainerStatus::Paused;\n        assert!(!cstatus.can_start());\n        assert!(!cstatus.can_delete());\n        assert!(cstatus.can_kill());\n        assert!(!cstatus.can_pause());\n        assert!(cstatus.can_resume());\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/container/tenant_builder.rs",
    "content": "use std::collections::HashMap;\nuse std::convert::TryFrom;\nuse std::ffi::{OsStr, OsString};\nuse std::fs;\nuse std::io::BufReader;\nuse std::os::fd::{AsRawFd, OwnedFd};\nuse std::path::{Path, PathBuf};\nuse std::rc::Rc;\nuse std::str::FromStr;\n\nuse caps::Capability;\nuse nix::fcntl::OFlag;\nuse nix::unistd::{Pid, pipe2, read};\nuse oci_spec::runtime::{\n    Capabilities as SpecCapabilities, Capability as SpecCapability, LinuxBuilder,\n    LinuxCapabilities, LinuxCapabilitiesBuilder, LinuxNamespace, LinuxNamespaceBuilder,\n    LinuxNamespaceType, LinuxSchedulerPolicy, Process, ProcessBuilder, Spec, UserBuilder,\n};\nuse procfs::process::Namespace;\n\nuse super::Container;\nuse super::builder::ContainerBuilder;\nuse crate::capabilities::CapabilityExt;\nuse crate::container::builder_impl::ContainerBuilderImpl;\nuse crate::error::{ErrInvalidSpec, LibcontainerError, MissingSpecError};\nuse crate::notify_socket::NotifySocket;\nuse crate::process::args::ContainerType;\nuse crate::syscall::syscall::create_syscall;\nuse crate::user_ns::UserNamespaceConfig;\nuse crate::{tty, utils};\n\nconst NAMESPACE_TYPES: &[&str] = &[\"ipc\", \"uts\", \"net\", \"pid\", \"mnt\", \"cgroup\"];\nconst TENANT_NOTIFY: &str = \"tenant-notify-\";\nconst TENANT_TTY: &str = \"tenant-tty-\";\n\n/// Builder that can be used to configure the properties of a process\n/// that will join an existing container sandbox\npub struct TenantContainerBuilder {\n    base: ContainerBuilder,\n    env: HashMap<String, String>,\n    cwd: Option<PathBuf>,\n    args: Vec<String>,\n    no_new_privs: Option<bool>,\n    capabilities: Vec<String>,\n    process: Option<PathBuf>,\n    detached: bool,\n    as_sibling: bool,\n    additional_gids: Vec<u32>,\n    user: Option<u32>,\n    group: Option<u32>,\n}\n\n/// This is a helper function to get capabilities for tenant container, based on\n/// additional capabilities provided by user and capabilities of existing container\n/// extracted into separate function for easier testing\nfn get_capabilities(\n    additional: &[String],\n    spec: &Spec,\n) -> Result<LinuxCapabilities, LibcontainerError> {\n    let mut caps: Vec<Capability> = Vec::with_capacity(additional.len());\n    for cap in additional {\n        caps.push(Capability::from_str(cap)?);\n    }\n    let caps: SpecCapabilities = caps.iter().map(|c| SpecCapability::from_cap(*c)).collect();\n\n    if let Some(spec_caps) = spec\n        .process()\n        .as_ref()\n        .ok_or(MissingSpecError::Process)?\n        .capabilities()\n    {\n        let mut capabilities_builder = LinuxCapabilitiesBuilder::default();\n\n        let bounding: SpecCapabilities = match spec_caps.bounding() {\n            Some(bounding) => bounding.union(&caps).copied().collect(),\n            None => SpecCapabilities::new().union(&caps).copied().collect(),\n        };\n        capabilities_builder = capabilities_builder.bounding(bounding);\n\n        let effective: SpecCapabilities = match spec_caps.effective() {\n            Some(effective) => effective.union(&caps).copied().collect(),\n            None => SpecCapabilities::new().union(&caps).copied().collect(),\n        };\n        capabilities_builder = capabilities_builder.effective(effective);\n\n        let permitted: SpecCapabilities = match spec_caps.permitted() {\n            Some(permitted) => permitted.union(&caps).copied().collect(),\n            None => SpecCapabilities::new().union(&caps).copied().collect(),\n        };\n        capabilities_builder = capabilities_builder.permitted(permitted);\n\n        // ambient capabilities are only useful when inherent capabilities\n        // are set. Hence we check and set accordingly. Inherent capabilities\n        // are never set from user as that can lead to vulnerability like\n        // https://github.com/advisories/GHSA-f3fp-gc8g-vw66\n        // Hence, we follow runc's code and set things similarly.\n        let caps = if let Some(inheritable) = spec_caps.inheritable() {\n            let ambient: SpecCapabilities = match spec_caps.ambient() {\n                Some(ambient) => ambient.union(&caps).copied().collect(),\n                None => SpecCapabilities::new().union(&caps).copied().collect(),\n            };\n            capabilities_builder = capabilities_builder.ambient(ambient);\n            capabilities_builder = capabilities_builder.inheritable(inheritable.clone());\n            capabilities_builder.build()?\n        } else {\n            let mut caps = capabilities_builder.build()?;\n            // oci-spec-rs sets these to some default caps, so we reset them here\n            caps.set_inheritable(None);\n            caps.set_ambient(None);\n            caps\n        };\n\n        return Ok(caps);\n    }\n\n    // If there are no caps in original container's spec,\n    // we simply set given caps , excluding the inherent and ambient\n    let mut caps = LinuxCapabilitiesBuilder::default()\n        .bounding(caps.clone())\n        .effective(caps.clone())\n        .permitted(caps.clone())\n        .build()?;\n    caps.set_inheritable(None);\n    caps.set_ambient(None);\n    Ok(caps)\n}\n\nimpl TenantContainerBuilder {\n    /// Generates the base configuration for a process that will join\n    /// an existing container sandbox from which configuration methods\n    /// can be chained\n    pub(super) fn new(builder: ContainerBuilder) -> Self {\n        Self {\n            base: builder,\n            env: HashMap::new(),\n            cwd: None,\n            args: Vec::new(),\n            no_new_privs: None,\n            capabilities: Vec::new(),\n            process: None,\n            detached: false,\n            as_sibling: false,\n            additional_gids: vec![],\n            user: None,\n            group: None,\n        }\n    }\n\n    /// Sets environment variables for the container\n    pub fn with_env(mut self, env: HashMap<String, String>) -> Self {\n        self.env = env;\n        self\n    }\n\n    /// Sets the working directory of the container\n    pub fn with_cwd<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {\n        self.cwd = path.map(|p| p.into());\n        self\n    }\n\n    /// Sets the command the container will be started with\n    pub fn with_container_args(mut self, args: Vec<String>) -> Self {\n        self.args = args;\n        self\n    }\n\n    pub fn with_no_new_privs(mut self, no_new_privs: bool) -> Self {\n        self.no_new_privs = Some(no_new_privs);\n        self\n    }\n\n    pub fn with_capabilities(mut self, capabilities: Vec<String>) -> Self {\n        self.capabilities = capabilities;\n        self\n    }\n\n    pub fn with_process<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {\n        self.process = path.map(|p| p.into());\n        self\n    }\n\n    /// Sets if the init process should be run as a child or a sibling of\n    /// the calling process\n    pub fn as_sibling(mut self, as_sibling: bool) -> Self {\n        self.as_sibling = as_sibling;\n        self\n    }\n\n    pub fn with_detach(mut self, detached: bool) -> Self {\n        self.detached = detached;\n        self\n    }\n\n    pub fn with_additional_gids(mut self, gids: Vec<u32>) -> Self {\n        self.additional_gids = gids;\n        self\n    }\n\n    pub fn with_user(mut self, user: Option<u32>) -> Self {\n        self.user = user;\n        self\n    }\n\n    pub fn with_group(mut self, group: Option<u32>) -> Self {\n        self.group = group;\n        self\n    }\n\n    /// Joins an existing container\n    pub fn build(self) -> Result<Pid, LibcontainerError> {\n        let container_dir = self.lookup_container_dir()?;\n        let container = self.load_container_state(container_dir.clone())?;\n        let mut spec = self.load_init_spec(&container)?;\n        self.adapt_spec_for_tenant(&mut spec, &container)?;\n\n        tracing::debug!(\"{:#?}\", spec);\n\n        let notify_path = Self::setup_notify_listener(&container_dir)?;\n        // convert path of root file system of the container to absolute path\n        let rootfs = fs::canonicalize(spec.root().as_ref().ok_or(MissingSpecError::Root)?.path())\n            .map_err(LibcontainerError::OtherIO)?;\n\n        // if socket file path is given in commandline options,\n        // get file descriptors of console socket\n        let csocketfd = self.setup_tty_socket(&container_dir)?;\n\n        let use_systemd = self.should_use_systemd(&container);\n        let user_ns_config = UserNamespaceConfig::new(&spec)?;\n\n        let (read_end, write_end) =\n            pipe2(OFlag::O_CLOEXEC).map_err(LibcontainerError::OtherSyscall)?;\n\n        let mut builder_impl = ContainerBuilderImpl {\n            container_type: ContainerType::TenantContainer {\n                exec_notify_fd: write_end.as_raw_fd(),\n            },\n            syscall: self.base.syscall,\n            container_id: self.base.container_id,\n            pid_file: self.base.pid_file,\n            console_socket: csocketfd,\n            use_systemd,\n            spec: Rc::new(spec),\n            rootfs,\n            user_ns_config,\n            notify_path: notify_path.clone(),\n            container: None,\n            preserve_fds: self.base.preserve_fds,\n            detached: self.detached,\n            executor: self.base.executor,\n            no_pivot: false,\n            stdin: self.base.stdin,\n            stdout: self.base.stdout,\n            stderr: self.base.stderr,\n            as_sibling: self.as_sibling,\n        };\n\n        let pid = builder_impl.create()?;\n\n        let mut notify_socket = NotifySocket::new(notify_path);\n        notify_socket.notify_container_start()?;\n\n        // Explicitly close the write end of the pipe here to notify the\n        // `read_end` that the init process is able to move forward. Closing one\n        // end of the pipe will immediately signal the other end of the pipe,\n        // which we use in the init thread as a form of barrier.  `drop` is used\n        // here because `OwnedFd` supports it, so we don't have to use `close`\n        // here with `RawFd`.\n        drop(write_end);\n\n        let mut err_str_buf = Vec::new();\n\n        loop {\n            let mut buf = [0; 3];\n            match read(read_end.as_raw_fd(), &mut buf).map_err(LibcontainerError::OtherSyscall)? {\n                0 => {\n                    if err_str_buf.is_empty() {\n                        return Ok(pid);\n                    } else {\n                        return Err(LibcontainerError::Other(\n                            String::from_utf8_lossy(&err_str_buf).to_string(),\n                        ));\n                    }\n                }\n                _ => {\n                    err_str_buf.extend(buf);\n                }\n            }\n        }\n    }\n\n    fn lookup_container_dir(&self) -> Result<PathBuf, LibcontainerError> {\n        let container_dir = self.base.root_path.join(&self.base.container_id);\n        if !container_dir.exists() {\n            tracing::error!(?container_dir, ?self.base.container_id, \"container dir does not exist\");\n            return Err(LibcontainerError::NoDirectory);\n        }\n\n        Ok(container_dir)\n    }\n\n    fn load_init_spec(&self, container: &Container) -> Result<Spec, LibcontainerError> {\n        let spec_path = container.bundle().join(\"config.json\");\n\n        let mut spec = Spec::load(&spec_path).map_err(|err| {\n            tracing::error!(path = ?spec_path, ?err, \"failed to load spec\");\n            err\n        })?;\n\n        Self::validate_spec(&spec)?;\n\n        spec.canonicalize_rootfs(container.bundle())?;\n        Ok(spec)\n    }\n\n    fn validate_spec(spec: &Spec) -> Result<(), LibcontainerError> {\n        let version = spec.version();\n        if !version.starts_with(\"1.\") {\n            tracing::error!(\n                \"runtime spec has incompatible version '{}'. Only 1.X.Y is supported\",\n                spec.version()\n            );\n            Err(ErrInvalidSpec::UnsupportedVersion)?;\n        }\n\n        if let Some(process) = spec.process() {\n            if let Some(io_priority) = process.io_priority() {\n                let priority = io_priority.priority();\n                let iop_class_res = serde_json::to_string(&io_priority.class());\n                match iop_class_res {\n                    Ok(iop_class) => {\n                        if !(0..=7).contains(&priority) {\n                            tracing::error!(\n                                ?priority,\n                                \"io priority '{}' not between 0 and 7 (inclusive), class '{}' not in (IO_PRIO_CLASS_RT,IO_PRIO_CLASS_BE,IO_PRIO_CLASS_IDLE)\",\n                                priority,\n                                iop_class\n                            );\n                            Err(ErrInvalidSpec::IoPriority)?;\n                        }\n                    }\n                    Err(e) => {\n                        tracing::error!(?priority, ?e, \"failed to parse io priority class\");\n                        Err(ErrInvalidSpec::IoPriority)?;\n                    }\n                }\n            }\n\n            if let Some(sc) = process.scheduler() {\n                let policy = sc.policy();\n                if let Some(nice) = sc.nice() {\n                    // https://man7.org/linux/man-pages/man2/sched_setattr.2.html#top_of_page\n                    if (*policy == LinuxSchedulerPolicy::SchedBatch\n                        || *policy == LinuxSchedulerPolicy::SchedOther)\n                        && (*nice < -20 || *nice > 19)\n                    {\n                        tracing::error!(\n                            ?nice,\n                            \"invalid scheduler.nice: '{}', must be within -20 to 19\",\n                            nice\n                        );\n                        Err(ErrInvalidSpec::Scheduler)?;\n                    }\n                }\n                if let Some(priority) = sc.priority() {\n                    if *priority != 0\n                        && (*policy != LinuxSchedulerPolicy::SchedFifo\n                            && *policy != LinuxSchedulerPolicy::SchedRr)\n                    {\n                        tracing::error!(\n                            ?policy,\n                            \"scheduler.priority can only be specified for SchedFIFO or SchedRR policy\"\n                        );\n                        Err(ErrInvalidSpec::Scheduler)?;\n                    }\n                }\n                if *policy != LinuxSchedulerPolicy::SchedDeadline {\n                    if let Some(runtime) = sc.runtime() {\n                        if *runtime != 0 {\n                            tracing::error!(\n                                ?runtime,\n                                \"scheduler runtime can only be specified for SchedDeadline policy\"\n                            );\n                            Err(ErrInvalidSpec::Scheduler)?;\n                        }\n                    }\n                    if let Some(deadline) = sc.deadline() {\n                        if *deadline != 0 {\n                            tracing::error!(\n                                ?deadline,\n                                \"scheduler deadline can only be specified for SchedDeadline policy\"\n                            );\n                            Err(ErrInvalidSpec::Scheduler)?;\n                        }\n                    }\n                    if let Some(period) = sc.period() {\n                        if *period != 0 {\n                            tracing::error!(\n                                ?period,\n                                \"scheduler period can only be specified for SchedDeadline policy\"\n                            );\n                            Err(ErrInvalidSpec::Scheduler)?;\n                        }\n                    }\n                }\n            }\n        }\n\n        if let Some(mounts) = spec.mounts() {\n            utils::validate_mount_options(mounts)?;\n        }\n\n        let syscall = create_syscall();\n        utils::validate_spec_for_new_user_ns(spec, &*syscall)?;\n        utils::validate_spec_for_net_devices(spec, &*syscall)\n            .map_err(LibcontainerError::NetDevicesError)?;\n\n        Ok(())\n    }\n\n    fn load_container_state(&self, container_dir: PathBuf) -> Result<Container, LibcontainerError> {\n        let container = Container::load(container_dir)?;\n        if !container.can_exec() {\n            tracing::error!(status = ?container.status(), \"cannot exec as container\");\n            return Err(LibcontainerError::IncorrectStatus(container.status()));\n        }\n\n        Ok(container)\n    }\n\n    fn adapt_spec_for_tenant(\n        &self,\n        spec: &mut Spec,\n        container: &Container,\n    ) -> Result<(), LibcontainerError> {\n        let process = if let Some(process) = &self.process {\n            self.get_process(process)?\n        } else {\n            // Use the spec's process env as the baseline for exec.\n            let spec_env = spec\n                .process()\n                .as_ref()\n                .and_then(|p| p.env().as_ref().cloned())\n                .unwrap_or_default();\n            let mut process_builder = ProcessBuilder::default()\n                .args(self.get_args()?)\n                .env(self.get_environment(spec_env));\n            if let Some(cwd) = self.get_working_dir()? {\n                process_builder = process_builder.cwd(cwd);\n            }\n\n            if let Some(process) = spec.process() {\n                if let Some(cpu_affinity) = process.exec_cpu_affinity() {\n                    process_builder = process_builder.exec_cpu_affinity(cpu_affinity.clone());\n                }\n            }\n\n            if let Some(no_new_priv) = self.get_no_new_privileges() {\n                process_builder = process_builder.no_new_privileges(no_new_priv);\n            }\n\n            let capabilities = get_capabilities(&self.capabilities, spec)?;\n            process_builder = process_builder.capabilities(capabilities);\n\n            let mut user_builder = UserBuilder::default();\n\n            if !self.additional_gids.is_empty() {\n                user_builder = user_builder.additional_gids(self.additional_gids.clone());\n            }\n\n            if let Some(uid) = self.user {\n                user_builder = user_builder.uid(uid);\n            }\n\n            if let Some(gid) = self.group {\n                user_builder = user_builder.gid(gid);\n            }\n\n            process_builder = process_builder.user(user_builder.build()?);\n\n            process_builder.build()?\n        };\n\n        let container_pid = container.pid().ok_or(LibcontainerError::Other(\n            \"could not retrieve container init pid\".into(),\n        ))?;\n\n        let init_process = procfs::process::Process::new(container_pid.as_raw())?;\n        let ns = self.get_namespaces(init_process.namespaces()?.0)?;\n\n        // it should never be the case that linux is not present in spec\n        let spec_linux = spec.linux().as_ref().unwrap();\n        let mut linux_builder = LinuxBuilder::default().namespaces(ns);\n\n        if let Some(cgroup_path) = spec_linux.cgroups_path() {\n            linux_builder = linux_builder.cgroups_path(cgroup_path.clone());\n        }\n\n        if let Some(personality) = spec_linux.personality() {\n            linux_builder = linux_builder.personality(personality.clone());\n        }\n\n        let linux = linux_builder.build()?;\n        spec.set_process(Some(process)).set_linux(Some(linux));\n\n        Ok(())\n    }\n\n    fn get_process(&self, process: &Path) -> Result<Process, LibcontainerError> {\n        if !process.exists() {\n            tracing::error!(?process, \"process.json file does not exist\");\n            return Err(LibcontainerError::Other(\n                \"process.json file does not exist\".into(),\n            ));\n        }\n\n        let process = utils::open(process).map_err(LibcontainerError::OtherIO)?;\n        let reader = BufReader::new(process);\n        let process_spec =\n            serde_json::from_reader(reader).map_err(LibcontainerError::OtherSerialization)?;\n        Ok(process_spec)\n    }\n\n    fn get_working_dir(&self) -> Result<Option<PathBuf>, LibcontainerError> {\n        if let Some(cwd) = &self.cwd {\n            if cwd.is_relative() {\n                tracing::error!(?cwd, \"current working directory must be an absolute path\");\n                return Err(LibcontainerError::Other(\n                    \"current working directory must be an absolute path\".into(),\n                ));\n            }\n            return Ok(Some(cwd.into()));\n        }\n        Ok(None)\n    }\n\n    fn get_args(&self) -> Result<Vec<String>, LibcontainerError> {\n        if self.args.is_empty() {\n            Err(MissingSpecError::Args)?;\n        }\n\n        Ok(self.args.clone())\n    }\n\n    /// Builds the environment for an exec process.\n    /// The spec's env vars are used as the baseline, and env vars provided to the\n    /// builder, such as those from the CLI, override entries with the same key.\n    /// This follows runc's behavior.\n    /// See <https://github.com/youki-dev/youki/issues/3428>.\n    fn get_environment(&self, spec_env: Vec<String>) -> Vec<String> {\n        // Start with spec env, skipping any vars that the CLI overrides.\n        let mut env: Vec<String> = spec_env\n            .into_iter()\n            .filter(|entry| {\n                let key = entry.split('=').next().unwrap_or(\"\");\n                !self.env.contains_key(key)\n            })\n            .collect();\n\n        // Append CLI overrides.\n        for (k, v) in &self.env {\n            env.push(format!(\"{k}={v}\"));\n        }\n\n        env\n    }\n\n    fn get_no_new_privileges(&self) -> Option<bool> {\n        self.no_new_privs\n    }\n\n    fn get_namespaces(\n        &self,\n        init_namespaces: HashMap<OsString, Namespace>,\n    ) -> Result<Vec<LinuxNamespace>, LibcontainerError> {\n        let mut tenant_namespaces = Vec::with_capacity(init_namespaces.len());\n\n        for &ns_type in NAMESPACE_TYPES {\n            if let Some(init_ns) = init_namespaces.get(OsStr::new(ns_type)) {\n                let tenant_ns = LinuxNamespaceType::try_from(ns_type)?;\n                tenant_namespaces.push(\n                    LinuxNamespaceBuilder::default()\n                        .typ(tenant_ns)\n                        .path(init_ns.path.clone())\n                        .build()?,\n                )\n            }\n        }\n\n        Ok(tenant_namespaces)\n    }\n\n    fn should_use_systemd(&self, container: &Container) -> bool {\n        container.systemd()\n    }\n\n    fn setup_notify_listener(container_dir: &Path) -> Result<PathBuf, LibcontainerError> {\n        let notify_name = Self::generate_name(container_dir, TENANT_NOTIFY);\n        let socket_path = container_dir.join(notify_name);\n\n        Ok(socket_path)\n    }\n\n    fn setup_tty_socket(&self, container_dir: &Path) -> Result<Option<OwnedFd>, LibcontainerError> {\n        let tty_name = Self::generate_name(container_dir, TENANT_TTY);\n        let csocketfd = if let Some(console_socket) = &self.base.console_socket {\n            Some(tty::setup_console_socket(\n                container_dir,\n                console_socket,\n                &tty_name,\n            )?)\n        } else {\n            None\n        };\n\n        Ok(csocketfd)\n    }\n\n    fn generate_name(dir: &Path, prefix: &str) -> String {\n        loop {\n            let rand = fastrand::i32(..);\n            let name = format!(\"{prefix}{rand:x}.sock\");\n            if !dir.join(&name).exists() {\n                return name;\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use caps::Capability as Cap;\n    use oci_spec::runtime::{Capabilities, Capability as SpecCap, SpecBuilder};\n\n    use super::*;\n    use crate::capabilities::CapabilityExt;\n    use crate::syscall::syscall::SyscallType;\n\n    fn get_spec(caps: LinuxCapabilities) -> Spec {\n        SpecBuilder::default()\n            .process(\n                ProcessBuilder::default()\n                    .capabilities(caps)\n                    .build()\n                    .unwrap(),\n            )\n            .build()\n            .unwrap()\n    }\n\n    fn cap_to_string(caps: &[Cap]) -> Vec<String> {\n        caps.iter().map(|c| c.to_string()).collect()\n    }\n\n    fn caps_to_spec_set(caps: &[Cap]) -> Capabilities {\n        caps.iter().map(|c| SpecCap::from_cap(*c)).collect()\n    }\n\n    fn empty_caps() -> LinuxCapabilities {\n        let mut t = LinuxCapabilities::default();\n        t.set_effective(None)\n            .set_bounding(None)\n            .set_permitted(None)\n            .set_inheritable(None)\n            .set_ambient(None);\n        t\n    }\n\n    /// Helper to build a minimal TenantContainerBuilder with the given CLI env.\n    fn builder_with_env(env: &[(&str, &str)]) -> TenantContainerBuilder {\n        let base = ContainerBuilder::new(\"test\".to_string(), SyscallType::default());\n        let env_map: HashMap<String, String> = env\n            .iter()\n            .map(|(k, v)| (k.to_string(), v.to_string()))\n            .collect();\n\n        TenantContainerBuilder::new(base).with_env(env_map)\n    }\n\n    // --- capabilities tests ---\n\n    // if there are no existing capabilities, then tenant can only\n    // set effective, bounding and permitted caps ; not inheritable or ambient\n    #[test]\n    fn test_capabilities_no_existing() -> Result<(), LibcontainerError> {\n        let spec = get_spec(empty_caps());\n\n        let extra_caps = &[Cap::CAP_SYS_ADMIN, Cap::CAP_NET_ADMIN, Cap::CAP_AUDIT_READ];\n\n        let additional = cap_to_string(extra_caps);\n        let caps = get_capabilities(&additional, &spec)?;\n\n        let expected_caps = empty_caps()\n            .set_effective(Some(caps_to_spec_set(extra_caps)))\n            .set_bounding(Some(caps_to_spec_set(extra_caps)))\n            .set_permitted(Some(caps_to_spec_set(extra_caps)))\n            .clone();\n\n        assert_eq!(caps, expected_caps);\n        Ok(())\n    }\n\n    // If there are existing capabilities, but not inherent, then tenant should union\n    // existing and provided caps only for effective, bounding and permitted,\n    // inherent and ambient should be explicitly None\n    #[test]\n    fn test_capabilities_with_existing() -> Result<(), LibcontainerError> {\n        let existing_caps = &[Cap::CAP_SYS_ADMIN, Cap::CAP_BPF, Cap::CAP_MKNOD];\n\n        let existing = LinuxCapabilities::default()\n            .set_effective(Some(caps_to_spec_set(existing_caps)))\n            .set_bounding(Some(caps_to_spec_set(existing_caps)))\n            .set_permitted(Some(caps_to_spec_set(existing_caps)))\n            .set_inheritable(None)\n            .set_ambient(None)\n            .clone();\n\n        let spec = get_spec(existing);\n\n        let extra_caps = &[Cap::CAP_SYS_ADMIN, Cap::CAP_NET_ADMIN, Cap::CAP_AUDIT_READ];\n\n        let additional = cap_to_string(extra_caps);\n        let caps = get_capabilities(&additional, &spec)?;\n\n        let mut combined_caps = existing_caps.to_vec();\n        combined_caps.extend(extra_caps);\n        let expected_caps = empty_caps()\n            .set_effective(Some(caps_to_spec_set(&combined_caps)))\n            .set_bounding(Some(caps_to_spec_set(&combined_caps)))\n            .set_permitted(Some(caps_to_spec_set(&combined_caps)))\n            .clone();\n\n        assert_eq!(caps, expected_caps);\n        Ok(())\n    }\n\n    // we check that if inherent capabilities are present, ambient are set correctly\n    #[test]\n    fn test_capabilities_with_existing_inherent() -> Result<(), LibcontainerError> {\n        let existing_caps = &[Cap::CAP_SYS_ADMIN, Cap::CAP_BPF, Cap::CAP_MKNOD];\n        let extra_caps = &[Cap::CAP_SYS_ADMIN, Cap::CAP_NET_ADMIN, Cap::CAP_AUDIT_READ];\n\n        let mut combined_caps = existing_caps.to_vec();\n        combined_caps.extend(extra_caps);\n\n        // case 1 :  when inheritable are there, but no ambient\n\n        let existing = LinuxCapabilities::default()\n            .set_effective(Some(caps_to_spec_set(existing_caps)))\n            .set_bounding(Some(caps_to_spec_set(existing_caps)))\n            .set_permitted(Some(caps_to_spec_set(existing_caps)))\n            .set_inheritable(Some(caps_to_spec_set(existing_caps)))\n            .set_ambient(None)\n            .clone();\n        let spec = get_spec(existing);\n        let additional = cap_to_string(extra_caps);\n        let caps = get_capabilities(&additional, &spec)?;\n        let expected_caps = empty_caps()\n            .set_effective(Some(caps_to_spec_set(&combined_caps)))\n            .set_bounding(Some(caps_to_spec_set(&combined_caps)))\n            .set_permitted(Some(caps_to_spec_set(&combined_caps)))\n            // inheritable must not change\n            .set_inheritable(Some(caps_to_spec_set(existing_caps)))\n            // as there were no existing ambient, only extra will be set\n            .set_ambient(Some(caps_to_spec_set(extra_caps)))\n            .clone();\n        assert_eq!(caps, expected_caps);\n\n        // case 2 :  when inheritable and ambient both are present\n\n        let existing = LinuxCapabilities::default()\n            .set_effective(Some(caps_to_spec_set(existing_caps)))\n            .set_bounding(Some(caps_to_spec_set(existing_caps)))\n            .set_permitted(Some(caps_to_spec_set(existing_caps)))\n            .set_inheritable(Some(caps_to_spec_set(existing_caps)))\n            .set_ambient(Some(caps_to_spec_set(existing_caps)))\n            .clone();\n        let spec = get_spec(existing);\n        let additional = cap_to_string(extra_caps);\n        let caps = get_capabilities(&additional, &spec)?;\n        let expected_caps = empty_caps()\n            .set_effective(Some(caps_to_spec_set(&combined_caps)))\n            .set_bounding(Some(caps_to_spec_set(&combined_caps)))\n            .set_permitted(Some(caps_to_spec_set(&combined_caps)))\n            // inheritable must not change\n            .set_inheritable(Some(caps_to_spec_set(existing_caps)))\n            .set_ambient(Some(caps_to_spec_set(&combined_caps)))\n            .clone();\n        assert_eq!(caps, expected_caps);\n\n        Ok(())\n    }\n\n    // --- environment tests ---\n\n    #[test]\n    fn env_inherits_spec_vars() {\n        let b = builder_with_env(&[]);\n        let spec_env = vec![\"PATH=/usr/bin\".to_string(), \"AAA=bbb\".to_string()];\n        let result = b.get_environment(spec_env);\n        assert!(result.contains(&\"PATH=/usr/bin\".to_string()));\n        assert!(result.contains(&\"AAA=bbb\".to_string()));\n    }\n\n    #[test]\n    fn builder_env_overrides_spec() {\n        let b = builder_with_env(&[(\"AAA\", \"override\")]);\n        let spec_env = vec![\"PATH=/usr/bin\".to_string(), \"AAA=bbb\".to_string()];\n        let result = b.get_environment(spec_env);\n        assert!(result.contains(&\"PATH=/usr/bin\".to_string()));\n        assert!(result.contains(&\"AAA=override\".to_string()));\n        assert!(!result.contains(&\"AAA=bbb\".to_string()));\n    }\n\n    #[test]\n    fn builder_env_adds_new_vars() {\n        let b = builder_with_env(&[(\"NEW_VAR\", \"hello\")]);\n        let spec_env = vec![\"PATH=/usr/bin\".to_string()];\n        let result = b.get_environment(spec_env);\n        assert!(result.contains(&\"PATH=/usr/bin\".to_string()));\n        assert!(result.contains(&\"NEW_VAR=hello\".to_string()));\n    }\n\n    #[test]\n    fn empty_spec_env_uses_builder_env_only() {\n        let b = builder_with_env(&[(\"FOO\", \"bar\")]);\n        let result = b.get_environment(Vec::new());\n        assert_eq!(result, vec![\"FOO=bar\".to_string()]);\n    }\n\n    #[test]\n    fn no_env_at_all() {\n        let b = builder_with_env(&[]);\n        let result = b.get_environment(Vec::new());\n        assert!(result.is_empty());\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/error.rs",
    "content": "use crate::container::ContainerStatus;\n\n#[derive(Debug, thiserror::Error)]\npub enum MissingSpecError {\n    #[error(\"missing process in spec\")]\n    Process,\n    #[error(\"missing linux in spec\")]\n    Linux,\n    #[error(\"missing args in the process spec\")]\n    Args,\n    #[error(\"missing root in the spec\")]\n    Root,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum LibcontainerError {\n    #[error(\"failed operation due to incompatible container status: `{0}`\")]\n    IncorrectStatus(ContainerStatus),\n    #[error(\"container already exists\")]\n    Exist,\n    #[error(\"container state directory does not exist\")]\n    NoDirectory,\n    #[error(\"invalid input\")]\n    InvalidInput(String),\n    #[error(\"requires at least one executors\")]\n    NoExecutors,\n    #[error(\"rootless container requires valid user namespace definition\")]\n    NoUserNamespace,\n\n    // Invalid inputs\n    #[error(transparent)]\n    InvalidID(#[from] ErrInvalidID),\n    #[error(transparent)]\n    MissingSpec(#[from] MissingSpecError),\n    #[error(\"invalid runtime spec\")]\n    InvalidSpec(#[from] ErrInvalidSpec),\n\n    // Errors from submodules and other errors\n    #[error(transparent)]\n    Tty(#[from] crate::tty::TTYError),\n    #[error(transparent)]\n    UserNamespace(#[from] crate::user_ns::UserNamespaceError),\n    #[error(transparent)]\n    NotifyListener(#[from] crate::notify_socket::NotifyListenerError),\n    #[error(transparent)]\n    Config(#[from] crate::config::ConfigError),\n    #[error(transparent)]\n    Hook(#[from] crate::hooks::HookError),\n    #[error(transparent)]\n    State(#[from] crate::container::state::StateError),\n    #[error(\"oci spec error\")]\n    Spec(#[from] oci_spec::OciSpecError),\n    #[error(transparent)]\n    MainProcess(#[from] crate::process::container_main_process::ProcessError),\n    #[error(transparent)]\n    Procfs(#[from] procfs::ProcError),\n    #[error(transparent)]\n    Capabilities(#[from] caps::errors::CapsError),\n    #[error(transparent)]\n    CgroupManager(#[from] libcgroups::common::AnyManagerError),\n    #[error(transparent)]\n    CgroupCreate(#[from] libcgroups::common::CreateCgroupSetupError),\n    #[error(transparent)]\n    CgroupGet(#[from] libcgroups::common::GetCgroupSetupError),\n    #[error[transparent]]\n    Checkpoint(#[from] crate::container::CheckpointError),\n    #[error[transparent]]\n    CreateContainerError(#[from] CreateContainerError),\n    #[error(transparent)]\n    NetDevicesError(#[from] crate::utils::NetDevicesError),\n    #[error(transparent)]\n    NetworkError(#[from] crate::network::NetworkError),\n\n    // Catch all errors that are not covered by the above\n    #[error(\"syscall error\")]\n    OtherSyscall(#[source] nix::Error),\n    #[error(\"io error\")]\n    OtherIO(#[source] std::io::Error),\n    #[error(\"serialization error\")]\n    OtherSerialization(#[source] serde_json::Error),\n    #[error(\"{0}\")]\n    OtherCgroup(String),\n    #[error(\"{0}\")]\n    Other(String),\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ErrInvalidID {\n    #[error(\"container id can't be empty\")]\n    Empty,\n    #[error(\"container id contains invalid characters: {0}\")]\n    InvalidChars(char),\n    #[error(\"container id can't be used to represent a file name (such as . or ..)\")]\n    FileName,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ErrInvalidSpec {\n    #[error(\"runtime spec has incompatible version. Only 1.X.Y is supported\")]\n    UnsupportedVersion,\n    #[error(\"apparmor is specified but not enabled on this system\")]\n    AppArmorNotEnabled,\n    #[error(\"invalid io priority or class.\")]\n    IoPriority,\n    #[error(\"invalid scheduler config for process\")]\n    Scheduler,\n}\n\n#[derive(Debug, thiserror::Error)]\npub struct CreateContainerError(Box<LibcontainerError>, Option<Box<LibcontainerError>>);\n\nimpl CreateContainerError {\n    pub(crate) fn new(\n        run_error: LibcontainerError,\n        cleanup_error: Option<LibcontainerError>,\n    ) -> Self {\n        Self(Box::new(run_error), cleanup_error.map(Box::new))\n    }\n}\n\nimpl std::fmt::Display for CreateContainerError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"failed to create container: {}\", self.0)?;\n        if let Some(cleanup_err) = &self.1 {\n            write!(f, \". error during cleanup: {}\", cleanup_err)?;\n        }\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use libcgroups::common::CreateCgroupSetupError;\n\n    use super::{CreateContainerError, ErrInvalidID};\n\n    #[test]\n    fn test_create_container() {\n        let create_container_err =\n            CreateContainerError::new(CreateCgroupSetupError::NonDefault.into(), None);\n        let msg = format!(\"{}\", create_container_err);\n        assert_eq!(\n            \"failed to create container: non default cgroup root not supported\",\n            msg\n        );\n\n        let create_container_err = CreateContainerError::new(\n            CreateCgroupSetupError::NonDefault.into(),\n            Some(ErrInvalidID::Empty.into()),\n        );\n        let msg = format!(\"{}\", create_container_err);\n        assert_eq!(\n            \"failed to create container: non default cgroup root not supported. \\\n         error during cleanup: container id can't be empty\",\n            msg\n        );\n    }\n    #[test]\n    fn test_libcontainer_error_msg() {\n        use crate::container::ContainerStatus::*;\n        use crate::error::LibcontainerError::IncorrectStatus;\n\n        assert_eq!(\n            \"failed operation due to incompatible container status: `Creating`\",\n            format!(\"{}\", IncorrectStatus(Creating))\n        );\n        assert_eq!(\n            \"failed operation due to incompatible container status: `Created`\",\n            format!(\"{}\", IncorrectStatus(Created))\n        );\n        assert_eq!(\n            \"failed operation due to incompatible container status: `Stopped`\",\n            format!(\"{}\", IncorrectStatus(Stopped))\n        );\n        assert_eq!(\n            \"failed operation due to incompatible container status: `Running`\",\n            format!(\"{}\", IncorrectStatus(Running))\n        );\n        assert_eq!(\n            \"failed operation due to incompatible container status: `Paused`\",\n            format!(\"{}\", IncorrectStatus(Paused))\n        );\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/hooks.rs",
    "content": "use std::collections::HashMap;\nuse std::io::{ErrorKind, Write};\nuse std::os::unix::prelude::CommandExt;\nuse std::path::Path;\nuse std::{process, thread, time};\n\nuse nix::sys::signal;\nuse nix::unistd::Pid;\nuse oci_spec::runtime::{Hook, State as OciState};\n\nuse crate::container::{State, StateConversionError};\nuse crate::utils;\n\n#[derive(Debug, thiserror::Error)]\npub enum HookError {\n    #[error(\"failed to execute hook command\")]\n    CommandExecute(#[source] std::io::Error),\n    #[error(\"failed to encode container state\")]\n    EncodeContainerState(#[source] serde_json::Error),\n    #[error(\"hook command exited with non-zero exit code: {0}\")]\n    NonZeroExitCode(i32),\n    #[error(\"hook command was killed by a signal\")]\n    Killed,\n    #[error(\"failed to execute hook command due to a timeout\")]\n    Timeout,\n    #[error(\"container state is required to run hook\")]\n    MissingContainerState,\n    #[error(\"failed to write container state to stdin\")]\n    WriteContainerState(#[source] std::io::Error),\n    #[error(\"failed to convert state to OCI format\")]\n    StateConversion(#[from] StateConversionError),\n}\n\ntype Result<T> = std::result::Result<T, HookError>;\n\npub fn run_hooks(\n    hooks: Option<&Vec<Hook>>,\n    state: Option<&State>,\n    // TODO: Remove the following parameters. To comply with the OCI State, hooks should only depend on structures defined in oci-spec-rs. Cleaning these up ensures proper functional isolation.\n    cwd: Option<&Path>,\n    pid: Option<Pid>,\n) -> Result<()> {\n    let base_state = state.ok_or(HookError::MissingContainerState)?;\n\n    // High-level container runtimes use OCI state to pass the container state to the hooks.\n    // So we need to convert the container state to OCI state.\n    // Ref: https://github.com/containerd/containerd/blob/v2.2.1/cmd/containerd/command/oci-hook.go#L82\n    let mut oci_state = OciState::try_from(base_state)?;\n\n    // The `pid` parameter allows overriding the PID in the state. This is needed because\n    // high-level container runtimes like containerd set the PID separately for certain hooks.\n    // Ref: https://github.com/containerd/containerd/blob/main/cmd/containerd/command/oci-hook.go#L90\n    if let Some(override_pid) = pid {\n        oci_state.set_pid(Some(override_pid.as_raw()));\n    }\n\n    if let Some(hooks) = hooks {\n        for hook in hooks {\n            let mut hook_command = process::Command::new(hook.path());\n\n            if let Some(cwd) = cwd {\n                hook_command.current_dir(cwd);\n            }\n\n            // Based on OCI spec, the first argument of the args vector is the\n            // arg0, which can be different from the path.  For example, path\n            // may be \"/usr/bin/true\" and arg0 is set to \"true\". However, rust\n            // command differentiates arg0 from args, where rust command arg\n            // doesn't include arg0. So we have to make the split arg0 from the\n            // rest of args.\n            if let Some((arg0, args)) = hook.args().as_ref().and_then(|a| a.split_first()) {\n                tracing::debug!(\"run_hooks arg0: {:?}, args: {:?}\", arg0, args);\n                hook_command.arg0(arg0).args(args)\n            } else {\n                hook_command.arg0(hook.path().display().to_string())\n            };\n\n            let envs: HashMap<String, String> = if let Some(env) = hook.env() {\n                utils::parse_env(env)\n            } else {\n                HashMap::new()\n            };\n            tracing::debug!(\"run_hooks envs: {:?}\", envs);\n\n            let mut hook_process = hook_command\n                .env_clear()\n                .envs(envs)\n                .stdin(process::Stdio::piped())\n                .stdout(std::process::Stdio::null())\n                .stderr(process::Stdio::inherit())\n                .spawn()\n                .map_err(HookError::CommandExecute)?;\n            let hook_process_pid = Pid::from_raw(hook_process.id() as i32);\n            // Based on the OCI spec, we need to pipe the container state into\n            // the hook command through stdin.\n            if let Some(stdin) = &mut hook_process.stdin {\n                // We want to ignore BrokenPipe here. A BrokenPipe indicates\n                // either the hook is crashed/errored or it ran successfully.\n                // Either way, this is an indication that the hook command\n                // finished execution.  If the hook command was successful,\n                // which we will check later in this function, we should not\n                // fail this step here. We still want to check for all the other\n                // error, in the case that the hook command is waiting for us to\n                // write to stdin.\n                let encoded_state =\n                    serde_json::to_string(&oci_state).map_err(HookError::EncodeContainerState)?;\n                if let Err(e) = stdin.write_all(encoded_state.as_bytes()) {\n                    if e.kind() != ErrorKind::BrokenPipe {\n                        // Not a broken pipe. The hook command may be waiting\n                        // for us.\n                        let _ = signal::kill(hook_process_pid, signal::Signal::SIGKILL);\n                        return Err(HookError::WriteContainerState(e));\n                    }\n                }\n            }\n\n            let res = if let Some(timeout_sec) = hook.timeout() {\n                // Rust does not make it easy to handle executing a command and\n                // timeout. Here we decided to wait for the command in a\n                // different thread, so the main thread is not blocked. We use a\n                // channel shared between main thread and the wait thread, since\n                // the channel has timeout functions out of the box. Rust won't\n                // let us copy the Command structure, so we can't share it\n                // between the wait thread and main thread. Therefore, we will\n                // use pid to identify the process and send a kill signal. This\n                // is what the Command.kill() does under the hood anyway. When\n                // timeout, we have to kill the process and clean up properly.\n                let (s, r) = std::sync::mpsc::channel();\n                thread::spawn(move || {\n                    let res = hook_process.wait();\n                    let _ = s.send(res);\n                });\n                match r.recv_timeout(time::Duration::from_secs(timeout_sec as u64)) {\n                    Ok(res) => res,\n                    Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {\n                        // Kill the process. There is no need to further clean\n                        // up because we will be error out.\n                        let _ = signal::kill(hook_process_pid, signal::Signal::SIGKILL);\n                        return Err(HookError::Timeout);\n                    }\n                    Err(_) => {\n                        unreachable!();\n                    }\n                }\n            } else {\n                hook_process.wait()\n            };\n\n            match res {\n                Ok(exit_status) => match exit_status.code() {\n                    Some(0) => Ok(()),\n                    Some(exit_code) => Err(HookError::NonZeroExitCode(exit_code)),\n                    None => Err(HookError::Killed),\n                },\n                Err(e) => Err(HookError::CommandExecute(e)),\n            }?;\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod test {\n    use std::{env, fs};\n\n    use anyhow::{Context, Result, bail};\n    use oci_spec::runtime::HookBuilder;\n    use serial_test::serial;\n\n    use super::*;\n    use crate::container::Container;\n\n    fn is_command_in_path(program: &str) -> bool {\n        if let Ok(path) = env::var(\"PATH\") {\n            for p in path.split(':') {\n                let p_str = format!(\"{p}/{program}\");\n                if fs::metadata(p_str).is_ok() {\n                    return true;\n                }\n            }\n        }\n        false\n    }\n\n    // Note: the run_hook will require the use of pipe to write the container\n    // state into stdin of the hook command. When cargo test runs these tests in\n    // parallel with other tests, the pipe becomes flaky and often we will get\n    // broken pipe or bad file descriptors. There is not much we can do and we\n    // decide not to retry in the test. The most sensible way to test this is\n    // ask cargo test to run these tests in serial.\n\n    #[test]\n    #[serial]\n    fn test_run_hook() -> Result<()> {\n        {\n            let default_container: Container = Default::default();\n            run_hooks(None, Some(&default_container.state), None, None)\n                .context(\"Failed simple test\")?;\n        }\n\n        {\n            assert!(is_command_in_path(\"true\"), \"The true was not found.\");\n            let default_container: Container = Default::default();\n\n            let hook = HookBuilder::default().path(\"true\").build()?;\n            let hooks = Some(vec![hook]);\n            run_hooks(hooks.as_ref(), Some(&default_container.state), None, None)\n                .context(\"Failed true\")?;\n        }\n\n        {\n            assert!(\n                is_command_in_path(\"printenv\"),\n                \"The printenv was not found.\"\n            );\n            // Use `printenv` to make sure the environment is set correctly.\n            let default_container: Container = Default::default();\n            let hook = HookBuilder::default()\n                .path(\"bash\")\n                .args(vec![\n                    String::from(\"bash\"),\n                    String::from(\"-c\"),\n                    String::from(\"printenv key > /dev/null\"),\n                ])\n                .env(vec![String::from(\"key=value\")])\n                .build()?;\n            let hooks = Some(vec![hook]);\n            run_hooks(hooks.as_ref(), Some(&default_container.state), None, None)\n                .context(\"Failed printenv test\")?;\n        }\n\n        {\n            assert!(is_command_in_path(\"pwd\"), \"The pwd was not found.\");\n\n            let tmp = tempfile::tempdir()?;\n\n            let default_container: Container = Default::default();\n            let hook = HookBuilder::default()\n                .path(\"bash\")\n                .args(vec![\n                    String::from(\"bash\"),\n                    String::from(\"-c\"),\n                    format!(\"test $(pwd) = {:?}\", tmp.path()),\n                ])\n                .build()?;\n            let hooks = Some(vec![hook]);\n            run_hooks(\n                hooks.as_ref(),\n                Some(&default_container.state),\n                Some(tmp.path()),\n                None,\n            )\n            .context(\"Failed pwd test\")?;\n        }\n\n        {\n            let default_container: Container = Default::default();\n            let expected_pid = Pid::from_raw(1000);\n\n            let hook = HookBuilder::default()\n                .path(\"bash\")\n                .args(vec![\n                    String::from(\"bash\"),\n                    String::from(\"-c\"),\n                    format!(\"cat | grep '\\\"pid\\\":{}'\", expected_pid),\n                ])\n                .build()?;\n            let hooks = Some(vec![hook]);\n            run_hooks(\n                hooks.as_ref(),\n                Some(&default_container.state),\n                None,\n                Some(expected_pid),\n            )\n            .context(\"Failed pid test\")?;\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    // This will test executing hook with a timeout. Since the timeout is set in\n    // secs, minimally, the test will run for 1 second to trigger the timeout.\n    fn test_run_hook_timeout() -> Result<()> {\n        let default_container: Container = Default::default();\n        // We use `tail -f /dev/null` here to simulate a hook command that hangs.\n        let hook = HookBuilder::default()\n            .path(\"tail\")\n            .args(vec![\n                String::from(\"tail\"),\n                String::from(\"-f\"),\n                String::from(\"/dev/null\"),\n            ])\n            .timeout(1)\n            .build()?;\n        let hooks = Some(vec![hook]);\n        match run_hooks(hooks.as_ref(), Some(&default_container.state), None, None) {\n            Ok(_) => {\n                bail!(\n                    \"The test expects the hook to error out with timeout. Should not execute cleanly\"\n                );\n            }\n            Err(HookError::Timeout) => {}\n            Err(err) => {\n                bail!(\n                    \"The test expects the hook to error out with timeout. Got error: {}\",\n                    err\n                );\n            }\n        };\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/lib.rs",
    "content": "pub mod apparmor;\npub mod capabilities;\npub mod channel;\npub mod config;\npub mod container;\npub mod error;\npub mod hooks;\npub mod namespaces;\npub mod network;\npub mod notify_socket;\npub mod process;\npub mod rootfs;\n#[cfg(feature = \"libseccomp\")]\npub mod seccomp;\npub mod signal;\npub mod syscall;\npub mod test_utils;\npub mod tty;\npub mod user_ns;\npub mod utils;\npub mod workload;\n\n// Because the `libcontainer` api uses the oci_spec who resides in a different\n// crate, we re-export the version of oci_spec this crate uses.\n// Ref: https://github.com/youki-dev/youki/issues/2066\n// Ref: https://github.com/rust-lang/api-guidelines/discussions/176\npub use oci_spec;\n"
  },
  {
    "path": "crates/libcontainer/src/namespaces.rs",
    "content": "//! Namespaces provide isolation of resources for processes at a kernel level.\n//! The namespaces are: Mount (filesystem),\n//! Process (processes in a namespace have two PIDs, one for the global PID,\n//! which is used by the main system and the second one is for the child within the process tree),\n//! Interprocess Communication (Control or communication between processes),\n//! Network (which network devices can be seen by the processes in the namespace), User (User configs),\n//! UTS (hostname and domain information, processes will think they're running on servers with different names),\n//! Cgroup (Resource limits, execution priority etc.)\n\nuse std::collections;\n\nuse nix::sched::CloneFlags;\nuse nix::sys::stat;\nuse nix::{fcntl, unistd};\nuse oci_spec::runtime::{LinuxNamespace, LinuxNamespaceType};\n\nuse crate::syscall::Syscall;\nuse crate::syscall::syscall::create_syscall;\n\ntype Result<T> = std::result::Result<T, NamespaceError>;\n\n#[derive(Debug, thiserror::Error)]\npub enum NamespaceError {\n    #[error(transparent)]\n    Nix(#[from] nix::Error),\n    #[error(transparent)]\n    IO(#[from] std::io::Error),\n    #[error(transparent)]\n    Syscall(#[from] crate::syscall::SyscallError),\n    #[error(\"Namespace type not supported: {0}\")]\n    NotSupported(String),\n}\n\nstatic ORDERED_NAMESPACES: &[CloneFlags] = &[\n    CloneFlags::CLONE_NEWUSER,\n    CloneFlags::CLONE_NEWPID,\n    CloneFlags::CLONE_NEWUTS,\n    CloneFlags::CLONE_NEWIPC,\n    CloneFlags::CLONE_NEWNET,\n    CloneFlags::CLONE_NEWCGROUP,\n    CloneFlags::CLONE_NEWNS,\n];\n\n/// Holds information about namespaces\npub struct Namespaces {\n    command: Box<dyn Syscall>,\n    namespace_map: collections::HashMap<CloneFlags, LinuxNamespace>,\n}\n\nfn get_clone_flag(namespace_type: LinuxNamespaceType) -> Result<CloneFlags> {\n    let flag = match namespace_type {\n        LinuxNamespaceType::User => CloneFlags::CLONE_NEWUSER,\n        LinuxNamespaceType::Pid => CloneFlags::CLONE_NEWPID,\n        LinuxNamespaceType::Uts => CloneFlags::CLONE_NEWUTS,\n        LinuxNamespaceType::Ipc => CloneFlags::CLONE_NEWIPC,\n        LinuxNamespaceType::Network => CloneFlags::CLONE_NEWNET,\n        LinuxNamespaceType::Cgroup => CloneFlags::CLONE_NEWCGROUP,\n        LinuxNamespaceType::Mount => CloneFlags::CLONE_NEWNS,\n        LinuxNamespaceType::Time => return Err(NamespaceError::NotSupported(\"time\".to_string())),\n    };\n\n    Ok(flag)\n}\n\nimpl TryFrom<Option<&Vec<LinuxNamespace>>> for Namespaces {\n    type Error = NamespaceError;\n\n    fn try_from(namespaces: Option<&Vec<LinuxNamespace>>) -> Result<Self> {\n        let command: Box<dyn Syscall> = create_syscall();\n        let namespace_map: collections::HashMap<CloneFlags, LinuxNamespace> = namespaces\n            .unwrap_or(&vec![])\n            .iter()\n            .map(|ns| match get_clone_flag(ns.typ()) {\n                Ok(flag) => Ok((flag, ns.clone())),\n                Err(err) => Err(err),\n            })\n            .collect::<Result<Vec<(CloneFlags, LinuxNamespace)>>>()?\n            .into_iter()\n            .collect();\n\n        Ok(Namespaces {\n            command,\n            namespace_map,\n        })\n    }\n}\n\nimpl Namespaces {\n    pub fn apply_namespaces<F: Fn(CloneFlags) -> bool>(&self, filter: F) -> Result<()> {\n        let to_enter: Vec<(&CloneFlags, &LinuxNamespace)> = ORDERED_NAMESPACES\n            .iter()\n            .filter(|c| filter(**c))\n            .filter_map(|c| self.namespace_map.get_key_value(c))\n            .collect();\n\n        for (_, ns) in to_enter {\n            self.unshare_or_setns(ns)?;\n        }\n        Ok(())\n    }\n\n    pub fn unshare_or_setns(&self, namespace: &LinuxNamespace) -> Result<()> {\n        tracing::debug!(\"unshare or setns: {:?}\", namespace);\n        match namespace.path() {\n            Some(path) => {\n                let fd = fcntl::open(path, fcntl::OFlag::empty(), stat::Mode::empty())\n                    .inspect_err(|err| {\n                        tracing::error!(?err, ?namespace, \"failed to open namespace file\");\n                    })?;\n                self.command\n                    .set_ns(fd, get_clone_flag(namespace.typ())?)\n                    .map_err(|err| {\n                        tracing::error!(?err, ?namespace, \"failed to set namespace\");\n                        err\n                    })?;\n                unistd::close(fd).inspect_err(|err| {\n                    tracing::error!(?err, ?namespace, \"failed to close namespace file\");\n                })?;\n            }\n            None => {\n                self.command\n                    .unshare(get_clone_flag(namespace.typ())?)\n                    .map_err(|err| {\n                        tracing::error!(?err, ?namespace, \"failed to unshare namespace\");\n                        err\n                    })?;\n            }\n        }\n\n        Ok(())\n    }\n\n    pub fn get(&self, k: LinuxNamespaceType) -> Result<Option<&LinuxNamespace>> {\n        Ok(self.namespace_map.get(&get_clone_flag(k)?))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use oci_spec::runtime::{LinuxNamespaceBuilder, LinuxNamespaceType};\n    use serial_test::serial;\n\n    use super::*;\n    use crate::syscall::test::TestHelperSyscall;\n\n    fn gen_sample_linux_namespaces() -> Vec<LinuxNamespace> {\n        vec![\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Mount)\n                .path(\"/dev/null\")\n                .build()\n                .unwrap(),\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Network)\n                .path(\"/dev/null\")\n                .build()\n                .unwrap(),\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Pid)\n                .build()\n                .unwrap(),\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::User)\n                .build()\n                .unwrap(),\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Ipc)\n                .build()\n                .unwrap(),\n        ]\n    }\n\n    #[test]\n    #[serial]\n    fn test_apply_namespaces() {\n        let sample_linux_namespaces = gen_sample_linux_namespaces();\n        let namespaces = Namespaces::try_from(Some(&sample_linux_namespaces))\n            .expect(\"create namespace struct should be good\");\n        let test_command: &TestHelperSyscall = namespaces.command.as_any().downcast_ref().unwrap();\n        assert!(\n            namespaces\n                .apply_namespaces(|ns_type| { ns_type != CloneFlags::CLONE_NEWIPC })\n                .is_ok()\n        );\n\n        let mut setns_args: Vec<_> = test_command\n            .get_setns_args()\n            .into_iter()\n            .map(|(_fd, cf)| cf)\n            .collect();\n        setns_args.sort();\n        let mut expect = vec![CloneFlags::CLONE_NEWNS, CloneFlags::CLONE_NEWNET];\n        expect.sort();\n        assert_eq!(setns_args, expect);\n\n        let mut unshare_args = test_command.get_unshare_args();\n        unshare_args.sort();\n        let mut expect = vec![CloneFlags::CLONE_NEWUSER, CloneFlags::CLONE_NEWPID];\n        expect.sort();\n        assert_eq!(unshare_args, expect)\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/address.rs",
    "content": "use std::net::{IpAddr, Ipv4Addr};\n\nuse netlink_packet_core::{\n    NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP, NLM_F_EXCL, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload,\n};\nuse netlink_packet_route::address::{AddressAttribute, AddressMessage};\nuse netlink_packet_route::{AddressFamily, RouteNetlinkMessage};\n\nuse crate::network::traits::{Client, NetlinkMessageHandler};\nuse crate::network::wrapper::ClientWrapper;\nuse crate::network::{NetlinkResponse, NetworkError, Result};\n\n/// Handler for Address messages in Netlink communication.\n///\n/// This handler processes Netlink messages related to network addresses\n/// and converts them into AddressMessage responses.\npub struct AddressMessageHandler {\n    target_index: Option<u32>,\n}\n\nimpl AddressMessageHandler {\n    pub fn new() -> Self {\n        Self { target_index: None }\n    }\n\n    pub fn with_index(index: u32) -> Self {\n        Self {\n            target_index: Some(index),\n        }\n    }\n}\n\nimpl Default for AddressMessageHandler {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl NetlinkMessageHandler for AddressMessageHandler {\n    type Response = AddressMessage;\n\n    fn handle_payload(\n        &self,\n        payload: NetlinkPayload<RouteNetlinkMessage>,\n    ) -> Result<NetlinkResponse<Self::Response>> {\n        match payload {\n            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr)) => {\n                if let Some(target_index) = self.target_index {\n                    if addr.header.index == target_index {\n                        Ok(NetlinkResponse::Success(addr))\n                    } else {\n                        Ok(NetlinkResponse::None)\n                    }\n                } else {\n                    Ok(NetlinkResponse::Success(addr))\n                }\n            }\n            NetlinkPayload::Error(e) => match e.code {\n                None => Ok(NetlinkResponse::Success(AddressMessage::default())),\n                Some(code) => Ok(NetlinkResponse::Error(code.get())),\n            },\n            NetlinkPayload::Done(_) => Ok(NetlinkResponse::Done),\n            _ => Err(NetworkError::IO(std::io::Error::other(format!(\n                \"Unexpected message type: {:?}\",\n                payload\n            )))),\n        }\n    }\n}\n\n/// Client for managing network addresses.\n///\n/// This client provides methods for querying and modifying network address properties\n/// through Netlink communication.\npub struct AddressClient {\n    client: ClientWrapper,\n}\n\nimpl AddressClient {\n    /// Creates a new AddressClient instance.\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a new AddressClient or an IO error\n    pub fn new(client: ClientWrapper) -> Result<Self> {\n        Ok(Self { client })\n    }\n\n    /// Retrieves all addresses associated with a network interface.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a vector of AddressMessages or an error\n    pub fn get_by_index(&mut self, index: u32) -> Result<Vec<AddressMessage>> {\n        let mut message = AddressMessage::default();\n        message.header.index = index;\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::GetAddress(message));\n        // NLM_F_REQUEST: This is a request to the kernel\n        // NLM_F_DUMP: Request a dump of all matching entries\n        req.header.flags = NLM_F_REQUEST | NLM_F_DUMP;\n        req.finalize();\n\n        let handler = AddressMessageHandler::with_index(index);\n\n        self.client.send_and_receive_multiple(&req, handler)\n    }\n\n    /// Adds a new address to a network interface.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface\n    /// * `address` - The IP address to add\n    /// * `prefix_len` - The prefix length of the address\n    ///\n    /// # Returns\n    ///\n    /// A Result indicating success or failure of the operation\n    pub fn add(&mut self, index: u32, address: IpAddr, prefix_len: u8) -> Result<()> {\n        let message = self.create_address_request(index, address, prefix_len)?;\n\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::NewAddress(message));\n        // NLM_F_REQUEST: This is a request to the kernel\n        // NLM_F_ACK: Request an acknowledgment from the kernel\n        // NLM_F_EXCL: Fail if the address already exists\n        // NLM_F_CREATE: Create the address if it doesn't exist\n        req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;\n        req.finalize();\n\n        let handler = AddressMessageHandler::new();\n\n        self.client.send_and_receive(&req, handler)?;\n        Ok(())\n    }\n\n    /// Creates an address request message.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface\n    /// * `address` - The IP address to add\n    /// * `prefix_len` - The prefix length of the address\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either the created AddressMessage or an error\n    /// ref: https://github.com/rust-netlink/rtnetlink/blob/v0.17.0/src/addr/add.rs#L27-L73\n    fn create_address_request(\n        &self,\n        index: u32,\n        address: IpAddr,\n        prefix_len: u8,\n    ) -> Result<AddressMessage> {\n        let mut message = AddressMessage::default();\n        message.header.prefix_len = prefix_len;\n        message.header.index = index;\n        message.header.family = match address {\n            IpAddr::V4(_) => AddressFamily::Inet,\n            IpAddr::V6(_) => AddressFamily::Inet6,\n        };\n\n        if address.is_multicast() {\n            if let IpAddr::V6(a) = address {\n                message.attributes.push(AddressAttribute::Multicast(a));\n            }\n        } else {\n            message.attributes.push(AddressAttribute::Address(address));\n            message.attributes.push(AddressAttribute::Local(address));\n\n            if let IpAddr::V4(a) = address {\n                if prefix_len == 32 {\n                    message.attributes.push(AddressAttribute::Broadcast(a));\n                } else {\n                    let ip_addr = u32::from(a);\n                    let brd =\n                        Ipv4Addr::from(((0xffff_ffff_u32) >> u32::from(prefix_len)) | ip_addr);\n                    message.attributes.push(AddressAttribute::Broadcast(brd));\n                };\n            }\n        }\n\n        Ok(message)\n    }\n\n    #[cfg(test)]\n    /// Test method to get send calls from the fake client\n    pub fn get_send_calls(\n        &self,\n    ) -> Option<&[netlink_packet_core::NetlinkMessage<RouteNetlinkMessage>]> {\n        if let ClientWrapper::Fake(fake_client) = &self.client {\n            Some(fake_client.get_send_calls())\n        } else {\n            None\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use serial_test::serial;\n\n    use super::*;\n    use crate::network::fake::FakeNetlinkClient;\n    use crate::network::wrapper::create_network_client;\n\n    #[test]\n    #[serial]\n    fn test_address_message_handler_success() {\n        let handler = AddressMessageHandler::new();\n        let mut addr_msg = AddressMessage::default();\n        addr_msg.header.index = 1;\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(\n                192, 168, 1, 1,\n            ))));\n\n        let payload =\n            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr_msg.clone()));\n        let result = handler.handle_payload(payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Success(response) => {\n                assert_eq!(response.header.index, 1);\n                assert_eq!(response.attributes.len(), 1);\n            }\n            _ => panic!(\"Expected Success response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_message_handler_errorcode_zero() {\n        let handler = AddressMessageHandler::new();\n        let mut error_msg = netlink_packet_core::ErrorMessage::default();\n        error_msg.code = std::num::NonZeroI32::new(0);\n        let error_payload = NetlinkPayload::Error(error_msg);\n        let result = handler.handle_payload(error_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Success(_) => {}\n            _ => panic!(\"Expected Success response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_message_handler_error() {\n        let handler = AddressMessageHandler::new();\n        let mut error_msg = netlink_packet_core::ErrorMessage::default();\n        error_msg.code = std::num::NonZeroI32::new(1);\n        let error_payload = NetlinkPayload::Error(error_msg);\n        let result = handler.handle_payload(error_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Error(code) => {\n                assert_eq!(code, 1);\n            }\n            _ => panic!(\"Expected Error response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_message_handler_done() {\n        let handler = AddressMessageHandler::new();\n        let done_payload = NetlinkPayload::Done(netlink_packet_core::DoneMessage::default());\n        let result = handler.handle_payload(done_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Done => {}\n            _ => panic!(\"Expected Done response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_message_handler_unexpected() {\n        let handler = AddressMessageHandler::new();\n        let unexpected_payload = NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(\n            netlink_packet_route::link::LinkMessage::default(),\n        ));\n        let result = handler.handle_payload(unexpected_payload);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_new() {\n        let result = AddressClient::new(create_network_client());\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_get_by_index_failure() {\n        let mut fake_client = FakeNetlinkClient::new();\n        fake_client.set_failure(\"Get by index failed\".to_string());\n\n        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = addr_client.get_by_index(1);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_get_by_index_without_response() {\n        let fake_client = FakeNetlinkClient::new();\n        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = addr_client.get_by_index(1);\n\n        // Should failed without response\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_get_by_index_with_multiple_responses() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        // Set up multiple responses\n        let mut addr1 = AddressMessage::default();\n        addr1.header.index = 1;\n        addr1\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(\n                192, 168, 1, 1,\n            ))));\n\n        let mut addr2 = AddressMessage::default();\n        addr2.header.index = 1;\n        addr2\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(\n                192, 168, 1, 2,\n            ))));\n\n        let responses = vec![\n            RouteNetlinkMessage::NewAddress(addr1),\n            RouteNetlinkMessage::NewAddress(addr2),\n        ];\n        fake_client.set_expected_responses(responses);\n\n        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = addr_client.get_by_index(1);\n\n        // Should succeed with multiple responses\n        assert!(result.is_ok());\n        let responses = result.unwrap();\n        assert_eq!(responses.len(), 2);\n        assert_eq!(responses[0].header.index, 1);\n        assert_eq!(responses[1].header.index, 1);\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_get_by_index_success() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        let responses = vec![RouteNetlinkMessage::NewAddress(AddressMessage::default())];\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut addr_client = AddressClient::new(client_wrapper).unwrap();\n\n        let result = addr_client.get_by_index(42);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let Some(send_calls) = addr_client.get_send_calls() {\n            assert_eq!(send_calls.len(), 1);\n\n            // Verify the message details\n            if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::GetAddress(addr)) =\n                &send_calls[0].payload\n            {\n                assert_eq!(addr.header.index, 42);\n            } else {\n                panic!(\"Expected GetAddress message\");\n            }\n\n            // Verify the netlink flags\n            let expected_flags = NLM_F_REQUEST | NLM_F_DUMP;\n            assert_eq!(send_calls[0].header.flags, expected_flags);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_add_failure() {\n        let mut fake_client = FakeNetlinkClient::new();\n        fake_client.set_failure(\"Add address failed\".to_string());\n\n        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = addr_client.add(1, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 24);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_add_success() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        let responses = vec![RouteNetlinkMessage::NewAddress(AddressMessage::default())];\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut addr_client = AddressClient::new(client_wrapper).unwrap();\n\n        let result = addr_client.add(42, IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 16);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let Some(send_calls) = addr_client.get_send_calls() {\n            assert_eq!(send_calls.len(), 1);\n\n            // Verify the message details\n            if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr)) =\n                &send_calls[0].payload\n            {\n                assert_eq!(addr.header.index, 42);\n                assert_eq!(addr.header.prefix_len, 16);\n                assert_eq!(addr.header.family, AddressFamily::Inet);\n                assert_eq!(addr.attributes.len(), 3); // Address, Local, Broadcast\n\n                // Check for Address attribute\n                let mut found_address = false;\n                for attr in &addr.attributes {\n                    if let AddressAttribute::Address(ip) = attr {\n                        assert_eq!(*ip, IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)));\n                        found_address = true;\n                        break;\n                    }\n                }\n                assert!(found_address, \"Address attribute not found\");\n            } else {\n                panic!(\"Expected NewAddress message\");\n            }\n\n            // Verify the netlink flags\n            let expected_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;\n            assert_eq!(send_calls[0].header.flags, expected_flags);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_address_client_add_with_different_parameters() {\n        let mut fake_client = FakeNetlinkClient::new();\n        let test_cases = vec![\n            (1, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 24),\n            (10, IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 16),\n            (\n                100,\n                IpAddr::V6(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),\n                64,\n            ),\n        ];\n        let responses: Vec<_> = test_cases\n            .iter()\n            .map(|(index, address, prefix_len)| {\n                let mut msg = AddressMessage::default();\n                msg.header.index = *index;\n                msg.header.prefix_len = *prefix_len;\n                msg.header.family = match address {\n                    IpAddr::V4(_) => AddressFamily::Inet,\n                    IpAddr::V6(_) => AddressFamily::Inet6,\n                };\n                msg.attributes.push(AddressAttribute::Address(*address));\n                msg.attributes.push(AddressAttribute::Local(*address));\n                if let IpAddr::V4(a) = address {\n                    msg.attributes.push(AddressAttribute::Broadcast(*a));\n                }\n                RouteNetlinkMessage::NewAddress(msg)\n            })\n            .collect();\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut addr_client = AddressClient::new(client_wrapper).unwrap();\n\n        // Test with different parameters\n        let test_cases_clone = test_cases.clone();\n\n        for (index, address, prefix_len) in test_cases {\n            let result = addr_client.add(index, address, prefix_len);\n            assert!(\n                result.is_ok(),\n                \"add failed for index {}, address {:?}, prefix_len {}\",\n                index,\n                address,\n                prefix_len\n            );\n        }\n\n        // Verify all calls were tracked\n        if let Some(send_calls) = addr_client.get_send_calls() {\n            assert_eq!(send_calls.len(), test_cases_clone.len());\n\n            for (i, (index, address, prefix_len)) in test_cases_clone.iter().enumerate() {\n                if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(addr)) =\n                    &send_calls[i].payload\n                {\n                    assert_eq!(addr.header.index, *index);\n                    assert_eq!(addr.header.prefix_len, *prefix_len);\n\n                    // Check address family\n                    let expected_family = match address {\n                        IpAddr::V4(_) => AddressFamily::Inet,\n                        IpAddr::V6(_) => AddressFamily::Inet6,\n                    };\n                    assert_eq!(addr.header.family, expected_family);\n\n                    // Check for Address attribute\n                    let mut found_address = false;\n                    for attr in &addr.attributes {\n                        if let AddressAttribute::Address(ip) = attr {\n                            assert_eq!(*ip, *address);\n                            found_address = true;\n                            break;\n                        }\n                    }\n                    assert!(\n                        found_address,\n                        \"Address attribute not found for index {}\",\n                        index\n                    );\n                } else {\n                    panic!(\"Expected NewAddress message for index {}\", index);\n                }\n            }\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_create_address_request_ipv4() {\n        let addr_client = AddressClient::new(create_network_client()).unwrap();\n        let result =\n            addr_client.create_address_request(1, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 24);\n\n        assert!(result.is_ok());\n        let message = result.unwrap();\n        assert_eq!(message.header.index, 1);\n        assert_eq!(message.header.prefix_len, 24);\n        assert_eq!(message.header.family, AddressFamily::Inet);\n        assert_eq!(message.attributes.len(), 3); // Address, Local, Broadcast\n    }\n\n    #[test]\n    #[serial]\n    fn test_create_address_request_ipv6() {\n        let addr_client = AddressClient::new(create_network_client()).unwrap();\n        let result = addr_client.create_address_request(\n            1,\n            IpAddr::V6(std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)),\n            64,\n        );\n\n        assert!(result.is_ok());\n        let message = result.unwrap();\n        assert_eq!(message.header.index, 1);\n        assert_eq!(message.header.prefix_len, 64);\n        assert_eq!(message.header.family, AddressFamily::Inet6);\n        assert_eq!(message.attributes.len(), 2); // Address, Local\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/cidr.rs",
    "content": "use std::net::IpAddr;\n\nuse netlink_packet_route::address::{AddressAttribute, AddressMessage};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct CidrAddress {\n    pub prefix_len: u8,\n    pub address: IpAddr,\n}\n\nimpl From<&AddressMessage> for CidrAddress {\n    fn from(msg: &AddressMessage) -> Self {\n        let address =\n            parse_ip_address(msg).expect(\"AddressMessage without IFA_LOCAL or IFA_ADDRESS\");\n        CidrAddress {\n            prefix_len: msg.header.prefix_len,\n            address,\n        }\n    }\n}\n\n/// Parses the IP address from an AddressMessage following libnl conventions.\n///\n/// From libnl addr.c:\n/// - IPv6 sends the local address as IFA_ADDRESS with no IFA_LOCAL\n/// - IPv4 sends both IFA_LOCAL and IFA_ADDRESS, with IFA_ADDRESS being the peer address if they differ\n/// - For IPv6 Point-to-Point addresses, IFA_LOCAL should also be handled\n///\n/// Priority:\n/// 1. If IFA_LOCAL exists, use it (this handles IPv4 and IPv6 PtP correctly)\n/// 2. Otherwise, fall back to IFA_ADDRESS (this handles regular IPv6)\n///\n/// Note: While the RFC does not explicitly prohibit multiple IFA_ADDRESS attributes,\n/// common implementations (libnl, vishvananda/netlink) assume a single instance.\n/// This implementation follows the same assumption and uses the first matching attribute.\nfn parse_ip_address(addr: &AddressMessage) -> Option<IpAddr> {\n    // First, try to find IFA_LOCAL\n    let local = addr.attributes.iter().find_map(|attr| match attr {\n        AddressAttribute::Local(ip) => Some(*ip),\n        _ => None,\n    });\n\n    // If IFA_LOCAL exists, use it\n    if let Some(ip) = local {\n        return Some(ip);\n    }\n\n    // Otherwise, fall back to IFA_ADDRESS\n    addr.attributes.iter().find_map(|attr| match attr {\n        AddressAttribute::Address(ip) => Some(*ip),\n        _ => None,\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use std::net::{IpAddr, Ipv4Addr};\n\n    use netlink_packet_route::AddressFamily;\n    use netlink_packet_route::address::{AddressFlags, AddressMessage, AddressScope};\n\n    use super::*;\n\n    #[test]\n    fn test_address_message_to_cidr() {\n        let mut msg = AddressMessage::default();\n        msg.header.index = 10;\n        msg.header.prefix_len = 24;\n        msg.header.family = AddressFamily::Inet;\n        msg.header.scope = AddressScope::Universe;\n        let ip = \"192.168.1.1\".parse().unwrap();\n        msg.attributes.push(AddressAttribute::Address(ip));\n        msg.attributes\n            .push(AddressAttribute::Flags(AddressFlags::Permanent));\n\n        let cidr = CidrAddress::from(&msg);\n        assert_eq!(cidr.prefix_len, 24);\n        assert_eq!(cidr.address, ip);\n    }\n\n    #[test]\n    fn test_parse_ip_address_with_local() {\n        // Test IPv4 with IFA_LOCAL (typical IPv4 case)\n        let mut addr_msg = AddressMessage::default();\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Local(IpAddr::V4(Ipv4Addr::new(\n                10, 0, 0, 1,\n            ))));\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(\n                10, 0, 0, 2,\n            ))));\n\n        let result = parse_ip_address(&addr_msg);\n        assert_eq!(result, Some(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1))));\n    }\n\n    #[test]\n    fn test_parse_ip_address_without_local() {\n        // Test IPv6 without IFA_LOCAL (typical IPv6 case)\n        let mut addr_msg = AddressMessage::default();\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V6(\n                std::net::Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1),\n            )));\n\n        let result = parse_ip_address(&addr_msg);\n        assert_eq!(\n            result,\n            Some(IpAddr::V6(std::net::Ipv6Addr::new(\n                0x2001, 0xdb8, 0, 0, 0, 0, 0, 1\n            )))\n        );\n    }\n\n    #[test]\n    fn test_parse_ip_address_ipv6_with_local() {\n        // Test IPv6 PtP with IFA_LOCAL\n        let mut addr_msg = AddressMessage::default();\n        addr_msg.attributes.push(AddressAttribute::Local(IpAddr::V6(\n            std::net::Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 1),\n        )));\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V6(\n                std::net::Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 2),\n            )));\n\n        let result = parse_ip_address(&addr_msg);\n        assert_eq!(\n            result,\n            Some(IpAddr::V6(std::net::Ipv6Addr::new(\n                0xfe80, 0, 0, 0, 0, 0, 0, 1\n            )))\n        );\n    }\n\n    #[test]\n    fn test_parse_ip_address_no_attributes() {\n        // Test with no address attributes\n        let addr_msg = AddressMessage::default();\n\n        let result = parse_ip_address(&addr_msg);\n        assert_eq!(result, None);\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/client.rs",
    "content": "use netlink_packet_core::NetlinkMessage;\nuse netlink_packet_route::RouteNetlinkMessage;\nuse netlink_sys::Socket;\nuse netlink_sys::protocols::NETLINK_ROUTE;\n\nuse super::traits::{Client, NetlinkMessageHandler};\nuse super::{NetlinkResponse, NetworkError, Result};\n\n/// Base client for Netlink communication.\n///\n/// This client provides the core functionality for sending and receiving Netlink messages.\n/// It manages the underlying socket connection and provides methods for message handling.\npub struct NetlinkClient {\n    socket: Socket,\n}\n\nimpl NetlinkClient {\n    /// Creates a new NetlinkClient instance.\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a new NetlinkClient or an IO error\n    pub fn new() -> Result<Self> {\n        let mut socket = Socket::new(NETLINK_ROUTE)?;\n        socket.bind_auto()?;\n        Ok(Self { socket })\n    }\n}\n\nimpl Client for NetlinkClient {\n    fn send(&mut self, req: &NetlinkMessage<RouteNetlinkMessage>) -> Result<()> {\n        let mut send_buf = vec![0; req.header.length as usize];\n        req.serialize(&mut send_buf[..]);\n        self.socket.send(&send_buf[..], 0)?;\n        Ok(())\n    }\n\n    fn receive<T, H>(&mut self, handler: H) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        let mut receive_buf = vec![0u8; 4096];\n        let n_received = self.socket.recv(&mut &mut receive_buf[..], 0)?;\n        let bytes = &receive_buf[..n_received];\n\n        let rx_packet = <NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes).map_err(|e| {\n            NetworkError::IO(std::io::Error::other(format!(\n                \"Deserialization error: {}\",\n                e\n            )))\n        })?;\n\n        match handler.handle_payload(rx_packet.payload)? {\n            NetlinkResponse::Success(response) => Ok(response),\n            NetlinkResponse::Error(code) => Err(NetworkError::IO(std::io::Error::other(format!(\n                \"Netlink error: {}\",\n                code\n            )))),\n            NetlinkResponse::Done => Err(NetworkError::IO(std::io::Error::other(\n                \"Unexpected done message\",\n            ))),\n            NetlinkResponse::None => Err(NetworkError::IO(std::io::Error::other(\n                \"Unexpected none message\",\n            ))),\n        }\n    }\n\n    fn receive_multiple<T, H>(&mut self, handler: H) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        let mut receive_buf = vec![0u8; 4096];\n        let mut responses = Vec::new();\n        let mut offset = 0;\n\n        loop {\n            let n_received = self.socket.recv(&mut &mut receive_buf[..], 0)?;\n            loop {\n                let bytes = &receive_buf[offset..];\n                let rx_packet = <NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes)\n                    .map_err(|e| std::io::Error::other(format!(\"Deserialization error: {}\", e)))?;\n\n                match handler.handle_payload(rx_packet.payload)? {\n                    NetlinkResponse::Success(response) => responses.push(response),\n                    NetlinkResponse::Error(code) => {\n                        return Err(NetworkError::IO(std::io::Error::other(format!(\n                            \"Netlink error: code={}\",\n                            code\n                        ))));\n                    }\n                    NetlinkResponse::Done => return Ok(responses),\n                    NetlinkResponse::None => {}\n                }\n\n                offset += rx_packet.header.length as usize;\n                if offset == n_received || rx_packet.header.length == 0 {\n                    offset = 0;\n                    break;\n                }\n            }\n        }\n    }\n\n    fn send_and_receive<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        self.send(req)?;\n        self.receive(handler)\n    }\n\n    fn send_and_receive_multiple<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        self.send(req)?;\n        self.receive_multiple(handler)\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/fake.rs",
    "content": "use std::collections::VecDeque;\n\nuse netlink_packet_core::{NetlinkMessage, NetlinkPayload};\nuse netlink_packet_route::RouteNetlinkMessage;\n\nuse super::traits::{Client, NetlinkMessageHandler};\nuse super::{NetlinkResponse, NetworkError, Result};\n\n#[derive(Clone)]\npub enum FakeResponse {\n    Success(RouteNetlinkMessage),\n    Error(String),\n}\n\n/// Fake implementation of NetlinkClient for testing.\n///\n/// This fake client allows you to predefine responses for specific requests,\n/// making it easy to test different scenarios without requiring actual\n/// network operations.\n#[derive(Clone)]\npub struct FakeNetlinkClient {\n    send_calls: Vec<NetlinkMessage<RouteNetlinkMessage>>,\n    expected_responses: VecDeque<FakeResponse>,\n}\nimpl FakeNetlinkClient {\n    /// Creates a new FakeNetlinkClient instance.\n    pub fn new() -> Self {\n        Self {\n            send_calls: Vec::new(),\n            expected_responses: VecDeque::new(),\n        }\n    }\n    /// Sets the fake to fail with a specific error message.\n    ///\n    /// # Arguments\n    ///\n    /// * `error_message` - The error message to return\n    pub fn set_failure(&mut self, error_message: String) {\n        self.expected_responses\n            .push_back(FakeResponse::Error(error_message));\n    }\n\n    /// Sets multiple expected responses for multiple message handlers.\n    ///\n    /// # Arguments\n    ///\n    /// * `responses` - Vector of RouteNetlinkMessage responses to return\n    pub fn set_expected_responses(&mut self, responses: Vec<RouteNetlinkMessage>) {\n        for response in responses {\n            self.expected_responses\n                .push_back(FakeResponse::Success(response));\n        }\n    }\n\n    /// Gets the list of send calls made to this fake.\n    pub fn get_send_calls(&self) -> &[NetlinkMessage<RouteNetlinkMessage>] {\n        &self.send_calls\n    }\n    /// Clears the send calls history.\n    pub fn clear_send_calls(&mut self) {\n        self.send_calls.clear();\n    }\n}\nimpl Default for FakeNetlinkClient {\n    fn default() -> Self {\n        Self::new()\n    }\n}\nimpl Client for FakeNetlinkClient {\n    fn send(&mut self, req: &NetlinkMessage<RouteNetlinkMessage>) -> Result<()> {\n        self.send_calls.push(req.clone());\n        Ok(())\n    }\n    fn receive<T, H>(&mut self, handler: H) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        if let Some(resp) = self.expected_responses.pop_front() {\n            match resp {\n                FakeResponse::Success(msg) => {\n                    let payload = NetlinkPayload::InnerMessage(msg);\n                    match handler.handle_payload(payload) {\n                        Ok(NetlinkResponse::Success(response)) => Ok(response),\n                        Ok(NetlinkResponse::Error(code)) => Err(NetworkError::IO(\n                            std::io::Error::other(format!(\"Netlink error: {}\", code)),\n                        )),\n                        Ok(NetlinkResponse::Done) => Err(NetworkError::IO(std::io::Error::other(\n                            \"Unexpected done message\",\n                        ))),\n                        Ok(NetlinkResponse::None) => Err(NetworkError::IO(std::io::Error::other(\n                            \"Unexpected none message\",\n                        ))),\n                        Err(e) => Err(e),\n                    }\n                }\n                FakeResponse::Error(msg) => Err(NetworkError::IO(std::io::Error::other(msg))),\n            }\n        } else {\n            Err(NetworkError::IO(std::io::Error::other(\n                \"No fake response set\",\n            )))\n        }\n    }\n    fn receive_multiple<T, H>(&mut self, handler: H) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        let mut responses = Vec::new();\n        while let Some(resp) = self.expected_responses.pop_front() {\n            match resp {\n                FakeResponse::Success(msg) => {\n                    let payload = NetlinkPayload::InnerMessage(msg);\n                    match handler.handle_payload(payload) {\n                        Ok(NetlinkResponse::Success(response)) => responses.push(response),\n                        Ok(NetlinkResponse::Error(code)) => {\n                            return Err(NetworkError::IO(std::io::Error::other(format!(\n                                \"Netlink error: {}\",\n                                code\n                            ))));\n                        }\n                        Ok(NetlinkResponse::Done) => break,\n                        Ok(NetlinkResponse::None) => continue,\n                        Err(e) => return Err(e),\n                    }\n                }\n                FakeResponse::Error(msg) => {\n                    return Err(NetworkError::IO(std::io::Error::other(msg)));\n                }\n            }\n        }\n        Ok(responses)\n    }\n\n    fn send_and_receive<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        self.send(req)?;\n        self.receive(handler)\n    }\n    fn send_and_receive_multiple<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        self.send(req)?;\n        self.receive_multiple(handler)\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/link.rs",
    "content": "use std::os::fd::RawFd;\n\nuse netlink_packet_core::{\n    NLM_F_ACK, NLM_F_CREATE, NLM_F_EXCL, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload,\n};\nuse netlink_packet_route::RouteNetlinkMessage;\nuse netlink_packet_route::link::{LinkAttribute, LinkFlags, LinkMessage};\n\nuse super::traits::{Client, NetlinkMessageHandler};\nuse super::wrapper::ClientWrapper;\nuse super::{NetlinkResponse, NetworkError, Result};\n\n/// Handler for Link messages in Netlink communication.\n///\n/// This handler processes Netlink messages related to network interfaces (links)\n/// and converts them into LinkMessage responses.\npub struct LinkMessageHandler;\n\nimpl NetlinkMessageHandler for LinkMessageHandler {\n    type Response = LinkMessage;\n\n    fn handle_payload(\n        &self,\n        payload: NetlinkPayload<RouteNetlinkMessage>,\n    ) -> Result<NetlinkResponse<Self::Response>> {\n        match payload {\n            NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link)) => {\n                Ok(NetlinkResponse::Success(link))\n            }\n            NetlinkPayload::Error(e) => match e.code {\n                // According to netlink(7), when e.code is 0, it indicates success (acknowledgement)\n                // rather than an error. This is used for ACK messages where the operation succeeded.\n                // See: https://www.man7.org/linux/man-pages/man7/netlink.7.html\n                None => Ok(NetlinkResponse::Success(LinkMessage::default())),\n                Some(code) => Ok(NetlinkResponse::Error(code.get())),\n            },\n            NetlinkPayload::Done(_) => Ok(NetlinkResponse::Done),\n            _ => Err(NetworkError::IO(std::io::Error::other(format!(\n                \"Unexpected message type: {:?}\",\n                payload\n            )))),\n        }\n    }\n}\n\n/// Client for managing network interfaces (links).\n///\n/// This client provides methods for querying and modifying network interface properties\n/// through Netlink communication.\npub struct LinkClient {\n    client: ClientWrapper,\n}\n\nimpl LinkClient {\n    /// Creates a new LinkClient instance.\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a new LinkClient or an IO error\n    pub fn new(client: ClientWrapper) -> Result<Self> {\n        Ok(Self { client })\n    }\n\n    /// Retrieves a network interface by its name.\n    ///\n    /// # Arguments\n    ///\n    /// * `name` - The name of the network interface to retrieve\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either the LinkMessage for the interface or an error\n    pub fn get_by_name(&mut self, name: &str) -> Result<LinkMessage> {\n        let mut message = LinkMessage::default();\n        message\n            .attributes\n            .push(LinkAttribute::IfName(name.to_string()));\n\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::GetLink(message));\n        req.header.flags = NLM_F_REQUEST;\n        req.finalize();\n\n        self.client.send_and_receive(&req, LinkMessageHandler)\n    }\n\n    /// Sets a network interface to the up state.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface to modify\n    ///\n    /// # Returns\n    ///\n    /// A Result indicating success or failure of the operation\n    pub fn set_up(&mut self, index: u32) -> Result<()> {\n        let mut message = LinkMessage::default();\n        message.header.index = index;\n\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::SetLink(message));\n        if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::SetLink(ref mut link)) =\n            req.payload\n        {\n            // change_mask specifies which flags we want to modify\n            link.header.change_mask |= LinkFlags::Up;\n            // Set the Up flag to bring the interface up\n            link.header.flags |= LinkFlags::Up;\n        }\n        // NLM_F_REQUEST: This is a request to the kernel\n        // NLM_F_ACK: Request an acknowledgment from the kernel\n        req.header.flags = NLM_F_REQUEST | NLM_F_ACK;\n        req.finalize();\n\n        self.client.send_and_receive(&req, LinkMessageHandler)?;\n        Ok(())\n    }\n\n    /// Sets a network interface to the down state.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface to modify\n    ///\n    /// # Returns\n    ///\n    /// A Result indicating success or failure of the operation\n    pub fn set_down(&mut self, index: u32) -> Result<()> {\n        let mut message = LinkMessage::default();\n        message.header.index = index;\n\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::SetLink(message));\n        if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::SetLink(ref mut link)) =\n            req.payload\n        {\n            // change_mask specifies which flags we want to modify\n            link.header.change_mask |= LinkFlags::Up;\n            // Remove the Up flag to bring the interface down\n            link.header.flags.remove(LinkFlags::Up);\n        }\n        // NLM_F_REQUEST: This is a request to the kernel\n        // NLM_F_ACK: Request an acknowledgment from the kernel\n        req.header.flags = NLM_F_REQUEST | NLM_F_ACK;\n        req.finalize();\n\n        self.client.send_and_receive(&req, LinkMessageHandler)?;\n        Ok(())\n    }\n\n    /// Moves a network interface to a different network namespace.\n    ///\n    /// # Arguments\n    ///\n    /// * `index` - The index of the network interface to move\n    /// * `new_name` - The new name for the interface in the target namespace\n    /// * `ns_path` - The file descriptor of the target network namespace\n    ///\n    /// # Returns\n    ///\n    /// A Result indicating success or failure of the operation\n    pub fn set_ns_fd(&mut self, index: u32, new_name: &str, ns_path: RawFd) -> Result<()> {\n        let mut message = LinkMessage::default();\n        message.header.index = index;\n        message\n            .attributes\n            .push(LinkAttribute::IfName(new_name.to_string()));\n        message.attributes.push(LinkAttribute::NetNsFd(ns_path));\n\n        let mut req = NetlinkMessage::from(RouteNetlinkMessage::SetLink(message));\n        // NLM_F_REQUEST: This is a request to the kernel\n        // NLM_F_ACK: Request an acknowledgment from the kernel\n        // NLM_F_EXCL: Fail if the interface name already exists in the target namespace\n        // NLM_F_CREATE: Create the interface if it doesn't exist\n        req.header.flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;\n        req.finalize();\n\n        self.client.send_and_receive(&req, LinkMessageHandler)?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use serial_test::serial;\n\n    use super::*;\n    use crate::network::NetlinkResponse;\n    use crate::network::fake::FakeNetlinkClient;\n    use crate::network::wrapper::{ClientWrapper, create_network_client};\n\n    #[test]\n    #[serial]\n    fn test_link_message_handler_success() {\n        let handler = LinkMessageHandler;\n        let mut link_msg = LinkMessage::default();\n        link_msg.header.index = 1;\n        link_msg\n            .attributes\n            .push(LinkAttribute::IfName(\"eth0\".to_string()));\n\n        let payload = NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link_msg.clone()));\n        let result = handler.handle_payload(payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Success(response) => {\n                assert_eq!(response.header.index, 1);\n                assert_eq!(response.attributes.len(), 1);\n            }\n            _ => panic!(\"Expected Success response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_message_handler_errorcode_zero() {\n        let handler = LinkMessageHandler;\n        let mut error_msg = netlink_packet_core::ErrorMessage::default();\n        error_msg.code = std::num::NonZeroI32::new(0);\n        let error_payload = NetlinkPayload::Error(error_msg);\n        let result = handler.handle_payload(error_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Success(_) => {}\n            _ => panic!(\"Expected Success response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_message_handler_error() {\n        let handler = LinkMessageHandler;\n        let mut error_msg = netlink_packet_core::ErrorMessage::default();\n        error_msg.code = std::num::NonZeroI32::new(1);\n        let error_payload = NetlinkPayload::Error(error_msg);\n        let result = handler.handle_payload(error_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Error(code) => {\n                assert_eq!(code, 1);\n            }\n            _ => panic!(\"Expected Error response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_message_handler_done() {\n        let handler = LinkMessageHandler;\n        let done_payload = NetlinkPayload::Done(netlink_packet_core::DoneMessage::default());\n        let result = handler.handle_payload(done_payload);\n\n        assert!(result.is_ok());\n        match result.unwrap() {\n            NetlinkResponse::Done => {}\n            _ => panic!(\"Expected Done response\"),\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_message_handler_unexpected() {\n        let handler = LinkMessageHandler;\n        let unexpected_payload = NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(\n            netlink_packet_route::address::AddressMessage::default(),\n        ));\n        let result = handler.handle_payload(unexpected_payload);\n\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_new() {\n        let result = LinkClient::new(create_network_client());\n\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_get_by_name_without_response() {\n        let fake_client = FakeNetlinkClient::new();\n        let mut link_client = LinkClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = link_client.get_by_name(\"eth0\");\n\n        // Should failed without LinkMessage\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_get_by_name_with_response() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        // Set up multiple responses\n        let mut link1 = LinkMessage::default();\n        link1.header.index = 1;\n        link1\n            .attributes\n            .push(LinkAttribute::IfName(\"eth0\".to_string()));\n\n        let responses = vec![RouteNetlinkMessage::NewLink(link1)];\n        fake_client.set_expected_responses(responses);\n\n        let mut link_client = LinkClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n        let result = link_client.get_by_name(\"eth0\");\n\n        // Should succeed with the first matching response\n        assert!(result.is_ok());\n        let response = result.unwrap();\n        assert_eq!(response.header.index, 1);\n        assert_eq!(response.attributes.len(), 1);\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_up_failure() {\n        let mut fake_client = FakeNetlinkClient::new();\n        fake_client.set_failure(\"Set up failed\".to_string());\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_up(1);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_up_success() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        // Set up a successful response (ACK with code 0)\n        let mut error_msg = netlink_packet_core::ErrorMessage::default();\n        error_msg.code = std::num::NonZeroI32::new(0);\n        let responses = vec![RouteNetlinkMessage::NewLink(LinkMessage::default())];\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_up(42);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let ClientWrapper::Fake(fake_client) = &mut link_client.client {\n            let send_calls = fake_client.get_send_calls();\n            assert_eq!(send_calls.len(), 1);\n\n            // Verify the message details\n            if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::SetLink(link)) =\n                &send_calls[0].payload\n            {\n                assert_eq!(link.header.index, 42);\n                assert!(link.header.flags.contains(LinkFlags::Up));\n                assert!(link.header.change_mask.contains(LinkFlags::Up));\n            } else {\n                panic!(\"Expected SetLink message\");\n            }\n\n            // Verify the netlink flags\n            let expected_flags = NLM_F_REQUEST | NLM_F_ACK;\n            assert_eq!(send_calls[0].header.flags, expected_flags);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_down_failure() {\n        let mut fake_client = FakeNetlinkClient::new();\n        fake_client.set_failure(\"Set down failed\".to_string());\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_down(1);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_down_success() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        let responses = vec![RouteNetlinkMessage::NewLink(LinkMessage::default())];\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_down(42);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let ClientWrapper::Fake(fake_client) = &mut link_client.client {\n            let send_calls = fake_client.get_send_calls();\n            assert_eq!(send_calls.len(), 1);\n\n            // Verify the message details\n            if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::SetLink(link)) =\n                &send_calls[0].payload\n            {\n                assert_eq!(link.header.index, 42);\n                assert!(!link.header.flags.contains(LinkFlags::Up));\n                assert!(link.header.change_mask.contains(LinkFlags::Up));\n            } else {\n                panic!(\"Expected SetLink message\");\n            }\n\n            // Verify the netlink flags\n            let expected_flags = NLM_F_REQUEST | NLM_F_ACK;\n            assert_eq!(send_calls[0].header.flags, expected_flags);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_ns_fd_failure() {\n        let mut fake_client = FakeNetlinkClient::new();\n        fake_client.set_failure(\"Set namespace failed\".to_string());\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_ns_fd(1, \"veth0\", 123);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    #[serial]\n    fn test_link_client_set_ns_fd_success() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        let responses = vec![RouteNetlinkMessage::NewLink(LinkMessage::default())];\n        fake_client.set_expected_responses(responses);\n\n        let client_wrapper = ClientWrapper::Fake(fake_client);\n        let mut link_client = LinkClient::new(client_wrapper).unwrap();\n\n        let result = link_client.set_ns_fd(42, \"new_veth\", 456);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let ClientWrapper::Fake(fake_client) = &mut link_client.client {\n            let send_calls = fake_client.get_send_calls();\n            assert_eq!(send_calls.len(), 1);\n\n            // Verify the message details\n            if let NetlinkPayload::InnerMessage(RouteNetlinkMessage::SetLink(link)) =\n                &send_calls[0].payload\n            {\n                assert_eq!(link.header.index, 42);\n                assert_eq!(link.attributes.len(), 2);\n\n                // Check for IfName attribute\n                let mut found_ifname = false;\n                let mut found_netns_fd = false;\n                for attr in &link.attributes {\n                    match attr {\n                        LinkAttribute::IfName(name) => {\n                            assert_eq!(name, \"new_veth\");\n                            found_ifname = true;\n                        }\n                        LinkAttribute::NetNsFd(fd) => {\n                            assert_eq!(*fd, 456);\n                            found_netns_fd = true;\n                        }\n                        _ => {}\n                    }\n                }\n                assert!(found_ifname, \"IfName attribute not found\");\n                assert!(found_netns_fd, \"NetNsFd attribute not found\");\n            } else {\n                panic!(\"Expected SetLink message\");\n            }\n\n            // Verify the netlink flags\n            let expected_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;\n            assert_eq!(send_calls[0].header.flags, expected_flags);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/mod.rs",
    "content": "pub mod address;\npub mod cidr;\nmod client;\nmod fake;\npub mod link;\npub mod network_device;\nmod traits;\npub mod wrapper;\n\n#[derive(Debug, thiserror::Error)]\npub enum NetworkError {\n    #[error(transparent)]\n    Nix(#[from] nix::Error),\n    #[error(transparent)]\n    IO(#[from] std::io::Error),\n    #[error(\"failed to initialize NetlinkClient\")]\n    ClientInitializeError,\n}\n\ntype Result<T> = std::result::Result<T, NetworkError>;\n\n/// Represents a response from a Netlink operation.\n///\n/// This enum encapsulates the possible outcomes of a Netlink operation:\n/// - Success: The operation completed successfully with a response of type T\n/// - Error: The operation failed with an error code\n/// - Done: The operation completed with no more data to process\n#[derive(Debug)]\npub enum NetlinkResponse<T> {\n    Success(T),\n    Error(i32),\n    Done,\n    None,\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/network_device.rs",
    "content": "use std::os::fd::RawFd;\n\nuse netlink_packet_route::address::{AddressHeaderFlags, AddressScope};\nuse oci_spec::runtime::LinuxNetDevice;\n\nuse super::Result;\nuse super::address::AddressClient;\nuse super::link::LinkClient;\nuse super::wrapper::create_network_client;\nuse crate::network::cidr::CidrAddress;\n\n/// Resolves the final name for a network device.\n/// If the device has a configured name (non-empty), use it; otherwise use the original name.\npub fn resolve_device_name<'a>(device: &'a LinuxNetDevice, original_name: &'a str) -> &'a str {\n    device\n        .name()\n        .as_ref()\n        .filter(|d| !d.is_empty())\n        .map_or(original_name, |d| d)\n}\n\n/// dev_change_netns allows to move a device given by name to a network namespace given by netns_fd\n/// and optionally change the device name.\n/// The device name will be kept the same if device.Name is None or an empty string.\n/// This function ensures that the move and rename operations occur atomically.\n/// It preserves existing interface attributes, including IP addresses.\npub fn dev_change_net_namespace(\n    name: &str,\n    netns_fd: RawFd,\n    device: &LinuxNetDevice,\n) -> Result<Vec<CidrAddress>> {\n    tracing::debug!(\n        \"attaching network device {} to network namespace fd {}\",\n        name,\n        netns_fd\n    );\n\n    let mut link_client = LinkClient::new(create_network_client())?;\n    let mut addr_client = AddressClient::new(create_network_client())?;\n\n    let new_name = resolve_device_name(device, name);\n\n    let link = link_client.get_by_name(name)?;\n\n    let index = link.header.index;\n\n    // Set the interface link state to DOWN before modifying attributes like namespace or name.\n    // This prevents potential conflicts or disruptions on the host network during the transition,\n    // particularly if other host components depend on this specific interface or its properties.\n    link_client.set_down(index)?;\n\n    // Get the existing IP addresses on the interface.\n    let addrs = addr_client.get_by_index(index)?;\n\n    link_client\n        .set_ns_fd(index, new_name, netns_fd)\n        .map_err(|err| {\n            tracing::error!(?err, \"failed to set_ns_fd\");\n            err\n        })?;\n\n    // Filter addresses before sending to init process:\n    // Only include IP addresses with global scope and permanent flag.\n    let cidr_addrs: Vec<CidrAddress> = addrs\n        .iter()\n        .filter(|addr| {\n            // Only move IP addresses with global scope because those are not host-specific, auto-configured,\n            // or have limited network scope, making them unsuitable inside the container namespace.\n            // Ref: https://www.ietf.org/rfc/rfc3549.txt\n            if addr.header.scope != AddressScope::Universe {\n                tracing::debug!(\n                    \"skipping address with scope {:?} from network device {}\",\n                    addr.header.scope,\n                    new_name\n                );\n                return false;\n            }\n\n            // Only move permanent IP addresses configured by the user, dynamic addresses are excluded because\n            // their validity may rely on the original network namespace's context and they may have limited\n            // lifetimes and are not guaranteed to be available in a new namespace.\n            // Ref: https://www.ietf.org/rfc/rfc3549.txt\n            if !addr.header.flags.contains(AddressHeaderFlags::Permanent) {\n                tracing::debug!(\n                    \"skipping non-permanent address from network device {}\",\n                    new_name\n                );\n                return false;\n            }\n\n            true\n        })\n        .map(CidrAddress::from)\n        .collect();\n\n    Ok(cidr_addrs)\n}\n\n/// Core logic for setting up addresses in the new network namespace\n/// This function is extracted to make it testable without system calls\n///\n/// Note: The addresses passed to this function are already filtered in the main process\n/// to include only global scope and permanent addresses.\npub fn setup_addresses_in_network_namespace(\n    addrs: &[CidrAddress],\n    link_index: u32,\n    new_name: &str,\n    addr_client: &mut AddressClient,\n) -> Result<()> {\n    // Re-add the original IP addresses to the interface in the new namespace.\n    // The kernel removes IP addresses when an interface is moved between network namespaces.\n    for addr in addrs {\n        tracing::debug!(\n            \"adding address {:?}/{} to network device {}\",\n            addr.address,\n            addr.prefix_len,\n            new_name\n        );\n        addr_client.add(link_index, addr.address, addr.prefix_len)?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::net::{IpAddr, Ipv4Addr};\n\n    use netlink_packet_route::RouteNetlinkMessage;\n    use netlink_packet_route::address::{AddressAttribute, AddressMessage};\n\n    use super::*;\n    use crate::network::address::AddressClient;\n    use crate::network::fake::FakeNetlinkClient;\n    use crate::network::wrapper::ClientWrapper;\n\n    #[test]\n    fn test_setup_addresses_in_network_namespace() {\n        let mut fake_client = FakeNetlinkClient::new();\n\n        let mut addr_msg = AddressMessage::default();\n        addr_msg.header.scope = AddressScope::Universe;\n        addr_msg.header.prefix_len = 24;\n        addr_msg.header.flags = AddressHeaderFlags::Permanent;\n        addr_msg\n            .attributes\n            .push(AddressAttribute::Address(IpAddr::V4(Ipv4Addr::new(\n                192, 168, 1, 1,\n            ))));\n\n        let responses = vec![RouteNetlinkMessage::NewAddress(addr_msg.clone())];\n        fake_client.set_expected_responses(responses);\n\n        let mut addr_client = AddressClient::new(ClientWrapper::Fake(fake_client)).unwrap();\n\n        let addrs = [addr_msg];\n        let serializable_addrs: Vec<CidrAddress> = addrs.iter().map(CidrAddress::from).collect();\n        let result =\n            setup_addresses_in_network_namespace(&serializable_addrs, 5, \"eth1\", &mut addr_client);\n        assert!(result.is_ok());\n\n        // Verify the call was tracked\n        if let Some(send_calls) = addr_client.get_send_calls() {\n            assert_eq!(send_calls.len(), 1);\n        } else {\n            panic!(\"Expected Fake client\");\n        }\n    }\n\n    #[test]\n    fn test_resolve_device_name_with_name() {\n        let device = LinuxNetDevice::default()\n            .set_name(Some(\"eth0\".to_string()))\n            .clone();\n        let original = \"veth0\";\n\n        let result = resolve_device_name(&device, original);\n        assert_eq!(result, \"eth0\");\n    }\n\n    #[test]\n    fn test_resolve_device_name_with_empty_name() {\n        let device = LinuxNetDevice::default()\n            .set_name(Some(\"\".to_string()))\n            .clone();\n        let original = \"veth0\";\n\n        let result = resolve_device_name(&device, original);\n        assert_eq!(result, \"veth0\");\n    }\n\n    #[test]\n    fn test_resolve_device_name_without_name() {\n        let device = LinuxNetDevice::default();\n        let original = \"veth0\";\n\n        let result = resolve_device_name(&device, original);\n        assert_eq!(result, \"veth0\");\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/traits.rs",
    "content": "use netlink_packet_core::{NetlinkMessage, NetlinkPayload};\nuse netlink_packet_route::RouteNetlinkMessage;\n\nuse super::{NetlinkResponse, Result};\n\n/// Trait for handling Netlink message payloads.\n///\n/// This trait defines how different types of Netlink messages should be processed\n/// and converted into appropriate response types.\npub trait NetlinkMessageHandler {\n    /// The type of response that this handler produces\n    type Response;\n\n    /// Process a Netlink payload and convert it into a response.\n    ///\n    /// # Arguments\n    ///\n    /// * `payload` - The Netlink payload to process\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a NetlinkResponse or an IO error\n    fn handle_payload(\n        &self,\n        payload: NetlinkPayload<RouteNetlinkMessage>,\n    ) -> Result<NetlinkResponse<Self::Response>>;\n}\n\n/// Trait for Netlink client operations.\n///\n/// This trait abstracts the Netlink communication, allowing for easy mocking in tests.\npub trait Client {\n    /// Sends a Netlink message.\n    ///\n    /// # Arguments\n    ///\n    /// * `req` - The Netlink message to send\n    ///\n    /// # Returns\n    ///\n    /// A Result indicating success or failure of the send operation\n    fn send(&mut self, req: &NetlinkMessage<RouteNetlinkMessage>) -> Result<()>;\n\n    /// Receives and processes a Netlink message.\n    ///\n    /// # Arguments\n    ///\n    /// * `handler` - The handler to process the received message\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either the processed response or an error\n    fn receive<T, H>(&mut self, handler: H) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>;\n\n    /// Receives and processes multiple Netlink messages.\n    ///\n    /// # Arguments\n    ///\n    /// * `handler` - The handler to process the received messages\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a vector of processed responses or an error\n    fn receive_multiple<T, H>(&mut self, handler: H) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>;\n\n    /// Sends a Netlink message and receives a single response.\n    ///\n    /// # Arguments\n    ///\n    /// * `req` - The Netlink message to send\n    /// * `handler` - The handler to process the received message\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either the processed response or an error\n    fn send_and_receive<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>;\n\n    /// Sends a Netlink message and receives multiple responses.\n    ///\n    /// # Arguments\n    ///\n    /// * `req` - The Netlink message to send\n    /// * `handler` - The handler to process the received messages\n    ///\n    /// # Returns\n    ///\n    /// A Result containing either a vector of processed responses or an error\n    fn send_and_receive_multiple<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        self.send(req)?;\n        self.receive_multiple(handler)\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/network/wrapper.rs",
    "content": "use netlink_packet_core::NetlinkMessage;\nuse netlink_packet_route::RouteNetlinkMessage;\n\nuse super::client::NetlinkClient;\nuse super::fake::FakeNetlinkClient;\nuse super::traits::{Client, NetlinkMessageHandler};\nuse super::{NetworkError, Result};\n\n/// Enum wrapper for different client types\n/// The `Client` trait contains generic methods, which makes it impossible to use as a trait object.\n/// Therefore, we define `ClientWrapper` as an enum-based dynamic dispatch to handle this.\npub enum ClientWrapper {\n    /// Real NetlinkClient instance for production use\n    Client(NetlinkClient),\n    /// Error state when NetlinkClient initialization failed\n    ErrorState,\n    /// Fake client for testing purposes\n    Fake(FakeNetlinkClient),\n}\n\nimpl Client for ClientWrapper {\n    fn send(&mut self, req: &NetlinkMessage<RouteNetlinkMessage>) -> Result<()> {\n        match self {\n            ClientWrapper::Client(client) => client.send(req),\n            ClientWrapper::ErrorState => Err(NetworkError::ClientInitializeError),\n            ClientWrapper::Fake(client) => client.send(req),\n        }\n    }\n\n    fn receive<T, H>(&mut self, handler: H) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        match self {\n            ClientWrapper::Client(client) => client.receive(handler),\n            ClientWrapper::ErrorState => Err(NetworkError::ClientInitializeError),\n            ClientWrapper::Fake(client) => client.receive(handler),\n        }\n    }\n\n    fn receive_multiple<T, H>(&mut self, handler: H) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        match self {\n            ClientWrapper::Client(client) => client.receive_multiple(handler),\n            ClientWrapper::ErrorState => Err(NetworkError::ClientInitializeError),\n            ClientWrapper::Fake(client) => client.receive_multiple(handler),\n        }\n    }\n\n    fn send_and_receive<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<T>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        match self {\n            ClientWrapper::Client(client) => client.send_and_receive(req, handler),\n            ClientWrapper::ErrorState => Err(NetworkError::ClientInitializeError),\n            ClientWrapper::Fake(client) => client.send_and_receive(req, handler),\n        }\n    }\n\n    fn send_and_receive_multiple<T, H>(\n        &mut self,\n        req: &NetlinkMessage<RouteNetlinkMessage>,\n        handler: H,\n    ) -> Result<Vec<T>>\n    where\n        H: NetlinkMessageHandler<Response = T>,\n    {\n        match self {\n            ClientWrapper::Client(client) => client.send_and_receive_multiple(req, handler),\n            ClientWrapper::ErrorState => Err(NetworkError::ClientInitializeError),\n            ClientWrapper::Fake(client) => client.send_and_receive_multiple(req, handler),\n        }\n    }\n}\n\nimpl Default for ClientWrapper {\n    fn default() -> Self {\n        if cfg!(test) {\n            ClientWrapper::Fake(FakeNetlinkClient::new())\n        } else {\n            // If NetlinkClient initialization fails, we store the error state\n            // instead of panicking. The error will be returned when the client is used.\n            match NetlinkClient::new() {\n                Ok(client) => ClientWrapper::Client(client),\n                Err(_) => ClientWrapper::ErrorState,\n            }\n        }\n    }\n}\n\npub fn create_network_client() -> ClientWrapper {\n    ClientWrapper::default()\n}\n"
  },
  {
    "path": "crates/libcontainer/src/notify_socket.rs",
    "content": "use std::env;\nuse std::io::prelude::*;\nuse std::os::fd::FromRawFd;\nuse std::os::unix::io::AsRawFd;\nuse std::os::unix::net::{UnixListener, UnixStream};\nuse std::path::{Path, PathBuf};\n\nuse nix::unistd::{self, close};\n\npub const NOTIFY_FILE: &str = \"notify.sock\";\n\n#[derive(Debug, thiserror::Error)]\npub enum NotifyListenerError {\n    #[error(\"failed to chdir {path} while creating notify socket: {source}\")]\n    Chdir { source: nix::Error, path: PathBuf },\n    #[error(\"invalid path: {0}\")]\n    InvalidPath(PathBuf),\n    #[error(\"failed to bind notify socket: {name}\")]\n    Bind {\n        source: std::io::Error,\n        name: String,\n    },\n    #[error(\"failed to connect to notify socket: {name}\")]\n    Connect {\n        source: std::io::Error,\n        name: String,\n    },\n    #[error(\"failed to get cwd\")]\n    GetCwd(#[source] std::io::Error),\n    #[error(\"failed to accept notify listener\")]\n    Accept(#[source] std::io::Error),\n    #[error(\"failed to close notify listener\")]\n    Close(#[source] nix::errno::Errno),\n    #[error(\"failed to read notify listener\")]\n    Read(#[source] std::io::Error),\n    #[error(\"failed to send start container\")]\n    SendStartContainer(#[source] std::io::Error),\n}\n\ntype Result<T> = std::result::Result<T, NotifyListenerError>;\n\npub struct NotifyListener {\n    socket: UnixListener,\n}\n\nimpl NotifyListener {\n    pub fn new(socket_path: &Path) -> Result<Self> {\n        tracing::debug!(?socket_path, \"create notify listener\");\n        // Unix domain socket has a maximum length of 108, different from\n        // normal path length of 255. Due to how docker create the path name\n        // to the container working directory, there is a high chance that\n        // the full absolute path is over the limit. To work around this\n        // limitation, we chdir first into the workdir where the socket is,\n        // and chdir back after the socket is created.\n        let workdir = socket_path\n            .parent()\n            .ok_or_else(|| NotifyListenerError::InvalidPath(socket_path.to_owned()))?;\n        let socket_name = socket_path\n            .file_name()\n            .ok_or_else(|| NotifyListenerError::InvalidPath(socket_path.to_owned()))?;\n        let cwd = env::current_dir().map_err(NotifyListenerError::GetCwd)?;\n        tracing::debug!(?cwd, \"the cwd to create the notify socket\");\n        unistd::chdir(workdir).map_err(|e| NotifyListenerError::Chdir {\n            source: e,\n            path: workdir.to_owned(),\n        })?;\n        let stream = UnixListener::bind(socket_name).map_err(|e| NotifyListenerError::Bind {\n            source: e,\n            // ok to unwrap here as OsStr should always be utf-8 compatible\n            name: socket_name.to_str().unwrap().to_owned(),\n        })?;\n        unistd::chdir(&cwd).map_err(|e| NotifyListenerError::Chdir {\n            source: e,\n            path: cwd,\n        })?;\n\n        Ok(Self { socket: stream })\n    }\n\n    pub fn wait_for_container_start(&self) -> Result<()> {\n        match self.socket.accept() {\n            Ok((mut socket, _)) => {\n                let mut response = String::new();\n                socket\n                    .read_to_string(&mut response)\n                    .map_err(NotifyListenerError::Read)?;\n                tracing::debug!(\"received: {}\", response);\n            }\n            Err(e) => Err(NotifyListenerError::Accept(e))?,\n        }\n\n        Ok(())\n    }\n\n    pub fn close(&self) -> Result<()> {\n        close(self.socket.as_raw_fd()).map_err(NotifyListenerError::Close)?;\n        Ok(())\n    }\n}\n\nimpl Clone for NotifyListener {\n    fn clone(&self) -> Self {\n        let fd = self.socket.as_raw_fd();\n        // This is safe because we just duplicate a valid fd. Theoretically, to\n        // truly clone a unix listener, we have to use dup(2) to duplicate the\n        // fd, and then use from_raw_fd to create a new UnixListener. However,\n        // for our purposes, fd is just an integer to pass around for the same\n        // socket. Our main usage is to pass the notify_listener across process\n        // boundary. Since fd tables are cloned during clone/fork calls, this\n        // should be safe to use, as long as we be careful with not closing the\n        // same fd in different places. If we observe an issue, we will switch\n        // to `dup`.\n        let socket = unsafe { UnixListener::from_raw_fd(fd) };\n        Self { socket }\n    }\n}\n\npub struct NotifySocket {\n    path: PathBuf,\n}\n\nimpl NotifySocket {\n    pub fn new<P: Into<PathBuf>>(socket_path: P) -> Self {\n        Self {\n            path: socket_path.into(),\n        }\n    }\n\n    pub fn notify_container_start(&mut self) -> Result<()> {\n        tracing::debug!(\"notify container start\");\n        let cwd = env::current_dir().map_err(NotifyListenerError::GetCwd)?;\n        let workdir = self\n            .path\n            .parent()\n            .ok_or_else(|| NotifyListenerError::InvalidPath(self.path.to_owned()))?;\n        unistd::chdir(workdir).map_err(|e| NotifyListenerError::Chdir {\n            source: e,\n            path: workdir.to_owned(),\n        })?;\n        let socket_name = self\n            .path\n            .file_name()\n            .ok_or_else(|| NotifyListenerError::InvalidPath(self.path.to_owned()))?;\n        let mut stream =\n            UnixStream::connect(socket_name).map_err(|e| NotifyListenerError::Connect {\n                source: e,\n                // ok to unwrap as OsStr should always be utf-8 compatible\n                name: socket_name.to_str().unwrap().to_owned(),\n            })?;\n        stream\n            .write_all(b\"start container\")\n            .map_err(NotifyListenerError::SendStartContainer)?;\n        tracing::debug!(\"notify finished\");\n        unistd::chdir(&cwd).map_err(|e| NotifyListenerError::Chdir {\n            source: e,\n            path: cwd,\n        })?;\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use tempfile::tempdir;\n\n    use super::*;\n\n    #[test]\n    /// Test that the listener can be cloned and function correctly. This test\n    /// also serves as a test for the normal case.\n    fn test_notify_listener_clone() {\n        let tempdir = tempdir().unwrap();\n        let socket_path = tempdir.path().join(\"notify.sock\");\n        // listener needs to be created first because it will create the socket.\n        let listener = NotifyListener::new(&socket_path).unwrap();\n        let mut socket = NotifySocket::new(socket_path.clone());\n        // This is safe without race because the unix domain socket is already\n        // created. It is OK for the socket to send the start notification\n        // before the listener wait is called.\n        let thread_handle = std::thread::spawn({\n            move || {\n                // We clone the listener and listen on the cloned listener to\n                // make sure the cloned fd functions correctly.\n                listener.wait_for_container_start().unwrap();\n            }\n        });\n\n        socket.notify_container_start().unwrap();\n        thread_handle.join().unwrap();\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/args.rs",
    "content": "use std::os::unix::prelude::RawFd;\nuse std::path::PathBuf;\nuse std::rc::Rc;\n\nuse libcgroups::common::CgroupConfig;\nuse oci_spec::runtime::Spec;\n\nuse crate::container::Container;\nuse crate::notify_socket::NotifyListener;\nuse crate::syscall::syscall::SyscallType;\nuse crate::user_ns::UserNamespaceConfig;\nuse crate::workload::Executor;\n#[derive(Debug, Copy, Clone)]\npub enum ContainerType {\n    InitContainer,\n    TenantContainer { exec_notify_fd: RawFd },\n}\n\n#[derive(Clone)]\npub struct ContainerArgs {\n    /// Indicates if an init or a tenant container should be created\n    pub container_type: ContainerType,\n    /// Interface to operating system primitives\n    pub syscall: SyscallType,\n    /// OCI compliant runtime spec\n    pub spec: Rc<Spec>,\n    /// Root filesystem of the container\n    pub rootfs: PathBuf,\n    /// Socket to communicate the file descriptor of the ptty\n    pub console_socket: Option<RawFd>,\n    /// The Unix Domain Socket to communicate container start\n    pub notify_listener: NotifyListener,\n    /// File descriptors preserved/passed to the container init process.\n    pub preserve_fds: i32,\n    /// Container state\n    pub container: Option<Container>,\n    /// Options for new namespace creation\n    pub user_ns_config: Option<UserNamespaceConfig>,\n    /// Cgroup Manager Config\n    pub cgroup_config: CgroupConfig,\n    /// If the container is to be run in detached mode\n    pub detached: bool,\n    /// Manage the functions that actually run on the container\n    pub executor: Box<dyn Executor>,\n    /// If do not use pivot root to jail process inside rootfs\n    pub no_pivot: bool,\n    // RawFd set to stdin of the container init process.\n    pub stdin: Option<RawFd>,\n    // RawFd set to stdout of the container init process.\n    pub stdout: Option<RawFd>,\n    // RawFd set to stderr of the container init process.\n    pub stderr: Option<RawFd>,\n    // Indicate if the init process should be a sibling of the main process.\n    pub as_sibling: bool,\n    /// File path used to communicate the PID of the\n    /// container process to the higher-level runtime.\n    pub pid_file: Option<PathBuf>,\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/channel.rs",
    "content": "use std::collections::HashMap;\nuse std::os::unix::prelude::{AsRawFd, RawFd};\n\nuse nix::unistd::Pid;\n\nuse crate::channel::{Receiver, Sender, channel};\nuse crate::network::cidr::CidrAddress;\nuse crate::process::message::Message;\n\n#[derive(Debug, thiserror::Error)]\npub enum ChannelError {\n    #[error(\"received unexpected message: {received:?}, expected: {expected:?}\")]\n    UnexpectedMessage {\n        expected: Message,\n        received: Message,\n    },\n    #[error(\"failed to receive. {msg:?}. {source:?}\")]\n    ReceiveError {\n        msg: String,\n        #[source]\n        source: crate::channel::ChannelError,\n    },\n    #[error(transparent)]\n    BaseChannelError(#[from] crate::channel::ChannelError),\n    #[error(\"missing fds from seccomp request\")]\n    MissingSeccompFds,\n    #[error(\"exec process failed with error {0}\")]\n    ExecError(String),\n    #[error(\"intermediate process error {0}\")]\n    OtherError(String),\n}\n\n// Channel Design\n//\n// Each of the main, intermediate, and init process will have a uni-directional\n// channel, a sender and a receiver. Each process will hold the receiver and\n// listen message on it. Each sender is shared between each process to send\n// message to the corresponding receiver. For example, main_sender and\n// main_receiver is used for the main process. The main process will use\n// receiver to receive all message sent to the main process. The other\n// processes will share the main_sender and use it to send message to the main\n// process.\n\npub fn main_channel() -> Result<(MainSender, MainReceiver), ChannelError> {\n    let (sender, receiver) = channel::<Message>()?;\n    Ok((MainSender { sender }, MainReceiver { receiver }))\n}\n\npub struct MainSender {\n    sender: Sender<Message>,\n}\n\nimpl MainSender {\n    // requests the Main to write the id mappings for the intermediate process\n    // this needs to be done from the parent see https://man7.org/linux/man-pages/man7/user_namespaces.7.html\n    pub fn identifier_mapping_request(&mut self) -> Result<(), ChannelError> {\n        tracing::debug!(\"send identifier mapping request\");\n        self.sender.send(Message::WriteMapping)?;\n\n        Ok(())\n    }\n\n    pub fn seccomp_notify_request(&mut self, fd: RawFd) -> Result<(), ChannelError> {\n        self.sender\n            .send_fds(Message::SeccompNotify, &[fd.as_raw_fd()])?;\n\n        Ok(())\n    }\n\n    pub fn network_setup_ready(&mut self) -> Result<(), ChannelError> {\n        tracing::debug!(\"notify network setup ready\");\n        self.sender.send(Message::SetupNetworkDeviceReady)?;\n\n        Ok(())\n    }\n\n    pub fn intermediate_ready(&mut self, pid: Pid) -> Result<(), ChannelError> {\n        // Send over the IntermediateReady follow by the pid.\n        tracing::debug!(\"sending init pid ({:?})\", pid);\n        self.sender.send(Message::IntermediateReady(pid.as_raw()))?;\n\n        Ok(())\n    }\n\n    pub fn init_ready(&mut self) -> Result<(), ChannelError> {\n        self.sender.send(Message::InitReady)?;\n\n        Ok(())\n    }\n\n    pub fn exec_failed(&mut self, err: String) -> Result<(), ChannelError> {\n        self.sender.send(Message::ExecFailed(err))?;\n        Ok(())\n    }\n\n    pub fn send_error(&mut self, err: String) -> Result<(), ChannelError> {\n        self.sender.send(Message::OtherError(err))?;\n        Ok(())\n    }\n\n    pub fn hook_request(&mut self) -> Result<(), ChannelError> {\n        self.sender.send(Message::HookRequest)?;\n        Ok(())\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.sender.close()?;\n\n        Ok(())\n    }\n}\n\npub struct MainReceiver {\n    receiver: Receiver<Message>,\n}\n\nimpl MainReceiver {\n    /// Waits for associated intermediate process to send ready message\n    /// and return the pid of init process which is forked by intermediate process\n    pub fn wait_for_intermediate_ready(&mut self) -> Result<Pid, ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for intermediate process\".to_string(),\n                source: err,\n            })?;\n\n        match msg {\n            Message::IntermediateReady(pid) => Ok(Pid::from_raw(pid)),\n            Message::ExecFailed(err) => Err(ChannelError::ExecError(err)),\n            Message::OtherError(err) => Err(ChannelError::OtherError(err)),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::IntermediateReady(0),\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_mapping_request(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for mapping request\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::WriteMapping => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::WriteMapping,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_seccomp_request(&mut self) -> Result<i32, ChannelError> {\n        let (msg, fds) = self.receiver.recv_with_fds::<[RawFd; 1]>().map_err(|err| {\n            ChannelError::ReceiveError {\n                msg: \"waiting for seccomp request\".to_string(),\n                source: err,\n            }\n        })?;\n\n        match msg {\n            Message::SeccompNotify => {\n                let fd = match fds {\n                    Some(fds) => {\n                        if fds.is_empty() {\n                            Err(ChannelError::MissingSeccompFds)\n                        } else {\n                            Ok(fds[0])\n                        }\n                    }\n                    None => Err(ChannelError::MissingSeccompFds),\n                }?;\n                Ok(fd)\n            }\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::SeccompNotify,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_network_setup_ready(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for init ready\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::SetupNetworkDeviceReady => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::SetupNetworkDeviceReady,\n                received: msg,\n            }),\n        }\n    }\n\n    /// Waits for associated init process to send ready message\n    /// and return the pid of init process which is forked by init process\n    pub fn wait_for_init_ready(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for init ready\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::InitReady => Ok(()),\n            // this case in unique and known enough to have a special error format\n            Message::ExecFailed(err) => Err(ChannelError::ExecError(format!(\n                \"error in executing process : {err}\"\n            ))),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::InitReady,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_hook_request(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for hook request\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::HookRequest => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::HookRequest,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.receiver.close()?;\n\n        Ok(())\n    }\n}\n\npub fn intermediate_channel() -> Result<(IntermediateSender, IntermediateReceiver), ChannelError> {\n    let (sender, receiver) = channel::<Message>()?;\n    Ok((\n        IntermediateSender { sender },\n        IntermediateReceiver { receiver },\n    ))\n}\n\npub struct IntermediateSender {\n    sender: Sender<Message>,\n}\n\nimpl IntermediateSender {\n    pub fn mapping_written(&mut self) -> Result<(), ChannelError> {\n        tracing::debug!(\"identifier mapping written\");\n        self.sender.send(Message::MappingWritten)?;\n\n        Ok(())\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.sender.close()?;\n\n        Ok(())\n    }\n}\n\npub struct IntermediateReceiver {\n    receiver: Receiver<Message>,\n}\n\nimpl IntermediateReceiver {\n    // wait until the parent process has finished writing the id mappings\n    pub fn wait_for_mapping_ack(&mut self) -> Result<(), ChannelError> {\n        tracing::debug!(\"waiting for mapping ack\");\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for mapping ack\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::MappingWritten => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::MappingWritten,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.receiver.close()?;\n\n        Ok(())\n    }\n}\n\npub fn init_channel() -> Result<(InitSender, InitReceiver), ChannelError> {\n    let (sender, receiver) = channel::<Message>()?;\n    Ok((InitSender { sender }, InitReceiver { receiver }))\n}\n\npub struct InitSender {\n    sender: Sender<Message>,\n}\n\nimpl InitSender {\n    pub fn seccomp_notify_done(&mut self) -> Result<(), ChannelError> {\n        self.sender.send(Message::SeccompNotifyDone)?;\n\n        Ok(())\n    }\n\n    pub fn hook_done(&mut self) -> Result<(), ChannelError> {\n        self.sender.send(Message::HookDone)?;\n        Ok(())\n    }\n\n    pub fn move_network_device(\n        &mut self,\n        addrs: HashMap<String, Vec<CidrAddress>>,\n    ) -> Result<(), ChannelError> {\n        self.sender.send(Message::MoveNetworkDevice(addrs))?;\n\n        Ok(())\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.sender.close()?;\n\n        Ok(())\n    }\n}\n\npub struct InitReceiver {\n    receiver: Receiver<Message>,\n}\n\nimpl InitReceiver {\n    pub fn wait_for_seccomp_request_done(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for seccomp request\".to_string(),\n                source: err,\n            })?;\n\n        match msg {\n            Message::SeccompNotifyDone => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::SeccompNotifyDone,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_move_network_device(\n        &mut self,\n    ) -> Result<HashMap<String, Vec<CidrAddress>>, ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for mapping request\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::MoveNetworkDevice(addr) => Ok(addr),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::WriteMapping,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn wait_for_hook_request_done(&mut self) -> Result<(), ChannelError> {\n        let msg = self\n            .receiver\n            .recv()\n            .map_err(|err| ChannelError::ReceiveError {\n                msg: \"waiting for hook done\".to_string(),\n                source: err,\n            })?;\n        match msg {\n            Message::HookDone => Ok(()),\n            msg => Err(ChannelError::UnexpectedMessage {\n                expected: Message::HookDone,\n                received: msg,\n            }),\n        }\n    }\n\n    pub fn close(&self) -> Result<(), ChannelError> {\n        self.receiver.close()?;\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::{Context, Result};\n    use nix::sys::wait;\n    use nix::unistd;\n    use serial_test::serial;\n\n    use super::*;\n\n    // Note: due to cargo test by default runs tests in parallel using a single\n    // process, these tests should not be running in parallel with other tests.\n    // Because we run tests in the same process, other tests may decide to close\n    // down file descriptors or saturate the IOs in the OS.  The channel uses\n    // pipe to communicate and can potentially become flaky as a result. There\n    // is not much else we can do other than to run the tests in serial.\n\n    #[test]\n    #[serial]\n    fn test_channel_intermadiate_ready() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                wait::waitpid(child, None)?;\n                let pid = receiver\n                    .wait_for_intermediate_ready()\n                    .with_context(|| \"Failed to wait for intermadiate ready\")?;\n                receiver.close()?;\n                assert_eq!(pid, child);\n            }\n            unistd::ForkResult::Child => {\n                let pid = unistd::getpid();\n                sender.intermediate_ready(pid)?;\n                sender.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_channel_id_mapping_request() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                wait::waitpid(child, None)?;\n                receiver.wait_for_mapping_request()?;\n                receiver.close()?;\n            }\n            unistd::ForkResult::Child => {\n                sender\n                    .identifier_mapping_request()\n                    .with_context(|| \"Failed to send mapping written\")?;\n                sender.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_channel_id_mapping_ack() -> Result<()> {\n        let (sender, receiver) = &mut intermediate_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                wait::waitpid(child, None)?;\n                receiver.wait_for_mapping_ack()?;\n            }\n            unistd::ForkResult::Child => {\n                sender\n                    .mapping_written()\n                    .with_context(|| \"Failed to send mapping written\")?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_channel_init_ready() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                wait::waitpid(child, None)?;\n                receiver.wait_for_init_ready()?;\n                receiver.close()?;\n            }\n            unistd::ForkResult::Child => {\n                sender\n                    .init_ready()\n                    .with_context(|| \"Failed to send init ready\")?;\n                sender.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_channel_main_graceful_exit() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                sender.close().context(\"failed to close sender\")?;\n                // The child process will exit without send the intermediate ready\n                // message. This should cause the wait_for_intermediate_ready to error\n                // out, instead of keep blocking.\n                let ret = receiver.wait_for_intermediate_ready();\n                assert!(ret.is_err());\n                wait::waitpid(child, None)?;\n            }\n            unistd::ForkResult::Child => {\n                receiver.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_channel_intermediate_graceful_exit() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                sender.close().context(\"failed to close sender\")?;\n                // The child process will exit without send the init ready\n                // message. This should cause the wait_for_init_ready to error\n                // out, instead of keep blocking.\n                let ret = receiver.wait_for_init_ready();\n                assert!(ret.is_err());\n                wait::waitpid(child, None)?;\n            }\n            unistd::ForkResult::Child => {\n                receiver.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_move_network_device_message() -> Result<()> {\n        use crate::network::cidr::CidrAddress;\n\n        let device_name = \"dummy\".to_string();\n        let ip = \"10.0.0.1\".parse().unwrap();\n        let addr = CidrAddress {\n            prefix_len: 24,\n            address: ip,\n        };\n        let mut addrs = HashMap::new();\n        addrs.insert(device_name.clone(), vec![addr.clone()]);\n\n        let (sender, receiver) = &mut init_channel()?;\n\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                sender.move_network_device(addrs)?;\n                sender.close().context(\"failed to close sender\")?;\n                let status = wait::waitpid(child, None)?;\n                if let nix::sys::wait::WaitStatus::Exited(_, code) = status {\n                    assert_eq!(code, 0, \"Child process failed assertions\");\n                } else {\n                    panic!(\"Child did not exit normally: {:?}\", status);\n                }\n            }\n            unistd::ForkResult::Child => {\n                let received_addrs = receiver.wait_for_move_network_device()?;\n                receiver.close()?;\n                if let Some(received_addr) = received_addrs.get(&device_name) {\n                    if !(received_addr[0].prefix_len == addr.prefix_len\n                        && received_addr[0].address == addr.address)\n                    {\n                        eprintln!(\"assertion failed in child\");\n                        std::process::exit(1);\n                    }\n                } else {\n                    eprintln!(\"assertion failed in child\");\n                    std::process::exit(1);\n                }\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_network_setup_ready() -> Result<()> {\n        let (sender, receiver) = &mut main_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                wait::waitpid(child, None)?;\n                receiver.wait_for_network_setup_ready()?;\n                receiver.close()?;\n            }\n            unistd::ForkResult::Child => {\n                sender\n                    .network_setup_ready()\n                    .with_context(|| \"Failed to send network setup ready\")?;\n                sender.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/container_intermediate_process.rs",
    "content": "use std::os::fd::FromRawFd;\n\nuse libcgroups::common::CgroupManager;\nuse nix::unistd::{Gid, Pid, Uid, close, getpid, write};\nuse oci_spec::runtime::{LinuxNamespace, LinuxNamespaceType, LinuxResources};\n\nuse super::args::{ContainerArgs, ContainerType};\nuse super::channel::{IntermediateReceiver, MainSender};\nuse super::fork::CloneCb;\nuse super::init::process as init_process;\nuse crate::error::MissingSpecError;\nuse crate::namespaces::Namespaces;\nuse crate::process::{channel, cpu_affinity, fork};\n\n#[derive(Debug, thiserror::Error)]\npub enum IntermediateProcessError {\n    #[error(transparent)]\n    Channel(#[from] channel::ChannelError),\n    #[error(transparent)]\n    Namespace(#[from] crate::namespaces::NamespaceError),\n    #[error(transparent)]\n    Syscall(#[from] crate::syscall::SyscallError),\n    #[error(\"failed to launch init process\")]\n    InitProcess(#[source] fork::CloneError),\n    #[error(\"cgroup error: {0}\")]\n    Cgroup(String),\n    #[error(transparent)]\n    Procfs(#[from] procfs::ProcError),\n    #[error(\"exec notify failed\")]\n    ExecNotify(#[source] nix::Error),\n    #[error(transparent)]\n    MissingSpec(#[from] crate::error::MissingSpecError),\n    #[error(\"CPU affinity error {0}\")]\n    CpuAffinity(#[from] cpu_affinity::CPUAffinityError),\n    #[error(\"other error\")]\n    Other(String),\n}\n\ntype Result<T> = std::result::Result<T, IntermediateProcessError>;\n\npub fn container_intermediate_process(\n    args: &ContainerArgs,\n    intermediate_chan: &mut (channel::IntermediateSender, channel::IntermediateReceiver),\n    init_chan: &mut (channel::InitSender, channel::InitReceiver),\n    main_sender: &mut channel::MainSender,\n) -> Result<()> {\n    let (inter_sender, inter_receiver) = intermediate_chan;\n    let (init_sender, init_receiver) = init_chan;\n    let command = args.syscall.create_syscall();\n    let spec = &args.spec;\n    let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n    let namespaces = Namespaces::try_from(linux.namespaces().as_ref())?;\n    let cgroup_manager = libcgroups::common::create_cgroup_manager(args.cgroup_config.to_owned())\n        .map_err(|e| IntermediateProcessError::Cgroup(e.to_string()))?;\n\n    let current_pid = Pid::this();\n    // setting CPU affinity for tenant container before cgroup move\n    if matches!(args.container_type, ContainerType::TenantContainer { .. }) {\n        if let Some(exec_cpu_affinity) = spec\n            .process()\n            .as_ref()\n            .and_then(|p| p.exec_cpu_affinity().as_ref())\n        {\n            if let Some(initial) = exec_cpu_affinity.initial() {\n                cpu_affinity::set_cpuset_affinity_from_string(current_pid, initial)?;\n            }\n        }\n    }\n    let _ = cpu_affinity::log_cpu_affinity();\n\n    // this needs to be done before we create the init process, so that the init\n    // process will already be captured by the cgroup. It also needs to be done\n    // before we enter the user namespace because if a privileged user starts a\n    // rootless container on a cgroup v1 system we can still fulfill resource\n    // restrictions through the cgroup fs support (delegation through systemd is\n    // not supported for v1 by us). This only works if the user has not yet been\n    // mapped to an unprivileged user by the user namespace however.\n    // In addition this needs to be done before we enter the cgroup namespace as\n    // the cgroup of the process will form the root of the cgroup hierarchy in\n    // the cgroup namespace.\n    apply_cgroups(\n        &cgroup_manager,\n        linux.resources().as_ref(),\n        matches!(args.container_type, ContainerType::InitContainer),\n    )?;\n\n    // setting CPU affinity for tenant container after cgroup move\n    if matches!(args.container_type, ContainerType::TenantContainer { .. }) {\n        if let Some(exec_cpu_affinity) = spec\n            .process()\n            .as_ref()\n            .and_then(|p| p.exec_cpu_affinity().as_ref())\n        {\n            if let Some(cpu_affinity_final) = exec_cpu_affinity.cpu_affinity_final() {\n                cpu_affinity::set_cpuset_affinity_from_string(current_pid, cpu_affinity_final)?;\n            }\n        }\n    }\n\n    // if new user is specified in specification, this will be true and new\n    // namespace will be created, check\n    // https://man7.org/linux/man-pages/man7/user_namespaces.7.html for more\n    // information\n    if let Some(user_namespace) = namespaces.get(LinuxNamespaceType::User)? {\n        setup_userns(&namespaces, user_namespace, main_sender, inter_receiver)?;\n\n        // After UID and GID mapping is configured correctly in the Youki main\n        // process, We want to make sure continue as the root user inside the\n        // new user namespace. This is required because the process of\n        // configuring the container process will require root, even though the\n        // root in the user namespace likely is mapped to an non-privileged user\n        // on the parent user namespace.\n        command.set_id(Uid::from_raw(0), Gid::from_raw(0))?;\n    }\n\n    // set limits and namespaces to the process\n    let proc = spec.process().as_ref().ok_or(MissingSpecError::Process)?;\n    if let Some(rlimits) = proc.rlimits() {\n        for rlimit in rlimits {\n            command.set_rlimit(rlimit).map_err(|err| {\n                tracing::error!(?err, ?rlimit, \"failed to set rlimit\");\n                err\n            })?;\n        }\n    }\n\n    // Pid namespace requires an extra fork to enter, so we enter pid namespace now.\n    if let Some(pid_namespace) = namespaces.get(LinuxNamespaceType::Pid)? {\n        namespaces.unshare_or_setns(pid_namespace)?;\n    }\n\n    let cb: CloneCb = {\n        Box::new(|| {\n            if let Err(ret) = prctl::set_name(\"youki:[2:INIT]\") {\n                tracing::error!(?ret, \"failed to set name for child process\");\n                return ret;\n            }\n\n            // We are inside the forked process here. The first thing we have to do\n            // is to close any unused senders, since fork will make a dup for all\n            // the socket.\n            if let Err(err) = init_sender.close() {\n                tracing::error!(?err, \"failed to close receiver in init process\");\n                return -1;\n            }\n            if let Err(err) = inter_sender.close() {\n                tracing::error!(?err, \"failed to close sender in the intermediate process\");\n                return -1;\n            }\n            match init_process::container_init_process(args, main_sender, init_receiver) {\n                Ok(_) => 0,\n                Err(e) => {\n                    tracing::error!(\"failed to initialize container process: {e}\");\n                    if let Err(err) = main_sender.exec_failed(e.to_string()) {\n                        tracing::error!(?err, \"failed sending error to main sender\");\n                    }\n                    if let ContainerType::TenantContainer { exec_notify_fd } = args.container_type {\n                        let buf = format!(\"{e}\");\n                        let exec_notify_fd =\n                            unsafe { std::os::fd::OwnedFd::from_raw_fd(exec_notify_fd) };\n                        if let Err(err) = write(&exec_notify_fd, buf.as_bytes()) {\n                            tracing::error!(?err, \"failed to write to exec notify fd\");\n                        }\n\n                        // After sending the error through the exec_notify_fd,\n                        // we need to explicitly close the pipe.\n                        drop(exec_notify_fd);\n                    }\n                    -1\n                }\n            }\n        })\n    };\n\n    // We have to record the pid of the init process. The init process will be\n    // inside the pid namespace, so we can't rely on the init process to send us\n    // the correct pid. We also want to clone the init process as a sibling\n    // process to the intermediate process. The intermediate process is only\n    // used as a jumping board to set the init process to the correct\n    // configuration. The youki main process can decide what to do with the init\n    // process and the intermediate process can just exit safely after the job\n    // is done.\n    let pid = fork::container_clone_sibling(cb).map_err(|err| {\n        tracing::error!(\"failed to fork init process: {}\", err);\n        IntermediateProcessError::InitProcess(err)\n    })?;\n\n    // Close the exec_notify_fd in this process\n    if let ContainerType::TenantContainer { exec_notify_fd } = args.container_type {\n        close(exec_notify_fd).map_err(|err| {\n            tracing::error!(\"failed to close exec notify fd: {}\", err);\n            IntermediateProcessError::ExecNotify(err)\n        })?;\n    }\n\n    main_sender.intermediate_ready(pid).map_err(|err| {\n        tracing::error!(\"failed to wait on intermediate process: {}\", err);\n        err\n    })?;\n\n    // Close unused senders here so we don't have lingering socket around.\n    main_sender.close().map_err(|err| {\n        tracing::error!(\"failed to close unused main sender: {}\", err);\n        err\n    })?;\n    inter_sender.close().map_err(|err| {\n        tracing::error!(\n            \"failed to close sender in the intermediate process: {}\",\n            err\n        );\n        err\n    })?;\n    init_sender.close().map_err(|err| {\n        tracing::error!(\"failed to close unused init sender: {}\", err);\n        err\n    })?;\n\n    Ok(())\n}\n\nfn setup_userns(\n    namespaces: &Namespaces,\n    user_namespace: &LinuxNamespace,\n    sender: &mut MainSender,\n    receiver: &mut IntermediateReceiver,\n) -> Result<()> {\n    namespaces.unshare_or_setns(user_namespace)?;\n    if user_namespace.path().is_some() {\n        return Ok(());\n    }\n\n    tracing::debug!(\"creating new user namespace\");\n    // child needs to be dumpable, otherwise the non root parent is not\n    // allowed to write the uid/gid maps\n    prctl::set_dumpable(true).map_err(|e| {\n        IntermediateProcessError::Other(format!(\n            \"error in setting dumpable to true : {}\",\n            nix::errno::Errno::from_raw(e)\n        ))\n    })?;\n    sender.identifier_mapping_request().map_err(|err| {\n        tracing::error!(\"failed to send id mapping request: {}\", err);\n        err\n    })?;\n    receiver.wait_for_mapping_ack().map_err(|err| {\n        tracing::error!(\"failed to receive id mapping ack: {}\", err);\n        err\n    })?;\n    prctl::set_dumpable(false).map_err(|e| {\n        IntermediateProcessError::Other(format!(\n            \"error in setting dumplable to false : {}\",\n            nix::errno::Errno::from_raw(e)\n        ))\n    })?;\n    Ok(())\n}\n\nfn apply_cgroups<\n    C: CgroupManager<Error = E> + ?Sized,\n    E: std::error::Error + Send + Sync + 'static,\n>(\n    cmanager: &C,\n    resources: Option<&LinuxResources>,\n    init: bool,\n) -> Result<()> {\n    let pid = getpid();\n    cmanager.add_task(pid).map_err(|err| {\n        tracing::error!(?pid, ?err, ?init, \"failed to add task to cgroup\");\n        IntermediateProcessError::Cgroup(err.to_string())\n    })?;\n\n    if let Some(resources) = resources {\n        if init {\n            let controller_opt = libcgroups::common::ControllerOpt {\n                resources,\n                freezer_state: None,\n                oom_score_adj: None,\n                disable_oom_killer: false,\n            };\n\n            cmanager.apply(&controller_opt).map_err(|err| {\n                tracing::error!(?pid, ?err, ?init, \"failed to apply cgroup\");\n                IntermediateProcessError::Cgroup(err.to_string())\n            })?;\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::Result;\n    use libcgroups::test_manager::TestManager;\n    use nix::unistd::Pid;\n    use oci_spec::runtime::LinuxResources;\n    use procfs::process::Process;\n\n    use super::*;\n\n    #[test]\n    fn apply_cgroup_init() -> Result<()> {\n        // arrange\n        let cmanager = TestManager::default();\n        let resources = LinuxResources::default();\n\n        // act\n        apply_cgroups(&cmanager, Some(&resources), true)?;\n\n        // assert\n        assert!(cmanager.get_add_task_args().len() == 1);\n        assert_eq!(\n            cmanager.get_add_task_args()[0],\n            Pid::from_raw(Process::myself()?.pid())\n        );\n        assert!(cmanager.apply_called());\n        Ok(())\n    }\n\n    #[test]\n    fn apply_cgroup_tenant() -> Result<()> {\n        // arrange\n        let cmanager = TestManager::default();\n        let resources = LinuxResources::default();\n\n        // act\n        apply_cgroups(&cmanager, Some(&resources), false)?;\n\n        // assert\n        assert_eq!(\n            cmanager.get_add_task_args()[0],\n            Pid::from_raw(Process::myself()?.pid())\n        );\n        assert!(!cmanager.apply_called());\n        Ok(())\n    }\n\n    #[test]\n    fn apply_cgroup_no_resources() -> Result<()> {\n        // arrange\n        let cmanager = TestManager::default();\n\n        // act\n        apply_cgroups(&cmanager, None, true)?;\n        // assert\n        assert_eq!(\n            cmanager.get_add_task_args()[0],\n            Pid::from_raw(Process::myself()?.pid())\n        );\n        assert!(!cmanager.apply_called());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/container_main_process.rs",
    "content": "use std::collections::HashMap;\nuse std::fs;\nuse std::fs::File;\nuse std::os::fd::AsRawFd;\nuse std::path::PathBuf;\n\nuse nix::sys::wait::{WaitStatus, waitpid};\nuse nix::unistd::Pid;\nuse oci_spec::runtime::{Linux, LinuxNamespaceType};\n#[cfg(feature = \"libseccomp\")]\nuse oci_spec::runtime::{SECCOMP_FD_NAME, VERSION as OCI_VERSION};\n\nuse crate::hooks;\nuse crate::network::network_device::dev_change_net_namespace;\nuse crate::process::args::{ContainerArgs, ContainerType};\nuse crate::process::fork::{self, CloneCb};\nuse crate::process::intel_rdt::setup_intel_rdt;\nuse crate::process::{channel, container_intermediate_process};\nuse crate::syscall::SyscallError;\nuse crate::user_ns::UserNamespaceConfig;\n\n#[derive(Debug, thiserror::Error)]\npub enum ProcessError {\n    #[error(transparent)]\n    Channel(#[from] channel::ChannelError),\n    #[error(\"failed to write deny to setgroups\")]\n    SetGroupsDeny(#[source] std::io::Error),\n    #[error(transparent)]\n    UserNamespace(#[from] crate::user_ns::UserNamespaceError),\n    #[error(\"container state is required\")]\n    ContainerStateRequired,\n    #[error(\"failed to wait for intermediate process\")]\n    WaitIntermediateProcess(#[source] nix::Error),\n    #[error(transparent)]\n    IntelRdt(#[from] crate::process::intel_rdt::IntelRdtError),\n    #[error(\"failed to create intermediate process\")]\n    IntermediateProcessFailed(#[source] fork::CloneError),\n    #[error(\"failed seccomp listener\")]\n    #[cfg(feature = \"libseccomp\")]\n    SeccompListener(#[from] crate::process::seccomp_listener::SeccompListenerError),\n    #[error(\"failed setup network device\")]\n    Network(#[from] crate::network::NetworkError),\n    #[error(\"failed syscall\")]\n    SyscallOther(#[source] SyscallError),\n    #[error(\"failed hooks {0}\")]\n    Hooks(#[from] crate::hooks::HookError),\n    #[error(\"failed to build OCI state: {0}\")]\n    OciStateBuild(String),\n}\n\ntype Result<T> = std::result::Result<T, ProcessError>;\n\npub fn container_main_process(container_args: &ContainerArgs) -> Result<(Pid, bool)> {\n    // We use a set of channels to communicate between parent and child process.\n    // Each channel is uni-directional. Because we will pass these channel to\n    // cloned process, we have to be deligent about closing any unused channel.\n    // At minimum, we have to close down any unused senders. The corresponding\n    // receivers will be cleaned up once the senders are closed down.\n    let (mut main_sender, mut main_receiver) = channel::main_channel()?;\n    let mut inter_chan = channel::intermediate_channel()?;\n    let mut init_chan = channel::init_channel()?;\n\n    let cb: CloneCb = {\n        Box::new(|| {\n            if let Err(ret) = prctl::set_name(\"youki:[1:INTER]\") {\n                tracing::error!(?ret, \"failed to set name for child process\");\n                return ret;\n            }\n\n            match container_intermediate_process::container_intermediate_process(\n                container_args,\n                &mut inter_chan,\n                &mut init_chan,\n                &mut main_sender,\n            ) {\n                Ok(_) => 0,\n                Err(err) => {\n                    tracing::error!(\"failed to run intermediate process {}\", err);\n                    match main_sender.send_error(err.to_string()) {\n                        Ok(_) => {}\n                        Err(e) => {\n                            tracing::error!(\n                                \"error in sending intermediate error message {} to main: {}\",\n                                err,\n                                e\n                            )\n                        }\n                    }\n                    -1\n                }\n            }\n        })\n    };\n\n    let container_clone_fn = if container_args.as_sibling {\n        fork::container_clone_sibling\n    } else {\n        fork::container_clone\n    };\n\n    let intermediate_pid = container_clone_fn(cb).map_err(|err| {\n        tracing::error!(\"failed to fork intermediate process: {}\", err);\n        ProcessError::IntermediateProcessFailed(err)\n    })?;\n\n    // Close down unused fds. The corresponding fds are duplicated to the\n    // child process during clone.\n    main_sender.close().map_err(|err| {\n        tracing::error!(\"failed to close unused sender: {}\", err);\n        err\n    })?;\n\n    let (mut inter_sender, inter_receiver) = inter_chan;\n    let (mut init_sender, init_receiver) = init_chan;\n\n    // If creating a container with new user namespace, the intermediate process will ask\n    // the main process to set up uid and gid mapping, once the intermediate\n    // process enters into a new user namespace.\n    if let Some(config) = &container_args.user_ns_config {\n        main_receiver.wait_for_mapping_request()?;\n        setup_mapping(config, intermediate_pid)?;\n        inter_sender.mapping_written()?;\n    }\n\n    // At this point, we don't need to send any message to intermediate process anymore,\n    // so we want to close this sender at the earliest point.\n    inter_sender.close().map_err(|err| {\n        tracing::error!(\"failed to close unused intermediate sender: {}\", err);\n        err\n    })?;\n\n    // The intermediate process will send the init pid once it forks the init\n    // process.  The intermediate process should exit after this point.\n    let init_pid = main_receiver.wait_for_intermediate_ready()?;\n    let mut need_to_clean_up_intel_rdt_subdirectory = false;\n\n    if let Some(linux) = container_args.spec.linux() {\n        if let Some(intel_rdt) = linux.intel_rdt() {\n            let container_id = container_args\n                .container\n                .as_ref()\n                .map(|container| container.id());\n            need_to_clean_up_intel_rdt_subdirectory =\n                setup_intel_rdt(container_id, &init_pid, intel_rdt)?;\n        }\n    }\n\n    // if file to write the pid to is specified, write pid of the child\n    if let Some(pid_file) = &container_args.pid_file {\n        if let Err(err) = fs::write(pid_file, format!(\"{init_pid}\")) {\n            tracing::warn!(\"failed to write pid to file: {err}\");\n        }\n    }\n\n    if matches!(container_args.container_type, ContainerType::InitContainer) {\n        if let Some(hooks) = container_args.spec.hooks() {\n            main_receiver.wait_for_hook_request()?;\n            if let Some(container_for_hooks) = &container_args.container {\n                hooks::run_hooks(\n                    hooks.prestart().as_ref(),\n                    Some(&container_for_hooks.state),\n                    None,\n                    Some(init_pid),\n                )\n                .map_err(|err| {\n                    tracing::error!(\"failed to run prestart hooks: {}\", err);\n                    err\n                })?;\n\n                hooks::run_hooks(\n                    hooks.create_runtime().as_ref(),\n                    Some(&container_for_hooks.state),\n                    None,\n                    Some(init_pid),\n                )\n                .map_err(|err| {\n                    tracing::error!(\"failed to run create runtime hooks: {}\", err);\n                    err\n                })?;\n            }\n            init_sender.hook_done()?;\n        }\n    }\n\n    if let Some(linux) = container_args.spec.linux() {\n        move_network_devices_to_container(linux, init_pid, &mut main_receiver, &mut init_sender)?;\n\n        #[cfg(feature = \"libseccomp\")]\n        if let Some(seccomp) = linux.seccomp() {\n            let container = container_args\n                .container\n                .as_ref()\n                .ok_or(ProcessError::ContainerStateRequired)?;\n\n            // Determine OCI status based on container type (matching runc behavior)\n            let oci_status = match container_args.container_type {\n                ContainerType::InitContainer => oci_spec::runtime::ContainerState::Creating,\n                ContainerType::TenantContainer { .. } => oci_spec::runtime::ContainerState::Running,\n            };\n\n            // Build OCI-compliant ContainerProcessState using builder pattern\n            let oci_state = oci_spec::runtime::StateBuilder::default()\n                .version(OCI_VERSION)\n                .id(container.state.id.clone())\n                .status(oci_status)\n                .pid(init_pid.as_raw())\n                .bundle(container.state.bundle.clone())\n                .annotations(container.state.annotations.clone().unwrap_or_default())\n                .build()\n                .map_err(|e| ProcessError::OciStateBuild(e.to_string()))?;\n\n            let state = oci_spec::runtime::ContainerProcessStateBuilder::default()\n                .version(OCI_VERSION)\n                .fds(vec![SECCOMP_FD_NAME.to_string()])\n                .pid(init_pid.as_raw())\n                .metadata(seccomp.listener_metadata().clone().unwrap_or_default())\n                .state(oci_state)\n                .build()\n                .map_err(|e| ProcessError::OciStateBuild(e.to_string()))?;\n            crate::process::seccomp_listener::sync_seccomp(\n                seccomp,\n                &state,\n                &mut init_sender,\n                &mut main_receiver,\n            )?;\n        }\n    }\n\n    // We don't need to send anything to the init process after this point, so\n    // close the sender.\n    init_sender.close().map_err(|err| {\n        tracing::error!(\"failed to close unused init sender: {}\", err);\n        err\n    })?;\n\n    main_receiver.wait_for_init_ready().map_err(|err| {\n        tracing::error!(\"failed to wait for init ready: {}\", err);\n        err\n    })?;\n\n    tracing::debug!(\"init pid is {:?}\", init_pid);\n\n    // Close the receiver ends to avoid leaking file descriptors.\n\n    inter_receiver.close().map_err(|err| {\n        tracing::error!(\"failed to close intermediate process receiver: {}\", err);\n        err\n    })?;\n\n    init_receiver.close().map_err(|err| {\n        tracing::error!(\"failed to close init process receiver: {}\", err);\n        err\n    })?;\n\n    main_receiver.close().map_err(|err| {\n        tracing::error!(\"failed to close main process receiver: {}\", err);\n        err\n    })?;\n\n    // Before the main process returns, we want to make sure the intermediate\n    // process is exit and reaped. By this point, the intermediate process\n    // should already exited successfully. If intermediate process errors out,\n    // the `init_ready` will not be sent.\n    match waitpid(intermediate_pid, None) {\n        Ok(WaitStatus::Exited(_, 0)) => (),\n        Ok(WaitStatus::Exited(_, s)) => {\n            tracing::warn!(\"intermediate process failed with exit status: {s}\");\n        }\n        Ok(WaitStatus::Signaled(_, sig, _)) => {\n            tracing::warn!(\"intermediate process killed with signal: {sig}\")\n        }\n        Ok(_) => (),\n        Err(nix::errno::Errno::ECHILD) => {\n            // This is safe because intermediate_process and main_process check if the process is\n            // finished by piping instead of exit code.\n            tracing::warn!(\"intermediate process already reaped\");\n        }\n        Err(err) => return Err(ProcessError::WaitIntermediateProcess(err)),\n    };\n\n    Ok((init_pid, need_to_clean_up_intel_rdt_subdirectory))\n}\n\nfn setup_mapping(config: &UserNamespaceConfig, pid: Pid) -> Result<()> {\n    tracing::debug!(\"write mapping for pid {:?}\", pid);\n    if !config.privileged {\n        // The main process is running as an unprivileged user and cannot write the mapping\n        // until \"deny\" has been written to setgroups. See CVE-2014-8989.\n        std::fs::write(format!(\"/proc/{pid}/setgroups\"), \"deny\")\n            .map_err(ProcessError::SetGroupsDeny)?;\n    }\n\n    config.write_uid_mapping(pid).map_err(|err| {\n        tracing::error!(\"failed to write uid mapping for pid {:?}: {}\", pid, err);\n        err\n    })?;\n    config.write_gid_mapping(pid).map_err(|err| {\n        tracing::error!(\"failed to write gid mapping for pid {:?}: {}\", pid, err);\n        err\n    })?;\n    Ok(())\n}\n\n/// Moves configured network devices from the host to the container's network namespace.\n/// This function waits for the init process to join its namespace, then transfers each\n/// configured device while preserving network addresses. Returns early if the container\n/// runs in the host network namespace.\nfn move_network_devices_to_container(\n    linux: &Linux,\n    init_pid: Pid,\n    main_receiver: &mut channel::MainReceiver,\n    init_sender: &mut channel::InitSender,\n) -> Result<()> {\n    // Early return if there are no network devices to move\n    let devices = match linux.net_devices() {\n        Some(devs) if !devs.is_empty() => devs,\n        _ => return Ok(()),\n    };\n\n    if let Some(namespaces) = linux.namespaces() {\n        // network devices are not moved for containers running in the host network.\n        let net_ns = match namespaces\n            .iter()\n            .find(|ns| ns.typ() == LinuxNamespaceType::Network)\n        {\n            Some(ns) => ns,\n            None => return Ok(()),\n        };\n\n        // Wait for the init process to signal that it has joined the network namespace\n        // and is ready for network device setup\n        main_receiver.wait_for_network_setup_ready()?;\n\n        // the container init process has already joined the provided net namespace,\n        // so we can use the process's net ns path directly.\n        let default_ns_path = PathBuf::from(format!(\"/proc/{}/ns/net\", init_pid.as_raw()));\n        let ns_path = net_ns.path().as_deref().unwrap_or(&default_ns_path);\n\n        // Open the network namespace file and validate it exists before moving devices\n        let netns_file = File::open(ns_path).map_err(|err| {\n            tracing::error!(\n                \"failed to open network namespace at {}: {}\",\n                ns_path.display(),\n                err\n            );\n            ProcessError::Network(err.into())\n        })?;\n        let netns_fd = netns_file.as_raw_fd();\n\n        // If moving any of the network devices fails, we return an error immediately.\n        // The runtime spec requires that the kernel handles moving back any devices\n        // that were successfully moved before the failure occurred.\n        // See: https://github.com/opencontainers/runtime-spec/blob/27cb0027fd92ef81eda1ea3a8153b8337f56d94a/config-linux.md#namespace-lifecycle-and-container-termination\n        let addrs_map = devices\n            .iter()\n            .map(|(name, net_dev)| {\n                let addrs = dev_change_net_namespace(name, netns_fd, net_dev).map_err(|err| {\n                    tracing::error!(\"failed to dev_change_net_namespace: {}\", err);\n                    err\n                })?;\n                Ok((name.clone(), addrs))\n            })\n            .collect::<Result<HashMap<String, Vec<crate::network::cidr::CidrAddress>>>>()?;\n        init_sender.move_network_device(addrs_map)?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use anyhow::Result;\n    use nix::sched::{CloneFlags, unshare};\n    use nix::unistd::{self, getgid, getuid};\n    use oci_spec::runtime::LinuxIdMappingBuilder;\n    use serial_test::serial;\n\n    use super::*;\n    use crate::process::channel::{intermediate_channel, main_channel};\n    use crate::user_ns::UserNamespaceIDMapper;\n\n    #[test]\n    #[serial]\n    fn setup_uid_mapping_should_succeed() -> Result<()> {\n        let uid_mapping = LinuxIdMappingBuilder::default()\n            .host_id(getuid())\n            .container_id(0u32)\n            .size(1u32)\n            .build()?;\n        let uid_mappings = vec![uid_mapping];\n        let tmp = tempfile::tempdir()?;\n        let id_mapper = UserNamespaceIDMapper::new_test(tmp.path().to_path_buf());\n        let ns_config = UserNamespaceConfig {\n            uid_mappings: Some(uid_mappings),\n            privileged: true,\n            id_mapper: id_mapper.clone(),\n            ..Default::default()\n        };\n        let (mut parent_sender, mut parent_receiver) = main_channel()?;\n        let (mut child_sender, mut child_receiver) = intermediate_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                parent_receiver.wait_for_mapping_request()?;\n                parent_receiver.close()?;\n\n                // In test, we fake the uid path in /proc/{pid}/uid_map, so we\n                // need to ensure the path exists before we write the mapping.\n                // The path requires the pid we use, so we can only do do after\n                // obtaining the child pid here.\n                id_mapper.ensure_uid_path(&child)?;\n                setup_mapping(&ns_config, child)?;\n                let line = fs::read_to_string(id_mapper.get_uid_path(&child))?;\n                let split_lines = line.split_whitespace();\n                for (act, expect) in split_lines.zip([\n                    uid_mapping.container_id().to_string(),\n                    uid_mapping.host_id().to_string(),\n                    uid_mapping.size().to_string(),\n                ]) {\n                    assert_eq!(act, expect);\n                }\n                child_sender.mapping_written()?;\n                child_sender.close()?;\n            }\n            unistd::ForkResult::Child => {\n                prctl::set_dumpable(true).unwrap();\n                unshare(CloneFlags::CLONE_NEWUSER)?;\n                parent_sender.identifier_mapping_request()?;\n                parent_sender.close()?;\n                child_receiver.wait_for_mapping_ack()?;\n                child_receiver.close()?;\n                std::process::exit(0);\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn setup_gid_mapping_should_succeed() -> Result<()> {\n        let gid_mapping = LinuxIdMappingBuilder::default()\n            .host_id(getgid())\n            .container_id(0u32)\n            .size(1u32)\n            .build()?;\n        let gid_mappings = vec![gid_mapping];\n        let tmp = tempfile::tempdir()?;\n        let id_mapper = UserNamespaceIDMapper::new_test(tmp.path().to_path_buf());\n        let ns_config = UserNamespaceConfig {\n            gid_mappings: Some(gid_mappings),\n            id_mapper: id_mapper.clone(),\n            ..Default::default()\n        };\n        let (mut parent_sender, mut parent_receiver) = main_channel()?;\n        let (mut child_sender, mut child_receiver) = intermediate_channel()?;\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                parent_receiver.wait_for_mapping_request()?;\n                parent_receiver.close()?;\n\n                // In test, we fake the gid path in /proc/{pid}/gid_map, so we\n                // need to ensure the path exists before we write the mapping.\n                // The path requires the pid we use, so we can only do do after\n                // obtaining the child pid here.\n                id_mapper.ensure_gid_path(&child)?;\n                setup_mapping(&ns_config, child)?;\n                let line = fs::read_to_string(id_mapper.get_gid_path(&child))?;\n                let split_lines = line.split_whitespace();\n                for (act, expect) in split_lines.zip([\n                    gid_mapping.container_id().to_string(),\n                    gid_mapping.host_id().to_string(),\n                    gid_mapping.size().to_string(),\n                ]) {\n                    assert_eq!(act, expect);\n                }\n                assert_eq!(\n                    fs::read_to_string(format!(\"/proc/{}/setgroups\", child.as_raw()))?,\n                    \"deny\\n\",\n                );\n                child_sender.mapping_written()?;\n                child_sender.close()?;\n            }\n            unistd::ForkResult::Child => {\n                prctl::set_dumpable(true).unwrap();\n                unshare(CloneFlags::CLONE_NEWUSER)?;\n                parent_sender.identifier_mapping_request()?;\n                parent_sender.close()?;\n                child_receiver.wait_for_mapping_ack()?;\n                child_receiver.close()?;\n                std::process::exit(0);\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/cpu_affinity.rs",
    "content": "use nix::sched::{CpuSet, sched_getaffinity, sched_setaffinity};\nuse nix::unistd::Pid;\nuse tracing::{Level, enabled};\n\n#[derive(Debug, thiserror::Error)]\npub enum CPUAffinityError {\n    #[error(\"invalid CPU string: {0}\")]\n    ParseError(String),\n    #[error(\"values larger than {max} are not supported\")]\n    CpuOutOfRange { cpu: usize, max: usize },\n    #[error(\"failed to set CPU for CPU {cpu}: {source}\")]\n    CpuSet {\n        cpu: usize,\n        #[source]\n        source: nix::Error,\n    },\n    #[error(\"failed to setaffinity\")]\n    SetAffinity(#[source] nix::Error),\n    #[error(\"failed to getaffinity\")]\n    GetAffinity(#[source] nix::Error),\n}\n\ntype Result<T> = std::result::Result<T, CPUAffinityError>;\n\npub fn to_cpuset(cpuset_str: &str) -> Result<CpuSet> {\n    let mut cpuset = CpuSet::new();\n    let max_cpu = CpuSet::count();\n\n    for part in cpuset_str\n        .trim()\n        .split(',')\n        .map(str::trim)\n        .filter(|s| !s.is_empty())\n    {\n        match part.split_once('-') {\n            Some((start_str, end_str)) => {\n                let start = parse_cpu_index(start_str, max_cpu)?;\n                let end = parse_cpu_index(end_str, max_cpu)?;\n                if start > end {\n                    return Err(CPUAffinityError::ParseError(format!(\n                        \"invalid range: {}-{}\",\n                        start, end\n                    )));\n                }\n                for cpu in start..=end {\n                    cpuset\n                        .set(cpu)\n                        .map_err(|e| CPUAffinityError::CpuSet { cpu, source: e })?;\n                }\n            }\n            None => {\n                let cpu = parse_cpu_index(part, max_cpu)?;\n                cpuset\n                    .set(cpu)\n                    .map_err(|e| CPUAffinityError::CpuSet { cpu, source: e })?;\n            }\n        }\n    }\n    Ok(cpuset)\n}\n\nfn parse_cpu_index(s: &str, max_cpu: usize) -> Result<usize> {\n    let cpu: usize = s\n        .parse()\n        .map_err(|_| CPUAffinityError::ParseError(s.to_string()))?;\n    if cpu >= max_cpu {\n        return Err(CPUAffinityError::CpuOutOfRange {\n            cpu,\n            max: max_cpu - 1,\n        });\n    }\n    Ok(cpu)\n}\n\npub fn set_cpuset_affinity_from_string(pid: Pid, cpuset_str: &str) -> Result<()> {\n    tracing::debug!(?cpuset_str, \"setting CPU affinity for tenant container\");\n    sched_setaffinity(pid, &to_cpuset(cpuset_str)?).map_err(CPUAffinityError::SetAffinity)\n}\n\n// Logs a compact CPU affinity bitmask similar to runc's nsexec.c (see: https://github.com/opencontainers/runc/blob/main/libcontainer/nsenter/nsexec.c#L676).\n// This helps in debugging which CPUs the current process is allowed to run on.\n// Only logs when DEBUG level is enabled.\npub fn log_cpu_affinity() -> Result<()> {\n    if !enabled!(Level::DEBUG) {\n        return Ok(());\n    }\n    let cpuset = sched_getaffinity(Pid::this()).map_err(CPUAffinityError::GetAffinity)?;\n    let mask = (0..usize::BITS as usize)\n        .filter(|&i| cpuset.is_set(i).unwrap_or(false))\n        .fold(0usize, |mask, i| mask | (1usize << i));\n    tracing::debug!(\"affinity: 0x{:x}\", mask);\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_to_cpuset_single_values() {\n        let cpuset = to_cpuset(\"0,1,2\").unwrap();\n        for cpu in [0, 1, 2] {\n            assert!(cpuset.is_set(cpu).unwrap());\n        }\n    }\n\n    #[test]\n    fn test_to_cpuset_range() {\n        let cpuset = to_cpuset(\"3-5\").unwrap();\n        for cpu in [3, 4, 5] {\n            assert!(cpuset.is_set(cpu).unwrap());\n        }\n    }\n\n    #[test]\n    fn test_to_cpuset_mixed() {\n        let cpuset = to_cpuset(\"0, 2-4, 6\").unwrap();\n        for cpu in [0, 2, 3, 4, 6] {\n            assert!(cpuset.is_set(cpu).unwrap());\n        }\n        for cpu in [1, 5, 7] {\n            assert!(!cpuset.is_set(cpu).unwrap_or(false));\n        }\n    }\n\n    #[test]\n    fn test_to_cpuset_spaces_and_empty() {\n        let cpuset = to_cpuset(\"  , 1 , 3 , 5-7 , \").unwrap();\n        for cpu in [1, 3, 5, 6, 7] {\n            assert!(cpuset.is_set(cpu).unwrap());\n        }\n    }\n\n    #[test]\n    fn test_to_cpuset_invalid_range() {\n        let err = to_cpuset(\"5-3\").unwrap_err();\n        matches!(err, CPUAffinityError::ParseError(_));\n    }\n\n    #[test]\n    fn test_to_cpuset_invalid_value() {\n        let err = to_cpuset(\"a,b,c\").unwrap_err();\n        matches!(err, CPUAffinityError::ParseError(_));\n    }\n\n    #[test]\n    fn test_to_cpuset_max_allowed_cpu() {\n        let max = CpuSet::count();\n        let highest = max - 1;\n        let cpuset = to_cpuset(&highest.to_string()).unwrap();\n        assert!(cpuset.is_set(highest).unwrap());\n    }\n\n    #[test]\n    fn test_to_cpuset_exceeds_max_cpu() {\n        let max = CpuSet::count();\n        let result = to_cpuset(&max.to_string());\n        assert!(matches!(\n            result,\n            Err(CPUAffinityError::CpuOutOfRange { .. })\n        ));\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/fork.rs",
    "content": "use std::ffi::c_int;\nuse std::num::NonZeroUsize;\n\nuse libc::SIGCHLD;\nuse nix::sys::{mman, resource};\nuse nix::unistd::Pid;\n\n#[derive(Debug, thiserror::Error)]\npub enum CloneError {\n    #[error(\"failed to clone process\")]\n    Clone(#[source] nix::Error),\n    #[error(\"failed to get system memory page size\")]\n    PageSize(#[source] nix::Error),\n    #[error(\"failed to get resource limit\")]\n    ResourceLimit(#[source] nix::Error),\n    #[error(\"the stack size is zero\")]\n    ZeroStackSize,\n    #[error(\"failed to allocate stack\")]\n    StackAllocation(#[source] nix::Error),\n    #[error(\"failed to create stack guard page\")]\n    GuardPage(#[source] nix::Error),\n    #[error(\"unknown error code {0}\")]\n    UnknownErrno(i32),\n}\n\n/// The callback function used in clone system call. The return value is i32\n/// which is consistent with C functions return code. The trait has to be\n/// `FnMut` because we need to be able to call the closure multiple times, once\n/// in clone3 and once in clone if fallback is required. The closure is boxed\n/// because we need to store the closure on heap, not stack in the case of\n/// `clone`. Unlike `fork` or `clone3`, the `clone` glibc wrapper requires us to\n/// pass in a child stack, which is empty. By storing the closure in heap, we\n/// can then in the new process to re-box the heap memory back to a closure\n/// correctly.\npub type CloneCb<'a> = Box<dyn FnMut() -> i32 + 'a>;\n\n// Clone a sibling process that shares the same parent as the calling\n// process. This is used to launch the container init process so the parent\n// process of the calling process can receive ownership of the process. If we\n// clone a child process as the init process, the calling process (likely the\n// youki main process) will exit and the init process will be re-parented to the\n// process 1 (system init process), which is not the right behavior of what we\n// look for.\npub fn container_clone_sibling(cb: CloneCb) -> Result<Pid, CloneError> {\n    // Note: normally, an exit signal is required, but when using\n    // `CLONE_PARENT`, the `clone3` will return EINVAL if an exit signal is set.\n    // The older `clone` will not return EINVAL in this case. Instead it ignores\n    // the exit signal bits in the glibc wrapper. Therefore, we explicitly set\n    // the exit_signal to None here, so this works for both version of clone.\n    clone_internal(cb, libc::CLONE_PARENT as u64, None)\n}\n\n// Clone a child process and execute the callback.\npub fn container_clone(cb: CloneCb) -> Result<Pid, CloneError> {\n    clone_internal(cb, 0, Some(SIGCHLD as u64))\n}\n\n// An internal wrapper to manage the clone3 vs clone fallback logic.\nfn clone_internal(\n    mut cb: CloneCb,\n    flags: u64,\n    exit_signal: Option<u64>,\n) -> Result<Pid, CloneError> {\n    match clone3(&mut cb, flags, exit_signal) {\n        Ok(pid) => Ok(pid),\n        // For now, we decide to only fallback on ENOSYS\n        Err(CloneError::Clone(nix::Error::ENOSYS)) => {\n            tracing::debug!(\"clone3 is not supported, fallback to clone\");\n            let pid = clone(cb, flags, exit_signal)?;\n\n            Ok(pid)\n        }\n        Err(err) => Err(err),\n    }\n}\n\n// Unlike the clone call, clone3 is currently using the kernel syscall, mimicking\n// the interface of fork. There is not need to explicitly manage the memory, so\n// we can safely passing the callback closure as reference.\nfn clone3(cb: &mut CloneCb, flags: u64, exit_signal: Option<u64>) -> Result<Pid, CloneError> {\n    #[repr(C)]\n    struct clone3_args {\n        flags: u64,\n        pidfd: u64,\n        child_tid: u64,\n        parent_tid: u64,\n        exit_signal: u64,\n        stack: u64,\n        stack_size: u64,\n        tls: u64,\n        set_tid: u64,\n        set_tid_size: u64,\n        cgroup: u64,\n    }\n    let mut args = clone3_args {\n        flags,\n        pidfd: 0,\n        child_tid: 0,\n        parent_tid: 0,\n        exit_signal: exit_signal.unwrap_or(0),\n        stack: 0,\n        stack_size: 0,\n        tls: 0,\n        set_tid: 0,\n        set_tid_size: 0,\n        cgroup: 0,\n    };\n    let args_ptr = &mut args as *mut clone3_args;\n    let args_size = std::mem::size_of::<clone3_args>();\n    // For now, we can only use clone3 as a kernel syscall. Libc wrapper is not\n    // available yet. This can have undefined behavior because libc authors do\n    // not like people calling kernel syscall to directly create processes. Libc\n    // does perform additional bookkeeping when calling clone or fork. So far,\n    // we have not observed any issues with calling clone3 directly, but we\n    // should keep an eye on it.\n    match unsafe { libc::syscall(libc::SYS_clone3, args_ptr, args_size) } {\n        -1 => Err(CloneError::Clone(nix::Error::last())),\n        0 => {\n            // Inside the cloned process, we execute the callback and exit with\n            // the return code.\n            std::process::exit(cb());\n        }\n        ret if ret >= 0 => Ok(Pid::from_raw(ret as i32)),\n        ret => Err(CloneError::UnknownErrno(ret as i32)),\n    }\n}\n\nfn clone(cb: CloneCb, flags: u64, exit_signal: Option<u64>) -> Result<Pid, CloneError> {\n    const DEFAULT_STACK_SIZE: usize = 8 * 1024 * 1024; // 8M\n    const DEFAULT_PAGE_SIZE: usize = 4 * 1024; // 4K\n\n    // Use sysconf to find the page size. If there is an error, we assume\n    // the default 4K page size.\n    let page_size = nix::unistd::sysconf(nix::unistd::SysconfVar::PAGE_SIZE)\n        .map_err(CloneError::PageSize)?\n        .map(|size| size as usize)\n        .unwrap_or(DEFAULT_PAGE_SIZE);\n\n    // Find out the default stack max size through getrlimit.\n    let (rlim_cur, _) =\n        resource::getrlimit(resource::Resource::RLIMIT_STACK).map_err(CloneError::ResourceLimit)?;\n    // mmap will return ENOMEM if stack size is unlimited when we create the\n    // child stack, so we need to set a reasonable default stack size.\n    let default_stack_size = if rlim_cur != u64::MAX {\n        rlim_cur as usize\n    } else {\n        tracing::debug!(\n            \"stack size returned by getrlimit() is unlimited, use DEFAULT_STACK_SIZE(8MB)\"\n        );\n        DEFAULT_STACK_SIZE\n    };\n\n    // Using the clone syscall requires us to create the stack space for the\n    // child process instead of taken cared for us like fork call. We use mmap\n    // here to create the stack.  Instead of guessing how much space the child\n    // process needs, we allocate through mmap to the system default limit,\n    // which is 8MB on most of the linux system today. This is OK since mmap\n    // will only reserve the address space upfront, instead of allocating\n    // physical memory upfront.  The stack will grow as needed, up to the size\n    // reserved, so no wasted memory here. Lastly, the child stack only needs\n    // to support the container init process set up code in Youki. When Youki\n    // calls exec into the container payload, exec will reset the stack.  Note,\n    // do not use MAP_GROWSDOWN since it is not well supported.\n    // Ref: https://man7.org/linux/man-pages/man2/mmap.2.html\n    let child_stack = unsafe {\n        mman::mmap_anonymous(\n            None,\n            NonZeroUsize::new(default_stack_size).ok_or(CloneError::ZeroStackSize)?,\n            mman::ProtFlags::PROT_READ | mman::ProtFlags::PROT_WRITE,\n            mman::MapFlags::MAP_PRIVATE | mman::MapFlags::MAP_STACK,\n        )\n        .map_err(CloneError::StackAllocation)?\n    };\n    unsafe {\n        // Consistent with how pthread_create sets up the stack, we create a\n        // guard page of 1 page, to protect the child stack collision. Note, for\n        // clone call, the child stack will grow downward, so the bottom of the\n        // child stack is in the beginning.\n        mman::mprotect(child_stack, page_size, mman::ProtFlags::PROT_NONE)\n            .map_err(CloneError::GuardPage)?;\n    };\n\n    // Since the child stack for clone grows downward, we need to pass in\n    // the top of the stack address.\n    let child_stack_top = unsafe { child_stack.as_ptr().add(default_stack_size) };\n\n    // Combine the clone flags with exit signals.\n    let combined_flags = (flags | exit_signal.unwrap_or(0)) as c_int;\n\n    // We are passing the boxed closure \"cb\" into the clone function as the a\n    // function pointer in C. The box closure in Rust is both a function pointer\n    // and a struct. However, when casting the box closure into libc::c_void,\n    // the function pointer will be lost. Therefore, to work around the issue,\n    // we double box the closure. This is consistent with how std::unix::thread\n    // handles the closure.\n    // Ref: https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/thread.rs\n    let data = Box::into_raw(Box::new(cb));\n    // The main is a wrapper function passed into clone call below. The \"data\"\n    // arg is actually a raw pointer to the Box closure. so here, we re-box the\n    // pointer back into a box closure so the main takes ownership of the\n    // memory. Then we can call the closure.\n    extern \"C\" fn main(data: *mut libc::c_void) -> libc::c_int {\n        unsafe { Box::from_raw(data as *mut CloneCb)() }\n    }\n\n    // The nix::sched::clone wrapper doesn't provide the right interface.  Using\n    // the clone syscall is one of the rare cases where we don't want rust to\n    // manage the child stack memory. Instead, we want to use c_void directly\n    // here.  Therefore, here we are using libc::clone syscall directly for\n    // better control.  The child stack will be cleaned when exec is called or\n    // the child process terminates. The nix wrapper also does not treat the\n    // closure memory correctly. The wrapper implementation fails to pass the\n    // right ownership to the new child process.\n    // Ref: https://github.com/nix-rust/nix/issues/919\n    // Ref: https://github.com/nix-rust/nix/pull/920\n    let ret = unsafe {\n        libc::clone(\n            main,\n            child_stack_top,\n            combined_flags,\n            data as *mut libc::c_void,\n        )\n    };\n\n    // After the clone returns, the heap memory associated with the Box closure\n    // is duplicated in the cloned process. Therefore, we can safely re-box the\n    // closure from the raw pointer and let rust to continue managing the\n    // memory. We call drop here explicitly to avoid the warning that the\n    // closure is not used. This is correct since the closure is called in the\n    // cloned process, not the parent process.\n    unsafe { drop(Box::from_raw(data)) };\n    match ret {\n        -1 => Err(CloneError::Clone(nix::Error::last())),\n        pid if ret > 0 => Ok(Pid::from_raw(pid)),\n        _ => unreachable!(\"clone returned a negative pid {ret}\"),\n    }\n}\n\n#[cfg(test)]\nmod test {\n    use anyhow::{Context, Result, bail};\n    use nix::sys::wait::{WaitStatus, waitpid};\n    use nix::unistd;\n\n    use super::*;\n    use crate::channel::channel;\n\n    #[test]\n    fn test_container_fork() -> Result<()> {\n        let pid = container_clone(Box::new(|| 0))?;\n        match waitpid(pid, None).expect(\"wait pid failed.\") {\n            WaitStatus::Exited(p, status) => {\n                assert_eq!(pid, p);\n                assert_eq!(status, 0);\n                Ok(())\n            }\n            _ => bail!(\"test failed\"),\n        }\n    }\n\n    #[test]\n    fn test_container_err_fork() -> Result<()> {\n        let pid = container_clone(Box::new(|| -1))?;\n        match waitpid(pid, None).expect(\"wait pid failed.\") {\n            WaitStatus::Exited(p, status) => {\n                assert_eq!(pid, p);\n                assert_eq!(status, 255);\n                Ok(())\n            }\n            _ => bail!(\"test failed\"),\n        }\n    }\n\n    #[test]\n    fn test_container_clone_sibling() -> Result<()> {\n        // The `container_clone_sibling` will create a sibling process (share\n        // the same parent) of the calling process. In Unix, a process can only\n        // wait on the immediate children process and can't wait on the sibling\n        // process. Therefore, to test the logic, we will have to fork a process\n        // first and then let the forked process call `container_clone_sibling`.\n        // Then the testing process (the process where test is called), who are\n        // the parent to this forked process and the sibling process cloned by\n        // the `container_clone_sibling`, can wait on both processes.\n\n        // We need to use a channel so that the forked process can pass the pid\n        // of the sibling process to the testing process.\n        let (sender, receiver) = &mut channel::<i32>()?;\n\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                let sibling_process_pid = Pid::from_raw(\n                    receiver\n                        .recv()\n                        .context(\"failed to receive the sibling pid from forked process\")?,\n                );\n                receiver.close()?;\n                match waitpid(sibling_process_pid, None).expect(\"wait pid failed.\") {\n                    WaitStatus::Exited(p, status) => {\n                        assert_eq!(sibling_process_pid, p);\n                        assert_eq!(status, 0);\n                    }\n                    _ => bail!(\"failed to wait on the sibling process\"),\n                }\n                // After sibling process exits, we can wait on the forked process.\n                match waitpid(child, None).expect(\"wait pid failed.\") {\n                    WaitStatus::Exited(p, status) => {\n                        assert_eq!(child, p);\n                        assert_eq!(status, 0);\n                    }\n                    _ => bail!(\"failed to wait on the forked process\"),\n                }\n            }\n            unistd::ForkResult::Child => {\n                // Inside the forked process. We call `container_clone` and pass\n                // the pid to the parent process.\n                let pid = container_clone_sibling(Box::new(|| 0))?;\n                sender.send(pid.as_raw())?;\n                sender.close()?;\n                std::process::exit(0);\n            }\n        };\n\n        Ok(())\n    }\n\n    // This test depends on libseccomp to work.\n    #[cfg(feature = \"libseccomp\")]\n    #[test]\n    fn test_clone_fallback() -> Result<()> {\n        use oci_spec::runtime::{\n            Arch, LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder,\n        };\n\n        use crate::test_utils::TestCallbackError;\n\n        fn has_clone3() -> bool {\n            // We use the probe syscall to check if the kernel supports clone3 or\n            // seccomp has successfully blocked clone3.\n            let res = unsafe { libc::syscall(libc::SYS_clone3, 0, 0) };\n            let err = (res == -1)\n                .then(std::io::Error::last_os_error)\n                .expect(\"probe syscall should not succeed\");\n            err.raw_os_error() != Some(libc::ENOSYS)\n        }\n\n        // To test the fallback behavior, we will create a seccomp rule that\n        // blocks `clone3` as ENOSYS.\n        let syscall = LinuxSyscallBuilder::default()\n            .names(vec![String::from(\"clone3\")])\n            .action(LinuxSeccompAction::ScmpActErrno)\n            .errno_ret(libc::ENOSYS as u32)\n            .build()?;\n        let seccomp_profile = LinuxSeccompBuilder::default()\n            .default_action(LinuxSeccompAction::ScmpActAllow)\n            .architectures(vec![Arch::ScmpArchNative])\n            .syscalls(vec![syscall])\n            .build()?;\n\n        crate::test_utils::test_in_child_process(|| {\n            // We use seccomp to block `clone3`\n            let _ = prctl::set_no_new_privileges(true);\n            crate::seccomp::initialize_seccomp(&seccomp_profile)\n                .expect(\"failed to initialize seccomp\");\n\n            if has_clone3() {\n                return Err(TestCallbackError::Custom(\n                    \"clone3 is not blocked by seccomp\".into(),\n                ));\n            }\n\n            let pid = container_clone(Box::new(|| 0)).map_err(|err| err.to_string())?;\n            match waitpid(pid, None).expect(\"wait pid failed.\") {\n                WaitStatus::Exited(p, status) => {\n                    assert_eq!(pid, p);\n                    assert_eq!(status, 0);\n                }\n                status => {\n                    return Err(TestCallbackError::Custom(format!(\n                        \"failed to wait on child process: {:?}\",\n                        status\n                    )));\n                }\n            };\n\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/init/context.rs",
    "content": "use std::collections::HashMap;\nuse std::path::Path;\n\nuse oci_spec::runtime;\n\nuse super::Result;\nuse crate::container::Container;\nuse crate::error::MissingSpecError;\nuse crate::namespaces::Namespaces;\nuse crate::process::args::ContainerArgs;\nuse crate::syscall::Syscall;\nuse crate::{notify_socket, utils};\n\npub(crate) struct InitContext<'a> {\n    pub(crate) spec: &'a runtime::Spec,\n    pub(crate) linux: &'a runtime::Linux,\n    pub(crate) process: &'a runtime::Process,\n    pub(crate) rootfs: &'a Path,\n    pub(crate) envs: HashMap<String, String>,\n    pub(crate) ns: Namespaces,\n    pub(crate) syscall: Box<dyn Syscall>,\n    pub(crate) notify_listener: &'a notify_socket::NotifyListener,\n    pub(crate) hooks: Option<&'a runtime::Hooks>,\n    pub(crate) container: Option<&'a Container>,\n    pub(crate) rootfs_ro: bool,\n}\n\nimpl<'a> InitContext<'a> {\n    pub fn try_from(args: &'a ContainerArgs) -> Result<Self> {\n        let spec = args.spec.as_ref();\n        let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n        let process = spec.process().as_ref().ok_or(MissingSpecError::Process)?;\n        let envs: HashMap<String, String> =\n            utils::parse_env(process.env().as_ref().unwrap_or(&vec![]));\n        let rootfs = spec.root().as_ref().ok_or(MissingSpecError::Root)?;\n        let rootfs_ro = rootfs.readonly().unwrap_or(false);\n\n        Ok(Self {\n            spec,\n            linux,\n            process,\n            rootfs: &args.rootfs,\n            envs,\n            rootfs_ro,\n            ns: Namespaces::try_from(linux.namespaces().as_ref())?,\n            syscall: args.syscall.create_syscall(),\n            notify_listener: &args.notify_listener,\n            hooks: spec.hooks().as_ref(),\n            container: args.container.as_ref(),\n        })\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/init/error.rs",
    "content": "use crate::namespaces::NamespaceError;\nuse crate::process::channel;\nuse crate::process::memory_policy::MemoryPolicyError;\nuse crate::rootfs::device::DeviceError;\n#[cfg(feature = \"libseccomp\")]\nuse crate::seccomp;\nuse crate::syscall::SyscallError;\nuse crate::workload::{ExecutorSetEnvsError, ExecutorValidationError};\nuse crate::{apparmor, hooks, notify_socket, rootfs, tty, workload};\n\n#[derive(Debug, thiserror::Error)]\npub enum InitProcessError {\n    #[error(\"failed to set sysctl\")]\n    Sysctl(#[source] std::io::Error),\n    #[error(\"failed to mount path as readonly\")]\n    MountPathReadonly(#[source] SyscallError),\n    #[error(\"failed to mount path as masked\")]\n    MountPathMasked(#[source] SyscallError),\n    #[error(transparent)]\n    Namespaces(#[from] NamespaceError),\n    #[error(\"failed to set hostname\")]\n    SetHostname(#[source] SyscallError),\n    #[error(\"failed to set domainname\")]\n    SetDomainname(#[source] SyscallError),\n    #[error(\"failed to reopen /dev/null\")]\n    ReopenDevNull(#[source] std::io::Error),\n    #[error(\"failed to unix syscall\")]\n    NixOther(#[source] nix::Error),\n    #[error(transparent)]\n    MissingSpec(#[from] crate::error::MissingSpecError),\n    #[error(\"failed to setup tty\")]\n    Tty(#[source] tty::TTYError),\n    #[error(\"failed to run hooks\")]\n    Hooks(#[from] hooks::HookError),\n    #[error(\"failed to prepare rootfs\")]\n    RootFS(#[source] rootfs::RootfsError),\n    #[error(\"failed syscall\")]\n    SyscallOther(#[source] SyscallError),\n    #[error(\"failed apparmor\")]\n    AppArmor(#[source] apparmor::AppArmorError),\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n    #[error(\"invalid umask\")]\n    InvalidUmask(u32),\n    #[error(transparent)]\n    #[cfg(feature = \"libseccomp\")]\n    Seccomp(#[from] seccomp::SeccompError),\n    #[error(\"invalid executable: {0}\")]\n    InvalidExecutable(String),\n    #[error(\"io error\")]\n    Io(#[source] std::io::Error),\n    #[error(transparent)]\n    Channel(#[from] channel::ChannelError),\n    #[error(\"setgroup is disabled\")]\n    SetGroupDisabled,\n    #[error(transparent)]\n    NotifyListener(#[from] notify_socket::NotifyListenerError),\n    #[error(transparent)]\n    Workload(#[from] workload::ExecutorError),\n    #[error(transparent)]\n    WorkloadValidation(#[from] ExecutorValidationError),\n    #[error(transparent)]\n    WorkloadSetEnvs(#[from] ExecutorSetEnvsError),\n    #[error(\"invalid io priority class: {0}\")]\n    IoPriorityClass(String),\n    #[error(\"call exec sched_setattr error: {0}\")]\n    SchedSetattr(String),\n    #[error(transparent)]\n    MemoryPolicy(#[from] MemoryPolicyError),\n    #[error(transparent)]\n    Network(#[from] crate::network::NetworkError),\n    #[error(\"failed to verify if current working directory is safe\")]\n    InvalidCwd(#[source] nix::Error),\n    #[error(\"missing linux section in spec\")]\n    NoLinux,\n    #[error(\"missing process section in spec\")]\n    NoProcess,\n    #[error(\"device error\")]\n    Device(#[source] DeviceError),\n    #[error(\"personality flag has not supported at this time\")]\n    UnsupportedPersonalityFlag,\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/init/mod.rs",
    "content": "mod context;\npub mod error;\npub mod process;\n\npub use process::container_init_process;\n\ntype Result<T> = std::result::Result<T, error::InitProcessError>;\n"
  },
  {
    "path": "crates/libcontainer/src/process/init/process.rs",
    "content": "use std::collections::HashMap;\nuse std::io::{Read, Write};\nuse std::os::unix::io::AsRawFd;\nuse std::path::{Path, PathBuf};\nuse std::{env, fs, mem};\n\nuse nc;\nuse nix::mount::{MntFlags, MsFlags};\nuse nix::sched::CloneFlags;\nuse nix::sys::stat::Mode;\nuse nix::unistd::{self, Gid, Uid, close, dup2, setsid};\nuse oci_spec::runtime::{\n    IOPriorityClass, LinuxIOPriority, LinuxNamespaceType, LinuxNetDevice, LinuxPersonalityDomain,\n    LinuxSchedulerFlag, LinuxSchedulerPolicy, Scheduler, Spec, User,\n};\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle, ProcfsHandleBuilder};\n\nuse super::Result;\nuse super::context::InitContext;\nuse super::error::InitProcessError;\nuse crate::config::PersonalityDomain;\nuse crate::error::MissingSpecError;\nuse crate::namespaces::Namespaces;\nuse crate::network::address::AddressClient;\nuse crate::network::link::LinkClient;\nuse crate::network::network_device::{resolve_device_name, setup_addresses_in_network_namespace};\nuse crate::network::wrapper::create_network_client;\nuse crate::process::args::{ContainerArgs, ContainerType};\nuse crate::process::{channel, memory_policy};\nuse crate::rootfs::RootFS;\nuse crate::rootfs::device::{open_device_fd, verify_dev_null};\n#[cfg(feature = \"libseccomp\")]\nuse crate::seccomp;\nuse crate::syscall::{Syscall, SyscallError};\nuse crate::user_ns::UserNamespaceConfig;\nuse crate::{apparmor, capabilities, hooks, tty, utils};\n\n// Some variables are unused in the case where libseccomp feature is not enabled.\n#[allow(unused_variables)]\npub fn container_init_process(\n    args: &ContainerArgs,\n    main_sender: &mut channel::MainSender,\n    init_receiver: &mut channel::InitReceiver,\n) -> Result<()> {\n    let mut ctx = InitContext::try_from(args)?;\n\n    setsid().map_err(|err| {\n        tracing::error!(?err, \"failed to setsid to create a session\");\n        InitProcessError::NixOther(err)\n    })?;\n\n    set_io_priority(ctx.syscall.as_ref(), ctx.process.io_priority())?;\n\n    setup_scheduler(ctx.process.scheduler())?;\n\n    memory_policy::setup_memory_policy(ctx.linux.memory_policy(), ctx.syscall.as_ref())?;\n\n    // If no console socket, set up stdio now\n    if args.console_socket.is_none() {\n        if let Some(stdin) = args.stdin {\n            dup2(stdin, 0).map_err(InitProcessError::NixOther)?;\n            close(stdin).map_err(InitProcessError::NixOther)?;\n        }\n        if let Some(stdout) = args.stdout {\n            dup2(stdout, 1).map_err(InitProcessError::NixOther)?;\n            close(stdout).map_err(InitProcessError::NixOther)?;\n        }\n        if let Some(stderr) = args.stderr {\n            dup2(stderr, 2).map_err(InitProcessError::NixOther)?;\n            close(stderr).map_err(InitProcessError::NixOther)?;\n        }\n    }\n\n    apply_rest_namespaces(&ctx.ns, ctx.spec, ctx.syscall.as_ref())?;\n\n    if let Some(true) = ctx.process.no_new_privileges() {\n        let _ = prctl::set_no_new_privileges(true);\n    }\n\n    if matches!(args.container_type, ContainerType::InitContainer) {\n        let in_user_ns = utils::is_in_new_userns().map_err(InitProcessError::Io)?;\n        let bind_service = ctx.ns.get(LinuxNamespaceType::User)?.is_some() || in_user_ns;\n        let rootfs = RootFS::new();\n        rootfs\n            .prepare_rootfs(\n                ctx.spec,\n                ctx.rootfs,\n                bind_service,\n                ctx.ns.get(LinuxNamespaceType::Cgroup)?.is_some(),\n            )\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to prepare rootfs\");\n                InitProcessError::RootFS(err)\n            })?;\n\n        if let Some(hooks) = ctx.hooks {\n            // send a request to the main process to run prestart and create_runtime hooks.\n            // prestart and create_runtime hook needs to be called after the namespace setup, but\n            // before pivot_root is called. This runs in the runtime(not container) namespaces.\n            main_sender.hook_request()?;\n            init_receiver.wait_for_hook_request_done()?;\n\n            // create_container hook needs to be called after the namespace setup, but\n            // before pivot_root is called. This runs in the container namespaces.\n            hooks::run_hooks(\n                hooks.create_container().as_ref(),\n                ctx.container.map(|c| &c.state),\n                None,\n                None,\n            )\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to run create container hooks\");\n                InitProcessError::Hooks(err)\n            })?;\n        }\n\n        // Entering into the rootfs jail. If mount namespace is specified, then\n        // we use pivot_root, but if we are on the host mount namespace, we will\n        // use simple chroot. Scary things will happen if you try to pivot_root\n        // in the host mount namespace...\n        do_pivot_root(ctx.syscall.as_ref(), &ctx.ns, args.no_pivot, ctx.rootfs)?;\n\n        // As we have changed the root mount, from here on\n        // logs are no longer visible in journalctl\n        // so make sure that you bubble up any errors\n        // and do not call unwrap() as any panics would not be correctly logged\n        rootfs\n            .adjust_root_mount_propagation(ctx.linux)\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to adjust root mount propagation\");\n                InitProcessError::RootFS(err)\n            })?;\n\n        reopen_dev_null().map_err(|err| {\n            tracing::error!(?err, \"failed to reopen /dev/null\");\n            err\n        })?;\n\n        if let Some(kernel_params) = ctx.linux.sysctl() {\n            sysctl(kernel_params)?;\n        }\n    }\n\n    // Setup console AFTER reopen_dev_null (for init) or at start (for exec).\n    // This follows runc's order:\n    //   - standard_init_linux.go: setupConsole() is called after prepareRootfs()\n    //     (which includes pivotRoot and reOpenDevNull)\n    //   - setns_init_linux.go: setupConsole() is called early\n    // mount=true for init (mount /dev/console), false for exec (already mounted)\n    // See: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/standard_init_linux.go\n    // See: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/setns_init_linux.go\n    if let Some(csocketfd) = args.console_socket {\n        let mount_console = matches!(args.container_type, ContainerType::InitContainer);\n        tty::setup_console(ctx.syscall.as_ref(), csocketfd, mount_console).map_err(|err| {\n            tracing::error!(?err, \"failed to set up tty\");\n            InitProcessError::Tty(err)\n        })?;\n    }\n\n    if let Some(personality) = ctx.linux.personality() {\n        if let Some(flags) = personality.flags() {\n            if !flags.is_empty() {\n                tracing::error!(\"personality flag has not supported at this time\");\n                return Err(InitProcessError::UnsupportedPersonalityFlag);\n            }\n        }\n\n        let domain = match personality.domain() {\n            // https://github.com/opencontainers/runtime-spec/blob/main/config-linux.md#personality\n            LinuxPersonalityDomain::PerLinux => PersonalityDomain::Linux,\n            LinuxPersonalityDomain::PerLinux32 => PersonalityDomain::Linux32,\n        };\n\n        ctx.syscall.personality(domain).map_err(|err| {\n            tracing::error!(?err, \"failed to set linux personality \");\n            InitProcessError::SyscallOther(err)\n        })?;\n    }\n\n    if let Some(profile) = ctx.process.apparmor_profile() {\n        apparmor::apply_profile(profile).map_err(|err| {\n            tracing::error!(?err, \"failed to apply apparmor profile\");\n            InitProcessError::AppArmor(err)\n        })?;\n    }\n\n    if let Some(umask) = ctx.process.user().umask() {\n        match Mode::from_bits(umask) {\n            Some(mode) => {\n                nix::sys::stat::umask(mode);\n            }\n            None => {\n                return Err(InitProcessError::InvalidUmask(umask));\n            }\n        }\n    }\n\n    if matches!(args.container_type, ContainerType::InitContainer) {\n        if ctx.rootfs_ro {\n            ctx.syscall\n                .mount(\n                    None,\n                    Path::new(\"/\"),\n                    None,\n                    MsFlags::MS_RDONLY | MsFlags::MS_REMOUNT | MsFlags::MS_BIND,\n                    None,\n                )\n                .map_err(|err| {\n                    tracing::error!(?err, \"failed to remount root `/` as readonly\");\n                    InitProcessError::SyscallOther(err)\n                })?;\n        }\n\n        if let Some(paths) = ctx.linux.readonly_paths() {\n            // mount readonly path\n            for path in paths {\n                readonly_path(Path::new(path), ctx.syscall.as_ref()).map_err(|err| {\n                    tracing::error!(?err, ?path, \"failed to set readonly path\");\n                    err\n                })?;\n            }\n        }\n\n        if let Some(paths) = ctx.linux.masked_paths() {\n            // mount masked paths\n            masked_paths(paths, ctx.linux.mount_label(), ctx.syscall.as_ref()).map_err(|err| {\n                tracing::error!(?err, \"failed to set masked paths\");\n                err\n            })?;\n        }\n    }\n\n    let cwd = format!(\"{}\", ctx.process.cwd().display());\n    let do_chdir = if cwd.is_empty() {\n        false\n    } else {\n        // This chdir must run before setting up the user.\n        // This may allow the user running youki to access directories\n        // that the container user cannot access.\n        match unistd::chdir(ctx.process.cwd()) {\n            std::result::Result::Ok(_) => false,\n            Err(nix::Error::EPERM) => true,\n            Err(e) => {\n                tracing::error!(?e, \"failed to chdir\");\n                return Err(InitProcessError::NixOther(e));\n            }\n        }\n    };\n\n    set_supplementary_gids(\n        ctx.process.user(),\n        &args.user_ns_config,\n        ctx.syscall.as_ref(),\n    )\n    .map_err(|err| {\n        tracing::error!(?err, \"failed to set supplementary gids\");\n        err\n    })?;\n\n    ctx.syscall\n        .set_id(\n            Uid::from_raw(ctx.process.user().uid()),\n            Gid::from_raw(ctx.process.user().gid()),\n        )\n        .map_err(|err| {\n            let uid = ctx.process.user().uid();\n            let gid = ctx.process.user().gid();\n            tracing::error!(?err, ?uid, ?gid, \"failed to set uid and gid\");\n            InitProcessError::SyscallOther(err)\n        })?;\n\n    // Take care of LISTEN_FDS used for systemd-active-socket. If the value is\n    // not 0, then we have to preserve those fds as well, and set up the correct\n    // environment variables.\n    let preserve_fds: i32 = match env::var(\"LISTEN_FDS\") {\n        std::result::Result::Ok(listen_fds_str) => {\n            let listen_fds = match listen_fds_str.parse::<i32>() {\n                std::result::Result::Ok(v) => v,\n                Err(error) => {\n                    tracing::warn!(\n                        \"LISTEN_FDS entered is not a fd. Ignore the value. {:?}\",\n                        error\n                    );\n\n                    0\n                }\n            };\n\n            // The LISTEN_FDS will have to be passed to container init process.\n            // The LISTEN_PID will be set to PID 1. Based on the spec, if\n            // LISTEN_FDS is 0, the variable should be unset, so we just ignore\n            // it here, if it is 0.\n            if listen_fds > 0 {\n                ctx.envs\n                    .insert(\"LISTEN_FDS\".to_owned(), listen_fds.to_string());\n                ctx.envs.insert(\"LISTEN_PID\".to_owned(), 1.to_string());\n            }\n\n            args.preserve_fds + listen_fds\n        }\n        Err(env::VarError::NotPresent) => args.preserve_fds,\n        Err(env::VarError::NotUnicode(value)) => {\n            tracing::warn!(\n                \"LISTEN_FDS entered is malformed: {:?}. Ignore the value.\",\n                &value\n            );\n            args.preserve_fds\n        }\n    };\n\n    // Cleanup any extra file descriptors, so the new container process will not\n    // leak a file descriptor from before execve gets executed. The first 3 fd will\n    // stay open: stdio, stdout, and stderr. We would further preserve the next\n    // \"preserve_fds\" number of fds. Set the rest of fd with CLOEXEC flag, so they\n    // will be closed after execve into the container payload. We can't close the\n    // fds immediately since we at least still need it for the pipe used to wait on\n    // starting the container.\n    //\n    // Note: this should happen very late, in order to avoid accidentally leaking FDs\n    // Please refer to https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv for more details.\n    ctx.syscall.close_range(preserve_fds).map_err(|err| {\n        tracing::error!(?err, \"failed to cleanup extra fds\");\n        InitProcessError::SyscallOther(err)\n    })?;\n\n    // Setup some operations in the network namespace.\n    // This is done here before dropping capabilities because we need to be able to add IP addresses to the device\n    // and set up the device.\n    if let Some(network_devices) = ctx.linux.net_devices() {\n        configure_container_network_devices(network_devices, main_sender, init_receiver).map_err(\n            |err| {\n                tracing::error!(?err, \"failed to setup net_device\");\n                err\n            },\n        )?;\n    }\n\n    // Without no new privileges, seccomp is a privileged operation. We have to\n    // do this before dropping capabilities. Otherwise, we should do it later,\n    // as close to exec as possible.\n    #[cfg(feature = \"libseccomp\")]\n    if let Some(seccomp) = ctx.linux.seccomp() {\n        if ctx.process.no_new_privileges().is_none() {\n            let notify_fd = seccomp::initialize_seccomp(seccomp).map_err(|err| {\n                tracing::error!(?err, \"failed to initialize seccomp\");\n                err\n            })?;\n            sync_seccomp(notify_fd, main_sender, init_receiver).map_err(|err| {\n                tracing::error!(?err, \"failed to sync seccomp\");\n                err\n            })?;\n        }\n    }\n    #[cfg(not(feature = \"libseccomp\"))]\n    if ctx.process.no_new_privileges().is_none() {\n        tracing::warn!(\"seccomp not available, unable to enforce no_new_privileges!\")\n    }\n\n    capabilities::reset_effective(ctx.syscall.as_ref()).map_err(|err| {\n        tracing::error!(?err, \"failed to reset effective capabilities\");\n        InitProcessError::SyscallOther(err)\n    })?;\n    if let Some(caps) = ctx.process.capabilities() {\n        capabilities::drop_privileges(caps, ctx.syscall.as_ref()).map_err(|err| {\n            tracing::error!(?err, \"failed to drop capabilities\");\n            InitProcessError::SyscallOther(err)\n        })?;\n    }\n\n    // Change directory to process.cwd if process.cwd is not empty\n    if do_chdir {\n        unistd::chdir(ctx.process.cwd()).map_err(|err| {\n            let cwd = ctx.process.cwd();\n            tracing::error!(?err, ?cwd, \"failed to chdir to cwd\");\n            InitProcessError::NixOther(err)\n        })?;\n    }\n\n    // Ensure that the current working directory is actually inside the container.\n    verify_cwd().map_err(|err| {\n        tracing::error!(?err, \"failed to verify cwd\");\n        err\n    })?;\n\n    // Initialize seccomp profile right before we are ready to execute the\n    // payload so as few syscalls will happen between here and payload exec. The\n    // notify socket will still need network related syscalls.\n    #[cfg(feature = \"libseccomp\")]\n    if let Some(seccomp) = ctx.linux.seccomp() {\n        if ctx.process.no_new_privileges().is_some() {\n            let notify_fd = seccomp::initialize_seccomp(seccomp).map_err(|err| {\n                tracing::error!(?err, \"failed to initialize seccomp\");\n                err\n            })?;\n            sync_seccomp(notify_fd, main_sender, init_receiver).map_err(|err| {\n                tracing::error!(?err, \"failed to sync seccomp\");\n                err\n            })?;\n        }\n    }\n    #[cfg(not(feature = \"libseccomp\"))]\n    if ctx.process.no_new_privileges().is_some() {\n        tracing::warn!(\"seccomp not available, unable to set seccomp privileges!\")\n    }\n\n    // add HOME into envs if not exists\n    set_home_env_if_not_exists(&mut ctx.envs, ctx.process.user().uid().into());\n\n    args.executor.validate(ctx.spec)?;\n    args.executor.setup_envs(ctx.envs)?;\n\n    // Notify main process that the init process is ready to execute the\n    // payload.  Note, because we are already inside the pid namespace, the pid\n    // outside the pid namespace should be recorded by the intermediate process\n    // already.\n    main_sender.init_ready().map_err(|err| {\n        tracing::error!(\n            ?err,\n            \"failed to notify main process that init process is ready\"\n        );\n        InitProcessError::Channel(err)\n    })?;\n    main_sender.close().map_err(|err| {\n        tracing::error!(?err, \"failed to close down main sender in init process\");\n        InitProcessError::Channel(err)\n    })?;\n\n    // listing on the notify socket for container start command\n    ctx.notify_listener\n        .wait_for_container_start()\n        .map_err(|err| {\n            tracing::error!(?err, \"failed to wait for container start\");\n            err\n        })?;\n    ctx.notify_listener.close().map_err(|err| {\n        tracing::error!(?err, \"failed to close notify socket\");\n        err\n    })?;\n\n    // start_container hook needs to be called after the namespace setup, but\n    // before pivot_root is called. This runs in the container namespaces.\n    if matches!(args.container_type, ContainerType::InitContainer) {\n        if let Some(hooks) = ctx.hooks {\n            hooks::run_hooks(\n                hooks.start_container().as_ref(),\n                ctx.container.map(|c| &c.state),\n                None,\n                None,\n            )\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to run start container hooks\");\n                err\n            })?;\n        }\n    }\n\n    if ctx.process.args().is_none() {\n        tracing::error!(\"on non-Windows, at least one process arg entry is required\");\n        Err(MissingSpecError::Args)?;\n    }\n\n    args.executor.exec(ctx.spec).map_err(|err| {\n        tracing::error!(?err, \"failed to execute payload\");\n        err\n    })?;\n\n    // Once the executor is executed without error, it should not return. For\n    // example, the default executor is expected to call `exec` and replace the\n    // current process.\n    unreachable!(\"the executor should not return if it is successful.\");\n}\n\nfn sysctl(kernel_params: &HashMap<String, String>) -> Result<()> {\n    let procfs = ProcfsHandleBuilder::new().unmasked().build()?;\n    let sys = PathBuf::from(\"sys\");\n    for (kernel_param, value) in kernel_params {\n        tracing::debug!(\n            \"apply value {} to kernel parameter {}.\",\n            value,\n            kernel_param\n        );\n\n        let subpath = sys.join(kernel_param.replace('.', \"/\"));\n        let mut f = procfs.open(\n            ProcfsBase::ProcRoot,\n            subpath,\n            OpenFlags::O_WRONLY | OpenFlags::O_CLOEXEC,\n        )?;\n        f.write_all(value.as_bytes()).map_err(|err| {\n            tracing::error!(\"failed to set sysctl {kernel_param}={value}: {err}\");\n            InitProcessError::Sysctl(err)\n        })?;\n    }\n\n    Ok(())\n}\n\n// make a read only path\n// The first time we bind mount, other flags are ignored,\n// so we need to mount it once and then remount it with the necessary flags specified.\n// https://man7.org/linux/man-pages/man2/mount.2.html\nfn readonly_path(path: &Path, syscall: &dyn Syscall) -> Result<()> {\n    if let Err(err) = syscall.mount(\n        Some(path),\n        path,\n        None,\n        MsFlags::MS_BIND | MsFlags::MS_REC,\n        None,\n    ) {\n        if let SyscallError::Nix(errno) = err {\n            // ignore error if path is not exist.\n            if matches!(errno, nix::errno::Errno::ENOENT) {\n                return Ok(());\n            }\n        }\n\n        tracing::error!(?path, ?err, \"failed to mount path as readonly\");\n        return Err(InitProcessError::MountPathReadonly(err));\n    }\n\n    syscall\n        .mount(\n            Some(path),\n            path,\n            None,\n            MsFlags::MS_NOSUID\n                | MsFlags::MS_NODEV\n                | MsFlags::MS_NOEXEC\n                | MsFlags::MS_BIND\n                | MsFlags::MS_REMOUNT\n                | MsFlags::MS_RDONLY,\n            None,\n        )\n        .map_err(|err| {\n            tracing::error!(?path, ?err, \"failed to remount path as readonly\");\n            InitProcessError::MountPathReadonly(err)\n        })?;\n\n    tracing::debug!(\"readonly path {:?} mounted\", path);\n    Ok(())\n}\n\n// For files, bind mounts /dev/null over the top of the specified path.\n// For directories, mounts read-only tmpfs over the top of the specified path.\nfn masked_paths(\n    paths: &Vec<String>,\n    mount_label: &Option<String>,\n    syscall: &dyn Syscall,\n) -> Result<()> {\n    let (dev_null_fd, dev_null_stat) =\n        open_device_fd(Path::new(\"/dev/null\")).map_err(InitProcessError::NixOther)?;\n    verify_dev_null(&dev_null_stat).map_err(|err| {\n        tracing::error!(?err, \"invalid /dev/null device\");\n        InitProcessError::Device(err)\n    })?;\n\n    for path_str in paths {\n        let path = Path::new(path_str);\n        if !path.exists() {\n            // Skip if the path does not exist.\n            continue;\n        }\n\n        if path.is_dir() {\n            // Destination is a directory, mount a read-only tmpfs over the top of it.\n            let label = match mount_label {\n                Some(l) => format!(\"context=\\\"{l}\\\"\"),\n                None => \"\".to_string(),\n            };\n            syscall\n                .mount(\n                    Some(Path::new(\"tmpfs\")),\n                    path,\n                    Some(\"tmpfs\"),\n                    MsFlags::MS_RDONLY,\n                    Some(label.as_str()),\n                )\n                .map_err(|err| {\n                    tracing::error!(?path, ?err, \"failed to mount path as masked using tempfs\");\n                    InitProcessError::MountPathMasked(err)\n                })?;\n        } else {\n            // Destination is a file, bind mount /dev/null over the top of it.\n            syscall.mount_from_fd(&dev_null_fd, path).map_err(|err| {\n                tracing::error!(\n                    ?path,\n                    ?err,\n                    \"failed to mount path as masked using /dev/null\"\n                );\n                InitProcessError::MountPathMasked(err)\n            })?;\n        }\n    }\n\n    Ok(())\n}\n\n// Enter into rest of namespace. Note, we already entered into user and pid\n// namespace. We also have to enter into mount namespace last since\n// namespace may be bind to /proc path. The /proc path will need to be\n// accessed before pivot_root.\nfn apply_rest_namespaces(\n    namespaces: &Namespaces,\n    spec: &Spec,\n    syscall: &dyn Syscall,\n) -> Result<()> {\n    namespaces\n        .apply_namespaces(|ns_type| -> bool {\n            ns_type != CloneFlags::CLONE_NEWUSER && ns_type != CloneFlags::CLONE_NEWPID\n        })\n        .map_err(|err| {\n            tracing::error!(\n                ?err,\n                \"failed to apply rest of the namespaces (exclude user and pid)\"\n            );\n            InitProcessError::Namespaces(err)\n        })?;\n\n    // Only set the host name if entering into a new uts namespace\n    if let Some(uts_namespace) = namespaces.get(LinuxNamespaceType::Uts)? {\n        if uts_namespace.path().is_none() {\n            if let Some(hostname) = spec.hostname() {\n                syscall.set_hostname(hostname).map_err(|err| {\n                    tracing::error!(?err, ?hostname, \"failed to set hostname\");\n                    InitProcessError::SetHostname(err)\n                })?;\n            }\n\n            if let Some(domainname) = spec.domainname() {\n                syscall.set_domainname(domainname).map_err(|err| {\n                    tracing::error!(?err, ?domainname, \"failed to set domainname\");\n                    InitProcessError::SetDomainname(err)\n                })?;\n            }\n        }\n    }\n    Ok(())\n}\n\nfn reopen_dev_null() -> Result<()> {\n    // At this point we should be inside of the container and now\n    // we can re-open /dev/null if it is in use to the /dev/null\n    // in the container.\n    let dev_null = fs::File::open(\"/dev/null\").map_err(|err| {\n        tracing::error!(?err, \"failed to open /dev/null inside the container\");\n        InitProcessError::ReopenDevNull(err)\n    })?;\n    let dev_null_fstat_info = nix::sys::stat::fstat(dev_null.as_raw_fd()).map_err(|err| {\n        tracing::error!(?err, \"failed to fstat /dev/null inside the container\");\n        InitProcessError::NixOther(err)\n    })?;\n    verify_dev_null(&dev_null_fstat_info).map_err(|err| {\n        tracing::error!(?err, \"invalid /dev/null device inside the container\");\n        InitProcessError::Device(err)\n    })?;\n\n    // Check if stdin, stdout or stderr point to /dev/null\n    for fd in 0..3 {\n        let fstat_info = nix::sys::stat::fstat(fd).map_err(|err| {\n            tracing::error!(?err, \"failed to fstat stdio fd {}\", fd);\n            InitProcessError::NixOther(err)\n        })?;\n\n        if dev_null_fstat_info.st_rdev == fstat_info.st_rdev {\n            // This FD points to /dev/null outside of the container.\n            // Let's point to /dev/null inside of the container.\n            nix::unistd::dup2(dev_null.as_raw_fd(), fd).map_err(|err| {\n                tracing::error!(?err, \"failed to dup2 fd {} to /dev/null\", fd);\n                InitProcessError::NixOther(err)\n            })?;\n        }\n    }\n\n    Ok(())\n}\n\n// umount or hide the target path. If the target path is mounted\n// try to unmount it first if the unmount operation fails with EINVAL\n// then mount a tmpfs with size 0k to hide the target path.\nfn unmount_or_hide(syscall: &dyn Syscall, target: impl AsRef<Path>) -> Result<()> {\n    let target_path = target.as_ref();\n    match syscall.umount2(target_path, MntFlags::MNT_DETACH) {\n        Ok(_) => Ok(()),\n        Err(SyscallError::Nix(nix::errno::Errno::EINVAL)) => syscall\n            .mount(\n                None,\n                target_path,\n                Some(\"tmpfs\"),\n                MsFlags::MS_RDONLY,\n                Some(\"size=0k\"),\n            )\n            .map_err(InitProcessError::SyscallOther),\n        Err(err) => Err(InitProcessError::SyscallOther(err)),\n    }\n}\n\nfn move_root(syscall: &dyn Syscall, rootfs: &Path) -> Result<()> {\n    unistd::chdir(rootfs).map_err(InitProcessError::NixOther)?;\n    // umount /sys and /proc if they are mounted, the purpose is to\n    // unmount or hide the /sys and /proc filesystems before the process changes its\n    // root to the new rootfs. thus ensure that the /sys and /proc filesystems are not\n    // accessible in the new rootfs. the logic is borrowed from crun\n    // https://github.com/containers/crun/blob/53cd1c1c697d7351d0cad23708d29bf4a7980a3a/src/libcrun/linux.c#L2780\n    unmount_or_hide(syscall, \"/sys\")?;\n    unmount_or_hide(syscall, \"/proc\")?;\n    syscall\n        .mount(Some(rootfs), Path::new(\"/\"), None, MsFlags::MS_MOVE, None)\n        .map_err(|err| {\n            tracing::error!(?err, ?rootfs, \"failed to mount ms_move\");\n            InitProcessError::SyscallOther(err)\n        })?;\n\n    syscall.chroot(Path::new(\".\")).map_err(|err| {\n        tracing::error!(?err, ?rootfs, \"failed to chroot\");\n        InitProcessError::SyscallOther(err)\n    })?;\n\n    unistd::chdir(\"/\").map_err(InitProcessError::NixOther)?;\n\n    Ok(())\n}\n\nfn do_pivot_root(\n    syscall: &dyn Syscall,\n    namespaces: &Namespaces,\n    no_pivot: bool,\n    rootfs: impl AsRef<Path>,\n) -> Result<()> {\n    let rootfs_path = rootfs.as_ref();\n\n    let handle_error = |err: SyscallError, msg: &str| -> InitProcessError {\n        tracing::error!(?err, ?rootfs_path, msg);\n        InitProcessError::SyscallOther(err)\n    };\n\n    match namespaces.get(LinuxNamespaceType::Mount)? {\n        Some(_) if no_pivot => move_root(syscall, rootfs_path),\n        Some(_) => syscall\n            .pivot_rootfs(rootfs.as_ref())\n            .map_err(|err| handle_error(err, \"failed to pivot root\")),\n        None => syscall\n            .chroot(rootfs_path)\n            .map_err(|err| handle_error(err, \"failed to chroot\")),\n    }\n}\n\n// Before 3.19 it was possible for an unprivileged user to enter an user namespace,\n// become root and then call setgroups in order to drop membership in supplementary\n// groups. This allowed access to files which blocked access based on being a member\n// of these groups (see CVE-2014-8989)\n//\n// This leaves us with three scenarios:\n//\n// Unprivileged user starting a rootless container: The main process is running as an\n// unprivileged user and therefore cannot write the mapping until \"deny\" has been written\n// to /proc/{pid}/setgroups. Once written /proc/{pid}/setgroups cannot be reset and the\n// setgroups system call will be disabled for all processes in this user namespace. This\n// also means that we should detect if the user is unprivileged and additional gids have\n// been specified and bail out early as this can never work. This is not handled here,\n// but during the validation for rootless containers.\n//\n// Privileged user starting a rootless container: It is not necessary to write \"deny\" to\n// /proc/setgroups in order to create the gid mapping and therefore we don't. This means\n// that setgroups could be used to drop groups, but this is fine as the user is privileged\n// and could do so anyway.\n// We already have checked during validation if the specified supplemental groups fall into\n// the range that are specified in the gid mapping and bail out early if they do not.\n//\n// Privileged user starting a normal container: Just add the supplementary groups.\n//\nfn set_supplementary_gids(\n    user: &User,\n    user_ns_config: &Option<UserNamespaceConfig>,\n    syscall: &dyn Syscall,\n) -> Result<()> {\n    if let Some(additional_gids) = user.additional_gids() {\n        if additional_gids.is_empty() {\n            return Ok(());\n        }\n\n        let mut setgroups = String::new();\n        ProcfsHandle::new()?\n            .open(ProcfsBase::ProcSelf, \"setgroups\", OpenFlags::O_RDONLY)?\n            .read_to_string(&mut setgroups)\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to read setgroups\");\n                InitProcessError::Io(err)\n            })?;\n\n        if setgroups.trim() == \"deny\" {\n            tracing::error!(\"cannot set supplementary gids, setgroup is disabled\");\n            return Err(InitProcessError::SetGroupDisabled);\n        }\n\n        let gids: Vec<Gid> = additional_gids\n            .iter()\n            .map(|gid| Gid::from_raw(*gid))\n            .collect();\n\n        match user_ns_config {\n            Some(r) if r.privileged => {\n                syscall.set_groups(&gids).map_err(|err| {\n                    tracing::error!(?err, ?gids, \"failed to set privileged supplementary gids\");\n                    InitProcessError::SyscallOther(err)\n                })?;\n            }\n            None => {\n                syscall.set_groups(&gids).map_err(|err| {\n                    tracing::error!(?err, ?gids, \"failed to set unprivileged supplementary gids\");\n                    InitProcessError::SyscallOther(err)\n                })?;\n            }\n            // this should have been detected during validation\n            _ => unreachable!(\n                \"unprivileged users cannot set supplementary gids in containers with new user namespace\"\n            ),\n        }\n    }\n\n    Ok(())\n}\n\n/// set_io_priority set io priority\nfn set_io_priority(syscall: &dyn Syscall, io_priority_op: &Option<LinuxIOPriority>) -> Result<()> {\n    if let Some(io_priority) = io_priority_op {\n        let io_prio_class_mapping: HashMap<_, _> = [\n            (IOPriorityClass::IoprioClassRt, 1i64),\n            (IOPriorityClass::IoprioClassBe, 2i64),\n            (IOPriorityClass::IoprioClassIdle, 3i64),\n        ]\n        .iter()\n        .filter_map(|(class, num)| match serde_json::to_string(&class) {\n            Ok(class_str) => Some((class_str, *num)),\n            Err(err) => {\n                tracing::error!(?err, \"failed to parse io priority class\");\n                None\n            }\n        })\n        .collect();\n\n        let iop_class = serde_json::to_string(&io_priority.class())\n            .map_err(|err| InitProcessError::IoPriorityClass(err.to_string()))?;\n\n        match io_prio_class_mapping.get(&iop_class) {\n            Some(value) => {\n                syscall\n                    .set_io_priority(*value, io_priority.priority())\n                    .map_err(|err| {\n                        tracing::error!(?err, ?io_priority, \"failed to set io_priority\");\n                        InitProcessError::SyscallOther(err)\n                    })?;\n            }\n            None => {\n                return Err(InitProcessError::IoPriorityClass(iop_class));\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Set the RT priority of a thread\nfn setup_scheduler(sc_op: &Option<Scheduler>) -> Result<()> {\n    if let Some(sc) = sc_op {\n        let policy: u32 = match *sc.policy() {\n            LinuxSchedulerPolicy::SchedOther => 0,\n            LinuxSchedulerPolicy::SchedFifo => 1,\n            LinuxSchedulerPolicy::SchedRr => 2,\n            LinuxSchedulerPolicy::SchedBatch => 3,\n            LinuxSchedulerPolicy::SchedIso => 4,\n            LinuxSchedulerPolicy::SchedIdle => 5,\n            LinuxSchedulerPolicy::SchedDeadline => 6,\n        };\n        let mut flags_value: u64 = 0;\n        if let Some(flags) = sc.flags() {\n            for flag in flags {\n                match *flag {\n                    LinuxSchedulerFlag::SchedResetOnFork => flags_value |= 0x01,\n                    LinuxSchedulerFlag::SchedFlagReclaim => flags_value |= 0x02,\n                    LinuxSchedulerFlag::SchedFlagDLOverrun => flags_value |= 0x04,\n                    LinuxSchedulerFlag::SchedFlagKeepPolicy => flags_value |= 0x08,\n                    LinuxSchedulerFlag::SchedFlagKeepParams => flags_value |= 0x10,\n                    LinuxSchedulerFlag::SchedFlagUtilClampMin => flags_value |= 0x20,\n                    LinuxSchedulerFlag::SchedFlagUtilClampMax => flags_value |= 0x40,\n                }\n            }\n        }\n        let a = nc::sched_attr_t {\n            // size of the structure should always be within u32 bounds,\n            // so this unwrap should never fail\n            size: mem::size_of::<nc::sched_attr_t>().try_into().unwrap(),\n            sched_policy: policy,\n            sched_flags: flags_value,\n            sched_nice: sc.nice().unwrap_or(0),\n            sched_priority: sc.priority().unwrap_or(0) as u32,\n            sched_runtime: sc.runtime().unwrap_or(0),\n            sched_deadline: sc.deadline().unwrap_or(0),\n            sched_period: sc.period().unwrap_or(0),\n            sched_util_min: 0,\n            sched_util_max: 0,\n        };\n        // TODO when nix or libc support this function, replace nx crates.\n        unsafe {\n            let result = nc::sched_setattr(0, &a, 0);\n            match result {\n                Ok(_) => {}\n                Err(err) => {\n                    tracing::error!(?err, \"error setting scheduler\");\n                    Err(InitProcessError::SchedSetattr(err.to_string()))?;\n                }\n            }\n        };\n    }\n    Ok(())\n}\n\n#[cfg(feature = \"libseccomp\")]\nfn sync_seccomp(\n    fd: Option<i32>,\n    main_sender: &mut channel::MainSender,\n    init_receiver: &mut channel::InitReceiver,\n) -> Result<()> {\n    if let Some(fd) = fd {\n        tracing::debug!(\"init process sync seccomp, notify fd: {}\", fd);\n        main_sender.seccomp_notify_request(fd).map_err(|err| {\n            tracing::error!(?err, \"failed to send seccomp notify request\");\n            InitProcessError::Channel(err)\n        })?;\n        init_receiver\n            .wait_for_seccomp_request_done()\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to wait for seccomp request done\");\n                InitProcessError::Channel(err)\n            })?;\n        // Once we are sure the seccomp notify fd is sent, we can safely close\n        // it. The fd is now duplicated to the main process and sent to seccomp\n        // listener.\n        let _ = unistd::close(fd);\n    }\n\n    Ok(())\n}\n\nfn configure_container_network_devices(\n    net_device: &HashMap<String, LinuxNetDevice>,\n    main_sender: &mut channel::MainSender,\n    init_receiver: &mut channel::InitReceiver,\n) -> Result<()> {\n    if net_device.is_empty() {\n        return Ok(());\n    }\n\n    main_sender.network_setup_ready()?;\n\n    let addrs_map = init_receiver.wait_for_move_network_device()?;\n    for (name, net_dev) in net_device {\n        if let Some(cidr_addrs) = addrs_map.get(name) {\n            // Get the device's final name (use configured name if provided, otherwise use original name)\n            let new_name = resolve_device_name(net_dev, name.as_str());\n\n            // Create network clients\n            let mut link_client = LinkClient::new(create_network_client()).map_err(|err| {\n                tracing::error!(?err, \"failed to create link client\");\n                err\n            })?;\n            let mut addr_client = AddressClient::new(create_network_client()).map_err(|err| {\n                tracing::error!(?err, \"failed to create address client\");\n                err\n            })?;\n\n            // Get the device index\n            let ns_link = link_client.get_by_name(new_name).map_err(|err| {\n                tracing::error!(?err, \"failed to get device by name: {}\", new_name);\n                err\n            })?;\n\n            // Assign IP addresses to the device\n            setup_addresses_in_network_namespace(\n                cidr_addrs,\n                ns_link.header.index,\n                new_name,\n                &mut addr_client,\n            )\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to setup addresses for device: {}\", new_name);\n                err\n            })?;\n\n            // Bring the device up\n            link_client.set_up(ns_link.header.index).map_err(|err| {\n                tracing::error!(?err, \"failed to bring up device: {}\", new_name);\n                err\n            })?;\n        }\n    }\n\n    Ok(())\n}\n\n// verifyCwd ensures that the current directory is actually inside the mount\n// namespace root of the current process.\n// Please refer to https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv for more details.\nfn verify_cwd() -> Result<()> {\n    let cwd = unistd::getcwd().map_err(|err| {\n        if let nix::errno::Errno::ENOENT = err {\n            // https://man7.org/linux/man-pages/man2/getcwd.2.html\n            // ENOENT The current working directory has been unlinked.\n            InitProcessError::InvalidCwd(err)\n        } else {\n            InitProcessError::NixOther(err)\n        }\n    })?;\n\n    if !cwd.is_absolute() {\n        // This should never happen, but just in case.\n        return Err(InitProcessError::InvalidCwd(nix::errno::Errno::ENOENT));\n    }\n\n    Ok(())\n}\n\n/// Set the HOME environment variable if it is not already set or is empty.\nfn set_home_env_if_not_exists(envs: &mut HashMap<String, String>, uid: Uid) {\n    if envs.get(\"HOME\").is_none_or(|v| v.is_empty()) {\n        if let Some(dir_home) = utils::get_user_home(uid.into()) {\n            set_home_from_path(envs, &dir_home);\n        }\n    }\n}\n\n/// Set the HOME environment variable if dir_home string is valid UTF-8\nfn set_home_from_path(envs: &mut HashMap<String, String>, dir_home: &Path) {\n    if let Some(home_str) = dir_home.to_str() {\n        envs.insert(\"HOME\".to_owned(), home_str.to_owned());\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::ffi::OsStr;\n    use std::fs;\n    use std::os::unix::ffi::OsStrExt;\n    use std::path::{Path, PathBuf};\n\n    use anyhow::Result;\n    #[cfg(feature = \"libseccomp\")]\n    use nix::unistd;\n    use nix::unistd::{Uid, User as NixUser};\n    use oci_spec::runtime::{LinuxNamespaceBuilder, SpecBuilder, UserBuilder};\n    #[cfg(feature = \"libseccomp\")]\n    use serial_test::serial;\n\n    use super::*;\n    use crate::syscall::syscall::create_syscall;\n    use crate::syscall::test::{ArgName, IoPriorityArgs, MountArgs, TestHelperSyscall};\n\n    #[test]\n    fn test_readonly_path() -> Result<()> {\n        let syscall = create_syscall();\n        readonly_path(Path::new(\"/proc/sys\"), syscall.as_ref())?;\n\n        let want = vec![\n            MountArgs {\n                source: Some(PathBuf::from(\"/proc/sys\")),\n                target: PathBuf::from(\"/proc/sys\"),\n                fstype: None,\n                flags: MsFlags::MS_BIND | MsFlags::MS_REC,\n                data: None,\n            },\n            MountArgs {\n                source: Some(PathBuf::from(\"/proc/sys\")),\n                target: PathBuf::from(\"/proc/sys\"),\n                fstype: None,\n                flags: MsFlags::MS_NOSUID\n                    | MsFlags::MS_NODEV\n                    | MsFlags::MS_NOEXEC\n                    | MsFlags::MS_BIND\n                    | MsFlags::MS_REMOUNT\n                    | MsFlags::MS_RDONLY,\n                data: None,\n            },\n        ];\n        let got = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(want, *got);\n        assert_eq!(got.len(), 2);\n        Ok(())\n    }\n\n    #[test]\n    fn test_apply_rest_namespaces() -> Result<()> {\n        let syscall = create_syscall();\n        let spec = SpecBuilder::default().build()?;\n        let linux_spaces = vec![\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Uts)\n                .build()?,\n            LinuxNamespaceBuilder::default()\n                .typ(LinuxNamespaceType::Pid)\n                .build()?,\n        ];\n        let namespaces = Namespaces::try_from(Some(&linux_spaces))?;\n\n        apply_rest_namespaces(&namespaces, &spec, syscall.as_ref())?;\n\n        let got_hostnames = syscall\n            .as_ref()\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_hostname_args();\n        assert_eq!(1, got_hostnames.len());\n        assert_eq!(\"youki\".to_string(), got_hostnames[0]);\n\n        let got_domainnames = syscall\n            .as_ref()\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_domainname_args();\n        assert_eq!(0, got_domainnames.len());\n        Ok(())\n    }\n\n    #[test]\n    fn test_set_supplementary_gids() -> Result<()> {\n        // gids additional gids is empty case\n        let user = UserBuilder::default().build().unwrap();\n        assert!(set_supplementary_gids(&user, &None, create_syscall().as_ref()).is_ok());\n\n        let tests = vec![\n            (\n                UserBuilder::default()\n                    .additional_gids(vec![33, 34])\n                    .build()?,\n                None::<UserNamespaceConfig>,\n                vec![Gid::from_raw(33), Gid::from_raw(34)],\n            ),\n            // unreachable case\n            (\n                UserBuilder::default().build()?,\n                Some(UserNamespaceConfig::default()),\n                vec![],\n            ),\n            (\n                UserBuilder::default()\n                    .additional_gids(vec![37, 38])\n                    .build()?,\n                Some(UserNamespaceConfig {\n                    privileged: true,\n                    gid_mappings: None,\n                    newgidmap: None,\n                    newuidmap: None,\n                    uid_mappings: None,\n                    user_namespace: None,\n                    ..Default::default()\n                }),\n                vec![Gid::from_raw(37), Gid::from_raw(38)],\n            ),\n            (\n                UserBuilder::default()\n                    .additional_gids(vec![33, 34, 34])\n                    .build()?,\n                None::<UserNamespaceConfig>,\n                vec![Gid::from_raw(33), Gid::from_raw(34), Gid::from_raw(34)],\n            ),\n        ];\n        for (user, ns_config, want) in tests.into_iter() {\n            let syscall = create_syscall();\n            let result = set_supplementary_gids(&user, &ns_config, syscall.as_ref());\n            match fs::read_to_string(\"/proc/self/setgroups\")?.trim() {\n                \"deny\" => {\n                    assert!(result.is_err());\n                }\n                \"allow\" => {\n                    assert!(result.is_ok());\n                    let got = syscall\n                        .as_any()\n                        .downcast_ref::<TestHelperSyscall>()\n                        .unwrap()\n                        .get_groups_args();\n                    // set set_supplementary_gids uses hashset internally\n                    // so we cannot be sure of the order, hence compare the\n                    // length and includes\n                    assert_eq!(want.len(), got.len());\n                    for gid in &want {\n                        assert!(got.contains(gid));\n                    }\n                }\n                _ => unreachable!(\"setgroups value unknown\"),\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    #[cfg(feature = \"libseccomp\")]\n    fn test_sync_seccomp() -> Result<()> {\n        use std::os::unix::io::IntoRawFd;\n        use std::thread;\n\n        let tmp_file = tempfile::tempfile()?;\n\n        let (mut main_sender, mut main_receiver) = channel::main_channel()?;\n        let (mut init_sender, mut init_receiver) = channel::init_channel()?;\n\n        let fd = tmp_file.into_raw_fd();\n        let th = thread::spawn(move || {\n            assert!(main_receiver.wait_for_seccomp_request().is_ok());\n            assert!(init_sender.seccomp_notify_done().is_ok());\n        });\n\n        // sync_seccomp close the fd,\n        sync_seccomp(Some(fd), &mut main_sender, &mut init_receiver)?;\n        // so expecting close the same fd again will causing EBADF error.\n        assert_eq!(nix::errno::Errno::EBADF, unistd::close(fd).unwrap_err());\n        assert!(th.join().is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn test_masked_path_does_not_exist() {\n        let syscall = create_syscall();\n        let mocks = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap();\n\n        let paths = vec![\"/doesnotexist\".to_string()];\n        assert!(super::masked_paths(&paths, &None, syscall.as_ref()).is_ok());\n        let got = mocks.get_mount_from_fd_args();\n        assert_eq!(0, got.len());\n        let got = mocks.get_mount_args();\n        assert_eq!(0, got.len());\n    }\n\n    #[test]\n    fn test_masked_path_mounts_via_fd() -> Result<()> {\n        let syscall = create_syscall();\n        let paths = vec![\"/proc/sys/kernel/core_pattern\".to_string()];\n        super::masked_paths(&paths, &None, syscall.as_ref()).map_err(anyhow::Error::from)?;\n\n        let got = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_from_fd_args();\n        assert_eq!(1, got.len());\n        let arg = &got[0];\n        assert!(arg.fd >= 0);\n        assert_eq!(PathBuf::from(\"/proc/sys/kernel/core_pattern\"), arg.target);\n        Ok(())\n    }\n\n    #[test]\n    fn test_masked_path_is_file_with_no_label() {\n        let syscall = create_syscall();\n        let mocks = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap();\n        mocks.set_ret_err(ArgName::MountFromFd, || {\n            Err(SyscallError::Nix(nix::errno::Errno::ENOTDIR))\n        });\n\n        let paths = vec![\"/proc/self\".to_string()];\n        assert!(super::masked_paths(&paths, &None, syscall.as_ref()).is_ok());\n\n        let got = mocks.get_mount_args();\n        let want = MountArgs {\n            source: Some(PathBuf::from(\"tmpfs\")),\n            target: PathBuf::from(\"/proc/self\"),\n            fstype: Some(\"tmpfs\".to_string()),\n            flags: MsFlags::MS_RDONLY,\n            data: Some(\"\".to_string()),\n        };\n        assert_eq!(1, got.len());\n        assert_eq!(want, got[0]);\n    }\n\n    #[test]\n    fn test_masked_path_is_file_with_label() {\n        let syscall = create_syscall();\n        let mocks = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap();\n        mocks.set_ret_err(ArgName::MountFromFd, || {\n            Err(SyscallError::Nix(nix::errno::Errno::ENOTDIR))\n        });\n\n        let paths = vec![\"/proc/self\".to_string()];\n        assert!(\n            super::masked_paths(&paths, &Some(\"default\".to_string()), syscall.as_ref()).is_ok()\n        );\n\n        let got = mocks.get_mount_args();\n        let want = MountArgs {\n            source: Some(PathBuf::from(\"tmpfs\")),\n            target: PathBuf::from(\"/proc/self\"),\n            fstype: Some(\"tmpfs\".to_string()),\n            flags: MsFlags::MS_RDONLY,\n            data: Some(\"context=\\\"default\\\"\".to_string()),\n        };\n        assert_eq!(1, got.len());\n        assert_eq!(want, got[0]);\n    }\n\n    #[test]\n    fn test_masked_path_with_unknown_error() {\n        let syscall = create_syscall();\n        let mocks = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap();\n        mocks.set_ret_err(ArgName::MountFromFd, || {\n            Err(SyscallError::Nix(nix::errno::Errno::UnknownErrno))\n        });\n\n        let paths = vec![\"/proc/self/exe\".to_string()];\n        assert!(super::masked_paths(&paths, &None, syscall.as_ref()).is_err());\n        let got = mocks.get_mount_args();\n        assert_eq!(0, got.len());\n\n        mocks.set_ret_err(ArgName::Mount, || {\n            Err(SyscallError::Nix(nix::errno::Errno::UnknownErrno))\n        });\n        let paths = vec![\"/proc/self\".to_string()];\n        assert!(super::masked_paths(&paths, &None, syscall.as_ref()).is_err());\n        let got = mocks.get_mount_args();\n        assert_eq!(0, got.len());\n    }\n\n    #[test]\n    fn test_set_io_priority() {\n        let test_command = TestHelperSyscall::default();\n        let io_priority_op = None;\n        assert!(set_io_priority(&test_command, &io_priority_op).is_ok());\n\n        let data = \"{\\\"class\\\":\\\"IOPRIO_CLASS_RT\\\",\\\"priority\\\":1}\";\n        let iop: LinuxIOPriority = serde_json::from_str(data).unwrap();\n        let io_priority_op = Some(iop);\n        assert!(set_io_priority(&test_command, &io_priority_op).is_ok());\n\n        let want_io_priority = IoPriorityArgs {\n            class: 1,\n            priority: 1,\n        };\n        let set_io_prioritys = test_command.get_io_priority_args();\n        assert_eq!(set_io_prioritys[0], want_io_priority);\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_already_exists() {\n        let mut envs = HashMap::new();\n        envs.insert(\"HOME\".to_owned(), \"/existing/home\".to_owned());\n\n        set_home_env_if_not_exists(&mut envs, Uid::from_raw(0));\n        assert_eq!(envs.get(\"HOME\"), Some(&\"/existing/home\".to_string()));\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_already_exists_non_root() {\n        let mut envs = HashMap::new();\n        envs.insert(\"HOME\".to_owned(), \"/existing/home\".to_owned());\n\n        set_home_env_if_not_exists(&mut envs, Uid::current());\n        assert_eq!(envs.get(\"HOME\"), Some(&\"/existing/home\".to_string()));\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_already_exists_but_empty_value() {\n        let mut envs = HashMap::new();\n        envs.insert(\"HOME\".to_owned(), \"\".to_owned());\n\n        set_home_env_if_not_exists(&mut envs, Uid::from_raw(0));\n        assert_eq!(envs.get(\"HOME\"), Some(&\"/root\".to_string()));\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_already_exists_but_empty_value_non_root() {\n        let mut envs = HashMap::new();\n        envs.insert(\"HOME\".to_owned(), \"\".to_owned());\n\n        // Make TEST_NON_ROOT_UID configurable to run tests on GitHub Actions runners.\n        let test_uid = env::var(\"TEST_NON_ROOT_UID\")\n            .ok()\n            .and_then(|s| s.parse::<u32>().ok())\n            .map(Uid::from_raw)\n            .unwrap_or_else(Uid::current);\n        let expected = NixUser::from_uid(test_uid)\n            .ok()\n            .flatten()\n            .and_then(|user| user.dir.to_str().map(|s| s.to_owned()))\n            .unwrap_or_default();\n\n        set_home_env_if_not_exists(&mut envs, test_uid);\n        assert_eq!(envs.get(\"HOME\"), Some(&expected));\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_not_set() {\n        let mut envs = HashMap::new();\n\n        set_home_env_if_not_exists(&mut envs, Uid::from_raw(0));\n        assert_eq!(envs.get(\"HOME\"), Some(&\"/root\".to_string()));\n    }\n\n    #[test]\n    fn test_set_home_env_if_not_exists_not_set_non_root() {\n        let mut envs = HashMap::new();\n\n        // Make TEST_NON_ROOT_UID configurable to run tests on GitHub Actions runners.\n        let test_uid = env::var(\"TEST_NON_ROOT_UID\")\n            .ok()\n            .and_then(|s| s.parse::<u32>().ok())\n            .map(Uid::from_raw)\n            .unwrap_or_else(Uid::current);\n        let expected = NixUser::from_uid(test_uid)\n            .ok()\n            .flatten()\n            .and_then(|user| user.dir.to_str().map(|s| s.to_owned()))\n            .unwrap_or_default();\n\n        set_home_env_if_not_exists(&mut envs, test_uid);\n        assert_eq!(envs.get(\"HOME\"), Some(&expected));\n    }\n\n    #[test]\n    fn test_set_home_from_path_valid_utf8() {\n        let mut envs = HashMap::new();\n        let valid_path = PathBuf::from(\"/home/user\");\n\n        set_home_from_path(&mut envs, &valid_path);\n        assert_eq!(envs.get(\"HOME\"), Some(&\"/home/user\".to_string()));\n    }\n\n    #[test]\n    fn test_set_home_from_path_invalid_utf8() {\n        let mut envs = HashMap::new();\n\n        let invalid_bytes = b\"/home/user/\\xFF\\xFE\";\n        let invalid_path = PathBuf::from(OsStr::from_bytes(invalid_bytes));\n        assert!(invalid_path.to_str().is_none());\n\n        set_home_from_path(&mut envs, &invalid_path);\n        assert_eq!(envs.get(\"HOME\"), None);\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/intel_rdt.rs",
    "content": "use std::collections::HashMap;\nuse std::fs::{self, OpenOptions};\nuse std::io::{BufRead, BufReader, Write};\nuse std::path::{Path, PathBuf};\nuse std::sync::LazyLock;\n\nuse nix::unistd::Pid;\nuse oci_spec::runtime::LinuxIntelRdt;\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\nuse procfs::process::MountInfo;\nuse regex::Regex;\n\n#[derive(Debug, thiserror::Error)]\npub enum IntelRdtError {\n    #[error(transparent)]\n    ProcError(#[from] procfs::ProcError),\n    #[error(\"failed to find resctrl mount point\")]\n    ResctrlMountPointNotFound,\n    #[error(\"failed to find ID for resctrl\")]\n    ResctrlIdNotFound,\n    #[error(\"existing schemata found but data did not match\")]\n    ExistingSchemataMismatch,\n    #[error(\"failed to read existing schemata\")]\n    ReadSchemata(#[source] std::io::Error),\n    #[error(\"failed to write schemata\")]\n    WriteSchemata(#[source] std::io::Error),\n    #[error(\"failed to open schemata file\")]\n    OpenSchemata(#[source] std::io::Error),\n    #[error(transparent)]\n    ParseLine(#[from] ParseLineError),\n    #[error(\"no resctrl subdirectory found for container id\")]\n    NoResctrlSubdirectory,\n    #[error(\"failed to remove subdirectory\")]\n    RemoveSubdirectory(#[source] std::io::Error),\n    #[error(\"no parent for resctrl subdirectory\")]\n    NoResctrlSubdirectoryParent,\n    #[error(\"invalid resctrl directory\")]\n    InvalidResctrlDirectory,\n    #[error(\"resctrl closID directory didn't exist\")]\n    NoClosIDDirectory,\n    #[error(\"failed to write to resctrl closID directory\")]\n    WriteClosIDDirectory(#[source] std::io::Error),\n    #[error(\"failed to open resctrl closID directory\")]\n    OpenClosIDDirectory(#[source] std::io::Error),\n    #[error(\"failed to create resctrl closID directory\")]\n    CreateClosIDDirectory(#[source] std::io::Error),\n    #[error(\"failed to canonicalize path\")]\n    Canonicalize(#[source] std::io::Error),\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n    #[error(transparent)]\n    Io(#[from] std::io::Error),\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ParseLineError {\n    #[error(\"MB line doesn't match validation\")]\n    MBLine,\n    #[error(\"MB token has wrong number of fields\")]\n    MBToken,\n    #[error(\"L3 line doesn't match validation\")]\n    L3Line,\n    #[error(\"L3 token has wrong number of fields\")]\n    L3Token,\n}\n\ntype Result<T> = std::result::Result<T, IntelRdtError>;\n\npub fn delete_resctrl_subdirectory(id: &str) -> Result<()> {\n    let dir = find_resctrl_mount_point().map_err(|err| {\n        tracing::error!(\"failed to find resctrl mount point: {}\", err);\n        err\n    })?;\n    let container_resctrl_path = dir.join(id).canonicalize().map_err(|err| {\n        tracing::error!(?dir, ?id, \"failed to canonicalize path: {}\", err);\n        IntelRdtError::Canonicalize(err)\n    })?;\n    match container_resctrl_path.parent() {\n        // Make sure the container_id really exists and the directory\n        // is inside the resctrl fs.\n        Some(parent) => {\n            if parent == dir && container_resctrl_path.exists() {\n                fs::remove_dir(&container_resctrl_path).map_err(|err| {\n                    tracing::error!(path = ?container_resctrl_path, \"failed to remove resctrl subdirectory: {}\", err);\n                    IntelRdtError::RemoveSubdirectory(err)\n                })?;\n            } else {\n                return Err(IntelRdtError::NoResctrlSubdirectory);\n            }\n        }\n        None => return Err(IntelRdtError::NoResctrlSubdirectoryParent),\n    }\n    Ok(())\n}\n\n/// Finds the resctrl mount path by looking at the process mountinfo data.\npub fn find_resctrl_mount_point() -> Result<PathBuf> {\n    let reader = BufReader::new(ProcfsHandle::new()?.open(\n        ProcfsBase::ProcSelf,\n        \"mountinfo\",\n        OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n    )?);\n\n    for lr in reader.lines() {\n        let s = lr.map_err(IntelRdtError::from)?;\n        let mi = MountInfo::from_line(&s).map_err(IntelRdtError::from)?;\n\n        if mi.fs_type == \"resctrl\" {\n            let path = mi\n                .mount_point\n                .canonicalize()\n                .map_err(IntelRdtError::Canonicalize)?;\n            return Ok(path);\n        }\n    }\n\n    Err(IntelRdtError::ResctrlMountPointNotFound)\n}\n\n/// Adds container PID to the tasks file in the correct resctrl\n/// pseudo-filesystem subdirectory.  Creates the directory if needed based on\n/// the rules in Linux OCI runtime config spec.\nfn write_container_pid_to_resctrl_tasks(\n    path: &Path,\n    id: &str,\n    init_pid: Pid,\n    only_clos_id_set: bool,\n) -> Result<bool> {\n    let tasks = path.to_owned().join(id).join(\"tasks\");\n    let dir = tasks.parent();\n    match dir {\n        None => Err(IntelRdtError::InvalidResctrlDirectory),\n        Some(resctrl_container_dir) => {\n            let mut created_dir = false;\n            if !resctrl_container_dir.exists() {\n                if only_clos_id_set {\n                    // Directory doesn't exist and only clos_id is set: error out.\n                    return Err(IntelRdtError::NoClosIDDirectory);\n                }\n                fs::create_dir_all(resctrl_container_dir).map_err(|err| {\n                    tracing::error!(\"failed to create resctrl subdirectory: {}\", err);\n                    IntelRdtError::CreateClosIDDirectory(err)\n                })?;\n                created_dir = true;\n            }\n            // TODO(ipuustin): File doesn't need to be created, but it's easier\n            // to test this way. Fix the tests so that the fake resctrl\n            // filesystem is pre-populated.\n            let mut file = OpenOptions::new()\n                .create(true)\n                .append(true)\n                .open(tasks)\n                .map_err(|err| {\n                    tracing::error!(\"failed to open resctrl tasks file: {}\", err);\n                    IntelRdtError::OpenClosIDDirectory(err)\n                })?;\n            write!(file, \"{init_pid}\").map_err(|err| {\n                tracing::error!(\"failed to write to resctrl tasks file: {}\", err);\n                IntelRdtError::WriteClosIDDirectory(err)\n            })?;\n            Ok(created_dir)\n        }\n    }\n}\n\n/// Merges the two schemas together, removing lines starting with \"MB:\" from\n/// l3_cache_schema if mem_bw_schema is also specified.\nfn combine_l3_cache_and_mem_bw_schemas(\n    l3_cache_schema: &Option<String>,\n    mem_bw_schema: &Option<String>,\n) -> Option<String> {\n    match (l3_cache_schema, mem_bw_schema) {\n        (Some(real_l3_cache_schema), Some(real_mem_bw_schema)) => {\n            // Combine the results. Filter out \"MB:\"-lines from l3_cache_schema\n            let mut output: Vec<&str> = vec![];\n\n            for line in real_l3_cache_schema.lines() {\n                if line.starts_with(\"MB:\") {\n                    continue;\n                }\n                output.push(line);\n            }\n            output.push(real_mem_bw_schema);\n            Some(output.join(\"\\n\"))\n        }\n        (Some(_), None) => {\n            // Apprarently the \"MB:\"-lines don't need to be removed in this case?\n            l3_cache_schema.to_owned()\n        }\n        (None, Some(_)) => mem_bw_schema.to_owned(),\n        (None, None) => None,\n    }\n}\n\n#[derive(PartialEq)]\nenum LineType {\n    L3Line,\n    L3DataLine,\n    L3CodeLine,\n    MbLine,\n    Unknown,\n}\n\n#[derive(PartialEq)]\nstruct ParsedLine {\n    line_type: LineType,\n    tokens: HashMap<String, String>,\n}\n\n/// Parse tokens (\"1=7000\") from a \"MB:\" line.\nfn parse_mb_line(line: &str) -> std::result::Result<HashMap<String, String>, ParseLineError> {\n    let mut token_map = HashMap::new();\n\n    static MB_VALIDATE_RE: LazyLock<Regex> = LazyLock::new(|| {\n        Regex::new(r\"^MB:(?:\\s|;)*(?:\\w+\\s*=\\s*\\w+)?(?:(?:\\s*;+\\s*)+\\w+\\s*=\\s*\\w+)*(?:\\s|;)*$\")\n            .unwrap()\n    });\n    static MB_CAPTURE_RE: LazyLock<Regex> =\n        LazyLock::new(|| Regex::new(r\"(\\w+)\\s*=\\s*(\\w+)\").unwrap());\n\n    if !MB_VALIDATE_RE.is_match(line) {\n        return Err(ParseLineError::MBLine);\n    }\n\n    for token in MB_CAPTURE_RE.captures_iter(line) {\n        match (token.get(1), token.get(2)) {\n            (Some(key), Some(value)) => {\n                token_map.insert(key.as_str().to_string(), value.as_str().to_string());\n            }\n            _ => return Err(ParseLineError::MBToken),\n        }\n    }\n\n    Ok(token_map)\n}\n\n/// Parse tokens (\"0=ffff\") from a L3{,CODE,DATA} line.\nfn parse_l3_line(line: &str) -> std::result::Result<HashMap<String, String>, ParseLineError> {\n    let mut token_map = HashMap::new();\n\n    static L3_VALIDATE_RE: LazyLock<Regex> = LazyLock::new(|| {\n        Regex::new(r\"^(?:L3|L3DATA|L3CODE):(?:\\s|;)*(?:\\w+\\s*=\\s*[[:xdigit:]]+)?(?:(?:\\s*;+\\s*)+\\w+\\s*=\\s*[[:xdigit:]]+)*(?:\\s|;)*$\").unwrap()\n    });\n    static L3_CAPTURE_RE: LazyLock<Regex> =\n        LazyLock::new(|| Regex::new(r\"(\\w+)\\s*=\\s*0*([[:xdigit:]]+)\").unwrap());\n    //                                        ^\n    //                          +-------------+\n    //                          |\n    // The capture regexp also removes leading zeros from mask values.\n\n    if !L3_VALIDATE_RE.is_match(line) {\n        return Err(ParseLineError::L3Line);\n    }\n\n    for token in L3_CAPTURE_RE.captures_iter(line) {\n        match (token.get(1), token.get(2)) {\n            (Some(key), Some(value)) => {\n                token_map.insert(key.as_str().to_string(), value.as_str().to_string());\n            }\n            _ => return Err(ParseLineError::L3Token),\n        }\n    }\n\n    Ok(token_map)\n}\n\n/// Get the resctrl line type. We only support L3{,CODE,DATA} and MB.\nfn get_line_type(line: &str) -> LineType {\n    if line.starts_with(\"L3:\") {\n        return LineType::L3Line;\n    }\n    if line.starts_with(\"L3CODE:\") {\n        return LineType::L3CodeLine;\n    }\n    if line.starts_with(\"L3DATA:\") {\n        return LineType::L3DataLine;\n    }\n    if line.starts_with(\"MB:\") {\n        return LineType::MbLine;\n    }\n\n    // Empty or unknown line.\n    LineType::Unknown\n}\n\n/// Parse a resctrl line.\nfn parse_line(line: &str) -> Option<std::result::Result<ParsedLine, ParseLineError>> {\n    let line_type = get_line_type(line);\n\n    let maybe_tokens = match line_type {\n        LineType::L3Line => parse_l3_line(line).map(Some),\n        LineType::L3DataLine => parse_l3_line(line).map(Some),\n        LineType::L3CodeLine => parse_l3_line(line).map(Some),\n        LineType::MbLine => parse_mb_line(line).map(Some),\n        LineType::Unknown => Ok(None),\n    };\n\n    match maybe_tokens {\n        Err(err) => Some(Err(err)),\n        Ok(None) => None,\n        Ok(Some(tokens)) => Some(Ok(ParsedLine { line_type, tokens })),\n    }\n}\n\n/// Compare two sets of parsed lines. Do this both ways because of possible\n/// duplicate lines, meaning that the vector lengths may be different.\nfn compare_lines(first_lines: &[ParsedLine], second_lines: &[ParsedLine]) -> bool {\n    first_lines.iter().all(|line| second_lines.contains(line))\n        && second_lines.iter().all(|line| first_lines.contains(line))\n}\n\n/// Compares that two strings have the same set of lines (even if the lines are\n/// in different order).\nfn is_same_schema(combined_schema: &str, existing_schema: &str) -> Result<bool> {\n    // Parse the strings first to lines and then to structs. Also filter\n    // out lines that are non-L3{DATA,CODE} and non-MB.\n    let combined = combined_schema\n        .lines()\n        .filter_map(parse_line)\n        .collect::<std::result::Result<Vec<ParsedLine>, _>>()?;\n    let existing = existing_schema\n        .lines()\n        .filter_map(parse_line)\n        .collect::<std::result::Result<Vec<ParsedLine>, _>>()?;\n\n    // Compare the two sets of parsed lines.\n    Ok(compare_lines(&combined, &existing))\n}\n\n/// Combines the l3_cache_schema and mem_bw_schema values together with the\n/// rules given in Linux OCI runtime config spec. If clos_id_was_set parameter\n/// is true and the directory wasn't created, the rules say that the schemas\n/// need to be compared with the existing value and an error must be generated\n/// if they don't match.\nfn write_resctrl_schemata(\n    path: &Path,\n    id: &str,\n    l3_cache_schema: &Option<String>,\n    mem_bw_schema: &Option<String>,\n    clos_id_was_set: bool,\n    created_dir: bool,\n) -> Result<()> {\n    let schemata = path.to_owned().join(id).join(\"schemata\");\n    let maybe_combined_schema = combine_l3_cache_and_mem_bw_schemas(l3_cache_schema, mem_bw_schema);\n\n    if let Some(combined_schema) = maybe_combined_schema {\n        if clos_id_was_set && !created_dir {\n            // Compare existing schema and error out if no match.\n            let data = fs::read_to_string(&schemata).map_err(IntelRdtError::ReadSchemata)?;\n            if !is_same_schema(&combined_schema, &data)? {\n                Err(IntelRdtError::ExistingSchemataMismatch)?;\n            }\n        } else {\n            // Write the combined schema to the schemata file.\n            // TODO(ipuustin): File doesn't need to be created, but it's easier\n            // to test this way. Fix the tests so that the fake resctrl\n            // filesystem is pre-populated.\n            let mut file = OpenOptions::new()\n                .create(true)\n                .truncate(true)\n                .write(true)\n                .open(schemata)\n                .map_err(IntelRdtError::OpenSchemata)?;\n            // Prevent write!() from writing the newline with a separate call.\n            let schema_with_newline = combined_schema + \"\\n\";\n            write!(file, \"{schema_with_newline}\").map_err(IntelRdtError::WriteSchemata)?;\n        }\n    }\n\n    Ok(())\n}\n\n/// Sets up Intel RDT configuration for the container process based on the\n/// OCI config. The result bool tells whether or not we need to clean up\n/// the created subdirectory.\npub fn setup_intel_rdt(\n    maybe_container_id: Option<&str>,\n    init_pid: &Pid,\n    intel_rdt: &LinuxIntelRdt,\n) -> Result<bool> {\n    // Find mounted resctrl filesystem, error out if it can't be found.\n    let path = find_resctrl_mount_point().inspect_err(|_err| {\n        tracing::error!(\"failed to find a mounted resctrl file system\");\n    })?;\n    let clos_id_set = intel_rdt.clos_id().is_some();\n    let only_clos_id_set =\n        clos_id_set && intel_rdt.l3_cache_schema().is_none() && intel_rdt.mem_bw_schema().is_none();\n    let id = match (intel_rdt.clos_id(), maybe_container_id) {\n        (Some(clos_id), _) => clos_id,\n        (None, Some(container_id)) => container_id,\n        (None, None) => Err(IntelRdtError::ResctrlIdNotFound)?,\n    };\n\n    let created_dir = write_container_pid_to_resctrl_tasks(&path, id, *init_pid, only_clos_id_set)\n        .inspect_err(|_err| {\n            tracing::error!(\"failed to write container pid to resctrl tasks file\");\n        })?;\n    write_resctrl_schemata(\n        &path,\n        id,\n        intel_rdt.l3_cache_schema(),\n        intel_rdt.mem_bw_schema(),\n        clos_id_set,\n        created_dir,\n    )\n    .inspect_err(|_err| {\n        tracing::error!(\"failed to write schemata to resctrl schemata file\");\n    })?;\n\n    // If closID is not set and the runtime has created the sub-directory,\n    // the runtime MUST remove the sub-directory when the container is deleted.\n    let need_to_delete_directory = !clos_id_set && created_dir;\n\n    Ok(need_to_delete_directory)\n}\n\n#[cfg(test)]\nmod test {\n    use anyhow::Result;\n\n    use super::*;\n\n    #[test]\n    fn test_combine_schemas() -> Result<()> {\n        let res = combine_l3_cache_and_mem_bw_schemas(&None, &None);\n        assert!(res.is_none());\n\n        let l3_1 = \"L3:0=f;1=f0\";\n        let bw_1 = \"MB:0=70;1=20\";\n\n        let res = combine_l3_cache_and_mem_bw_schemas(&Some(l3_1.to_owned()), &None);\n        assert!(res.is_some());\n        assert!(res.unwrap() == \"L3:0=f;1=f0\");\n\n        let res = combine_l3_cache_and_mem_bw_schemas(&None, &Some(bw_1.to_owned()));\n        assert!(res.is_some());\n        assert!(res.unwrap() == \"MB:0=70;1=20\");\n\n        let res =\n            combine_l3_cache_and_mem_bw_schemas(&Some(l3_1.to_owned()), &Some(bw_1.to_owned()));\n        assert!(res.is_some());\n        let val = res.unwrap();\n        assert!(val.lines().any(|line| line == \"MB:0=70;1=20\"));\n        assert!(val.lines().any(|line| line == \"L3:0=f;1=f0\"));\n\n        let l3_2 = \"L3:0=f;1=f0\\nL3:2=f\\n;MB:0=20;1=70\";\n        let res =\n            combine_l3_cache_and_mem_bw_schemas(&Some(l3_2.to_owned()), &Some(bw_1.to_owned()));\n        assert!(res.is_some());\n        let val = res.unwrap();\n        assert!(val.lines().any(|line| line == \"MB:0=70;1=20\"));\n        assert!(val.lines().any(|line| line == \"L3:0=f;1=f0\"));\n        assert!(val.lines().any(|line| line == \"L3:2=f\"));\n        assert!(!val.lines().any(|line| line == \"MB:0=20;1=70\"));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_is_same_schema() -> Result<()> {\n        // Exact same schemas.\n        assert!(is_same_schema(\"L3:0=f;1=f0\", \"L3:0=f;1=f0\")?);\n        assert!(is_same_schema(\"L3DATA:0=f;1=f0\", \"L3DATA:0=f;1=f0\")?);\n        assert!(is_same_schema(\"L3CODE:0=f;1=f0\", \"L3CODE:0=f;1=f0\")?);\n        assert!(is_same_schema(\"MB:0=bar;1=f0\", \"MB:0=bar;1=f0\")?);\n        assert!(is_same_schema(\"L3:\", \"L3:\")?);\n        assert!(is_same_schema(\"MB:\", \"MB:\")?);\n\n        // Different schemas.\n        assert!(!is_same_schema(\"L3:0=f;1=f0\", \"L3:2=f\")?);\n        assert!(!is_same_schema(\"MB:0=bar;1=f0\", \"MB:0=foo;1=f0\")?);\n        assert!(!is_same_schema(\"L3DATA:0=f;1=f0\", \"L3CODE:2=f\")?);\n        assert!(!is_same_schema(\"L3DATA:0=f;1=f0\", \"L3CODE:2=f\")?);\n        assert!(!is_same_schema(\"L3DATA:0=f\", \"L3CODE:0=f\")?);\n        assert!(!is_same_schema(\"L3:0=f\", \"L3DATA:0=f\")?);\n        assert!(!is_same_schema(\"L3CODE:0=f\", \"L3:0=f\")?);\n        assert!(!is_same_schema(\"MB:0=f\", \"L3:0=f\")?);\n\n        // Exact same multi-line schema.\n        assert!(is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\",\n            \"L3:0=f;1=f0\\nL3:2=f\"\n        )?);\n\n        // Unknown line type is ignored.\n        assert!(is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\\nBAR:foo\",\n            \"L3:0=f;1=f0\\nL3:2=f\"\n        )?);\n\n        // Different multi-line schema.\n        assert!(!is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\\nL3:3=f\",\n            \"L3:0=f;1=f0\\nL3:2=f\"\n        )?);\n\n        // Different lines (two ways).\n        assert!(!is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\\nL3:3=f\",\n            \"L3:0=f;1=f0\\nL3:2=f\"\n        )?);\n        assert!(!is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\",\n            \"L3:0=f;1=f0\\nL3:2=f\\nL3:3=f\"\n        )?);\n\n        // Same schema, different token order.\n        assert!(is_same_schema(\"L3:1=f0;0=0\", \"L3:0=0;1=f0\")?);\n\n        // Same schema, different whitespace and semicolons.\n        assert!(is_same_schema(\"L3:;;  0 = f; ;  1=f0\", \"L3:0=f;1  = f0;;\")?);\n\n        // Same schema, different leading zeros in masks.\n        assert!(is_same_schema(\"L3:0=000f\", \"L3:0=0f\")?);\n        assert!(is_same_schema(\"L3:0=000f\", \"L3:0=0f\")?);\n        assert!(is_same_schema(\"L3:0=f\", \"L3:0=0f\")?);\n        assert!(is_same_schema(\"L3:0=0\", \"L3:0=0000\")?);\n\n        // Invalid schemas.\n        assert!(is_same_schema(\"L3:1=;0=f\", \"L3:1=;0=f\").is_err());\n        assert!(is_same_schema(\"L3:=0;0=f\", \"L3:=0;0=f\").is_err());\n        assert!(is_same_schema(\"L3:1=0=3;0=f\", \"L3:1=0=3;0=f\").is_err());\n        assert!(is_same_schema(\"L3:1=bar\", \"L3:1=bar\").is_err());\n        assert!(is_same_schema(\"MB:1=;0=f\", \"MB:1=;0=f\").is_err());\n        assert!(is_same_schema(\"MB:=0;0=f\", \"MB:=0;0=f\").is_err());\n        assert!(is_same_schema(\"MB:1=0=3;0=f\", \"MB:1=0=3;0=f\").is_err());\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_write_pid_to_resctrl_tasks() -> Result<()> {\n        let tmp = tempfile::tempdir().unwrap();\n\n        // Create the directory for id \"foo\".\n        let res =\n            write_container_pid_to_resctrl_tasks(tmp.path(), \"foo\", Pid::from_raw(1000), false);\n        assert!(res.unwrap()); // new directory created\n        let res = fs::read_to_string(tmp.path().join(\"foo\").join(\"tasks\"));\n        assert!(res.unwrap() == \"1000\");\n\n        // Create the same directory the second time.\n        let res =\n            write_container_pid_to_resctrl_tasks(tmp.path(), \"foo\", Pid::from_raw(1500), false);\n        assert!(!res.unwrap()); // no new directory created\n\n        // If just clos_id then throw an error.\n        let res =\n            write_container_pid_to_resctrl_tasks(tmp.path(), \"foobar\", Pid::from_raw(2000), true);\n        assert!(res.is_err());\n\n        // If the directory already exists then it's fine to have just clos_id.\n        let res =\n            write_container_pid_to_resctrl_tasks(tmp.path(), \"foo\", Pid::from_raw(2500), true);\n        assert!(!res.unwrap()); // no new directory created\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_write_resctrl_schemata() -> Result<()> {\n        let tmp = tempfile::tempdir().unwrap();\n\n        let res =\n            write_container_pid_to_resctrl_tasks(tmp.path(), \"foobar\", Pid::from_raw(1000), false);\n        assert!(res.unwrap()); // new directory created\n\n        // No schemes, clos_id was not set, directory created (with container id).\n        let res = write_resctrl_schemata(tmp.path(), \"foobar\", &None, &None, false, true);\n        assert!(res.is_ok());\n        let res = fs::read_to_string(tmp.path().join(\"foobar\").join(\"schemata\"));\n        assert!(res.is_err()); // File not found because no schemes.\n\n        let l3_1 = \"L3:0=f;1=f0\\nL3:2=f\\nMB:0=20;1=70\";\n        let bw_1 = \"MB:0=70;1=20\";\n        let res = write_resctrl_schemata(\n            tmp.path(),\n            \"foobar\",\n            &Some(l3_1.to_owned()),\n            &Some(bw_1.to_owned()),\n            false,\n            true,\n        );\n        assert!(res.is_ok());\n\n        let res = fs::read_to_string(tmp.path().join(\"foobar\").join(\"schemata\"));\n        assert!(res.is_ok());\n        assert!(is_same_schema(\n            \"L3:0=f;1=f0\\nL3:2=f\\nMB:0=70;1=20\\n\",\n            &res.unwrap()\n        )?);\n\n        // Try the verification case. If the directory existed (was not created\n        // by us) and the clos_id was set, it needs to contain the same data as\n        // we are trying to set. This is the same data:\n        let res = write_resctrl_schemata(\n            tmp.path(),\n            \"foobar\",\n            &Some(l3_1.to_owned()),\n            &Some(bw_1.to_owned()),\n            true,\n            false,\n        );\n        assert!(res.is_ok());\n\n        // And this different data:\n        let l3_2 = \"L3:0=f;1=f0\\nMB:0=20;1=70\";\n        let bw_2 = \"MB:0=70;1=20\";\n        let res = write_resctrl_schemata(\n            tmp.path(),\n            \"foobar\",\n            &Some(l3_2.to_owned()),\n            &Some(bw_2.to_owned()),\n            true,\n            false,\n        );\n        assert!(res.is_err());\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/memory_policy.rs",
    "content": "use std::fmt;\n\nuse oci_spec::runtime::{MemoryPolicyFlagType, MemoryPolicyModeType};\n\nuse crate::syscall::{Syscall, SyscallError};\n\n#[derive(Debug, thiserror::Error)]\npub enum MemoryPolicyError {\n    #[error(\"Invalid memory policy flag: {0}\")]\n    InvalidFlag(String),\n\n    #[error(\"Invalid node specification: {0}\")]\n    InvalidNodes(String),\n\n    #[error(\"Incompatible flag and mode combination: {0}\")]\n    IncompatibleFlagMode(String),\n\n    #[error(\"Mutually exclusive flags: {0}\")]\n    MutuallyExclusiveFlags(String),\n\n    #[error(\"Syscall error: {0}\")]\n    Syscall(#[from] SyscallError),\n}\n\ntype Result<T> = std::result::Result<T, MemoryPolicyError>;\n\n#[repr(i32)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum MemoryPolicyMode {\n    Default = 0,\n    Preferred = 1,\n    Bind = 2,\n    Interleave = 3,\n    Local = 4,\n    PreferredMany = 5,\n    WeightedInterleave = 6,\n}\n\nimpl From<MemoryPolicyMode> for i32 {\n    fn from(mode: MemoryPolicyMode) -> Self {\n        mode as i32\n    }\n}\n\nimpl From<MemoryPolicyModeType> for MemoryPolicyMode {\n    fn from(mode: MemoryPolicyModeType) -> Self {\n        match mode {\n            MemoryPolicyModeType::MpolDefault => MemoryPolicyMode::Default,\n            MemoryPolicyModeType::MpolPreferred => MemoryPolicyMode::Preferred,\n            MemoryPolicyModeType::MpolBind => MemoryPolicyMode::Bind,\n            MemoryPolicyModeType::MpolInterleave => MemoryPolicyMode::Interleave,\n            MemoryPolicyModeType::MpolLocal => MemoryPolicyMode::Local,\n            MemoryPolicyModeType::MpolPreferredMany => MemoryPolicyMode::PreferredMany,\n            MemoryPolicyModeType::MpolWeightedInterleave => MemoryPolicyMode::WeightedInterleave,\n        }\n    }\n}\n\nimpl fmt::Display for MemoryPolicyMode {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let s = match self {\n            MemoryPolicyMode::Default => \"MPOL_DEFAULT\",\n            MemoryPolicyMode::Preferred => \"MPOL_PREFERRED\",\n            MemoryPolicyMode::Bind => \"MPOL_BIND\",\n            MemoryPolicyMode::Interleave => \"MPOL_INTERLEAVE\",\n            MemoryPolicyMode::Local => \"MPOL_LOCAL\",\n            MemoryPolicyMode::PreferredMany => \"MPOL_PREFERRED_MANY\",\n            MemoryPolicyMode::WeightedInterleave => \"MPOL_WEIGHTED_INTERLEAVE\",\n        };\n        write!(f, \"{}\", s)\n    }\n}\n\n#[repr(u32)]\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum MemoryPolicyFlag {\n    NumaBalancing = 1 << 13, // 0x2000\n    RelativeNodes = 1 << 14, // 0x4000\n    StaticNodes = 1 << 15,   // 0x8000\n}\n\nimpl From<MemoryPolicyFlag> for u32 {\n    fn from(flag: MemoryPolicyFlag) -> Self {\n        flag as u32\n    }\n}\n\nstruct ValidatedMemoryPolicy {\n    mode_with_flags: i32,\n    nodemask: Vec<libc::c_ulong>,\n    maxnode: u64,\n}\n\nfn validate_memory_policy(\n    memory_policy: &Option<oci_spec::runtime::LinuxMemoryPolicy>,\n) -> Result<Option<ValidatedMemoryPolicy>> {\n    let Some(policy) = memory_policy else {\n        return Ok(None);\n    };\n\n    let base_mode = MemoryPolicyMode::from(policy.mode());\n\n    let (flags_value, has_static, has_relative) = policy\n        .flags()\n        .as_ref()\n        .map(|flags| {\n            flags\n                .iter()\n                .fold((0u32, false, false), |(val, s, r), flag| match flag {\n                    MemoryPolicyFlagType::MpolFNumaBalancing => {\n                        (val | u32::from(MemoryPolicyFlag::NumaBalancing), s, r)\n                    }\n                    MemoryPolicyFlagType::MpolFStaticNodes => {\n                        (val | u32::from(MemoryPolicyFlag::StaticNodes), true, r)\n                    }\n                    MemoryPolicyFlagType::MpolFRelativeNodes => {\n                        (val | u32::from(MemoryPolicyFlag::RelativeNodes), s, true)\n                    }\n                })\n        })\n        .unwrap_or((0, false, false));\n\n    // Validate flags\n    if let Some(flags) = policy.flags() {\n        if flags.contains(&MemoryPolicyFlagType::MpolFNumaBalancing)\n            && base_mode != MemoryPolicyMode::Bind\n        {\n            return Err(MemoryPolicyError::IncompatibleFlagMode(\n                \"MPOL_F_NUMA_BALANCING can only be used with MPOL_BIND\".to_string(),\n            ));\n        }\n    }\n\n    if has_static && has_relative {\n        return Err(MemoryPolicyError::MutuallyExclusiveFlags(\n            \"MPOL_F_STATIC_NODES and MPOL_F_RELATIVE_NODES are mutually exclusive\".to_string(),\n        ));\n    }\n\n    let mode_with_flags = i32::from(base_mode) | (flags_value as i32);\n\n    match base_mode {\n        MemoryPolicyMode::Default | MemoryPolicyMode::Local => {\n            let mode_name = base_mode.to_string();\n\n            if let Some(nodes) = policy.nodes() {\n                if !nodes.trim().is_empty() {\n                    return Err(MemoryPolicyError::InvalidNodes(format!(\n                        \"{} does not accept node specification\",\n                        mode_name\n                    )));\n                }\n            }\n            if flags_value != 0 {\n                return Err(MemoryPolicyError::InvalidFlag(format!(\n                    \"{} does not accept flags\",\n                    mode_name\n                )));\n            }\n            Ok(Some(ValidatedMemoryPolicy {\n                mode_with_flags,\n                nodemask: Vec::new(),\n                maxnode: 0,\n            }))\n        }\n        MemoryPolicyMode::Preferred => {\n            let relative_or_static: u32 = u32::from(MemoryPolicyFlag::RelativeNodes)\n                | u32::from(MemoryPolicyFlag::StaticNodes);\n\n            let check_empty_nodes_flags = |flags_value: u32| -> Result<()> {\n                if flags_value & relative_or_static != 0u32 {\n                    return Err(MemoryPolicyError::IncompatibleFlagMode(\n                        \"MPOL_PREFERRED with empty nodes cannot use MPOL_F_STATIC_NODES or MPOL_F_RELATIVE_NODES flags\".to_string(),\n                    ));\n                }\n                Ok(())\n            };\n\n            match policy.nodes() {\n                None => {\n                    check_empty_nodes_flags(flags_value)?;\n                    Ok(Some(ValidatedMemoryPolicy {\n                        mode_with_flags,\n                        nodemask: Vec::new(),\n                        maxnode: 0,\n                    }))\n                }\n                Some(nodes) if nodes.trim().is_empty() => {\n                    check_empty_nodes_flags(flags_value)?;\n                    Ok(Some(ValidatedMemoryPolicy {\n                        mode_with_flags,\n                        nodemask: Vec::new(),\n                        maxnode: 0,\n                    }))\n                }\n                Some(nodes) => {\n                    let (nodemask, maxnode) = build_nodemask(nodes)?;\n                    if maxnode == 0 {\n                        check_empty_nodes_flags(flags_value)?;\n                        return Ok(Some(ValidatedMemoryPolicy {\n                            mode_with_flags,\n                            nodemask: Vec::new(),\n                            maxnode: 0,\n                        }));\n                    }\n                    Ok(Some(ValidatedMemoryPolicy {\n                        mode_with_flags,\n                        nodemask,\n                        maxnode,\n                    }))\n                }\n            }\n        }\n        _ => {\n            let mode_name = base_mode.to_string();\n            let nodes = match policy.nodes() {\n                None => {\n                    return Err(MemoryPolicyError::InvalidNodes(format!(\n                        \"Mode {} requires non-empty node specification\",\n                        mode_name\n                    )));\n                }\n                Some(nodes) if nodes.trim().is_empty() => {\n                    return Err(MemoryPolicyError::InvalidNodes(format!(\n                        \"Mode {} requires non-empty node specification\",\n                        mode_name\n                    )));\n                }\n                Some(nodes) => nodes,\n            };\n            let (nodemask, maxnode) = build_nodemask(nodes)?;\n            if maxnode == 0 {\n                return Err(MemoryPolicyError::InvalidNodes(format!(\n                    \"Mode {} requires non-empty node specification (parsed result is empty)\",\n                    mode_name\n                )));\n            }\n            Ok(Some(ValidatedMemoryPolicy {\n                mode_with_flags,\n                nodemask,\n                maxnode,\n            }))\n        }\n    }\n}\n\n/// Configure the memory policy for the process using set_mempolicy(2).\n///\n/// See: https://man7.org/linux/man-pages/man2/set_mempolicy.2.html\npub fn setup_memory_policy(\n    memory_policy: &Option<oci_spec::runtime::LinuxMemoryPolicy>,\n    syscall: &dyn Syscall,\n) -> Result<()> {\n    let validated = validate_memory_policy(memory_policy)?;\n    if let Some(valid) = validated {\n        syscall\n            .set_mempolicy(valid.mode_with_flags, &valid.nodemask, valid.maxnode)\n            .map_err(|err| {\n                tracing::error!(?err, \"failed to set memory policy\");\n                MemoryPolicyError::Syscall(err)\n            })?;\n    }\n    Ok(())\n}\n\n// Build a proper nodemask for set_mempolicy\nfn build_nodemask(nodes: &str) -> Result<(Vec<libc::c_ulong>, u64)> {\n    let node_ids = parse_node_string(nodes)?;\n\n    if node_ids.is_empty() {\n        // Empty nodemask - return NULL equivalent (empty vector)\n        return Ok((Vec::new(), 0));\n    }\n\n    // Find the highest node ID\n    let highest_node = node_ids.iter().max().copied().unwrap_or(0) as usize;\n\n    // Calculate how many c_ulong values we need to store the bitmask\n    let bits_per_ulong = std::mem::size_of::<libc::c_ulong>() * 8;\n    let num_ulongs = (highest_node / bits_per_ulong) + 1;\n\n    // Calculate maxnode = number of bits provided in nodemask\n    let maxnode = (num_ulongs * bits_per_ulong) as u64;\n\n    // Build the nodemask array as Vec<c_ulong>\n    let mut nodemask = vec![0 as libc::c_ulong; num_ulongs];\n\n    // Set bits for each node ID\n    for node_id in node_ids {\n        let node_id = node_id as usize;\n        let word_index = node_id / bits_per_ulong;\n        let bit_index = node_id % bits_per_ulong;\n\n        if word_index < nodemask.len() {\n            nodemask[word_index] |= (1 as libc::c_ulong) << bit_index;\n        }\n    }\n\n    Ok((nodemask, maxnode))\n}\n\nfn parse_node_string(nodes: &str) -> Result<Vec<u32>> {\n    let mut node_ids = Vec::new();\n\n    // Trim whitespace and check for empty string\n    let nodes = nodes.trim();\n    if nodes.is_empty() {\n        return Ok(node_ids);\n    }\n\n    for range in nodes.split(',') {\n        let range = range.trim();\n        if range.is_empty() {\n            continue; // Skip empty entries caused by multiple commas\n        }\n\n        if let Some(dash_pos) = range.find('-') {\n            // Range format: \"node1-node2\"\n            let start_str = range[..dash_pos].trim();\n            let end_str = range[dash_pos + 1..].trim();\n\n            let start: u32 = start_str.parse().map_err(|_| {\n                MemoryPolicyError::InvalidNodes(format!(\"Invalid node range start: {}\", start_str))\n            })?;\n            let end: u32 = end_str.parse().map_err(|_| {\n                MemoryPolicyError::InvalidNodes(format!(\"Invalid node range end: {}\", end_str))\n            })?;\n\n            if start > end {\n                return Err(MemoryPolicyError::InvalidNodes(format!(\n                    \"Invalid node range: {}-{}\",\n                    start, end\n                )));\n            }\n\n            for node in start..=end {\n                node_ids.push(node);\n            }\n        } else {\n            // Single node\n            let node: u32 = range\n                .parse()\n                .map_err(|_| MemoryPolicyError::InvalidNodes(format!(\"Invalid node: {}\", range)))?;\n\n            node_ids.push(node);\n        }\n    }\n\n    Ok(node_ids)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::syscall::syscall::create_syscall;\n    use crate::syscall::test::TestHelperSyscall;\n\n    #[test]\n    fn test_parse_node_string() {\n        // Test empty string\n        assert_eq!(parse_node_string(\"\").unwrap(), Vec::<u32>::new());\n\n        // Test single node\n        assert_eq!(parse_node_string(\"0\").unwrap(), vec![0]);\n        assert_eq!(parse_node_string(\"1\").unwrap(), vec![1]);\n        assert_eq!(parse_node_string(\"2\").unwrap(), vec![2]);\n\n        // Test node range\n        assert_eq!(parse_node_string(\"0-2\").unwrap(), vec![0, 1, 2]);\n        assert_eq!(parse_node_string(\"1-3\").unwrap(), vec![1, 2, 3]);\n\n        // Test multiple nodes\n        assert_eq!(parse_node_string(\"0,2\").unwrap(), vec![0, 2]);\n        assert_eq!(parse_node_string(\"0,1,3\").unwrap(), vec![0, 1, 3]);\n\n        // Test combination of ranges and single nodes\n        assert_eq!(parse_node_string(\"0-1,3\").unwrap(), vec![0, 1, 3]);\n        assert_eq!(parse_node_string(\"0,2-3\").unwrap(), vec![0, 2, 3]);\n\n        // Test with spaces\n        assert_eq!(parse_node_string(\" 0 , 2 \").unwrap(), vec![0, 2]);\n        assert_eq!(parse_node_string(\" 0 - 2 \").unwrap(), vec![0, 1, 2]);\n\n        // Test whitespace-only string\n        assert_eq!(parse_node_string(\"   \").unwrap(), Vec::<u32>::new());\n        assert_eq!(parse_node_string(\" , , \").unwrap(), Vec::<u32>::new());\n\n        // Test error cases\n        assert!(parse_node_string(\"2-1\").is_err()); // Invalid range\n        assert!(parse_node_string(\"abc\").is_err()); // Invalid format\n        assert!(parse_node_string(\"0-abc\").is_err()); // Invalid range end\n    }\n\n    #[test]\n    fn test_setup_memory_policy() {\n        use oci_spec::runtime::{LinuxMemoryPolicyBuilder, MemoryPolicyModeType};\n\n        let syscall = create_syscall();\n\n        // Test with None (no memory policy)\n        assert!(setup_memory_policy(&None, syscall.as_ref()).is_ok());\n\n        // Test with basic memory policy\n        let policy = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolBind)\n            .nodes(\"0,1\".to_string())\n            .flags(vec![])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy), syscall.as_ref()).is_ok());\n\n        let got_args = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mempolicy_args();\n\n        assert_eq!(got_args.len(), 1);\n        assert_eq!(got_args[0].mode, 2); // MPOL_BIND (corrected value)\n        assert_eq!(got_args[0].nodemask.len(), 1); // One c_ulong needed\n        assert_eq!(got_args[0].nodemask[0], 3); // 2^0 + 2^1 = 1 + 2 = 3\n        assert_eq!(got_args[0].maxnode, 64); // (num_u64s * u64_bits) = (1 * 64) = 64\n\n        // Test with flags\n        let policy_with_flags = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolBind)\n            .nodes(\"0\".to_string())\n            .flags(vec![\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFStaticNodes,\n            ])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_with_flags), syscall.as_ref()).is_ok());\n\n        let got_args_with_flags = syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mempolicy_args();\n\n        assert_eq!(got_args_with_flags.len(), 2);\n        // Second call should have mode with flags OR'ed in\n        // MPOL_BIND (2) | MPOL_F_STATIC_NODES (0x8000)\n        assert_eq!(got_args_with_flags[1].mode, 2 | (1 << 15));\n        assert_eq!(got_args_with_flags[1].nodemask.len(), 1);\n        assert_eq!(got_args_with_flags[1].nodemask[0], 1); // 2^0 = 1\n        assert_eq!(got_args_with_flags[1].maxnode, 64); // (num_u64s * u64_bits) = (1 * 64) = 64\n\n        // Test invalid flag combinations\n        let policy_invalid_flags = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolBind)\n            .nodes(\"0\".to_string())\n            .flags(vec![\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFStaticNodes,\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFRelativeNodes,\n            ])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_invalid_flags), syscall.as_ref()).is_err());\n\n        // Test MPOL_F_NUMA_BALANCING with non-BIND mode\n        let policy_invalid_numa_balancing = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolInterleave)\n            .nodes(\"0\".to_string())\n            .flags(vec![\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFNumaBalancing,\n            ])\n            .build()\n            .unwrap();\n\n        assert!(\n            setup_memory_policy(&Some(policy_invalid_numa_balancing), syscall.as_ref()).is_err()\n        );\n\n        // Test MPOL_DEFAULT with nodes (should fail)\n        let policy_default_with_nodes = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolDefault)\n            .nodes(\"0\".to_string())\n            .flags(vec![])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_default_with_nodes), syscall.as_ref()).is_err());\n\n        // Test MPOL_DEFAULT with flags (should fail)\n        let policy_default_with_flags = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolDefault)\n            .nodes(\"\".to_string())\n            .flags(vec![\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFStaticNodes,\n            ])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_default_with_flags), syscall.as_ref()).is_err());\n\n        // Test MPOL_LOCAL with nodes (should fail)\n        let policy_local_with_nodes = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolLocal)\n            .nodes(\"0\".to_string())\n            .flags(vec![])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_local_with_nodes), syscall.as_ref()).is_err());\n\n        // Test MPOL_BIND with empty nodes (should fail)\n        let policy_bind_empty = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolBind)\n            .nodes(\"\".to_string())\n            .flags(vec![])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_bind_empty), syscall.as_ref()).is_err());\n\n        // Test MPOL_BIND with whitespace-only nodes (should fail)\n        let policy_bind_whitespace = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolBind)\n            .nodes(\"   \".to_string())\n            .flags(vec![])\n            .build()\n            .unwrap();\n\n        assert!(setup_memory_policy(&Some(policy_bind_whitespace), syscall.as_ref()).is_err());\n\n        // Test MPOL_PREFERRED with empty nodes and STATIC_NODES flag (should fail)\n        let policy_preferred_empty_with_flags = LinuxMemoryPolicyBuilder::default()\n            .mode(MemoryPolicyModeType::MpolPreferred)\n            .nodes(\"\".to_string())\n            .flags(vec![\n                oci_spec::runtime::MemoryPolicyFlagType::MpolFStaticNodes,\n            ])\n            .build()\n            .unwrap();\n\n        assert!(\n            setup_memory_policy(&Some(policy_preferred_empty_with_flags), syscall.as_ref())\n                .is_err()\n        );\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/message.rs",
    "content": "use core::fmt;\nuse std::collections::HashMap;\n\nuse serde::{Deserialize, Serialize};\n\nuse crate::network::cidr::CidrAddress;\n\n/// Used as a wrapper for messages to be sent between child and parent processes\n#[derive(Debug, Serialize, Deserialize, Clone)]\npub enum Message {\n    IntermediateReady(i32),\n    InitReady,\n    WriteMapping,\n    MappingWritten,\n    SeccompNotify,\n    SeccompNotifyDone,\n    SetupNetworkDeviceReady,\n    MoveNetworkDevice(HashMap<String, Vec<CidrAddress>>),\n    ExecFailed(String),\n    OtherError(String),\n    HookRequest,\n    HookDone,\n}\n\nimpl fmt::Display for Message {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Message::IntermediateReady(pid) => write!(f, \"IntermediateReady({})\", pid),\n            Message::InitReady => write!(f, \"InitReady\"),\n            Message::WriteMapping => write!(f, \"WriteMapping\"),\n            Message::MappingWritten => write!(f, \"MappingWritten\"),\n            Message::SetupNetworkDeviceReady => write!(f, \"SetupNetworkDeviceReady\"),\n            Message::MoveNetworkDevice(addr) => write!(f, \"MoveNetworkDevice({:?})\", addr),\n            Message::SeccompNotify => write!(f, \"SeccompNotify\"),\n            Message::SeccompNotifyDone => write!(f, \"SeccompNotifyDone\"),\n            Message::HookRequest => write!(f, \"HookRequest\"),\n            Message::HookDone => write!(f, \"HookDone\"),\n            Message::ExecFailed(s) => write!(f, \"ExecFailed({})\", s),\n            Message::OtherError(s) => write!(f, \"OtherError({})\", s),\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/process/mod.rs",
    "content": "//! Provides a thin wrapper around fork syscall,\n//! with enums and functions specific to youki implemented\n\npub mod args;\npub mod channel;\npub mod container_intermediate_process;\npub mod container_main_process;\npub mod cpu_affinity;\nmod fork;\npub mod init;\npub mod intel_rdt;\npub mod memory_policy;\nmod message;\n#[cfg(feature = \"libseccomp\")]\nmod seccomp_listener;\n"
  },
  {
    "path": "crates/libcontainer/src/process/seccomp_listener.rs",
    "content": "use std::io::IoSlice;\nuse std::os::fd::AsRawFd;\nuse std::path::Path;\n\nuse nix::sys::socket::{self, UnixAddr};\nuse nix::unistd;\nuse oci_spec::runtime;\n\nuse super::channel;\nuse crate::seccomp;\n\n#[derive(Debug, thiserror::Error)]\npub enum SeccompListenerError {\n    #[error(\"notify will require seccomp listener path to be set\")]\n    MissingListenerPath,\n    #[error(\"failed to encode container process state\")]\n    EncodeState(#[source] serde_json::Error),\n    #[error(transparent)]\n    ChannelError(#[from] channel::ChannelError),\n    #[error(\"unix syscall fails\")]\n    UnixOther(#[source] nix::Error),\n}\n\ntype Result<T> = std::result::Result<T, SeccompListenerError>;\n\npub fn sync_seccomp(\n    seccomp: &runtime::LinuxSeccomp,\n    state: &runtime::ContainerProcessState,\n    init_sender: &mut channel::InitSender,\n    main_receiver: &mut channel::MainReceiver,\n) -> Result<()> {\n    if seccomp::is_notify(seccomp) {\n        tracing::debug!(\"main process waiting for sync seccomp\");\n        let seccomp_fd = main_receiver.wait_for_seccomp_request()?;\n        let listener_path = seccomp\n            .listener_path()\n            .as_ref()\n            .ok_or(SeccompListenerError::MissingListenerPath)?;\n        let encoded_state = serde_json::to_vec(state).map_err(SeccompListenerError::EncodeState)?;\n        sync_seccomp_send_msg(listener_path, &encoded_state, seccomp_fd).map_err(|err| {\n            tracing::error!(\"failed to send msg to seccomp listener: {}\", err);\n            err\n        })?;\n        init_sender.seccomp_notify_done()?;\n        // Once we sent the seccomp notify fd to the seccomp listener, we can\n        // safely close the fd. The SCM_RIGHTS msg will duplicate the fd to the\n        // process on the other end of the listener.\n        let _ = unistd::close(seccomp_fd);\n    }\n\n    Ok(())\n}\n\nfn sync_seccomp_send_msg(listener_path: &Path, msg: &[u8], fd: i32) -> Result<()> {\n    // The seccomp listener has specific instructions on how to transmit the\n    // information through seccomp listener.  Therefore, we have to use\n    // libc/nix APIs instead of Rust std lib APIs to maintain flexibility.\n    let socket = socket::socket(\n        socket::AddressFamily::Unix,\n        socket::SockType::Stream,\n        socket::SockFlag::empty(),\n        None,\n    )\n    .map_err(|err| {\n        tracing::error!(\n            ?err,\n            \"failed to create unix domain socket for seccomp listener\"\n        );\n        SeccompListenerError::UnixOther(err)\n    })?;\n    let unix_addr = socket::UnixAddr::new(listener_path).map_err(|err| {\n        tracing::error!(\n            ?err,\n            ?listener_path,\n            \"failed to create unix domain socket address\"\n        );\n        SeccompListenerError::UnixOther(err)\n    })?;\n    socket::connect(socket.as_raw_fd(), &unix_addr).map_err(|err| {\n        tracing::error!(\n            ?err,\n            ?listener_path,\n            \"failed to connect to seccomp notify listener path\"\n        );\n        SeccompListenerError::UnixOther(err)\n    })?;\n    // We have to use sendmsg here because the spec requires us to send seccomp notify fds through\n    // SCM_RIGHTS message.\n    // Ref: https://man7.org/linux/man-pages/man3/sendmsg.3p.html\n    // Ref: https://man7.org/linux/man-pages/man3/cmsg.3.html\n    let iov = [IoSlice::new(msg)];\n    let fds = [fd];\n    let cmsgs = socket::ControlMessage::ScmRights(&fds);\n    socket::sendmsg::<UnixAddr>(\n        socket.as_raw_fd(),\n        &iov,\n        &[cmsgs],\n        socket::MsgFlags::empty(),\n        None,\n    )\n    .map_err(|err| {\n        tracing::error!(?err, \"failed to write container state to seccomp listener\");\n        SeccompListenerError::UnixOther(err)\n    })?;\n    // The spec requires the listener socket to be closed immediately after sending.\n    drop(socket);\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::Result;\n    use oci_spec::runtime::{LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder};\n    use serial_test::serial;\n\n    use super::*;\n    use crate::process::channel;\n\n    #[test]\n    #[serial]\n    fn test_sync_seccomp() -> Result<()> {\n        use std::io::Read;\n        use std::os::unix::io::IntoRawFd;\n        use std::os::unix::net::UnixListener;\n        use std::thread;\n\n        let tmp_dir = tempfile::tempdir()?;\n        let scmp_file = std::fs::OpenOptions::new()\n            .write(true)\n            .create(true)\n            .truncate(true)\n            .open(tmp_dir.path().join(\"scmp_file\"))?;\n\n        std::fs::OpenOptions::new()\n            .write(true)\n            .create(true)\n            .truncate(true)\n            .open(tmp_dir.path().join(\"socket_file.sock\"))?;\n\n        let (mut main_sender, mut main_receiver) = channel::main_channel()?;\n        let (mut init_sender, mut init_receiver) = channel::init_channel()?;\n        let socket_path = tmp_dir.path().join(\"socket_file.sock\");\n        let socket_path_seccomp_th = socket_path.clone();\n\n        let state = runtime::ContainerProcessStateBuilder::default()\n            .version(\"1.0.0\".to_string())\n            .fds(vec![\"seccompFd\".to_string()])\n            .pid(1234)\n            .metadata(\"test\".to_string())\n            .state(\n                runtime::StateBuilder::default()\n                    .version(\"1.0.0\".to_string())\n                    .id(\"test-container\".to_string())\n                    .status(runtime::ContainerState::Creating)\n                    .pid(1234)\n                    .bundle(std::path::PathBuf::from(\"/tmp/bundle\"))\n                    .annotations(std::collections::HashMap::new())\n                    .build()\n                    .unwrap(),\n            )\n            .build()\n            .unwrap();\n        let want = serde_json::to_string(&state)?;\n        let th = thread::spawn(move || {\n            sync_seccomp(\n                &LinuxSeccompBuilder::default()\n                    .listener_path(socket_path_seccomp_th)\n                    .syscalls(vec![\n                        LinuxSyscallBuilder::default()\n                            .action(LinuxSeccompAction::ScmpActNotify)\n                            .build()\n                            .unwrap(),\n                    ])\n                    .build()\n                    .unwrap(),\n                &state,\n                &mut init_sender,\n                &mut main_receiver,\n            )\n            .unwrap();\n        });\n\n        let fd = scmp_file.into_raw_fd();\n        assert!(main_sender.seccomp_notify_request(fd).is_ok());\n\n        std::fs::remove_file(socket_path.clone())?;\n        let lis = UnixListener::bind(socket_path)?;\n        let (mut socket, _) = lis.accept()?;\n        let mut got = String::new();\n        socket.read_to_string(&mut got)?;\n        assert!(init_receiver.wait_for_seccomp_request_done().is_ok());\n\n        assert_eq!(want, got);\n        assert!(th.join().is_ok());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/device.rs",
    "content": "use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};\nuse std::path::{Path, PathBuf};\n\nuse libc;\nuse nix::fcntl::{OFlag, open};\nuse nix::mount::MsFlags;\nuse nix::sys::stat::{FileStat, Mode, fstat, umask};\nuse nix::unistd::{Gid, Uid, close};\nuse oci_spec::runtime::LinuxDevice;\n\nuse super::utils::to_sflag;\nuse crate::syscall::Syscall;\nuse crate::syscall::syscall::create_syscall;\nuse crate::utils::PathBufExt;\n\n#[derive(Debug, thiserror::Error)]\npub enum DeviceError {\n    #[error(\"{0:?} is not a valid device path\")]\n    InvalidDevicePath(std::path::PathBuf),\n    #[error(\"failed syscall to create device\")]\n    Syscall(#[from] crate::syscall::SyscallError),\n    #[error(transparent)]\n    Nix(#[from] nix::Error),\n    #[error(transparent)]\n    Other(Box<dyn std::error::Error + Send + Sync>),\n    #[error(\"{0}\")]\n    Custom(String),\n}\n\ntype Result<T> = std::result::Result<T, DeviceError>;\n\npub(crate) fn open_device_fd(dev_path: &Path) -> nix::Result<(OwnedFd, FileStat)> {\n    let fd = open(\n        dev_path,\n        OFlag::O_PATH | OFlag::O_CLOEXEC,\n        Mode::from_bits_truncate(0o000),\n    )?;\n    let owned = unsafe { OwnedFd::from_raw_fd(fd) };\n    let stat = fstat(owned.as_raw_fd())?;\n    Ok((owned, stat))\n}\n\npub(crate) fn verify_dev_null(stat: &FileStat) -> Result<()> {\n    if stat.st_mode & libc::S_IFMT != libc::S_IFCHR {\n        return Err(DeviceError::Custom(\n            \"device is not a character device\".to_string(),\n        ));\n    }\n\n    let actual_major = libc::major(stat.st_rdev) as i64;\n    let actual_minor = libc::minor(stat.st_rdev) as i64;\n    if actual_major != 1 || actual_minor != 3 {\n        return Err(DeviceError::Custom(format!(\n            \"device dev null major/minor mismatch: expected 1/3, actual {}/{}\",\n            actual_major, actual_minor\n        )));\n    }\n    Ok(())\n}\n\npub struct Device {\n    syscall: Box<dyn Syscall>,\n}\n\nimpl Default for Device {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Device {\n    pub fn new() -> Device {\n        Device {\n            syscall: create_syscall(),\n        }\n    }\n\n    pub fn new_with_syscall(syscall: Box<dyn Syscall>) -> Device {\n        Device { syscall }\n    }\n\n    pub fn create_devices<'a, I>(&self, rootfs: &Path, devices: I, bind: bool) -> Result<()>\n    where\n        I: IntoIterator<Item = &'a LinuxDevice>,\n    {\n        let old_mode = umask(Mode::from_bits_truncate(0o000));\n        devices\n            .into_iter()\n            .map(|dev| {\n                if !dev.path().starts_with(\"/dev\") {\n                    tracing::error!(\n                        \"{:?} is not a valid device path starting with /dev\",\n                        dev.path()\n                    );\n                    return Err(DeviceError::InvalidDevicePath(dev.path().to_path_buf()));\n                }\n\n                if bind {\n                    self.bind_dev(rootfs, dev)\n                } else {\n                    self.mknod_dev(rootfs, dev)\n                }\n            })\n            .collect::<Result<Vec<_>>>()?;\n        umask(old_mode);\n\n        Ok(())\n    }\n\n    fn bind_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> {\n        let full_container_path = create_container_dev_path(rootfs, dev)?;\n        tracing::debug!(\n            \"bind_dev with full container path {:?}\",\n            full_container_path\n        );\n\n        let fd = open(\n            &full_container_path,\n            OFlag::O_RDWR | OFlag::O_CREAT,\n            Mode::from_bits_truncate(0o644),\n        )\n        .inspect_err(|err| {\n            tracing::error!(\"failed to open bind dev {:?}: {}\", full_container_path, err);\n        })?;\n        close(fd)?;\n        self.syscall\n            .mount(\n                Some(dev.path()),\n                &full_container_path,\n                Some(\"bind\"),\n                MsFlags::MS_BIND,\n                None,\n            )\n            .map_err(|err| {\n                tracing::error!(\n                    ?err,\n                    path = ?full_container_path,\n                    \"failed to mount bind dev\",\n                );\n                err\n            })?;\n\n        Ok(())\n    }\n\n    fn mknod_dev(&self, rootfs: &Path, dev: &LinuxDevice) -> Result<()> {\n        fn makedev(major: i64, minor: i64) -> u64 {\n            ((minor & 0xff)\n                | ((major & 0xfff) << 8)\n                | ((minor & !0xff) << 12)\n                | ((major & !0xfff) << 32)) as u64\n        }\n\n        let full_container_path = create_container_dev_path(rootfs, dev)?;\n\n        self.syscall\n            .mknod(\n                &full_container_path,\n                to_sflag(dev.typ()),\n                Mode::from_bits_truncate(dev.file_mode().unwrap_or(0o666)),\n                makedev(dev.major(), dev.minor()),\n            )\n            .map_err(|err| {\n                tracing::error!(\n                    ?err,\n                    path = ?full_container_path,\n                    major = ?dev.major(),\n                    minor = ?dev.minor(),\n                    \"failed to mknod device\"\n                );\n\n                err\n            })?;\n        self.syscall\n            .chown(\n                &full_container_path,\n                dev.uid().map(Uid::from_raw),\n                dev.gid().map(Gid::from_raw),\n            )\n            .map_err(|err| {\n                tracing::error!(\n                    path = ?full_container_path,\n                    ?err,\n                    uid = ?dev.uid(),\n                    gid = ?dev.gid(),\n                    \"failed to chown device\"\n                );\n\n                err\n            })?;\n\n        Ok(())\n    }\n}\n\nfn create_container_dev_path(rootfs: &Path, dev: &LinuxDevice) -> Result<PathBuf> {\n    let relative_dev_path = dev.path().as_relative().map_err(|err| {\n        tracing::error!(\n            \"failed to convert {:?} to relative path: {}\",\n            dev.path(),\n            err\n        );\n        DeviceError::Other(err.into())\n    })?;\n    let full_container_path = safe_path::scoped_join(rootfs, relative_dev_path).map_err(|err| {\n        tracing::error!(\"failed to join {rootfs:?} with {:?}: {err}\", dev.path());\n        DeviceError::Other(err.into())\n    })?;\n    std::fs::create_dir_all(\n        full_container_path\n            .parent()\n            .unwrap_or_else(|| Path::new(\"\")),\n    )\n    .map_err(|err| {\n        tracing::error!(\n            \"failed to create parent dir of {:?}: {}\",\n            full_container_path,\n            err\n        );\n        DeviceError::Other(err.into())\n    })?;\n\n    Ok(full_container_path)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::PathBuf;\n\n    use anyhow::Result;\n    use nix::sys::stat::SFlag;\n    use nix::unistd::{Gid, Uid};\n    use oci_spec::runtime::{LinuxDeviceBuilder, LinuxDeviceType};\n\n    use super::*;\n    use crate::syscall::test::{ChownArgs, MknodArgs, TestHelperSyscall};\n\n    #[test]\n    fn test_bind_dev() -> Result<()> {\n        let tmp_dir = tempfile::tempdir()?;\n        let device = Device::new_with_syscall(Box::<TestHelperSyscall>::default());\n        assert!(\n            device\n                .bind_dev(\n                    tmp_dir.path(),\n                    &LinuxDeviceBuilder::default()\n                        .path(PathBuf::from(\"/dev/null\"))\n                        .build()\n                        .unwrap(),\n                )\n                .is_ok()\n        );\n\n        let helper = device\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap();\n        let mount_args = helper.get_mount_args();\n        assert_eq!(1, mount_args.len());\n        let got = &mount_args[0];\n        assert_eq!(Some(PathBuf::from(\"/dev/null\")), got.source);\n        assert_eq!(tmp_dir.path().join(\"dev\").join(\"null\"), got.target);\n        assert_eq!(MsFlags::MS_BIND, got.flags);\n        assert!(got.data.is_none());\n        Ok(())\n    }\n\n    #[test]\n    fn test_mknod_dev() -> Result<()> {\n        let tmp_dir = tempfile::tempdir()?;\n        let device = Device::new_with_syscall(Box::<TestHelperSyscall>::default());\n        assert!(\n            device\n                .mknod_dev(\n                    tmp_dir.path(),\n                    &LinuxDeviceBuilder::default()\n                        .path(PathBuf::from(\"/null\"))\n                        .major(1)\n                        .minor(3)\n                        .typ(LinuxDeviceType::C)\n                        .file_mode(0o644u32)\n                        .uid(1000u32)\n                        .gid(1000u32)\n                        .build()\n                        .unwrap(),\n                )\n                .is_ok()\n        );\n\n        let want_mknod = MknodArgs {\n            path: tmp_dir.path().join(\"null\"),\n            kind: SFlag::S_IFCHR,\n            perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH,\n            dev: 259,\n        };\n        let got_mknod = &device\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mknod_args()[0];\n        assert_eq!(want_mknod, *got_mknod);\n\n        let want_chown = ChownArgs {\n            path: tmp_dir.path().join(\"null\"),\n            owner: Some(Uid::from_raw(1000)),\n            group: Some(Gid::from_raw(1000)),\n        };\n        let got_chown = &device\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_chown_args()[0];\n        assert_eq!(want_chown, *got_chown);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_create_devices() -> Result<()> {\n        let tmp_dir = tempfile::tempdir()?;\n        let device = Device::new_with_syscall(Box::<TestHelperSyscall>::default());\n\n        let devices = vec![\n            LinuxDeviceBuilder::default()\n                .path(PathBuf::from(\"/dev/null\"))\n                .major(1)\n                .minor(3)\n                .typ(LinuxDeviceType::C)\n                .file_mode(0o644u32)\n                .uid(1000u32)\n                .gid(1000u32)\n                .build()\n                .unwrap(),\n        ];\n\n        assert!(\n            device\n                .create_devices(tmp_dir.path(), &devices, true)\n                .is_ok()\n        );\n\n        let mount_args = device\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n        assert_eq!(1, mount_args.len());\n        let bind = &mount_args[0];\n        assert_eq!(Some(PathBuf::from(\"/dev/null\")), bind.source);\n        assert_eq!(tmp_dir.path().join(\"dev/null\"), bind.target);\n        assert_eq!(MsFlags::MS_BIND, bind.flags);\n        assert!(bind.data.is_none());\n\n        assert!(\n            device\n                .create_devices(tmp_dir.path(), &devices, false)\n                .is_ok()\n        );\n\n        let want = MknodArgs {\n            path: tmp_dir.path().join(\"dev/null\"),\n            kind: SFlag::S_IFCHR,\n            perm: Mode::S_IRUSR | Mode::S_IWUSR | Mode::S_IRGRP | Mode::S_IROTH,\n            dev: 259,\n        };\n        let got = &device\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mknod_args()[0];\n        assert_eq!(want, *got);\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/mod.rs",
    "content": "//! During kernel initialization, a minimal replica of the ramfs filesystem is\n//! loaded, called rootfs.  Most systems mount another filesystem over it\n\n#[allow(clippy::module_inception)]\npub(crate) mod rootfs;\npub use rootfs::RootFS;\n\npub mod device;\npub use device::Device;\n\npub(super) mod mount;\npub use mount::Mount;\n\npub(super) mod symlink;\n\npub mod utils;\n\n#[derive(Debug, thiserror::Error)]\npub enum RootfsError {\n    #[error(\"failed syscall\")]\n    Syscall(#[from] crate::syscall::SyscallError),\n    #[error(transparent)]\n    MissingSpec(#[from] crate::error::MissingSpecError),\n    #[error(\"unknown rootfs propagation\")]\n    UnknownRootfsPropagation(String),\n    #[error(transparent)]\n    Symlink(#[from] symlink::SymlinkError),\n    #[error(transparent)]\n    Mount(#[from] mount::MountError),\n    #[error(transparent)]\n    Device(#[from] device::DeviceError),\n}\n\ntype Result<T> = std::result::Result<T, RootfsError>;\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/mount.rs",
    "content": "use std::fs::{Permissions, canonicalize};\nuse std::io::{BufRead, BufReader, ErrorKind};\nuse std::os::fd::{AsFd, OwnedFd};\nuse std::os::unix::fs::{MetadataExt, PermissionsExt};\nuse std::path::{Path, PathBuf};\nuse std::time::Duration;\n#[cfg(feature = \"v1\")]\nuse std::{borrow::Cow, collections::HashMap};\nuse std::{fs, mem};\n\nuse libcgroups::common::CgroupSetup::{Hybrid, Legacy, Unified};\n#[cfg(feature = \"v1\")]\nuse libcgroups::common::DEFAULT_CGROUP_ROOT;\nuse nix::NixPath;\nuse nix::errno::Errno;\nuse nix::mount::MsFlags;\nuse nix::sys::statfs::{PROC_SUPER_MAGIC, statfs};\nuse oci_spec::runtime::{Mount as SpecMount, MountBuilder as SpecMountBuilder};\nuse pathrs::Root;\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\n#[cfg(feature = \"v1\")]\nuse procfs::process::Process;\nuse procfs::process::{MountInfo, MountOptFields};\nuse procfs::{FromRead, ProcessCGroups};\n\n#[cfg(feature = \"v1\")]\nuse super::symlink::Symlink;\nuse super::symlink::SymlinkError;\nuse super::utils::{MountOptionConfig, parse_mount};\nuse crate::syscall::syscall::create_syscall;\nuse crate::syscall::{Syscall, SyscallError, linux};\nuse crate::utils::{PathBufExt, retry};\n\nconst MAX_EBUSY_MOUNT_ATTEMPTS: u32 = 3;\n// runc has a retry interval of 100ms. We are following this.\n// https://github.com/opencontainers/runc/blob/v1.3.0/libcontainer/rootfs_linux.go#L1235\n#[cfg(not(test))]\nconst MOUNT_RETRY_DELAY_MS: u64 = 100;\n// In tests, there is no need to delay, so set it to 0ms.\n#[cfg(test)]\nconst MOUNT_RETRY_DELAY_MS: u64 = 0;\n\n#[derive(Debug, thiserror::Error)]\npub enum MountError {\n    #[error(\"no source in mount spec\")]\n    NoSource,\n    #[error(\"io error\")]\n    Io(#[from] std::io::Error),\n    #[error(\"syscall\")]\n    Syscall(#[from] crate::syscall::SyscallError),\n    #[error(\"nix error\")]\n    Nix(#[from] nix::Error),\n    #[error(\"failed to build oci spec\")]\n    SpecBuild(#[from] oci_spec::OciSpecError),\n    #[error(transparent)]\n    Other(Box<dyn std::error::Error + Send + Sync>),\n    #[error(\"{0}\")]\n    Custom(String),\n    #[error(\"symlink\")]\n    Symlink(#[from] SymlinkError),\n    #[error(\"procfs failed\")]\n    Procfs(#[from] procfs::ProcError),\n    #[error(\"unknown mount option: {0}\")]\n    UnsupportedMountOption(String),\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n}\n\ntype Result<T> = std::result::Result<T, MountError>;\n\npub trait MountInfoProvider {\n    fn mountinfo(&self) -> Result<Vec<MountInfo>>;\n}\n\n/// Default provider that reads mountinfo from /proc via procfs.\npub struct ProcMountInfoProvider;\n\nimpl ProcMountInfoProvider {\n    pub fn new() -> Self {\n        ProcMountInfoProvider\n    }\n}\n\nimpl MountInfoProvider for ProcMountInfoProvider {\n    fn mountinfo(&self) -> Result<Vec<MountInfo>> {\n        let reader = BufReader::new(ProcfsHandle::new()?.open(\n            ProcfsBase::ProcSelf,\n            \"mountinfo\",\n            OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n        )?);\n\n        let mount_infos: Vec<MountInfo> = reader\n            .lines()\n            .map(|lr| {\n                lr.map_err(MountError::from)\n                    .and_then(|s| MountInfo::from_line(&s).map_err(MountError::from))\n            })\n            .collect::<Result<_>>()?;\n        Ok(mount_infos)\n    }\n}\n\n#[derive(Debug)]\npub struct MountOptions<'a> {\n    pub root: &'a Path,\n    pub label: Option<&'a str>,\n    #[allow(dead_code)]\n    pub cgroup_ns: bool,\n}\n\npub struct Mount {\n    syscall: Box<dyn Syscall>,\n    mountinfo_provider: Box<dyn MountInfoProvider>,\n}\n\nimpl Default for Mount {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Mount {\n    pub fn new() -> Mount {\n        Mount {\n            syscall: create_syscall(),\n            mountinfo_provider: Box::new(ProcMountInfoProvider::new()),\n        }\n    }\n\n    pub fn with_mountinfo_provider<P: MountInfoProvider + 'static>(mut self, provider: P) -> Self {\n        self.mountinfo_provider = Box::new(provider);\n        self\n    }\n\n    pub fn setup_mount(&self, mount: &SpecMount, options: &MountOptions) -> Result<()> {\n        tracing::debug!(\"mounting {:?}\", mount);\n        let mut mount_option_config = parse_mount(mount)?;\n\n        match mount.typ().as_deref() {\n            Some(\"cgroup\") => {\n                let cgroup_setup = libcgroups::common::get_cgroup_setup().map_err(|err| {\n                    tracing::error!(\"failed to determine cgroup setup: {}\", err);\n                    MountError::Other(err.into())\n                })?;\n                match cgroup_setup {\n                    Legacy | Hybrid => {\n                        #[cfg(not(feature = \"v1\"))]\n                        panic!(\n                            \"libcontainer can't run in a Legacy or Hybrid cgroup setup without the v1 feature\"\n                        );\n                        #[cfg(feature = \"v1\")]\n                        self.mount_cgroup_v1(mount, options).map_err(|err| {\n                            tracing::error!(\"failed to mount cgroup v1: {}\", err);\n                            err\n                        })?\n                    }\n                    Unified => {\n                        #[cfg(not(feature = \"v2\"))]\n                        panic!(\n                            \"libcontainer can't run in a Unified cgroup setup without the v2 feature\"\n                        );\n                        #[cfg(feature = \"v2\")]\n                        self.mount_cgroup_v2(mount, options, &mount_option_config)\n                            .map_err(|err| {\n                                tracing::error!(\"failed to mount cgroup v2: {}\", err);\n                                err\n                            })?\n                    }\n                }\n            }\n            // procfs and sysfs are special because we need to ensure they are actually\n            // mounted on a specific path in a container without any funny business.\n            // Ref: https://github.com/opencontainers/runc/security/advisories/GHSA-fh74-hm69-rqjw\n            Some(typ @ (\"proc\" | \"sysfs\")) => {\n                let dest_path = options\n                    .root\n                    .join_safely(Path::new(mount.destination()).normalize())\n                    .map_err(|err| {\n                        tracing::error!(\n                            \"could not join rootfs path with mount destination {:?}: {}\",\n                            mount.destination(),\n                            err\n                        );\n                        MountError::Other(err.into())\n                    })?;\n\n                match fs::symlink_metadata(&dest_path) {\n                    Ok(m) if !m.is_dir() => {\n                        return Err(MountError::Other(\n                            format!(\"filesystem {} must be mounted on ordinary directory\", typ)\n                                .into(),\n                        ));\n                    }\n                    Err(e) if e.kind() != ErrorKind::NotFound => {\n                        return Err(MountError::Other(\n                            format!(\"symlink_metadata failed for {}: {}\", dest_path.display(), e)\n                                .into(),\n                        ));\n                    }\n                    _ => {}\n                }\n\n                self.check_proc_mount(options.root, mount)?;\n\n                self.mount_into_container(mount, options.root, &mount_option_config, options.label)\n                    .map_err(|err| {\n                        tracing::error!(\"failed to mount {:?}: {}\", mount, err);\n                        err\n                    })?;\n            }\n            _ => {\n                if mount.destination() == Path::new(\"/dev\") {\n                    mount_option_config.flags &= !MsFlags::MS_RDONLY;\n                    self.mount_into_container(\n                        mount,\n                        options.root,\n                        &mount_option_config,\n                        options.label,\n                    )\n                    .map_err(|err| {\n                        tracing::error!(\"failed to mount /dev: {}\", err);\n                        err\n                    })?;\n                } else {\n                    self.mount_into_container(\n                        mount,\n                        options.root,\n                        &mount_option_config,\n                        options.label,\n                    )\n                    .map_err(|err| {\n                        tracing::error!(\"failed to mount {:?}: {}\", mount, err);\n                        err\n                    })?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    #[cfg(feature = \"v1\")]\n    fn mount_cgroup_v1(&self, cgroup_mount: &SpecMount, options: &MountOptions) -> Result<()> {\n        tracing::debug!(\"mounting cgroup v1 filesystem\");\n        // create tmpfs into which the cgroup subsystems will be mounted\n        let tmpfs = SpecMountBuilder::default()\n            .source(\"tmpfs\")\n            .typ(\"tmpfs\")\n            .destination(cgroup_mount.destination())\n            .options(\n                [\"noexec\", \"nosuid\", \"nodev\", \"mode=755\"]\n                    .iter()\n                    .map(|o| o.to_string())\n                    .collect::<Vec<String>>(),\n            )\n            .build()\n            .map_err(|err| {\n                tracing::error!(\"failed to build tmpfs for cgroup: {}\", err);\n                err\n            })?;\n\n        self.setup_mount(&tmpfs, options).map_err(|err| {\n            tracing::error!(\"failed to mount tmpfs for cgroup: {}\", err);\n            err\n        })?;\n\n        // get all cgroup mounts on the host system\n        let host_mounts: Vec<PathBuf> = libcgroups::v1::util::list_subsystem_mount_points()\n            .map_err(|err| {\n                tracing::error!(\"failed to get subsystem mount points: {}\", err);\n                MountError::Other(err.into())\n            })?\n            .into_iter()\n            .filter(|p| p.as_path().starts_with(DEFAULT_CGROUP_ROOT))\n            .collect();\n        tracing::debug!(\"cgroup mounts: {:?}\", host_mounts);\n\n        // get process cgroups\n        let ppid = std::os::unix::process::parent_id();\n        // The non-zero ppid means that the PID Namespace is not separated.\n        let ppid = if ppid == 0 { std::process::id() } else { ppid };\n        let root_cgroups = Process::new(ppid as i32)?.cgroups()?.0;\n        let process_cgroups: HashMap<String, String> =\n            ProcessCGroups::from_read(ProcfsHandle::new()?.open(\n                ProcfsBase::ProcSelf,\n                \"cgroup\",\n                OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n            )?)?\n            .into_iter()\n            .map(|c| {\n                let hierarchy = c.hierarchy;\n                // When youki itself is running inside a container, the cgroup path\n                // will include the path of pid-1, which needs to be stripped before\n                // mounting.\n                let root_pathname = root_cgroups\n                    .iter()\n                    .find(|c| c.hierarchy == hierarchy)\n                    .map(|c| c.pathname.as_ref())\n                    .unwrap_or(\"\");\n                let path = c\n                    .pathname\n                    .strip_prefix(root_pathname)\n                    .unwrap_or(&c.pathname);\n                (c.controllers.join(\",\"), path.to_owned())\n            })\n            .collect();\n        tracing::debug!(\"Process cgroups: {:?}\", process_cgroups);\n\n        let cgroup_root = options\n            .root\n            .join_safely(cgroup_mount.destination())\n            .map_err(|err| {\n                tracing::error!(\n                    \"could not join rootfs path with cgroup mount destination: {}\",\n                    err\n                );\n                MountError::Other(err.into())\n            })?;\n        tracing::debug!(\"cgroup root: {:?}\", cgroup_root);\n\n        let symlink = Symlink::new();\n\n        // setup cgroup mounts for container\n        for host_mount in &host_mounts {\n            if let Some(subsystem_name) = host_mount.file_name().and_then(|n| n.to_str()) {\n                if options.cgroup_ns {\n                    self.setup_namespaced_subsystem(\n                        cgroup_mount,\n                        options,\n                        subsystem_name,\n                        subsystem_name == \"systemd\",\n                    )?;\n                } else {\n                    self.setup_emulated_subsystem(\n                        cgroup_mount,\n                        options,\n                        subsystem_name,\n                        subsystem_name == \"systemd\",\n                        host_mount,\n                        &process_cgroups,\n                    )?;\n                }\n\n                symlink.setup_comount_symlinks(&cgroup_root, subsystem_name)?;\n            } else {\n                tracing::warn!(\"could not get subsystem name from {:?}\", host_mount);\n            }\n        }\n\n        Ok(())\n    }\n\n    // On some distros cgroup subsystems are comounted e.g. cpu,cpuacct or net_cls,net_prio. These systems\n    // have to be comounted in the container as well as the kernel will reject trying to mount them separately.\n    #[cfg(feature = \"v1\")]\n    fn setup_namespaced_subsystem(\n        &self,\n        cgroup_mount: &SpecMount,\n        options: &MountOptions,\n        subsystem_name: &str,\n        named: bool,\n    ) -> Result<()> {\n        tracing::debug!(\n            \"Mounting (namespaced) {:?} cgroup subsystem\",\n            subsystem_name\n        );\n        let subsystem_mount = SpecMountBuilder::default()\n            .source(\"cgroup\")\n            .typ(\"cgroup\")\n            .destination(cgroup_mount.destination().join(subsystem_name))\n            .options(\n                [\"noexec\", \"nosuid\", \"nodev\"]\n                    .iter()\n                    .map(|o| o.to_string())\n                    .collect::<Vec<String>>(),\n            )\n            .build()\n            .map_err(|err| {\n                tracing::error!(\"failed to build {subsystem_name} mount: {err}\");\n                err\n            })?;\n\n        let data: Cow<str> = if named {\n            format!(\"name={subsystem_name}\").into()\n        } else {\n            subsystem_name.into()\n        };\n\n        let mount_options_config = MountOptionConfig {\n            flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV,\n            data: vec![data.into_owned()],\n            rec_attr: None,\n        };\n\n        self.mount_into_container(\n            &subsystem_mount,\n            options.root,\n            &mount_options_config,\n            options.label,\n        )\n        .map_err(|err| {\n            tracing::error!(\"failed to mount {subsystem_mount:?}: {err}\");\n            err\n        })\n    }\n\n    #[cfg(feature = \"v1\")]\n    fn setup_emulated_subsystem(\n        &self,\n        cgroup_mount: &SpecMount,\n        options: &MountOptions,\n        subsystem_name: &str,\n        named: bool,\n        host_mount: &Path,\n        process_cgroups: &HashMap<String, String>,\n    ) -> Result<()> {\n        tracing::debug!(\"Mounting (emulated) {:?} cgroup subsystem\", subsystem_name);\n        let named_hierarchy: Cow<str> = if named {\n            format!(\"name={subsystem_name}\").into()\n        } else {\n            subsystem_name.into()\n        };\n\n        if let Some(proc_path) = process_cgroups.get(named_hierarchy.as_ref()) {\n            let emulated = SpecMountBuilder::default()\n                .source(\n                    host_mount\n                        .join_safely(proc_path.as_str())\n                        .map_err(|err| {\n                            tracing::error!(\n                                \"failed to join mount source for {subsystem_name} subsystem: {}\",\n                                err\n                            );\n                            MountError::Other(err.into())\n                        })?,\n                )\n                .destination(\n                    cgroup_mount\n                        .destination()\n                        .join_safely(subsystem_name)\n                        .map_err(|err| {\n                            tracing::error!(\n                                \"failed to join mount destination for {subsystem_name} subsystem: {}\",\n                                err\n                            );\n                            MountError::Other(err.into())\n                        })?,\n                )\n                .typ(\"bind\")\n                .options(\n                    [\"rw\", \"rbind\"]\n                        .iter()\n                        .map(|o| o.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .build()?;\n            tracing::debug!(\"Mounting emulated cgroup subsystem: {:?}\", emulated);\n\n            self.setup_mount(&emulated, options).map_err(|err| {\n                tracing::error!(\"failed to mount {subsystem_name} cgroup hierarchy: {}\", err);\n                err\n            })?;\n        } else {\n            tracing::warn!(\"Could not mount {:?} cgroup subsystem\", subsystem_name);\n        }\n\n        Ok(())\n    }\n\n    #[cfg(feature = \"v2\")]\n    fn mount_cgroup_v2(\n        &self,\n        cgroup_mount: &SpecMount,\n        options: &MountOptions,\n        mount_option_config: &MountOptionConfig,\n    ) -> Result<()> {\n        tracing::debug!(\"Mounting cgroup v2 filesystem\");\n\n        let cgroup_mount = SpecMountBuilder::default()\n            .typ(\"cgroup2\")\n            .source(\"cgroup\")\n            .destination(cgroup_mount.destination())\n            .options(Vec::new())\n            .build()?;\n        tracing::debug!(\"{:?}\", cgroup_mount);\n\n        if self\n            .mount_into_container(\n                &cgroup_mount,\n                options.root,\n                mount_option_config,\n                options.label,\n            )\n            .is_err()\n        {\n            let host_mount = libcgroups::v2::util::get_unified_mount_point().map_err(|err| {\n                tracing::error!(\"failed to get unified mount point: {}\", err);\n                MountError::Other(err.into())\n            })?;\n\n            let process_cgroup = ProcessCGroups::from_read(ProcfsHandle::new()?.open(\n                ProcfsBase::ProcSelf,\n                \"cgroup\",\n                OpenFlags::O_RDONLY | OpenFlags::O_CLOEXEC,\n            )?)?\n            .into_iter()\n            .find(|c| c.hierarchy == 0)\n            .map(|c| PathBuf::from(c.pathname))\n            .ok_or_else(|| MountError::Custom(\"failed to find unified process cgroup\".into()))?;\n\n            let bind_mount = SpecMountBuilder::default()\n                .typ(\"bind\")\n                .source(host_mount.join_safely(process_cgroup).map_err(|err| {\n                    tracing::error!(\"failed to join host mount for cgroup hierarchy: {}\", err);\n                    MountError::Other(err.into())\n                })?)\n                .destination(cgroup_mount.destination())\n                .options(Vec::new())\n                .build()\n                .map_err(|err| {\n                    tracing::error!(\"failed to build cgroup bind mount: {}\", err);\n                    err\n                })?;\n            tracing::debug!(\"{:?}\", bind_mount);\n\n            let mut mount_option_config = (*mount_option_config).clone();\n            mount_option_config.flags |= MsFlags::MS_BIND;\n            self.mount_into_container(\n                &bind_mount,\n                options.root,\n                &mount_option_config,\n                options.label,\n            )\n            .map_err(|err| {\n                tracing::error!(\"failed to bind mount cgroup hierarchy: {}\", err);\n                err\n            })?;\n        }\n\n        Ok(())\n    }\n\n    /// Make parent mount of rootfs private if it was shared, which is required by pivot_root.\n    /// It also makes sure following bind mount does not propagate in other namespaces.\n    pub fn make_parent_mount_private(&self, rootfs: &Path) -> Result<MountInfo> {\n        let mount_infos = self.mountinfo_provider.mountinfo()?;\n        let parent_mount = find_parent_mount(rootfs, mount_infos)?;\n\n        // check parent mount has 'shared' propagation type\n        if parent_mount\n            .opt_fields\n            .iter()\n            .any(|field| matches!(field, MountOptFields::Shared(_)))\n        {\n            self.syscall.mount(\n                None,\n                &parent_mount.mount_point,\n                None,\n                MsFlags::MS_PRIVATE,\n                None,\n            )?;\n        }\n        Ok(parent_mount)\n    }\n\n    fn mount_into_container(\n        &self,\n        m: &SpecMount,\n        rootfs: &Path,\n        mount_option_config: &MountOptionConfig,\n        label: Option<&str>,\n    ) -> Result<()> {\n        let typ = m.typ().as_deref();\n        let mut data_options = mount_option_config.data.clone();\n\n        if let Some(l) = label {\n            if typ != Some(\"proc\") && typ != Some(\"sysfs\") {\n                if Path::new(\"/sys/fs/selinux\").exists() {\n                    data_options.push(format!(\"context={}\", l));\n                } else {\n                    tracing::debug!(\"ignoring mount label because SELinux is disabled\");\n                }\n            }\n        }\n\n        let root = Root::open(rootfs)?;\n        let container_dest = m.destination();\n\n        let source = m.source().as_ref().ok_or(MountError::NoSource)?;\n        let dir_perm = Permissions::from_mode(0o755);\n        let src = if typ == Some(\"bind\") {\n            let src = canonicalize(source).map_err(|err| {\n                tracing::error!(\"failed to canonicalize {:?}: {}\", source, err);\n                err\n            })?;\n\n            if src.is_file() {\n                let parent = container_dest\n                    .parent()\n                    .ok_or(MountError::Custom(\"destination has no parent\".to_string()))?;\n                root.mkdir_all(parent, &dir_perm)?;\n\n                match root.create_file(\n                    container_dest,\n                    OpenFlags::O_EXCL\n                        | OpenFlags::O_CREAT\n                        | OpenFlags::O_NOFOLLOW\n                        | OpenFlags::O_CLOEXEC,\n                    &Permissions::from_mode(0o644),\n                ) {\n                    Ok(_) => Ok(()),\n                    // If we get here, the file is already present, so continue.\n                    Err(create_err) => root\n                        .resolve(container_dest)\n                        .map(|_| ())\n                        .map_err(|_| create_err),\n                }?;\n            } else {\n                root.mkdir_all(container_dest, &dir_perm)?;\n            };\n\n            src\n        } else {\n            root.mkdir_all(container_dest, &dir_perm)?;\n            PathBuf::from(source)\n        };\n\n        let dest: OwnedFd = root.resolve(container_dest)?.into();\n        let dest_fd = dest.as_fd();\n\n        let is_bind = typ == Some(\"bind\")\n            || m.options()\n                .as_deref()\n                .is_some_and(|ops| ops.iter().any(|o| o == \"bind\" || o == \"rbind\"));\n\n        // fd-based mount flow:\n        // - bind: open_tree -> mount_setattr -> move_mount\n        // - nonbind: fsopen -> fsconfig -> fsmount -> mount_setattr -> move_mount\n        if is_bind {\n            let recursive = m\n                .options()\n                .as_ref()\n                .map(|v| v.iter().any(|o| o == \"rbind\"))\n                .unwrap_or(false);\n            let mut open_tree_flags: libc::c_uint = (libc::OPEN_TREE_CLOEXEC as libc::c_uint)\n                | (libc::OPEN_TREE_CLONE as libc::c_uint)\n                | (libc::AT_EMPTY_PATH as libc::c_uint);\n            if recursive {\n                open_tree_flags |= libc::AT_RECURSIVE as libc::c_uint;\n            };\n\n            let src_str = src.to_str().ok_or(SyscallError::Nix(Errno::EINVAL))?;\n            let mount_fd_owned =\n                self.syscall\n                    .open_tree(libc::AT_FDCWD, Some(src_str), open_tree_flags)?;\n            let mount_fd = mount_fd_owned.as_fd();\n\n            // mount_setattr\n            let attr_set_from_flags = self.mount_flag_to_attr(&mount_option_config.flags);\n            let mut mount_attr = linux::MountAttr {\n                attr_set: 0,\n                attr_clr: 0,\n                propagation: 0,\n                userns_fd: 0,\n            };\n            mount_attr.attr_set |= attr_set_from_flags;\n\n            self.apply_atime_from_msflags(\n                &mut mount_attr,\n                attr_set_from_flags,\n                mount_option_config.flags,\n            );\n\n            self.syscall.mount_setattr(\n                mount_fd,\n                Path::new(\"\"),\n                linux::AT_EMPTY_PATH,\n                &mount_attr,\n                mem::size_of::<linux::MountAttr>(),\n            )?;\n\n            // rec_attr is applied recursively\n            if let Some(rec_attr) = &mount_option_config.rec_attr {\n                self.syscall.mount_setattr(\n                    mount_fd,\n                    Path::new(\"\"),\n                    linux::AT_EMPTY_PATH | linux::AT_RECURSIVE,\n                    rec_attr,\n                    mem::size_of::<linux::MountAttr>(),\n                )?;\n            }\n\n            // move_mount\n            self.syscall.move_mount(\n                mount_fd,\n                None,\n                dest_fd,\n                None,\n                linux::MOVE_MOUNT_T_EMPTY_PATH | linux::MOVE_MOUNT_F_EMPTY_PATH,\n            )?;\n        } else {\n            let mount_fn = || -> std::result::Result<(), SyscallError> {\n                // fsopen\n                let fsfd_owned = self.syscall.fsopen(typ, 0)?;\n                let fsfd = fsfd_owned.as_fd();\n\n                // fsconfig\n                let src_str = src\n                    .as_os_str()\n                    .to_str()\n                    .ok_or(SyscallError::Nix(Errno::EINVAL))?;\n                self.syscall.fsconfig(\n                    fsfd,\n                    linux::FSCONFIG_SET_STRING as u32,\n                    Some(\"source\"),\n                    Some(src_str),\n                    0,\n                )?;\n\n                for opt in data_options.iter().filter(|s| !s.is_empty()) {\n                    if let Some((k, v)) = opt.split_once('=') {\n                        self.syscall.fsconfig(\n                            fsfd,\n                            linux::FSCONFIG_SET_STRING as u32,\n                            Some(k),\n                            Some(v),\n                            0,\n                        )?;\n                    } else {\n                        self.syscall.fsconfig(\n                            fsfd,\n                            linux::FSCONFIG_SET_FLAG as u32,\n                            Some(opt),\n                            None,\n                            0,\n                        )?;\n                    };\n                }\n\n                self.syscall\n                    .fsconfig(fsfd, linux::FSCONFIG_CMD_CREATE as u32, None, None, 0)?;\n\n                // fsmount\n                let mount_fd_owned = self.syscall.fsmount(fsfd, 0, None)?;\n                let mount_fd = mount_fd_owned.as_fd();\n\n                // mount_setattr\n                let attr_set_from_flags = self.mount_flag_to_attr(&mount_option_config.flags);\n                let mut mount_attr = linux::MountAttr {\n                    attr_set: 0,\n                    attr_clr: 0,\n                    propagation: 0,\n                    userns_fd: 0,\n                };\n                mount_attr.attr_set |= attr_set_from_flags;\n\n                self.apply_atime_from_msflags(\n                    &mut mount_attr,\n                    attr_set_from_flags,\n                    mount_option_config.flags,\n                );\n\n                self.syscall.mount_setattr(\n                    mount_fd,\n                    Path::new(\"\"),\n                    linux::AT_EMPTY_PATH,\n                    &mount_attr,\n                    mem::size_of::<linux::MountAttr>(),\n                )?;\n\n                // rec_attr is applied recursively\n                if let Some(rec_attr) = &mount_option_config.rec_attr {\n                    self.syscall.mount_setattr(\n                        mount_fd,\n                        Path::new(\"\"),\n                        linux::AT_EMPTY_PATH | linux::AT_RECURSIVE,\n                        rec_attr,\n                        mem::size_of::<linux::MountAttr>(),\n                    )?;\n                }\n\n                // move_mount\n                self.syscall.move_mount(\n                    mount_fd,\n                    None,\n                    dest_fd,\n                    None,\n                    linux::MOVE_MOUNT_T_EMPTY_PATH | linux::MOVE_MOUNT_F_EMPTY_PATH,\n                )?;\n                Ok(())\n            };\n\n            match mount_fn() {\n                Ok(()) => {}\n                Err(SyscallError::Nix(nix::Error::EINVAL)) => {\n                    mount_fn()?;\n                }\n                Err(SyscallError::Nix(nix::Error::EBUSY)) => {\n                    let delay = Duration::from_millis(MOUNT_RETRY_DELAY_MS);\n                    let retry_policy =\n                        |err: &SyscallError| matches!(err, SyscallError::Nix(Errno::EBUSY));\n                    retry(mount_fn, MAX_EBUSY_MOUNT_ATTEMPTS - 1, delay, retry_policy)?;\n                }\n                Err(e) => return Err(e.into()),\n            }\n        }\n\n        Ok(())\n    }\n\n    // https://man7.org/linux/man-pages/man2/mount_setattr.2.html\n    // To apply MsFlags via mount_setattr, we set the corresponding bits in attr_set\n    fn mount_flag_to_attr(&self, flags: &MsFlags) -> u64 {\n        const MAP_SET: &[(MsFlags, u64)] = &[\n            (MsFlags::MS_RDONLY, linux::MOUNT_ATTR_RDONLY),\n            (MsFlags::MS_NOSUID, linux::MOUNT_ATTR_NOSUID),\n            (MsFlags::MS_NODEV, linux::MOUNT_ATTR_NODEV),\n            (MsFlags::MS_NOEXEC, linux::MOUNT_ATTR_NOEXEC),\n            (MsFlags::MS_NOATIME, linux::MOUNT_ATTR_NOATIME),\n            (MsFlags::MS_NODIRATIME, linux::MOUNT_ATTR_NODIRATIME),\n            (MsFlags::MS_RELATIME, linux::MOUNT_ATTR_RELATIME),\n            (MsFlags::MS_STRICTATIME, linux::MOUNT_ATTR_STRICTATIME),\n        ];\n\n        let mut set = 0;\n        for (ms, attr) in MAP_SET {\n            if flags.intersects(*ms) {\n                set |= *attr;\n            }\n        }\n        set\n    }\n\n    // Apply atime-related configuration.\n    // https://man7.org/linux/man-pages/man2/mount_setattr.2.html\n    // ref: MOUNT_ATTR__ATIME\n    fn apply_atime_from_msflags(\n        &self,\n        mount_attr: &mut linux::MountAttr,\n        attr_set_from_flags: u64,\n        msflags: MsFlags,\n    ) {\n        let atime_bits =\n            linux::MOUNT_ATTR_NOATIME | linux::MOUNT_ATTR_STRICTATIME | linux::MOUNT_ATTR_RELATIME;\n\n        let noatime = msflags.contains(MsFlags::MS_NOATIME);\n        let strictatime = msflags.contains(MsFlags::MS_STRICTATIME);\n        let relatime = msflags.contains(MsFlags::MS_RELATIME);\n\n        let atime = if strictatime {\n            linux::MOUNT_ATTR_STRICTATIME\n        } else if noatime {\n            linux::MOUNT_ATTR_NOATIME\n        } else if relatime {\n            linux::MOUNT_ATTR_RELATIME\n        } else {\n            0\n        };\n\n        let non_atime = attr_set_from_flags & !atime_bits;\n\n        if atime != 0 {\n            mount_attr.attr_clr |= linux::MOUNT_ATTR__ATIME;\n            mount_attr.attr_set |= non_atime | atime;\n        } else {\n            mount_attr.attr_set |= non_atime;\n        }\n    }\n\n    /// check_proc_mount checks to ensure that the mount destination is not over the top of /proc.\n    /// dest is required to be an abs path and have any symlinks resolved before calling this function.\n    /// # Example  (a valid case where `/proc` is mounted with `proc` type.)\n    ///\n    /// ```\n    /// use std::path::PathBuf;\n    /// use oci_spec::runtime::MountBuilder as SpecMountBuilder;\n    /// use libcontainer::rootfs::Mount;\n    ///\n    /// let mounter = Mount::new();\n    ///\n    /// let rootfs = PathBuf::from(\"/var/lib/my-runtime/containers/abcd1234/rootfs\");\n    /// let destination = PathBuf::from(\"/proc\");\n    /// let source = PathBuf::from(\"proc\");\n    /// let typ = \"proc\";\n    ///\n    /// let mount = SpecMountBuilder::default()\n    ///     .destination(destination)\n    ///     .typ(typ)\n    ///     .source(source)\n    ///     .build()\n    ///     .expect(\"failed to build SpecMount\");\n    ///\n    /// assert!(mounter.check_proc_mount(rootfs.as_path(), &mount).is_ok());\n    /// ```\n    /// # Example (bind mount to `/proc` that should fail)\n    /// ```\n    /// use std::path::PathBuf;\n    /// use oci_spec::runtime::MountBuilder as SpecMountBuilder;\n    /// use libcontainer::rootfs::Mount;\n    ///\n    /// let mounter = Mount::new();\n    ///\n    /// let rootfs = PathBuf::from(\"/var/lib/my-runtime/containers/abcd1234/rootfs\");\n    /// let destination = PathBuf::from(\"/proc\");\n    /// let source = PathBuf::from(\"/tmp\");\n    /// let typ = \"bind\";\n    ///\n    /// let mount = SpecMountBuilder::default()\n    ///     .destination(destination)\n    ///     .typ(typ)\n    ///     .source(source)\n    ///     .build()\n    ///     .expect(\"failed to build SpecMount\");\n    ///\n    /// assert!(mounter.check_proc_mount(rootfs.as_path(), &mount).is_err());\n    /// ```\n    pub fn check_proc_mount(&self, rootfs: &Path, mount: &SpecMount) -> Result<()> {\n        const PROC_ROOT_INO: u64 = 1;\n        const VALID_PROC_MOUNTS: &[&str] = &[\n            \"/proc/cpuinfo\",\n            \"/proc/diskstats\",\n            \"/proc/meminfo\",\n            \"/proc/stat\",\n            \"/proc/swaps\",\n            \"/proc/uptime\",\n            \"/proc/loadavg\",\n            \"/proc/slabinfo\",\n            \"/proc/sys/kernel/ns_last_pid\",\n            \"/proc/sys/crypto/fips_enabled\",\n        ];\n\n        let dest = mount.destination();\n\n        let container_proc_path = rootfs.join(\"proc\");\n        let dest_path = rootfs.join_safely(dest).map_err(|err| {\n            tracing::error!(\n                \"could not join rootfs path with mount destination {:?}: {}\",\n                dest,\n                err\n            );\n            MountError::Other(err.into())\n        })?;\n\n        // If path is Ok, it means dest_path is under /proc.\n        // - Ok(p) with p.is_empty(): mount target is exactly /proc.\n        //   In this case, check if the mount source is procfs.\n        // - Ok(p) with !p.is_empty(): mount target is under /proc.\n        //   Only allow if it matches a specific whitelist of proc entries.\n        // - Err: not under /proc, so no further checks are needed\n        let path = dest_path.strip_prefix(&container_proc_path);\n\n        match path {\n            Err(_) => Ok(()),\n            Ok(p) if p.as_os_str().is_empty() => {\n                if mount.typ().as_deref() == Some(\"proc\") {\n                    return Ok(());\n                }\n\n                if mount.typ().as_deref() == Some(\"bind\") {\n                    if let Some(source) = mount.source() {\n                        let stat = statfs(source).map_err(MountError::from)?;\n                        if stat.filesystem_type() == PROC_SUPER_MAGIC {\n                            let meta = fs::metadata(source).map_err(MountError::from)?;\n                            // Follow the behavior of runc's checkProcMount function.\n                            if meta.ino() != PROC_ROOT_INO {\n                                tracing::warn!(\n                                    \"bind-mount {} (source {:?}) is of type procfs but not the root (inode {}). \\\n                                    Future versions may reject this.\",\n                                    dest.display(),\n                                    mount.source(),\n                                    meta.ino()\n                                );\n                            }\n                            return Ok(());\n                        }\n                    }\n                }\n\n                Err(MountError::Custom(format!(\n                    \"{} cannot be mounted because it is not type proc\",\n                    dest.display()\n                )))\n            }\n            Ok(_) => {\n                // Here dest is definitely under /proc. Do not allow those,\n                // except for a few specific entries emulated by lxcfs.\n                let is_allowed = VALID_PROC_MOUNTS.iter().any(|allowed_path| {\n                    let container_allowed_path = rootfs.join(allowed_path.trim_start_matches('/'));\n                    dest_path == container_allowed_path\n                });\n\n                if is_allowed {\n                    Ok(())\n                } else {\n                    Err(MountError::Other(\n                        format!(\"{} is not a valid mount under /proc\", dest.display()).into(),\n                    ))\n                }\n            }\n        }\n    }\n}\n\n/// Find parent mount of rootfs in given mount infos\npub fn find_parent_mount(\n    rootfs: &Path,\n    mount_infos: Vec<MountInfo>,\n) -> std::result::Result<MountInfo, MountError> {\n    // find the longest mount point\n    let parent_mount_info = mount_infos\n        .into_iter()\n        .filter(|mi| rootfs.starts_with(&mi.mount_point))\n        .max_by(|mi1, mi2| mi1.mount_point.len().cmp(&mi2.mount_point.len()))\n        .ok_or_else(|| {\n            MountError::Custom(format!(\"can't find the parent mount of {:?}\", rootfs))\n        })?;\n    Ok(parent_mount_info)\n}\n\n#[cfg(test)]\nmod tests {\n    #[cfg(feature = \"v1\")]\n    use std::fs;\n    use std::fs::OpenOptions;\n    use std::os::unix::fs::symlink;\n    use std::str::FromStr;\n\n    use anyhow::{Context, Ok, Result};\n\n    use super::*;\n    use crate::syscall::test::{ArgName, MountArgs, TestHelperSyscall};\n\n    #[test]\n    #[ignore] // TODO: fix fd-based test\n    fn test_mount_into_container() -> Result<()> {\n        let tmp_dir = tempfile::tempdir()?;\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/dev/pts\"))\n                .typ(\"devpts\")\n                .source(PathBuf::from(\"devpts\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"newinstance\".to_string(),\n                    \"ptmxmode=0666\".to_string(),\n                    \"mode=0620\".to_string(),\n                    \"gid=5\".to_string(),\n                ])\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n\n            assert!(\n                m.mount_into_container(\n                    mount,\n                    tmp_dir.path(),\n                    &mount_option_config,\n                    Some(\"defaults\")\n                )\n                .is_ok()\n            );\n\n            let want = vec![MountArgs {\n                source: Some(PathBuf::from(\"devpts\")),\n                target: tmp_dir.path().join(\"dev/pts\"),\n                fstype: Some(\"devpts\".to_string()),\n                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC,\n                data: Some(\n                    \"newinstance,ptmxmode=0666,mode=0620,gid=5,context=\\\"defaults\\\"\".to_string(),\n                ),\n            }];\n            let got = &m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap()\n                .get_mount_args();\n            assert_eq!(want, *got);\n            assert_eq!(got.len(), 1);\n        }\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/dev/null\"))\n                .typ(\"bind\")\n                .source(tmp_dir.path().join(\"null\"))\n                .options(vec![\"ro\".to_string()])\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n            OpenOptions::new()\n                .create(true)\n                .truncate(true)\n                .write(true)\n                .open(tmp_dir.path().join(\"null\"))?;\n\n            assert!(\n                m.mount_into_container(mount, tmp_dir.path(), &mount_option_config, None)\n                    .is_ok()\n            );\n\n            let want = vec![\n                MountArgs {\n                    source: Some(tmp_dir.path().join(\"null\")),\n                    target: tmp_dir.path().join(\"dev/null\"),\n                    fstype: Some(\"bind\".to_string()),\n                    flags: MsFlags::MS_RDONLY,\n                    data: Some(\"\".to_string()),\n                },\n                // remount one\n                MountArgs {\n                    source: None,\n                    target: tmp_dir.path().join(\"dev/null\"),\n                    fstype: None,\n                    flags: MsFlags::MS_RDONLY | MsFlags::MS_REMOUNT,\n                    data: None,\n                },\n            ];\n            let got = &m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap()\n                .get_mount_args();\n            assert_eq!(want, *got);\n            assert_eq!(got.len(), 2);\n        }\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/tmp/retry\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"tmpfs\"))\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n\n            let syscall = m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap();\n            syscall.set_ret_err(ArgName::Mount, || {\n                Err(crate::syscall::SyscallError::Nix(nix::errno::Errno::EINVAL))\n            });\n            syscall.set_ret_err_times(ArgName::Mount, 1);\n\n            assert!(\n                m.mount_into_container(mount, tmp_dir.path(), &mount_option_config, None)\n                    .is_ok()\n            );\n            assert_eq!(syscall.get_mount_args().len(), 1);\n        }\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/tmp/retry\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"tmpfs\"))\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n\n            let syscall = m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap();\n            syscall.set_ret_err(ArgName::Mount, || {\n                Err(crate::syscall::SyscallError::Nix(nix::errno::Errno::EINVAL))\n            });\n            syscall.set_ret_err_times(ArgName::Mount, 2);\n\n            assert!(\n                m.mount_into_container(mount, tmp_dir.path(), &mount_option_config, None)\n                    .is_err()\n            );\n            assert_eq!(syscall.get_mount_args().len(), 0);\n        }\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/tmp/retry\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"tmpfs\"))\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n\n            let syscall = m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap();\n            syscall.set_ret_err(ArgName::Mount, || {\n                Err(crate::syscall::SyscallError::Nix(nix::errno::Errno::EBUSY))\n            });\n            syscall.set_ret_err_times(ArgName::Mount, MAX_EBUSY_MOUNT_ATTEMPTS as usize - 1);\n\n            assert!(\n                m.mount_into_container(mount, tmp_dir.path(), &mount_option_config, None)\n                    .is_ok()\n            );\n            assert_eq!(syscall.get_mount_args().len(), 1);\n        }\n        {\n            let m = Mount::new();\n            let mount = &SpecMountBuilder::default()\n                .destination(PathBuf::from(\"/tmp/retry\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"tmpfs\"))\n                .build()?;\n            let mount_option_config = parse_mount(mount)?;\n\n            let syscall = m\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap();\n            syscall.set_ret_err(ArgName::Mount, || {\n                Err(crate::syscall::SyscallError::Nix(nix::errno::Errno::EBUSY))\n            });\n            syscall.set_ret_err_times(ArgName::Mount, MAX_EBUSY_MOUNT_ATTEMPTS as usize);\n\n            assert!(\n                m.mount_into_container(mount, tmp_dir.path(), &mount_option_config, None)\n                    .is_err()\n            );\n            assert_eq!(syscall.get_mount_args().len(), 0);\n        }\n\n        Ok(())\n    }\n\n    struct FakeMountInfo {\n        entries: Vec<MountInfo>,\n    }\n    impl MountInfoProvider for FakeMountInfo {\n        fn mountinfo(&self) -> std::result::Result<Vec<MountInfo>, MountError> {\n            std::result::Result::Ok(self.entries.clone())\n        }\n    }\n\n    #[test]\n    fn test_make_parent_mount_private() -> Result<()> {\n        let tmp_dir = PathBuf::from_str(\"/tmp/mydir\")?;\n\n        let parent = tmp_dir.as_path().parent().unwrap().to_path_buf();\n        let fake = FakeMountInfo {\n            entries: vec![MountInfo {\n                mnt_id: 1,\n                pid: 0,\n                majmin: \"\".to_string(),\n                root: \"/\".to_string(),\n                mount_point: parent.clone(),\n                mount_options: Default::default(),\n                opt_fields: vec![MountOptFields::Shared(1)],\n                fs_type: \"tmpfs\".to_string(),\n                mount_source: None,\n                super_options: Default::default(),\n            }],\n        };\n\n        let m = Mount::new().with_mountinfo_provider(fake);\n        m.make_parent_mount_private(tmp_dir.as_path())?;\n\n        let set = m\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(set.len(), 1);\n\n        let got = &set[0];\n        assert_eq!(got.source, None);\n        assert_eq!(got.fstype, None);\n        assert_eq!(got.flags, MsFlags::MS_PRIVATE);\n        assert_eq!(got.data, None);\n\n        assert_eq!(got.target, parent);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_not_make_parent_mount_private_if_already_private() -> Result<()> {\n        let tmp_dir = PathBuf::from_str(\"/tmp/mydir\")?;\n\n        let parent = tmp_dir.as_path().parent().unwrap().to_path_buf();\n        let fake = FakeMountInfo {\n            entries: vec![MountInfo {\n                mnt_id: 1,\n                pid: 0,\n                majmin: \"\".to_string(),\n                root: \"/\".to_string(),\n                mount_point: parent.clone(),\n                mount_options: Default::default(),\n                opt_fields: vec![],\n                fs_type: \"tmpfs\".to_string(),\n                mount_source: None,\n                super_options: Default::default(),\n            }],\n        };\n\n        let m = Mount::new().with_mountinfo_provider(fake);\n        m.make_parent_mount_private(tmp_dir.as_path())?;\n\n        let set = m\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(set.len(), 0);\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"v1\")]\n    #[ignore] // TODO: fix fd-based test\n    fn test_namespaced_subsystem_success() -> Result<()> {\n        let tmp = tempfile::tempdir().unwrap();\n        let container_cgroup = Path::new(\"/container_cgroup\");\n\n        let mounter = Mount::new();\n\n        let spec_cgroup_mount = SpecMountBuilder::default()\n            .destination(container_cgroup)\n            .source(\"cgroup\")\n            .typ(\"cgroup\")\n            .build()\n            .context(\"failed to build cgroup mount\")?;\n\n        let mount_opts = MountOptions {\n            root: tmp.path(),\n            label: None,\n            cgroup_ns: true,\n        };\n\n        let subsystem_name = \"cpu\";\n\n        mounter\n            .setup_namespaced_subsystem(&spec_cgroup_mount, &mount_opts, subsystem_name, false)\n            .context(\"failed to setup namespaced subsystem\")?;\n\n        let expected = MountArgs {\n            source: Some(PathBuf::from(\"cgroup\")),\n            target: tmp\n                .path()\n                .join_safely(container_cgroup)?\n                .join(subsystem_name),\n            fstype: Some(\"cgroup\".to_owned()),\n            flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV,\n            data: Some(\"cpu\".to_owned()),\n        };\n\n        let got = mounter\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(got.len(), 1);\n        assert_eq!(expected, got[0]);\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"v1\")]\n    #[ignore] // TODO: fix fd-based test\n    fn test_emulated_subsystem_success() -> Result<()> {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let host_cgroup_mount = tmp.path().join(\"host_cgroup\");\n        let host_cgroup = host_cgroup_mount.join(\"cpu/container1\");\n        fs::create_dir_all(&host_cgroup)?;\n\n        let container_cgroup = Path::new(\"/container_cgroup\");\n        let mounter = Mount::new();\n\n        let spec_cgroup_mount = SpecMountBuilder::default()\n            .destination(container_cgroup)\n            .source(\"cgroup\")\n            .typ(\"cgroup\")\n            .build()\n            .context(\"failed to build cgroup mount\")?;\n\n        let mount_opts = MountOptions {\n            root: tmp.path(),\n            label: None,\n            cgroup_ns: false,\n        };\n\n        let subsystem_name = \"cpu\";\n        let mut process_cgroups = HashMap::new();\n        process_cgroups.insert(\"cpu\".to_owned(), \"container1\".to_owned());\n\n        // act\n        mounter\n            .setup_emulated_subsystem(\n                &spec_cgroup_mount,\n                &mount_opts,\n                subsystem_name,\n                false,\n                &host_cgroup_mount.join(subsystem_name),\n                &process_cgroups,\n            )\n            .context(\"failed to setup emulated subsystem\")?;\n\n        // assert\n        let expected = MountArgs {\n            source: Some(host_cgroup),\n            target: tmp\n                .path()\n                .join_safely(container_cgroup)?\n                .join(subsystem_name),\n            fstype: Some(\"bind\".to_owned()),\n            flags: MsFlags::MS_BIND | MsFlags::MS_REC,\n            data: Some(\"\".to_owned()),\n        };\n\n        let got = mounter\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(got.len(), 1);\n        assert_eq!(expected, got[0]);\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"v1\")]\n    #[ignore] // TODO: fix fd-based test\n    fn test_mount_cgroup_v1() -> Result<()> {\n        // arrange\n        let tmp = tempfile::tempdir()?;\n        let container_cgroup = PathBuf::from(\"/sys/fs/cgroup\");\n\n        let spec_cgroup_mount = SpecMountBuilder::default()\n            .destination(&container_cgroup)\n            .source(\"cgroup\")\n            .typ(\"cgroup\")\n            .build()\n            .context(\"failed to build cgroup mount\")?;\n\n        let mount_opts = MountOptions {\n            root: tmp.path(),\n            label: None,\n            cgroup_ns: true,\n        };\n\n        let mounter = Mount::new();\n\n        // act\n        mounter\n            .mount_cgroup_v1(&spec_cgroup_mount, &mount_opts)\n            .context(\"failed to mount cgroup v1\")?;\n\n        // assert\n        let mut got = mounter\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args()\n            .into_iter();\n\n        let host_mounts = libcgroups::v1::util::list_subsystem_mount_points()?;\n        assert_eq!(got.len(), host_mounts.len() + 1);\n\n        let expected = MountArgs {\n            source: Some(PathBuf::from(\"tmpfs\".to_owned())),\n            target: tmp.path().join_safely(&container_cgroup)?,\n            fstype: Some(\"tmpfs\".to_owned()),\n            flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV,\n            data: Some(\"mode=755\".to_owned()),\n        };\n        assert_eq!(expected, got.next().unwrap());\n\n        for (host_mount, act) in host_mounts.iter().zip(got) {\n            let subsystem_name = host_mount.file_name().and_then(|f| f.to_str()).unwrap();\n            let expected = MountArgs {\n                source: Some(PathBuf::from(\"cgroup\".to_owned())),\n                target: tmp\n                    .path()\n                    .join_safely(&container_cgroup)?\n                    .join(subsystem_name),\n                fstype: Some(\"cgroup\".to_owned()),\n                flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV,\n                data: Some(\n                    if subsystem_name == \"systemd\" {\n                        format!(\"name={subsystem_name}\")\n                    } else {\n                        subsystem_name.to_string()\n                    }\n                    .to_owned(),\n                ),\n            };\n            assert_eq!(expected, act);\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"v2\")]\n    #[ignore] // TODO: fix fd-based test\n    fn test_mount_cgroup_v2() -> Result<()> {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let container_cgroup = PathBuf::from(\"/sys/fs/cgroup\");\n\n        let spec_cgroup_mount = SpecMountBuilder::default()\n            .destination(&container_cgroup)\n            .source(\"cgroup\")\n            .typ(\"cgroup\")\n            .build()\n            .context(\"failed to build cgroup mount\")?;\n\n        let mount_opts = MountOptions {\n            root: tmp.path(),\n            label: None,\n            cgroup_ns: true,\n        };\n\n        let mounter = Mount::new();\n        let flags = MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV;\n\n        // act\n        let mount_option_config = MountOptionConfig {\n            flags,\n            data: vec![],\n            rec_attr: None,\n        };\n        mounter\n            .mount_cgroup_v2(&spec_cgroup_mount, &mount_opts, &mount_option_config)\n            .context(\"failed to mount cgroup v2\")?;\n\n        // assert\n        let expected = MountArgs {\n            source: Some(PathBuf::from(\"cgroup\".to_owned())),\n            target: tmp.path().join_safely(container_cgroup)?,\n            fstype: Some(\"cgroup2\".to_owned()),\n            flags: MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_NODEV,\n            data: Some(\"\".to_owned()),\n        };\n\n        let got = mounter\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_mount_args();\n\n        assert_eq!(got.len(), 1);\n        assert_eq!(expected, got[0]);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_find_parent_mount() -> anyhow::Result<()> {\n        let mount_infos = vec![\n            MountInfo {\n                mnt_id: 11,\n                pid: 10,\n                majmin: \"\".to_string(),\n                root: \"/\".to_string(),\n                mount_point: PathBuf::from(\"/\"),\n                mount_options: Default::default(),\n                opt_fields: vec![],\n                fs_type: \"ext4\".to_string(),\n                mount_source: Some(\"/dev/sda1\".to_string()),\n                super_options: Default::default(),\n            },\n            MountInfo {\n                mnt_id: 12,\n                pid: 11,\n                majmin: \"\".to_string(),\n                root: \"/\".to_string(),\n                mount_point: PathBuf::from(\"/proc\"),\n                mount_options: Default::default(),\n                opt_fields: vec![],\n                fs_type: \"proc\".to_string(),\n                mount_source: Some(\"proc\".to_string()),\n                super_options: Default::default(),\n            },\n        ];\n\n        let res = find_parent_mount(Path::new(\"/path/to/rootfs\"), mount_infos)\n            .context(\"failed to get parent mount\")?;\n        assert_eq!(res.mnt_id, 11);\n        Ok(())\n    }\n\n    #[test]\n    fn test_find_parent_mount_with_empty_mount_infos() {\n        let mount_infos = vec![];\n        let res = find_parent_mount(Path::new(\"/path/to/rootfs\"), mount_infos);\n        assert!(res.is_err());\n    }\n\n    #[test]\n    fn test_check_proc_mount_proc_ok() -> Result<()> {\n        let rootfs = tempfile::tempdir()?;\n        let mounter = Mount::new();\n\n        let mount = SpecMountBuilder::default()\n            .destination(PathBuf::from(\"/proc\"))\n            .typ(\"proc\".to_string())\n            .source(PathBuf::from(\"proc\"))\n            .build()?;\n\n        assert!(mounter.check_proc_mount(rootfs.path(), &mount).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn test_check_proc_mount_allowed_subpath() -> Result<()> {\n        let rootfs = tempfile::tempdir()?;\n        let uptime = rootfs.path().join(\"proc/uptime\");\n        std::fs::create_dir_all(uptime.parent().unwrap())?;\n\n        let mounter = Mount::new();\n        let mount = SpecMountBuilder::default()\n            .destination(PathBuf::from(\"/proc/uptime\"))\n            .typ(\"bind\".to_string())\n            .source(uptime)\n            .build()?;\n\n        assert!(mounter.check_proc_mount(rootfs.path(), &mount).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn test_check_proc_mount_denied_subpath() -> Result<()> {\n        let rootfs = tempfile::tempdir()?;\n        let custom = rootfs.path().join(\"proc/custom\");\n        std::fs::create_dir_all(custom.parent().unwrap())?;\n\n        let mounter = Mount::new();\n        let mount = SpecMountBuilder::default()\n            .destination(PathBuf::from(\"/proc/custom\"))\n            .typ(\"bind\".to_string())\n            .source(custom)\n            .build()?;\n\n        assert!(mounter.check_proc_mount(rootfs.path(), &mount).is_err());\n        Ok(())\n    }\n\n    #[test]\n    fn setup_mount_proc_fails_if_destination_is_symlink() -> Result<()> {\n        let tmp = tempfile::tempdir()?;\n        let rootfs = tmp.path();\n\n        let symlink_path = rootfs.join(\"symlink\");\n        fs::create_dir_all(&symlink_path)?;\n        let proc_path = rootfs.join(\"proc\");\n\n        symlink(&symlink_path, &proc_path)?;\n\n        let mount = SpecMountBuilder::default()\n            .destination(PathBuf::from(\"/proc\"))\n            .typ(\"proc\")\n            .source(proc_path)\n            .build()?;\n\n        let options = MountOptions {\n            root: rootfs,\n            label: None,\n            cgroup_ns: true,\n        };\n\n        let m = Mount::new();\n\n        let res = m.setup_mount(&mount, &options);\n\n        // proc destination symlink should be rejected\n        assert!(res.is_err());\n        let err = format!(\"{:?}\", res.err().unwrap());\n        assert!(err.contains(\"must be mounted on ordinary directory\"));\n\n        Ok(())\n    }\n\n    #[test]\n    fn setup_mount_sys_fails_if_destination_is_symlink() -> Result<()> {\n        let tmp = tempfile::tempdir()?;\n        let rootfs = tmp.path();\n\n        let symlink_path = rootfs.join(\"symlink\");\n        fs::create_dir_all(&symlink_path)?;\n        let sys_path = rootfs.join(\"sys\");\n\n        symlink(&symlink_path, &sys_path)?;\n\n        let mount = SpecMountBuilder::default()\n            .destination(PathBuf::from(\"/sys\"))\n            .typ(\"sysfs\")\n            .source(sys_path)\n            .build()?;\n\n        let options = MountOptions {\n            root: rootfs,\n            label: None,\n            cgroup_ns: true,\n        };\n\n        let m = Mount::new();\n\n        let res = m.setup_mount(&mount, &options);\n\n        // sys destination symlink should be rejected\n        assert!(res.is_err());\n        let err = format!(\"{:?}\", res.err().unwrap());\n        assert!(err.contains(\"must be mounted on ordinary directory\"));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/rootfs.rs",
    "content": "use std::collections::HashSet;\nuse std::path::Path;\n\nuse nix::mount::MsFlags;\nuse oci_spec::runtime::{Linux, Spec};\n\nuse super::device::Device;\nuse super::mount::{Mount, MountOptions};\nuse super::symlink::Symlink;\nuse super::utils::default_devices;\nuse super::{Result, RootfsError};\nuse crate::error::MissingSpecError;\nuse crate::syscall::Syscall;\nuse crate::syscall::syscall::create_syscall;\n\n/// Holds information about rootfs\npub struct RootFS {\n    syscall: Box<dyn Syscall>,\n}\n\nimpl Default for RootFS {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl RootFS {\n    pub fn new() -> RootFS {\n        RootFS {\n            syscall: create_syscall(),\n        }\n    }\n\n    pub fn mount_to_rootfs(\n        &self,\n        linux: &Linux,\n        spec: &Spec,\n        rootfs: &Path,\n        cgroup_ns: bool,\n    ) -> Result<()> {\n        let mut flags = MsFlags::MS_REC;\n        match linux.rootfs_propagation().as_deref() {\n            Some(\"shared\") => flags |= MsFlags::MS_SHARED,\n            Some(\"private\") => flags |= MsFlags::MS_PRIVATE,\n            Some(\"slave\" | \"unbindable\") | None => flags |= MsFlags::MS_SLAVE,\n            Some(unknown) => {\n                return Err(RootfsError::UnknownRootfsPropagation(unknown.to_string()));\n            }\n        }\n\n        self.syscall\n            .mount(None, Path::new(\"/\"), None, flags, None)\n            .map_err(|err| {\n                tracing::error!(\n                    ?err,\n                    ?flags,\n                    \"failed to change the mount propagation type of the root\"\n                );\n\n                err\n            })?;\n\n        let mounter = Mount::new();\n\n        mounter.make_parent_mount_private(rootfs)?;\n\n        tracing::debug!(\"mount root fs {:?}\", rootfs);\n        self.syscall\n            .mount(\n                Some(rootfs),\n                rootfs,\n                None,\n                MsFlags::MS_BIND | MsFlags::MS_REC,\n                None,\n            )\n            .map_err(|err| {\n                tracing::error!(?rootfs, ?err, \"failed to bind mount rootfs\");\n                err\n            })?;\n\n        let global_options = MountOptions {\n            root: rootfs,\n            label: linux.mount_label().as_deref(),\n            cgroup_ns,\n        };\n\n        if let Some(mounts) = spec.mounts() {\n            for mount in mounts {\n                mounter.setup_mount(mount, &global_options)?;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn prepare_rootfs(\n        &self,\n        spec: &Spec,\n        rootfs: &Path,\n        bind_devices: bool,\n        cgroup_ns: bool,\n    ) -> Result<()> {\n        tracing::debug!(?rootfs, \"prepare rootfs\");\n        let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n\n        self.mount_to_rootfs(linux, spec, rootfs, cgroup_ns)?;\n\n        let symlinker = Symlink::new();\n        symlinker.setup_kcore_symlink(rootfs)?;\n        symlinker.setup_default_symlinks(rootfs)?;\n\n        let devicer = Device::new();\n        if let Some(added_devices) = linux.devices() {\n            let mut path_set = HashSet::new();\n            let devices = default_devices();\n            added_devices.iter().for_each(|d| {\n                path_set.insert(d.path());\n            });\n            let default = devices.iter().filter(|d| !path_set.contains(d.path()));\n            devicer.create_devices(rootfs, added_devices.iter().chain(default), bind_devices)\n        } else {\n            devicer.create_devices(rootfs, &default_devices(), bind_devices)\n        }?;\n\n        symlinker.setup_ptmx(rootfs)?;\n        Ok(())\n    }\n\n    /// Change propagation type of rootfs as specified in spec.\n    pub fn adjust_root_mount_propagation(&self, linux: &Linux) -> Result<()> {\n        let rootfs_propagation = linux.rootfs_propagation().as_deref();\n        let flags = match rootfs_propagation {\n            Some(\"shared\") => Some(MsFlags::MS_SHARED),\n            Some(\"unbindable\") => Some(MsFlags::MS_UNBINDABLE),\n            _ => None,\n        };\n\n        if let Some(flags) = flags {\n            self.syscall\n                .mount(None, Path::new(\"/\"), None, flags, None)\n                .map_err(|err| {\n                    tracing::error!(\n                        ?err,\n                        ?flags,\n                        \"failed to adjust the mount propagation type of the root\"\n                    );\n                    err\n                })?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/symlink.rs",
    "content": "use std::fs::remove_file;\nuse std::path::Path;\n\nuse crate::syscall::Syscall;\nuse crate::syscall::syscall::create_syscall;\n\n#[derive(Debug, thiserror::Error)]\npub enum SymlinkError {\n    #[error(\"syscall failed\")]\n    Syscall {\n        source: crate::syscall::SyscallError,\n    },\n    #[error(\"failed symlink: {msg}\")]\n    Other { msg: String },\n}\n\ntype Result<T> = std::result::Result<T, SymlinkError>;\n\npub struct Symlink {\n    syscall: Box<dyn Syscall>,\n}\n\nimpl Default for Symlink {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl Symlink {\n    pub fn new() -> Symlink {\n        Symlink::with_syscall(create_syscall())\n    }\n\n    fn with_syscall(syscall: Box<dyn Syscall>) -> Symlink {\n        Symlink { syscall }\n    }\n\n    // Create symlinks for subsystems that have been comounted e.g. cpu -> cpu,cpuacct, cpuacct -> cpu,cpuacct\n    #[cfg(feature = \"v1\")]\n    pub fn setup_comount_symlinks(&self, cgroup_root: &Path, subsystem_name: &str) -> Result<()> {\n        if !subsystem_name.contains(',') {\n            return Ok(());\n        }\n\n        for comount in subsystem_name.split_terminator(',') {\n            let link = cgroup_root.join(comount);\n            self.syscall\n                .symlink(Path::new(subsystem_name), &link)\n                .map_err(|err| {\n                    tracing::error!(\"failed to symlink {link:?} to {subsystem_name:?}\");\n                    SymlinkError::Syscall { source: err }\n                })?;\n        }\n\n        Ok(())\n    }\n\n    pub fn setup_ptmx(&self, rootfs: &Path) -> Result<()> {\n        let ptmx = rootfs.join(\"dev/ptmx\");\n        if let Err(e) = remove_file(&ptmx) {\n            if e.kind() != ::std::io::ErrorKind::NotFound {\n                return Err(SymlinkError::Other {\n                    msg: \"could not delete /dev/ptmx\".into(),\n                });\n            }\n        }\n\n        self.syscall\n            .symlink(Path::new(\"pts/ptmx\"), &ptmx)\n            .map_err(|err| {\n                tracing::error!(\"failed to symlink ptmx\");\n                SymlinkError::Syscall { source: err }\n            })?;\n        Ok(())\n    }\n\n    // separating kcore symlink out from setup_default_symlinks for a better way to do the unit test,\n    // since not every architecture has /proc/kcore file.\n    pub fn setup_kcore_symlink(&self, rootfs: &Path) -> Result<()> {\n        if Path::new(\"/proc/kcore\").exists() {\n            self.syscall\n                .symlink(Path::new(\"/proc/kcore\"), &rootfs.join(\"dev/kcore\"))\n                .map_err(|err| {\n                    tracing::error!(\"failed to symlink kcore\");\n                    SymlinkError::Syscall { source: err }\n                })?;\n        }\n        Ok(())\n    }\n\n    pub fn setup_default_symlinks(&self, rootfs: &Path) -> Result<()> {\n        let defaults = [\n            (\"/proc/self/fd\", \"dev/fd\"),\n            (\"/proc/self/fd/0\", \"dev/stdin\"),\n            (\"/proc/self/fd/1\", \"dev/stdout\"),\n            (\"/proc/self/fd/2\", \"dev/stderr\"),\n        ];\n        for (src, dst) in defaults {\n            self.syscall\n                .symlink(Path::new(src), &rootfs.join(dst))\n                .map_err(|err| {\n                    tracing::error!(\"failed to symlink defaults\");\n                    SymlinkError::Syscall { source: err }\n                })?;\n        }\n\n        Ok(())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    #[cfg(feature = \"v1\")]\n    use std::fs;\n    use std::path::PathBuf;\n\n    #[cfg(feature = \"v1\")]\n    use anyhow::{Context, Result};\n    use nix::fcntl::{OFlag, open};\n    use nix::sys::stat::Mode;\n\n    use super::*;\n    #[cfg(feature = \"v1\")]\n    use crate::syscall::linux::LinuxSyscall;\n    use crate::syscall::test::TestHelperSyscall;\n\n    #[test]\n    fn test_setup_ptmx() {\n        {\n            let tmp_dir = tempfile::tempdir().unwrap();\n            let symlink = Symlink::new();\n            assert!(symlink.setup_ptmx(tmp_dir.path()).is_ok());\n            let want = (PathBuf::from(\"pts/ptmx\"), tmp_dir.path().join(\"dev/ptmx\"));\n            let got = &symlink\n                .syscall\n                .as_any()\n                .downcast_ref::<TestHelperSyscall>()\n                .unwrap()\n                .get_symlink_args()[0];\n            assert_eq!(want, *got)\n        }\n        // make remove_file goes into the bail! path\n        {\n            let tmp_dir = tempfile::tempdir().unwrap();\n            open(\n                &tmp_dir.path().join(\"dev\"),\n                OFlag::O_RDWR | OFlag::O_CREAT,\n                Mode::from_bits_truncate(0o644),\n            )\n            .unwrap();\n\n            let symlink = Symlink::new();\n            assert!(symlink.setup_ptmx(tmp_dir.path()).is_err());\n            assert_eq!(\n                0,\n                symlink\n                    .syscall\n                    .as_any()\n                    .downcast_ref::<TestHelperSyscall>()\n                    .unwrap()\n                    .get_symlink_args()\n                    .len()\n            );\n        }\n    }\n\n    #[test]\n    fn test_setup_default_symlinks() {\n        let tmp_dir = tempfile::tempdir().unwrap();\n        let symlink = Symlink::new();\n        assert!(symlink.setup_default_symlinks(tmp_dir.path()).is_ok());\n        let want = vec![\n            (\n                PathBuf::from(\"/proc/self/fd\"),\n                tmp_dir.path().join(\"dev/fd\"),\n            ),\n            (\n                PathBuf::from(\"/proc/self/fd/0\"),\n                tmp_dir.path().join(\"dev/stdin\"),\n            ),\n            (\n                PathBuf::from(\"/proc/self/fd/1\"),\n                tmp_dir.path().join(\"dev/stdout\"),\n            ),\n            (\n                PathBuf::from(\"/proc/self/fd/2\"),\n                tmp_dir.path().join(\"dev/stderr\"),\n            ),\n        ];\n        let got = symlink\n            .syscall\n            .as_any()\n            .downcast_ref::<TestHelperSyscall>()\n            .unwrap()\n            .get_symlink_args();\n        assert_eq!(want, got)\n    }\n\n    #[test]\n    #[cfg(feature = \"v1\")]\n    fn setup_comounted_symlinks_success() -> Result<()> {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let cpu = tmp.path().join(\"cpu\");\n        let cpuacct = tmp.path().join(\"cpuacct\");\n        let cpu_cpuacct = tmp.path().join(\"cpu,cpuacct\");\n        fs::create_dir_all(cpu_cpuacct)?;\n        let symlink = Symlink::with_syscall(Box::new(LinuxSyscall));\n\n        // act\n        symlink\n            .setup_comount_symlinks(tmp.path(), \"cpu,cpuacct\")\n            .context(\"failed to setup symlinks\")?;\n\n        // assert\n        assert!(cpu.exists(), \"cpu symlink does not exist\");\n        assert!(cpuacct.exists(), \"cpuacct symlink does not exist\");\n\n        assert!(\n            fs::symlink_metadata(&cpu)?.file_type().is_symlink(),\n            \"cpu is not a symlink\"\n        );\n        assert!(\n            fs::symlink_metadata(&cpuacct)?.file_type().is_symlink(),\n            \"cpuacct is not a symlink\"\n        );\n\n        assert_eq!(\n            fs::read_link(cpu)?,\n            PathBuf::from(\"cpu,cpuacct\"),\n            \"cpu does not link to cpu,cpuacct\"\n        );\n        assert_eq!(\n            fs::read_link(cpuacct)?,\n            PathBuf::from(\"cpu,cpuacct\"),\n            \"cpuacct does not link to cpu,cpuacct\"\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    #[cfg(feature = \"v1\")]\n    fn setup_comounted_symlinks_no_comounts() -> Result<()> {\n        // arrange\n        let tmp = tempfile::tempdir().unwrap();\n        let symlink = Symlink::with_syscall(Box::new(LinuxSyscall));\n\n        // act\n        let result = symlink\n            .setup_comount_symlinks(tmp.path(), \"memory,task\")\n            .context(\"failed to setup symlinks\");\n\n        // assert\n        assert!(result.is_ok());\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/rootfs/utils.rs",
    "content": "use std::path::PathBuf;\nuse std::str::FromStr;\n\nuse nix::mount::MsFlags;\nuse nix::sys::stat::SFlag;\nuse oci_spec::runtime::{LinuxDevice, LinuxDeviceBuilder, LinuxDeviceType, Mount};\n\nuse super::mount::MountError;\nuse crate::syscall::linux::{self, MountOption, MountRecursive};\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct MountOptionConfig {\n    /// Mount Flags.\n    pub flags: MsFlags,\n\n    /// Mount data options applied to the mount (e.g. `lowerdir=...`).\n    pub data: Vec<String>,\n\n    /// RecAttr represents mount properties to be applied recursively.\n    pub rec_attr: Option<linux::MountAttr>,\n}\n\npub fn default_devices() -> Vec<LinuxDevice> {\n    vec![\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/null\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(3)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/zero\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(5)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/full\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(7)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/tty\"))\n            .typ(LinuxDeviceType::C)\n            .major(5)\n            .minor(0)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/urandom\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(9)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n        LinuxDeviceBuilder::default()\n            .path(PathBuf::from(\"/dev/random\"))\n            .typ(LinuxDeviceType::C)\n            .major(1)\n            .minor(8)\n            .file_mode(0o0666u32)\n            .build()\n            .unwrap(),\n    ]\n}\n\npub fn to_sflag(dev_type: LinuxDeviceType) -> SFlag {\n    match dev_type {\n        LinuxDeviceType::A => SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO,\n        LinuxDeviceType::B => SFlag::S_IFBLK,\n        LinuxDeviceType::C | LinuxDeviceType::U => SFlag::S_IFCHR,\n        LinuxDeviceType::P => SFlag::S_IFIFO,\n    }\n}\n\npub fn parse_mount(m: &Mount) -> std::result::Result<MountOptionConfig, MountError> {\n    let mut flags = MsFlags::empty();\n    let mut data = Vec::new();\n    let mut mount_attr: Option<linux::MountAttr> = None;\n\n    if let Some(options) = &m.options() {\n        for option in options {\n            if let Ok(mount_attr_option) = linux::MountRecursive::from_str(option.as_str()) {\n                // Some options aren't corresponding to the mount flags.\n                // These options need `AT_RECURSIVE` options.\n                // ref: https://github.com/opencontainers/runtime-spec/blob/main/config.md#linux-mount-options\n                let (is_clear, flag) = match mount_attr_option {\n                    MountRecursive::Rdonly(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Nosuid(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Nodev(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Noexec(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Atime(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Relatime(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Noatime(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::StrictAtime(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::NoDiratime(is_clear, flag) => (is_clear, flag),\n                    MountRecursive::Nosymfollow(is_clear, flag) => (is_clear, flag),\n                };\n\n                if mount_attr.is_none() {\n                    mount_attr = Some(linux::MountAttr {\n                        attr_set: 0,\n                        attr_clr: 0,\n                        propagation: 0,\n                        userns_fd: 0,\n                    });\n                }\n\n                if let Some(mount_attr) = &mut mount_attr {\n                    if is_clear {\n                        mount_attr.attr_clr |= flag;\n                    } else {\n                        mount_attr.attr_set |= flag;\n                        if flag & linux::MOUNT_ATTR__ATIME == flag {\n                            // https://man7.org/linux/man-pages/man2/mount_setattr.2.html\n                            // cannot simply specify the access-time setting in attr_set, but must\n                            // also include MOUNT_ATTR__ATIME in the attr_clr field.\n                            mount_attr.attr_clr |= linux::MOUNT_ATTR__ATIME;\n                        }\n                    }\n                }\n                continue;\n            }\n\n            if let Some((is_clear, flag)) = match MountOption::from_str(option.as_ref()) {\n                Ok(v) => match v {\n                    MountOption::Defaults(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Ro(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Rw(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Suid(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Nosuid(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Dev(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Nodev(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Exec(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Noexec(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Sync(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Async(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Dirsync(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Remount(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Mand(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Nomand(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Atime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Noatime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Diratime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Nodiratime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Bind(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Rbind(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Unbindable(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Runbindable(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Private(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Rprivate(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Shared(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Rshared(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Slave(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Rslave(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Relatime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Norelatime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Strictatime(is_clear, flag) => Some((is_clear, flag)),\n                    MountOption::Nostrictatime(is_clear, flag) => Some((is_clear, flag)),\n                },\n                Err(unknown) => {\n                    if unknown == \"idmap\" || unknown == \"ridmap\" {\n                        return Err(MountError::UnsupportedMountOption(unknown));\n                    }\n                    None\n                }\n            } {\n                if is_clear {\n                    flags &= !flag;\n                } else {\n                    flags |= flag;\n                }\n                continue;\n            }\n\n            data.push(option.as_str());\n        }\n    }\n    Ok(MountOptionConfig {\n        flags,\n        data: data.into_iter().map(|s| s.to_string()).collect(),\n        rec_attr: mount_attr,\n    })\n}\n\n#[cfg(test)]\nmod tests {\n    use anyhow::Result;\n    use oci_spec::runtime::MountBuilder;\n\n    use super::*;\n    use crate::syscall::linux::MountAttr;\n\n    #[test]\n    fn test_to_sflag() {\n        assert_eq!(\n            SFlag::S_IFBLK | SFlag::S_IFCHR | SFlag::S_IFIFO,\n            to_sflag(LinuxDeviceType::A)\n        );\n        assert_eq!(SFlag::S_IFBLK, to_sflag(LinuxDeviceType::B));\n        assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::C));\n        assert_eq!(SFlag::S_IFCHR, to_sflag(LinuxDeviceType::U));\n        assert_eq!(SFlag::S_IFIFO, to_sflag(LinuxDeviceType::P));\n    }\n\n    #[test]\n    fn test_parse_mount() -> Result<()> {\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/proc\"))\n                .typ(\"proc\")\n                .source(PathBuf::from(\"proc\"))\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::empty(),\n                data: vec![],\n                rec_attr: None,\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/dev\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"tmpfs\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"strictatime\".to_string(),\n                    \"mode=755\".to_string(),\n                    \"size=65536k\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID | MsFlags::MS_STRICTATIME,\n                data: vec![\"mode=755\".to_string(), \"size=65536k\".to_string()],\n                rec_attr: None,\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/dev/pts\"))\n                .typ(\"devpts\")\n                .source(PathBuf::from(\"devpts\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"newinstance\".to_string(),\n                    \"ptmxmode=0666\".to_string(),\n                    \"mode=0620\".to_string(),\n                    \"gid=5\".to_string(),\n                ])\n                .build()\n                .unwrap(),\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC,\n                data: vec![\n                    \"newinstance\".to_string(),\n                    \"ptmxmode=0666\".to_string(),\n                    \"mode=0620\".to_string(),\n                    \"gid=5\".to_string()\n                ],\n                rec_attr: None\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/dev/shm\"))\n                .typ(\"tmpfs\")\n                .source(PathBuf::from(\"shm\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"nodev\".to_string(),\n                    \"mode=1777\".to_string(),\n                    \"size=65536k\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV,\n                data: vec![\"mode=1777\".to_string(), \"size=65536k\".to_string()],\n                rec_attr: None\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/dev/mqueue\"))\n                .typ(\"mqueue\")\n                .source(PathBuf::from(\"mqueue\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"nodev\".to_string(),\n                ])\n                .build()\n                .unwrap(),\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID | MsFlags::MS_NOEXEC | MsFlags::MS_NODEV,\n                data: vec![],\n                rec_attr: None\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/sys\"))\n                .typ(\"sysfs\")\n                .source(PathBuf::from(\"sysfs\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"nodev\".to_string(),\n                    \"ro\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID\n                    | MsFlags::MS_NOEXEC\n                    | MsFlags::MS_NODEV\n                    | MsFlags::MS_RDONLY,\n                data: vec![],\n                rec_attr: None,\n            },\n            mount_option_config\n        );\n\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .destination(PathBuf::from(\"/sys/fs/cgroup\"))\n                .typ(\"cgroup\")\n                .source(PathBuf::from(\"cgroup\"))\n                .options(vec![\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"nodev\".to_string(),\n                    \"relatime\".to_string(),\n                    \"ro\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID\n                    | MsFlags::MS_NOEXEC\n                    | MsFlags::MS_NODEV\n                    | MsFlags::MS_RDONLY\n                    | MsFlags::MS_RELATIME,\n                data: vec![],\n                rec_attr: None\n            },\n            mount_option_config,\n        );\n\n        // this case is just for coverage purpose\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .options(vec![\n                    \"defaults\".to_string(),\n                    \"ro\".to_string(),\n                    \"rw\".to_string(),\n                    \"suid\".to_string(),\n                    \"nosuid\".to_string(),\n                    \"dev\".to_string(),\n                    \"nodev\".to_string(),\n                    \"exec\".to_string(),\n                    \"noexec\".to_string(),\n                    \"sync\".to_string(),\n                    \"async\".to_string(),\n                    \"dirsync\".to_string(),\n                    \"remount\".to_string(),\n                    \"mand\".to_string(),\n                    \"nomand\".to_string(),\n                    \"atime\".to_string(),\n                    \"noatime\".to_string(),\n                    \"diratime\".to_string(),\n                    \"nodiratime\".to_string(),\n                    \"bind\".to_string(),\n                    \"rbind\".to_string(),\n                    \"unbindable\".to_string(),\n                    \"runbindable\".to_string(),\n                    \"private\".to_string(),\n                    \"rprivate\".to_string(),\n                    \"shared\".to_string(),\n                    \"rshared\".to_string(),\n                    \"slave\".to_string(),\n                    \"rslave\".to_string(),\n                    \"relatime\".to_string(),\n                    \"norelatime\".to_string(),\n                    \"strictatime\".to_string(),\n                    \"nostrictatime\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::MS_NOSUID\n                    | MsFlags::MS_NODEV\n                    | MsFlags::MS_NOEXEC\n                    | MsFlags::MS_REMOUNT\n                    | MsFlags::MS_DIRSYNC\n                    | MsFlags::MS_NOATIME\n                    | MsFlags::MS_NODIRATIME\n                    | MsFlags::MS_BIND\n                    | MsFlags::MS_UNBINDABLE,\n                data: vec![],\n                rec_attr: None,\n            },\n            mount_option_config\n        );\n\n        // this case is just for coverage purpose\n        let mount_option_config = parse_mount(\n            &MountBuilder::default()\n                .options(vec![\n                    \"rro\".to_string(),\n                    \"rrw\".to_string(),\n                    \"rnosuid\".to_string(),\n                    \"rsuid\".to_string(),\n                    \"rnodev\".to_string(),\n                    \"rdev\".to_string(),\n                    \"rnoexec\".to_string(),\n                    \"rexec\".to_string(),\n                    \"rnodiratime\".to_string(),\n                    \"rdiratime\".to_string(),\n                    \"rrelatime\".to_string(),\n                    \"rnorelatime\".to_string(),\n                    \"rnoatime\".to_string(),\n                    \"ratime\".to_string(),\n                    \"rstrictatime\".to_string(),\n                    \"rnostrictatime\".to_string(),\n                    \"rnosymfollow\".to_string(),\n                    \"rsymfollow\".to_string(),\n                ])\n                .build()?,\n        )?;\n        assert_eq!(\n            MountOptionConfig {\n                flags: MsFlags::empty(),\n                data: vec![],\n                rec_attr: Some(MountAttr::all())\n            },\n            mount_option_config\n        );\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/seccomp/fixture/config.json",
    "content": "{\n    \"ociVersion\": \"1.0.1-dev\",\n    \"process\": {\n        \"terminal\": false,\n        \"user\": {\n            \"uid\": 0,\n            \"gid\": 0\n        },\n        \"args\": [\n            \"helloworld\"\n        ],\n        \"env\": [\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"TERM=xterm\"\n        ],\n        \"cwd\": \"/\",\n        \"capabilities\": {\n            \"bounding\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"effective\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"inheritable\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"permitted\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"ambient\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ]\n        },\n        \"rlimits\": [\n            {\n                \"type\": \"RLIMIT_NOFILE\",\n                \"hard\": 1024,\n                \"soft\": 1024\n            }\n        ],\n        \"noNewPrivileges\": true\n    },\n    \"root\": {\n        \"path\": \"tests/assets/oci/helloworld/rootfs\"\n    },\n    \"hostname\": \"runc\",\n    \"mounts\": [\n        {\n            \"destination\": \"/proc\",\n            \"type\": \"proc\",\n            \"source\": \"proc\"\n        },\n        {\n            \"destination\": \"/dev\",\n            \"type\": \"tmpfs\",\n            \"source\": \"tmpfs\",\n            \"options\": [\n                \"nosuid\",\n                \"strictatime\",\n                \"mode=755\",\n                \"size=65536k\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/pts\",\n            \"type\": \"devpts\",\n            \"source\": \"devpts\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"newinstance\",\n                \"ptmxmode=0666\",\n                \"mode=0620\",\n                \"gid=5\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/shm\",\n            \"type\": \"tmpfs\",\n            \"source\": \"shm\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\",\n                \"mode=1777\",\n                \"size=65536k\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/mqueue\",\n            \"type\": \"mqueue\",\n            \"source\": \"mqueue\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\"\n            ]\n        },\n        {\n            \"destination\": \"/sys\",\n            \"type\": \"sysfs\",\n            \"source\": \"sysfs\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\",\n                \"ro\"\n            ]\n        },\n        {\n            \"destination\": \"/sys/fs/cgroup\",\n            \"type\": \"cgroup\",\n            \"source\": \"cgroup\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\",\n                \"relatime\",\n                \"ro\"\n            ]\n        }\n    ],\n    \"linux\": {\n        \"devices\": [\n            {\n                \"path\": \"/dev/kvm\",\n                \"type\": \"c\",\n                \"major\": 10,\n                \"minor\": 232,\n                \"fileMode\": 666,\n                \"uid\": 0,\n                \"gid\": 36\n            }\n        ],\n        \"seccomp\": {\n            \"defaultAction\": \"SCMP_ACT_ERRNO\",\n            \"defaultErrnoRet\": 1,\n            \"archMap\": [\n                {\n                    \"architecture\": \"SCMP_ARCH_X86_64\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_X86\",\n                        \"SCMP_ARCH_X32\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_AARCH64\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_ARM\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_MIPS64\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_MIPS\",\n                        \"SCMP_ARCH_MIPS64N32\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_MIPS64N32\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_MIPS\",\n                        \"SCMP_ARCH_MIPS64\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_MIPSEL64\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_MIPSEL\",\n                        \"SCMP_ARCH_MIPSEL64N32\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_MIPSEL64N32\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_MIPSEL\",\n                        \"SCMP_ARCH_MIPSEL64\"\n                    ]\n                },\n                {\n                    \"architecture\": \"SCMP_ARCH_S390X\",\n                    \"subArchitectures\": [\n                        \"SCMP_ARCH_S390\"\n                    ]\n                }\n            ],\n            \"syscalls\": [\n                {\n                    \"names\": [\n                        \"accept\",\n                        \"accept4\",\n                        \"access\",\n                        \"adjtimex\",\n                        \"alarm\",\n                        \"bind\",\n                        \"brk\",\n                        \"capget\",\n                        \"capset\",\n                        \"chdir\",\n                        \"chmod\",\n                        \"chown\",\n                        \"chown32\",\n                        \"clock_adjtime\",\n                        \"clock_adjtime64\",\n                        \"clock_getres\",\n                        \"clock_getres_time64\",\n                        \"clock_gettime\",\n                        \"clock_gettime64\",\n                        \"clock_nanosleep\",\n                        \"clock_nanosleep_time64\",\n                        \"close\",\n                        \"close_range\",\n                        \"connect\",\n                        \"copy_file_range\",\n                        \"creat\",\n                        \"dup\",\n                        \"dup2\",\n                        \"dup3\",\n                        \"epoll_create\",\n                        \"epoll_create1\",\n                        \"epoll_ctl\",\n                        \"epoll_ctl_old\",\n                        \"epoll_pwait\",\n                        \"epoll_pwait2\",\n                        \"epoll_wait\",\n                        \"epoll_wait_old\",\n                        \"eventfd\",\n                        \"eventfd2\",\n                        \"execve\",\n                        \"execveat\",\n                        \"exit\",\n                        \"exit_group\",\n                        \"faccessat\",\n                        \"faccessat2\",\n                        \"fadvise64\",\n                        \"fadvise64_64\",\n                        \"fallocate\",\n                        \"fanotify_mark\",\n                        \"fchdir\",\n                        \"fchmod\",\n                        \"fchmodat\",\n                        \"fchown\",\n                        \"fchown32\",\n                        \"fchownat\",\n                        \"fcntl\",\n                        \"fcntl64\",\n                        \"fdatasync\",\n                        \"fgetxattr\",\n                        \"flistxattr\",\n                        \"flock\",\n                        \"fork\",\n                        \"fremovexattr\",\n                        \"fsetxattr\",\n                        \"fstat\",\n                        \"fstat64\",\n                        \"fstatat64\",\n                        \"fstatfs\",\n                        \"fstatfs64\",\n                        \"fsync\",\n                        \"ftruncate\",\n                        \"ftruncate64\",\n                        \"futex\",\n                        \"futex_time64\",\n                        \"futimesat\",\n                        \"getcpu\",\n                        \"getcwd\",\n                        \"getdents\",\n                        \"getdents64\",\n                        \"getegid\",\n                        \"getegid32\",\n                        \"geteuid\",\n                        \"geteuid32\",\n                        \"getgid\",\n                        \"getgid32\",\n                        \"getgroups\",\n                        \"getgroups32\",\n                        \"getitimer\",\n                        \"getpeername\",\n                        \"getpgid\",\n                        \"getpgrp\",\n                        \"getpid\",\n                        \"getppid\",\n                        \"getpriority\",\n                        \"getrandom\",\n                        \"getresgid\",\n                        \"getresgid32\",\n                        \"getresuid\",\n                        \"getresuid32\",\n                        \"getrlimit\",\n                        \"get_robust_list\",\n                        \"getrusage\",\n                        \"getsid\",\n                        \"getsockname\",\n                        \"getsockopt\",\n                        \"get_thread_area\",\n                        \"gettid\",\n                        \"gettimeofday\",\n                        \"getuid\",\n                        \"getuid32\",\n                        \"getxattr\",\n                        \"inotify_add_watch\",\n                        \"inotify_init\",\n                        \"inotify_init1\",\n                        \"inotify_rm_watch\",\n                        \"io_cancel\",\n                        \"ioctl\",\n                        \"io_destroy\",\n                        \"io_getevents\",\n                        \"io_pgetevents\",\n                        \"io_pgetevents_time64\",\n                        \"ioprio_get\",\n                        \"ioprio_set\",\n                        \"io_setup\",\n                        \"io_submit\",\n                        \"io_uring_enter\",\n                        \"io_uring_register\",\n                        \"io_uring_setup\",\n                        \"ipc\",\n                        \"kill\",\n                        \"lchown\",\n                        \"lchown32\",\n                        \"lgetxattr\",\n                        \"link\",\n                        \"linkat\",\n                        \"listen\",\n                        \"listxattr\",\n                        \"llistxattr\",\n                        \"_llseek\",\n                        \"lremovexattr\",\n                        \"lseek\",\n                        \"lsetxattr\",\n                        \"lstat\",\n                        \"lstat64\",\n                        \"madvise\",\n                        \"membarrier\",\n                        \"memfd_create\",\n                        \"mincore\",\n                        \"mkdir\",\n                        \"mkdirat\",\n                        \"mknod\",\n                        \"mknodat\",\n                        \"mlock\",\n                        \"mlock2\",\n                        \"mlockall\",\n                        \"mmap\",\n                        \"mmap2\",\n                        \"mprotect\",\n                        \"mq_getsetattr\",\n                        \"mq_notify\",\n                        \"mq_open\",\n                        \"mq_timedreceive\",\n                        \"mq_timedreceive_time64\",\n                        \"mq_timedsend\",\n                        \"mq_timedsend_time64\",\n                        \"mq_unlink\",\n                        \"mremap\",\n                        \"msgctl\",\n                        \"msgget\",\n                        \"msgrcv\",\n                        \"msgsnd\",\n                        \"msync\",\n                        \"munlock\",\n                        \"munlockall\",\n                        \"munmap\",\n                        \"nanosleep\",\n                        \"newfstatat\",\n                        \"_newselect\",\n                        \"open\",\n                        \"openat\",\n                        \"openat2\",\n                        \"pause\",\n                        \"pidfd_open\",\n                        \"pidfd_send_signal\",\n                        \"pipe\",\n                        \"pipe2\",\n                        \"poll\",\n                        \"ppoll\",\n                        \"ppoll_time64\",\n                        \"prctl\",\n                        \"pread64\",\n                        \"preadv\",\n                        \"preadv2\",\n                        \"prlimit64\",\n                        \"pselect6\",\n                        \"pselect6_time64\",\n                        \"pwrite64\",\n                        \"pwritev\",\n                        \"pwritev2\",\n                        \"read\",\n                        \"readahead\",\n                        \"readlink\",\n                        \"readlinkat\",\n                        \"readv\",\n                        \"recv\",\n                        \"recvfrom\",\n                        \"recvmmsg\",\n                        \"recvmmsg_time64\",\n                        \"recvmsg\",\n                        \"remap_file_pages\",\n                        \"removexattr\",\n                        \"rename\",\n                        \"renameat\",\n                        \"renameat2\",\n                        \"restart_syscall\",\n                        \"rmdir\",\n                        \"rseq\",\n                        \"rt_sigaction\",\n                        \"rt_sigpending\",\n                        \"rt_sigprocmask\",\n                        \"rt_sigqueueinfo\",\n                        \"rt_sigreturn\",\n                        \"rt_sigsuspend\",\n                        \"rt_sigtimedwait\",\n                        \"rt_sigtimedwait_time64\",\n                        \"rt_tgsigqueueinfo\",\n                        \"sched_getaffinity\",\n                        \"sched_getattr\",\n                        \"sched_getparam\",\n                        \"sched_get_priority_max\",\n                        \"sched_get_priority_min\",\n                        \"sched_getscheduler\",\n                        \"sched_rr_get_interval\",\n                        \"sched_rr_get_interval_time64\",\n                        \"sched_setaffinity\",\n                        \"sched_setattr\",\n                        \"sched_setparam\",\n                        \"sched_setscheduler\",\n                        \"sched_yield\",\n                        \"seccomp\",\n                        \"select\",\n                        \"semctl\",\n                        \"semget\",\n                        \"semop\",\n                        \"semtimedop\",\n                        \"semtimedop_time64\",\n                        \"send\",\n                        \"sendfile\",\n                        \"sendfile64\",\n                        \"sendmmsg\",\n                        \"sendmsg\",\n                        \"sendto\",\n                        \"setfsgid\",\n                        \"setfsgid32\",\n                        \"setfsuid\",\n                        \"setfsuid32\",\n                        \"setgid\",\n                        \"setgid32\",\n                        \"setgroups\",\n                        \"setgroups32\",\n                        \"setitimer\",\n                        \"setpgid\",\n                        \"setpriority\",\n                        \"setregid\",\n                        \"setregid32\",\n                        \"setresgid\",\n                        \"setresgid32\",\n                        \"setresuid\",\n                        \"setresuid32\",\n                        \"setreuid\",\n                        \"setreuid32\",\n                        \"setrlimit\",\n                        \"set_robust_list\",\n                        \"setsid\",\n                        \"setsockopt\",\n                        \"set_thread_area\",\n                        \"set_tid_address\",\n                        \"setuid\",\n                        \"setuid32\",\n                        \"setxattr\",\n                        \"shmat\",\n                        \"shmctl\",\n                        \"shmdt\",\n                        \"shmget\",\n                        \"shutdown\",\n                        \"sigaltstack\",\n                        \"signalfd\",\n                        \"signalfd4\",\n                        \"sigprocmask\",\n                        \"sigreturn\",\n                        \"socket\",\n                        \"socketcall\",\n                        \"socketpair\",\n                        \"splice\",\n                        \"stat\",\n                        \"stat64\",\n                        \"statfs\",\n                        \"statfs64\",\n                        \"statx\",\n                        \"symlink\",\n                        \"symlinkat\",\n                        \"sync\",\n                        \"sync_file_range\",\n                        \"syncfs\",\n                        \"sysinfo\",\n                        \"tee\",\n                        \"tgkill\",\n                        \"time\",\n                        \"timer_create\",\n                        \"timer_delete\",\n                        \"timer_getoverrun\",\n                        \"timer_gettime\",\n                        \"timer_gettime64\",\n                        \"timer_settime\",\n                        \"timer_settime64\",\n                        \"timerfd_create\",\n                        \"timerfd_gettime\",\n                        \"timerfd_gettime64\",\n                        \"timerfd_settime\",\n                        \"timerfd_settime64\",\n                        \"times\",\n                        \"tkill\",\n                        \"truncate\",\n                        \"truncate64\",\n                        \"ugetrlimit\",\n                        \"umask\",\n                        \"uname\",\n                        \"unlink\",\n                        \"unlinkat\",\n                        \"utime\",\n                        \"utimensat\",\n                        \"utimensat_time64\",\n                        \"utimes\",\n                        \"vfork\",\n                        \"vmsplice\",\n                        \"wait4\",\n                        \"waitid\",\n                        \"waitpid\",\n                        \"write\",\n                        \"writev\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\"\n                },\n                {\n                    \"names\": [\n                        \"process_vm_readv\",\n                        \"process_vm_writev\",\n                        \"ptrace\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"minKernel\": \"4.8\"\n                    }\n                },\n                {\n                    \"names\": [\n                        \"personality\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 0,\n                            \"value\": 0,\n                            \"op\": \"SCMP_CMP_EQ\"\n                        },\n                        {\n                            \"index\": 0,\n                            \"value\": 8,\n                            \"op\": \"SCMP_CMP_EQ\"\n                        }\n                    ]\n                },\n                {\n                    \"names\": [\n                        \"personality\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 0,\n                            \"value\": 131072,\n                            \"op\": \"SCMP_CMP_EQ\"\n                        }\n                    ]\n                },\n                {\n                    \"names\": [\n                        \"personality\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 0,\n                            \"value\": 131080,\n                            \"op\": \"SCMP_CMP_EQ\"\n                        }\n                    ]\n                },\n                {\n                    \"names\": [\n                        \"personality\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 0,\n                            \"value\": 4294967295,\n                            \"op\": \"SCMP_CMP_EQ\"\n                        }\n                    ]\n                },\n                {\n                    \"names\": [\n                        \"sync_file_range2\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"ppc64le\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"arm_fadvise64_64\",\n                        \"arm_sync_file_range\",\n                        \"sync_file_range2\",\n                        \"breakpoint\",\n                        \"cacheflush\",\n                        \"set_tls\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"arm\",\n                            \"arm64\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"arch_prctl\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"amd64\",\n                            \"x32\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"modify_ldt\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"amd64\",\n                            \"x32\",\n                            \"x86\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"s390_pci_mmio_read\",\n                        \"s390_pci_mmio_write\",\n                        \"s390_runtime_instr\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"s390\",\n                            \"s390x\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"open_by_handle_at\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_DAC_READ_SEARCH\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"bpf\",\n                        \"clone\",\n                        \"clone3\",\n                        \"fanotify_init\",\n                        \"fsconfig\",\n                        \"fsmount\",\n                        \"fsopen\",\n                        \"fspick\",\n                        \"lookup_dcookie\",\n                        \"mount\",\n                        \"move_mount\",\n                        \"name_to_handle_at\",\n                        \"open_tree\",\n                        \"perf_event_open\",\n                        \"quotactl\",\n                        \"setdomainname\",\n                        \"sethostname\",\n                        \"setns\",\n                        \"syslog\",\n                        \"umount\",\n                        \"umount2\",\n                        \"unshare\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_ADMIN\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"clone\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 0,\n                            \"value\": 2114060288,\n                            \"op\": \"SCMP_CMP_MASKED_EQ\"\n                        }\n                    ],\n                    \"excludes\": {\n                        \"caps\": [\n                            \"CAP_SYS_ADMIN\"\n                        ],\n                        \"arches\": [\n                            \"s390\",\n                            \"s390x\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"clone\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"args\": [\n                        {\n                            \"index\": 1,\n                            \"value\": 2114060288,\n                            \"op\": \"SCMP_CMP_MASKED_EQ\"\n                        }\n                    ],\n                    \"comment\": \"s390 parameter ordering for clone is different\",\n                    \"includes\": {\n                        \"arches\": [\n                            \"s390\",\n                            \"s390x\"\n                        ]\n                    },\n                    \"excludes\": {\n                        \"caps\": [\n                            \"CAP_SYS_ADMIN\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"clone3\"\n                    ],\n                    \"action\": \"SCMP_ACT_ERRNO\",\n                    \"errnoRet\": 38,\n                    \"excludes\": {\n                        \"caps\": [\n                            \"CAP_SYS_ADMIN\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"reboot\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_BOOT\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"chroot\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_CHROOT\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"delete_module\",\n                        \"init_module\",\n                        \"finit_module\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_MODULE\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"acct\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_PACCT\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"kcmp\",\n                        \"pidfd_getfd\",\n                        \"process_madvise\",\n                        \"process_vm_readv\",\n                        \"process_vm_writev\",\n                        \"ptrace\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_PTRACE\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"iopl\",\n                        \"ioperm\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_RAWIO\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"settimeofday\",\n                        \"stime\",\n                        \"clock_settime\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_TIME\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"vhangup\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_TTY_CONFIG\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"get_mempolicy\",\n                        \"mbind\",\n                        \"set_mempolicy\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYS_NICE\"\n                        ]\n                    }\n                },\n                {\n                    \"names\": [\n                        \"syslog\"\n                    ],\n                    \"action\": \"SCMP_ACT_ALLOW\",\n                    \"includes\": {\n                        \"caps\": [\n                            \"CAP_SYSLOG\"\n                        ]\n                    }\n                }\n            ]\n        },\n        \"resources\": {\n            \"devices\": [\n                {\n                    \"allow\": true,\n                    \"access\": \"rwm\"\n                }\n            ]\n        },\n        \"uidMappings\": [\n            {\n                \"containerID\": 0,\n                \"hostID\": 1000,\n                \"size\": 1\n            }\n        ],\n        \"gidMappings\": [\n            {\n                \"containerID\": 0,\n                \"hostID\": 1000,\n                \"size\": 1\n            }\n        ],\n        \"namespaces\": [\n            {\n                \"type\": \"pid\"\n            },\n            {\n                \"type\": \"network\"\n            },\n            {\n                \"type\": \"user\"\n            },\n            {\n                \"type\": \"ipc\"\n            },\n            {\n                \"type\": \"uts\"\n            },\n            {\n                \"type\": \"mount\"\n            }\n        ],\n        \"maskedPaths\": [\n            \"/proc/kcore\",\n            \"/proc/latency_stats\",\n            \"/proc/timer_list\",\n            \"/proc/timer_stats\",\n            \"/proc/sched_debug\",\n            \"/sys/firmware\",\n            \"/proc/scsi\"\n        ],\n        \"readonlyPaths\": [\n            \"/proc/asound\",\n            \"/proc/bus\",\n            \"/proc/fs\",\n            \"/proc/irq\",\n            \"/proc/sys\",\n            \"/proc/sysrq-trigger\"\n        ]\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/seccomp/mod.rs",
    "content": "use std::num::TryFromIntError;\nuse std::os::unix::io;\n\nuse libseccomp::{\n    ScmpAction, ScmpArch, ScmpArgCompare, ScmpCompareOp, ScmpFilterContext, ScmpSyscall,\n};\nuse oci_spec::runtime::{\n    Arch, LinuxSeccomp, LinuxSeccompAction, LinuxSeccompFilterFlag, LinuxSeccompOperator,\n};\n\n#[derive(Debug, thiserror::Error)]\npub enum SeccompError {\n    #[error(\"failed to translate trace action due to failed to convert errno {errno} into i16\")]\n    TraceAction { source: TryFromIntError, errno: i32 },\n    #[error(\"SCMP_ACT_NOTIFY cannot be used as default action\")]\n    NotifyAsDefaultAction,\n    #[error(\"SCMP_ACT_NOTIFY cannot be used for the write syscall\")]\n    NotifyWriteSyscall,\n    #[error(\"failed to add arch to seccomp\")]\n    AddArch {\n        source: libseccomp::error::SeccompError,\n        arch: Arch,\n    },\n    #[error(\"failed to load seccomp context\")]\n    LoadContext {\n        source: libseccomp::error::SeccompError,\n    },\n    #[error(\"failed to get seccomp notify id\")]\n    GetNotifyId {\n        source: libseccomp::error::SeccompError,\n    },\n    #[error(\"failed to add rule to seccomp\")]\n    AddRule {\n        source: libseccomp::error::SeccompError,\n    },\n    #[error(\"failed to create new seccomp filter\")]\n    NewFilter {\n        source: libseccomp::error::SeccompError,\n        default: LinuxSeccompAction,\n    },\n    #[error(\"failed to set filter flag\")]\n    SetFilterFlag {\n        source: libseccomp::error::SeccompError,\n        flag: LinuxSeccompFilterFlag,\n    },\n    #[error(\"failed to set SCMP_FLTATR_CTL_NNP\")]\n    SetCtlNnp {\n        source: libseccomp::error::SeccompError,\n    },\n}\n\ntype Result<T> = std::result::Result<T, SeccompError>;\n\nfn translate_arch(arch: Arch) -> ScmpArch {\n    match arch {\n        Arch::ScmpArchNative => ScmpArch::Native,\n        Arch::ScmpArchX86 => ScmpArch::X86,\n        Arch::ScmpArchX86_64 => ScmpArch::X8664,\n        Arch::ScmpArchX32 => ScmpArch::X32,\n        Arch::ScmpArchArm => ScmpArch::Arm,\n        Arch::ScmpArchAarch64 => ScmpArch::Aarch64,\n        Arch::ScmpArchMips => ScmpArch::Mips,\n        Arch::ScmpArchMips64 => ScmpArch::Mips64,\n        Arch::ScmpArchMips64n32 => ScmpArch::Mips64N32,\n        Arch::ScmpArchMipsel => ScmpArch::Mipsel,\n        Arch::ScmpArchMipsel64 => ScmpArch::Mipsel64,\n        Arch::ScmpArchMipsel64n32 => ScmpArch::Mipsel64N32,\n        Arch::ScmpArchPpc => ScmpArch::Ppc,\n        Arch::ScmpArchPpc64 => ScmpArch::Ppc64,\n        Arch::ScmpArchPpc64le => ScmpArch::Ppc64Le,\n        Arch::ScmpArchS390 => ScmpArch::S390,\n        Arch::ScmpArchS390x => ScmpArch::S390X,\n        Arch::ScmpArchRiscv64 => ScmpArch::Riscv64,\n    }\n}\n\nfn translate_action(action: LinuxSeccompAction, errno: Option<u32>) -> Result<ScmpAction> {\n    tracing::trace!(?action, ?errno, \"translating action\");\n    let errno = errno.map(|e| e as i32).unwrap_or(libc::EPERM);\n    let action = match action {\n        LinuxSeccompAction::ScmpActKill => ScmpAction::KillThread,\n        LinuxSeccompAction::ScmpActTrap => ScmpAction::Trap,\n        LinuxSeccompAction::ScmpActErrno => ScmpAction::Errno(errno),\n        LinuxSeccompAction::ScmpActTrace => ScmpAction::Trace(\n            errno\n                .try_into()\n                .map_err(|err| SeccompError::TraceAction { source: err, errno })?,\n        ),\n        LinuxSeccompAction::ScmpActAllow => ScmpAction::Allow,\n        LinuxSeccompAction::ScmpActKillProcess => ScmpAction::KillProcess,\n        LinuxSeccompAction::ScmpActNotify => ScmpAction::Notify,\n        LinuxSeccompAction::ScmpActLog => ScmpAction::Log,\n        LinuxSeccompAction::ScmpActKillThread => ScmpAction::KillThread,\n    };\n\n    tracing::trace!(?action, \"translated action\");\n    Ok(action)\n}\n\nfn translate_op(op: LinuxSeccompOperator, datum_b: Option<u64>) -> ScmpCompareOp {\n    match op {\n        LinuxSeccompOperator::ScmpCmpNe => ScmpCompareOp::NotEqual,\n        LinuxSeccompOperator::ScmpCmpLt => ScmpCompareOp::Less,\n        LinuxSeccompOperator::ScmpCmpLe => ScmpCompareOp::LessOrEqual,\n        LinuxSeccompOperator::ScmpCmpEq => ScmpCompareOp::Equal,\n        LinuxSeccompOperator::ScmpCmpGe => ScmpCompareOp::GreaterEqual,\n        LinuxSeccompOperator::ScmpCmpGt => ScmpCompareOp::Greater,\n        LinuxSeccompOperator::ScmpCmpMaskedEq => ScmpCompareOp::MaskedEqual(datum_b.unwrap_or(0)),\n    }\n}\n\nfn check_seccomp(seccomp: &LinuxSeccomp) -> Result<()> {\n    // We don't support notify as default action. After the seccomp filter is\n    // created with notify, the container process will have to communicate the\n    // returned fd to another process. Therefore, we need the write syscall or\n    // otherwise, the write syscall will be block by the seccomp filter causing\n    // the container process to hang. `runc` also disallow notify as default\n    // action.\n    // Note: read and close syscall are also used, because if we can\n    // successfully write fd to another process, the other process can choose to\n    // handle read/close syscall and allow read and close to proceed as\n    // expected.\n    if seccomp.default_action() == LinuxSeccompAction::ScmpActNotify {\n        return Err(SeccompError::NotifyAsDefaultAction);\n    }\n\n    if let Some(syscalls) = seccomp.syscalls() {\n        for syscall in syscalls {\n            if syscall.action() == LinuxSeccompAction::ScmpActNotify {\n                for name in syscall.names() {\n                    if name == \"write\" {\n                        return Err(SeccompError::NotifyWriteSyscall);\n                    }\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\n#[tracing::instrument(level = \"trace\", skip(seccomp))]\npub fn initialize_seccomp(seccomp: &LinuxSeccomp) -> Result<Option<io::RawFd>> {\n    check_seccomp(seccomp)?;\n\n    tracing::trace!(default_action = ?seccomp.default_action(), errno = ?seccomp.default_errno_ret(), \"initializing seccomp\");\n    let default_action = translate_action(seccomp.default_action(), seccomp.default_errno_ret())?;\n    let mut ctx =\n        ScmpFilterContext::new(default_action).map_err(|err| SeccompError::NewFilter {\n            source: err,\n            default: seccomp.default_action(),\n        })?;\n\n    if let Some(flags) = seccomp.flags() {\n        for flag in flags {\n            match flag {\n                LinuxSeccompFilterFlag::SeccompFilterFlagLog => ctx.set_ctl_log(true),\n                LinuxSeccompFilterFlag::SeccompFilterFlagTsync => ctx.set_ctl_tsync(true),\n                LinuxSeccompFilterFlag::SeccompFilterFlagSpecAllow => ctx.set_ctl_ssb(true),\n                LinuxSeccompFilterFlag::SeccompFilterFlagWaitKillableRecv => {\n                    ctx.set_ctl_waitkill(true)\n                }\n            }\n            .map_err(|err| SeccompError::SetFilterFlag {\n                source: err,\n                flag: *flag,\n            })?;\n        }\n    }\n\n    if let Some(architectures) = seccomp.architectures() {\n        for &arch in architectures {\n            tracing::trace!(?arch, \"adding architecture\");\n            ctx.add_arch(translate_arch(arch))\n                .map_err(|err| SeccompError::AddArch { source: err, arch })?;\n        }\n    }\n\n    // The SCMP_FLTATR_CTL_NNP controls if the seccomp load function will set\n    // the new privilege bit automatically in prctl. Normally this is a good\n    // thing, but for us we need better control. Based on the spec, if OCI\n    // runtime spec doesn't set the no new privileges in Process, we should not\n    // set it here.  If the seccomp load operation fails without enough\n    // privilege, so be it. To prevent this automatic behavior, we unset the\n    // value here.\n    ctx.set_ctl_nnp(false)\n        .map_err(|err| SeccompError::SetCtlNnp { source: err })?;\n\n    if let Some(syscalls) = seccomp.syscalls() {\n        for syscall in syscalls {\n            let action = translate_action(syscall.action(), syscall.errno_ret())?;\n            if action == default_action {\n                // When the action is the same as the default action, the rule is redundant. We can\n                // skip this here to avoid failing when we add the rules.\n                tracing::warn!(\n                    \"detect a seccomp action that is the same as the default action: {:?}\",\n                    syscall\n                );\n                continue;\n            }\n\n            for name in syscall.names() {\n                let sc = match ScmpSyscall::from_name(name) {\n                    Ok(x) => x,\n                    Err(_) => {\n                        // If we failed to resolve the syscall by name, likely the kernel\n                        // doeesn't support this syscall. So it is safe to skip...\n                        tracing::warn!(\n                            \"failed to resolve syscall, likely kernel doesn't support this. {:?}\",\n                            name\n                        );\n                        continue;\n                    }\n                };\n                match syscall.args() {\n                    Some(args) => {\n                        // The `seccomp_rule_add` requires us to break multiple\n                        // args attaching to the same rules into multiple rules.\n                        // Breaking this rule will cause `seccomp_rule_add` to\n                        // return EINVAL.\n                        //\n                        // From the man page: when adding syscall argument\n                        // comparisons to the filter it is important to remember\n                        // that while it is possible to have multiple\n                        // comparisons in a single rule, you can only compare\n                        // each argument once in a single rule.  In other words,\n                        // you can not have multiple comparisons of the 3rd\n                        // syscall argument in a single rule.\n                        for arg in args {\n                            let cmp = ScmpArgCompare::new(\n                                arg.index() as u32,\n                                translate_op(arg.op(), arg.value_two()),\n                                arg.value(),\n                            );\n                            tracing::trace!(?name, ?action, ?arg, \"add seccomp conditional rule\");\n                            ctx.add_rule_conditional(action, sc, &[cmp])\n                                .map_err(|err| {\n                                    tracing::error!(\n                                        \"failed to add seccomp action: {:?}. Cmp: {:?} Syscall: {name}\", &action, cmp,\n                                    );\n                                    SeccompError::AddRule {\n                                        source: err,\n                                    }\n                                })?;\n                        }\n                    }\n                    None => {\n                        tracing::trace!(?name, ?action, \"add seccomp rule\");\n                        ctx.add_rule(action, sc).map_err(|err| {\n                            tracing::error!(\n                                \"failed to add seccomp rule: {:?}. Syscall: {name}\",\n                                &sc\n                            );\n                            SeccompError::AddRule { source: err }\n                        })?;\n                    }\n                }\n            }\n        }\n    }\n\n    // In order to use the SECCOMP_SET_MODE_FILTER operation, either the calling\n    // thread must have the CAP_SYS_ADMIN capability in its user namespace, or\n    // the thread must already have the no_new_privs bit set.\n    // Ref: https://man7.org/linux/man-pages/man2/seccomp.2.html\n    ctx.load()\n        .map_err(|err| SeccompError::LoadContext { source: err })?;\n\n    let fd = if is_notify(seccomp) {\n        Some(\n            ctx.get_notify_fd()\n                .map_err(|err| SeccompError::GetNotifyId { source: err })?,\n        )\n    } else {\n        None\n    };\n\n    Ok(fd)\n}\n\npub fn is_notify(seccomp: &LinuxSeccomp) -> bool {\n    seccomp\n        .syscalls()\n        .iter()\n        .flatten()\n        .any(|syscall| syscall.action() == LinuxSeccompAction::ScmpActNotify)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path;\n\n    use anyhow::{Context, Result};\n    use oci_spec::runtime::{Arch, LinuxSeccompBuilder, LinuxSyscallBuilder};\n    use serial_test::serial;\n\n    use super::*;\n    use crate::test_utils::{self, TestCallbackError};\n\n    #[test]\n    #[serial]\n    fn test_basic() -> Result<()> {\n        // Note: seccomp profile is really hard to write unit test for. First,\n        // we can't really test default error or kill action, since rust test\n        // actually relies on certain syscalls. Second, some of the syscall will\n        // not return errorno. These syscalls will just send an abort signal or\n        // even just segfaults.  Here we choose to use `getcwd` syscall for\n        // testing, since it will correctly return an error under seccomp rule.\n        // This is more of a sanity check.\n\n        // Here, we choose an error that getcwd call would never return on its own, so\n        // we can make sure that getcwd failed because of seccomp rule.\n        let expect_error = libc::EAGAIN;\n\n        let syscall = LinuxSyscallBuilder::default()\n            .names(vec![String::from(\"getcwd\")])\n            .action(LinuxSeccompAction::ScmpActErrno)\n            .errno_ret(expect_error as u32)\n            .build()?;\n        let seccomp_profile = LinuxSeccompBuilder::default()\n            .default_action(LinuxSeccompAction::ScmpActAllow)\n            .architectures(vec![Arch::ScmpArchNative])\n            .syscalls(vec![syscall])\n            .build()?;\n\n        test_utils::test_in_child_process(|| {\n            let _ = prctl::set_no_new_privileges(true);\n            initialize_seccomp(&seccomp_profile).expect(\"failed to initialize seccomp\");\n            let ret = nix::unistd::getcwd();\n            if ret.is_ok() {\n                Err(TestCallbackError::Custom(\n                    \"getcwd didn't error out as seccomp profile specified\".to_string(),\n                ))?;\n            }\n\n            if let Some(errno) = ret.err() {\n                if errno != nix::errno::Errno::from_raw(expect_error) {\n                    Err(TestCallbackError::Custom(format!(\n                        \"getcwd failed but we didn't get the expected error from seccomp profile: {}\",\n                        errno\n                    )))?;\n                }\n            }\n\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_moby() -> Result<()> {\n        let fixture_path =\n            path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"src/seccomp/fixture/config.json\");\n        let spec = oci_spec::runtime::Spec::load(fixture_path)\n            .context(\"Failed to load test spec for seccomp\")?;\n\n        // We know linux and seccomp exist, so let's just unwrap.\n        let seccomp_profile = spec.linux().as_ref().unwrap().seccomp().as_ref().unwrap();\n        test_utils::test_in_child_process(|| {\n            let _ = prctl::set_no_new_privileges(true);\n            initialize_seccomp(seccomp_profile).expect(\"failed to initialize seccomp\");\n\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_seccomp_notify() -> Result<()> {\n        let syscall = LinuxSyscallBuilder::default()\n            .names(vec![String::from(\"getcwd\")])\n            .action(LinuxSeccompAction::ScmpActNotify)\n            .build()?;\n        let seccomp_profile = LinuxSeccompBuilder::default()\n            .default_action(LinuxSeccompAction::ScmpActAllow)\n            .architectures(vec![Arch::ScmpArchNative])\n            .syscalls(vec![syscall])\n            .build()?;\n        test_utils::test_in_child_process(|| {\n            let _ = prctl::set_no_new_privileges(true);\n            let fd =\n                initialize_seccomp(&seccomp_profile).expect(\"failed to initialize seccomp profile\");\n            if fd.is_none() {\n                Err(TestCallbackError::Custom(\n                    \"failed to get a seccomp notify fd with notify seccomp profile\".to_string(),\n                ))?;\n            }\n\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/signal.rs",
    "content": "//! Returns *nix signal enum value from passed string\n\nuse std::convert::TryFrom;\n\nuse nix::sys::signal::Signal as NixSignal;\n\n/// POSIX Signal\n#[derive(Debug)]\npub struct Signal(NixSignal);\n\n#[derive(Debug, thiserror::Error)]\npub enum SignalError<T> {\n    #[error(\"invalid signal: {0}\")]\n    InvalidSignal(T),\n}\n\nimpl TryFrom<&str> for Signal {\n    type Error = SignalError<String>;\n\n    fn try_from(s: &str) -> Result<Self, Self::Error> {\n        use NixSignal::*;\n\n        Ok(Signal(match s.to_ascii_uppercase().as_str() {\n            \"1\" | \"HUP\" | \"SIGHUP\" => SIGHUP,\n            \"2\" | \"INT\" | \"SIGINT\" => SIGINT,\n            \"3\" | \"QUIT\" | \"SIGQUIT\" => SIGQUIT,\n            \"4\" | \"ILL\" | \"SIGILL\" => SIGILL,\n            \"5\" | \"BUS\" | \"SIGBUS\" => SIGBUS,\n            \"6\" | \"ABRT\" | \"IOT\" | \"SIGABRT\" | \"SIGIOT\" => SIGABRT,\n            \"7\" | \"TRAP\" | \"SIGTRAP\" => SIGTRAP,\n            \"8\" | \"FPE\" | \"SIGFPE\" => SIGFPE,\n            \"9\" | \"KILL\" | \"SIGKILL\" => SIGKILL,\n            \"10\" | \"USR1\" | \"SIGUSR1\" => SIGUSR1,\n            \"11\" | \"SEGV\" | \"SIGSEGV\" => SIGSEGV,\n            \"12\" | \"USR2\" | \"SIGUSR2\" => SIGUSR2,\n            \"13\" | \"PIPE\" | \"SIGPIPE\" => SIGPIPE,\n            \"14\" | \"ALRM\" | \"SIGALRM\" => SIGALRM,\n            \"15\" | \"TERM\" | \"SIGTERM\" => SIGTERM,\n            \"16\" | \"STKFLT\" | \"SIGSTKFLT\" => SIGSTKFLT,\n            \"17\" | \"CHLD\" | \"SIGCHLD\" => SIGCHLD,\n            \"18\" | \"CONT\" | \"SIGCONT\" => SIGCONT,\n            \"19\" | \"STOP\" | \"SIGSTOP\" => SIGSTOP,\n            \"20\" | \"TSTP\" | \"SIGTSTP\" => SIGTSTP,\n            \"21\" | \"TTIN\" | \"SIGTTIN\" => SIGTTIN,\n            \"22\" | \"TTOU\" | \"SIGTTOU\" => SIGTTOU,\n            \"23\" | \"URG\" | \"SIGURG\" => SIGURG,\n            \"24\" | \"XCPU\" | \"SIGXCPU\" => SIGXCPU,\n            \"25\" | \"XFSZ\" | \"SIGXFSZ\" => SIGXFSZ,\n            \"26\" | \"VTALRM\" | \"SIGVTALRM\" => SIGVTALRM,\n            \"27\" | \"PROF\" | \"SIGPROF\" => SIGPROF,\n            \"28\" | \"WINCH\" | \"SIGWINCH\" => SIGWINCH,\n            \"29\" | \"IO\" | \"SIGIO\" => SIGIO,\n            \"30\" | \"PWR\" | \"SIGPWR\" => SIGPWR,\n            \"31\" | \"SYS\" | \"SIGSYS\" => SIGSYS,\n            _ => return Err(SignalError::InvalidSignal(s.to_string())),\n        }))\n    }\n}\n\nimpl TryFrom<i32> for Signal {\n    type Error = SignalError<i32>;\n\n    fn try_from(value: i32) -> Result<Self, Self::Error> {\n        NixSignal::try_from(value)\n            .map_err(|_| SignalError::InvalidSignal(value))\n            .map(Signal)\n    }\n}\n\nimpl From<NixSignal> for Signal {\n    fn from(s: NixSignal) -> Self {\n        Signal(s)\n    }\n}\n\nimpl Signal {\n    pub(crate) fn into_raw(self) -> NixSignal {\n        self.0\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashMap;\n\n    use nix::sys::signal::Signal::*;\n\n    use super::*;\n\n    #[test]\n    fn test_conversion_from_string() {\n        let mut test_sets = HashMap::new();\n        test_sets.insert(SIGHUP, vec![\"1\", \"HUP\", \"SIGHUP\"]);\n        test_sets.insert(SIGINT, vec![\"2\", \"INT\", \"SIGINT\"]);\n        test_sets.insert(SIGQUIT, vec![\"3\", \"QUIT\", \"SIGQUIT\"]);\n        test_sets.insert(SIGILL, vec![\"4\", \"ILL\", \"SIGILL\"]);\n        test_sets.insert(SIGBUS, vec![\"5\", \"BUS\", \"SIGBUS\"]);\n        test_sets.insert(SIGABRT, vec![\"6\", \"ABRT\", \"IOT\", \"SIGABRT\", \"SIGIOT\"]);\n        test_sets.insert(SIGTRAP, vec![\"7\", \"TRAP\", \"SIGTRAP\"]);\n        test_sets.insert(SIGFPE, vec![\"8\", \"FPE\", \"SIGFPE\"]);\n        test_sets.insert(SIGKILL, vec![\"9\", \"KILL\", \"SIGKILL\"]);\n        test_sets.insert(SIGUSR1, vec![\"10\", \"USR1\", \"SIGUSR1\"]);\n        test_sets.insert(SIGSEGV, vec![\"11\", \"SEGV\", \"SIGSEGV\"]);\n        test_sets.insert(SIGUSR2, vec![\"12\", \"USR2\", \"SIGUSR2\"]);\n        test_sets.insert(SIGPIPE, vec![\"13\", \"PIPE\", \"SIGPIPE\"]);\n        test_sets.insert(SIGALRM, vec![\"14\", \"ALRM\", \"SIGALRM\"]);\n        test_sets.insert(SIGTERM, vec![\"15\", \"TERM\", \"SIGTERM\"]);\n        test_sets.insert(SIGSTKFLT, vec![\"16\", \"STKFLT\", \"SIGSTKFLT\"]);\n        test_sets.insert(SIGCHLD, vec![\"17\", \"CHLD\", \"SIGCHLD\"]);\n        test_sets.insert(SIGCONT, vec![\"18\", \"CONT\", \"SIGCONT\"]);\n        test_sets.insert(SIGSTOP, vec![\"19\", \"STOP\", \"SIGSTOP\"]);\n        test_sets.insert(SIGTSTP, vec![\"20\", \"TSTP\", \"SIGTSTP\"]);\n        test_sets.insert(SIGTTIN, vec![\"21\", \"TTIN\", \"SIGTTIN\"]);\n        test_sets.insert(SIGTTOU, vec![\"22\", \"TTOU\", \"SIGTTOU\"]);\n        test_sets.insert(SIGURG, vec![\"23\", \"URG\", \"SIGURG\"]);\n        test_sets.insert(SIGXCPU, vec![\"24\", \"XCPU\", \"SIGXCPU\"]);\n        test_sets.insert(SIGXFSZ, vec![\"25\", \"XFSZ\", \"SIGXFSZ\"]);\n        test_sets.insert(SIGVTALRM, vec![\"26\", \"VTALRM\", \"SIGVTALRM\"]);\n        test_sets.insert(SIGPROF, vec![\"27\", \"PROF\", \"SIGPROF\"]);\n        test_sets.insert(SIGWINCH, vec![\"28\", \"WINCH\", \"SIGWINCH\"]);\n        test_sets.insert(SIGIO, vec![\"29\", \"IO\", \"SIGIO\"]);\n        test_sets.insert(SIGPWR, vec![\"30\", \"PWR\", \"SIGPWR\"]);\n        test_sets.insert(SIGSYS, vec![\"31\", \"SYS\", \"SIGSYS\"]);\n        for (signal, strings) in test_sets {\n            for s in strings {\n                assert_eq!(signal, Signal::try_from(s).unwrap().into_raw());\n            }\n        }\n    }\n\n    #[test]\n    fn test_conversion_from_string_should_be_failed() {\n        assert!(Signal::try_from(\"invalid\").is_err())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/syscall/linux.rs",
    "content": "//! Implements Command trait for Linux systems\nuse std::any::Any;\nuse std::ffi::{CStr, CString, OsStr};\nuse std::os::fd::{BorrowedFd, FromRawFd, RawFd};\nuse std::os::unix::ffi::OsStrExt;\nuse std::os::unix::fs::symlink;\nuse std::os::unix::io::{AsRawFd, OwnedFd};\nuse std::path::Path;\nuse std::str::FromStr;\nuse std::sync::Arc;\nuse std::{mem, ptr};\n\nuse caps::{CapSet, CapsHashSet};\nuse libc::{c_char, setdomainname, uid_t};\nuse nix::dir::Dir;\nuse nix::fcntl;\nuse nix::fcntl::{OFlag, open};\nuse nix::mount::{MntFlags, MsFlags, mount, umount2};\nuse nix::sched::{CloneFlags, unshare};\nuse nix::sys::stat::{Mode, SFlag, mknod};\nuse nix::unistd::{Gid, Uid, chown, chroot, close, fchdir, pivot_root, sethostname};\nuse oci_spec::runtime::PosixRlimit;\nuse pathrs::flags::OpenFlags;\nuse pathrs::procfs::{ProcfsBase, ProcfsHandle};\n\nuse super::{Result, Syscall, SyscallError};\nuse crate::capabilities;\nuse crate::config::PersonalityDomain;\n\n// Flags used in mount_setattr(2).\n// see https://man7.org/linux/man-pages/man2/mount_setattr.2.html.\npub const AT_RECURSIVE: u32 = 0x00008000; // Change the mount properties of the entire mount tree.\npub const AT_EMPTY_PATH: u32 = 0x00001000;\n#[allow(non_upper_case_globals)]\npub const MOUNT_ATTR__ATIME: u64 = 0x00000070; // Setting on how atime should be updated.\npub const MOUNT_ATTR_RDONLY: u64 = 0x00000001;\npub const MOUNT_ATTR_NOSUID: u64 = 0x00000002;\npub const MOUNT_ATTR_NODEV: u64 = 0x00000004;\npub const MOUNT_ATTR_NOEXEC: u64 = 0x00000008;\npub const MOUNT_ATTR_RELATIME: u64 = 0x00000000;\npub const MOUNT_ATTR_NOATIME: u64 = 0x00000010;\npub const MOUNT_ATTR_STRICTATIME: u64 = 0x00000020;\npub const MOUNT_ATTR_NODIRATIME: u64 = 0x00000080;\npub const MOUNT_ATTR_NOSYMFOLLOW: u64 = 0x00200000;\npub const MOVE_MOUNT_F_EMPTY_PATH: u32 = 0x00000004;\npub const MOVE_MOUNT_T_EMPTY_PATH: u32 = 0x00000040;\n\n// The type of fsconfig() call made.\npub const FSCONFIG_SET_FLAG: u64 = 0;\npub const FSCONFIG_SET_STRING: u64 = 1;\npub const FSCONFIG_SET_BINARY: u64 = 2;\npub const FSCONFIG_SET_PATH: u64 = 3;\npub const FSCONFIG_SET_PATH_EMPTY: u64 = 4;\npub const FSCONFIG_SET_FD: u64 = 5;\npub const FSCONFIG_CMD_CREATE: u64 = 6;\npub const FSCONFIG_CMD_RECONFIGURE: u64 = 7;\npub const FSCONFIG_CMD_CREATE_EXCL: u64 = 8;\n\n/// Constants used by mount(2).\npub enum MountOption {\n    Defaults(bool, MsFlags),\n    Ro(bool, MsFlags),\n    Rw(bool, MsFlags),\n    Suid(bool, MsFlags),\n    Nosuid(bool, MsFlags),\n    Dev(bool, MsFlags),\n    Nodev(bool, MsFlags),\n    Exec(bool, MsFlags),\n    Noexec(bool, MsFlags),\n    Sync(bool, MsFlags),\n    Async(bool, MsFlags),\n    Dirsync(bool, MsFlags),\n    Remount(bool, MsFlags),\n    Mand(bool, MsFlags),\n    Nomand(bool, MsFlags),\n    Atime(bool, MsFlags),\n    Noatime(bool, MsFlags),\n    Diratime(bool, MsFlags),\n    Nodiratime(bool, MsFlags),\n    Bind(bool, MsFlags),\n    Rbind(bool, MsFlags),\n    Unbindable(bool, MsFlags),\n    Runbindable(bool, MsFlags),\n    Private(bool, MsFlags),\n    Rprivate(bool, MsFlags),\n    Shared(bool, MsFlags),\n    Rshared(bool, MsFlags),\n    Slave(bool, MsFlags),\n    Rslave(bool, MsFlags),\n    Relatime(bool, MsFlags),\n    Norelatime(bool, MsFlags),\n    Strictatime(bool, MsFlags),\n    Nostrictatime(bool, MsFlags),\n}\n\nimpl MountOption {\n    // Return all possible mount options\n    pub fn known_options() -> Vec<String> {\n        [\n            \"defaults\",\n            \"ro\",\n            \"rw\",\n            \"suid\",\n            \"nosuid\",\n            \"dev\",\n            \"nodev\",\n            \"exec\",\n            \"noexec\",\n            \"sync\",\n            \"async\",\n            \"dirsync\",\n            \"remount\",\n            \"mand\",\n            \"nomand\",\n            \"atime\",\n            \"noatime\",\n            \"diratime\",\n            \"nodiratime\",\n            \"bind\",\n            \"rbind\",\n            \"unbindable\",\n            \"runbindable\",\n            \"private\",\n            \"rprivate\",\n            \"shared\",\n            \"rshared\",\n            \"slave\",\n            \"rslave\",\n            \"relatime\",\n            \"norelatime\",\n            \"strictatime\",\n            \"nostrictatime\",\n        ]\n        .iter()\n        .map(|s| s.to_string())\n        .collect()\n    }\n}\n\nimpl FromStr for MountOption {\n    type Err = String;\n\n    fn from_str(option: &str) -> std::result::Result<Self, Self::Err> {\n        match option {\n            \"defaults\" => Ok(MountOption::Defaults(false, MsFlags::empty())),\n            \"ro\" => Ok(MountOption::Ro(false, MsFlags::MS_RDONLY)),\n            \"rw\" => Ok(MountOption::Rw(true, MsFlags::MS_RDONLY)),\n            \"suid\" => Ok(MountOption::Suid(true, MsFlags::MS_NOSUID)),\n            \"nosuid\" => Ok(MountOption::Nosuid(false, MsFlags::MS_NOSUID)),\n            \"dev\" => Ok(MountOption::Dev(true, MsFlags::MS_NODEV)),\n            \"nodev\" => Ok(MountOption::Nodev(false, MsFlags::MS_NODEV)),\n            \"exec\" => Ok(MountOption::Exec(true, MsFlags::MS_NOEXEC)),\n            \"noexec\" => Ok(MountOption::Noexec(false, MsFlags::MS_NOEXEC)),\n            \"sync\" => Ok(MountOption::Sync(false, MsFlags::MS_SYNCHRONOUS)),\n            \"async\" => Ok(MountOption::Async(true, MsFlags::MS_SYNCHRONOUS)),\n            \"dirsync\" => Ok(MountOption::Dirsync(false, MsFlags::MS_DIRSYNC)),\n            \"remount\" => Ok(MountOption::Remount(false, MsFlags::MS_REMOUNT)),\n            \"mand\" => Ok(MountOption::Mand(false, MsFlags::MS_MANDLOCK)),\n            \"nomand\" => Ok(MountOption::Nomand(true, MsFlags::MS_MANDLOCK)),\n            \"atime\" => Ok(MountOption::Atime(true, MsFlags::MS_NOATIME)),\n            \"noatime\" => Ok(MountOption::Noatime(false, MsFlags::MS_NOATIME)),\n            \"diratime\" => Ok(MountOption::Diratime(true, MsFlags::MS_NODIRATIME)),\n            \"nodiratime\" => Ok(MountOption::Nodiratime(false, MsFlags::MS_NODIRATIME)),\n            \"bind\" => Ok(MountOption::Bind(false, MsFlags::MS_BIND)),\n            \"rbind\" => Ok(MountOption::Rbind(\n                false,\n                MsFlags::MS_BIND | MsFlags::MS_REC,\n            )),\n            \"unbindable\" => Ok(MountOption::Unbindable(false, MsFlags::MS_UNBINDABLE)),\n            \"runbindable\" => Ok(MountOption::Runbindable(\n                false,\n                MsFlags::MS_UNBINDABLE | MsFlags::MS_REC,\n            )),\n            \"private\" => Ok(MountOption::Private(true, MsFlags::MS_PRIVATE)),\n            \"rprivate\" => Ok(MountOption::Rprivate(\n                true,\n                MsFlags::MS_PRIVATE | MsFlags::MS_REC,\n            )),\n            \"shared\" => Ok(MountOption::Shared(true, MsFlags::MS_SHARED)),\n            \"rshared\" => Ok(MountOption::Rshared(\n                true,\n                MsFlags::MS_SHARED | MsFlags::MS_REC,\n            )),\n            \"slave\" => Ok(MountOption::Slave(true, MsFlags::MS_SLAVE)),\n            \"rslave\" => Ok(MountOption::Rslave(\n                true,\n                MsFlags::MS_SLAVE | MsFlags::MS_REC,\n            )),\n            \"relatime\" => Ok(MountOption::Relatime(false, MsFlags::MS_RELATIME)),\n            \"norelatime\" => Ok(MountOption::Norelatime(true, MsFlags::MS_RELATIME)),\n            \"strictatime\" => Ok(MountOption::Strictatime(false, MsFlags::MS_STRICTATIME)),\n            \"nostrictatime\" => Ok(MountOption::Nostrictatime(true, MsFlags::MS_STRICTATIME)),\n            _ => Err(option.to_string()),\n        }\n    }\n}\n\n/// Constants used by mount_setattr(2).\npub enum MountRecursive {\n    /// Mount read-only.\n    Rdonly(bool, u64),\n\n    /// Ignore suid and sgid bits.\n    Nosuid(bool, u64),\n\n    /// Disallow access to device special files.\n    Nodev(bool, u64),\n\n    /// Disallow program execution.\n    Noexec(bool, u64),\n\n    /// Setting on how atime should be updated.\n    Atime(bool, u64),\n\n    /// Update atime relative to mtime/ctime.\n    Relatime(bool, u64),\n\n    /// Do not update access times.\n    Noatime(bool, u64),\n\n    /// Always perform atime updates.\n    StrictAtime(bool, u64),\n\n    /// Do not update directory access times.\n    NoDiratime(bool, u64),\n\n    /// Prevents following symbolic links.\n    Nosymfollow(bool, u64),\n}\n\nimpl FromStr for MountRecursive {\n    type Err = SyscallError;\n\n    fn from_str(option: &str) -> std::result::Result<Self, Self::Err> {\n        match option {\n            \"rro\" => Ok(MountRecursive::Rdonly(false, MOUNT_ATTR_RDONLY)),\n            \"rrw\" => Ok(MountRecursive::Rdonly(true, MOUNT_ATTR_RDONLY)),\n            \"rnosuid\" => Ok(MountRecursive::Nosuid(false, MOUNT_ATTR_NOSUID)),\n            \"rsuid\" => Ok(MountRecursive::Nosuid(true, MOUNT_ATTR_NOSUID)),\n            \"rnodev\" => Ok(MountRecursive::Nodev(false, MOUNT_ATTR_NODEV)),\n            \"rdev\" => Ok(MountRecursive::Nodev(true, MOUNT_ATTR_NODEV)),\n            \"rnoexec\" => Ok(MountRecursive::Noexec(false, MOUNT_ATTR_NOEXEC)),\n            \"rexec\" => Ok(MountRecursive::Noexec(true, MOUNT_ATTR_NOEXEC)),\n            \"rnodiratime\" => Ok(MountRecursive::NoDiratime(false, MOUNT_ATTR_NODIRATIME)),\n            \"rdiratime\" => Ok(MountRecursive::NoDiratime(true, MOUNT_ATTR_NODIRATIME)),\n            \"rrelatime\" => Ok(MountRecursive::Relatime(false, MOUNT_ATTR_RELATIME)),\n            \"rnorelatime\" => Ok(MountRecursive::Relatime(true, MOUNT_ATTR_RELATIME)),\n            \"rnoatime\" => Ok(MountRecursive::Noatime(false, MOUNT_ATTR_NOATIME)),\n            \"ratime\" => Ok(MountRecursive::Noatime(true, MOUNT_ATTR_NOATIME)),\n            \"rstrictatime\" => Ok(MountRecursive::StrictAtime(false, MOUNT_ATTR_STRICTATIME)),\n            \"rnostrictatime\" => Ok(MountRecursive::StrictAtime(true, MOUNT_ATTR_STRICTATIME)),\n            \"rnosymfollow\" => Ok(MountRecursive::Nosymfollow(false, MOUNT_ATTR_NOSYMFOLLOW)),\n            \"rsymfollow\" => Ok(MountRecursive::Nosymfollow(true, MOUNT_ATTR_NOSYMFOLLOW)),\n            // No support for MOUNT_ATTR_IDMAP yet (needs UserNS FD)\n            _ => Err(SyscallError::UnexpectedMountRecursiveOption(\n                option.to_string(),\n            )),\n        }\n    }\n}\n\n#[repr(C)]\n#[derive(Debug, Clone, PartialEq, Eq)]\n/// A structure used as te third argument of mount_setattr(2).\npub struct MountAttr {\n    /// Mount properties to set.\n    pub attr_set: u64,\n\n    /// Mount properties to clear.\n    pub attr_clr: u64,\n\n    /// Mount propagation type.\n    pub propagation: u64,\n\n    /// User namespace file descriptor.\n    pub userns_fd: u64,\n}\n\nimpl MountAttr {\n    /// Return MountAttr with the flag raised.\n    /// This function is used in test code.\n    pub fn all() -> Self {\n        MountAttr {\n            attr_set: MOUNT_ATTR_RDONLY\n                | MOUNT_ATTR_NOSUID\n                | MOUNT_ATTR_NODEV\n                | MOUNT_ATTR_NOEXEC\n                | MOUNT_ATTR_NODIRATIME\n                | MOUNT_ATTR_RELATIME\n                | MOUNT_ATTR_NOATIME\n                | MOUNT_ATTR_STRICTATIME\n                | MOUNT_ATTR_NOSYMFOLLOW,\n            attr_clr: MOUNT_ATTR_RDONLY\n                | MOUNT_ATTR_NOSUID\n                | MOUNT_ATTR_NODEV\n                | MOUNT_ATTR_NOEXEC\n                | MOUNT_ATTR_NODIRATIME\n                | MOUNT_ATTR_RELATIME\n                | MOUNT_ATTR_NOATIME\n                | MOUNT_ATTR_STRICTATIME\n                | MOUNT_ATTR_NOSYMFOLLOW\n                | MOUNT_ATTR__ATIME,\n            propagation: 0,\n            userns_fd: 0,\n        }\n    }\n}\n\n/// Empty structure to implement Command trait for\n#[derive(Clone)]\npub struct LinuxSyscall;\n\nimpl LinuxSyscall {\n    unsafe fn from_raw_buf<'a, T>(p: *const c_char) -> T\n    where\n        T: From<&'a OsStr>,\n    {\n        unsafe { T::from(OsStr::from_bytes(CStr::from_ptr(p).to_bytes())) }\n    }\n\n    /// Reads data from the `c_passwd` and returns it as a `User`.\n    unsafe fn passwd_to_user(passwd: libc::passwd) -> Arc<OsStr> {\n        let name: Arc<OsStr> = unsafe { Self::from_raw_buf(passwd.pw_name) };\n        name\n    }\n\n    fn emulate_close_range(preserve_fds: i32) -> Result<()> {\n        let open_fds = Self::get_open_fds()?;\n        // Include stdin, stdout, and stderr for fd 0, 1, and 2 respectively.\n        let min_fd = preserve_fds + 3;\n        let to_be_cleaned_up_fds: Vec<i32> = open_fds\n            .iter()\n            .filter_map(|&fd| if fd >= min_fd { Some(fd) } else { None })\n            .collect();\n\n        to_be_cleaned_up_fds.iter().for_each(|&fd| {\n            // Intentionally ignore errors here -- the cases where this might fail\n            // are basically file descriptors that have already been closed.\n            let _ = fcntl::fcntl(fd, fcntl::F_SETFD(fcntl::FdFlag::FD_CLOEXEC));\n        });\n\n        Ok(())\n    }\n\n    // Get a list of open fds for the calling process.\n    fn get_open_fds() -> Result<Vec<i32>> {\n        let dir = ProcfsHandle::new()?.open(\n            ProcfsBase::ProcSelf,\n            Path::new(\"fd\"),\n            OpenFlags::O_DIRECTORY | OpenFlags::O_CLOEXEC,\n        )?;\n\n        let fds = Dir::from(dir)?\n            .into_iter()\n            .filter_map(|entry| entry.ok())\n            .filter_map(|entry| {\n                // Convert the file name from string into i32. Since we are looking\n                // at /proc/<pid>/fd, anything that's not a number (i32) can be\n                // ignored. We are only interested in opened fds.\n                entry\n                    .file_name()\n                    .to_str()\n                    .ok()\n                    .and_then(|name| name.parse::<i32>().ok())\n            })\n            .collect();\n\n        Ok(fds)\n    }\n}\n\nimpl Syscall for LinuxSyscall {\n    /// To enable dynamic typing,\n    /// see <https://doc.rust-lang.org/std/any/index.html> for more information\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    /// Function to set given path as root path inside process\n    fn pivot_rootfs(&self, path: &Path) -> Result<()> {\n        // open the path as directory and read only\n        let newroot = open(\n            path,\n            OFlag::O_DIRECTORY | OFlag::O_RDONLY | OFlag::O_CLOEXEC,\n            Mode::empty(),\n        )\n        .inspect_err(|errno| {\n            tracing::error!(?errno, ?path, \"failed to open the new root for pivot root\");\n        })?;\n\n        // make the given path as the root directory for the container\n        // see https://man7.org/linux/man-pages/man2/pivot_root.2.html, specially the notes\n        // pivot root usually changes the root directory to first argument, and then mounts the original root\n        // directory at second argument. Giving same path for both stacks mapping of the original root directory\n        // above the new directory at the same path, then the call to umount unmounts the original root directory from\n        // this path. This is done, as otherwise, we will need to create a separate temporary directory under the new root path\n        // so we can move the original root there, and then unmount that. This way saves the creation of the temporary\n        // directory to put original root directory.\n        pivot_root(path, path).inspect_err(|errno| {\n            tracing::error!(?errno, ?path, \"failed to pivot root to\");\n        })?;\n\n        // Make the original root directory rslave to avoid propagating unmount event to the host mount namespace.\n        // We should use MS_SLAVE not MS_PRIVATE according to https://github.com/opencontainers/runc/pull/1500.\n        mount(\n            None::<&str>,\n            \"/\",\n            None::<&str>,\n            MsFlags::MS_SLAVE | MsFlags::MS_REC,\n            None::<&str>,\n        )\n        .inspect_err(|errno| {\n            tracing::error!(?errno, \"failed to make original root directory rslave\");\n        })?;\n\n        // Unmount the original root directory which was stacked on top of new root directory\n        // MNT_DETACH makes the mount point unavailable to new accesses, but waits till the original mount point\n        // to be free of activity to actually unmount\n        // see https://man7.org/linux/man-pages/man2/umount2.2.html for more information\n        umount2(\"/\", MntFlags::MNT_DETACH).inspect_err(|errno| {\n            tracing::error!(?errno, \"failed to unmount old root directory\");\n        })?;\n        // Change directory to the new root\n        fchdir(newroot).inspect_err(|errno| {\n            tracing::error!(?errno, ?newroot, \"failed to change directory to new root\");\n        })?;\n\n        close(newroot).inspect_err(|errno| {\n            tracing::error!(?errno, ?newroot, \"failed to close new root directory\");\n        })?;\n\n        Ok(())\n    }\n\n    /// Set namespace for process\n    fn set_ns(&self, rawfd: i32, nstype: CloneFlags) -> Result<()> {\n        let fd = unsafe { BorrowedFd::borrow_raw(rawfd) };\n        nix::sched::setns(fd, nstype)?;\n        Ok(())\n    }\n\n    /// set uid and gid for process\n    fn set_id(&self, uid: Uid, gid: Gid) -> Result<()> {\n        prctl::set_keep_capabilities(true).map_err(|errno| {\n            tracing::error!(?errno, \"failed to set keep capabilities to true\");\n            nix::errno::Errno::from_raw(errno)\n        })?;\n        // args : real *id, effective *id, saved set *id respectively\n\n        // This is safe because at this point we have only\n        // one thread in the process\n        if unsafe { libc::syscall(libc::SYS_setresgid, gid, gid, gid) } == -1 {\n            let err = nix::errno::Errno::last();\n            tracing::error!(\n                ?err,\n                ?gid,\n                \"failed to set real, effective and saved set gid\"\n            );\n            return Err(err.into());\n        }\n\n        // This is safe because at this point we have only\n        // one thread in the process\n        if unsafe { libc::syscall(libc::SYS_setresuid, uid, uid, uid) } == -1 {\n            let err = nix::errno::Errno::last();\n            tracing::error!(\n                ?err,\n                ?uid,\n                \"failed to set real, effective and saved set uid\"\n            );\n            return Err(err.into());\n        }\n\n        // if not the root user, reset capabilities to effective capabilities,\n        // which are used by kernel to perform checks\n        // see https://man7.org/linux/man-pages/man7/capabilities.7.html for more information\n        if uid != Uid::from_raw(0) {\n            capabilities::reset_effective(self)?;\n        }\n        prctl::set_keep_capabilities(false).map_err(|errno| {\n            tracing::error!(?errno, \"failed to set keep capabilities to false\");\n            nix::errno::Errno::from_raw(errno)\n        })?;\n        Ok(())\n    }\n\n    /// Disassociate parts of execution context\n    // see https://man7.org/linux/man-pages/man2/unshare.2.html for more information\n    fn unshare(&self, flags: CloneFlags) -> Result<()> {\n        unshare(flags)?;\n\n        Ok(())\n    }\n    /// Set capabilities for container process\n    fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<()> {\n        match cset {\n            // caps::set cannot set capabilities in bounding set,\n            // so we do it differently\n            CapSet::Bounding => {\n                // get all capabilities\n                let all = caps::read(None, CapSet::Bounding)?;\n                // the difference will give capabilities\n                // which are to be unset\n                // for each such =, drop that capability\n                // after this, only those which are to be set will remain set\n                for c in all.difference(value) {\n                    caps::drop(None, CapSet::Bounding, *c)?\n                }\n            }\n            CapSet::Ambient => {\n                // check specifically for ambient, as those might not always be available\n                //\n                // Ambient capabilities are applied from an unordered HashSet, and if any\n                // set_capability() call fails, Youki stops applying the rest. This causes\n                // inconsistent CapAmb results between runs and diverges from runc, which\n                // continues applying all ambient caps even after a failure. The same flawed\n                // ambient-cap logic also causes exec-path capability test failures.\n                caps::clear(None, CapSet::Ambient)?;\n                for c in value {\n                    if let Err(e) = caps::raise(None, CapSet::Ambient, *c) {\n                        tracing::warn!(?e, ?c, \"can't raise ambient capability\");\n                    }\n                }\n            }\n            _ => {\n                caps::set(None, cset, value)?;\n            }\n        }\n        Ok(())\n    }\n\n    /// Sets hostname for process\n    fn set_hostname(&self, hostname: &str) -> Result<()> {\n        sethostname(hostname)?;\n        Ok(())\n    }\n\n    /// Sets domainname for process (see\n    /// [setdomainname(2)](https://man7.org/linux/man-pages/man2/setdomainname.2.html)).\n    fn set_domainname(&self, domainname: &str) -> Result<()> {\n        let ptr = domainname.as_bytes().as_ptr() as *const c_char;\n        let len = domainname.len();\n        match unsafe { setdomainname(ptr, len) } {\n            0 => Ok(()),\n            -1 => Err(nix::Error::last()),\n\n            _ => Err(nix::Error::UnknownErrno),\n        }?;\n\n        Ok(())\n    }\n\n    /// Sets resource limit for process\n    fn set_rlimit(&self, rlimit: &PosixRlimit) -> Result<()> {\n        let rlim = &libc::rlimit {\n            rlim_cur: rlimit.soft(),\n            rlim_max: rlimit.hard(),\n        };\n\n        // Change for musl libc based on seccomp needs\n        #[cfg(not(target_env = \"musl\"))]\n        let res = unsafe { libc::setrlimit(rlimit.typ() as u32, rlim) };\n        #[cfg(target_env = \"musl\")]\n        let res = unsafe { libc::setrlimit(rlimit.typ() as i32, rlim) };\n\n        match res {\n            0 => Ok(()),\n            -1 => Err(SyscallError::Nix(nix::Error::last())),\n            _ => Err(SyscallError::Nix(nix::Error::UnknownErrno)),\n        }?;\n\n        Ok(())\n    }\n\n    // taken from https://crates.io/crates/users\n    fn get_pwuid(&self, uid: uid_t) -> Option<Arc<OsStr>> {\n        let mut passwd = unsafe { mem::zeroed::<libc::passwd>() };\n        let mut buf = vec![0; 2048];\n        let mut result = ptr::null_mut::<libc::passwd>();\n\n        loop {\n            let r = unsafe {\n                libc::getpwuid_r(uid, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result)\n            };\n\n            if r != libc::ERANGE {\n                break;\n            }\n\n            let newsize = buf.len().checked_mul(2)?;\n            buf.resize(newsize, 0);\n        }\n\n        if result.is_null() {\n            // There is no such user, or an error has occurred.\n            // errno gets set if there's an error.\n            return None;\n        }\n\n        if result != &mut passwd {\n            // The result of getpwuid_r should be its input passwd.\n            return None;\n        }\n\n        let user = unsafe { Self::passwd_to_user(result.read()) };\n        Some(user)\n    }\n\n    fn chroot(&self, path: &Path) -> Result<()> {\n        chroot(path)?;\n\n        Ok(())\n    }\n\n    fn mount(\n        &self,\n        source: Option<&Path>,\n        target: &Path,\n        fstype: Option<&str>,\n        flags: MsFlags,\n        data: Option<&str>,\n    ) -> Result<()> {\n        mount(source, target, fstype, flags, data)?;\n        Ok(())\n    }\n\n    fn mount_from_fd(&self, source_fd: &OwnedFd, target: &Path) -> Result<()> {\n        let parent = target.parent().ok_or_else(|| {\n            tracing::error!(?target, \"target has no parent\");\n            SyscallError::Nix(nix::Error::EINVAL)\n        })?;\n        let name = target.file_name().ok_or_else(|| {\n            tracing::error!(?target, \"target has no file name\");\n            SyscallError::Nix(nix::Error::EINVAL)\n        })?;\n\n        let parent_fd = unsafe {\n            OwnedFd::from_raw_fd(open(\n                parent,\n                OFlag::O_PATH | OFlag::O_CLOEXEC | OFlag::O_DIRECTORY,\n                Mode::empty(),\n            )?)\n        };\n\n        let open_tree_flags: libc::c_uint = (libc::OPEN_TREE_CLOEXEC as libc::c_uint)\n            | (libc::OPEN_TREE_CLONE as libc::c_uint)\n            | (libc::AT_EMPTY_PATH as libc::c_uint);\n\n        const EMPTY_PATH: [libc::c_char; 1] = [0];\n\n        let mount_fd_raw = unsafe {\n            libc::syscall(\n                libc::SYS_open_tree,\n                source_fd.as_raw_fd(),\n                EMPTY_PATH.as_ptr(),\n                open_tree_flags,\n            )\n        };\n\n        if mount_fd_raw < 0 {\n            let err = nix::errno::Errno::last();\n            tracing::error!(?err, \"open_tree from fd failed\");\n            return Err(SyscallError::Nix(err));\n        }\n        let mount_fd = unsafe { OwnedFd::from_raw_fd(mount_fd_raw as RawFd) };\n\n        let name_cstr = CString::new(name.as_bytes()).map_err(|err| {\n            tracing::error!(?target, ?err, \"failed to convert file name to cstring\");\n            SyscallError::Nix(nix::Error::EINVAL)\n        })?;\n\n        let res = unsafe {\n            libc::syscall(\n                libc::SYS_move_mount,\n                mount_fd.as_raw_fd(),\n                EMPTY_PATH.as_ptr(),\n                parent_fd.as_raw_fd(),\n                name_cstr.as_ptr(),\n                MOVE_MOUNT_F_EMPTY_PATH as libc::c_uint,\n            )\n        };\n\n        if res < 0 {\n            let err = nix::errno::Errno::last();\n            tracing::error!(?target, ?err, \"move_mount failed\");\n            return Err(SyscallError::Nix(err));\n        }\n\n        Ok(())\n    }\n\n    fn move_mount(\n        &self,\n        from_dirfd: BorrowedFd<'_>,\n        from_path: Option<&str>,\n        to_dirfd: BorrowedFd<'_>,\n        to_path: Option<&str>,\n        flags: u32,\n    ) -> Result<()> {\n        const EMPTY_PATH: [libc::c_char; 1] = [0];\n\n        let from_cstr: Option<CString> = from_path\n            .and_then(|s| if s.is_empty() { None } else { Some(s) })\n            .map(|s| CString::new(s).map_err(|_| nix::Error::EINVAL))\n            .transpose()?;\n        let from_ptr = from_cstr\n            .as_ref()\n            .map_or(EMPTY_PATH.as_ptr(), |c| c.as_ptr());\n\n        let to_cstr: Option<CString> = to_path\n            .and_then(|s| if s.is_empty() { None } else { Some(s) })\n            .map(|s| CString::new(s).map_err(|_| nix::Error::EINVAL))\n            .transpose()?;\n        let to_ptr = to_cstr.as_ref().map_or(EMPTY_PATH.as_ptr(), |c| c.as_ptr());\n\n        let rc = unsafe {\n            libc::syscall(\n                libc::SYS_move_mount,\n                from_dirfd,\n                from_ptr,\n                to_dirfd,\n                to_ptr,\n                flags as libc::c_uint,\n            )\n        };\n\n        match rc {\n            0 => Ok(()),\n            -1 => Err(nix::Error::last().into()),\n            _ => Err(nix::Error::UnknownErrno.into()),\n        }\n    }\n\n    fn fsopen(&self, fstype: Option<&str>, flags: u32) -> Result<OwnedFd> {\n        let t_cstr: Option<CString> = fstype\n            .map(|t| CString::new(t).map_err(|_| SyscallError::Nix(nix::errno::Errno::EINVAL)))\n            .transpose()?;\n\n        let t_ptr = t_cstr.as_ref().map_or(std::ptr::null(), |c| c.as_ptr());\n\n        let fd =\n            unsafe { libc::syscall(libc::SYS_fsopen, t_ptr, flags as libc::c_uint) } as libc::c_int;\n        if fd < 0 {\n            return Err(SyscallError::Nix(nix::Error::last()));\n        }\n        Ok(unsafe { OwnedFd::from_raw_fd(fd) })\n    }\n\n    fn fsconfig(\n        &self,\n        fsfd: BorrowedFd<'_>,\n        cmd: u32,\n        key: Option<&str>,\n        val: Option<&str>,\n        aux: libc::c_int,\n    ) -> Result<()> {\n        let k_cstr: Option<CString> = key\n            .map(|k| CString::new(k).map_err(|_| SyscallError::Nix(nix::errno::Errno::EINVAL)))\n            .transpose()?;\n        let k_ptr = k_cstr.as_ref().map_or(std::ptr::null(), |k| k.as_ptr());\n\n        let v_cstr: Option<CString> = val\n            .map(|v| CString::new(v).map_err(|_| SyscallError::Nix(nix::errno::Errno::EINVAL)))\n            .transpose()?;\n        let v_ptr = v_cstr\n            .as_ref()\n            .map_or(std::ptr::null(), |v| v.as_ptr() as *const libc::c_void);\n\n        let rc = unsafe {\n            libc::syscall(\n                libc::SYS_fsconfig,\n                fsfd.as_raw_fd() as libc::c_int,\n                cmd as libc::c_uint,\n                k_ptr,\n                v_ptr,\n                aux,\n            )\n        };\n        if rc == -1 {\n            return Err(SyscallError::Nix(nix::Error::last()));\n        }\n        Ok(())\n    }\n\n    fn fsmount(\n        &self,\n        fsfd: BorrowedFd<'_>,\n        flags: u32,\n        attr_flags: Option<u64>,\n    ) -> Result<OwnedFd> {\n        let attr = attr_flags.unwrap_or(0);\n\n        let ret = unsafe {\n            libc::syscall(\n                libc::SYS_fsmount,\n                fsfd.as_raw_fd() as libc::c_int,\n                flags as libc::c_uint,\n                attr as libc::c_ulong,\n            )\n        } as libc::c_int;\n\n        if ret < 0 {\n            return Err(SyscallError::Nix(nix::Error::last()));\n        }\n        Ok(unsafe { std::os::fd::OwnedFd::from_raw_fd(ret) })\n    }\n\n    //dirfd is RawFd because we need to pass AT_FDCWD\n    fn open_tree(&self, dirfd: RawFd, path: Option<&str>, flags: u32) -> Result<OwnedFd> {\n        static EMPTY: [libc::c_char; 1] = [0];\n        let path_cstr: Option<CString> = path\n            .map(|s| CString::new(s).map_err(|_| SyscallError::Nix(nix::errno::Errno::EINVAL)))\n            .transpose()?;\n        let c_path: *const c_char = match path_cstr.as_ref() {\n            Some(cs) => cs.as_ptr(),\n            None => EMPTY.as_ptr(),\n        };\n\n        let fd = unsafe {\n            libc::syscall(\n                libc::SYS_open_tree,\n                dirfd as libc::c_int,\n                c_path,\n                flags as libc::c_uint,\n            )\n        } as libc::c_int;\n\n        if fd < 0 {\n            return Err(SyscallError::Nix(nix::Error::last()));\n        }\n        Ok(unsafe { OwnedFd::from_raw_fd(fd) })\n    }\n\n    fn symlink(&self, original: &Path, link: &Path) -> Result<()> {\n        symlink(original, link)?;\n\n        Ok(())\n    }\n\n    fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()> {\n        mknod(path, kind, perm, dev)?;\n\n        Ok(())\n    }\n\n    fn chown(&self, path: &Path, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {\n        chown(path, owner, group)?;\n\n        Ok(())\n    }\n\n    fn set_groups(&self, groups: &[Gid]) -> Result<()> {\n        let n_groups = groups.len() as libc::size_t;\n        let groups_ptr = groups.as_ptr() as *const libc::gid_t;\n\n        // This is safe because at this point we have only\n        // one thread in the process\n        if unsafe { libc::syscall(libc::SYS_setgroups, n_groups, groups_ptr) } == -1 {\n            let err = nix::errno::Errno::last();\n            tracing::error!(?err, ?groups, \"failed to set groups\");\n            return Err(err.into());\n        }\n        Ok(())\n    }\n\n    #[tracing::instrument(skip(self))]\n    fn close_range(&self, preserve_fds: i32) -> Result<()> {\n        match unsafe {\n            libc::syscall(\n                libc::SYS_close_range,\n                3 + preserve_fds,\n                libc::c_int::MAX,\n                libc::CLOSE_RANGE_CLOEXEC,\n            )\n        } {\n            0 => Ok(()),\n            -1 => {\n                match nix::errno::Errno::last() {\n                    nix::errno::Errno::ENOSYS | nix::errno::Errno::EINVAL => {\n                        // close_range was introduced in kernel 5.9 and CLOSEEXEC was introduced in\n                        // kernel 5.11. If the kernel is older we emulate close_range in userspace.\n                        Self::emulate_close_range(preserve_fds)\n                    }\n                    e => Err(SyscallError::Nix(e)),\n                }\n            }\n            _ => Err(SyscallError::Nix(nix::errno::Errno::UnknownErrno)),\n        }?;\n\n        Ok(())\n    }\n\n    fn mount_setattr(\n        &self,\n        dirfd: BorrowedFd<'_>,\n        pathname: &Path,\n        flags: u32,\n        mount_attr: &MountAttr,\n        size: libc::size_t,\n    ) -> Result<()> {\n        let path_c_string = pathname\n            .to_path_buf()\n            .to_str()\n            .map(CString::new)\n            .ok_or_else(|| {\n                tracing::error!(path = ?pathname, \"failed to convert path to string\");\n                nix::Error::EINVAL\n            })?\n            .map_err(|err| {\n                tracing::error!(path = ?pathname, ?err, \"failed to convert path to string\");\n                nix::Error::EINVAL\n            })?;\n\n        match unsafe {\n            libc::syscall(\n                libc::SYS_mount_setattr,\n                dirfd,\n                path_c_string.as_ptr(),\n                flags,\n                mount_attr as *const MountAttr,\n                size,\n            )\n        } {\n            0 => Ok(()),\n            -1 => Err(nix::Error::last()),\n            _ => Err(nix::Error::UnknownErrno),\n        }?;\n        Ok(())\n    }\n\n    fn set_io_priority(&self, class: i64, priority: i64) -> Result<()> {\n        let ioprio_who_progress: libc::c_int = 1;\n        let ioprio_who_pid = 0;\n        let iop = (class << 13) | priority;\n        match unsafe {\n            libc::syscall(\n                libc::SYS_ioprio_set,\n                ioprio_who_progress,\n                ioprio_who_pid,\n                iop as libc::c_ulong,\n            )\n        } {\n            0 => Ok(()),\n            -1 => Err(nix::Error::last()),\n            _ => Err(nix::Error::UnknownErrno),\n        }?;\n        Ok(())\n    }\n\n    fn set_mempolicy(&self, mode: i32, nodemask: &[libc::c_ulong], maxnode: u64) -> Result<()> {\n        // Convert Rust types to libc types\n        let libc_nodemask = if nodemask.is_empty() {\n            std::ptr::null()\n        } else {\n            nodemask.as_ptr()\n        };\n        let libc_maxnode = maxnode as libc::c_ulong;\n\n        match unsafe {\n            libc::syscall(\n                libc::SYS_set_mempolicy,\n                mode as libc::c_long,\n                libc_nodemask,\n                libc_maxnode,\n            )\n        } {\n            0 => Ok(()),\n            -1 => Err(SyscallError::Nix(nix::Error::last())),\n            _ => Err(SyscallError::Nix(nix::Error::UnknownErrno)),\n        }\n    }\n\n    fn umount2(&self, target: &Path, flags: MntFlags) -> Result<()> {\n        umount2(target, flags)?;\n        Ok(())\n    }\n\n    fn get_uid(&self) -> Uid {\n        nix::unistd::getuid()\n    }\n\n    fn get_gid(&self) -> Gid {\n        nix::unistd::getgid()\n    }\n\n    fn get_euid(&self) -> Uid {\n        nix::unistd::geteuid()\n    }\n\n    fn get_egid(&self) -> Gid {\n        nix::unistd::getegid()\n    }\n\n    fn personality(&self, domain: PersonalityDomain) -> Result<()> {\n        let domain = nix::sys::personality::Persona::from_bits_retain(domain as i32);\n        nix::sys::personality::set(domain)\n            .map(|_| ())\n            .map_err(|e| e.into())\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    // Note: We have to run these tests here as serial. The main issue is that\n    // these tests has a dependency on the system state. The\n    // cleanup_file_descriptors test is especially evil when running with other\n    // tests because it would ran around close down different fds.\n\n    use std::fs;\n    use std::os::unix::prelude::AsRawFd;\n    use std::str::FromStr;\n\n    use anyhow::{Context, Result, bail};\n    use nix::{fcntl, sys, unistd};\n    use serial_test::serial;\n\n    use super::{LinuxSyscall, MountOption};\n    use crate::syscall::Syscall;\n\n    #[test]\n    #[serial]\n    fn test_get_open_fds() -> Result<()> {\n        let file = fs::File::open(\"/dev/null\")?;\n        let fd = file.as_raw_fd();\n        let open_fds = LinuxSyscall::get_open_fds()?;\n\n        if !open_fds.contains(&fd) {\n            bail!(\"failed to find the opened dev null fds: {:?}\", open_fds);\n        }\n\n        // explicitly close the file before the test case returns.\n        drop(file);\n\n        // The stdio fds should also be contained in the list of opened fds.\n        if ![0, 1, 2]\n            .iter()\n            .all(|&stdio_fd| open_fds.contains(&stdio_fd))\n        {\n            bail!(\"failed to find the stdio fds: {:?}\", open_fds);\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_close_range_userspace() -> Result<()> {\n        // Open a fd without the CLOEXEC flag. Rust automatically adds the flag,\n        // so we use fcntl::open here for more control.\n        let fd = fcntl::open(\"/dev/null\", fcntl::OFlag::O_RDWR, sys::stat::Mode::empty())?;\n        LinuxSyscall::emulate_close_range(0).context(\"failed to clean up the fds\")?;\n\n        let fd_flag = fcntl::fcntl(fd, fcntl::F_GETFD)?;\n        if (fd_flag & fcntl::FdFlag::FD_CLOEXEC.bits()) == 0 {\n            bail!(\"CLOEXEC flag is not set correctly\");\n        }\n\n        unistd::close(fd)?;\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_close_range_native() -> Result<()> {\n        let fd = fcntl::open(\"/dev/null\", fcntl::OFlag::O_RDWR, sys::stat::Mode::empty())?;\n        let syscall = LinuxSyscall {};\n        syscall\n            .close_range(0)\n            .context(\"failed to clean up the fds\")?;\n\n        let fd_flag = fcntl::fcntl(fd, fcntl::F_GETFD)?;\n        if (fd_flag & fcntl::FdFlag::FD_CLOEXEC.bits()) == 0 {\n            bail!(\"CLOEXEC flag is not set correctly\");\n        }\n\n        unistd::close(fd)?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_known_mount_options_implemented() -> Result<()> {\n        for option in MountOption::known_options() {\n            match MountOption::from_str(&option) {\n                Ok(_) => {}\n                Err(e) => bail!(\"failed to parse mount option: {}\", e),\n            }\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/syscall/mod.rs",
    "content": "//! Contains a wrapper of syscalls for unit tests\n//! This provides a uniform interface for rest of Youki\n//! to call syscalls required for container management\n\npub mod linux;\n#[allow(clippy::module_inception)]\npub mod syscall;\npub mod test;\n\npub use syscall::Syscall;\n#[derive(Debug, thiserror::Error)]\npub enum SyscallError {\n    #[error(\"unexpected mount attr option: {0}\")]\n    UnexpectedMountRecursiveOption(String),\n    #[error(transparent)]\n    Nix(#[from] nix::Error),\n    #[error(transparent)]\n    IO(#[from] std::io::Error),\n    #[error(\"failed to set capabilities: {0}\")]\n    SetCaps(#[from] caps::errors::CapsError),\n    #[error(transparent)]\n    Pathrs(#[from] pathrs::error::Error),\n}\n\ntype Result<T> = std::result::Result<T, SyscallError>;\n"
  },
  {
    "path": "crates/libcontainer/src/syscall/syscall.rs",
    "content": "//! An interface trait so that rest of Youki can call\n//! necessary functions without having to worry about their\n//! implementation details\nuse std::any::Any;\nuse std::ffi::OsStr;\nuse std::os::fd::{BorrowedFd, RawFd};\nuse std::os::unix::io::OwnedFd;\nuse std::path::Path;\nuse std::sync::Arc;\n\nuse caps::{CapSet, CapsHashSet};\nuse libc;\nuse nix::mount::{MntFlags, MsFlags};\nuse nix::sched::CloneFlags;\nuse nix::sys::stat::{Mode, SFlag};\nuse nix::unistd::{Gid, Uid};\nuse oci_spec::runtime::PosixRlimit;\n\nuse crate::config::PersonalityDomain;\nuse crate::syscall::Result;\nuse crate::syscall::linux::{LinuxSyscall, MountAttr};\nuse crate::syscall::test::TestHelperSyscall;\n\n/// This specifies various kernel/other functionalities required for\n/// container management\npub trait Syscall {\n    fn as_any(&self) -> &dyn Any;\n    fn pivot_rootfs(&self, path: &Path) -> Result<()>;\n    fn chroot(&self, path: &Path) -> Result<()>;\n    fn set_ns(&self, rawfd: i32, nstype: CloneFlags) -> Result<()>;\n    fn set_id(&self, uid: Uid, gid: Gid) -> Result<()>;\n    fn unshare(&self, flags: CloneFlags) -> Result<()>;\n    fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<()>;\n    fn set_hostname(&self, hostname: &str) -> Result<()>;\n    fn set_domainname(&self, domainname: &str) -> Result<()>;\n    fn set_rlimit(&self, rlimit: &PosixRlimit) -> Result<()>;\n    fn get_pwuid(&self, uid: u32) -> Option<Arc<OsStr>>;\n    fn mount(\n        &self,\n        source: Option<&Path>,\n        target: &Path,\n        fstype: Option<&str>,\n        flags: MsFlags,\n        data: Option<&str>,\n    ) -> Result<()>;\n    // mount_from_fd mounts a filesystem specified by source_fd to target path.\n    // NOTE: mount_from_fd only supports BIND_MOUNT.\n    fn mount_from_fd(&self, source_fd: &OwnedFd, target: &Path) -> Result<()>;\n    fn move_mount(\n        &self,\n        from_dirfd: BorrowedFd<'_>,\n        from_path: Option<&str>,\n        to_dirfd: BorrowedFd<'_>,\n        to_path: Option<&str>,\n        flags: u32,\n    ) -> Result<()>;\n    fn fsopen(&self, fstype: Option<&str>, flags: u32) -> Result<OwnedFd>;\n    fn fsconfig(\n        &self,\n        fsfd: BorrowedFd<'_>,\n        cmd: u32,\n        key: Option<&str>,\n        val: Option<&str>,\n        aux: libc::c_int,\n    ) -> Result<()>;\n    fn fsmount(&self, fsfd: BorrowedFd<'_>, flags: u32, attr_flags: Option<u64>)\n    -> Result<OwnedFd>;\n    fn open_tree(&self, dirfd: RawFd, path: Option<&str>, flags: u32) -> Result<OwnedFd>;\n    fn symlink(&self, original: &Path, link: &Path) -> Result<()>;\n    fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()>;\n    fn chown(&self, path: &Path, owner: Option<Uid>, group: Option<Gid>) -> Result<()>;\n    fn set_groups(&self, groups: &[Gid]) -> Result<()>;\n    fn close_range(&self, preserve_fds: i32) -> Result<()>;\n    fn mount_setattr(\n        &self,\n        dirfd: BorrowedFd<'_>,\n        pathname: &Path,\n        flags: u32,\n        mount_attr: &MountAttr,\n        size: libc::size_t,\n    ) -> Result<()>;\n    fn set_io_priority(&self, class: i64, priority: i64) -> Result<()>;\n    fn set_mempolicy(&self, mode: i32, nodemask: &[libc::c_ulong], maxnode: u64) -> Result<()>;\n    fn umount2(&self, target: &Path, flags: MntFlags) -> Result<()>;\n    fn get_uid(&self) -> Uid;\n    fn get_gid(&self) -> Gid;\n    fn get_euid(&self) -> Uid;\n    fn get_egid(&self) -> Gid;\n    fn personality(&self, domain: PersonalityDomain) -> Result<()>;\n}\n\n#[derive(Clone, Copy)]\npub enum SyscallType {\n    Linux,\n    Test,\n}\n\nimpl Default for SyscallType {\n    fn default() -> Self {\n        if cfg!(test) {\n            SyscallType::Test\n        } else {\n            SyscallType::Linux\n        }\n    }\n}\n\nimpl SyscallType {\n    pub fn create_syscall(&self) -> Box<dyn Syscall> {\n        match self {\n            SyscallType::Linux => Box::new(LinuxSyscall),\n            SyscallType::Test => Box::<TestHelperSyscall>::default(),\n        }\n    }\n}\n\npub fn create_syscall() -> Box<dyn Syscall> {\n    SyscallType::default().create_syscall()\n}\n"
  },
  {
    "path": "crates/libcontainer/src/syscall/test.rs",
    "content": "use std::any::Any;\nuse std::cell::{Ref, RefCell, RefMut};\nuse std::collections::HashMap;\nuse std::ffi::{OsStr, OsString};\nuse std::os::fd::{AsRawFd, BorrowedFd, RawFd};\nuse std::os::unix::io::OwnedFd;\nuse std::path::{Path, PathBuf};\nuse std::sync::Arc;\n\nuse caps::{CapSet, CapsHashSet};\nuse nix::mount::{MntFlags, MsFlags};\nuse nix::sched::CloneFlags;\nuse nix::sys::stat::{Mode, SFlag};\nuse nix::unistd::{Gid, Uid};\nuse oci_spec::runtime::PosixRlimit;\n\nuse super::super::config::PersonalityDomain;\nuse super::{Result, Syscall, linux};\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct MountArgs {\n    pub source: Option<PathBuf>,\n    pub target: PathBuf,\n    pub fstype: Option<String>,\n    pub flags: MsFlags,\n    pub data: Option<String>,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct MountFromFdArgs {\n    pub fd: i32,\n    pub target: PathBuf,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct MoveMountArgs {\n    pub from_dirfd: i32,\n    pub from_path: Option<OsString>,\n    pub to_dirfd: i32,\n    pub to_path: Option<OsString>,\n    pub flags: u32,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct FsopenArgs {\n    pub fsname: Option<String>,\n    pub flags: u32,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct MknodArgs {\n    pub path: PathBuf,\n    pub kind: SFlag,\n    pub perm: Mode,\n    pub dev: u64,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct ChownArgs {\n    pub path: PathBuf,\n    pub owner: Option<Uid>,\n    pub group: Option<Gid>,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct IoPriorityArgs {\n    pub class: i64,\n    pub priority: i64,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct MemPolicyArgs {\n    pub mode: i32,\n    pub nodemask: Vec<libc::c_ulong>, // Store the nodemask vector for testing\n    pub maxnode: u64,\n}\n\n#[derive(Clone, PartialEq, Eq, Debug)]\npub struct UMount2Args {\n    pub target: PathBuf,\n    pub flags: MntFlags,\n}\n\n#[derive(Default)]\nstruct Mock {\n    values: Vec<Box<dyn Any>>,\n    ret_err: Option<fn() -> Result<()>>,\n    ret_err_times: usize,\n}\n\n#[derive(PartialEq, Eq, Hash, Copy, Clone)]\npub enum ArgName {\n    Namespace,\n    Unshare,\n    Mount,\n    MountFromFd,\n    Symlink,\n    Mknod,\n    Chown,\n    Hostname,\n    Domainname,\n    Groups,\n    Capability,\n    IoPriority,\n    MemPolicy,\n    UMount2,\n    MoveMount,\n    Fsopen,\n}\n\nimpl ArgName {\n    fn iterator() -> impl Iterator<Item = ArgName> {\n        [\n            ArgName::Namespace,\n            ArgName::Unshare,\n            ArgName::Mount,\n            ArgName::MountFromFd,\n            ArgName::Symlink,\n            ArgName::Mknod,\n            ArgName::Chown,\n            ArgName::Hostname,\n            ArgName::Domainname,\n            ArgName::Groups,\n            ArgName::Capability,\n            ArgName::IoPriority,\n            ArgName::MemPolicy,\n            ArgName::MoveMount,\n        ]\n        .iter()\n        .copied()\n    }\n}\n\nstruct MockCalls {\n    args: HashMap<ArgName, RefCell<Mock>>,\n}\n\nimpl Default for MockCalls {\n    fn default() -> Self {\n        let mut m = MockCalls {\n            args: HashMap::new(),\n        };\n\n        for name in ArgName::iterator() {\n            m.args.insert(name, RefCell::new(Mock::default()));\n        }\n\n        m\n    }\n}\n\nimpl MockCalls {\n    fn act(&self, name: ArgName, value: Box<dyn Any>) -> Result<()> {\n        if self.args.get(&name).unwrap().borrow().ret_err_times > 0 {\n            self.args.get(&name).unwrap().borrow_mut().ret_err_times -= 1;\n            if let Some(e) = &self.args.get(&name).unwrap().borrow().ret_err {\n                return e();\n            }\n        }\n\n        self.args\n            .get(&name)\n            .unwrap()\n            .borrow_mut()\n            .values\n            .push(value);\n        Ok(())\n    }\n\n    fn fetch(&self, name: ArgName) -> Ref<'_, Mock> {\n        self.args.get(&name).unwrap().borrow()\n    }\n\n    fn fetch_mut(&self, name: ArgName) -> RefMut<'_, Mock> {\n        self.args.get(&name).unwrap().borrow_mut()\n    }\n}\n\n#[derive(Default)]\npub struct TestHelperSyscall {\n    mock_id: RefCell<MockId>,\n    mocks: MockCalls,\n}\n\npub struct MockId {\n    uid: Uid,\n    gid: Gid,\n    euid: Uid,\n    egid: Gid,\n}\n\nimpl Default for MockId {\n    fn default() -> Self {\n        Self {\n            uid: nix::unistd::getuid(),\n            gid: nix::unistd::getgid(),\n            euid: nix::unistd::geteuid(),\n            egid: nix::unistd::getegid(),\n        }\n    }\n}\n\nimpl Syscall for TestHelperSyscall {\n    fn as_any(&self) -> &dyn Any {\n        self\n    }\n\n    fn pivot_rootfs(&self, _path: &Path) -> Result<()> {\n        unimplemented!()\n    }\n\n    fn set_ns(&self, rawfd: i32, nstype: CloneFlags) -> Result<()> {\n        self.mocks\n            .act(ArgName::Namespace, Box::new((rawfd, nstype)))\n    }\n\n    fn set_id(&self, _uid: Uid, _gid: Gid) -> Result<()> {\n        self.mock_id.borrow_mut().uid = _uid;\n        self.mock_id.borrow_mut().gid = _gid;\n        self.mock_id.borrow_mut().euid = _uid;\n        self.mock_id.borrow_mut().egid = _gid;\n        Ok(())\n    }\n\n    fn unshare(&self, flags: CloneFlags) -> Result<()> {\n        self.mocks.act(ArgName::Unshare, Box::new(flags))\n    }\n\n    fn set_capability(&self, cset: CapSet, value: &CapsHashSet) -> Result<()> {\n        self.mocks\n            .act(ArgName::Capability, Box::new((cset, value.clone())))\n    }\n\n    fn set_hostname(&self, hostname: &str) -> Result<()> {\n        self.mocks\n            .act(ArgName::Hostname, Box::new(hostname.to_owned()))\n    }\n\n    fn set_domainname(&self, domainname: &str) -> Result<()> {\n        self.mocks\n            .act(ArgName::Domainname, Box::new(domainname.to_owned()))\n    }\n\n    fn set_rlimit(&self, _rlimit: &PosixRlimit) -> Result<()> {\n        todo!()\n    }\n\n    fn get_pwuid(&self, _: u32) -> Option<Arc<OsStr>> {\n        Some(OsString::from(\"youki\").into())\n    }\n\n    fn chroot(&self, _: &Path) -> Result<()> {\n        todo!()\n    }\n\n    fn mount(\n        &self,\n        source: Option<&Path>,\n        target: &Path,\n        fstype: Option<&str>,\n        flags: MsFlags,\n        data: Option<&str>,\n    ) -> Result<()> {\n        // For tests: resolve /proc/self/fd/<n> to the real path before recording.\n        let target_owned = if target.starts_with(Path::new(\"/proc/self/fd\")) {\n            std::fs::read_link(target).unwrap_or_else(|_| target.to_owned())\n        } else {\n            target.to_owned()\n        };\n\n        self.mocks.act(\n            ArgName::Mount,\n            Box::new(MountArgs {\n                source: source.map(|x| x.to_owned()),\n                target: target_owned,\n                fstype: fstype.map(|x| x.to_owned()),\n                flags,\n                data: data.map(|x| x.to_owned()),\n            }),\n        )\n    }\n\n    fn mount_from_fd(&self, source_fd: &OwnedFd, target: &Path) -> Result<()> {\n        self.mocks.act(\n            ArgName::MountFromFd,\n            Box::new(MountFromFdArgs {\n                fd: source_fd.as_raw_fd(),\n                target: target.to_owned(),\n            }),\n        )\n    }\n\n    fn move_mount(\n        &self,\n        from_dirfd: BorrowedFd<'_>,\n        from_path: Option<&str>,\n        to_dirfd: BorrowedFd<'_>,\n        to_path: Option<&str>,\n        flags: u32,\n    ) -> Result<()> {\n        let rec = MoveMountArgs {\n            from_dirfd: from_dirfd.as_raw_fd(),\n            from_path: from_path.map(OsString::from),\n            to_dirfd: to_dirfd.as_raw_fd(),\n            to_path: to_path.map(OsString::from),\n            flags,\n        };\n        self.mocks.act(ArgName::MoveMount, Box::new(rec))\n    }\n\n    fn fsopen(&self, _: Option<&str>, _: u32) -> Result<OwnedFd> {\n        todo!()\n    }\n\n    fn fsconfig(\n        &self,\n        _: BorrowedFd<'_>,\n        _: u32,\n        _: Option<&str>,\n        _: Option<&str>,\n        _: libc::c_int,\n    ) -> Result<()> {\n        todo!()\n    }\n\n    fn fsmount(&self, _: BorrowedFd<'_>, _: u32, _: Option<u64>) -> Result<OwnedFd> {\n        todo!()\n    }\n\n    fn open_tree(&self, _: RawFd, _: Option<&str>, _: u32) -> Result<OwnedFd> {\n        todo!()\n    }\n\n    fn symlink(&self, original: &Path, link: &Path) -> Result<()> {\n        self.mocks.act(\n            ArgName::Symlink,\n            Box::new((original.to_path_buf(), link.to_path_buf())),\n        )\n    }\n\n    fn mknod(&self, path: &Path, kind: SFlag, perm: Mode, dev: u64) -> Result<()> {\n        self.mocks.act(\n            ArgName::Mknod,\n            Box::new(MknodArgs {\n                path: path.to_path_buf(),\n                kind,\n                perm,\n                dev,\n            }),\n        )\n    }\n    fn chown(&self, path: &Path, owner: Option<Uid>, group: Option<Gid>) -> Result<()> {\n        self.mocks.act(\n            ArgName::Chown,\n            Box::new(ChownArgs {\n                path: path.to_path_buf(),\n                owner,\n                group,\n            }),\n        )\n    }\n\n    fn set_groups(&self, groups: &[Gid]) -> Result<()> {\n        self.mocks.act(ArgName::Groups, Box::new(groups.to_vec()))\n    }\n\n    fn close_range(&self, _: i32) -> Result<()> {\n        todo!()\n    }\n\n    fn mount_setattr(\n        &self,\n        _: BorrowedFd<'_>,\n        _: &Path,\n        _: u32,\n        _: &linux::MountAttr,\n        _: libc::size_t,\n    ) -> Result<()> {\n        todo!()\n    }\n\n    fn set_io_priority(&self, class: i64, priority: i64) -> Result<()> {\n        self.mocks.act(\n            ArgName::IoPriority,\n            Box::new(IoPriorityArgs { class, priority }),\n        )\n    }\n\n    fn set_mempolicy(&self, mode: i32, nodemask: &[libc::c_ulong], maxnode: u64) -> Result<()> {\n        self.mocks.act(\n            ArgName::MemPolicy,\n            Box::new(MemPolicyArgs {\n                mode,\n                nodemask: nodemask.to_vec(),\n                maxnode,\n            }),\n        )\n    }\n\n    fn umount2(&self, target: &Path, flags: MntFlags) -> Result<()> {\n        self.mocks.act(\n            ArgName::UMount2,\n            Box::new(UMount2Args {\n                target: target.to_owned(),\n                flags,\n            }),\n        )\n    }\n\n    fn get_uid(&self) -> Uid {\n        self.mock_id.borrow().uid\n    }\n\n    fn get_gid(&self) -> Gid {\n        self.mock_id.borrow().gid\n    }\n\n    fn get_euid(&self) -> Uid {\n        self.mock_id.borrow().euid\n    }\n\n    fn get_egid(&self) -> Gid {\n        self.mock_id.borrow().egid\n    }\n\n    fn personality(&self, _: PersonalityDomain) -> Result<()> {\n        todo!()\n    }\n}\n\nimpl TestHelperSyscall {\n    pub fn set_ret_err(&self, name: ArgName, err: fn() -> Result<()>) {\n        self.mocks.fetch_mut(name).ret_err = Some(err);\n        self.set_ret_err_times(name, 1);\n    }\n\n    pub fn set_ret_err_times(&self, name: ArgName, times: usize) {\n        self.mocks.fetch_mut(name).ret_err_times = times;\n    }\n\n    pub fn get_setns_args(&self) -> Vec<(i32, CloneFlags)> {\n        self.mocks\n            .fetch(ArgName::Namespace)\n            .values\n            .iter()\n            .map(|x| *x.downcast_ref::<(i32, CloneFlags)>().unwrap())\n            .collect::<Vec<(i32, CloneFlags)>>()\n    }\n\n    pub fn get_unshare_args(&self) -> Vec<CloneFlags> {\n        self.mocks\n            .fetch(ArgName::Unshare)\n            .values\n            .iter()\n            .map(|x| *x.downcast_ref::<CloneFlags>().unwrap())\n            .collect::<Vec<CloneFlags>>()\n    }\n\n    pub fn get_set_capability_args(&self) -> Vec<(CapSet, CapsHashSet)> {\n        self.mocks\n            .fetch(ArgName::Capability)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<(CapSet, CapsHashSet)>().unwrap().clone())\n            .collect::<Vec<(CapSet, CapsHashSet)>>()\n    }\n\n    pub fn get_mount_args(&self) -> Vec<MountArgs> {\n        self.mocks\n            .fetch(ArgName::Mount)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<MountArgs>().unwrap().clone())\n            .collect::<Vec<MountArgs>>()\n    }\n\n    pub fn get_mount_from_fd_args(&self) -> Vec<MountFromFdArgs> {\n        self.mocks\n            .fetch(ArgName::MountFromFd)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<MountFromFdArgs>().unwrap().clone())\n            .collect::<Vec<MountFromFdArgs>>()\n    }\n\n    pub fn get_symlink_args(&self) -> Vec<(PathBuf, PathBuf)> {\n        self.mocks\n            .fetch(ArgName::Symlink)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<(PathBuf, PathBuf)>().unwrap().clone())\n            .collect::<Vec<(PathBuf, PathBuf)>>()\n    }\n\n    pub fn get_mknod_args(&self) -> Vec<MknodArgs> {\n        self.mocks\n            .fetch(ArgName::Mknod)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<MknodArgs>().unwrap().clone())\n            .collect::<Vec<MknodArgs>>()\n    }\n\n    pub fn get_chown_args(&self) -> Vec<ChownArgs> {\n        self.mocks\n            .fetch(ArgName::Chown)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<ChownArgs>().unwrap().clone())\n            .collect::<Vec<ChownArgs>>()\n    }\n\n    pub fn get_hostname_args(&self) -> Vec<String> {\n        self.mocks\n            .fetch(ArgName::Hostname)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<String>().unwrap().clone())\n            .collect::<Vec<String>>()\n    }\n\n    pub fn get_domainname_args(&self) -> Vec<String> {\n        self.mocks\n            .fetch(ArgName::Domainname)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<String>().unwrap().clone())\n            .collect::<Vec<String>>()\n    }\n\n    pub fn get_groups_args(&self) -> Vec<Gid> {\n        self.mocks\n            .fetch(ArgName::Groups)\n            .values\n            .iter()\n            .flat_map(|x| x.downcast_ref::<Vec<Gid>>().unwrap().clone())\n            .collect::<Vec<Gid>>()\n    }\n\n    pub fn get_io_priority_args(&self) -> Vec<IoPriorityArgs> {\n        self.mocks\n            .fetch(ArgName::IoPriority)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<IoPriorityArgs>().unwrap().clone())\n            .collect::<Vec<IoPriorityArgs>>()\n    }\n\n    pub fn get_mempolicy_args(&self) -> Vec<MemPolicyArgs> {\n        self.mocks\n            .fetch(ArgName::MemPolicy)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<MemPolicyArgs>().unwrap().clone())\n            .collect::<Vec<MemPolicyArgs>>()\n    }\n\n    pub fn get_umount_args(&self) -> Vec<UMount2Args> {\n        self.mocks\n            .fetch(ArgName::UMount2)\n            .values\n            .iter()\n            .map(|x| x.downcast_ref::<UMount2Args>().unwrap().clone())\n            .collect::<Vec<UMount2Args>>()\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/test_utils.rs",
    "content": "use nix::sys::wait;\nuse serde::{Deserialize, Serialize};\n\n// Normally, error types are not implemented as serialize/deserialize, but to\n// pass the error from the child process to the parent process, we need to\n// implement an error type that can be serialized and deserialized.\n#[derive(Debug, Serialize, Deserialize)]\nstruct ErrorEnclosure {\n    source: Option<Box<ErrorEnclosure>>,\n    description: String,\n}\n\nimpl ErrorEnclosure {\n    pub fn new<T>(e: &T) -> ErrorEnclosure\n    where\n        T: ?Sized + std::error::Error,\n    {\n        ErrorEnclosure {\n            description: e.to_string(),\n            source: e.source().map(|s| Box::new(ErrorEnclosure::new(s))),\n        }\n    }\n}\n\nimpl std::fmt::Display for ErrorEnclosure {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.description)\n    }\n}\n\nimpl std::error::Error for ErrorEnclosure {\n    fn source(&self) -> Option<&(dyn 'static + std::error::Error)> {\n        self.source\n            .as_ref()\n            .map(|source| &**source as &(dyn 'static + std::error::Error))\n    }\n\n    fn description(&self) -> &str {\n        &self.description\n    }\n}\n\ntype ClosureResult = Result<(), ErrorEnclosure>;\n\n#[derive(Debug, thiserror::Error)]\npub enum TestError {\n    #[error(\"failed to create channel\")]\n    Channel(#[from] crate::channel::ChannelError),\n    #[error(\"failed to fork\")]\n    Fork(#[source] nix::Error),\n    #[error(\"failed to wait for child process\")]\n    Wait(#[source] nix::Error),\n    #[error(\"failed to run function in child process\")]\n    Execution(#[source] Box<dyn std::error::Error + Send + Sync>),\n    #[error(\"the closure caused the child process to panic\")]\n    Panic,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum TestCallbackError {\n    #[error(\"{0}\")]\n    Custom(String),\n    #[error(\"{0:?}\")]\n    Other(#[from] Box<dyn std::error::Error + Send + Sync>),\n}\n\nimpl From<&str> for TestCallbackError {\n    fn from(s: &str) -> Self {\n        TestCallbackError::Custom(s.to_string())\n    }\n}\n\nimpl From<String> for TestCallbackError {\n    fn from(s: String) -> Self {\n        TestCallbackError::Custom(s)\n    }\n}\n\npub fn test_in_child_process<F>(cb: F) -> Result<(), TestError>\nwhere\n    F: FnOnce() -> Result<(), TestCallbackError> + std::panic::UnwindSafe,\n{\n    let (mut sender, mut receiver) = crate::channel::channel::<ClosureResult>()?;\n    match unsafe { nix::unistd::fork().map_err(TestError::Fork)? } {\n        nix::unistd::ForkResult::Parent { child } => {\n            // Close unused senders\n            sender.close().map_err(TestError::Channel)?;\n            let res = receiver.recv().map_err(TestError::Channel)?;\n            wait::waitpid(child, None).map_err(TestError::Wait)?;\n            res.map_err(|err| TestError::Execution(Box::new(err)))?;\n        }\n        nix::unistd::ForkResult::Child => {\n            // Close unused receiver in the child\n            receiver.close().map_err(TestError::Channel)?;\n            let test_result = match std::panic::catch_unwind(cb) {\n                Ok(ret) => ret.map_err(|err| ErrorEnclosure::new(&err)),\n                Err(_) => Err(ErrorEnclosure::new(&TestError::Panic)),\n            };\n\n            // If we can't send the error to the parent process, there is\n            // nothing we can do other than exit properly.\n            let _ = sender.send(test_result);\n            std::process::exit(0);\n        }\n    };\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use core::panic;\n\n    use anyhow::{Result, bail};\n\n    use super::*;\n\n    #[test]\n    fn test_child_process() -> Result<()> {\n        if test_in_child_process(|| Err(TestCallbackError::Custom(\"test error\".to_string())))\n            .is_ok()\n        {\n            bail!(\"expecting the child process to return an error\")\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_panic_child_process() -> Result<()> {\n        let ret = test_in_child_process(|| {\n            panic!(\"test panic\");\n        });\n        if ret.is_ok() {\n            bail!(\"expecting the child process to panic\")\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/tty.rs",
    "content": "//! tty (teletype) for user-system interaction\n//!\n//! This module handles console/TTY setup for containers.\n//!\n//! Console setup is done AFTER pivot_root (following runc's approach).\n//! This follows runc's approach in prepareRootfs():\n//! 1. pivot_root is called first\n//! 2. Create PTY pair from /dev/pts/ptmx (container's devpts)\n//! 3. Mount PTY slave onto /dev/console\n//! 4. Send PTY master to console socket\n//! 5. Set controlling terminal and connect stdio\n//!\n//! See: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/rootfs_linux.go\n\nuse std::env;\nuse std::io::IoSlice;\nuse std::os::fd::OwnedFd;\nuse std::os::unix::fs::{OpenOptionsExt, symlink};\nuse std::os::unix::io::AsRawFd;\nuse std::os::unix::prelude::RawFd;\nuse std::path::{Path, PathBuf};\n\nuse nix::sys::socket::{self, UnixAddr};\nuse nix::sys::stat::{SFlag, major, minor};\nuse nix::sys::statfs::FsType;\nuse nix::unistd::{close, dup2};\n\nuse crate::syscall::Syscall;\nuse crate::utils::{VerifyInodeError, verify_inode};\n\n#[derive(Debug)]\npub enum StdIO {\n    Stdin = 0,\n    Stdout = 1,\n    Stderr = 2,\n}\n\nimpl From<StdIO> for i32 {\n    fn from(value: StdIO) -> Self {\n        match value {\n            StdIO::Stdin => 0,\n            StdIO::Stdout => 1,\n            StdIO::Stderr => 2,\n        }\n    }\n}\n\nimpl std::fmt::Display for StdIO {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            StdIO::Stdin => write!(f, \"stdin\"),\n            StdIO::Stdout => write!(f, \"stdout\"),\n            StdIO::Stderr => write!(f, \"stderr\"),\n        }\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum TTYError {\n    #[error(\"failed to connect/duplicate {stdio}\")]\n    ConnectStdIO { source: nix::Error, stdio: StdIO },\n    #[error(\"failed to create console socket\")]\n    CreateConsoleSocket {\n        source: nix::Error,\n        socket_name: String,\n    },\n    #[error(\"failed to symlink console socket into container_dir\")]\n    Symlink {\n        source: std::io::Error,\n        linked: Box<PathBuf>,\n        console_socket_path: Box<PathBuf>,\n    },\n    #[error(\"invalid socket name: {socket_name:?}\")]\n    InvalidSocketName {\n        socket_name: String,\n        source: nix::Error,\n    },\n    #[error(\"failed to create console socket fd\")]\n    CreateConsoleSocketFd { source: nix::Error },\n    #[error(\"could not create pseudo terminal\")]\n    CreatePseudoTerminal { source: nix::Error },\n    #[error(\"failed to send pty master\")]\n    SendPtyMaster { source: nix::Error },\n    #[error(\"could not close console socket\")]\n    CloseConsoleSocket { source: nix::Error },\n    #[error(\"failed to create /dev/console\")]\n    CreateDevConsole { source: std::io::Error },\n    #[error(\"failed to mount pty on /dev/console\")]\n    MountConsole {\n        source: crate::syscall::SyscallError,\n    },\n    #[error(\"invalid PTY device: {reason}\")]\n    InvalidPty { reason: String },\n    #[error(\"stat operation failed\")]\n    Stat(#[from] nix::Error),\n}\n\ntype Result<T> = std::result::Result<T, TTYError>;\n\n// TODO: Handling when there isn't console-socket.\npub fn setup_console_socket(\n    container_dir: &Path,\n    console_socket_path: &Path,\n    socket_name: &str,\n) -> Result<OwnedFd> {\n    struct CurrentDirGuard {\n        path: PathBuf,\n    }\n    impl Drop for CurrentDirGuard {\n        fn drop(&mut self) {\n            let _ = env::set_current_dir(&self.path);\n        }\n    }\n    // Move into the container directory to avoid sun family conflicts with long socket path names.\n    // ref: https://github.com/youki-dev/youki/issues/2910\n\n    let prev_dir = env::current_dir().unwrap();\n    let _ = env::set_current_dir(container_dir);\n    let _guard = CurrentDirGuard { path: prev_dir };\n\n    let linked = PathBuf::from(socket_name);\n\n    symlink(console_socket_path, &linked).map_err(|err| TTYError::Symlink {\n        source: err,\n        linked: linked.to_path_buf().into(),\n        console_socket_path: console_socket_path.to_path_buf().into(),\n    })?;\n    let csocketfd = socket::socket(\n        socket::AddressFamily::Unix,\n        socket::SockType::Stream,\n        socket::SockFlag::empty(),\n        None,\n    )\n    .map_err(|err| TTYError::CreateConsoleSocketFd { source: err })?;\n    socket::connect(\n        csocketfd.as_raw_fd(),\n        &socket::UnixAddr::new(linked.as_path()).map_err(|err| TTYError::InvalidSocketName {\n            source: err,\n            socket_name: socket_name.to_string(),\n        })?,\n    )\n    .map_err(|e| TTYError::CreateConsoleSocket {\n        source: e,\n        socket_name: socket_name.to_string(),\n    })?;\n\n    Ok(csocketfd)\n}\n\n/// Device numbers from Linux kernel headers.\n/// TTYAUX_MAJOR from <linux/major.h>\nconst PTMX_MAJOR: u64 = 5;\n/// From mknod_ptmx in fs/devpts/inode.c\nconst PTMX_MINOR: u64 = 2;\n/// From mknod_ptmx in fs/devpts/inode.c\nconst PTMX_INO: u64 = 2;\n/// PTY slave major number\nconst PTY_SLAVE_MAJOR: u64 = 136;\n/// DEVPTS_SUPER_MAGIC from <linux/magic.h>\n/// Note: Using libc::DEVPTS_SUPER_MAGIC is not available, so we define it here.\n/// The type must match nix::sys::statfs::FsType's inner type (fs_type_t),\n/// which is i64 on glibc and u64 on musl.\n#[cfg(target_env = \"musl\")]\nconst DEVPTS_SUPER_MAGIC: u64 = 0x1cd1;\n#[cfg(not(target_env = \"musl\"))]\nconst DEVPTS_SUPER_MAGIC: i64 = 0x1cd1;\n/// Path to the PTY master device\nconst PTMX_PATH: &[u8] = b\"/dev/ptmx\";\n/// Path to the console device\nconst CONSOLE_PATH: &str = \"/dev/console\";\n\n/// Verify that the ptmx handle points to a real /dev/pts/ptmx device.\n///\n/// This follows runc's checkPtmxHandle pattern (CVE-2025-52565 mitigation):\n/// - Must be on a real devpts mount\n/// - Must have the correct inode number (2)\n/// - Must be a character device with major:minor = 5:2\n///\n/// Ref: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/console_linux.go\nfn verify_ptmx_handle(ptmx: &OwnedFd) -> Result<()> {\n    verify_inode(ptmx, |stat, fs_stat| {\n        // 1. Check filesystem type is devpts\n        if fs_stat.filesystem_type() != FsType(DEVPTS_SUPER_MAGIC) {\n            return Err(VerifyInodeError::Verification(format!(\n                \"ptmx handle is not on a real devpts mount: super magic is {:#x}\",\n                fs_stat.filesystem_type().0\n            )));\n        }\n\n        // 2. Check inode number\n        if stat.st_ino != PTMX_INO {\n            return Err(VerifyInodeError::Verification(format!(\n                \"ptmx handle has wrong inode number: {}\",\n                stat.st_ino\n            )));\n        }\n\n        // 3. Check it's a character device with correct major:minor\n        let mode_type = SFlag::from_bits_truncate(stat.st_mode) & SFlag::S_IFMT;\n        let dev_major = major(stat.st_rdev);\n        let dev_minor = minor(stat.st_rdev);\n\n        if mode_type != SFlag::S_IFCHR || dev_major != PTMX_MAJOR || dev_minor != PTMX_MINOR {\n            return Err(VerifyInodeError::Verification(format!(\n                \"ptmx handle is not a real char ptmx device: ftype {:#x} {}:{}\",\n                mode_type.bits(),\n                dev_major,\n                dev_minor\n            )));\n        }\n\n        tracing::debug!(\n            ptmx_fd = ptmx.as_raw_fd(),\n            ino = stat.st_ino,\n            \"verified ptmx handle\"\n        );\n        Ok(())\n    })\n    .map_err(|e| TTYError::InvalidPty {\n        reason: e.to_string(),\n    })\n}\n\n/// Verify that the slave handle points to a real PTY slave device.\n///\n/// This validates (CVE-2025-52565 mitigation):\n/// - Must be on a real devpts mount\n/// - Must be a character device with PTY slave major number (136)\n///\n/// Ref: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/console_linux.go\nfn verify_pty_slave(slave: &OwnedFd) -> Result<()> {\n    verify_inode(slave, |stat, fs_stat| {\n        // 1. Check filesystem type is devpts\n        if fs_stat.filesystem_type() != FsType(DEVPTS_SUPER_MAGIC) {\n            return Err(VerifyInodeError::Verification(format!(\n                \"slave handle is not on a real devpts mount: super magic is {:#x}\",\n                fs_stat.filesystem_type().0\n            )));\n        }\n\n        // 2. Check it's a character device with PTY slave major number\n        let mode_type = SFlag::from_bits_truncate(stat.st_mode) & SFlag::S_IFMT;\n        let dev_major = major(stat.st_rdev);\n\n        if mode_type != SFlag::S_IFCHR || dev_major != PTY_SLAVE_MAJOR {\n            return Err(VerifyInodeError::Verification(format!(\n                \"slave handle is not a real PTY slave device: ftype {:#x} major {}\",\n                mode_type.bits(),\n                dev_major\n            )));\n        }\n\n        tracing::debug!(\n            slave_fd = slave.as_raw_fd(),\n            major = dev_major,\n            minor = minor(stat.st_rdev),\n            \"verified PTY slave\"\n        );\n        Ok(())\n    })\n    .map_err(|e| TTYError::InvalidPty {\n        reason: e.to_string(),\n    })\n}\n\n/// Setup console AFTER pivot_root.\n///\n/// This function should be called AFTER pivot_root. This follows runc's approach:\n/// setupConsole is called after pivotRoot in prepareRootfs.\n///\n/// The process:\n/// 1. Create PTY pair from /dev/pts/ptmx (we're already in the container)\n/// 2. Optionally mount PTY slave on /dev/console (bind mount) - only for init\n/// 3. Send PTY master to console socket\n/// 4. Set controlling terminal\n/// 5. Connect stdio to PTY slave\n///\n/// # Arguments\n/// * `console_fd` - The console socket file descriptor\n/// * `mount` - Whether to mount PTY slave on /dev/console (true for init, false for exec)\n///\n/// By creating PTY from container's devpts, the PTY belongs to a mount that\n/// exists within the container's namespace, which is required for CRIU checkpoint.\n///\n/// See: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/rootfs_linux.go\npub fn setup_console(syscall: &dyn Syscall, console_fd: RawFd, mount: bool) -> Result<()> {\n    // Create PTY pair from /dev/pts/ptmx\n    // After pivot_root, /dev/pts points to the container's devpts\n    let openpty_result = nix::pty::openpty(None, None)\n        .map_err(|err| TTYError::CreatePseudoTerminal { source: err })?;\n\n    let master = &openpty_result.master;\n    let slave = &openpty_result.slave;\n\n    // Verify both master and slave are real PTY devices (CVE-2025-52565 mitigation)\n    verify_ptmx_handle(master)?;\n    verify_pty_slave(slave)?;\n\n    // Mount PTY slave on /dev/console (only for init container)\n    if mount {\n        mount_console(syscall, slave)?;\n    }\n\n    // Send PTY master to console socket\n    let pty_name: &[u8] = PTMX_PATH;\n    let iov = [IoSlice::new(pty_name)];\n    let fds = [master.as_raw_fd()];\n    let cmsg = socket::ControlMessage::ScmRights(&fds);\n    socket::sendmsg::<UnixAddr>(console_fd, &iov, &[cmsg], socket::MsgFlags::empty(), None)\n        .map_err(|err| TTYError::SendPtyMaster { source: err })?;\n\n    // Set controlling terminal\n    if unsafe { libc::ioctl(slave.as_raw_fd(), libc::TIOCSCTTY) } < 0 {\n        tracing::warn!(\"could not TIOCSCTTY\");\n    };\n\n    // Connect stdio to PTY slave\n    connect_stdio(&slave.as_raw_fd(), &slave.as_raw_fd(), &slave.as_raw_fd())?;\n\n    // Close console socket\n    close(console_fd).map_err(|err| TTYError::CloseConsoleSocket { source: err })?;\n\n    Ok(())\n}\n\n/// Mount PTY slave on /dev/console.\n///\n/// This bind-mounts the PTY slave device onto /dev/console so programs\n/// that operate on /dev/console use the container's PTY.\n///\n/// This is called AFTER pivot_root, so we mount onto /dev/console directly.\n/// Uses FD-based mounting to avoid path resolution vulnerabilities (CVE-2025-52565).\n///\n/// See: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/rootfs_linux.go\nfn mount_console(syscall: &dyn Syscall, slave: &OwnedFd) -> Result<()> {\n    use std::fs::OpenOptions;\n\n    let console_path = Path::new(CONSOLE_PATH);\n\n    tracing::debug!(\n        slave_fd = slave.as_raw_fd(),\n        \"mounting PTY on {CONSOLE_PATH}\"\n    );\n\n    // Create /dev/console mount target.\n    // O_NOFOLLOW: prevent symlink attacks (CVE-2025-52565)\n    // O_CLOEXEC: close on exec\n    // Ref: https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/rootfs_linux.go\n    OpenOptions::new()\n        .create(true)\n        .write(true)\n        .custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC)\n        .mode(0o666)\n        .open(console_path)\n        .map_err(|err| {\n            tracing::error!(?err, \"failed to create {CONSOLE_PATH}\");\n            TTYError::CreateDevConsole { source: err }\n        })?;\n\n    // Bind mount the PTY slave onto /dev/console using FD-based mounting.\n    // This avoids path resolution vulnerabilities (CVE-2025-52565).\n    syscall.mount_from_fd(slave, console_path).map_err(|err| {\n        tracing::error!(\n            ?err,\n            slave_fd = slave.as_raw_fd(),\n            \"failed to bind mount pty on {CONSOLE_PATH}\"\n        );\n        TTYError::MountConsole { source: err }\n    })?;\n\n    tracing::debug!(\n        slave_fd = slave.as_raw_fd(),\n        \"mounted PTY on {CONSOLE_PATH}\"\n    );\n    Ok(())\n}\n\nfn connect_stdio(stdin: &RawFd, stdout: &RawFd, stderr: &RawFd) -> Result<()> {\n    dup2(stdin.as_raw_fd(), StdIO::Stdin.into()).map_err(|err| TTYError::ConnectStdIO {\n        source: err,\n        stdio: StdIO::Stdin,\n    })?;\n    dup2(stdout.as_raw_fd(), StdIO::Stdout.into()).map_err(|err| TTYError::ConnectStdIO {\n        source: err,\n        stdio: StdIO::Stdout,\n    })?;\n    // FIXME: Rarely does it fail.\n    // error message: `Error: Resource temporarily unavailable (os error 11)`\n    dup2(stderr.as_raw_fd(), StdIO::Stderr.into()).map_err(|err| TTYError::ConnectStdIO {\n        source: err,\n        stdio: StdIO::Stderr,\n    })?;\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs::File;\n    use std::os::fd::IntoRawFd;\n    use std::os::unix::net::UnixListener;\n\n    use anyhow::{Ok, Result};\n    use serial_test::serial;\n\n    use super::*;\n\n    const CONSOLE_SOCKET: &str = \"console-socket\";\n\n    #[test]\n    #[serial]\n    fn test_setup_console_socket() -> Result<()> {\n        let testdir = tempfile::tempdir()?;\n        let socket_path = Path::join(testdir.path(), \"test-socket\");\n        let lis = UnixListener::bind(&socket_path);\n        assert!(lis.is_ok());\n        let fd = setup_console_socket(testdir.path(), &socket_path, CONSOLE_SOCKET)?;\n        assert_ne!(fd.as_raw_fd(), -1);\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_setup_console_socket_empty() -> Result<()> {\n        let testdir = tempfile::tempdir()?;\n        let socket_path = Path::join(testdir.path(), \"test-socket\");\n        let fd = setup_console_socket(testdir.path(), &socket_path, CONSOLE_SOCKET);\n        assert!(fd.is_err());\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_setup_console_socket_invalid() -> Result<()> {\n        let testdir = tempfile::tempdir()?;\n        let socket_path = Path::join(testdir.path(), \"test-socket\");\n        let _socket = File::create(Path::join(testdir.path(), \"console-socket\"));\n        assert!(_socket.is_ok());\n        let fd = setup_console_socket(testdir.path(), &socket_path, CONSOLE_SOCKET);\n        assert!(fd.is_err());\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_setup_console() -> Result<()> {\n        use crate::syscall::syscall::create_syscall;\n\n        let testdir = tempfile::tempdir()?;\n        let socket_path = Path::join(testdir.path(), \"test-socket\");\n\n        // duplicate the existing std* fds\n        // we need to restore them later, and we cannot simply store them\n        // as they themselves get modified in setup_console\n        let old_stdin: RawFd = nix::unistd::dup(StdIO::Stdin.into())?;\n        let old_stdout: RawFd = nix::unistd::dup(StdIO::Stdout.into())?;\n        let old_stderr: RawFd = nix::unistd::dup(StdIO::Stderr.into())?;\n\n        let lis = UnixListener::bind(&socket_path);\n        assert!(lis.is_ok());\n        let fd = setup_console_socket(testdir.path(), &socket_path, CONSOLE_SOCKET)?;\n        // This test verifies PTY setup behavior that occurs after pivot_root.\n        // mount=false because mounting /dev/console requires an actual container\n        // environment with proper namespace setup (pivot_root completed).\n        let syscall = create_syscall();\n        let status = setup_console(syscall.as_ref(), fd.into_raw_fd(), false);\n\n        // restore the original std* before doing final assert\n        dup2(old_stdin, StdIO::Stdin.into())?;\n        dup2(old_stdout, StdIO::Stdout.into())?;\n        dup2(old_stderr, StdIO::Stderr.into())?;\n\n        assert!(status.is_ok(), \"setup_console failed: {:?}\", status);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_verify_pty_slave_with_real_pty() -> Result<()> {\n        // Allocate a real PTY pair\n        let openpty_result = nix::pty::openpty(None, None)\n            .map_err(|e| TTYError::CreatePseudoTerminal { source: e })?;\n\n        // Verify slave handle should succeed\n        let result = verify_pty_slave(&openpty_result.slave);\n        assert!(result.is_ok(), \"verify_pty_slave failed: {:?}\", result);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_verify_ptmx_handle_with_real_pty() -> Result<()> {\n        // Allocate a real PTY pair\n        let openpty_result = nix::pty::openpty(None, None)\n            .map_err(|e| TTYError::CreatePseudoTerminal { source: e })?;\n\n        // Verify ptmx handle should succeed\n        let result = verify_ptmx_handle(&openpty_result.master);\n        assert!(result.is_ok(), \"verify_ptmx_handle failed: {:?}\", result);\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_verify_ptmx_handle_with_regular_file() {\n        use std::fs::File;\n        use std::os::fd::AsFd;\n\n        use tempfile::tempfile;\n\n        // Create a regular file\n        let file: File = tempfile().expect(\"failed to create tempfile\");\n        let fd = file.as_fd().try_clone_to_owned().unwrap();\n\n        // Verify should fail for regular file\n        let result = verify_ptmx_handle(&fd);\n        assert!(\n            result.is_err(),\n            \"verify_ptmx_handle should fail for regular file\"\n        );\n\n        if let Err(TTYError::InvalidPty { reason }) = result {\n            assert!(\n                reason.contains(\"devpts\") || reason.contains(\"inode\") || reason.contains(\"device\"),\n                \"unexpected error reason: {}\",\n                reason\n            );\n        }\n    }\n\n    #[test]\n    fn test_verify_pty_slave_with_regular_file() {\n        use std::fs::File;\n        use std::os::fd::AsFd;\n\n        use tempfile::tempfile;\n\n        // Create a regular file\n        let file: File = tempfile().expect(\"failed to create tempfile\");\n        let fd = file.as_fd().try_clone_to_owned().unwrap();\n\n        // Verify should fail for regular file\n        let result = verify_pty_slave(&fd);\n        assert!(\n            result.is_err(),\n            \"verify_pty_slave should fail for regular file\"\n        );\n\n        if let Err(TTYError::InvalidPty { reason }) = result {\n            assert!(\n                reason.contains(\"devpts\") || reason.contains(\"device\"),\n                \"unexpected error reason: {}\",\n                reason\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/user_ns.rs",
    "content": "use std::path::{Path, PathBuf};\nuse std::process::Command;\nuse std::{env, fs};\n\nuse nix::unistd::Pid;\nuse oci_spec::runtime::{Linux, LinuxIdMapping, LinuxNamespace, LinuxNamespaceType, Mount, Spec};\n\nuse crate::error::MissingSpecError;\nuse crate::namespaces::{NamespaceError, Namespaces};\nuse crate::syscall::syscall::{Syscall, create_syscall};\nuse crate::utils;\n// Wrap the uid/gid path function into a struct for dependency injection. This\n// allows us to mock the id mapping logic in unit tests by using a different\n// base path other than `/proc`.\n#[derive(Debug, Clone)]\npub struct UserNamespaceIDMapper {\n    base_path: PathBuf,\n}\n\nimpl Default for UserNamespaceIDMapper {\n    fn default() -> Self {\n        Self {\n            // By default, the `uid_map` and `gid_map` files are located in the\n            // `/proc` directory. In the production code, we can use the\n            // default.\n            base_path: PathBuf::from(\"/proc\"),\n        }\n    }\n}\n\nimpl UserNamespaceIDMapper {\n    // In production code, we can direct use the `new` function without the\n    // need to worry about the default.\n    pub fn new() -> Self {\n        Default::default()\n    }\n\n    pub fn get_uid_path(&self, pid: &Pid) -> PathBuf {\n        self.base_path.join(pid.to_string()).join(\"uid_map\")\n    }\n    pub fn get_gid_path(&self, pid: &Pid) -> PathBuf {\n        self.base_path.join(pid.to_string()).join(\"gid_map\")\n    }\n\n    #[cfg(test)]\n    pub fn ensure_uid_path(&self, pid: &Pid) -> std::result::Result<(), std::io::Error> {\n        std::fs::create_dir_all(self.get_uid_path(pid).parent().unwrap())?;\n\n        Ok(())\n    }\n\n    #[cfg(test)]\n    pub fn ensure_gid_path(&self, pid: &Pid) -> std::result::Result<(), std::io::Error> {\n        std::fs::create_dir_all(self.get_gid_path(pid).parent().unwrap())?;\n\n        Ok(())\n    }\n\n    #[cfg(test)]\n    // In test, we need to fake the base path to a temporary directory.\n    pub fn new_test(path: PathBuf) -> Self {\n        Self { base_path: path }\n    }\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum UserNamespaceError {\n    #[error(transparent)]\n    MissingSpec(#[from] crate::error::MissingSpecError),\n    #[error(\"user namespace definition is invalid\")]\n    NoUserNamespace,\n    #[error(\"invalid spec for new user namespace container\")]\n    InvalidSpec(#[from] ValidateSpecError),\n    #[error(\"failed to read unprivileged userns clone\")]\n    ReadUnprivilegedUsernsClone(#[source] std::io::Error),\n    #[error(\"failed to parse unprivileged userns clone\")]\n    ParseUnprivilegedUsernsClone(#[source] std::num::ParseIntError),\n    #[error(\"unknown userns clone value\")]\n    UnknownUnprivilegedUsernsClone(u8),\n    #[error(transparent)]\n    IDMapping(#[from] MappingError),\n    #[error(transparent)]\n    OtherIO(#[from] std::io::Error),\n}\n\ntype Result<T> = std::result::Result<T, UserNamespaceError>;\n\n#[derive(Debug, thiserror::Error)]\npub enum ValidateSpecError {\n    #[error(transparent)]\n    MissingSpec(#[from] crate::error::MissingSpecError),\n    #[error(\"new user namespace requires valid uid mappings\")]\n    NoUIDMappings,\n    #[error(\"new user namespace requires valid gid mappings\")]\n    NoGIDMapping,\n    #[error(\"no mount in spec\")]\n    NoMountSpec,\n    #[error(\"unprivileged user can't set supplementary groups\")]\n    UnprivilegedUser,\n    #[error(\"supplementary group needs to be mapped in the gid mappings\")]\n    GidNotMapped(u32),\n    #[error(\"failed to parse ID\")]\n    ParseID(#[source] std::num::ParseIntError),\n    #[error(\"mount options require mapping valid uid inside the container with new user namespace\")]\n    MountGidMapping(u32),\n    #[error(\"mount options require mapping valid gid inside the container with new user namespace\")]\n    MountUidMapping(u32),\n    #[error(transparent)]\n    Namespaces(#[from] NamespaceError),\n    #[error(transparent)]\n    OtherIO(#[from] std::io::Error),\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum MappingError {\n    #[error(\"newuidmap/newgidmap binaries could not be found in path\")]\n    BinaryNotFound,\n    #[error(\"could not find PATH\")]\n    NoPathEnv,\n    #[error(\"failed to execute newuidmap/newgidmap\")]\n    Execute(#[source] std::io::Error),\n    #[error(\"at least one id mapping needs to be defined\")]\n    NoIDMapping,\n    #[error(\"failed to write id mapping\")]\n    WriteIDMapping(#[source] std::io::Error),\n}\n\n#[derive(Debug, Clone, Default)]\npub struct UserNamespaceConfig {\n    /// Location of the newuidmap binary\n    pub newuidmap: Option<PathBuf>,\n    /// Location of the newgidmap binary\n    pub newgidmap: Option<PathBuf>,\n    /// Mappings for user ids\n    pub(crate) uid_mappings: Option<Vec<LinuxIdMapping>>,\n    /// Mappings for group ids\n    pub(crate) gid_mappings: Option<Vec<LinuxIdMapping>>,\n    /// Info on the user namespaces\n    pub user_namespace: Option<LinuxNamespace>,\n    /// Is the container requested by a privileged user\n    pub privileged: bool,\n    /// Path to the id mappings\n    pub id_mapper: UserNamespaceIDMapper,\n}\n\nimpl UserNamespaceConfig {\n    pub fn new(spec: &Spec) -> Result<Option<Self>> {\n        let syscall = create_syscall();\n        let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n        let namespaces = Namespaces::try_from(linux.namespaces().as_ref())\n            .map_err(ValidateSpecError::Namespaces)?;\n        let user_namespace = namespaces\n            .get(LinuxNamespaceType::User)\n            .map_err(ValidateSpecError::Namespaces)?;\n\n        if user_namespace.is_some() && user_namespace.unwrap().path().is_none() {\n            tracing::debug!(\"container with new user namespace should be created\");\n\n            validate_spec_for_new_user_ns(spec, &*syscall).map_err(|err| {\n                tracing::error!(\"failed to validate spec for new user namespace: {}\", err);\n                err\n            })?;\n            let mut user_ns_config = UserNamespaceConfig::try_from(linux)?;\n            if let Some((uid_binary, gid_binary)) = lookup_map_binaries(linux)? {\n                user_ns_config.newuidmap = Some(uid_binary);\n                user_ns_config.newgidmap = Some(gid_binary);\n            }\n\n            Ok(Some(user_ns_config))\n        } else {\n            tracing::debug!(\"this container does NOT create a new user namespace\");\n            Ok(None)\n        }\n    }\n\n    pub fn write_uid_mapping(&self, target_pid: Pid) -> Result<()> {\n        tracing::debug!(\"write UID mapping for {:?}\", target_pid);\n        if let Some(uid_mappings) = self.uid_mappings.as_ref() {\n            write_id_mapping(\n                target_pid,\n                self.id_mapper.get_uid_path(&target_pid).as_path(),\n                uid_mappings,\n                self.newuidmap.as_deref(),\n            )?;\n        }\n        Ok(())\n    }\n\n    pub fn write_gid_mapping(&self, target_pid: Pid) -> Result<()> {\n        tracing::debug!(\"write GID mapping for {:?}\", target_pid);\n        if let Some(gid_mappings) = self.gid_mappings.as_ref() {\n            write_id_mapping(\n                target_pid,\n                self.id_mapper.get_gid_path(&target_pid).as_path(),\n                gid_mappings,\n                self.newgidmap.as_deref(),\n            )?;\n        }\n        Ok(())\n    }\n\n    pub fn with_id_mapper(&mut self, mapper: UserNamespaceIDMapper) {\n        self.id_mapper = mapper\n    }\n}\n\nimpl TryFrom<&Linux> for UserNamespaceConfig {\n    type Error = UserNamespaceError;\n\n    fn try_from(linux: &Linux) -> Result<Self> {\n        let namespaces = Namespaces::try_from(linux.namespaces().as_ref())\n            .map_err(ValidateSpecError::Namespaces)?;\n        let user_namespace = namespaces\n            .get(LinuxNamespaceType::User)\n            .map_err(ValidateSpecError::Namespaces)?;\n        let syscall = create_syscall();\n        Ok(Self {\n            newuidmap: None,\n            newgidmap: None,\n            uid_mappings: linux.uid_mappings().to_owned(),\n            gid_mappings: linux.gid_mappings().to_owned(),\n            user_namespace: user_namespace.cloned(),\n            privileged: !utils::rootless_required(&*syscall)?,\n            id_mapper: UserNamespaceIDMapper::new(),\n        })\n    }\n}\n\npub fn unprivileged_user_ns_enabled() -> Result<bool> {\n    let user_ns_sysctl = Path::new(\"/proc/sys/kernel/unprivileged_userns_clone\");\n    if !user_ns_sysctl.exists() {\n        return Ok(true);\n    }\n\n    let content = fs::read_to_string(user_ns_sysctl)\n        .map_err(UserNamespaceError::ReadUnprivilegedUsernsClone)?;\n\n    match content\n        .trim()\n        .parse::<u8>()\n        .map_err(UserNamespaceError::ParseUnprivilegedUsernsClone)?\n    {\n        0 => Ok(false),\n        1 => Ok(true),\n        v => Err(UserNamespaceError::UnknownUnprivilegedUsernsClone(v)),\n    }\n}\n\n/// Validates that the spec contains the required information for\n/// creating a new user namespace\nfn validate_spec_for_new_user_ns(\n    spec: &Spec,\n    syscall: &dyn Syscall,\n) -> std::result::Result<(), ValidateSpecError> {\n    tracing::debug!(\n        ?spec,\n        \"validating spec for container with new user namespace\"\n    );\n    let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?;\n\n    let gid_mappings = linux\n        .gid_mappings()\n        .as_ref()\n        .ok_or(ValidateSpecError::NoGIDMapping)?;\n    let uid_mappings = linux\n        .uid_mappings()\n        .as_ref()\n        .ok_or(ValidateSpecError::NoUIDMappings)?;\n\n    if uid_mappings.is_empty() {\n        return Err(ValidateSpecError::NoUIDMappings);\n    }\n    if gid_mappings.is_empty() {\n        return Err(ValidateSpecError::NoGIDMapping);\n    }\n\n    validate_mounts_for_new_user_ns(\n        spec.mounts()\n            .as_ref()\n            .ok_or(ValidateSpecError::NoMountSpec)?,\n        uid_mappings,\n        gid_mappings,\n    )?;\n\n    if let Some(additional_gids) = spec\n        .process()\n        .as_ref()\n        .and_then(|process| process.user().additional_gids().as_ref())\n    {\n        let privileged = !utils::rootless_required(syscall)?;\n\n        match (privileged, additional_gids.is_empty()) {\n            (true, false) => {\n                for gid in additional_gids {\n                    if !is_id_mapped(*gid, gid_mappings) {\n                        tracing::error!(\n                            ?gid,\n                            \"gid is specified as supplementary group, but is not mapped in the user namespace\"\n                        );\n                        return Err(ValidateSpecError::GidNotMapped(*gid));\n                    }\n                }\n            }\n            (false, false) => {\n                tracing::error!(\n                    user = ?syscall.get_euid(),\n                    \"user is unprivileged. Supplementary groups cannot be set in \\\n                        a rootless container for this user due to CVE-2014-8989\",\n                );\n                return Err(ValidateSpecError::UnprivilegedUser);\n            }\n            _ => {}\n        }\n    }\n\n    Ok(())\n}\n\nfn validate_mounts_for_new_user_ns(\n    mounts: &[Mount],\n    uid_mappings: &[LinuxIdMapping],\n    gid_mappings: &[LinuxIdMapping],\n) -> std::result::Result<(), ValidateSpecError> {\n    for mount in mounts {\n        if let Some(options) = mount.options() {\n            for opt in options {\n                if opt.starts_with(\"uid=\")\n                    && !is_id_mapped(\n                        opt[4..].parse().map_err(ValidateSpecError::ParseID)?,\n                        uid_mappings,\n                    )\n                {\n                    tracing::error!(\n                        ?mount,\n                        ?opt,\n                        \"mount specifies option which is not mapped inside the container with new user namespace\"\n                    );\n                    return Err(ValidateSpecError::MountUidMapping(\n                        opt[4..].parse().map_err(ValidateSpecError::ParseID)?,\n                    ));\n                }\n\n                if opt.starts_with(\"gid=\")\n                    && !is_id_mapped(\n                        opt[4..].parse().map_err(ValidateSpecError::ParseID)?,\n                        gid_mappings,\n                    )\n                {\n                    tracing::error!(\n                        ?mount,\n                        ?opt,\n                        \"mount specifies option which is not mapped inside the container with new user namespace\"\n                    );\n                    return Err(ValidateSpecError::MountGidMapping(\n                        opt[4..].parse().map_err(ValidateSpecError::ParseID)?,\n                    ));\n                }\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn is_id_mapped(id: u32, mappings: &[LinuxIdMapping]) -> bool {\n    mappings\n        .iter()\n        .any(|m| id >= m.container_id() && id <= m.container_id() + m.size())\n}\n\n/// Looks up the location of the newuidmap and newgidmap binaries which\n/// are required to write multiple user/group mappings\npub fn lookup_map_binaries(\n    spec: &Linux,\n) -> std::result::Result<Option<(PathBuf, PathBuf)>, MappingError> {\n    if let Some(uid_mappings) = spec.uid_mappings() {\n        if uid_mappings.len() == 1 && uid_mappings.len() == 1 {\n            return Ok(None);\n        }\n\n        let uidmap = lookup_map_binary(\"newuidmap\")?;\n        let gidmap = lookup_map_binary(\"newgidmap\")?;\n\n        match (uidmap, gidmap) {\n            (Some(newuidmap), Some(newgidmap)) => Ok(Some((newuidmap, newgidmap))),\n            _ => Err(MappingError::BinaryNotFound),\n        }\n    } else {\n        Ok(None)\n    }\n}\n\nfn lookup_map_binary(binary: &str) -> std::result::Result<Option<PathBuf>, MappingError> {\n    let paths = env::var(\"PATH\").map_err(|_| MappingError::NoPathEnv)?;\n    Ok(paths\n        .split_terminator(':')\n        .map(|p| Path::new(p).join(binary))\n        .find(|p| p.exists()))\n}\n\nfn write_id_mapping(\n    pid: Pid,\n    map_file: &Path,\n    mappings: &[LinuxIdMapping],\n    map_binary: Option<&Path>,\n) -> std::result::Result<(), MappingError> {\n    tracing::debug!(\"Write ID mapping: {:?}\", mappings);\n\n    match mappings.len() {\n        0 => return Err(MappingError::NoIDMapping),\n        1 => {\n            let mapping = mappings\n                .first()\n                .and_then(|m| format!(\"{} {} {}\", m.container_id(), m.host_id(), m.size()).into())\n                .unwrap();\n            std::fs::write(map_file, &mapping).map_err(|err| {\n                tracing::error!(?err, ?map_file, ?mapping, \"failed to write uid/gid mapping\");\n                MappingError::WriteIDMapping(err)\n            })?;\n        }\n        _ => {\n            let args: Vec<String> = mappings\n                .iter()\n                .flat_map(|m| {\n                    [\n                        m.container_id().to_string(),\n                        m.host_id().to_string(),\n                        m.size().to_string(),\n                    ]\n                })\n                .collect();\n\n            // we can be certain here that map_binary will not be None,\n            // as in the lookup_map_binaries function, we return error\n            // if there are mappings.len() > 1 and binaries are not present\n            Command::new(map_binary.unwrap())\n                .arg(pid.to_string())\n                .args(args)\n                .output()\n                .map_err(|err| {\n                    tracing::error!(?err, ?map_binary, \"failed to execute newuidmap/newgidmap\");\n                    MappingError::Execute(err)\n                })?;\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n\n    use anyhow::Result;\n    use nix::unistd::getpid;\n    use oci_spec::runtime::{\n        LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespaceBuilder, SpecBuilder,\n    };\n    use rand::RngExt;\n    use serial_test::serial;\n\n    use super::*;\n\n    fn gen_u32() -> u32 {\n        rand::rng().random()\n    }\n\n    #[test]\n    fn test_validate_ok() -> Result<()> {\n        let syscall = create_syscall();\n        let userns = LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::User)\n            .build()?;\n        let uid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(gen_u32())\n                .container_id(0_u32)\n                .size(10_u32)\n                .build()?,\n        ];\n        let gid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(gen_u32())\n                .container_id(0_u32)\n                .size(10_u32)\n                .build()?,\n        ];\n        let linux = LinuxBuilder::default()\n            .namespaces(vec![userns])\n            .uid_mappings(uid_mappings)\n            .gid_mappings(gid_mappings)\n            .build()?;\n        let spec = SpecBuilder::default().linux(linux).build()?;\n        assert!(validate_spec_for_new_user_ns(&spec, &*syscall).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn test_validate_err() -> Result<()> {\n        let syscall = create_syscall();\n        let userns = LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::User)\n            .build()?;\n        let uid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(gen_u32())\n                .container_id(0_u32)\n                .size(10_u32)\n                .build()?,\n        ];\n        let gid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(gen_u32())\n                .container_id(0_u32)\n                .size(10_u32)\n                .build()?,\n        ];\n\n        let linux_uid_empty = LinuxBuilder::default()\n            .namespaces(vec![userns.clone()])\n            .uid_mappings(vec![])\n            .gid_mappings(gid_mappings.clone())\n            .build()?;\n        assert!(\n            validate_spec_for_new_user_ns(\n                &SpecBuilder::default()\n                    .linux(linux_uid_empty)\n                    .build()\n                    .unwrap(),\n                &*syscall\n            )\n            .is_err()\n        );\n\n        let linux_gid_empty = LinuxBuilder::default()\n            .namespaces(vec![userns.clone()])\n            .uid_mappings(uid_mappings.clone())\n            .gid_mappings(vec![])\n            .build()?;\n        assert!(\n            validate_spec_for_new_user_ns(\n                &SpecBuilder::default()\n                    .linux(linux_gid_empty)\n                    .build()\n                    .unwrap(),\n                &*syscall\n            )\n            .is_err()\n        );\n\n        let linux_uid_none = LinuxBuilder::default()\n            .namespaces(vec![userns.clone()])\n            .gid_mappings(gid_mappings)\n            .build()?;\n        assert!(\n            validate_spec_for_new_user_ns(\n                &SpecBuilder::default()\n                    .linux(linux_uid_none)\n                    .build()\n                    .unwrap(),\n                &*syscall\n            )\n            .is_err()\n        );\n\n        let linux_gid_none = LinuxBuilder::default()\n            .namespaces(vec![userns])\n            .uid_mappings(uid_mappings)\n            .build()?;\n        assert!(\n            validate_spec_for_new_user_ns(\n                &SpecBuilder::default()\n                    .linux(linux_gid_none)\n                    .build()\n                    .unwrap(),\n                &*syscall\n            )\n            .is_err()\n        );\n\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_write_uid_mapping() -> Result<()> {\n        let userns = LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::User)\n            .build()?;\n        let host_uid = gen_u32();\n        let host_gid = gen_u32();\n        let container_id = 0_u32;\n        let size = 10_u32;\n        let uid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(host_uid)\n                .container_id(container_id)\n                .size(size)\n                .build()?,\n        ];\n        let gid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(host_gid)\n                .container_id(container_id)\n                .size(size)\n                .build()?,\n        ];\n        let linux = LinuxBuilder::default()\n            .namespaces(vec![userns])\n            .uid_mappings(uid_mappings)\n            .gid_mappings(gid_mappings)\n            .build()?;\n        let spec = SpecBuilder::default().linux(linux).build()?;\n\n        let pid = getpid();\n        let tmp = tempfile::tempdir()?;\n        let id_mapper = UserNamespaceIDMapper {\n            base_path: tmp.path().to_path_buf(),\n        };\n        id_mapper.ensure_uid_path(&pid)?;\n\n        let mut config = UserNamespaceConfig::new(&spec)?.unwrap();\n        config.with_id_mapper(id_mapper.clone());\n        config.write_uid_mapping(pid)?;\n        assert_eq!(\n            format!(\"{container_id} {host_uid} {size}\"),\n            fs::read_to_string(id_mapper.get_uid_path(&pid))?\n        );\n        config.write_gid_mapping(pid)?;\n        Ok(())\n    }\n\n    #[test]\n    #[serial]\n    fn test_write_gid_mapping() -> Result<()> {\n        let userns = LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::User)\n            .build()?;\n        let host_uid = gen_u32();\n        let host_gid = gen_u32();\n        let container_id = 0_u32;\n        let size = 10_u32;\n        let uid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(host_uid)\n                .container_id(container_id)\n                .size(size)\n                .build()?,\n        ];\n        let gid_mappings = vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(host_gid)\n                .container_id(container_id)\n                .size(size)\n                .build()?,\n        ];\n        let linux = LinuxBuilder::default()\n            .namespaces(vec![userns])\n            .uid_mappings(uid_mappings)\n            .gid_mappings(gid_mappings)\n            .build()?;\n        let spec = SpecBuilder::default().linux(linux).build()?;\n\n        let pid = getpid();\n        let tmp = tempfile::tempdir()?;\n        let id_mapper = UserNamespaceIDMapper {\n            base_path: tmp.path().to_path_buf(),\n        };\n        id_mapper.ensure_gid_path(&pid)?;\n\n        let mut config = UserNamespaceConfig::new(&spec)?.unwrap();\n        config.with_id_mapper(id_mapper.clone());\n        config.write_gid_mapping(pid)?;\n        assert_eq!(\n            format!(\"{container_id} {host_gid} {size}\"),\n            fs::read_to_string(id_mapper.get_gid_path(&pid))?\n        );\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/utils.rs",
    "content": "//! Utility functionality\n\nuse std::collections::HashMap;\nuse std::fs::{self, DirBuilder, File};\nuse std::os::fd::{AsRawFd, OwnedFd};\nuse std::os::linux::fs::MetadataExt;\nuse std::os::unix::fs::DirBuilderExt;\nuse std::path::{Component, Path, PathBuf};\nuse std::time::Duration;\n\nuse libc::IFNAMSIZ;\nuse nix::sys::stat::{Mode, fstat};\nuse nix::sys::statfs::{Statfs, fstatfs};\nuse nix::unistd::{Uid, User};\nuse oci_spec::runtime::{LinuxNamespaceType, Spec};\n\nuse crate::error::{LibcontainerError, MissingSpecError};\nuse crate::syscall::syscall::Syscall;\nuse crate::user_ns::UserNamespaceConfig;\n\n#[derive(Debug, thiserror::Error)]\npub enum PathBufExtError {\n    #[error(\"relative path cannot be converted to the path in the container\")]\n    RelativePath,\n    #[error(\"failed to strip prefix from {path:?}\")]\n    StripPrefix {\n        path: PathBuf,\n        source: std::path::StripPrefixError,\n    },\n    #[error(\"failed to canonicalize path {path:?}\")]\n    Canonicalize {\n        path: PathBuf,\n        source: std::io::Error,\n    },\n    #[error(\"failed to get current directory\")]\n    CurrentDir { source: std::io::Error },\n}\n\npub trait PathBufExt {\n    fn as_relative(&self) -> Result<&Path, PathBufExtError>;\n    fn join_safely<P: AsRef<Path>>(&self, p: P) -> Result<PathBuf, PathBufExtError>;\n    fn canonicalize_safely(&self) -> Result<PathBuf, PathBufExtError>;\n    fn normalize(&self) -> PathBuf;\n}\n\nimpl PathBufExt for Path {\n    fn as_relative(&self) -> Result<&Path, PathBufExtError> {\n        match self.is_relative() {\n            true => Err(PathBufExtError::RelativePath),\n            false => Ok(self\n                .strip_prefix(\"/\")\n                .map_err(|e| PathBufExtError::StripPrefix {\n                    path: self.to_path_buf(),\n                    source: e,\n                })?),\n        }\n    }\n\n    fn join_safely<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, PathBufExtError> {\n        let path = path.as_ref();\n        if path.is_relative() {\n            return Ok(self.join(path));\n        }\n\n        let stripped = path\n            .strip_prefix(\"/\")\n            .map_err(|e| PathBufExtError::StripPrefix {\n                path: self.to_path_buf(),\n                source: e,\n            })?;\n        Ok(self.join(stripped))\n    }\n\n    /// Canonicalizes existing and not existing paths\n    fn canonicalize_safely(&self) -> Result<PathBuf, PathBufExtError> {\n        if self.exists() {\n            self.canonicalize()\n                .map_err(|e| PathBufExtError::Canonicalize {\n                    path: self.to_path_buf(),\n                    source: e,\n                })\n        } else {\n            if self.is_relative() {\n                let p = std::env::current_dir()\n                    .map_err(|e| PathBufExtError::CurrentDir { source: e })?\n                    .join(self);\n                return Ok(p.normalize());\n            }\n\n            Ok(self.normalize())\n        }\n    }\n\n    /// Normalizes a path. In contrast to canonicalize the path does not need to exist.\n    // adapted from https://github.com/rust-lang/cargo/blob/fede83ccf973457de319ba6fa0e36ead454d2e20/src/cargo/util/paths.rs#L61\n    fn normalize(&self) -> PathBuf {\n        let mut components = self.components().peekable();\n        let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {\n            components.next();\n            PathBuf::from(c.as_os_str())\n        } else {\n            PathBuf::new()\n        };\n\n        for component in components {\n            match component {\n                Component::Prefix(..) => unreachable!(),\n                Component::RootDir => {\n                    ret.push(component.as_os_str());\n                }\n                Component::CurDir => {}\n                Component::ParentDir => {\n                    ret.pop();\n                }\n                Component::Normal(c) => {\n                    ret.push(c);\n                }\n            }\n        }\n        ret\n    }\n}\n\npub fn parse_env(envs: &[String]) -> HashMap<String, String> {\n    envs.iter()\n        .filter_map(|e| {\n            let mut split = e.split('=');\n\n            split.next().map(|key| {\n                let value = split.collect::<Vec<&str>>().join(\"=\");\n                (key.into(), value)\n            })\n        })\n        .collect()\n}\n\n/// Get a nix::unistd::User via UID. Potential errors will be ignored.\npub fn get_unix_user(uid: Uid) -> Option<User> {\n    User::from_uid(uid).unwrap_or_default()\n}\n\n/// Get home path of a User via UID.\npub fn get_user_home(uid: u32) -> Option<PathBuf> {\n    match get_unix_user(Uid::from_raw(uid)) {\n        Some(user) => Some(user.dir),\n        None => None,\n    }\n}\n\n/// If None, it will generate a default path for cgroups.\npub fn get_cgroup_path(cgroups_path: &Option<PathBuf>, container_id: &str) -> PathBuf {\n    match cgroups_path {\n        Some(cpath) => cpath.clone(),\n        None => PathBuf::from(format!(\":youki:{container_id}\")),\n    }\n}\n\npub fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(\n    path: P,\n    contents: C,\n) -> Result<(), std::io::Error> {\n    fs::write(path.as_ref(), contents).map_err(|err| {\n        tracing::error!(path = ?path.as_ref(), ?err, \"failed to write file\");\n        err\n    })?;\n\n    Ok(())\n}\n\npub fn create_dir_all<P: AsRef<Path>>(path: P) -> Result<(), std::io::Error> {\n    fs::create_dir_all(path.as_ref()).map_err(|err| {\n        tracing::error!(path = ?path.as_ref(), ?err, \"failed to create directory\");\n        err\n    })?;\n    Ok(())\n}\n\npub fn open<P: AsRef<Path>>(path: P) -> Result<File, std::io::Error> {\n    File::open(path.as_ref()).map_err(|err| {\n        tracing::error!(path = ?path.as_ref(), ?err, \"failed to open file\");\n        err\n    })\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum MkdirWithModeError {\n    #[error(\"IO error\")]\n    Io(#[from] std::io::Error),\n    #[error(\"metadata doesn't match the expected attributes\")]\n    MetadataMismatch,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum VerifyInodeError {\n    #[error(\"stat operation failed\")]\n    Stat(#[from] nix::Error),\n    #[error(\"{0}\")]\n    Verification(String),\n}\n\n/// Verify file descriptor using stat and statfs, similar to runc's VerifyInode.\n///\n/// This is a helper function that gets stat/statfs for a file descriptor and\n/// calls the provided verification function with the results.\n///\n/// # Arguments\n/// * `fd` - The file descriptor to verify\n/// * `verify` - A closure that receives stat and statfs results and performs verification\n///\n/// # Returns\n/// Returns `Ok(())` if verification succeeds, or an error if stat/statfs fails\n/// or the verification function returns an error.\n///\n/// Ref: <https://github.com/opencontainers/runc/blob/v1.4.0/libcontainer/system/linux.go>\npub fn verify_inode<F>(fd: &OwnedFd, verify: F) -> Result<(), VerifyInodeError>\nwhere\n    F: FnOnce(&libc::stat, &Statfs) -> Result<(), VerifyInodeError>,\n{\n    let stat = fstat(fd.as_raw_fd())?;\n    let fs_stat = fstatfs(fd)?;\n    verify(&stat, &fs_stat)\n}\n\n/// Creates the specified directory and all parent directories with the specified mode. Ensures\n/// that the directory has been created with the correct mode and that the owner of the directory\n/// is the owner that has been specified\n/// # Example\n/// ``` no_run\n/// use libcontainer::utils::create_dir_all_with_mode;\n/// use nix::sys::stat::Mode;\n/// use std::path::Path;\n///\n/// let path = Path::new(\"/tmp/youki\");\n/// create_dir_all_with_mode(&path, 1000, Mode::S_IRWXU).unwrap();\n/// assert!(path.exists())\n/// ```\npub fn create_dir_all_with_mode<P: AsRef<Path>>(\n    path: P,\n    owner: u32,\n    mode: Mode,\n) -> Result<(), MkdirWithModeError> {\n    let path = path.as_ref();\n    if !path.exists() {\n        DirBuilder::new()\n            .recursive(true)\n            .mode(mode.bits())\n            .create(path)?;\n    }\n\n    let metadata = path.metadata()?;\n    if metadata.is_dir()\n        && metadata.st_uid() == owner\n        && metadata.st_mode() & mode.bits() == mode.bits()\n    {\n        Ok(())\n    } else {\n        Err(MkdirWithModeError::MetadataMismatch)\n    }\n}\n\npub fn is_in_new_userns() -> Result<bool, std::io::Error> {\n    let uid_map_path = \"/proc/self/uid_map\";\n    let content = std::fs::read_to_string(uid_map_path)?;\n    Ok(!content.contains(\"4294967295\"))\n}\n\n/// Checks if rootless mode needs to be used\npub fn rootless_required(syscall: &dyn Syscall) -> Result<bool, std::io::Error> {\n    if !syscall.get_euid().is_root() {\n        return Ok(true);\n    }\n    is_in_new_userns()\n}\n\n/// checks if given spec is valid for current user namespace setup\npub fn validate_spec_for_new_user_ns(\n    spec: &Spec,\n    syscall: &dyn Syscall,\n) -> Result<(), LibcontainerError> {\n    let config = UserNamespaceConfig::new(spec)?;\n    let in_user_ns = is_in_new_userns().map_err(LibcontainerError::OtherIO)?;\n    let is_rootless_required = rootless_required(syscall).map_err(LibcontainerError::OtherIO)?;\n    // In case of rootless, there are 2 possible cases :\n    // we have a new user ns specified in the spec\n    // or the youki is launched in a new user ns (this is how podman does it)\n    // So here, we check if rootless is required,\n    // but we are neither in a new user ns nor a new user ns is specified in spec\n    // then it is an error\n    if is_rootless_required && !in_user_ns && config.is_none() {\n        return Err(LibcontainerError::NoUserNamespace);\n    }\n    Ok(())\n}\n\n// Generic retry function with delay and policy.\n// Retries the operation `op` up to `attempts` times if it fails.\n// Waits for `delay` duration between retries.\n// Only retries if the error satisfies the `policy` function.\npub fn retry<F, T, E, P>(mut op: F, attempts: u32, delay: Duration, policy: P) -> Result<T, E>\nwhere\n    F: FnMut() -> Result<T, E>,\n    P: Fn(&E) -> bool,\n{\n    if attempts == 0 {\n        panic!(\"retry called with 0 attempts. Minimum attempts is 1.\");\n    }\n    for attempt in 0..attempts {\n        match op() {\n            Ok(res) => return Ok(res),\n            Err(err) => {\n                if attempt + 1 < attempts && policy(&err) {\n                    std::thread::sleep(delay);\n                } else {\n                    return Err(err);\n                }\n            }\n        }\n    }\n    unreachable!(\"retry loop completed without returning a result.\");\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum NetDevicesError {\n    #[error(\"unable to move network devices without a NET namespace\")]\n    NoNetNamespace,\n    #[error(\"network devices are not supported in rootless containers\")]\n    RootlessNotSupported,\n    #[error(\"invalid network device name: {0}\")]\n    InvalidDeviceName(String),\n    #[error(transparent)]\n    IO(#[from] std::io::Error),\n    #[error(transparent)]\n    Spec(#[from] MissingSpecError),\n}\n\n// check if given spec is valid for netDevices\npub fn validate_spec_for_net_devices(\n    spec: &Spec,\n    syscall: &dyn Syscall,\n) -> Result<(), NetDevicesError> {\n    let linux = spec\n        .linux()\n        .as_ref()\n        .ok_or(NetDevicesError::Spec(MissingSpecError::Linux))?;\n\n    if linux.net_devices().is_none() {\n        return Ok(());\n    }\n\n    let has_net_namespace = match linux.namespaces() {\n        Some(namespaces) => namespaces\n            .iter()\n            .any(|ns| ns.typ() == LinuxNamespaceType::Network),\n        None => false,\n    };\n\n    if !has_net_namespace {\n        return Err(NetDevicesError::NoNetNamespace);\n    }\n\n    let is_rootless = rootless_required(syscall).map_err(NetDevicesError::IO)?;\n    if is_rootless {\n        return Err(NetDevicesError::RootlessNotSupported);\n    }\n\n    if let Some(devices) = linux.net_devices() {\n        devices.iter().try_for_each(|(name, net_dev)| {\n            if !dev_valid_name(name) {\n                return Err(NetDevicesError::InvalidDeviceName(name.into()));\n            }\n            if let Some(dev_name) = net_dev.name() {\n                if !dev_valid_name(dev_name) {\n                    return Err(NetDevicesError::InvalidDeviceName(dev_name.into()));\n                }\n            }\n            Ok(())\n        })?;\n    }\n\n    Ok(())\n}\n\n/// Validates mount destinations and warns about deprecated relative paths.\n/// Follows the OCI Runtime Spec requirement that mount destinations SHOULD be absolute.\n/// Relative paths are deprecated but still accepted for backward compatibility.\npub fn validate_mount_options(\n    mounts: &[oci_spec::runtime::Mount],\n) -> Result<(), LibcontainerError> {\n    mounts\n        .iter()\n        .filter(|mount| !mount.destination().is_absolute())\n        .for_each(|mount| {\n            tracing::warn!(\n                \"mount destination {:?} is not absolute. \\\n                Relative paths are deprecated in OCI Runtime Spec and may not be supported in future versions. \\\n                The path will be interpreted as relative to '/'.\",\n                mount.destination()\n            );\n        });\n\n    Ok(())\n}\n\n// https://elixir.bootlin.com/linux/v6.12/source/net/core/dev.c#L1066\nfn dev_valid_name(name: &str) -> bool {\n    if name.is_empty() || name.len() > IFNAMSIZ {\n        return false;\n    }\n    if name.eq(\".\") || name.eq(\"..\") {\n        return false;\n    }\n\n    for c in name.chars() {\n        if c == '/' || c == ':' || c.is_whitespace() {\n            return false;\n        }\n    }\n\n    true\n}\n\n#[cfg(test)]\nmod tests {\n    use core::panic;\n\n    use anyhow::{Result, bail};\n    use nix::unistd::Gid;\n    use oci_spec::runtime::{LinuxBuilder, LinuxNamespaceBuilder, LinuxNetDevice, SpecBuilder};\n    use serial_test::serial;\n\n    use super::*;\n    use crate::syscall::syscall::create_syscall;\n    use crate::test_utils;\n\n    #[test]\n    pub fn test_get_unix_user() {\n        let user = get_unix_user(Uid::from_raw(0));\n        assert_eq!(user.unwrap().name, \"root\");\n\n        // for a non-exist UID\n        let user = get_unix_user(Uid::from_raw(1000000000));\n        assert!(user.is_none());\n    }\n\n    #[test]\n    pub fn test_get_user_home() {\n        let dir = get_user_home(0);\n        assert_eq!(dir.unwrap().to_str().unwrap(), \"/root\");\n\n        // for a non-exist UID\n        let dir = get_user_home(1000000000);\n        assert!(dir.is_none());\n    }\n\n    #[test]\n    fn test_get_cgroup_path() {\n        let cid = \"sample_container_id\";\n        assert_eq!(\n            get_cgroup_path(&None, cid),\n            PathBuf::from(\":youki:sample_container_id\")\n        );\n        assert_eq!(\n            get_cgroup_path(&Some(PathBuf::from(\"/youki\")), cid),\n            PathBuf::from(\"/youki\")\n        );\n    }\n\n    #[test]\n    fn test_parse_env() -> Result<()> {\n        let key = \"key\".into();\n        let value = \"value\".into();\n        let env_input = vec![format!(\"{key}={value}\")];\n        let env_output = parse_env(&env_input);\n        assert_eq!(\n            env_output.len(),\n            1,\n            \"There should be exactly one entry inside\"\n        );\n        assert_eq!(env_output.get_key_value(&key), Some((&key, &value)));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_create_dir_all_with_mode() -> Result<()> {\n        {\n            let temdir = tempfile::tempdir()?;\n            let path = temdir.path().join(\"test\");\n            let syscall = create_syscall();\n            let uid = syscall.get_uid().as_raw();\n            let mode = Mode::S_IRWXU;\n            create_dir_all_with_mode(&path, uid, mode)?;\n            let metadata = path.metadata()?;\n            assert!(path.is_dir());\n            assert_eq!(metadata.st_uid(), uid);\n            assert_eq!(metadata.st_mode() & mode.bits(), mode.bits());\n        }\n        {\n            let temdir = tempfile::tempdir()?;\n            let path = temdir.path().join(\"test\");\n            let mode = Mode::S_IRWXU;\n            std::fs::create_dir(&path)?;\n            assert!(path.is_dir());\n            match create_dir_all_with_mode(&path, 8899, mode) {\n                Err(MkdirWithModeError::MetadataMismatch) => {}\n                _ => bail!(\"should return MetadataMismatch\"),\n            }\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn test_io() -> Result<()> {\n        {\n            let tempdir = tempfile::tempdir()?;\n            let path = tempdir.path().join(\"test\");\n            write_file(&path, \"test\".as_bytes())?;\n            open(&path)?;\n            assert!(create_dir_all(path).is_err());\n        }\n        {\n            let tempdir = tempfile::tempdir()?;\n            let path = tempdir.path().join(\"test\");\n            create_dir_all(&path)?;\n            assert!(write_file(&path, \"test\".as_bytes()).is_err());\n        }\n        {\n            let tempdir = tempfile::tempdir()?;\n            let path = tempdir.path().join(\"test\");\n            assert!(open(&path).is_err());\n            create_dir_all(&path)?;\n            assert!(path.is_dir())\n        }\n\n        Ok(())\n    }\n\n    // the following test is marked as serial because\n    // we are doing unshare of user ns and fork, so better to run in serial,\n    #[test]\n    #[serial]\n    fn test_userns_spec_validation() -> Result<(), test_utils::TestError> {\n        use nix::sched::{CloneFlags, unshare};\n        let syscall = create_syscall();\n        // default rootful spec\n        let rootful_spec = Spec::default();\n        // as we are not in a user ns, and spec does not have user ns\n        // we should get error here\n        assert!(validate_spec_for_new_user_ns(&rootful_spec, &*syscall).is_err());\n\n        let rootless_spec = Spec::rootless(1000, 1000);\n        // because the spec contains user ns info, we should not get error\n        assert!(validate_spec_for_new_user_ns(&rootless_spec, &*syscall).is_ok());\n\n        test_utils::test_in_child_process(|| {\n            unshare(CloneFlags::CLONE_NEWUSER).unwrap();\n            // here we are in a new user namespace\n            let rootful_spec = Spec::default();\n            let syscall = create_syscall();\n            // because we are already in a new user ns, it is fine if spec\n            // does not have user ns, and because the test is running as\n            // non root\n            assert!(validate_spec_for_new_user_ns(&rootful_spec, &*syscall).is_ok());\n\n            let rootless_spec = Spec::rootless(1000, 1000);\n            // following should succeed irrespective if we're in user ns or not\n            assert!(validate_spec_for_new_user_ns(&rootless_spec, &*syscall).is_ok());\n            Ok(())\n        })\n    }\n\n    #[test]\n    fn test_dev_valid_name() {\n        assert!(!dev_valid_name(\"\"));\n\n        let long_name = \"a\".repeat(IFNAMSIZ + 1);\n        assert!(!dev_valid_name(&long_name));\n\n        let valid_name = \"a\".repeat(IFNAMSIZ);\n        assert!(dev_valid_name(&valid_name));\n\n        assert!(!dev_valid_name(\".\"));\n        assert!(!dev_valid_name(\"..\"));\n\n        assert!(!dev_valid_name(\"/: \"));\n        assert!(!dev_valid_name(\"eth0/: \"));\n\n        assert!(dev_valid_name(\"eth0\"));\n        assert!(dev_valid_name(\"veth123\"));\n        assert!(dev_valid_name(\"abc.def\"));\n    }\n\n    fn build_spec_with_ns_and_devices(include_net_ns: bool, devices: Vec<(&str, &str)>) -> Spec {\n        let mut namespaces = vec![];\n        if include_net_ns {\n            namespaces.push(\n                LinuxNamespaceBuilder::default()\n                    .typ(LinuxNamespaceType::Network)\n                    .path(PathBuf::from(\"/dev/net\"))\n                    .build()\n                    .unwrap(),\n            );\n        }\n\n        let net_devices: HashMap<String, LinuxNetDevice> = devices\n            .into_iter()\n            .map(|(key, val)| {\n                (\n                    key.into(),\n                    LinuxNetDevice::default().set_name(Some(val.into())).clone(),\n                )\n            })\n            .collect();\n        let linux = LinuxBuilder::default()\n            .namespaces(namespaces)\n            .net_devices(net_devices)\n            .build()\n            .unwrap();\n\n        SpecBuilder::default().linux(linux).build().unwrap()\n    }\n\n    #[test]\n    fn test_net_devices_none() {\n        let spec = Spec::default();\n        let syscall = create_syscall();\n        syscall.set_id(Uid::from_raw(0), Gid::from_raw(0)).unwrap();\n        let result = validate_spec_for_net_devices(&spec, &*syscall);\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    fn test_missing_net_namespace() {\n        let spec = build_spec_with_ns_and_devices(false, vec![]);\n        let syscall = create_syscall();\n        let err = validate_spec_for_net_devices(&spec, &*syscall).unwrap_err();\n        assert!(matches!(err, NetDevicesError::NoNetNamespace));\n    }\n\n    #[test]\n    fn test_invalid_device_name() {\n        let spec = build_spec_with_ns_and_devices(true, vec![(\"eth0\", \"/:invalid\")]);\n        let syscall = create_syscall();\n        syscall.set_id(Uid::from_raw(0), Gid::from_raw(0)).unwrap();\n        let err = validate_spec_for_net_devices(&spec, &*syscall).unwrap_err();\n        if let NetDevicesError::InvalidDeviceName(name) = err {\n            assert_eq!(name, \"/:invalid\");\n        } else {\n            panic!(\"Expected InvalidDeviceName error\");\n        }\n    }\n\n    #[test]\n    fn test_valid_config() {\n        let spec = build_spec_with_ns_and_devices(true, vec![(\"eth0\", \"eth0_container\")]);\n        let syscall = create_syscall();\n        syscall.set_id(Uid::from_raw(0), Gid::from_raw(0)).unwrap();\n        let result = validate_spec_for_net_devices(&spec, &*syscall);\n        assert!(result.is_ok());\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/workload/default.rs",
    "content": "use std::ffi::CString;\nuse std::path::{Path, PathBuf};\n\nuse nix::unistd;\nuse oci_spec::runtime::Spec;\n\nuse super::{Executor, ExecutorError, ExecutorValidationError};\n\n#[derive(Clone)]\npub struct DefaultExecutor {}\n\nimpl Executor for DefaultExecutor {\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {\n        tracing::debug!(\"executing workload with default handler\");\n        let args = spec\n            .process()\n            .as_ref()\n            .and_then(|p| p.args().as_ref())\n            .ok_or_else(|| {\n                tracing::error!(\"no arguments provided to execute\");\n                ExecutorError::InvalidArg\n            })?;\n\n        let executable = args[0].as_str();\n        let cstring_path = CString::new(executable.as_bytes()).map_err(|err| {\n            tracing::error!(\"failed to convert path {executable:?} to cstring: {}\", err,);\n            ExecutorError::InvalidArg\n        })?;\n        let a: Vec<CString> = args\n            .iter()\n            .map(|s| CString::new(s.as_bytes()).unwrap_or_default())\n            .collect();\n        unistd::execvp(&cstring_path, &a).map_err(|err| {\n            tracing::error!(?err, filename = ?cstring_path, args = ?a, \"failed to execvp\");\n            ExecutorError::Execution(\n                format!(\n                    \"error '{}' executing '{:?}' with args '{:?}'\",\n                    err, cstring_path, a\n                )\n                .into(),\n            )\n        })?;\n\n        // After execvp is called, the process is replaced with the container\n        // payload through execvp, so it should never reach here.\n        unreachable!();\n    }\n\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {\n        let proc = spec\n            .process()\n            .as_ref()\n            .ok_or(ExecutorValidationError::ArgValidationError(\n                \"spec did not contain process\".into(),\n            ))?;\n\n        if let Some(args) = proc.args() {\n            let envs: Vec<String> = proc.env().as_ref().unwrap_or(&vec![]).clone();\n            let path_vars: Vec<&String> = envs.iter().filter(|&e| e.starts_with(\"PATH=\")).collect();\n            if path_vars.is_empty() {\n                tracing::error!(\"PATH environment variable is not set\");\n                Err(ExecutorValidationError::ArgValidationError(\n                    \"PATH environment variable is not set\".into(),\n                ))?;\n            }\n            let path_var = path_vars[0].trim_start_matches(\"PATH=\");\n            match get_executable_path(&args[0], path_var) {\n                None => {\n                    tracing::error!(\n                        executable = ?args[0],\n                        \"executable for container process not found in PATH\",\n                    );\n                    Err(ExecutorValidationError::ArgValidationError(format!(\n                        \"executable '{}' not found in $PATH\",\n                        args[0]\n                    )))?;\n                }\n                Some(path) => match is_executable(&path) {\n                    Ok(true) => {\n                        tracing::debug!(executable = ?path, \"found executable in executor\");\n                    }\n                    Ok(false) => {\n                        tracing::error!(\n                            executable = ?path,\n                            \"executable does not have the correct permission set\",\n                        );\n                        Err(ExecutorValidationError::ArgValidationError(format!(\n                            \"executable '{}' at path '{:?}' does not have correct permissions\",\n                            args[0], path\n                        )))?;\n                    }\n                    Err(err) => {\n                        tracing::error!(\n                            executable = ?path,\n                            ?err,\n                            \"failed to check permissions for executable\",\n                        );\n                        Err(ExecutorValidationError::ArgValidationError(format!(\n                            \"failed to check permissions for executable '{}' at path '{:?}' : {}\",\n                            args[0], path, err\n                        )))?;\n                    }\n                },\n            }\n        }\n\n        Ok(())\n    }\n}\n\npub fn get_executor() -> Box<dyn Executor> {\n    Box::new(DefaultExecutor {})\n}\n\nfn get_executable_path(name: &str, path_var: &str) -> Option<PathBuf> {\n    // if path has / in it, we have to assume absolute path, as per runc impl\n    if name.contains('/') && PathBuf::from(name).exists() {\n        return Some(PathBuf::from(name));\n    }\n    for path in path_var.split(':') {\n        let potential_path = PathBuf::from(path).join(name);\n        if potential_path.exists() {\n            return Some(potential_path);\n        }\n    }\n    None\n}\n\nfn is_executable(path: &Path) -> std::result::Result<bool, std::io::Error> {\n    use std::os::unix::fs::PermissionsExt;\n    let metadata = path.metadata()?;\n    let permissions = metadata.permissions();\n    // we have to check if the path is file and the execute bit\n    // is set. In case of directories, the execute bit is also set,\n    // so have to check if this is a file or not\n    Ok(metadata.is_file() && permissions.mode() & 0o001 != 0)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashMap;\n    use std::env;\n\n    use serial_test::serial;\n\n    use super::*;\n\n    #[test]\n    fn test_get_executable_path() {\n        let non_existing_abs_path = \"/some/non/existent/absolute/path\";\n        let existing_abs_path = \"/usr/bin/sh\";\n        let existing_binary = \"sh\";\n        let non_existing_binary = \"non-existent\";\n        let path_value = \"/usr/bin:/bin\";\n\n        assert_eq!(\n            get_executable_path(existing_abs_path, path_value),\n            Some(PathBuf::from(existing_abs_path))\n        );\n        assert_eq!(get_executable_path(non_existing_abs_path, path_value), None);\n\n        assert_eq!(\n            get_executable_path(existing_binary, path_value),\n            Some(PathBuf::from(\"/usr/bin/sh\"))\n        );\n\n        assert_eq!(get_executable_path(non_existing_binary, path_value), None);\n    }\n\n    #[test]\n    fn test_is_executable() {\n        let tmp = tempfile::tempdir().expect(\"create temp directory for test\");\n        let executable_path = PathBuf::from(\"/bin/sh\");\n        let directory_path = tmp.path();\n        let non_executable_path = directory_path.join(\"non_executable_file\");\n        let non_existent_path = PathBuf::from(\"/some/non/existent/path\");\n\n        std::fs::File::create(non_executable_path.as_path()).unwrap();\n\n        assert!(is_executable(&non_existent_path).is_err());\n        assert!(is_executable(&executable_path).unwrap());\n        assert!(!is_executable(&non_executable_path).unwrap());\n        assert!(!is_executable(directory_path).unwrap());\n    }\n\n    #[test]\n    #[serial]\n    fn test_executor_set_envs() {\n        // Store original environment variables to restore later\n        let original_envs: HashMap<String, String> = env::vars().collect();\n\n        // Test setting environment variables\n        {\n            let executor = get_executor();\n            let envs = HashMap::from([\n                (\"FOO\".to_owned(), \"hoge\".to_owned()),\n                (\"BAR\".to_owned(), \"fuga\".to_owned()),\n                (\"BAZ\".to_owned(), \"piyo\".to_owned()),\n            ]);\n            assert!(executor.setup_envs(envs).is_ok());\n\n            // Check if the environment variables are set correctly\n            let current_envs = std::env::vars().collect::<HashMap<String, String>>();\n            assert_eq!(current_envs.get(\"FOO\").unwrap(), \"hoge\");\n            assert_eq!(current_envs.get(\"BAR\").unwrap(), \"fuga\");\n            assert_eq!(current_envs.get(\"BAZ\").unwrap(), \"piyo\");\n            // No other environment variables should be set\n            assert_eq!(current_envs.len(), 3);\n        }\n\n        // Restore original environment variables\n        original_envs.iter().for_each(|(key, value)| {\n            unsafe { env::set_var(key, value) };\n        });\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/src/workload/mod.rs",
    "content": "use std::collections::HashMap;\nuse std::env;\n\nuse oci_spec::runtime::Spec;\n\npub mod default;\n\npub static EMPTY: Vec<String> = Vec::new();\n\n#[derive(Debug, thiserror::Error)]\npub enum ExecutorError {\n    #[error(\"invalid argument\")]\n    InvalidArg,\n    #[error(\"failed to execute workload\")]\n    Execution(#[from] Box<dyn std::error::Error + Send + Sync>),\n    #[error(\"{0}\")]\n    Other(String),\n    #[error(\"{0} executor can't handle spec\")]\n    CantHandle(&'static str),\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ExecutorValidationError {\n    #[error(\"{0} executor can't handle spec\")]\n    CantHandle(&'static str),\n    #[error(\"{0}\")]\n    ArgValidationError(String),\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ExecutorSetEnvsError {\n    #[error(\"failed to set envs\")]\n    SetEnvs(#[from] Box<dyn std::error::Error + Send + Sync>),\n    #[error(\"{0}\")]\n    Other(String),\n}\n\n// Here is an explanation about the complexity below regarding to\n// CloneBoxExecutor and Executor traits. This is one of the places rust actually\n// makes our life harder. The usecase for the executor is to allow users of\n// `libcontainer` to pass in a closure like function where the actual execution\n// of the container workload can be defined by user. To maximize the flexibility\n// for the users, we use trait object to allow users to pass in a closure like\n// objects, so the function can container any number of variables through the\n// structure. This is similar to the Fn family of traits that rust std lib has.\n// However, our usecase has a little bit more complexity than the Fn family of\n// traits. We require the struct implementing this Executor traits to be\n// cloneable, so we can pass the struct across fork/clone process boundary with\n// memory safety. We can't make the Executor trait to require Clone trait\n// because doing so will make the Executor trait not object safe. Part of the\n// reason is that without the CloneBoxExecutor trait, the default clone\n// implementation for Box<dyn trait> will first unwrap the box. However, the\n// `dyn trait` inside the box doesn't have a size, which violates the object\n// safety requirement for a trait. To work around this, we implement our own\n// CloneBoxExecutor trait, which is object safe.\n//\n// Note to future maintainers: if you find a better way to do this or Rust\n// introduced some new magical feature to simplify this logic, please consider\n// to refactor this part.\n\npub trait CloneBoxExecutor {\n    fn clone_box(&self) -> Box<dyn Executor>;\n}\n\npub trait Executor: CloneBoxExecutor {\n    /// Executes the workload\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError>;\n\n    /// Validate if the spec can be executed by the executor. This step runs\n    /// after the container init process is created, entered into the correct\n    /// namespace and cgroups, and pivot_root into the rootfs. But this step\n    /// runs before waiting for the container start signal.\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError>;\n\n    /// Set environment variables for the container process to be executed.\n    /// This step runs after the container init process is created, entered\n    /// into the correct namespace and cgroups, and pivot_root into the rootfs.\n    /// But this step runs before waiting for the container start signal.\n    /// The host's environment variables are not cleared yet at this point.\n    /// They should be cleared explicitly if needed.\n    fn setup_envs(&self, envs: HashMap<String, String>) -> Result<(), ExecutorSetEnvsError> {\n        // The default implementation resets the process env based on the OCI spec.\n        // First, clear all host's envs.\n        env::vars().for_each(|(key, _value)| unsafe { env::remove_var(key) });\n\n        // Next, set envs based on the spec\n        envs.iter()\n            .for_each(|(key, value)| unsafe { env::set_var(key, value) });\n\n        Ok(())\n    }\n}\n\nimpl<T> CloneBoxExecutor for T\nwhere\n    T: 'static + Executor + Clone,\n{\n    fn clone_box(&self) -> Box<dyn Executor> {\n        Box::new(self.clone())\n    }\n}\n\nimpl Clone for Box<dyn Executor> {\n    fn clone(&self) -> Self {\n        self.clone_box()\n    }\n}\n"
  },
  {
    "path": "crates/libcontainer/tests/as_sibling.rs",
    "content": "use std::collections::HashMap;\nuse std::fs::create_dir;\nuse std::hash::{DefaultHasher, Hash, Hasher};\nuse std::path::Path;\n\nuse anyhow::Result;\nuse libcontainer::container::builder::ContainerBuilder;\nuse libcontainer::syscall::syscall::SyscallType;\nuse libcontainer::workload::{\n    Executor, ExecutorError, ExecutorSetEnvsError, ExecutorValidationError,\n};\nuse nix::unistd::{getegid, geteuid};\nuse oci_spec::runtime::{RootBuilder, Spec};\nuse procfs::process::Process;\nuse serial_test::serial;\nuse tempfile::tempdir;\n\nfn prepare_container_root(root: impl AsRef<Path>) -> Result<()> {\n    let root = root.as_ref();\n    create_dir(root.join(\"rootfs\"))?;\n\n    let uid = geteuid().as_raw();\n    let gid = getegid().as_raw();\n\n    let mut spec = Spec::rootless(uid, gid);\n    spec.set_root(\n        RootBuilder::default()\n            .path(\"rootfs\")\n            .readonly(false)\n            .build()\n            .ok(),\n    );\n\n    spec.save(root.join(\"config.json\"))?;\n\n    Ok(())\n}\n\nfn hash(v: impl Hash) -> u64 {\n    let mut hasher = DefaultHasher::default();\n    v.hash(&mut hasher);\n    hasher.finish()\n}\n\n#[derive(Clone)]\nstruct SomeExecutor;\n\nimpl Executor for SomeExecutor {\n    fn setup_envs(&self, _: HashMap<String, String>) -> Result<(), ExecutorSetEnvsError> {\n        Ok(())\n    }\n\n    fn validate(&self, _: &Spec) -> Result<(), ExecutorValidationError> {\n        Ok(())\n    }\n\n    fn exec(&self, _: &Spec) -> Result<(), ExecutorError> {\n        Ok(())\n    }\n}\n\n#[test]\n#[serial]\nfn run_init_process_as_child() -> Result<()> {\n    let root = tempdir()?;\n    prepare_container_root(&root)?;\n\n    let id = format!(\"test-container-{:x}\", hash(root.as_ref()));\n    let container = ContainerBuilder::new(id, SyscallType::Linux)\n        .with_executor(SomeExecutor)\n        .with_root_path(root.as_ref())?\n        .as_init(root.as_ref())\n        .build()?;\n\n    let container = scopeguard::guard(container, |mut container| {\n        let _ = container.delete(true);\n    });\n\n    let init_pid = container.pid().unwrap().as_raw();\n\n    let init_ppid = Process::new(init_pid)?.stat()?.ppid;\n    let this_pid = Process::myself()?.pid();\n\n    assert_eq!(init_ppid, this_pid);\n\n    Ok(())\n}\n\n#[test]\n#[serial]\nfn run_init_process_as_sibling() -> Result<()> {\n    let root = tempdir()?;\n    prepare_container_root(&root)?;\n\n    let id = format!(\"test-container-{:x}\", hash(root.as_ref()));\n    let container = ContainerBuilder::new(id, SyscallType::Linux)\n        .with_executor(SomeExecutor)\n        .with_root_path(root.as_ref())?\n        .as_init(root.as_ref())\n        .as_sibling(true)\n        .build()?;\n\n    let container = scopeguard::guard(container, |mut container| {\n        let _ = container.delete(true);\n    });\n\n    let init_pid = container.pid().unwrap().as_raw();\n\n    let init_ppid = Process::new(init_pid)?.stat()?.ppid;\n    let this_ppid = Process::myself()?.stat()?.ppid;\n\n    assert_eq!(init_ppid, this_ppid);\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/liboci-cli/Cargo.toml",
    "content": "[package]\nname = \"liboci-cli\"\nversion = \"0.6.0\" # MARK: Version\ndescription = \"Parse command line arguments for OCI container runtimes\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nkeywords = [\"youki\", \"container\", \"oci\"]\n\n[dependencies.clap]\nversion = \"4.5.13\"\ndefault-features = false\nfeatures = [\"std\", \"suggestions\", \"derive\", \"cargo\", \"help\", \"usage\", \"error-context\"]\n"
  },
  {
    "path": "crates/liboci-cli/README.md",
    "content": "# liboci-cli\n\nThis is a crate to parse command line arguments for OCI container\nruntimes as specified in the [OCI Runtime Command Line\nInterface](https://github.com/opencontainers/runtime-tools/blob/master/docs/command-line-interface.md).\n\n## Implemented subcommands\n\n| Command    | liboci-cli | CLI Specification | runc | crun | youki |\n| :--------: | :--------: | :---------------: | :--: | :--: | :---: |\n| create     | ✅         | ✅                | ✅   | ✅   | ✅    |\n| start      | ✅         | ✅                | ✅   | ✅   | ✅    |\n| state      | ✅         | ✅                | ✅   | ✅   | ✅    |\n| kill       | ✅         | ✅                | ✅   | ✅   | ✅    |\n| delete     | ✅         | ✅                | ✅   | ✅   | ✅    |\n| checkpoint |            |                   | ✅   | ✅   |       |\n| events     | ✅         |                   | ✅   |      | ✅    |\n| exec       | ✅         |                   | ✅   | ✅   | ✅    |\n| features   | ✅         |                   | ✅   |      |       |\n| list       | ✅         |                   | ✅   | ✅   | ✅    |\n| pause      | ✅         |                   | ✅   | ✅   | ✅    |\n| ps         | ✅         |                   | ✅   | ✅   | ✅    |\n| restore    |            |                   | ✅   | ✅   |       |\n| resume     | ✅         |                   | ✅   | ✅   | ✅    |\n| run        | ✅         |                   | ✅   | ✅   | ✅    |\n| spec       | ✅         |                   | ✅   | ✅   | ✅    |\n| update     |            |                   | ✅   | ✅   |       |\n"
  },
  {
    "path": "crates/liboci-cli/src/checkpoint.rs",
    "content": "use std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Checkpoint a running container\n/// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-checkpoint.8.md\n/// Unimplemented options vs runc: https://github.com/youki-dev/youki/issues/3394\n#[derive(Parser, Debug)]\npub struct Checkpoint {\n    /// Path for saving criu image files\n    #[clap(long, default_value = \"checkpoint\")]\n    pub image_path: PathBuf,\n    /// Path for saving work files and logs\n    #[clap(long)]\n    pub work_path: Option<PathBuf>,\n    /// TODO: Path for previous criu image file in pre-dump\n    /// #[clap(long)]\n    /// pub parent_path: Option<PathBuf>,\n    /// Leave the process running after checkpointing\n    #[clap(long)]\n    pub leave_running: bool,\n    /// Allow open tcp connections\n    #[clap(long)]\n    pub tcp_established: bool,\n    /// TODO: Skip in-flight tcp connections\n    /// #[clap(long)]\n    /// pub tcp_skip_in_flight: bool,\n    /// TODO: Allow one to link unlinked files back when possible\n    /// #[clap(long)]\n    /// pub link_remap: bool,\n    /// Allow external unix sockets\n    #[clap(long)]\n    pub ext_unix_sk: bool,\n    /// Allow shell jobs\n    #[clap(long)]\n    pub shell_job: bool,\n    /// TODO: Use lazy migration mechanism\n    /// #[clap(long)]\n    /// pub lazy_pages: bool,\n    /// TODO: Pass a file descriptor fd to criu. Is u32 the right type?\n    /// #[clap(long)]\n    /// pub status_fd: Option<u32>,\n    /// TODO: Start a page server at the given URL\n    /// #[clap(long)]\n    /// pub page_server: Option<String>,\n    /// Allow file locks\n    #[clap(long)]\n    pub file_locks: bool,\n    /// TODO: Do a pre-dump\n    /// #[clap(long)]\n    /// pub pre_dump: bool,\n    /// TODO: Cgroups mode\n    /// #[clap(long)]\n    /// pub manage_cgroups_mode: Option<String>,\n    /// TODO: Checkpoint a namespace, but don't save its properties\n    /// #[clap(long)]\n    /// pub empty_ns: bool,\n    /// TODO: Enable auto-deduplication\n    /// #[clap(long)]\n    /// pub auto_dedup: bool,\n\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/create.rs",
    "content": "//! Handles the creation of a new container\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Create a container\n/// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-create.8.md\n#[derive(Parser, Debug)]\npub struct Create {\n    /// Path to the bundle directory, containing config.json and root filesystem\n    #[clap(short, long, default_value = \".\")]\n    pub bundle: PathBuf,\n    /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal\n    #[clap(short, long)]\n    pub console_socket: Option<PathBuf>,\n    /// File to write pid of the container created\n    // note that in the end, container is just another process\n    #[clap(short, long)]\n    pub pid_file: Option<PathBuf>,\n    /// Do not use pivot rool to jail process inside rootfs\n    #[clap(long)]\n    pub no_pivot: bool,\n    /// Do not create a new session keyring for the container.\n    #[clap(long)]\n    pub no_new_keyring: bool,\n    /// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)\n    #[clap(long, default_value = \"0\")]\n    pub preserve_fds: i32,\n\n    /// Name of the container instance to be started\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/delete.rs",
    "content": "use clap::Parser;\n\n/// Release any resources held by the container\n#[derive(Parser, Debug)]\npub struct Delete {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n    /// forces deletion of the container if it is still running (using SIGKILL)\n    #[clap(short, long)]\n    pub force: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/events.rs",
    "content": "use clap::Parser;\n\n/// Show resource statistics for the container\n#[derive(Parser, Debug)]\npub struct Events {\n    /// Sets the stats collection interval in seconds (default: 5s)\n    #[clap(long, default_value = \"5\")]\n    pub interval: u32,\n    /// Display the container stats only once\n    #[clap(long)]\n    pub stats: bool,\n    /// Name of the container instance\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/exec.rs",
    "content": "use std::error::Error;\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Execute a process within an existing container\n/// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-exec.8.md\n#[derive(Parser, Debug)]\npub struct Exec {\n    /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal\n    #[clap(long)]\n    pub console_socket: Option<PathBuf>,\n    #[clap(long)]\n    /// Current working directory of the container\n    pub cwd: Option<PathBuf>,\n    /// Environment variables that should be set in the container\n    #[clap(short, long, value_parser = parse_env::<String, String>, number_of_values = 1)]\n    pub env: Vec<(String, String)>,\n    #[clap(short, long)]\n    pub tty: bool,\n    /// Run the command as a user\n    #[clap(short, long, value_parser = parse_user::<u32, u32>)]\n    pub user: Option<(u32, Option<u32>)>,\n    /// Add additional group IDs. Can be specified multiple times\n    #[clap(long, short = 'g', number_of_values = 1)]\n    pub additional_gids: Vec<u32>,\n    /// Path to process.json\n    #[clap(short, long)]\n    pub process: Option<PathBuf>,\n    /// Detach from the container process\n    #[clap(short, long)]\n    pub detach: bool,\n    #[clap(long)]\n    /// The file to which the pid of the container process should be written to\n    pub pid_file: Option<PathBuf>,\n    /// Set the asm process label for the process commonly used with selinux\n    #[clap(long)]\n    pub process_label: Option<String>,\n    /// Set the apparmor profile for the process\n    #[clap(long)]\n    pub apparmor: Option<String>,\n    /// Prevent the process from gaining additional privileges\n    #[clap(long)]\n    pub no_new_privs: bool,\n    /// Add a capability to the bounding set for the process\n    #[clap(long, number_of_values = 1)]\n    pub cap: Vec<String>,\n    /// Pass N additional file descriptors to the container\n    #[clap(long, default_value = \"0\")]\n    pub preserve_fds: i32,\n    /// Allow exec in a paused container\n    #[clap(long)]\n    pub ignore_paused: bool,\n    /// Execute a process in a sub-cgroup\n    #[clap(long)]\n    pub cgroup: Option<String>,\n\n    /// Identifier of the container\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n\n    /// Command that should be executed in the container\n    #[clap(required = false, trailing_var_arg = true)]\n    pub command: Vec<String>,\n}\n\nfn parse_env<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>\nwhere\n    T: std::str::FromStr,\n    T::Err: Error + Send + Sync + 'static,\n    U: std::str::FromStr,\n    U::Err: Error + Send + Sync + 'static,\n{\n    let pos = s\n        .find('=')\n        .ok_or_else(|| format!(\"invalid VAR=value: no `=` found in `{s}`\"))?;\n    Ok((s[..pos].parse()?, s[pos + 1..].parse()?))\n}\n\nfn parse_user<T, U>(s: &str) -> Result<(T, Option<U>), Box<dyn Error + Send + Sync + 'static>>\nwhere\n    T: std::str::FromStr,\n    T::Err: Error + Send + Sync + 'static,\n    U: std::str::FromStr,\n    U::Err: Error + Send + Sync + 'static,\n{\n    if let Some(pos) = s.find(':') {\n        Ok((s[..pos].parse()?, Some(s[pos + 1..].parse()?)))\n    } else {\n        Ok((s.parse()?, None))\n    }\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/features.rs",
    "content": "use clap::Parser;\n\n/// Return the features list for a container\n/// This subcommand was introduced in runc by\n/// https://github.com/opencontainers/runc/pull/3296\n/// It is documented here:\n/// https://github.com/opencontainers/runtime-spec/blob/main/features-linux.md\n#[derive(Parser, Debug)]\npub struct Features {}\n"
  },
  {
    "path": "crates/liboci-cli/src/info.rs",
    "content": "use clap::Parser;\n\n/// Show information about the system\n#[derive(Parser, Debug)]\npub struct Info {}\n"
  },
  {
    "path": "crates/liboci-cli/src/kill.rs",
    "content": "use clap::Parser;\n\n/// Send the specified signal to the container\n#[derive(Parser, Debug)]\npub struct Kill {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n    pub signal: String,\n    #[clap(short, long)]\n    pub all: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/lib.rs",
    "content": "use std::fmt::Debug;\nuse std::path::PathBuf;\n\nuse clap::Parser;\n\n// Subcommands that are specified in https://github.com/opencontainers/runtime-tools/blob/master/docs/command-line-interface.md\n\nmod create;\nmod delete;\nmod kill;\nmod start;\nmod state;\n\npub use create::Create;\npub use delete::Delete;\npub use kill::Kill;\npub use start::Start;\npub use state::State;\n\n// Other common subcommands that aren't specified in the document\nmod checkpoint;\nmod events;\nmod exec;\nmod features;\nmod list;\nmod pause;\nmod ps;\nmod resume;\nmod run;\nmod spec;\nmod update;\n\npub use checkpoint::Checkpoint;\npub use events::Events;\npub use exec::Exec;\npub use features::Features;\npub use list::List;\npub use pause::Pause;\npub use ps::Ps;\npub use resume::Resume;\npub use run::Run;\npub use spec::Spec;\npub use update::Update;\n\n// Subcommands parsed by liboci-cli, based on the [OCI\n// runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)\n// and specifically the [OCI Command Line\n// Interface](https://github.com/opencontainers/runtime-tools/blob/master/docs/command-line-interface.md)\n#[derive(Parser, Debug)]\npub enum StandardCmd {\n    Create(Create),\n    Start(Start),\n    State(State),\n    Kill(Kill),\n    Delete(Delete),\n}\n\n// Extra subcommands not documented in the OCI Command Line Interface,\n// but found in\n// [runc](https://github.com/opencontainers/runc/blob/master/man/runc.8.md)\n// and other runtimes.\n#[derive(Parser, Debug)]\npub enum CommonCmd {\n    Checkpointt(Checkpoint),\n    Events(Events),\n    Exec(Exec),\n    Features(Features),\n    List(List),\n    Pause(Pause),\n    #[clap(allow_hyphen_values = true)]\n    Ps(Ps),\n    Resume(Resume),\n    Run(Run),\n    Update(Update),\n    Spec(Spec),\n}\n\n// The OCI Command Line Interface document doesn't define any global\n// flags, but these are commonly accepted by runtimes\n#[derive(Parser, Debug)]\npub struct GlobalOpts {\n    /// set the log file to write youki logs to (default is '/dev/stderr')\n    #[clap(short, long, overrides_with(\"log\"))]\n    pub log: Option<PathBuf>,\n    /// change log level to debug, but the `log-level` flag takes precedence\n    #[clap(long)]\n    pub debug: bool,\n    /// set the log format ('text' (default), or 'json') (default: \"text\")\n    #[clap(long)]\n    pub log_format: Option<String>,\n    /// root directory to store container state\n    #[clap(short, long)]\n    pub root: Option<PathBuf>,\n    /// Enable systemd cgroup manager, rather then use the cgroupfs directly.\n    #[clap(short, long)]\n    pub systemd_cgroup: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/list.rs",
    "content": "use clap::Parser;\n\n/// List created containers\n#[derive(Parser, Debug)]\npub struct List {\n    /// Specify the format (default or table)\n    #[clap(long, default_value = \"table\")]\n    pub format: String,\n\n    /// Only display container IDs\n    #[clap(long, short)]\n    pub quiet: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/pause.rs",
    "content": "use clap::Parser;\n\n/// Suspend the processes within the container\n#[derive(Parser, Debug)]\npub struct Pause {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/ps.rs",
    "content": "use clap::{self, Parser};\n\n/// Display the processes inside the container\n#[derive(Parser, Debug)]\npub struct Ps {\n    /// format to display processes: table or json (default: \"table\")\n    #[clap(short, long, default_value = \"table\")]\n    pub format: String,\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n    /// options will be passed to the ps utility\n    #[clap(trailing_var_arg = true)]\n    pub ps_options: Vec<String>,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/resume.rs",
    "content": "use clap::Parser;\n\n/// Resume the processes within the container\n#[derive(Parser, Debug)]\npub struct Resume {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/run.rs",
    "content": "use std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Create a container and immediately start it\n#[derive(Parser, Debug)]\npub struct Run {\n    /// Path to the bundle directory, containing config.json and root filesystem\n    #[clap(short, long, default_value = \".\")]\n    pub bundle: PathBuf,\n    /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal\n    #[clap(short, long)]\n    pub console_socket: Option<PathBuf>,\n    /// File to write pid of the container created\n    // note that in the end, container is just another process\n    #[clap(short, long)]\n    pub pid_file: Option<PathBuf>,\n    /// Disable the use of the subreaper used to reap reparented processes\n    #[clap(long)]\n    pub no_subreaper: bool,\n    /// Do not use pivot root to jail process inside rootfs\n    #[clap(long)]\n    pub no_pivot: bool,\n    /// Do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key.\n    #[clap(long)]\n    pub no_new_keyring: bool,\n    /// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)\n    #[clap(long, default_value = \"0\")]\n    pub preserve_fds: i32,\n    // Keep container's state directory and cgroup\n    #[clap(long)]\n    pub keep: bool,\n    /// name of the container instance to be started\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n    /// Detach from the container process\n    #[clap(short, long)]\n    pub detach: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/spec.rs",
    "content": "use std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Command generates a config.json\n#[derive(Parser, Debug)]\npub struct Spec {\n    /// Set path to the root of the bundle directory\n    #[clap(long, short)]\n    pub bundle: Option<PathBuf>,\n\n    /// Generate a configuration for a rootless container\n    #[clap(long)]\n    pub rootless: bool,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/start.rs",
    "content": "use clap::Parser;\n\n/// Start a previously created container\n#[derive(Parser, Debug)]\npub struct Start {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/state.rs",
    "content": "use clap::Parser;\n\n/// Show the container state\n#[derive(Parser, Debug)]\npub struct State {\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/liboci-cli/src/update.rs",
    "content": "use std::path::PathBuf;\n\nuse clap::Parser;\n\n/// Update running container resource constraints\n#[derive(Parser, Debug)]\npub struct Update {\n    /// Read the new resource limits from the given json file. Use - to read from stdin.\n    /// If this option is used, all other options are ignored.\n    #[clap(short, long)]\n    pub resources: Option<PathBuf>,\n\n    /// Set a new I/O weight\n    #[clap(long)]\n    pub blkio_weight: Option<u64>,\n\n    /// Set CPU CFS period to be used for hardcapping (in microseconds)\n    #[clap(long)]\n    pub cpu_period: Option<u64>,\n\n    /// Set CPU usage limit within a given period (in microseconds)\n    #[clap(long)]\n    pub cpu_quota: Option<u64>,\n\n    /// Set CPU realtime period to be used for hardcapping (in microseconds)\n    #[clap(long)]\n    pub cpu_rt_period: Option<u64>,\n\n    /// Set CPU realtime hardcap limit (in microseconds)\n    #[clap(long)]\n    pub cpu_rt_runtime: Option<u64>,\n\n    /// Set CPU shares (relative weight vs. other containers)\n    #[clap(long)]\n    pub cpu_share: Option<u64>,\n\n    /// Set CPU(s) to use. The list can contain commas and ranges. For example: 0-3,7\n    #[clap(long)]\n    pub cpuset_cpus: Option<String>,\n\n    /// Set memory node(s) to use. The list format is the same as for --cpuset-cpus.\n    #[clap(long)]\n    pub cpuset_mems: Option<String>,\n\n    /// Set memory limit to num bytes.\n    #[clap(long)]\n    pub memory: Option<u64>,\n\n    /// Set memory reservation (or soft limit) to num bytes.\n    #[clap(long)]\n    pub memory_reservation: Option<u64>,\n\n    /// Set total memory + swap usage to num bytes. Use -1 to unset the limit (i.e. use unlimited swap).\n    #[clap(long)]\n    pub memory_swap: Option<i64>,\n\n    /// Set the maximum number of processes allowed in the container\n    #[clap(long)]\n    pub pids_limit: Option<i64>,\n\n    /// Set the value for Intel RDT/CAT L3 cache schema.\n    #[clap(long)]\n    pub l3_cache_schema: Option<String>,\n\n    /// Set the Intel RDT/MBA memory bandwidth schema.\n    #[clap(long)]\n    pub mem_bw_schema: Option<String>,\n\n    #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]\n    pub container_id: String,\n}\n"
  },
  {
    "path": "crates/youki/Cargo.toml",
    "content": "[package]\nname = \"youki\"\nversion = \"0.6.0\" # MARK: Version\ndescription = \"A container runtime written in Rust\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"../../README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nbuild = \"build.rs\"\nkeywords = [\"youki\", \"container\"]\n\n[features]\nsystemd = [\"libcgroups/systemd\", \"libcontainer/systemd\", \"v2\"]\nv2 = [\"libcgroups/v2\", \"libcontainer/v2\"]\nv1 = [\"libcgroups/v1\", \"libcontainer/v1\"]\ncgroupsv2_devices = [\"libcgroups/cgroupsv2_devices\", \"libcontainer/cgroupsv2_devices\"]\nseccomp = [\"libcontainer/libseccomp\"]\n\nwasm-wasmer = [\"wasmer\", \"wasmer-wasix\"]\nwasm-wasmedge = [\"wasmedge-sdk/standalone\", \"wasmedge-sdk/static\"]\nwasm-wasmtime = [\"wasmtime\", \"wasi-common\"]\n\n[dependencies.clap]\nversion = \"4.5.13\"\ndefault-features = false\nfeatures = [\"std\", \"suggestions\", \"derive\", \"cargo\", \"help\", \"usage\", \"error-context\"]\n\n[dependencies]\nanyhow = \"1.0.102\"\nchrono = { version = \"0.4\", default-features = false, features = [\"clock\", \"serde\"] }\nlibcgroups = { path = \"../libcgroups\", default-features = false, version = \"0.6.0\" } # MARK: Version\nlibcontainer = { path = \"../libcontainer\", default-features = false, version = \"0.6.0\" } # MARK: Version\nliboci-cli = { path = \"../liboci-cli\", version = \"0.6.0\" } # MARK: Version\nnix = \"0.29.0\"\npentacle = \"1.1.0\"\nprocfs = \"0.17.0\"\nserde_json = \"1.0\"\ntabwriter = \"1\"\nclap_complete = \"4.5.13\"\ncaps = \"0.5.6\"\nwasmer = { version = \"7.0.1\", optional = true, features = [\"sys\",\"singlepass\"] }\nwasmer-wasix = { version = \"0.700.1\", optional = true }\nwasmedge-sdk = { version = \"0.14.0\", optional = true }\nwasmtime = { version = \"42.0.0\", optional = true }\nwasi-common = { version = \"42.0.1\", optional = true }\ntracing = { version = \"0.1.44\", features = [\"attributes\"] }\ntracing-subscriber = { version = \"0.3.23\", features = [\"json\", \"env-filter\"] }\ntracing-journald = \"0.3.2\"\noci-spec = { version = \"0.9.0\", features = [\"runtime\"] }\n\n[dev-dependencies]\nserial_test = \"3.4.0\"\ntempfile = \"3\"\nscopeguard = \"1.2.0\"\n\n[build-dependencies]\nanyhow = \"1.0.102\"\nvergen-gitcl = { version = \"9.1.0\", features = [\"build\", \"rustc\"] }\npkg-config = \"0.3.32\"\n"
  },
  {
    "path": "crates/youki/build.rs",
    "content": "use anyhow::Result;\nuse vergen_gitcl::{Emitter, GitclBuilder, RustcBuilder};\n\npub fn main() -> Result<()> {\n    if Emitter::default()\n        .add_instructions(&GitclBuilder::all_git()?)?\n        .add_instructions(&RustcBuilder::all_rustc()?)?\n        .emit()\n        .is_err()\n    {\n        // currently we only inject git sha, so just this\n        // else we will need to think of more elegant way to check\n        // what failed, and what needs to be added\n        println!(\"cargo:rustc-env=VERGEN_GIT_SHA=unknown\");\n        println!(\"cargo:rustc-env=VERGEN_RUSTC_SEMVER=unknown\");\n    }\n\n    // Embed libseccomp version at build time (only when seccomp feature is enabled)\n    if std::env::var(\"CARGO_FEATURE_SECCOMP\").is_ok() {\n        let version = pkg_config::probe_library(\"libseccomp\")\n            .map(|lib| lib.version)\n            .unwrap_or_else(|_| \"unknown\".to_string());\n        println!(\"cargo:rustc-env=LIBSECCOMP_VERSION={}\", version);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/youki/src/commands/checkpoint.rs",
    "content": "//! Contains functionality of pause container command\nuse std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Checkpoint;\n\nuse crate::commands::load_container;\n\npub fn checkpoint(args: Checkpoint, root_path: PathBuf) -> Result<()> {\n    tracing::debug!(\"start checkpointing container {}\", args.container_id);\n    let mut container = load_container(root_path, &args.container_id)?;\n    let opts = libcontainer::container::CheckpointOptions {\n        ext_unix_sk: args.ext_unix_sk,\n        file_locks: args.file_locks,\n        image_path: args.image_path,\n        leave_running: args.leave_running,\n        shell_job: args.shell_job,\n        tcp_established: args.tcp_established,\n        work_path: args.work_path,\n    };\n    container\n        .checkpoint(&opts)\n        .with_context(|| format!(\"failed to checkpoint container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/completion.rs",
    "content": "use std::io;\n\nuse anyhow::Result;\nuse clap::{Command, Parser};\nuse clap_complete::{Shell, generate};\n\n#[derive(Debug, Parser)]\n/// Generate scripts for shell completion\npub struct Completion {\n    #[clap(long = \"shell\", short = 's', value_enum)]\n    pub shell: Shell,\n}\n\npub fn completion(args: Completion, app: &mut Command) -> Result<()> {\n    generate(\n        args.shell,\n        app,\n        app.get_name().to_string(),\n        &mut io::stdout(),\n    );\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/youki/src/commands/create.rs",
    "content": "//! Handles the creation of a new container\nuse std::path::PathBuf;\n\nuse anyhow::Result;\nuse libcontainer::container::builder::ContainerBuilder;\nuse libcontainer::syscall::syscall::SyscallType;\nuse liboci_cli::Create;\n\nuse crate::workload::executor::default_executor;\n\n// One thing to note is that in the end, container is just another process in Linux\n// it has specific/different control group, namespace, using which program executing in it\n// can be given impression that is is running on a complete system, but on the system which\n// it is running, it is just another process, and has attributes such as pid, file descriptors, etc.\n// associated with it like any other process.\npub fn create(args: Create, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> {\n    ContainerBuilder::new(args.container_id.clone(), SyscallType::default())\n        .with_executor(default_executor())\n        .with_pid_file(args.pid_file.as_ref())?\n        .with_console_socket(args.console_socket.as_ref())\n        .with_root_path(root_path)?\n        .with_preserved_fds(args.preserve_fds)\n        .validate_id()?\n        .as_init(&args.bundle)\n        .with_systemd(systemd_cgroup)\n        .with_detach(true)\n        .with_no_pivot(args.no_pivot)\n        .build()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/youki/src/commands/delete.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Delete;\n\nuse crate::commands::{container_exists, load_container};\n\npub fn delete(args: Delete, root_path: PathBuf) -> Result<()> {\n    tracing::debug!(\"start deleting {}\", args.container_id);\n    if !container_exists(&root_path, &args.container_id)? && args.force {\n        return Ok(());\n    }\n\n    let mut container = load_container(root_path, &args.container_id)?;\n    container\n        .delete(args.force)\n        .with_context(|| format!(\"failed to delete container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/events.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Events;\n\nuse crate::commands::load_container;\n\npub fn events(args: Events, root_path: PathBuf) -> Result<()> {\n    let mut container = load_container(root_path, &args.container_id)?;\n    container\n        .events(args.interval, args.stats)\n        .with_context(|| format!(\"failed to get events from container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/exec.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::Result;\nuse libcontainer::container::builder::ContainerBuilder;\nuse libcontainer::syscall::syscall::SyscallType;\nuse liboci_cli::Exec;\nuse nix::sys::wait::{WaitStatus, waitpid};\n\nuse crate::workload::executor::default_executor;\n\npub fn exec(args: Exec, root_path: PathBuf) -> Result<i32> {\n    // TODO: not all values from exec are used here. We need to support\n    // the remaining ones.\n    let user = args.user.map(|(u, _)| u);\n    let group = args.user.and_then(|(_, g)| g);\n\n    let pid = ContainerBuilder::new(args.container_id.clone(), SyscallType::default())\n        .with_executor(default_executor())\n        .with_root_path(root_path)?\n        .with_console_socket(args.console_socket.as_ref())\n        .with_pid_file(args.pid_file.as_ref())?\n        .validate_id()?\n        .as_tenant()\n        .with_detach(args.detach)\n        .with_cwd(args.cwd.as_ref())\n        .with_env(args.env.clone().into_iter().collect())\n        .with_process(args.process.as_ref())\n        .with_no_new_privs(args.no_new_privs)\n        .with_container_args(args.command.clone())\n        .with_additional_gids(args.additional_gids)\n        .with_user(user)\n        .with_group(group)\n        .build()?;\n\n    // See https://github.com/youki-dev/youki/pull/1252 for a detailed explanation\n    // basically, if there is any error in starting exec, the build above will return error\n    // however, if the process does start, and detach is given, we do not wait for it\n    // if not detached, then we wait for it using waitpid below\n    if args.detach {\n        return Ok(0);\n    }\n\n    match waitpid(pid, None)? {\n        WaitStatus::Exited(_, status) => Ok(status),\n        WaitStatus::Signaled(_, sig, _) => Ok(sig as i32),\n        _ => Ok(0),\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/features.rs",
    "content": "//! Contains Functionality of `features` container command\nuse anyhow::Result;\nuse libcontainer::oci_spec::runtime::{\n    ApparmorBuilder, CgroupBuilder, FeaturesBuilder, IDMapBuilder, IntelRdtBuilder,\n    LinuxFeatureBuilder, LinuxNamespaceType, MountExtensionsBuilder, SelinuxBuilder, VERSION,\n};\nuse libcontainer::syscall::linux::MountOption;\nuse liboci_cli::Features;\n\n// Function to query and return capabilities\nfn query_caps() -> Result<Vec<String>> {\n    Ok(caps::all().iter().map(|cap| format!(\"{:?}\", cap)).collect())\n}\n\n// Function to query and return namespaces\nfn query_supported_namespaces() -> Result<Vec<LinuxNamespaceType>> {\n    Ok(vec![\n        LinuxNamespaceType::Pid,\n        LinuxNamespaceType::Network,\n        LinuxNamespaceType::Uts,\n        LinuxNamespaceType::Ipc,\n        LinuxNamespaceType::Mount,\n        LinuxNamespaceType::User,\n        LinuxNamespaceType::Cgroup,\n        LinuxNamespaceType::Time,\n    ])\n}\n\n// Return a list of known hooks supported by youki\nfn known_hooks() -> Vec<String> {\n    [\n        \"prestart\",\n        \"createRuntime\",\n        \"createContainer\",\n        \"startContainer\",\n        \"poststart\",\n        \"poststop\",\n    ]\n    .iter()\n    .map(|s| s.to_string())\n    .collect()\n}\n\n/// lists all existing containers\npub fn features(_: Features) -> Result<()> {\n    // Query supported namespaces\n    let namespaces = match query_supported_namespaces() {\n        Ok(ns) => ns,\n        Err(e) => {\n            eprintln!(\"Error querying supported namespaces: {}\", e);\n            Vec::new()\n        }\n    };\n\n    // Query available capabilities\n    let capabilities = match query_caps() {\n        Ok(caps) => caps,\n        Err(e) => {\n            eprintln!(\"Error querying available capabilities: {}\", e);\n            Vec::new()\n        }\n    };\n\n    let linux = LinuxFeatureBuilder::default()\n        .namespaces(namespaces)\n        .capabilities(capabilities)\n        .cgroup(\n            CgroupBuilder::default()\n                .v1(cfg!(feature = \"v1\"))\n                .v2(cfg!(feature = \"v2\"))\n                .systemd(cfg!(feature = \"systemd\"))\n                .systemd_user(cfg!(feature = \"systemd\"))\n                // cgroupv2 rdma controller is not implemented in youki.\n                .rdma(false)\n                .build()\n                .unwrap(),\n        )\n        // TODO: Expose seccomp support information\n        .apparmor(ApparmorBuilder::default().enabled(true).build().unwrap())\n        .mount_extensions(\n            MountExtensionsBuilder::default()\n                // idmapped mounts is not supported in youki\n                .idmap(IDMapBuilder::default().enabled(false).build().unwrap())\n                .build()\n                .unwrap(),\n        )\n        // SELinux is not supported in youki.\n        .selinux(SelinuxBuilder::default().enabled(false).build().unwrap())\n        .intel_rdt(IntelRdtBuilder::default().enabled(true).build().unwrap())\n        .build()\n        .unwrap();\n\n    let features = FeaturesBuilder::default()\n        .oci_version_max(VERSION)\n        .oci_version_min(String::from(\"1.0.0\"))\n        .hooks(known_hooks())\n        .mount_options(MountOption::known_options())\n        .linux(linux)\n        .build()\n        .unwrap();\n\n    // Print out the created struct to verify\n    let pretty_json_str = serde_json::to_string_pretty(&features)?;\n    println!(\"{}\", pretty_json_str);\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_features() {\n        let features = Features {};\n        assert!(crate::commands::features::features(features).is_ok());\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/info.rs",
    "content": "//! Contains functions related to printing information about system running Youki\n#[cfg(feature = \"v2\")]\nuse std::collections::HashSet;\nuse std::fs;\nuse std::path::Path;\n\nuse anyhow::Result;\nuse clap::Parser;\n#[cfg(feature = \"v2\")]\nuse libcgroups::{common::CgroupSetup, v2::controller_type::ControllerType};\nuse libcontainer::user_ns;\nuse procfs::{CpuInfo, Current, Meminfo};\n/// Show information about the system\n#[derive(Parser, Debug)]\npub struct Info {}\n\npub fn info(_: Info) -> Result<()> {\n    print_youki();\n    print_kernel();\n    print_os();\n    print_hardware();\n    print_cgroups();\n    print_namespaces();\n    print_capabilities();\n\n    Ok(())\n}\n\n/// Prints the version of youki in a format compatible with `runc --version` and Moby.\n///\n/// See:\n/// - <https://github.com/opencontainers/runc/blob/v1.4.0/main.go#L37-L51>\n/// - <https://github.com/moby/moby/blob/65cc84abc522a564699bb171ca54ea1857256d10/daemon/info_unix.go#L280>\npub fn print_youki() {\n    println!(\"youki version: {}\", env!(\"CARGO_PKG_VERSION\"));\n    println!(\n        \"commit: {}-{}\",\n        env!(\"CARGO_PKG_VERSION\"),\n        env!(\"VERGEN_GIT_SHA\")\n    );\n    println!(\"spec: {}\", oci_spec::runtime::VERSION);\n    println!(\n        \"rustc: {}\",\n        option_env!(\"VERGEN_RUSTC_SEMVER\").unwrap_or(\"unknown\")\n    );\n    #[cfg(feature = \"seccomp\")]\n    println!(\n        \"libseccomp: {}\",\n        option_env!(\"LIBSECCOMP_VERSION\").unwrap_or(\"unknown\")\n    );\n}\n\n/// Print Kernel Release, Version and Architecture\npub fn print_kernel() {\n    let uname = nix::sys::utsname::uname().unwrap();\n    println!(\n        \"{:<18}{}\",\n        \"Kernel-Release\",\n        uname.release().to_string_lossy()\n    );\n    println!(\n        \"{:<18}{}\",\n        \"Kernel-Version\",\n        uname.version().to_string_lossy()\n    );\n    println!(\n        \"{:<18}{}\",\n        \"Architecture\",\n        uname.machine().to_string_lossy()\n    );\n}\n\n/// Prints OS Distribution information\n// see https://www.freedesktop.org/software/systemd/man/os-release.html\npub fn print_os() {\n    if let Some(os) = try_read_os_from(\"/etc/os-release\") {\n        println!(\"{:<18}{}\", \"Operating System\", os);\n    } else if let Some(os) = try_read_os_from(\"/usr/lib/os-release\") {\n        println!(\"{:<18}{}\", \"Operating System\", os);\n    } else {\n        println!(\"{:<18}UNKNOWN\", \"Operating System\");\n    }\n}\n\n/// Helper function to read the OS Distribution info\nfn try_read_os_from<P: AsRef<Path>>(path: P) -> Option<String> {\n    let os_release = path.as_ref();\n    if !os_release.exists() {\n        return None;\n    }\n\n    if let Ok(release_content) = fs::read_to_string(path) {\n        let pretty = find_parameter(&release_content, \"PRETTY_NAME\");\n\n        if let Some(pretty) = pretty {\n            return Some(pretty.trim_matches('\"').to_owned());\n        }\n\n        let name = find_parameter(&release_content, \"NAME\");\n        let version = find_parameter(&release_content, \"VERSION\");\n\n        if let Some((name, version)) = name.zip(version) {\n            return Some(format!(\n                \"{} {}\",\n                name.trim_matches('\"'),\n                version.trim_matches('\"')\n            ));\n        }\n    }\n\n    None\n}\n\n/// Helper function to find keyword values in OS info string\nfn find_parameter<'a>(content: &'a str, param_name: &str) -> Option<&'a str> {\n    content\n        .lines()\n        .find(|l| l.starts_with(param_name))\n        .and_then(|l| l.split_terminator('=').next_back())\n}\n\n/// Print Hardware information of system\npub fn print_hardware() {\n    if let Ok(cpu_info) = CpuInfo::current() {\n        println!(\"{:<18}{}\", \"Cores\", cpu_info.num_cores());\n    }\n\n    if let Ok(mem_info) = Meminfo::current() {\n        println!(\n            \"{:<18}{}\",\n            \"Total Memory\",\n            mem_info.mem_total / u64::pow(1024, 2)\n        );\n    }\n}\n\n/// Print cgroups info of system\npub fn print_cgroups() {\n    print_cgroups_setup();\n    print_cgroup_mounts();\n    #[cfg(feature = \"v2\")]\n    print_cgroup_v2_controllers();\n}\n\npub fn print_cgroups_setup() {\n    let cgroup_setup = libcgroups::common::get_cgroup_setup();\n    if let Ok(cgroup_setup) = &cgroup_setup {\n        println!(\"{:<18}{}\", \"Cgroup setup\", cgroup_setup);\n    }\n}\n\npub fn print_cgroup_mounts() {\n    println!(\"Cgroup mounts\");\n    #[cfg(feature = \"v1\")]\n    if let Ok(v1_mounts) = libcgroups::v1::util::list_supported_mount_points() {\n        let mut v1_mounts: Vec<String> = v1_mounts\n            .iter()\n            .map(|kv| format!(\"  {:<16}{}\", kv.0.to_string(), kv.1.display()))\n            .collect();\n\n        v1_mounts.sort();\n        for cgroup_mount in v1_mounts {\n            println!(\"{cgroup_mount}\");\n        }\n    }\n\n    #[cfg(feature = \"v2\")]\n    if let Ok(mount_point) = libcgroups::v2::util::get_unified_mount_point() {\n        println!(\"  {:<16}{}\", \"unified\", mount_point.display());\n    }\n}\n\n#[cfg(feature = \"v2\")]\npub fn print_cgroup_v2_controllers() {\n    let cgroup_setup = libcgroups::common::get_cgroup_setup();\n    let unified = libcgroups::v2::util::get_unified_mount_point();\n\n    if let Ok(cgroup_setup) = cgroup_setup\n        && let Ok(unified) = &unified\n        && matches!(cgroup_setup, CgroupSetup::Hybrid | CgroupSetup::Unified)\n    {\n        if let Ok(controllers) = libcgroups::v2::util::get_available_controllers(unified) {\n            println!(\"CGroup v2 controllers\");\n            let active_controllers: HashSet<ControllerType> = controllers.into_iter().collect();\n            for controller in libcgroups::v2::controller_type::CONTROLLER_TYPES {\n                let status = if active_controllers.contains(controller) {\n                    \"attached\"\n                } else {\n                    \"detached\"\n                };\n\n                println!(\"  {:<16}{}\", controller.to_string(), status);\n            }\n        }\n\n        if let Some(config) = read_kernel_config() {\n            let display = FeatureDisplay::with_status(\"device\", \"attached\", \"detached\");\n            print_feature_status(&config, \"CONFIG_CGROUP_BPF\", display);\n        }\n    }\n}\n\nfn read_kernel_config() -> Option<String> {\n    let uname = nix::sys::utsname::uname();\n    let kernel_config = Path::new(\"/boot\").join(format!(\n        \"config-{}\",\n        uname.unwrap().release().to_string_lossy()\n    ));\n    if !kernel_config.exists() {\n        return None;\n    }\n\n    fs::read_to_string(kernel_config).ok()\n}\n\npub fn print_namespaces() {\n    if let Some(content) = read_kernel_config() {\n        if let Some(ns_enabled) = find_parameter(&content, \"CONFIG_NAMESPACES\") {\n            if ns_enabled == \"y\" {\n                println!(\"{:<18}enabled\", \"Namespaces\");\n            } else {\n                println!(\"{:<18}disabled\", \"Namespaces\");\n                return;\n            }\n        } else {\n            println!(\"{:<18}UNKNOWN\", \"Namespaces\");\n            // we don't return as  we can atleast try and see if anything is enabled\n        }\n\n        // mount namespace is always enabled if namespaces are enabled\n        println!(\"  {:<16}enabled\", \"mount\");\n        print_feature_status(&content, \"CONFIG_UTS_NS\", FeatureDisplay::new(\"uts\"));\n        print_feature_status(&content, \"CONFIG_IPC_NS\", FeatureDisplay::new(\"ipc\"));\n\n        let user_display = match user_ns::unprivileged_user_ns_enabled() {\n            Ok(false) => FeatureDisplay::with_status(\"user\", \"enabled (root only)\", \"disabled\"),\n            _ => FeatureDisplay::new(\"user\"),\n        };\n        print_feature_status(&content, \"CONFIG_USER_NS\", user_display);\n        print_feature_status(&content, \"CONFIG_PID_NS\", FeatureDisplay::new(\"pid\"));\n        print_feature_status(&content, \"CONFIG_NET_NS\", FeatureDisplay::new(\"network\"));\n        // While the CONFIG_CGROUP_NS kernel feature exists, it is obsolete and should not be used. CGroup namespaces\n        // are instead enabled with CONFIG_CGROUPS.\n        print_feature_status(&content, \"CONFIG_CGROUPS\", FeatureDisplay::new(\"cgroup\"))\n    }\n}\n\n#[inline]\nfn is_cap_available(caps: &caps::CapsHashSet, cap: caps::Capability) -> &'static str {\n    if caps.contains(&cap) {\n        \"available\"\n    } else {\n        \"unavailable\"\n    }\n}\n\npub fn print_capabilities() {\n    println!(\"Capabilities\");\n    if let Ok(current) = caps::read(None, caps::CapSet::Bounding) {\n        println!(\n            \"{:<17} {}\",\n            \"CAP_BPF\",\n            is_cap_available(&current, caps::Capability::CAP_BPF)\n        );\n        println!(\n            \"{:<17} {}\",\n            \"CAP_PERFMON\",\n            is_cap_available(&current, caps::Capability::CAP_PERFMON)\n        );\n        println!(\n            \"{:<17} {}\",\n            \"CAP_CHECKPOINT_RESTORE\",\n            is_cap_available(&current, caps::Capability::CAP_CHECKPOINT_RESTORE)\n        );\n    } else {\n        println!(\"<cannot find cap info>\");\n    }\n}\n\nfn print_feature_status(config: &str, feature: &str, display: FeatureDisplay) {\n    if let Some(status_flag) = find_parameter(config, feature) {\n        let status = if status_flag == \"y\" {\n            display.enabled\n        } else {\n            display.disabled\n        };\n\n        println!(\"  {:<16}{}\", display.name, status);\n    } else {\n        println!(\"  {:<16}UNKNOWN\", display.name);\n    }\n}\n\nstruct FeatureDisplay<'a> {\n    name: &'a str,\n    enabled: &'a str,\n    disabled: &'a str,\n}\n\nimpl<'a> FeatureDisplay<'a> {\n    fn new(name: &'a str) -> Self {\n        Self {\n            name,\n            enabled: \"enabled\",\n            disabled: \"disabled\",\n        }\n    }\n\n    fn with_status(name: &'a str, enabled: &'a str, disabled: &'a str) -> Self {\n        Self {\n            name,\n            enabled,\n            disabled,\n        }\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/kill.rs",
    "content": "//! Contains functionality of kill container command\nuse std::convert::TryInto;\nuse std::path::PathBuf;\n\nuse anyhow::{Result, anyhow};\nuse libcontainer::container::ContainerStatus;\nuse libcontainer::signal::Signal;\nuse liboci_cli::Kill;\n\nuse crate::commands::load_container;\n\npub fn kill(args: Kill, root_path: PathBuf) -> Result<()> {\n    let mut container = load_container(root_path, &args.container_id)?;\n    let signal: Signal = args.signal.as_str().try_into()?;\n    match container.kill(signal, args.all) {\n        Ok(_) => Ok(()),\n        Err(e) => {\n            // see https://github.com/youki-dev/youki/issues/1314\n            if container.status() == ContainerStatus::Stopped {\n                return Err(anyhow!(e).context(\"container not running\"));\n            }\n            Err(anyhow!(e).context(\"failed to kill container\"))\n        }\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/list.rs",
    "content": "//! Contains Functionality of list container command\nuse std::fmt::Write as _;\nuse std::io::Write;\nuse std::path::PathBuf;\nuse std::{fs, io};\n\nuse anyhow::Result;\nuse chrono::{DateTime, Local};\nuse libcontainer::container::Container;\nuse libcontainer::container::state::State;\nuse liboci_cli::List;\nuse tabwriter::TabWriter;\n\n/// lists all existing containers\npub fn list(_: List, root_path: PathBuf) -> Result<()> {\n    let root_path = fs::canonicalize(root_path)?;\n    let mut content = String::new();\n    // all containers' data is stored in their respective dir in root directory\n    // so we iterate through each and print the various info\n    for container_dir in fs::read_dir(root_path)? {\n        let container_dir = container_dir?.path();\n        let state_file = State::file_path(&container_dir);\n        if !state_file.exists() {\n            continue;\n        }\n\n        let container = Container::load(container_dir)?;\n        let pid = if let Some(pid) = container.pid() {\n            pid.to_string()\n        } else {\n            \"\".to_owned()\n        };\n\n        let user_name = container.creator().unwrap_or_default();\n\n        let created = if let Some(utc) = container.created() {\n            let local: DateTime<Local> = DateTime::from(utc);\n            local.to_rfc3339_opts(chrono::SecondsFormat::Secs, false)\n        } else {\n            \"\".to_owned()\n        };\n\n        let _ = writeln!(\n            content,\n            \"{}\\t{}\\t{}\\t{}\\t{}\\t{}\",\n            container.id(),\n            pid,\n            container.status(),\n            container.bundle().display(),\n            created,\n            user_name.to_string_lossy()\n        );\n    }\n\n    let mut tab_writer = TabWriter::new(io::stdout());\n    writeln!(&mut tab_writer, \"ID\\tPID\\tSTATUS\\tBUNDLE\\tCREATED\\tCREATOR\")?;\n    write!(&mut tab_writer, \"{content}\")?;\n    tab_writer.flush()?;\n\n    Ok(())\n}\n"
  },
  {
    "path": "crates/youki/src/commands/mod.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, bail};\nuse libcgroups::common::AnyCgroupManager;\nuse libcontainer::container::Container;\n\npub mod checkpoint;\npub mod completion;\npub mod create;\npub mod delete;\npub mod events;\npub mod exec;\npub mod features;\npub mod info;\npub mod kill;\npub mod list;\npub mod pause;\npub mod ps;\npub mod resume;\npub mod run;\npub mod spec_json;\npub mod start;\npub mod state;\npub mod update;\n\nfn construct_container_root<P: AsRef<Path>>(root_path: P, container_id: &str) -> Result<PathBuf> {\n    // resolves relative paths, symbolic links etc. and get complete path\n    let root_path = fs::canonicalize(&root_path).with_context(|| {\n        format!(\n            \"failed to canonicalize {} for container {}\",\n            root_path.as_ref().display(),\n            container_id\n        )\n    })?;\n    // the state of the container is stored in a directory named after the container id\n    Ok(root_path.join(container_id))\n}\n\nfn load_container<P: AsRef<Path>>(root_path: P, container_id: &str) -> Result<Container> {\n    let container_root = construct_container_root(root_path, container_id)?;\n    if !container_root.exists() {\n        bail!(\"container {} does not exist.\", container_id)\n    }\n\n    Container::load(container_root)\n        .with_context(|| format!(\"could not load state for container {container_id}\"))\n}\n\nfn container_exists<P: AsRef<Path>>(root_path: P, container_id: &str) -> Result<bool> {\n    let container_root = construct_container_root(root_path, container_id)?;\n    Ok(container_root.exists())\n}\n\nfn create_cgroup_manager<P: AsRef<Path>>(\n    root_path: P,\n    container_id: &str,\n) -> Result<AnyCgroupManager> {\n    let container = load_container(root_path, container_id)?;\n    Ok(libcgroups::common::create_cgroup_manager(\n        libcgroups::common::CgroupConfig {\n            cgroup_path: container.spec()?.cgroup_path,\n            systemd_cgroup: container.systemd(),\n            container_name: container.id().to_string(),\n        },\n    )?)\n}\n"
  },
  {
    "path": "crates/youki/src/commands/pause.rs",
    "content": "//! Contains functionality of pause container command\nuse std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Pause;\n\nuse crate::commands::load_container;\n\n// Pausing a container indicates suspending all processes in given container\n// This uses Freezer cgroup to suspend and resume processes\n// For more information see :\n// https://man7.org/linux/man-pages/man7/cgroups.7.html\n// https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt\npub fn pause(args: Pause, root_path: PathBuf) -> Result<()> {\n    tracing::debug!(\"start pausing container {}\", args.container_id);\n    let mut container = load_container(root_path, &args.container_id)?;\n    container\n        .pause()\n        .with_context(|| format!(\"failed to pause container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/ps.rs",
    "content": "use std::path::PathBuf;\nuse std::process::Command;\n\nuse anyhow::{Result, bail};\nuse libcgroups::common::CgroupManager;\nuse liboci_cli::Ps;\n\nuse crate::commands::create_cgroup_manager;\n\npub fn ps(args: Ps, root_path: PathBuf) -> Result<()> {\n    let cmanager = create_cgroup_manager(root_path, &args.container_id)?;\n\n    let pids: Vec<i32> = cmanager\n        .get_all_pids()?\n        .iter()\n        .map(|pid| pid.as_raw())\n        .collect();\n\n    if args.format == \"json\" {\n        println!(\"{}\", serde_json::to_string(&pids)?);\n    } else if args.format == \"table\" {\n        let default_ps_options = vec![String::from(\"-ef\")];\n        let ps_options = if args.ps_options.is_empty() {\n            &default_ps_options\n        } else {\n            &args.ps_options\n        };\n        let output = Command::new(\"ps\").args(ps_options).output()?;\n        if !output.status.success() {\n            println!(\"{}\", std::str::from_utf8(&output.stderr)?);\n        } else {\n            let lines = std::str::from_utf8(&output.stdout)?;\n            let lines: Vec<&str> = lines.split('\\n').collect();\n            let pid_index = get_pid_index(lines[0])?;\n            println!(\"{}\", &lines[0]);\n            for line in &lines[1..] {\n                if line.is_empty() {\n                    continue;\n                }\n                let fields: Vec<&str> = line.split_whitespace().collect();\n                let pid: i32 = fields[pid_index].parse()?;\n                if pids.contains(&pid) {\n                    println!(\"{line}\");\n                }\n            }\n        }\n    }\n    Ok(())\n}\n\nfn get_pid_index(title: &str) -> Result<usize> {\n    let titles = title.split_whitespace();\n\n    for (index, name) in titles.enumerate() {\n        if name == \"PID\" {\n            return Ok(index);\n        }\n    }\n    bail!(\"could't find PID field in ps output\");\n}\n"
  },
  {
    "path": "crates/youki/src/commands/resume.rs",
    "content": "//! Contains functionality of resume container command\nuse std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Resume;\n\nuse crate::commands::load_container;\n\n// Resuming a container indicates resuming all processes in given container from paused state\n// This uses Freezer cgroup to suspend and resume processes\n// For more information see :\n// https://man7.org/linux/man-pages/man7/cgroups.7.html\n// https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt\npub fn resume(args: Resume, root_path: PathBuf) -> Result<()> {\n    tracing::debug!(\"start resuming container {}\", args.container_id);\n    let mut container = load_container(root_path, &args.container_id)?;\n    container\n        .resume()\n        .with_context(|| format!(\"failed to resume container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/run.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse libcontainer::container::builder::ContainerBuilder;\nuse libcontainer::syscall::syscall::SyscallType;\nuse liboci_cli::Run;\nuse nix::sys::signal::{self, kill};\nuse nix::sys::signalfd::SigSet;\nuse nix::sys::wait::{WaitPidFlag, WaitStatus, waitpid};\nuse nix::unistd::Pid;\n\nuse crate::workload::executor::default_executor;\n\npub fn run(args: Run, root_path: PathBuf, systemd_cgroup: bool) -> Result<i32> {\n    let mut container = ContainerBuilder::new(args.container_id.clone(), SyscallType::default())\n        .with_executor(default_executor())\n        .with_pid_file(args.pid_file.as_ref())?\n        .with_console_socket(args.console_socket.as_ref())\n        .with_root_path(root_path)?\n        .with_preserved_fds(args.preserve_fds)\n        .validate_id()?\n        .as_init(&args.bundle)\n        .with_systemd(systemd_cgroup)\n        .with_detach(args.detach)\n        .with_no_pivot(args.no_pivot)\n        .build()?;\n\n    container\n        .start()\n        .with_context(|| format!(\"failed to start container {}\", args.container_id))?;\n\n    if args.detach {\n        return Ok(0);\n    }\n\n    // Using `debug_assert` here rather than returning an error because this is\n    // a invariant. The design when the code path arrives to this point, is that\n    // the container state must have recorded the container init pid.\n    debug_assert!(\n        container.pid().is_some(),\n        \"expects a container init pid in the container state\"\n    );\n    let foreground_result = handle_foreground(container.pid().unwrap());\n    // execute the destruction action after the container finishes running\n    container.delete(true)?;\n    // return result\n    foreground_result\n}\n\n// handle_foreground will match the `runc` behavior running the foreground mode.\n// The youki main process will wait and reap the container init process. The\n// youki main process also forwards most of the signals to the container init\n// process.\n#[tracing::instrument(level = \"trace\")]\nfn handle_foreground(init_pid: Pid) -> Result<i32> {\n    tracing::trace!(\"waiting for container init process to exit\");\n    // We mask all signals here and forward most of the signals to the container\n    // init process.\n    let signal_set = SigSet::all();\n    signal_set\n        .thread_block()\n        .with_context(|| \"failed to call pthread_sigmask\")?;\n    loop {\n        match signal_set\n            .wait()\n            .with_context(|| \"failed to call sigwait\")?\n        {\n            signal::SIGCHLD => {\n                // Reap all child until either container init process exits or\n                // no more child to be reaped. Once the container init process\n                // exits we can then return.\n                tracing::trace!(\"reaping child processes\");\n                loop {\n                    match waitpid(None, Some(WaitPidFlag::WNOHANG))? {\n                        WaitStatus::Exited(pid, status) => {\n                            if pid.eq(&init_pid) {\n                                return Ok(status);\n                            }\n\n                            // Else, some random child process exited, ignoring...\n                        }\n                        WaitStatus::Signaled(pid, signal, _) => {\n                            if pid.eq(&init_pid) {\n                                return Ok(signal as i32);\n                            }\n\n                            // Else, some random child process exited, ignoring...\n                        }\n                        WaitStatus::StillAlive => {\n                            // No more child to reap.\n                            break;\n                        }\n                        _ => {}\n                    }\n                }\n            }\n            signal::SIGURG => {\n                // In `runc`, SIGURG is used by go runtime and should not be forwarded to\n                // the container process. Here, we just ignore the signal.\n            }\n            signal::SIGWINCH => {\n                // TODO: resize the terminal\n            }\n            signal => {\n                tracing::trace!(?signal, \"forwarding signal\");\n                // There is nothing we can do if we fail to forward the signal.\n                let _ = kill(init_pid, Some(signal)).map_err(|err| {\n                    tracing::warn!(\n                        ?err,\n                        ?signal,\n                        \"failed to forward signal to container init process\",\n                    );\n                });\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::time::Duration;\n\n    use nix::sys::signal::Signal::SIGINT;\n    use nix::sys::wait;\n    use nix::unistd;\n\n    use super::*;\n\n    #[test]\n    fn test_foreground_forward_sig() -> Result<()> {\n        // To set up the test correctly, we need to run the test in dedicated\n        // process, so the rust unit test runtime and other unit tests will not\n        // mess with the signal handling. We use `sigkill` as a simple way to\n        // make sure the signal is properly forwarded. In this test, P0 is the\n        // rust process that runs this unit test (in a thread). P1 mocks youki\n        // main and P2 mocks the container init process\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                // Inside P0\n                //\n                // We need to make sure that the child process has entered into\n                // the signal forwarding loops. There is no way to 100% sync\n                // that the child has executed the for loop waiting to forward\n                // the signal. There are sync mechanisms with condvar or\n                // channels to make it as close to calling the handle_foreground\n                // function as possible, but still have a tiny (highly unlikely\n                // but probable) window that a race can still happen. So instead\n                // we just wait for 1 second for everything to settle. In\n                // general, I don't like sleep in tests to avoid race condition,\n                // but I'd rather not over-engineer this now. We can revisit\n                // this later if the test becomes flaky.\n                std::thread::sleep(Duration::from_secs(1));\n                // Send the `sigint` signal to P1 who will forward the signal\n                // to P2. P2 will then exit and send a sigchld to P1. P1 will\n                // then reap P2 and exits. In P0, we can then reap P1.\n                kill(child, SIGINT)?;\n                wait::waitpid(child, None)?;\n            }\n            unistd::ForkResult::Child => {\n                // Inside P1. Fork P2 as mock container init process and run\n                // signal handler process inside.\n                match unsafe { unistd::fork()? } {\n                    unistd::ForkResult::Parent { child } => {\n                        // Inside P1.\n                        let _ = handle_foreground(child).map_err(|err| {\n                            // Since we are in a child process, we want to use trace to log the error.\n                            let _ = tracing_subscriber::fmt()\n                                .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())\n                                .try_init();\n                            tracing::error!(?err, \"failed to handle foreground\");\n                            err\n                        });\n                        std::process::exit(0);\n                    }\n                    unistd::ForkResult::Child => {\n                        let mut signal_set = SigSet::empty();\n                        signal_set.add(SIGINT);\n                        signal_set.thread_block()?;\n                        signal_set.wait()?;\n                        std::process::exit(0);\n                    }\n                };\n            }\n        };\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_foreground_exit() -> Result<()> {\n        // The setup is similar to `handle_foreground`, but instead of\n        // forwarding signal, the container init process will exit. Again, we\n        // use `sleep` to simulate the conditions to avoid fine grained\n        // synchronization for now.\n        match unsafe { unistd::fork()? } {\n            unistd::ForkResult::Parent { child } => {\n                // Inside P0\n                std::thread::sleep(Duration::from_secs(1));\n                wait::waitpid(child, None)?;\n            }\n            unistd::ForkResult::Child => {\n                // Inside P1. Fork P2 as mock container init process and run\n                // signal handler process inside.\n                match unsafe { unistd::fork()? } {\n                    unistd::ForkResult::Parent { child } => {\n                        // Inside P1.\n                        handle_foreground(child)?;\n                        wait::waitpid(child, None)?;\n                    }\n                    unistd::ForkResult::Child => {\n                        // Inside P2. The process exits after 1 second.\n                        std::thread::sleep(Duration::from_secs(1));\n                    }\n                };\n            }\n        };\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/spec_json.rs",
    "content": "use std::fs::File;\nuse std::io::{BufWriter, Write};\nuse std::path::{Path, PathBuf};\n\nuse anyhow::Result;\nuse libcontainer::oci_spec::runtime::{\n    LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder, LinuxNamespaceType,\n    Mount, Spec,\n};\nuse libcontainer::syscall::syscall::Syscall;\nuse serde_json::to_writer_pretty;\n\npub fn get_default() -> Result<Spec> {\n    Ok(Spec::default())\n}\n\npub fn get_rootless(syscall: &dyn Syscall) -> Result<Spec> {\n    // Remove network and user namespace from the default spec\n    let mut namespaces: Vec<LinuxNamespace> =\n        libcontainer::oci_spec::runtime::get_default_namespaces()\n            .into_iter()\n            .filter(|ns| {\n                ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User\n            })\n            .collect();\n\n    // Add user namespace\n    namespaces.push(\n        LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::User)\n            .build()?,\n    );\n\n    let uid = syscall.get_euid().as_raw();\n    let gid = syscall.get_egid().as_raw();\n\n    let linux = LinuxBuilder::default()\n        .namespaces(namespaces)\n        .uid_mappings(vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(uid)\n                .container_id(0_u32)\n                .size(1_u32)\n                .build()?,\n        ])\n        .gid_mappings(vec![\n            LinuxIdMappingBuilder::default()\n                .host_id(gid)\n                .container_id(0_u32)\n                .size(1_u32)\n                .build()?,\n        ])\n        .build()?;\n\n    // Prepare the mounts\n\n    let mut mounts: Vec<Mount> = libcontainer::oci_spec::runtime::get_default_mounts();\n    for mount in &mut mounts {\n        if mount.destination().eq(Path::new(\"/sys\")) {\n            mount\n                .set_source(Some(PathBuf::from(\"/sys\")))\n                .set_typ(Some(String::from(\"none\")))\n                .set_options(Some(vec![\n                    \"rbind\".to_string(),\n                    \"nosuid\".to_string(),\n                    \"noexec\".to_string(),\n                    \"nodev\".to_string(),\n                    \"ro\".to_string(),\n                ]));\n        } else {\n            let options: Vec<String> = mount\n                .options()\n                .as_ref()\n                .unwrap_or(&vec![])\n                .iter()\n                .filter(|&o| !o.starts_with(\"gid=\") && !o.starts_with(\"uid=\"))\n                .map(|o| o.to_string())\n                .collect();\n            mount.set_options(Some(options));\n        }\n    }\n\n    let mut spec = get_default()?;\n    spec.set_linux(Some(linux)).set_mounts(Some(mounts));\n    Ok(spec)\n}\n\n/// spec Cli command\npub fn spec(args: liboci_cli::Spec, syscall: &dyn Syscall) -> Result<()> {\n    let spec = if args.rootless {\n        get_rootless(syscall)?\n    } else {\n        get_default()?\n    };\n\n    // write data to config.json\n    let file = File::create(\"config.json\")?;\n    let mut writer = BufWriter::new(file);\n    to_writer_pretty(&mut writer, &spec)?;\n    writer.flush()?;\n    Ok(())\n}\n\n#[cfg(test)]\n// Tests become unstable if not serial. The cause is not known.\nmod tests {\n    use libcontainer::syscall::syscall::create_syscall;\n    use serial_test::serial;\n\n    use super::*;\n\n    #[test]\n    #[serial]\n    fn test_spec_json() -> Result<()> {\n        let syscall = create_syscall();\n        let spec = get_rootless(&*syscall)?;\n        let tmpdir = tempfile::tempdir().expect(\"failed to create temp dir\");\n        let path = tmpdir.path().join(\"config.json\");\n        let file = File::create(path)?;\n        let mut writer = BufWriter::new(file);\n        to_writer_pretty(&mut writer, &spec)?;\n        writer.flush()?;\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/commands/start.rs",
    "content": "//! Starts execution of the container\n\nuse std::path::PathBuf;\n\nuse anyhow::{Context, Result};\nuse liboci_cli::Start;\n\nuse crate::commands::load_container;\n\npub fn start(args: Start, root_path: PathBuf) -> Result<()> {\n    let mut container = load_container(root_path, &args.container_id)?;\n    container\n        .start()\n        .with_context(|| format!(\"failed to start container {}\", args.container_id))\n}\n"
  },
  {
    "path": "crates/youki/src/commands/state.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::Result;\nuse liboci_cli::State;\n\nuse crate::commands::load_container;\n\npub fn state(args: State, root_path: PathBuf) -> Result<()> {\n    let container = load_container(root_path, &args.container_id)?;\n    println!(\"{}\", serde_json::to_string_pretty(&container.state)?);\n    std::process::exit(0);\n}\n"
  },
  {
    "path": "crates/youki/src/commands/update.rs",
    "content": "use std::path::PathBuf;\nuse std::{fs, io};\n\nuse anyhow::Result;\nuse libcgroups::common::{CgroupManager, ControllerOpt};\nuse libcgroups::{self};\nuse libcontainer::oci_spec::runtime::{LinuxPidsBuilder, LinuxResources, LinuxResourcesBuilder};\nuse liboci_cli::Update;\n\nuse crate::commands::create_cgroup_manager;\n\npub fn update(args: Update, root_path: PathBuf) -> Result<()> {\n    let cmanager = create_cgroup_manager(root_path, &args.container_id)?;\n\n    let linux_res: LinuxResources;\n    if let Some(resources_path) = args.resources {\n        linux_res = if resources_path.to_string_lossy() == \"-\" {\n            serde_json::from_reader(io::stdin())?\n        } else {\n            let file = fs::File::open(resources_path)?;\n            let reader = io::BufReader::new(file);\n            serde_json::from_reader(reader)?\n        };\n    } else {\n        let mut builder = LinuxResourcesBuilder::default();\n        if let Some(new_pids_limit) = args.pids_limit {\n            builder = builder.pids(LinuxPidsBuilder::default().limit(new_pids_limit).build()?);\n        }\n        linux_res = builder.build()?;\n    }\n\n    cmanager.apply(&ControllerOpt {\n        resources: &linux_res,\n        disable_oom_killer: false,\n        oom_score_adj: None,\n        freezer_state: None,\n    })?;\n    Ok(())\n}\n"
  },
  {
    "path": "crates/youki/src/main.rs",
    "content": "//! # Youki\n//! Container Runtime written in Rust, inspired by [railcar](https://github.com/oracle/railcar)\n//! This crate provides a container runtime which can be used by a high-level container runtime to run containers.\nmod commands;\nmod observability;\nmod rootpath;\nmod workload;\n\nuse anyhow::{Context, Result};\nuse clap::{CommandFactory, Parser};\nuse libcontainer::syscall::syscall::create_syscall;\nuse liboci_cli::{CommonCmd, GlobalOpts, StandardCmd};\n\nuse crate::commands::info;\n\n// Additional options that are not defined in OCI runtime-spec, but are used by Youki.\n#[derive(Parser, Debug)]\nstruct YoukiExtendOpts {\n    /// Enable logging to systemd-journald\n    #[clap(long)]\n    pub systemd_log: bool,\n    /// set the log level (default is 'error')\n    #[clap(long)]\n    pub log_level: Option<String>,\n}\n\n// High-level commandline option definition\n// This takes global options as well as individual commands as specified in [OCI runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)\n// Also check [runc commandline documentation](https://github.com/opencontainers/runc/blob/master/man/runc.8.md) for more explanation\n#[derive(Parser, Debug)]\n#[clap(author = env!(\"CARGO_PKG_AUTHORS\"))]\n#[command(version, disable_version_flag = true)]\nstruct Opts {\n    #[clap(flatten)]\n    global: GlobalOpts,\n\n    #[clap(flatten)]\n    youki_extend: YoukiExtendOpts,\n\n    #[clap(subcommand)]\n    subcmd: Option<SubCommand>,\n\n    /// Display youki version and commit hash\n    #[clap(short, long)]\n    version: bool,\n}\n\n// Subcommands accepted by Youki, confirming with [OCI runtime-spec](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)\n// Also for a short information, check [runc commandline documentation](https://github.com/opencontainers/runc/blob/master/man/runc.8.md)\n#[derive(Parser, Debug)]\nenum SubCommand {\n    // Standard and common commands handled by the liboci_cli crate\n    #[clap(flatten)]\n    Standard(Box<liboci_cli::StandardCmd>),\n    #[clap(flatten)]\n    Common(Box<liboci_cli::CommonCmd>),\n\n    // Youki specific extensions\n    Info(info::Info),\n    Completion(commands::completion::Completion),\n}\n\n/// This is the entry point in the container runtime. The binary is run by a high-level container runtime,\n/// with various flags passed. This parses the flags, creates and manages appropriate resources.\nfn main() -> Result<()> {\n    // A malicious container can gain access to the host machine by modifying youki's host\n    // binary and infect it with malicious code. This vulnerability was first discovered\n    // in runc and was assigned as CVE-2019-5736, but it also affects youki.\n    //\n    // The fix is to copy /proc/self/exe in an anonymous file descriptor (created via memfd_create),\n    // seal it and re-execute it. Because the final step is re-execution, this needs to be done at\n    // the beginning of this process.\n    //\n    // Ref: https://github.com/opencontainers/runc/commit/0a8e4117e7f715d5fbeef398405813ce8e88558b\n    // Ref: https://github.com/lxc/lxc/commit/6400238d08cdf1ca20d49bafb85f4e224348bf9d\n    pentacle::ensure_sealed().context(\"failed to seal /proc/self/exe\")?;\n\n    let opts = Opts::parse();\n    if opts.version {\n        info::print_youki();\n        return Ok(());\n    }\n\n    let mut app = Opts::command();\n    let syscall = create_syscall();\n\n    observability::init(&opts).map_err(|err| {\n        eprintln!(\"failed to initialize observability: {}\", err);\n        err\n    })?;\n\n    tracing::debug!(\n        \"started by user {} with {:?}\",\n        syscall.get_euid(),\n        std::env::args_os()\n    );\n\n    let root_path = rootpath::determine(opts.global.root, &*syscall)?;\n    let systemd_cgroup = opts.global.systemd_cgroup;\n\n    let cmd_result = match opts.subcmd {\n        Some(SubCommand::Standard(cmd)) => match *cmd {\n            StandardCmd::Create(create) => {\n                commands::create::create(create, root_path, systemd_cgroup)\n            }\n            StandardCmd::Start(start) => commands::start::start(start, root_path),\n            StandardCmd::Kill(kill) => commands::kill::kill(kill, root_path),\n            StandardCmd::Delete(delete) => commands::delete::delete(delete, root_path),\n            StandardCmd::State(state) => commands::state::state(state, root_path),\n        },\n        Some(SubCommand::Common(cmd)) => match *cmd {\n            CommonCmd::Checkpointt(checkpoint) => {\n                commands::checkpoint::checkpoint(checkpoint, root_path)\n            }\n            CommonCmd::Events(events) => commands::events::events(events, root_path),\n            CommonCmd::Exec(exec) => match commands::exec::exec(exec, root_path) {\n                Ok(exit_code) => std::process::exit(exit_code),\n                Err(e) => {\n                    tracing::error!(\"error in executing command: {:?}\", e);\n                    std::process::exit(-1);\n                }\n            },\n            CommonCmd::Features(features) => commands::features::features(features),\n            CommonCmd::List(list) => commands::list::list(list, root_path),\n            CommonCmd::Pause(pause) => commands::pause::pause(pause, root_path),\n            CommonCmd::Ps(ps) => commands::ps::ps(ps, root_path),\n            CommonCmd::Resume(resume) => commands::resume::resume(resume, root_path),\n            CommonCmd::Run(run) => match commands::run::run(run, root_path, systemd_cgroup) {\n                Ok(exit_code) => std::process::exit(exit_code),\n                Err(e) => {\n                    tracing::error!(\"error in executing command: {:?}\", e);\n                    std::process::exit(-1);\n                }\n            },\n            CommonCmd::Spec(spec) => commands::spec_json::spec(spec, &*syscall),\n            CommonCmd::Update(update) => commands::update::update(update, root_path),\n        },\n\n        Some(SubCommand::Info(info)) => commands::info::info(info),\n        Some(SubCommand::Completion(completion)) => {\n            commands::completion::completion(completion, &mut app)\n        }\n        None => app\n            .print_help()\n            .map_err(|e| anyhow::anyhow!(\"failed to print help: {e}\")),\n    };\n\n    if let Err(ref e) = cmd_result {\n        tracing::error!(\"error in executing command: {:?}\", e);\n    }\n    cmd_result\n}\n"
  },
  {
    "path": "crates/youki/src/observability.rs",
    "content": "use std::borrow::Cow;\nuse std::fs::OpenOptions;\nuse std::path::PathBuf;\nuse std::str::FromStr;\n\nuse anyhow::{Context, Result, bail};\nuse tracing::Level;\nuse tracing_subscriber::prelude::*;\n\nconst LOG_FORMAT_TEXT: &str = \"text\";\nconst LOG_FORMAT_JSON: &str = \"json\";\nenum LogFormat {\n    Text,\n    Json,\n}\n\n/// If in debug mode, default level is debug to get maximum logging\n#[cfg(debug_assertions)]\nconst DEFAULT_LOG_LEVEL: &str = \"debug\";\n\n/// If not in debug mode, default level is error to get important logs\n#[cfg(not(debug_assertions))]\nconst DEFAULT_LOG_LEVEL: &str = \"error\";\n\nfn detect_log_format(log_format: Option<&str>) -> Result<LogFormat> {\n    match log_format {\n        None | Some(LOG_FORMAT_TEXT) => Ok(LogFormat::Text),\n        Some(LOG_FORMAT_JSON) => Ok(LogFormat::Json),\n        Some(unknown) => bail!(\"unknown log format: {}\", unknown),\n    }\n}\n\nfn detect_log_level(input: Option<String>, is_debug: bool) -> Result<Level> {\n    // We keep the `debug` flag for backward compatibility, but use `log-level`\n    // as the main way to set the log level due to the flexibility. If both are\n    // specified, `log-level` takes precedence.\n    let log_level: Cow<str> = match input {\n        None if is_debug => \"debug\".into(),\n        None => DEFAULT_LOG_LEVEL.into(),\n        Some(level) => level.into(),\n    };\n\n    Ok(Level::from_str(log_level.as_ref())?)\n}\n\n#[derive(Debug, Default)]\npub struct ObservabilityConfig {\n    pub log_debug_flag: bool,\n    pub log_level: Option<String>,\n    pub log_file: Option<PathBuf>,\n    pub log_format: Option<String>,\n    #[allow(dead_code)]\n    pub systemd_log: bool,\n}\n\nimpl From<&crate::Opts> for ObservabilityConfig {\n    fn from(opts: &crate::Opts) -> Self {\n        Self {\n            log_debug_flag: opts.global.debug,\n            log_level: opts.youki_extend.log_level.to_owned(),\n            log_file: opts.global.log.to_owned(),\n            log_format: opts.global.log_format.to_owned(),\n            systemd_log: opts.youki_extend.systemd_log,\n        }\n    }\n}\n\npub fn init<T>(config: T) -> Result<()>\nwhere\n    T: Into<ObservabilityConfig>,\n{\n    let config = config.into();\n    let level = detect_log_level(config.log_level, config.log_debug_flag)\n        .with_context(|| \"failed to parse log level\")?;\n    let log_level_filter = tracing_subscriber::filter::LevelFilter::from(level);\n    let log_format = detect_log_format(config.log_format.as_deref())\n        .with_context(|| \"failed to detect log format\")?;\n\n    #[cfg(debug_assertions)]\n    let journald = true;\n    #[cfg(not(debug_assertions))]\n    let journald = config.systemd_log;\n\n    let systemd_journald = if journald {\n        match tracing_journald::layer() {\n            Ok(layer) => Some(layer.with_syslog_identifier(\"youki\".to_string())),\n            Err(err) => {\n                // Do not fail if we can't open syslog, just print a warning.\n                // This is the case in, e.g., docker-in-docker.\n                eprintln!(\"failed to initialize syslog logging: {:?}\", err);\n                None\n            }\n        }\n    } else {\n        None\n    };\n    let subscriber = tracing_subscriber::registry()\n        .with(log_level_filter)\n        .with(systemd_journald);\n\n    // I really dislike how we have to specify individual branch for each\n    // combination, but I can't find any better way to do this. The tracing\n    // crate makes it hard to build a single format layer with different\n    // conditions.\n    match (config.log_file.as_ref(), log_format) {\n        (None, LogFormat::Text) => {\n            // Text to stderr\n            subscriber\n                .with(\n                    tracing_subscriber::fmt::layer()\n                        // TODO: consider making colors configurable\n                        // https://github.com/youki-dev/youki/issues/3435\n                        .with_ansi(false)\n                        .without_time()\n                        .with_writer(std::io::stderr),\n                )\n                .try_init()\n                .map_err(|e| anyhow::anyhow!(\"failed to init logger: {}\", e))?;\n        }\n        (None, LogFormat::Json) => {\n            // JSON to stderr\n            subscriber\n                .with(\n                    tracing_subscriber::fmt::layer()\n                        .json()\n                        .flatten_event(true)\n                        .with_span_list(false)\n                        .with_writer(std::io::stderr),\n                )\n                .try_init()\n                .map_err(|e| anyhow::anyhow!(\"failed to init logger: {}\", e))?;\n        }\n        (Some(path), LogFormat::Text) => {\n            // Log file with text format\n            let file = OpenOptions::new()\n                .create(true)\n                .write(true)\n                .truncate(false)\n                .open(path)\n                .with_context(|| \"failed to open log file\")?;\n            subscriber\n                .with(tracing_subscriber::fmt::layer().with_writer(file))\n                .try_init()\n                .map_err(|e| anyhow::anyhow!(\"failed to init logger: {}\", e))?;\n        }\n        (Some(path), LogFormat::Json) => {\n            // Log file with JSON format\n            let file = OpenOptions::new()\n                .create(true)\n                .write(true)\n                .truncate(false)\n                .open(path)\n                .with_context(|| \"failed to open log file\")?;\n            subscriber\n                .with(\n                    tracing_subscriber::fmt::layer()\n                        .json()\n                        .flatten_event(true)\n                        .with_span_list(false)\n                        .with_writer(file),\n                )\n                .try_init()\n                .map_err(|e| anyhow::anyhow!(\"failed to init logger: {}\", e))?;\n        }\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::path::Path;\n\n    use libcontainer::test_utils::TestCallbackError;\n\n    use super::*;\n\n    #[test]\n    fn test_detect_log_level() {\n        let test = vec![\n            (\"error\", tracing::Level::ERROR),\n            (\"warn\", tracing::Level::WARN),\n            (\"info\", tracing::Level::INFO),\n            (\"debug\", tracing::Level::DEBUG),\n            (\"trace\", tracing::Level::TRACE),\n        ];\n        for (input, expected) in test {\n            assert_eq!(\n                detect_log_level(Some(input.to_string()), false)\n                    .expect(\"failed to parse log level\"),\n                expected\n            )\n        }\n        assert_eq!(\n            detect_log_level(None, true).expect(\"failed to parse log level\"),\n            tracing::Level::DEBUG\n        );\n        // Invalid log level should fail the parse\n        assert!(detect_log_level(Some(\"invalid\".to_string()), false).is_err());\n    }\n\n    #[test]\n    fn test_detect_log_level_default() {\n        if cfg!(debug_assertions) {\n            assert_eq!(\n                detect_log_level(None, false).unwrap(),\n                tracing::Level::DEBUG\n            )\n        } else {\n            assert_eq!(\n                detect_log_level(None, false).unwrap(),\n                tracing::Level::ERROR\n            )\n        }\n    }\n\n    #[test]\n    fn test_init_many_times() -> Result<()> {\n        let cb = || {\n            let temp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n            let log_file = Path::join(temp_dir.path(), \"test.log\");\n            let config = ObservabilityConfig {\n                log_file: Some(log_file),\n                ..Default::default()\n            };\n            init(config).map_err(|err| TestCallbackError::Other(err.into()))?;\n            Ok(())\n        };\n        libcontainer::test_utils::test_in_child_process(cb)\n            .with_context(|| \"failed the first init tracing\")?;\n        libcontainer::test_utils::test_in_child_process(cb)\n            .with_context(|| \"failed the second init tracing\")?;\n        Ok(())\n    }\n\n    #[test]\n    fn test_higher_loglevel_no_log() -> Result<()> {\n        libcontainer::test_utils::test_in_child_process(|| {\n            let temp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n            let log_file = Path::join(temp_dir.path(), \"test.log\");\n            // Note, we can only init the tracing once, so we have to test in a\n            // single unit test. The orders are important here.\n            let config = ObservabilityConfig {\n                log_file: Some(log_file.clone()),\n                log_level: Some(\"error\".to_string()),\n                ..Default::default()\n            };\n            init(config).map_err(|err| TestCallbackError::Other(err.into()))?;\n            assert!(\n                log_file\n                    .as_path()\n                    .metadata()\n                    .expect(\"failed to get logfile metadata\")\n                    .len()\n                    == 0,\n                \"a new logfile should be empty\"\n            );\n            // Test that info level is not logged into the logfile because we set the log level to error.\n            tracing::info!(\"testing this\");\n            if log_file\n                .as_path()\n                .metadata()\n                .map_err(|err| format!(\"failed to get logfile metadata: {err:?}\"))?\n                .len()\n                != 0\n            {\n                let data = std::fs::read_to_string(&log_file)\n                    .map_err(|err| format!(\"failed to read the logfile: {err:?}\"))?;\n                Err(TestCallbackError::Custom(format!(\n                    \"info level should not be logged into the logfile, but got: {data}\"\n                )))?;\n            }\n\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_json_logfile() -> Result<()> {\n        libcontainer::test_utils::test_in_child_process(|| {\n            let temp_dir = tempfile::tempdir().expect(\"failed to create temp dir\");\n            let log_file = Path::join(temp_dir.path(), \"test.log\");\n            // Note, we can only init the tracing once, so we have to test in a\n            // single unit test. The orders are important here.\n            let config = ObservabilityConfig {\n                log_file: Some(log_file.clone()),\n                log_format: Some(LOG_FORMAT_JSON.to_owned()),\n                ..Default::default()\n            };\n            init(config).map_err(|err| TestCallbackError::Other(err.into()))?;\n            assert!(\n                log_file\n                    .as_path()\n                    .metadata()\n                    .expect(\"failed to get logfile metadata\")\n                    .len()\n                    == 0,\n                \"a new logfile should be empty\"\n            );\n            // Test that the message logged is actually JSON format.\n            tracing::error!(\"testing json log\");\n            let data = std::fs::read_to_string(&log_file)\n                .map_err(|err| format!(\"failed to read the logfile: {err:?}\"))?;\n            if data.is_empty() {\n                Err(\"logfile should not be empty\")?;\n            }\n            serde_json::from_str::<serde_json::Value>(&data)\n                .map_err(|err| format!(\"failed to parse {data}: {err:?}\"))?;\n            Ok(())\n        })?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/rootpath.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Result, bail};\nuse libcontainer::utils::{create_dir_all_with_mode, rootless_required};\nuse nix::libc;\nuse nix::sys::stat::Mode;\n\npub fn determine(\n    root_path: Option<PathBuf>,\n    syscall: &dyn libcontainer::syscall::Syscall,\n) -> Result<PathBuf> {\n    let uid = syscall.get_uid().as_raw();\n\n    if let Some(path) = root_path {\n        if !path.exists() {\n            create_dir_all_with_mode(&path, uid, Mode::S_IRWXU)?;\n        }\n        let path = path.canonicalize()?;\n        return Ok(path);\n    }\n\n    if !rootless_required(syscall)? {\n        let path = get_default_not_rootless_path();\n        create_dir_all_with_mode(&path, uid, Mode::S_IRWXU)?;\n        return Ok(path);\n    }\n\n    // see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html\n    if let Ok(path) = std::env::var(\"XDG_RUNTIME_DIR\") {\n        let path = Path::new(&path).join(\"youki\");\n        if create_dir_all_with_mode(&path, uid, Mode::S_IRWXU).is_ok() {\n            return Ok(path);\n        }\n    }\n\n    // XDG_RUNTIME_DIR is not set, try the usual location\n    let path = get_default_rootless_path(uid);\n    if create_dir_all_with_mode(&path, uid, Mode::S_IRWXU).is_ok() {\n        return Ok(path);\n    }\n\n    if let Ok(path) = std::env::var(\"HOME\")\n        && let Ok(resolved) = fs::canonicalize(path)\n    {\n        let run_dir = resolved.join(\".youki/run\");\n        if create_dir_all_with_mode(&run_dir, uid, Mode::S_IRWXU).is_ok() {\n            return Ok(run_dir);\n        }\n    }\n\n    let tmp_dir = PathBuf::from(format!(\"/tmp/youki-{uid}\"));\n    if create_dir_all_with_mode(&tmp_dir, uid, Mode::S_IRWXU).is_ok() {\n        return Ok(tmp_dir);\n    }\n\n    bail!(\"could not find a storage location with suitable permissions for the current user\");\n}\n\n#[cfg(not(test))]\nfn get_default_not_rootless_path() -> PathBuf {\n    PathBuf::from(\"/run/youki\")\n}\n\n#[cfg(test)]\nfn get_default_not_rootless_path() -> PathBuf {\n    std::env::temp_dir().join(\"default_youki_path\")\n}\n\n#[cfg(not(test))]\nfn get_default_rootless_path(uid: libc::uid_t) -> PathBuf {\n    PathBuf::from(format!(\"/run/user/{uid}/youki\"))\n}\n\n#[cfg(test)]\nfn get_default_rootless_path(uid: libc::uid_t) -> PathBuf {\n    std::env::temp_dir().join(format!(\"default_rootless_youki_path_{uid}\").as_str())\n}\n\n#[cfg(test)]\nmod tests {\n    use std::fs;\n    use std::fs::Permissions;\n    use std::os::unix::fs::PermissionsExt;\n    use std::path::PathBuf;\n\n    use anyhow::{Context, Result};\n    use libcontainer::syscall::syscall::create_syscall;\n    use nix::sys::stat::Mode;\n\n    use super::*;\n\n    #[test]\n    fn test_user_specified() -> Result<()> {\n        // If the user specifies a path that does not exist, we should\n        // create it and return the absolute path.\n        let tmp = tempfile::tempdir()?;\n        // Note, the path doesn't exist yet because tempfile generated a random new empty dir.\n        let specified_path = tmp.path().join(\"provided_path\");\n        let non_abs_path = specified_path.join(\"../provided_path\");\n        let syscall = create_syscall();\n        let path =\n            determine(Some(non_abs_path), &*syscall).context(\"failed with specified path\")?;\n        assert_eq!(path, specified_path);\n        Ok(())\n    }\n\n    #[test]\n    fn test_user_specified_exists() -> Result<()> {\n        // If the user specifies a path that exists, we should return the\n        // absolute path.\n        let tmp = tempfile::tempdir()?;\n        let specified_path = tmp.path().join(\"provided_path\");\n        std::fs::create_dir(&specified_path).context(\"failed to create dir\")?;\n        let non_abs_path = specified_path.join(\"../provided_path\");\n        let syscall = create_syscall();\n        let path =\n            determine(Some(non_abs_path), &*syscall).context(\"failed with specified path\")?;\n        assert_eq!(path, specified_path);\n        Ok(())\n    }\n\n    #[test]\n    fn test_determine_root_path_non_rootless() -> Result<()> {\n        let syscall = create_syscall();\n        // If we do not have root privileges skip the test as it will not succeed.\n        if !syscall.get_uid().is_root() {\n            return Ok(());\n        }\n\n        {\n            let expected_path = super::get_default_not_rootless_path();\n            let path =\n                determine(None, &*syscall).context(\"failed with default non rootless path\")?;\n            assert_eq!(path, expected_path);\n            assert!(path.exists());\n            fs::remove_dir_all(&expected_path).context(\"failed to remove dir\")?;\n        }\n        {\n            let expected_path = get_default_not_rootless_path();\n            fs::create_dir(&expected_path).context(\"failed to create dir\")?;\n            fs::set_permissions(&expected_path, Permissions::from_mode(Mode::S_IRUSR.bits()))\n                .context(\"failed to set invalid permissions\")?;\n            assert!(determine(None, &*syscall).is_err());\n            fs::remove_dir_all(&expected_path).context(\"failed to remove dir\")?;\n        }\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_determine_root_path_rootless() -> Result<()> {\n        unsafe { std::env::set_var(\"YOUKI_USE_ROOTLESS\", \"true\") };\n\n        let syscall = create_syscall();\n\n        // XDG_RUNTIME_DIR\n        let tmp = tempfile::tempdir()?;\n        let xdg_dir = tmp.path().join(\"xdg_runtime\");\n        unsafe { std::env::set_var(\"XDG_RUNTIME_DIR\", &xdg_dir) };\n        let path = determine(None, &*syscall).context(\"failed with $XDG_RUNTIME_DIR path\")?;\n        assert_eq!(path, xdg_dir.join(\"youki\"));\n        assert!(path.exists());\n        unsafe { std::env::remove_var(\"XDG_RUNTIME_DIR\") };\n\n        // Default rootless location\n        let uid = syscall.get_uid().as_raw();\n        let default_rootless_path = get_default_rootless_path(uid);\n        scopeguard::defer!({\n            let _ = fs::remove_dir_all(&default_rootless_path);\n        });\n        let path = determine(None, &*syscall).context(\"failed with default rootless path\")?;\n        assert_eq!(path, default_rootless_path);\n        assert!(path.exists());\n\n        // The `determine` function will default to the rootless default\n        // path under `/run/user/$uid``. To test the `determine` function to\n        // not use the default rootless path, we need to make the default\n        // rootless path fail to create. So we set the path to an invalid\n        // permission, so the `determine` function will use HOME env var or\n        // `/tmp` directory instead.\n        fs::set_permissions(\n            &default_rootless_path,\n            Permissions::from_mode(Mode::S_IRUSR.bits()),\n        )\n        .context(\"failed to set invalid permissions\")?;\n\n        // Use HOME env var\n        let tmp = tempfile::tempdir()?;\n        let home_path = tmp.path().join(\"youki_home\");\n        fs::create_dir_all(&home_path).context(\"failed to create fake home path\")?;\n        unsafe { std::env::set_var(\"HOME\", &home_path) };\n        let path = determine(None, &*syscall).context(\"failed with $HOME path\")?;\n        assert_eq!(path, home_path.join(\".youki/run\"));\n        assert!(path.exists());\n        unsafe { std::env::remove_var(\"HOME\") };\n\n        // Use /tmp dir\n        let uid = syscall.get_uid().as_raw();\n        let expected_temp_path = PathBuf::from(format!(\"/tmp/youki-{uid}\"));\n        let path = determine(None, &*syscall).context(\"failed with temp path\")?;\n        assert_eq!(path, expected_temp_path);\n        // Set invalid permissions to temp path so determine_root_path fails.\n        fs::set_permissions(\n            &expected_temp_path,\n            Permissions::from_mode(Mode::S_IRUSR.bits()),\n        )\n        .context(\"failed to set invalid permissions\")?;\n        assert!(determine(None, &*syscall).is_err());\n        fs::remove_dir_all(&expected_temp_path).context(\"failed to remove dir\")?;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/workload/executor.rs",
    "content": "use libcontainer::oci_spec::runtime::Spec;\nuse libcontainer::workload::{Executor, ExecutorError, ExecutorValidationError};\n\n#[derive(Clone)]\npub struct DefaultExecutor {}\n\nimpl Executor for DefaultExecutor {\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {\n        #[cfg(feature = \"wasm-wasmer\")]\n        match super::wasmer::get_executor().exec(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n        #[cfg(feature = \"wasm-wasmedge\")]\n        match super::wasmedge::get_executor().exec(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n        #[cfg(feature = \"wasm-wasmtime\")]\n        match super::wasmtime::get_executor().exec(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n\n        // Leave the default executor as the last option, which executes normal\n        // container workloads.\n        libcontainer::workload::default::get_executor().exec(spec)\n    }\n\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {\n        #[cfg(feature = \"wasm-wasmer\")]\n        match super::wasmer::get_executor().validate(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorValidationError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n        #[cfg(feature = \"wasm-wasmedge\")]\n        match super::wasmedge::get_executor().validate(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorValidationError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n        #[cfg(feature = \"wasm-wasmtime\")]\n        match super::wasmtime::get_executor().validate(spec) {\n            Ok(_) => return Ok(()),\n            Err(ExecutorValidationError::CantHandle(_)) => (),\n            Err(err) => return Err(err),\n        }\n\n        libcontainer::workload::default::get_executor().validate(spec)\n    }\n}\n\npub fn default_executor() -> DefaultExecutor {\n    DefaultExecutor {}\n}\n"
  },
  {
    "path": "crates/youki/src/workload/mod.rs",
    "content": "pub mod executor;\n#[cfg(feature = \"wasm-wasmedge\")]\nmod wasmedge;\n#[cfg(feature = \"wasm-wasmer\")]\nmod wasmer;\n#[cfg(feature = \"wasm-wasmtime\")]\nmod wasmtime;\n"
  },
  {
    "path": "crates/youki/src/workload/wasmedge.rs",
    "content": "use std::collections::HashMap;\n\nuse libcontainer::oci_spec::runtime::Spec;\nuse libcontainer::workload::{Executor, ExecutorError, ExecutorValidationError};\nuse wasmedge_sdk::error::{CoreError, CoreExecutionError, WasmEdgeError};\nuse wasmedge_sdk::wasi::WasiModule;\nuse wasmedge_sdk::{Module, Store, Vm, params};\n\nconst EXECUTOR_NAME: &str = \"wasmedge\";\n\n#[derive(Clone)]\npub struct WasmedgeExecutor {}\n\nimpl Executor for WasmedgeExecutor {\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {\n        if !can_handle(spec) {\n            return Err(ExecutorError::CantHandle(EXECUTOR_NAME));\n        }\n\n        tracing::debug!(\"executing workload with wasmedge handler\");\n\n        // parse wasi parameters\n        let args = get_args(spec);\n        let mut cmd = args[0].clone();\n        if let Some(stripped) = args[0].strip_prefix(std::path::MAIN_SEPARATOR) {\n            cmd = stripped.to_string();\n        }\n        let envs = env_to_wasi(spec);\n\n        // initialize the wasi module with the parsed parameters\n        let mut wasi_module = WasiModule::create(\n            Some(args.iter().map(|s| s as &str).collect()),\n            Some(envs.iter().map(|s| s as &str).collect()),\n            None,\n        )\n        .map_err(|err| ExecutorError::Other(format!(\"failed to create wasi module: {:?}\", err)))?;\n\n        let mut instances = HashMap::new();\n        instances.insert(wasi_module.name().to_string(), wasi_module.as_mut());\n\n        // create a vm\n        let mut vm = Vm::new(\n            Store::new(None, instances)\n                .map_err(|err| ExecutorError::Other(format!(\"failed to create store: {}\", err)))?,\n        );\n\n        let module = Module::from_file(None, cmd).unwrap();\n        vm.register_module(Some(\"main\"), module).unwrap();\n\n        vm.run_func(Some(\"main\"), \"_start\", params!())\n            .map_err(|err| match *err {\n                // This case indicates that the wasm code panicked.\n                WasmEdgeError::Core(CoreError::Execution(CoreExecutionError::Unreachable)) => {\n                    std::process::exit(101)\n                }\n                _ => ExecutorError::Execution(err),\n            })?;\n\n        std::process::exit(0)\n    }\n\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {\n        if !can_handle(spec) {\n            return Err(ExecutorValidationError::CantHandle(EXECUTOR_NAME));\n        }\n\n        Ok(())\n    }\n}\n\npub fn get_executor() -> WasmedgeExecutor {\n    WasmedgeExecutor {}\n}\n\nfn can_handle(spec: &Spec) -> bool {\n    if let Some(annotations) = spec.annotations() {\n        if let Some(handler) = annotations.get(\"run.oci.handler\") {\n            return handler == \"wasm\";\n        }\n\n        if let Some(variant) = annotations.get(\"module.wasm.image/variant\") {\n            return variant == \"compat\";\n        }\n    }\n\n    false\n}\n\nfn get_args(spec: &Spec) -> &[String] {\n    let p = match spec.process() {\n        None => return &[],\n        Some(p) => p,\n    };\n\n    match p.args() {\n        None => &[],\n        Some(args) => args.as_slice(),\n    }\n}\n\nfn env_to_wasi(spec: &Spec) -> Vec<String> {\n    let default = vec![];\n    // below we can be sure that process exists, as otherwise container init process\n    // function would have returned error at the very start\n    let env = spec\n        .process()\n        .as_ref()\n        .unwrap()\n        .env()\n        .as_ref()\n        .unwrap_or(&default);\n    env.to_vec()\n}\n"
  },
  {
    "path": "crates/youki/src/workload/wasmer.rs",
    "content": "use std::error::Error;\n\nuse libcontainer::oci_spec::runtime::Spec;\nuse libcontainer::workload::{EMPTY, Executor, ExecutorError, ExecutorValidationError};\nuse wasmer::{Instance, Module, Store};\nuse wasmer_wasix::{WasiEnv, WasiError};\n\nconst EXECUTOR_NAME: &str = \"wasmer\";\n\n#[derive(Clone)]\npub struct WasmerExecutor {}\n\nimpl Executor for WasmerExecutor {\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {\n        if !can_handle(spec) {\n            return Err(ExecutorError::CantHandle(EXECUTOR_NAME));\n        }\n\n        tracing::debug!(\"executing workload with wasmer handler\");\n        let process = spec.process().as_ref();\n\n        let args = process.and_then(|p| p.args().as_ref()).unwrap_or(&EMPTY);\n        let env = process\n            .and_then(|p| p.env().as_ref())\n            .unwrap_or(&EMPTY)\n            .iter()\n            .filter_map(|e| {\n                e.split_once('=')\n                    .filter(|kv| !kv.0.contains('\\u{0}') && !kv.1.contains('\\u{0}'))\n                    .map(|kv| (kv.0.trim(), kv.1.trim()))\n            });\n\n        if args.is_empty() {\n            tracing::error!(\"at least one process arg must be specified\");\n            return Err(ExecutorError::InvalidArg);\n        }\n\n        if !args[0].ends_with(\".wasm\") && !args[0].ends_with(\".wat\") {\n            tracing::error!(\n                \"first argument must be a wasm or wat module, but was {}\",\n                args[0]\n            );\n            return Err(ExecutorError::InvalidArg);\n        }\n\n        let mut store = Store::default();\n        let module = Module::from_file(&store, &args[0]).map_err(|err| {\n            tracing::error!(err = ?err, file = ?args[0], \"could not load wasm module from file\");\n            ExecutorError::Other(\"could not load wasm module from file\".to_string())\n        })?;\n\n        let mut wasi_env = WasiEnv::builder(\"youki_wasm_app\")\n            .args(args.iter().skip(1))\n            .envs(env)\n            .finalize(&mut store)\n            .map_err(|err| ExecutorError::Other(format!(\"could not create wasi env: {}\", err)))?;\n\n        let imports = wasi_env.import_object(&mut store, &module).map_err(|err| {\n            ExecutorError::Other(format!(\"could not retrieve wasm imports: {}\", err))\n        })?;\n        let instance = Instance::new(&mut store, &module, &imports).map_err(|err| {\n            ExecutorError::Other(format!(\"could not instantiate wasm module: {}\", err))\n        })?;\n\n        wasi_env\n            .initialize(&mut store, instance.clone())\n            .map_err(|err| {\n                ExecutorError::Other(format!(\"could not initialize wasi env: {}\", err))\n            })?;\n\n        let start = instance.exports.get_function(\"_start\").map_err(|err| {\n            ExecutorError::Other(format!(\n                \"could not retrieve wasm module main function: {err}\"\n            ))\n        })?;\n\n        start.call(&mut store, &[]).map_err(|err| {\n            if let Some(WasiError::Exit(exit_code)) = err\n                .source()\n                .and_then(|err_source| err_source.downcast_ref::<WasiError>())\n            {\n                std::process::exit(exit_code.raw())\n            } else {\n                ExecutorError::Execution(err.into())\n            }\n        })?;\n\n        wasi_env.on_exit(&mut store, None);\n\n        std::process::exit(0)\n    }\n\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {\n        if !can_handle(spec) {\n            return Err(ExecutorValidationError::CantHandle(EXECUTOR_NAME));\n        }\n\n        Ok(())\n    }\n}\n\npub fn get_executor() -> WasmerExecutor {\n    WasmerExecutor {}\n}\n\nfn can_handle(spec: &Spec) -> bool {\n    if let Some(annotations) = spec.annotations() {\n        if let Some(handler) = annotations.get(\"run.oci.handler\") {\n            return handler == \"wasm\";\n        }\n\n        if let Some(variant) = annotations.get(\"module.wasm.image/variant\") {\n            return variant == \"compat\";\n        }\n    }\n\n    false\n}\n\n#[cfg(test)]\nmod tests {\n    use std::collections::HashMap;\n\n    use anyhow::{Context, Result};\n    use libcontainer::oci_spec::runtime::SpecBuilder;\n\n    use super::*;\n\n    #[test]\n    fn test_can_handle_oci_handler() -> Result<()> {\n        let mut annotations = HashMap::with_capacity(1);\n        annotations.insert(\"run.oci.handler\".to_owned(), \"wasm\".to_owned());\n        let spec = SpecBuilder::default()\n            .annotations(annotations)\n            .build()\n            .context(\"build spec\")?;\n\n        assert!(can_handle(&spec));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_can_handle_compat_wasm_spec() -> Result<()> {\n        let mut annotations = HashMap::with_capacity(1);\n        annotations.insert(\"module.wasm.image/variant\".to_owned(), \"compat\".to_owned());\n        let spec = SpecBuilder::default()\n            .annotations(annotations)\n            .build()\n            .context(\"build spec\")?;\n\n        assert!(can_handle(&spec));\n\n        Ok(())\n    }\n\n    #[test]\n    fn test_can_handle_no_execute() -> Result<()> {\n        let spec = SpecBuilder::default().build().context(\"build spec\")?;\n\n        assert!(!can_handle(&spec));\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "crates/youki/src/workload/wasmtime.rs",
    "content": "use libcontainer::oci_spec::runtime::Spec;\nuse libcontainer::workload::{EMPTY, Executor, ExecutorError, ExecutorValidationError};\nuse wasi_common::I32Exit;\nuse wasi_common::sync::{WasiCtxBuilder, add_to_linker};\nuse wasmtime::{Engine, Linker, Module, Store};\n\nconst EXECUTOR_NAME: &str = \"wasmtime\";\n\n#[derive(Clone)]\npub struct WasmtimeExecutor {}\n\nimpl Executor for WasmtimeExecutor {\n    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {\n        if !can_handle(spec) {\n            return Err(ExecutorError::CantHandle(EXECUTOR_NAME));\n        }\n\n        tracing::debug!(\"executing workload with wasmtime handler\");\n        let process = spec.process().as_ref();\n\n        let args = spec\n            .process()\n            .as_ref()\n            .and_then(|p| p.args().as_ref())\n            .unwrap_or(&EMPTY);\n        if args.is_empty() {\n            tracing::error!(\"at least one process arg must be specified\");\n            return Err(ExecutorError::InvalidArg);\n        }\n\n        if !args[0].ends_with(\".wasm\") && !args[0].ends_with(\".wat\") {\n            tracing::error!(\n                \"first argument must be a wasm or wat module, but was {}\",\n                args[0]\n            );\n            return Err(ExecutorError::InvalidArg);\n        }\n\n        let mut cmd = args[0].clone();\n        let stripped = args[0].strip_prefix(std::path::MAIN_SEPARATOR);\n        if let Some(cmd_stripped) = stripped {\n            cmd = cmd_stripped.to_string();\n        }\n\n        let envs: Vec<(String, String)> = process\n            .and_then(|p| p.env().as_ref())\n            .unwrap_or(&EMPTY)\n            .iter()\n            .filter_map(|e| {\n                e.split_once('=')\n                    .map(|kv| (kv.0.trim().to_string(), kv.1.trim().to_string()))\n            })\n            .collect();\n\n        let engine = Engine::default();\n        let module = Module::from_file(&engine, &cmd).map_err(|err| {\n            tracing::error!(err = ?err, file = ?cmd, \"could not load wasm module from file\");\n            ExecutorError::Other(\"could not load wasm module from file\".to_string())\n        })?;\n\n        let mut linker = Linker::new(&engine);\n        add_to_linker(&mut linker, |s| s).map_err(|err| {\n            tracing::error!(err = ?err, \"cannot add wasi context to linker\");\n            ExecutorError::Other(\"cannot add wasi context to linker\".to_string())\n        })?;\n\n        let wasi = WasiCtxBuilder::new()\n            .inherit_stdio()\n            .args(args)\n            .map_err(|err| {\n                ExecutorError::Other(format!(\"cannot add args to wasi context: {}\", err))\n            })?\n            .envs(&envs)\n            .map_err(|err| {\n                ExecutorError::Other(format!(\"cannot add envs to wasi context: {}\", err))\n            })?\n            .build();\n\n        let mut store = Store::new(&engine, wasi);\n\n        let instance = linker.instantiate(&mut store, &module).map_err(|err| {\n            tracing::error!(err = ?err, \"wasm module could not be instantiated\");\n            ExecutorError::Other(\"wasm module could not be instantiated\".to_string())\n        })?;\n        let start = instance.get_func(&mut store, \"_start\").ok_or_else(|| {\n            ExecutorError::Other(\"could not retrieve wasm module main function\".into())\n        })?;\n\n        start.call(&mut store, &[], &mut []).map_err(|err| {\n            match err.downcast_ref::<I32Exit>() {\n                Some(exit) => std::process::exit(exit.0),\n                None => ExecutorError::Execution(err.into()),\n            }\n        })?;\n\n        std::process::exit(0)\n    }\n\n    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {\n        if !can_handle(spec) {\n            return Err(ExecutorValidationError::CantHandle(EXECUTOR_NAME));\n        }\n\n        Ok(())\n    }\n}\n\npub fn get_executor() -> WasmtimeExecutor {\n    WasmtimeExecutor {}\n}\n\nfn can_handle(spec: &Spec) -> bool {\n    if let Some(annotations) = spec.annotations() {\n        if let Some(handler) = annotations.get(\"run.oci.handler\") {\n            return handler == \"wasm\";\n        }\n\n        if let Some(variant) = annotations.get(\"module.wasm.image/variant\") {\n            return variant == \"compat\";\n        }\n    }\n\n    false\n}\n"
  },
  {
    "path": "cross/Dockerfile.gnu",
    "content": "ARG CROSS_BASE_IMAGE\nARG CROSS_DEB_ARCH\nFROM $CROSS_BASE_IMAGE\n\nARG CROSS_DEB_ARCH\nRUN dpkg --add-architecture ${CROSS_DEB_ARCH} && \\\n    apt-get -y update && \\\n    apt-get install -y pkg-config \\\n    # dependencies required to build libsecccomp-rs\n    libseccomp-dev:${CROSS_DEB_ARCH} \\\n    # dependencies required to build libbpf-sys\n    libelf-dev:${CROSS_DEB_ARCH} \\\n    zlib1g-dev:${CROSS_DEB_ARCH} \\\n    # dependencies to build wasmedge-sys\n    libzstd-dev:${CROSS_DEB_ARCH}\n\nCOPY hack/busctl.sh /bin/busctl\nRUN chmod +x /bin/busctl\n"
  },
  {
    "path": "cross/Dockerfile.musl",
    "content": "ARG CROSS_BASE_IMAGE\nFROM $CROSS_BASE_IMAGE\n\nCOPY --from=jorgeprendes420/apk-anywhere / /\nENV MARCH=${CROSS_CMAKE_SYSTEM_PROCESSOR}\nRUN apk-init ${MARCH} ${CROSS_SYSROOT}\n\nRUN apk-${MARCH} add \\\n    # dependencies required to build libsecccomp-rs\n    libseccomp-static libseccomp-dev \\\n    # dependencies required to build libbpf-sys\n    libelf-static elfutils-dev \\\n    zlib-dev zlib-static \\\n    # dependencies to build wasmedge-sys\n    g++ zstd-static\n\n# configure libsecccomp-rs to use static linking\nENV LIBSECCOMP_LINK_TYPE=\"static\"\nENV LIBSECCOMP_LIB_PATH=\"${CROSS_SYSROOT}/lib\"    \n\n# configure wasmedge-sys to link stdc++ statically\nENV WASMEDGE_DEP_STDCXX_LINK_TYPE=\"static\"\nENV WASMEDGE_DEP_STDCXX_LIB_PATH=\"${CROSS_SYSROOT}/lib\"\n\nCOPY hack/busctl.sh /bin/busctl\nRUN chmod +x /bin/busctl\n\n# wasmedge-sys (through llvm) needs some symbols defined in libgcc\nRUN mkdir /.cargo && cat <<'EOF' > /.cargo/config.toml\n[target.'cfg(target_env = \"musl\")']\nrustflags = [\"-Clink-arg=-lgcc\"]\nEOF\n\nRUN apt-get -y update && \\\n    apt-get install -y pkg-config\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "book\n"
  },
  {
    "path": "docs/book.toml",
    "content": "[book]\nauthors = [\"Youki Contributors\"]\nlanguage = \"en\"\nsrc = \"src\"\ntitle = \"Youki User and Developer Documentation\"\n"
  },
  {
    "path": "docs/doc-draft.md",
    "content": "\\_This is a draft for a high level documentation of Youki. After it is finished this is intended to explain how control flow and high level functioning of Youki happens for development purposes.\n\n## Some reference links\n\nThese are references to various documentations and specifications, which can be useful to understand commands and constraints.\n\n- [OCI runtime specification](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md) : The specification for a container runtime. Any OCI complaisant runtime must follow this.\n- [runc man pages](https://github.com/opencontainers/runc/tree/master/man) : Has description on commands and their options in runc.\n- [cgroups man page](https://man7.org/linux/man-pages/man7/cgroups.7.html) : Contains information about cgroups, their creation, deletion etc.\n- [pseudoterminal man page](https://man7.org/linux/man-pages/man7/pty.7.html) : Information about the pseudoterminal system, useful to understand console_socket parameter in create subcommand\n- [Unix Sockets man page](https://man7.org/linux/man-pages/man7/unix.7.html) : Useful to understand sockets\n- [prctl man page](https://man7.org/linux/man-pages/man2/prctl.2.html) : Process control man pages\n- [OCI Linux spec](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md) : Linux specific section of OCI Spec\n- [pipe2 man page](https://man7.org/linux/man-pages/man2/pipe.2.html) : Definition and usage of pipe2\n- [systemd resource control](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html): Options offered by systemd for resource control\n\n---\n\n## Control flow diagram\n\nThis is diagram as given in #14, which is not actually how this works, but helpful to understand overall flow. Someone needs to check and correct.\n\n```mermaid\nsequenceDiagram\nparticipant U as User\nparticipant D as Docker\nparticipant Y_Main as Youki(Main Process)\nparticipant Y_Intermediate as Youki(Intermediate Process)\nparticipant Y_init as Youki(Init Process)\n\n\nU ->> D : $ docker run --rm -it --runtime youki $image\nD ->> Y_Main : youki create $container_id\nY_Main ->> Y_Intermediate : fork(2) to create new intermediate process, entering into user and pid namespaces.\nY_Intermediate ->> Y_Main : set user id mapping if entering into usernamespaces\nY_Intermediate ->> Y_Init: fork(2) to create the container init process.\nY_Init ->> Y_Init : configure resource limits, mount the devices, entering into rest of namespaces, and etc.\nY_Init ->> Y_Intermediate : ready message (Unix domain socket)\nY_Intermediate ->> Y_Main : ready message (Unix domain socket)\nY_Main ->> Y_Main: set cgroup configuration for Y_Init\nY_Main ->> D : exit $code\nD ->> Y_Main : $ youki start $container_id\nY_Main -->> Y_Init : start message through notify listener (Unix domain socket)\nY_Init ->> Y_Init : run the commands in dockerfile, using `execv`\nD ->> D : monitor pid written in pid file\nD ->> U : exit $code\n```\n\n---\n\n## Control flow\n\n### main invocation\n\nOn invoking Youki, main function parses args passed to it, which contains directory path to store container state (check runc . 8 . md in [runc man pages]), optional log path and log format string and a subcommand such as create, delete etc.\n\nFrom there it matches subcommand arg with possible subcommand and takes appropriate actions, such as creating a new container, deleting a container, etc.\n\n### create container\n\nOne thing to note is that in the end, a container is just another process in Linux, which has control groups, namespaces, pivot_root and other mechanisms applied to it. The program executing has the impression that is is running on a complete system, but from the host system's perspective, it is just another process, and has attributes such as pid, file descriptors, etc. associated with it like any other process.\n\nWhen given the create command, Youki will load the specification, configuration, sockets etc., use clone syscall to create the container process (init process), applies the limits, namespaces, and etc. to the cloned container process. The container process will wait on a unix domain socket before executing the command/program.\n\nThe main youki process will setup pipes to communicate and synchronize with the intermediate and init process. The init process will notify the intermediate process, and then intermediate process to the main youki process that it is ready and start to wait on a unix domain socket. The youki process will then write the container state and exit.\n\n- [mio Token definition](https://docs.rs/mio/0.7.11/mio/struct.Token.html)\n- [oom-score-adj](https://dev.to/rrampage/surviving-the-linux-oom-killer-2ki9)\n- [unshare man page](https://man7.org/linux/man-pages/man1/unshare.1.html)\n- [user-namespace man page](https://man7.org/linux/man-pages/man7/user_namespaces.7.html)\n- [wait man page](https://man7.org/linux/man-pages/man3/wait.3p.html)\n\n### Process\n\nThis handles creation of the container process. The main youki process creates the intermediate process and the intermediate process creates the container process (init process). The hierarchy is: `main youki process -> intermediate process -> init process`\n\nThe main youki process will set up pipes used as message passing and synchronization mechanism with the init process. The reason youki needs to create/fork two process instead of one is due to the user and pid namespaces. In rootless container, we need to first enter user namespace, since all other namespaces requires CAP_SYSADMIN. When unshare or set_ns into pid namespace, only the children of the current process will enter into a different pid namespace. As a result, we must first fork a process to enter into user namespace, call unshare or set_ns for pid namespace, then fork again to enter into the correct pid namespace.\n\nNote: clone(2) offers us the ability to enter into user and pid namespace by creatng only one process. However, clone(2) can only create new pid namespace, but cannot enter into existing pid namespaces. Therefore, to enter into existing pid namespaces, we would need to fork twice. Currently, there is no getting around this limitation.\n\n- [fork(2) man page](https://man7.org/linux/man-pages/man2/fork.2.html)\n- [clone(2) man page](https://man7.org/linux/man-pages/man2/clone.2.html)\n- [pid namespace man page](https://man7.org/linux/man-pages/man7/pid_namespaces.7.html)\n\n### Container\n\nThis structure contains functions related to container process and its state and status.\n\n### Command\n\nThis contains a trait to wrap commonly required syscalls, so that they can be abstracted from implementation details for rest of Youki.\nThis also provides implementation for Linux syscalls for the trait.\n\n- [pivot_root man page](https://man7.org/linux/man-pages/man2/pivot_root.2.html)\n- [umount2 man page](https://man7.org/linux/man-pages/man2/umount2.2.html)\n- [capabilities man page](https://man7.org/linux/man-pages/man7/capabilities.7.html)\n- [unshare man page](https://man7.org/linux/man-pages/man2/unshare.2.html)\n\n## Capabilities\n\nThis has functions related to set and reset specific capabilities, as well as to drop extra privileges\n\n- [Simple explanation of capabilities](https://blog.container-solutions.com/linux-capabilities-in-practice)\n- [man page for capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html)\n\n## Info\n\nThis is primarily for printing info about system running youki, such as OS release, architecture, cpu info, cgroups info etc. , as this info can be helpful when reporting issues.\n\n- [about /etc/os-release](https://www.freedesktop.org/software/systemd/man/os-release.html)\n\n## Namespaces\n\nThis has functions related to setting of namespaces to the calling process\n\n- [CLONE_NEWUSER flag](https://man7.org/linux/man-pages/man2/clone.2.html)\n\n## Pause and Resume\n\nThis contains functionality regarding pausing and resuming container. Pausing a container indicates suspending all processes in it. This can be done with signals SIGSTOP and SIGCONT, but these can be intercepted. Using cgroups to suspend and resume processes without letting tasks know.\n\n- [cgroups man page](https://man7.org/linux/man-pages/man7/cgroups.7.html)\n- [freezer cgroup kernel documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)\n\n## Other references\n\n- [oci runtime specification](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md)\n- [runc man pages](https://github.com/opencontainers/runc/blob/master/man/runc.8.md)\n"
  },
  {
    "path": "docs/src/SUMMARY.md",
    "content": "# Summary\n\n[Youki](./youki.md)\n\n---\n\n- [User Documentation](./user/introduction.md)\n  - [Basic Setup](./user/basic_setup.md)\n  - [Basic Usage](./user/basic_usage.md)\n  - [Crates provided](./user/crates.md)\n    - [libcgroups](./user/libcgroups.md)\n    - [libcontainer](./user/libcontainer.md)\n    - [liboci-cli](./user/liboci_cli.md)\n    - [libseccomp](./user/libseccomp.md)\n  - [Webassembly](./user/webassembly.md)\n\n---\n\n- [Community](./community/introduction.md)\n    - [Adopters and Use Cases](./community/adopters_and_use_cases.md)\n    - [Maintainer](./community/maintainer.md)\n    - [Governance](./community/governance.md)\n    - [Contributing](./community/contributing.md)\n    - [Chat](./community/chat.md)\n\n---\n\n- [Developer Documentation](./developer/introduction.md)\n  - [Basics](./developer/basics.md)\n  - [Unwritten Rules](./developer/unwritten_rules.md)\n  - [Good places to start](./developer/good_places_to_start.md)\n  - [This Documentation](./developer/documentation_mdbook.md)\n  - [Repository Structure](./developer/repo_structure.md)\n  - [Debugging](./developer/debugging.md)\n  - [Crate Specific Information](./developer/crate_specific_information.md)\n    - [libcgroups](./developer/libcgroups.md)\n    - [libcontainer](./developer/libcontainer.md)\n    - [liboci-cli](./developer/liboci_cli.md)\n    - [libseccomp](./developer/libseccomp.md)\n    - [youki](./developer/youki.md)\n  - [e2e tests](./developer/e2e/e2e_tests.md)\n      - [rust oci tests](./developer/e2e/rust_oci_test.md)\n        - [integration_test](./developer/e2e/integration_test.md)\n        - [test_framework](./developer/e2e/test_framework.md)\n        - [runtimetest](./developer/e2e/runtimetest.md)\n      - [containerd integration test](./developer/e2e/containerd_integration_test_using_youki.md)\n      - [runtime tools](./developer/e2e/runtime_tools.md)\n      - [runc compatibility test](./developer/e2e/runc_compatibility_test.md)\n      - [Kubernetes test](./developer/e2e/kubernetes_test.md)\n"
  },
  {
    "path": "docs/src/community/adopters_and_use_cases.md",
    "content": "# Adopters and Use Cases\n\nThis page collects public examples of projects that use youki or its crates.\n\nIf you would like to add your project, please open a pull request with:\n\n- project name and link\n- a one-line summary\n- crates being used, if you want to share them\n\n## [rk8s](https://github.com/rk8s-dev/rk8s)\n\nA lightweight Kubernetes-compatible container orchestration system written in Rust, implementing the Container Runtime Interface (CRI) with support for single containers, Kubernetes-style pods, and Docker Compose-style multi-container applications.\n\nUses: `libcontainer`, `libcgroups`\n\n## [runwasi](https://github.com/containerd/runwasi)\n\nA containerd shim that runs WebAssembly workloads in Docker and Kubernetes while using an OCI-compatible sandbox.\n\nUses: `libcontainer`\n\n## [SpinKube](https://www.spinkube.dev/)\n\nA platform for running Spin applications on Kubernetes. SpinKube uses `runwasi`, which in turn uses `libcontainer` to provide an OCI-compatible sandbox for workloads.\n"
  },
  {
    "path": "docs/src/community/chat.md",
    "content": "# Chat\nPlease join our chat find help or discuss issues:\n- [Discord invite](https://discord.gg/zHnyXKSQFD)\n"
  },
  {
    "path": "docs/src/community/contributing.md",
    "content": "# Contributing\n\n## Developer Certificate of Origin\n\nEvery commit must be signed off with the `Signed-off-by: REAL NAME <email@example.com>` line.\nUse the `git commit -s` command to add the Signed-off-by line.\n\n## Licensing\n\nYouki is licensed under the terms of [Apache License 2.0](https://github.com/youki-dev/youki/blob/main/LICENSE).\n\n## Sending pull requests\n\nPull requests can be submitted using [the GitHub standard process](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).\n\n## Merging pull requests\n\nCommitters can merge pull requests.\nA Committer shouldn’t merge their own pull requests without approval by at least one other Committer.\n"
  },
  {
    "path": "docs/src/community/governance.md",
    "content": "# Code of Conduct\n\nYouki follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).\n\n# Maintainership\n\nYouki is governed by Maintainers who are elected from active contributors.\nYouki will remain [vendor-neutral](https://contribute.cncf.io/maintainers/community/vendor-neutrality/).\nMaintainers are [here](./maintainer.md).\n\n## Roles\n\nMaintainers consist of the following roles:\n\n- Committer (Full maintainership)  \n    Committers have full write accesses to repos.\n    Committers’ commits should still be made via GitHub pull requests (except for urgent security fixes), and should not be pushed directly to the default branch.\n\n- Reviewer (Limited maintainership)  \n    Reviewers may moderate GitHub issues and pull requests (such as adding labels and cleaning up spams), but they do not have any access to merge pull requests nor push commits.\n    A Reviewer is considered as a candidate to become a Committer.\n\n## Addition and promotion of Maintainers\n\nA contributor who has successfully merged 10 or more pull requests to the project has the qualification to become a Reviewer.\nAt least 5 of these pull requests must be code-related.\nContributors who meet this qualification can self-nominate by creating a GitHub issue.\nA proposal to add or promote a Maintainer must be approved by 2/3 of the Committers who vote within 7 days.\nVoting needs a minimum of 2 approvals. The proposer can vote too. Votes from the same company will be counted as one vote.\nA proposal should be made via a GitHub pull request to the file containing the list of Maintainer defined above.\nBefore submitting the pull request, the proposer should reach out to the Committers to check their willingness to support the proposal.\n\n## Removal and demotion of Maintainers\n\nA Maintainer who do not show significant activities for 12 months, or, who violated the Code of Conduct, may be demoted or removed from the project.\n\nA proposal to demote or remove a Maintainer must be approved by 2/3 of the Committers (excluding the person in question) who vote within 14 days.\nVoting needs a minimum of 2 approvals. The proposer can vote too. Votes from the same company will be counted as one vote.\n\nA proposal should be made via a GitHub pull request to the file containing the list of Maintainer defined above.\nIn the special case of removing a harmful Maintainer, this process can take place via a private discussion.\nBefore submitting the pull request, the proposer should reach out to the Committers to check their willingness.\n"
  },
  {
    "path": "docs/src/community/introduction.md",
    "content": "# Community\n\n- [Adopters and Use Cases](./adopters_and_use_cases.md)\n- [Maintainer](./maintainer.md)\n- [Governance](./governance.md)\n- [Contributing](./contributing.md)\n- [Chat](./chat.md)\n"
  },
  {
    "path": "docs/src/community/maintainer.md",
    "content": "# Current Maintainers\n\n| Name            | Role      | GitHub ID                                          | Affiliation        |\n|-----------------|-----------|----------------------------------------------------|--------------------|\n| Toru Komatsu    | Committer | [@utam0k](https://github.com/utam0k)               | Preferred Networks |\n| Thomas Schubart | Committer | [@Furisto](https://github.com/Furisto)             | Gitpod             |\n| Yashodhan       | Committer | [@YJDoc2](https://github.com/YJDoc2)               | Independent        |\n| Eric Fang       | Committer | [@yihuaf](https://github.com/yihuaf)               | Independent        |\n| Sascha Grunert  | Committer | [@saschagrunert](https://github.com/saschagrunert) | Red Hat            |\n| Jorge Prendes   | Committer | [@jprendes](https://github.com/jprendes)           | Microsoft          |\n| Yusuke Sakurai  | Committer | [@saku3](https://github.com/saku3)                 | 3-shake            |\n| Lindroos Hsu    | Reviewer  | [@tommady](https://github.com/tommady)             | Independent        |\n| Yuta Nagai      | Reviewer  | [@nayuta723](https://github.com/nayuta723)         | CyberAgent         |\n"
  },
  {
    "path": "docs/src/developer/basics.md",
    "content": "# Basics\n\nThis section has the general information and resources needed to work with any part of youki. As youki is written in Rust, you should know some basic Rust before. If you don't yet, some good resources for that can be found on the Rust's [official site](https://www.rust-lang.org/learn).\n\n## Youki\n\nYouki is a low level container runtime, which deals with the creation and management of Linux containers. Some of other such low-level runtimes are [runc](https://github.com/opencontainers/runc) and [crun](https://github.com/containers/crun). These are usually used by a higher-level runtime such as Docker or Podman to actually create and manage containers, where the higher level runtime provides a much easier interface for users.\n\nBefore you start working on developing youki, you should go through [the User documentation](../user/introduction.md) as it specifies the requirements and setup for running youki. For developing youki, you will need to install the dependencies and clone the repo, as specified in the [Basic Setup](../user/basic_setup.md) and [Basic Usage](../user/basic_usage.md) sections.\n\n## Testing while developing\n\nWhile developing youki, you might need to compile and test the code from time to time, to make sure it is working and and something is not accidentally broken. Currently there are two ways to verify that:\n\n- Unit tests, which test individual components of youki\n- Integration tests, which test the complete functionality of youki commands from start to end.\n\nAs the steps to run these tests can be a bit tedious, a makefile in project the root provides an easy way to run these quickly. The makefile currently states three individual test :\n\n- test: The unit tests\n- oci-integration-test: The integration tests provided by OCI, these are the current standard to make sure youki is OCI compliant.\n- integration-test: This is the Rust port of the OCI runtime tests, as there are some issues in the OCI tests. See [integration_test](./e2e/integration_test.md) page.\n\nAll three can be run by using `make test-all`, or you can run the individual command to run specific tests.\n\n## Resources\n\n#### OCI\n\nOpen containers initiative is project, which provides a standardization and standardized specification for operating-system-level virtualization. That way components that confirm to the specification provided by OCI spec, can interoperate with each other easily, and developing of new applications becomes easier. For example youki can be used in place of runc in Docker, as all three : Docker, runc and youki are OCI compliant, and have a standard interface.\n\nTheir main GitHub page is at [https://github.com/opencontainers](https://github.com/opencontainers), and more information about the runtime specifications can be found at [https://github.com/opencontainers/runtime-spec/blob/master/runtime.md](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md).\n\nAs youki needs to deal with a lot of low level programming interfaces of Linux Kernel, another good place know is the online man pages project, which can be found at [https://man7.org/](https://man7.org/). Man pages provide detailed information about the programming interfaces of various features of Linux Kernel. You can simply search `man <feature-name>` using a search engine, or you can search at the site itself, at [https://man7.org/linux/man-pages/index.html](https://man7.org/linux/man-pages/index.html). These can be very helpful to know about the behavior and usage of and reasoning behind various kernel features used throughout youki.\n\nHappy developing!!!\n"
  },
  {
    "path": "docs/src/developer/crate_specific_information.md",
    "content": "# Crate Specific Information\n\nThis section contains subsections for each individual crate in the youki workspace. Each of the subsection will have information and resources on that particular crate.\n\nIn case you are working with some specific crate, you can find resources about it in its section. Also make sure you add any resources that you find when working on them as well.\n"
  },
  {
    "path": "docs/src/developer/debugging.md",
    "content": "# Debugging\n\nSince Youki uses pipe and double-fork in the creating phase, it is hard to debug what happened.\nYou might encounter the error message, \"Broken pipe ...\" Unfortunately, \nthis error message has only information that a child process exists with an error for some reason.\n\nThis section will give some tips to debug youki to know what happens in the child processes.\n\n# bpftrace\n\n[bpftrace](https://github.com/iovisor/bpftrace) is an eBPF based tool.\nIn the case of youki, you can catch the system calls youki issued.\n\nFor example, if you catch write system calls, you can see the log output until the middle of process.\nIt allows you to do something similar to print debugging.\n\n_How to debug_\n1. You need to install bpftrace, please refer to [the official documentation](https://github.com/iovisor/bpftrace/blob/master/INSTALL.md) to know how to install it.\n2. Before running the process or comannd you want to debug, run the following command in another terminal.\n\n    You need the root privilege to run it.\n    ```console\n    $ cd ${youki_repo}\n    $ just hack-bpftrace\n    ```\n3. Run the command you want to debug.\n\n_For example_\n\n1. Run the bpftrace script.\n\n    ```console\n    $ just hack-bpftrace\n    BPFTRACE_STRLEN=120 ./hack/debug.bt\n    Attaching 13 probes...\n    Tracing Youki syscalls... Hit Ctrl-C to end.\n    TIME                 COMMAND PID      EVENT     CONTENT\n    ```\n\n2. Run the Kubernetes cluster using kind with youki\n\n    ```console\n    $ cd ${youki_repo}\n    $ just test-kind\n    docker buildx build --output=bin/ -f tests/k8s/Dockerfile --target kind-bin .\n    ...\n    Creating cluster \"youki\" ...\n    ...\n    kubectl --context=kind-youki apply -f tests/k8s/deploy.yaml\n    runtimeclass.node.k8s.io/youki created\n    deployment.apps/nginx-deployment created\n    ...\n    kubectl --context=kind-youki delete -f tests/k8s/deploy.yaml\n    runtimeclass.node.k8s.io \"youki\" deleted\n    deployment.apps \"nginx-deployment\" deleted\n    ```\n\n3. Returning to the first command executed, the system calls youki issued are caught and logged.\n    \n    ```console\n    $ just hack-bpftrace\n    BPFTRACE_STRLEN=120 ./hack/debug.bt\n    Attaching 13 probes...\n    Tracing Youki syscalls... Hit Ctrl-C to end.\n    TIME                 COMMAND PID      EVENT     CONTENT\n    207033348942           youki 13743    open      errno=2, fd=-1, file=/opt/containerd/lib/glibc-hwcaps/x86-64-v3/libc.so.6\n    ...\n    207035462044           youki 13743    open      errno=0, fd=3, file=/proc/self/exe\n    207035478523           youki 13743    write     fd=4, ELF\n    207066996623               4 13743    open      errno=2, fd=-1, file=/opt/containerd/lib/glibc-hwcaps/x86-64-v3/libc.so.6\n    ...\n    207070130175               4 13743    clone3\n    207070418829 youki:[1:INTER] 13747    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.427846Z\",\"level\":\"INFO\",\"message\":\"cgroup manager V2 will be used\",\"target\":\"libcgrou\n    ...\n    207084948440 youki:[1:INTER] 13747    clone3\n    207085058811 youki:[1:INTER] 13747    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.442502Z\",\"level\":\"DEBUG\",\"message\":\"sending init pid (Pid(1305))\",\"target\":\"libcontai\n    207085343170  youki:[2:INIT] 13750    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.442746Z\",\"level\":\"DEBUG\",\"message\":\"unshare or setns: LinuxNamespace { typ: Uts, path\n    ...\n    207088256843  youki:[2:INIT] 13750    pivt_root new_root=/run/containerd/io.containerd.runtime.v2.task/k8s.io/0fea8cf5f8d1619a35ca67fd6fa73d8d7c8fc70ac2ed43ee2ac2f8610bb938f6/r, put_old=/run/containerd/io.containerd.runtime.v2.task/k8s.io/0fea8cf5f8d1619a35ca67fd6fa73d8d7c8fc70ac2ed43ee2ac2f8610bb938f6/r\n    ...\n    207097207551  youki:[2:INIT] 13750    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.454645Z\",\"level\":\"DEBUG\",\"message\":\"found executable in executor\",\"executable\":\"\\\"/pa\n    ...\n    207139391811  youki:[2:INIT] 13750    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.496815Z\",\"level\":\"DEBUG\",\"message\":\"received: start container\",\"target\":\"libcontainer\n    207139423243  youki:[2:INIT] 13750    write     fd=4, {\"timestamp\":\"2023-09-24T10:47:07.496868Z\",\"level\":\"DEBUG\",\"message\":\"executing workload with default handler\",\"target\"\n\n    ```\n"
  },
  {
    "path": "docs/src/developer/documentation_mdbook.md",
    "content": "# This Documentation\n\nThis documentation is created using mdbook and aims to provide a concise reference for users and developers of youki. For more information on mdbook itself, you can check out the [mdbook documentation](https://rust-lang.github.io/mdBook/).\n\nPlease make sure that you update this documentation along with newly added features and resources that you found helpful while developing, so that it will be helpful for newcomers.\n\nCurrently this documentation is hosted at [https://youki-dev.github.io/youki/](https://youki-dev.github.io/youki/), using GitHub pages. GitHub CI actions are used to automatically check if any files are changed in /docs on each push / PR merge to main branch, and if there are any changes, the mdbook is build and deployed to gh-pages. We use [https://github.com/peaceiris/actions-mdbook](https://github.com/peaceiris/actions-mdbook) to build and then [https://github.com/peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) GitHub action to deploy the mdbook.\n\nWhen testing locally you can manually test the changes by running `mdbook serve` in the docs directory (after installing mdbook), which will temporarily serve the mdbook at `localhost:3000` by default. You can check the mdbook documentation for more information.  \n(If you are running inside a Dev Container, use `mdbook serve -n 0.0.0.0` instead)\n\nIf you want to test it using gh-pages on your own fork, you can use following steps in the docs directory.\n\n```console\ngit worktree prune\n# Do this if you are running this command first time after booting,\n# As after shutdown /tmp files are removed\ngit branch -D gh-pages && git worktree add /tmp/book -b gh-pages\nmdbook build\nrm -rf /tmp/book/* # this won't delete the .git directory\ncp -rp book/* /tmp/book/\ncd /tmp/book\ngit add -A\ngit commit 'new book message'\ngit push -f origin gh-pages\ncd -\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/containerd_integration_test_using_youki.md",
    "content": "# containerd integration test using youki\n\n## local\n\n```console\njust containerd-test\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/e2e_tests.md",
    "content": "# e2e tests\n\nThere are various e2e tests:\n\n- [rust oci tests](./rust_oci_test.md)\n\n  This is youki's original integration to verify the behavior of the low-level container runtime.\n\n- [containerd integration test](./containerd_integration_test_using_youki.md)\n\n  This is the method that containerd's integration test runs with youki.\n\n- [runtime tools](./runtime_tools.md)\n\n  This is the method to run the runtime tools that OCI manages as a test tool to verify meeting the OCI Runtime Spec\n\n- [Kubernetes test](./kubernetes_test.md)\n\n  This test verifies youki works as a Kubernetes container runtime using Kind.\n"
  },
  {
    "path": "docs/src/developer/e2e/integration_test.md",
    "content": "# integration_test\n\n**Note** that these tests resides in `/tests/integration_test/` at the time of writing.\n\nThis crate contains the Rust port of OCI-runtime tools integration tests, which are used to test if the runtime works as per the OCI spec or not. Initially youki used the original implementation of these test provided in the OCI repository [here](https://github.com/opencontainers/runtime-tools/tree/master/validation). But those tests are written in Go, which made the developers depend on two language environments Rust and Go to compile youki and test it. The Validation tests themselves also have an optional dependency on node js to parse their output, which can make it a third language dependency.\n\nOther than that, those tests also showed some issues while running on some local systems, and thus running the tests would be difficult on local system. As the runtime is a complex piece of software, it becomes useful to have a set of tests that can be run with changes in code, so one can verify that change in one part of youki has not accidentally broken some other part of youki.\n\nThus we decided to port the tests to Rust, and validate them, so that we have a set of unit tests as well of integration tests to validate the working of runtime. These tests are still under development, and you can check the [tracking issue](https://github.com/youki-dev/youki/issues/361) for more details. More details on working of these tests can be found at [https://github.com/youki-dev/youki/tree/main/crates/integration_test](https://github.com/youki-dev/youki/tree/main/tests/contest/contest).\n\nAs these tests are under development, these are validated on a standard runtime such as runc in the GitHub CI, so validate the tests themselves.\n\n## Notes\n\n### About the create container function\n\nThe test_utils provides a `create_container` function which can be used to run the `youki create` command. It returns the child process struct, which can be either `wait()` or `wait_with_output()` to wait for it finishing. Unless you know what are you doing, it is recommended to call `wait()` on it, as otherwise the process will hang. As explained in the [youki docs](../youki.md) , the `youki create` process, after starting forks, and the forked process keeps waiting for another youki process to send it the `start` signal , and after receiving it, that forked process execs the container program. If you are simply trying to create a container, such as in case of `test_outside_runtime` then calling `wait_with_output()` will cause it to hang. If you are actually going to start a container, and need output from the container process, then you must keep the `Child struct` returned by `create` function and call `wait_with_output()` on it **AFTER** you have called the start command on that container, which will give you the `stdout` and `stderr` of the process running inside the container.\n\nTo understand how this works, take a look at [handling stdio](https://github.com/opencontainers/runc/blob/master/docs/terminals.md) of the runc, specially the [detached pass-through mode](https://github.com/opencontainers/runc/blob/master/docs/terminals.md#detached-pass-through) section. As explained in it, we setup the stdio for the original youki process in `youki create` by setting the stdio to `Stdio::piped()` in the `create` function. Then we set the `terminal` option to `false` (which is the default anyway) in the spec, which makes it run in the pass-through mode. Then when the create process is done its work, and its forked process is waiting for the start signal, it uses the same stdio pipes. Thus calling `wait_with_output()` without starting will keep it hanged up, and after calling start, stdio of the program to be run inside the container can be obtained from the `youki create`'s process.\n\n### How test inside container works\n\nWe use `test_inside_container` for making sure that the restrictions and constraints are uphold from inside the container process.\nFor that, first whichever integration test needs to use it, must define the runtimetest as the container process in the spec, and then use `test_inside_container` function. It requires a function which will do the necessary setup for the tests that are to be run inside. Then the counterpart for the test should be added to the `runtimetest` crate, which will run inside the container and if there is any error, print it to the `stderr`. The `test_inside_container` function will wait for the tests to be over and then check the `stderr` to be empty. If it is not, the test is assumed to fail.\n"
  },
  {
    "path": "docs/src/developer/e2e/kubernetes_test.md",
    "content": "# Kubernetes test\n\n## Notes\n\nThis test verifies that youki works correctly as a container runtime in a Kubernetes environment using [Kind](https://kind.sigs.k8s.io/) (Kubernetes in Docker).\n\nThe test builds a custom Kind node image with youki, creates a cluster, and deploys nginx pods using a RuntimeClass that specifies youki as the runtime.\n\n## Local\n\n```console\n$ just test-kind\n```\n\nTo clean up an existing Kind cluster first:\n\n```console\n$ just clean-test-kind\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/runc_compatibility_test.md",
    "content": "# runc compatibility test\n\n## Notes\n\nThis test verifies compatibility with runc by running runc’s\n[integration tests](https://github.com/opencontainers/runc/tree/main/tests/integration)\nagainst the youki binary.\n\nThe list of tests to run is defined in `tests/runc/runc_test_pattern`.\nEach line must match the test name in runc’s Bats test files (the string in `@test \"...\"`).\nPrefix a line with `[skip]` to skip that test.\n\n## Local\n\n```console\n$ git submodule update --init --recursive\n$ just test-runc-comp\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/runtime_tools.md",
    "content": "# runtime tools\n\n## local\n\n```console\n$ git submodule update --init --recursive\n$ just test-oci\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/runtimetest.md",
    "content": "# Runtime Test\n\n**Note** that these tests resides in /tests/runtimetest at the time of writing.\n\nThis crate provides a binary which is used by integration tests to verify that the restrictions and constraints applied to the container are upheld by the container process, from inside the container process. This runs the tests one-by-one, and the failing test prints the error to the stderr.\n\n## Notes\n\nThis binary must be compiled with the option of static linking to crt0 given to the rustc. If compiled without it, it will add a linking to /lib64/ld-linux-x86-64.so . The binary compiled this way cannot be run inside the container process, as they do not have access to /lib64/... Thus the runtime test must be statically linked to crt0.\n\nWhile developing, originally this was added to the common workspace of all crates in youki. But then it was realized that this was quite inefficient because :\n\n- All packages except runtimetest will be compiled with dynamic linking\n- Runtimetest will be compiled with static linking\n\nNow runtimetest needs at least `oci-spec` and `nix` package for its operations, which are also dependencies of other packages in the workspace. Thus both of these, and recursively their dependencies must be compiled twice, each time, once for dynamic linking and once for static. The took a long time in the compilation stage, especially when developing / adding new tests. Separating runtimetest from the workspace allows it to have a separate target/ directory, where it can store the statically compiled dependencies, and the workspace can have its target/ directory, where it can store its dynamically compiled dependencies. That way only the crates which have changes need to be compiled (runtimetest or integration test), and not their dependencies.\n\nIn case in future this separation is not required, or some other configuration is chosen, make sure the multiple compilation issue does not arise, or the advantages of new method outweigh the time spent in double compilation.\n\nTo see if a binary can be run inside the container process, run\n\n```console\nreadelf -l path/to/binary |grep \"program interpreter\"\n```\n\n`[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]` means that the binary is not statically linked, and cannot be run inside the container process. If the above command gives no output, that means it does not require any program interpreter and can be run inside the container.\n\nAnother way is to run\n\n```console\nfile path/to/binary\n```\n\n```console\n./youki: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=...., for GNU/Linux 3.2.0, with debug_info, not stripped`\n```\n\nThis output indicates that the binary is dynamically linked, thus cannot be run inside the container process\n\n```console\n./runtimetest: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=...., for GNU/Linux 3.2.0, with debug_info, not stripped\n```\n\nThis output indicates that the binary is statically linked, and can be run inside the container process\n\nSome links to help :\n\n- [how to generate static executable](https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables)\n- [understanding the error which dynamically linked library gives](https://superuser.com/questions/248512/why-do-i-get-command-not-found-when-the-binary-file-exists)\n- [Rust cargo config for rustflags](https://doc.rust-lang.org/cargo/reference/config.html)\n"
  },
  {
    "path": "docs/src/developer/e2e/rust_oci_test.md",
    "content": "# Contest\n\nThis is youki's original integration to verify the behavior of the low-level container runtime.\n\n![Overview](../../assets/rust_oci_tests.png)\n\n# How to run\n\n```console\njust test-contest\n```\n\n# How to write\n\nWe will not go into detail here, but will explain how to write and add a new test case based on an example test.\n\n<details>\n<summary>Fully the code of the example test</summary>\n<p>\n\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs}}\n```\n\n</p>\n</details>\n\n\n1. Build the OCI Runtime Spec you want to verify\n\n    This testing framework automatically places [runtimetest](./runtimetest.md) in the container.\n    In other words, you can test the processes you want to execute within a container by writing them in runtimetest.\n    Therefore, it is common practice here to write an OCI Runtime Spec that executes `runtimetest`.\n\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:get_example_spec}}\n```\n\n2. Prepare a function that returns a `TestResult`, which represents the result of the test.\n\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:example_test}}\n```\n\n3. Create a `TestGroup` and register a test case you created\n\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:get_example_test}}\n```\n\n4. Register the `TestGroup` you created to a `TestManager`\n\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/contest/src/main.rs:register_example_test}}\n```\n\n5. Write the validation you want to run within a test container\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/runtimetest/src/main.rs:example_runtimetest_main}}\n```\n```rust,no_run,noplayground\n{{#include ../../../../tests/contest/runtimetest/src/tests.rs:example_hello_world}}\n```\n"
  },
  {
    "path": "docs/src/developer/e2e/test_framework.md",
    "content": "# test_framework\n\n**Note** that these tests resides in /tests/test_framework at the time of writing.\n\nThis crate contains the testing framework specifically developed for porting the OCI integration test to rust. This contains structs to represent the individual tests, group of tests and a test manager that has responsibility to run tests. This Also exposes traits which can be used to implement custom test structs or test group structs if needed.\n\nBy default the test groups are run in parallel using the [crossbeam crate](https://www.crates.io/crates/crossbeam), and the default test_group implementation also runs individual tests parallelly.\n\nSometimes you might need to run the tests in a test group serially or in certain order, for example in case of testing container lifecycle, a container must be created and started before stopping it. In such cases, you will need to implement the respective traits on your own structs, so that you can have fine control over the running of tests. Check the readme of the test_framework crate to see the struct and trait documentation [here](https://github.com/youki-dev/youki/tree/main/tests/contest/test_framework).\n"
  },
  {
    "path": "docs/src/developer/good_places_to_start.md",
    "content": "# Good places to start\n\nFirst of all, welcome to youki! Hope you have fun while developing and contributing :)\n\nThis lists some of the known places that are long-running and would be useful for beginners. But as the things under development can change any time, the best place to check are the issues on the [GitHub repo](https://github.com/youki-dev/youki/issues). You can find issues with labels `good first issue` or `help wanted` and start working on them.\n\nYou can also search for `TODO` or `FIXME` comments in the source, and try working on them, but not all of them are easy places to start, and some of them can be particularly tricky to fix.\n\n---\n\nThis lists known parts of youki that can be good for beginners at the time of the writing. Please update as things change.\n\n#### Documentation Comments\n\nCurrently youki is decently commented, and those explain most of the public facing API and structs. But there are still places which can use more doc comments, and examples showing usage, so people can use the docs generated by `cargo doc` as a guide.\n\nIf you don't know much about container runtime or low level system working, then this can be a good place to start. While going through the code and documenting it, you can learn about it. Make sure that you update this documentation with useful links that you found while commenting some code if it has some peculiar behavior, or it is hard to understand without knowing some background.\n\n#### Integration Tests\n\nYou can find more detailed information about this in the `integration_test` crate, but in brief, we currently use [OCI-runtime-tools](https://github.com/opencontainers/runtime-tools) provided integration tests to validate that youki is OCI spec compliant. But those are written in Go, which makes the developer depend on two language env to compile youki and test it. These tests also have some issues which makes them hard to use on some system setups.\n\nThus we are porting those test to Rust, so that it can be a Rust implementation of OCI-runtime integration tests, as well as be easy to run on local systems for testing. If you know Go and Rust this can be a great place to start. Check out the [tracking issue](https://github.com/youki-dev/youki/issues/361).\n"
  },
  {
    "path": "docs/src/developer/introduction.md",
    "content": "# Developer Documentation\n\nThis section of the documentation is more oriented towards those who wish to contribute to youki, and contains more information and resources about the working and implementation of it. So if you are thinking of helping, this is a great place to start with.\n\nAlso, Thank you! If you have any issues or doubts, you can ask them on youki's discord server [here](https://discord.gg/h7R3HgWUct).\n\nThis section is split into following parts\n\n- Basics : This contains general resources and information that you wold need to work with any parts of youki.\n\n- Unwritten Rules : This is the part to make them written! This will contain conventions and rules that were discussed and decided in PRs or just commonly followed when developing.\n\n- Good Places to Start : This section will contain some suggestions about the areas that will be a good place to start for new contributors.\n\n- Crate specific Information : This is split into one sections for each crate, and will contains information and resources specific for that crate.\n\nHappy Contributing!\n"
  },
  {
    "path": "docs/src/developer/libcgroups.md",
    "content": "# libcgroups\n\nThis crate provides an interface for working with cgroups in Linux. cgroups or control groups is a Linux kernel feature which can be used to fine-control resources and permissions given to a particular process or a group of processes. You can read more about them on the [cgroups man page](https://man7.org/linux/man-pages/man7/cgroups.7.html).\n\nThe initial version of cgroups is called the version 1 was implemented in kernel 2.6.24, and later in kernel version 4.5, a new version of cgroups was released, aimed to solve issues with v1, the version v2.\n\nThis crates exposes several functions and modules that can be used to work with cgroups :\n\n- Common traits and functions which are used by both v1 and v2 such as\n\n  - Trait `CgroupManager`, this abstracts over the underlying implementation of interacting with specific version of cgroups, and gives functions to add certain process to a certain cgroup, apply resource restrictions, get statistics of a cgroups, freeze a cgroup, remove a cgroup or get list of all processes belonging to a cgroup. v1 and v2 modules both contain a version specific cgroup manager which implements this trait, and thus either can be given to functions or structs which expects a cgroup manager, depending on which cgroups the host system uses.\n  - Apart from the trait, this also contains functions which help with reading cgroups files, and write data to a cgroup file, which are used throughout this crate.\n  - Functions to detect which cgroup setup (v1, v2 or hybrid) is on the host system with/without specified mounted cgroup root path, as well as functions to get the corresponding cgroups manager w/o cgroup root path.\n\n- Functions and structs to get and store the statistics of a cgroups such as\n\n  - CPU stats including usage and throttling\n  - Memory stats including usage of normal and swap memory, usage of kernel memory, page cache in bytes etc\n  - Pid stat including current active pids and maximum allowed pids\n  - Block IO stats such as number of bytest transferred to/from a device in the cgroup, io operations performed by a device in the cgroup, amount of time cgroup had access to a device etc\n  - Huge TLB stats such as usage and maximum usage etc.\n  - Function to get pid stats\n  - Function to get supported hugepage size\n  - Function to parse flat keyed data and nested keyed data that can be in a cgroups file\n  - Parse a device number\n\n- Cgroups V1 module which deal with implementing a cgroup manager for systems which have cgroups v1 or hybrid cgroups\n- Cgroups V2 module which deal with implementing a cgroup manager for systems which have cgroups v2\n\nAs youki currently depends on systemd as an init system, this crate also exposes module systemd, which provides interface for working with systemd related operations. [systemd resource control](https://www.freedesktop.org/software/systemd/man/systemd.resource-control.html) is a good place to read more about systemd and its involvement in resource control.\n\n## Dbus Native\n\nThis module is the native implementation of dbus connection functionality used for connecting with systemd via dbus. Refer to [this issue discussion](https://github.com/youki-dev/youki/issues/2208) following for the discussion regarding moving away from existing dbus-interfacing library.\n\nNote that this implements the minimal required functionality for youki to use dbus, and thus does not have all the dbus features.\n\n- Refer to see [dbus specification](https://dbus.freedesktop.org/doc/dbus-specification.html) and [header format](https://dbus.freedesktop.org/doc/api/html/structDBusHeader.html) for the individual specifications.\n\n- For systemd interface and types, you can generate the following file and take help from the auto-generated functions\n`dbus-codegen-rust -s -g -m None -d org.freedesktop.systemd1 -p /org/freedesktop/systemd1`, see https://github.com/diwic/dbus-rs\n"
  },
  {
    "path": "docs/src/developer/libcontainer.md",
    "content": "# libcontainer\n\nThis crate is one of the core crates of the youki workspace, and has functions and structs that deal with the actual creation and management of the container processes.\n\nRemember, in the end, a container is just another process in Linux, which has control groups, namespaces, pivot_root and other mechanisms applied to it. The program executing has the impression that it is running on a complete system, but from the host system's perspective, it is just another process, and has attributes such as pid, file descriptors, etc. associated with it like any other process.\n\nAlong with the container related functions, this crate also provides Youki Config, a subset of the OCI spec config. This config contains only the essential data required for running the containers, and due to its smaller size, parsing it and passing it around is more efficient than the complete OCI spec config struct.\n\nOther than that, this crate also provides a wrapper over basic Linux sockets, which are then used internally as well as by youki to communicate between the main youki process and the forked container process as well as the intermediate process.\n\nThis crate also provides an interface for Apparmor which is another Linux Kernel module allowing to apply security profiles on a per-program basis. More information about it can be found at [https://apparmor.net/](https://apparmor.net/).\n\n### Notes\n\n#### Some other modules exposed by this crate are\n\n- rootfs, which is a ramfs like simple filesystem used by kernel during initialization\n- hooks, which allow running of specified program at certain points in the container lifecycle, such as before and after creation, start etc.\n- signals, which provide a wrapper to convert to and from signal numbers and text representation of signal names\n- capabilities, which has functions related to set and reset specific capabilities, as well as to drop extra privileges\n  - [Simple explanation of capabilities](https://blog.container-solutions.com/linux-capabilities-in-practice)\n  - [man page for capabilities](https://man7.org/linux/man-pages/man7/capabilities.7.html)\n- tty module which deals with providing terminal interface to the container process\n  - [pseudoterminal man page](https://man7.org/linux/man-pages/man7/pty.7.html) : Information about the pseudoterminal system, useful to understand console_socket parameter in create subcommand\n\n#### Executor\n\nBy default and traditionally, the executor forks and execs into the binary\ncommand that specified in the oci spec. Using executors, we can override this\nbehavior. For example, `youki` uses executor to implement running wasm\nworkloads. Instead of running the command specified in the process section of\nthe OCI spec, the wasm related executors can choose to execute wasm code\ninstead. The executor will run at the end of the container init process.\n\nThe API accepts only a single executor, so when using multiple executors, (try\nwasm first, then defaults to running a binary), the users should compose\nmultiple executors into a single executor. The executor will return an error\nwhen the executor can't handle the workload.\n\n#### Namespaces : namespaces provide isolation of resources such as filesystem, process ids networks etc on kernel level. This module contains structs and functions related to applying or un-applying namespaces to the calling process\n\n- [pid namespace man page](https://man7.org/linux/man-pages/man7/pid_namespaces.7.html)\n- [CLONE_NEWUSER flag](https://man7.org/linux/man-pages/man2/clone.2.html)\n\n> Note: clone(2) offers us the ability to enter into user and pid namespace by creating only one process. However, clone(2) can only create new pid namespace, but cannot enter into existing pid namespaces. Therefore, to enter into existing pid namespaces, we would need to fork twice. Currently, there is no getting around this limitation.\n\n- [fork(2) man page](https://man7.org/linux/man-pages/man2/fork.2.html)\n- [clone(2) man page](https://man7.org/linux/man-pages/man2/clone.2.html)\n\n#### Pausing and resuming\n\nPausing a container indicates suspending all processes in it. This can be done with signals SIGSTOP and SIGCONT, but these can be intercepted. Using cgroups to suspend and resume processes without letting tasks know.\n\n- [cgroups man page](https://man7.org/linux/man-pages/man7/cgroups.7.html)\n- [freezer cgroup kernel documentation](https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt)\n\n#### The following are some resources that can help understand with various Linux features used in the code of this crate\n\n- [oom-score-adj](https://dev.to/rrampage/surviving-the-linux-oom-killer-2ki9)\n- [unshare man page](https://man7.org/linux/man-pages/man1/unshare.1.html)\n- [user-namespace man page](https://man7.org/linux/man-pages/man7/user_namespaces.7.html)\n- [wait man page](https://man7.org/linux/man-pages/man3/wait.3p.html)\n- [pipe2 man page](https://man7.org/linux/man-pages/man2/pipe.2.html) : Definition and usage of pipe2\n- [Unix Sockets man page](https://man7.org/linux/man-pages/man7/unix.7.html) : Useful to understand sockets\n- [prctl man page](https://man7.org/linux/man-pages/man2/prctl.2.html) : Process control man pages\n"
  },
  {
    "path": "docs/src/developer/liboci_cli.md",
    "content": "# liboci-cli\n\nThis crate was separated from original youki crate, and now contains a standalone implementation of structs needed for parsing commandline arguments for OCI-spec compliant runtime commandline interface. This is in turn used by youki to parse the command line arguments passed to it, but can be used in any other projects where there is need to parse OCI spec based commandline arguments.\n\nThis primarily uses the [crate clap-v3](https://docs.rs/clap/latest/clap/index.html) for parsing the actual commandline arguments given to the runtime.\n\nYou can refer to [OCI Commandline interface guide](https://github.com/opencontainers/runtime-tools/blob/master/docs/command-line-interface.md) to know more about the exact specification.\n"
  },
  {
    "path": "docs/src/developer/libseccomp.md",
    "content": "# libseccomp\n\nSeccomp is a linux kernel feature that allows a process a one-way transition into secure mode, where restrictions are applied to the syscalls the process can make, as well as restrictions on the file descriptors. Specifically, it can exit, sigreturn and read/write already open file descriptors. This way the process can be isolated and restricted on how it interacts with rest of the system on a kernel level.\n\nThis crate does not actually implement any particular feature, but provides Rust FFI bindings for seccomp module. These are primarily generated by using rsut-bindgen on seccomp C header file, and then manually fixed where any issues were found. More information about seccomp can be found in its [man page](https://man7.org/linux/man-pages/man2/seccomp.2.html).\n"
  },
  {
    "path": "docs/src/developer/repo_structure.md",
    "content": "# Repository Structure\n\nThis page might be the one that gets most easily outdated, as the structure might change at any time! Thus make sure to update this whenever there are any changes in the overall structure of the whole repo. For the same reason, this does not list the structure in detail but instead describes only the main directories.\n\n### .github\n\nContains workflows and files needed by those workflows.\n\n### crates\n\nThis is the core of youki. This contains various libraries that are developed alongside of youki and the youki binary itself.\n\n### docs\n\nThe directory where the source of this documentation resides. The source is also divided into two parts, for developers and users. Please see [Documentation documentation](./documentation_mdbook.md) for more information.\n\n### hack\n\nAs the name suggests, contains hack scripts for patching some issues which are currently not solvable in a straightforward way or solving issues for which we have no idea of why they occur.\n\n### Scripts\n\nContains scripts for various purposes, such as building youki, running integration tests etc. These might be small scripts called from many other scripts, big scripts that perform a complex task or helper scripts for the main makefile.\n\n### tests\n\nThis contains all the integration tests for validating youki. Note that these are integration tests for start-to-end testing of youki commands. Unit tests for individual parts are in their respective source files in crates.\n"
  },
  {
    "path": "docs/src/developer/runtimetest.md",
    "content": "# Runtime Test\n\n**Note** that these tests resides in /tests/runtimetest at the time of writing.\n\nThis crate provides a binary which is used by integration tests to verify that the restrictions and constraints applied to the container are upheld by the container process, from inside the container process. This runs the tests one-by-one, and the failing test prints the error to the stderr.\n\n## Notes\n\nThis binary must be compiled with the option of static linking to crt0 given to the rustc. If compiled without it, it will add a linking to /lib64/ld-linux-x86-64.so . The binary compiled this way cannot be run inside the container process, as they do not have access to /lib64/... Thus the runtime test must be statically linked to crt0.\n\nWhile developing, originally this was added to the common workspace of all crates in youki. But then it was realized that this was quite inefficient because :\n\n- All packages except runtimetest will be compiled with dynamic linking\n- Runtimetest will be compiled with static linking\n\nNow runtimetest needs at least `oci-spec` and `nix` package for its operations, which are also dependencies of other packages in the workspace. Thus both of these, and recursively their dependencies must be compiled twice, each time, once for dynamic linking and once for static. The took a long time in the compilation stage, especially when developing / adding new tests. Separating runtimetest from the workspace allows it to have a separate target/ directory, where it can store the statically compiled dependencies, and the workspace can have its target/ directory, where it can store its dynamically compiled dependencies. That way only the crates which have changes need to be compiled (runtimetest or integration test), and not their dependencies.\n\nIn case in future this separation is not required, or some other configuration is chosen, make sure the multiple compilation issue does not arise, or the advantages of new method outweigh the time spent in double compilation.\n\nTo see if a binary can be run inside the container process, run\n\n```console\nreadelf -l path/to/binary |grep \"program interpreter\"\n```\n\n`[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]` means that the binary is not statically linked, and cannot be run inside the container process. If the above command gives no output, that means it does not require any program interpreter and can be run inside the container.\n\nAnother way is to run\n\n```console\nfile path/to/binary\n```\n\n```console\n./youki: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=...., for GNU/Linux 3.2.0, with debug_info, not stripped`\n```\n\nThis output indicates that the binary is dynamically linked, thus cannot be run inside the container process\n\n```console\n./runtimetest: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=...., for GNU/Linux 3.2.0, with debug_info, not stripped\n```\n\nThis output indicates that the binary is statically linked, and can be run inside the container process\n\nSome links to help :\n\n- [how to generate static executable](https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables)\n- [understanding the error which dynamically linked library gives](https://superuser.com/questions/248512/why-do-i-get-command-not-found-when-the-binary-file-exists)\n- [Rust cargo config for rustflags](https://doc.rust-lang.org/cargo/reference/config.html)\n"
  },
  {
    "path": "docs/src/developer/unwritten_rules.md",
    "content": "# Unwritten Rule\n\nThis is the place to write down rules or conventions that were discussed in PRs, so that newcomers can easily find them, without having to go through the PR history. So if you decide on any convention to follow for the project, please make sure to add them here.\n\n## Conventions to follow\n\n#### Errors\n\nYouki currently uses [anyhow](https://www.crates.io/crates/anyhow) library to deal with errors occurring during its execution. So wherever you use fallible actions, or functions that can return `Result`, make sure you attach enough information with the errors so that error logs can be useful for debugging later. For example, if you are reading a file, or parsing something and the operation does not succeed, you can add the path from which you attempted to read the file, or the string that you attempted to parse.\n\nAlso for the error messages, we follow the convention all small-case letters and no period at the end, as discussed in [this PR](https://github.com/youki-dev/youki/issues/313). Whenever you write error messages, please follow this convention to keep them uniform.\n\n#### Logs\n\nyouki uses [log](https://crates.io/crates/log) crate to log information while running. Whenever adding code to interact with system or kernel features or such, make sure to add debug logs so that if youki crashes, you can trace the errors and zero-in on the reasons using logs.\n\n#### Comments\n\nMake sure that you comment copiously, and explain the peculiar behavior of your code so that others can understand why certain code is written the way it is. Also make sure to add doc comments and examples for `pub` items in the crates, so that users can find it from the docs generated by `cargo doc`.\n\n#### Scripts\n\nIn any script, any makefile etc, make sure to `set -e` at the start. This will abort the script after any command fails, rather than continuing with incorrect state and creating knock-on errors.\n\n#### Update This Documentation\n\nKeep this Documentation updated! Make sure you add any relevant doc-links and resources to this that you found helpful or contains background information required to understand the code, so that it can help newcomers as well as others to find the resources in one single place.\n"
  },
  {
    "path": "docs/src/developer/youki.md",
    "content": "# youki\n\nThis is the core crate that contains the youki binary itself. This provides the user interface, as well as binds the other crates together to actually perform the work of creation and management of containers. Thus, this provides implementation of all the commands supported by youki.\n\nThe simple control flow of youki can be explained as :\n\n<p align=\"center\">\n  <img src=\"../assets/control_flow.drawio.svg\">\n</p>\n\nWhen given the create command, Youki will load the specification, configuration, sockets etc., and use clone syscall to create an intermediate process. This process will set the cgroups and capabilities, and then fork to the init process. Reason to create this intermediate process is that the clone syscall cannot enter into existing pid namespace that has been created for the container. Thus first we need to make a transition to that namespace in the intermediate process and fork that to the container process. After that the main youki process is requested the uid and gid mappings, and after receiving them the intermediate process sets these mapping, fork the init process and return pid of this init process to the main youki process before exiting.\n\nThe init process then transition completely into the new namespace setup for the container (the init process only transitions the pid namespace). It changes the root mountpoint for the process using [pivot_root](https://man7.org/linux/man-pages/man2/pivot_root.2.html), so that the container process can get impression that it has a complete root path access. After that the init process sets up the capabilities and seccomp, and sends the seccomp notify fd to the main youki process. When the seccomp agent running on the host system sets up the seccomp profile, it notifies the init process, after which it can execute the program inside the container. Thus the init process then sends ready notification to the main youki process, and waits for the start signal.\n\nThe main youki process which started creating the container, when receives the ready signals update the pid file of the container process and exits. This concludes the creation of the container.\n\nTo start the container, when youki start it executed along with the container id, start signal is sent to the waiting container init process, and the the youki process exists.\n\nWhen the init process receives the start signal, it execs the program to be run in the container, and then exits.\n\n### Notes\n\nThe main youki process will set up pipes used as message passing and synchronization mechanism with the init process. The reason youki needs to create/fork two process instead of one is due to the user and pid namespaces. In rootless container, we need to first enter user namespace, since all other namespaces requires CAP_SYSADMIN. When unshare or set_ns into pid namespace, only the children of the current process will enter into a different pid namespace. As a result, we must first fork a process to enter into user namespace, call unshare or set_ns for pid namespace, then fork again to enter into the correct pid namespace.\n"
  },
  {
    "path": "docs/src/user/basic_setup.md",
    "content": "# Basic Setup\n\nThis explains the requirements to use youki as a low-level container runtime, or to depend once of its crates as dependency for your own project.\n\nYouki currently only supports Linux Platform, and to use it on other platform you will need to use some kind of virtualization.\n\nAlso note that youki currently only supports and expects systemd as init system, and would not work on other systems. There is currently work on-going to put systemd dependent features behind a feature flag, but till then you will need a systemd enabled system to work with youki.\n\n## Getting Started\n\n### Runtime requirements\n\nThe static binary (musl) builds of youki have no additional runtime requirements. Otherwise you need to install the runtime requirements using your distribution's package manager:\n\n#### Debian, Ubuntu and related distributions\n```console\nsudo apt-get install libseccomp2\n```\n\n#### Fedora, CentOS, RHEL and related distributions\n```console\nsudo dnf install libseccomp\n```\n\n### Install youki (prebuilt binary)\n\nInstall from the GitHub release as root:\n\n<!--youki release begin-->\n```console\n# curl -sSfL https://github.com/youki-dev/youki/releases/download/v0.6.0/youki-0.6.0-$(uname -m)-musl.tar.gz | tar -xzvC /usr/bin/ youki\n```\n<!--youki release end-->\n\n### Running youki\n\nYou can use youki by itself to start and run containers, but it can be a little tedious, as it is a low-level container runtime. You can use a High-level container runtime, with its runtime set to youki, so that it will be easier to use.\n\nThis documentation uses Docker in its examples, which can be installed from [here](https://docs.docker.com/engine/install).\nAfter installing Docker, configure youki as a Docker runtime as follows:\n\n```console\nsudo cat > /etc/docker/daemon.json <<EOF\n{\n  \"runtimes\": {\n    \"youki\": {\n      \"path\": \"/usr/bin/youki\"\n    }\n  }\n}\nEOF\n\nsudo systemctl reload docker\n```\n\nOnce configured, you can run a container using youki like this:\n\n```console\ndocker run --rm --runtime youki hello-world\n```\n\nFor more details, see [Basic Usage](./basic_usage.md).\n\n---\n\n## Installing youki from source\n\n### Build Requirements\n\nAs youki is written in Rust, you will need to install and setup Rust toolchain to compile it. The instructions for that can be found on Rust's official site [here](https://www.rust-lang.org/tools/install).\nIf you installed it using rustup, the correct compiler version will be setup automatically from `rust-toolchain.toml` in the repo root.\n\n### Build with cross-rs\n\nYou can compile youki using [cross-rs](https://github.com/cross-rs/cross), which provides:\n* Seamless compilation for different architectures (see `Cross.toml` in the repo root for the list of supported targets)\n* No build time dependencies (compilation runs in a container)\n* No runtime dependencies when building static binaries (musl targets)\n\nThe only build dependency is [cross-rs](https://github.com/cross-rs/cross?tab=readme-ov-file#installation) and its [dependencies](https://github.com/cross-rs/cross?tab=readme-ov-file#dependencies) (rustup and docker or podman).\n\n\n```console\nCARGO=cross TARGET=musl just youki-dev # or youki-release\n```\n\n### Build without cross-rs\n\nInstall the build dependencies and then run:\n```console\njust youki-dev # or youki-release\n```\n\nInstall the build dependencies using your distribution's package manger\n\n#### Debian, Ubuntu and related distributions\n```console\nsudo apt-get install    \\\n      pkg-config        \\\n      libsystemd-dev    \\\n      build-essential   \\\n      libelf-dev        \\\n      libseccomp-dev    \\\n      libclang-dev      \\\n      libssl-dev\n```\n\n#### Fedora, CentOS, RHEL and related distributions\n```console\nsudo dnf install            \\\n      pkg-config            \\\n      systemd-devel         \\\n      elfutils-libelf-devel \\\n      libseccomp-devel      \\\n      clang-devel           \\\n      openssl-devel\n```\n\n### Getting the source\n\nCurrently youki can only be installed from the source code itself, so you will need to clone the youki GitHub repository to get the source code for using it as a runtime. If you are using any crates of youki as dependency you need to do this step, as Cargo will automatically clone the repository for you.\n\nTo clone the repository, run\n\n```console\ngit clone https://github.com/youki-dev/youki.git\n```\n\nThis will create a directory named youki in the directory you ran the command in. This youki directory will be referred to as root directory throughout the documentation.\n\n### Build from source\n\nOnce you have cloned the source, you can build it with [just](https://github.com/casey/just#installation) :\n\n```console\n# go into the cloned directory\ncd youki\njust youki-dev # or youki-release\n./youki -h # get information about youki command\n```\n\nThis will build the youki binary, and put it at the root level of the cloned directory, that is in the youki/ .\n\n---\n\n### Using sub-crates as dependency\n\nTo use any of the sub-crate as a dependency in your own project, you can specify the dependency as follows,\n\n```toml\n[dependencies]\n...\nliboci-cli = { git = \"https://github.com/youki-dev/youki.git\" }\n...\n```\n\nHere we use `liboci-cli` as an example, which can be replaced by the sub-crate that you need.\n\nThen you can use it in your source as\n\n```\nuse liboci_cli::{...}\n```\n\n---\n\n### Using Vagrant to run youki on non-Linux Platform\n\nAs explained before, youki only support Linux, and to build/use it on non-Linux Platforms, you will need to use some kind of virtualization. The repo provides a Vagrantfile to do the required VM setup using Vagrant, which can be installed from [here](https://www.vagrantup.com/docs/installation).\n\nOnce installed and setup, you can run vagrant commands in the cloned directory to run youki inside the VM created by vagrant :\n\n```console\n# in the youki directory\n\n# for rootless mode, which is default\nvagrant up default\nvagrant ssh default\n\n# or if you want to develop in rootful mode\nvagrant up rootful\nvagrant ssh rootful\n\n# in virtual machine\ncd youki\njust youki-dev # or youki-release\n```\n"
  },
  {
    "path": "docs/src/user/basic_usage.md",
    "content": "# Basic Usage\n\nThis explains using Youki as a low-level container runtime. Youki can be used by itself to create, start and run containers, but doing so can be tedious, and thus you might want to use a higher-level runtime with Youki set as its runtime, so that you can get a convenient and easy interface.\n\nYou can use Youki with Docker, or Podman, but for the purpose of the examples, we will illustrate using Docker.\n\nYouki can run in two modes, namely rootful mode, and rootless mode. The primary difference from the user-perspective in these is that as the name suggests, rootless mode does not require root/admin permissions, while rootful mode needs the root permissions. Both of these are shown in the examples below.\n\n#### Using youki with a high-level runtime\n\nWe will first see how to use Youki with a high-level runtime such as Docker. You can install Docker from [here](https://docs.docker.com/engine/install/).\n\nBy default, after installation the docker sets up so that its daemon process will start running in background after booting up. By default, this configures Docker to use its default low-level runtime, and to use Youki instead , we will first need to stop the running Docker daemon.\n\nAs Youki needs systemd to compile, this assumes that you are running on a systemd based system. So you an first check if the docker daemon is running or not by running\n\n```console\nsudo systemctl status docker\n```\n\nThis will print a message showing if the daemon is active or not. If it is active, then you will need to stop it by running\n\n```console\nsudo systemctl stop docker\n```\n\nAfter this you need to manually restart the docker daemon, but with Youki as its runtime. To do this, run following command in the youki/ directory after building youki\n\n```console\ndockerd --experimental --add-runtime=\"youki=$(pwd)/youki\" # run in the youki/scripts directory\n```\n\nThis will start the daemon and hang up the console. You can either start this as a background process to continue using the same terminal, or use another terminal, which will make it easier to stop the docker daemon later.\n\nIn case you don't stop the original daemon, you can get an error message after previous command\n\n```console\nfailed to start daemon: pid file found, ensure docker is not running or delete /var/run/docker.pid\n```\n\nNow that the docker daemon is running, you can use docker normally as you will, but you will be able to specify Youki as its low-level runtime to actually create, start and stop the containers.\n\nYou can try running a container such as\n\n```console\ndocker run -it --rm --runtime youki busybox   # run a container\n```\n\nThis will start a busybox container, and give access to terminal inside it.\n\nAfter you are done, you can stop the docker daemon by sending it a signal, either by using `Ctrl` + `C` if you are running the process in another terminal, or by using kill command with the pid of it, if you have started it as a background process.\n\nThen to start the original/normal Docker daemon, you can run\n\n```console\nsudo systemctl start docker\n```\n\n#### Let docker permanently know youki as a runtime\n\nWith newer versions of docker, you can update file `/etc/docker/daemon.json` to\nlet docker know youki\n([source](https://docs.docker.com/engine/reference/commandline/dockerd/#on-linux)).\nYou may need to create this file, if it does not yet exist. A sample content of it:\n\n```json\n{\n  \"default-runtime\": \"runc\",\n  \"runtimes\": {\n    \"youki\": {\n      \"path\": \"/path/to/youki/youki\",\n      \"runtimeArgs\": [\n          \"--debug\",\n          \"--systemd-log\"\n      ]\n    }\n  }\n}\n```\n\nAfter this (need to restart docker at the first time), you can use youki\nwith docker: `docker run --runtime youki ...`. You can verify the runtime includes `youki`:\n\n```console\n$ docker info|grep -i runtime\n Runtimes: youki runc\n Default Runtime: runc\n```\n\n#### Using Youki Standalone\n\nYouki can also be used directly, without a higher-level runtime such as Docker to create, start, stop and delete the container, but the process can be tedious. Here we will show how you can do that, to run a simple container with desired program running in it.\n\nNote that we will still be using Docker to generate the rootfs required for running the container.\n\nTo start, in the youki/scripts directory, make another directory named tutorial, and create a sub-directory rootfs inside it\n\n```console\nmkdir -p tutorial/rootfs\n```\n\nAfter that, you will need to use docker to create the required directory structure\n\n```console\ncd tutorial\ndocker export $(docker create busybox) | tar -C rootfs -xvf -\n```\n\nThis will create the required directory structure for using it as a root directory inside the container.\n\nNow the any container runtime gets the information about the permissions, configurations and constraints for the container process by using a config.json file. Youki has a command which can generate the default config for you. To do this, run\n\n```console\n../youki spec\n```\n\nAfter this, you can manually edit the file to customize the behavior of the container process. For example, to run the desired program inside the container, you can edit the process.args\n\n```json\n\"process\": {\n...\n\"args\": [\n  \"sleep\", \"30\"\n],\n...\n }\n```\n\nHere you can change the args to specify the program to be run, and arguments to be given to it.\n\nAfter this, go back to the youki/ directory\n\n```console\ncd ..\n```\n\nAs the setup is complete, you can now use youki to create the container, start the container, get its state etc.\n\n```console\n# create a container with name `tutorial_container`\nsudo ./youki create -b tutorial tutorial_container\n\n# you can see the state the container is `created`\nsudo ./youki state tutorial_container\n\n# start the container\nsudo ./youki start tutorial_container\n\n# will show the list of containers, the container is `running`\nsudo ./youki list\n\n# delete the container\nsudo ./youki delete tutorial_container\n```\n\nThe example above shows how to run Youki in a 'rootful' way. To run it without root permissions, that is, in rootless mode, few changes are required.\n\nFirst, after exporting the rootfs from docker, while generating the config, you will need to pass the rootless flag. This will generate the config withe the options needed for rootless operation of the container.\n\n```console\n../youki spec --rootless\n```\n\nAfter this, the steps are basically the same, except you do not need to use sudo while running youki.\n\n```console\ncd ..\n./youki create -b tutorial rootless_container\n./youki state rootless_container\n./youki start rootless_container\n./youki list\n./youki delete rootless_container\n```\n\n#### Log level\n\n`youki` defaults the log level to `error` in the release build. In the debug\nbuild, the log level defaults to `debug`. The `--log-level` flag can be used to\nset the log-level. For least amount of log, we recommend using the `error` log\nlevel. For the most spammy logging, we have a `trace` level.\n\nFor compatibility with `runc` and `crun`, we have a `--debug` flag to set the\nlog level to `debug`. This flag is ignored if `--log-level` is also set.\n"
  },
  {
    "path": "docs/src/user/crates.md",
    "content": "# Crates provided\n\nYouki repo itself is a Cargo workspace, comprising of several sub-crates, each one for some specific functionality. The youki binary depends on this to provide the low-level functionality, and you can use these crate as a dependency for your own projects as well.\n\nFor more information on how to add a sub-crate as a dependency in your project, see [Basic Usage](./basic_usage.md).\n\nThe subsection in this part briefly explains some of the crates, and some of the functionality they expose. This should be good enough to get a general idea of each crate. To get detailed information about the structs, functions and modules each crate exposes, unfortunately, for the time being, you will need to go through the source itself, but we are working on creating better docs.\n"
  },
  {
    "path": "docs/src/user/introduction.md",
    "content": "# User Documentation\n\nThis section provides documentation of youki and the sub crates that the youki repo contains for the users. So if you are using youki as a low level container runtime, or using any of the crates in youki workspace as dependencies for your own project, this section might be helpful for you.\n\nThis is divided into following sub-sections :\n\n- Basic Setup : This explains how the dependencies and setup required to compile and run youki\n- Basic Usage : This explains using youki itself as a low-level container runtime, or using one of the crates as dependencies\n- crates : This section provides brief explanation of the member crates of youki repo workspace\n  - libcgroups\n  - libcontainer\n  - liboci-cli\n  - libseccomp\n- Webassembly : This explains how to use webassembly module with youki.\n"
  },
  {
    "path": "docs/src/user/libcgroups.md",
    "content": "# libcgroups\n\nlibcgroups is the crate that contains functionality to work with Linux cgroups. This provide an easy to use interface over reading and writing cgroups files, as well as various structs that represent the cgroups data.\n\nThe modules that it exposes are :\n\n- common\n- stats\n- systemd\n- test_manager\n- v1\n- v2\n\nFollowing is a short explanation of these modules.\n\n### common\n\nThis module contains functionality that is general to any type of cgroup. Some of the things it provides are:\n\n- trait `CgroupManager` which gives and interface for the following:\n\n  - add a task to a cgroup\n  - apply resource restriction\n  - remove a cgroup\n  - control freezer cgroup state\n  - get stats from a cgroup\n  - get pids belonging to the cgroup\n\n- functions `write_cgroup_file_str` and `write_cgroup_file` which write data to a cgroup file\n- function `read_cgroup_file` which reads data from given cgroup file\n- function `get_cgroup_setup_with_root` which returns setup of cgroups (v1,v2, hybrid) on the system with specified cgroup root path\n- function `get_cgroup_setup` which returns setup of cgroups (v1,v2, hybrid) on the system with default cgroup root path `/sys/fs/cgroup`\n- function `create_cgroup_manager_with_root` which returns corresponding cgroup manager on the system with specified cgroup root path, if the passed `root_path` argument is `None`, then it's same as function `create_cgroup_manager`\n- function `create_cgroup_manager` which returns corresponding cgroup manager on the system with default cgroup root path `/sys/fs/cgroup`\n\n### stats\n\nThis module has functionalities related to statistics data of the cgroups, and structs representing it.\n\nSome of the things it exposes are\n\n- struct `Stats` which contains following structs:\n\n  - `CpuStats` : contains cpu usage and throttling information\n\n  - `MemoryStats` : contains usage of memory, swap and memory combined, kernel memory, kernel tcp memory and other memory stats\n\n  - `PidStats` : contains current number of active pids and allowed number of pids\n\n  - `BlkioStats` : contains block io related stats, such as number of bytes transferred from/to a device in cgroup, number of io operations done by a device in cgroup, device access and queue information etc.\n\n  - `HugeTlbStats` : containing stats for Huge TLB such as usage, max_usage, and fail count\n\n- function `supported_page_size` which returns hugepage size supported by the system\n\n- utility functions to operate with data in cgroups files such as:\n\n  - `parse_single_value` : reads file expecting it to have a single value, and returns the value\n\n  - `parse_flat_keyed_data` : parses cgroup file data which is in flat keyed format (key value)\n\n  - `parse_nested_keyed_data` : parses cgroup file data which is in nested keyed format (key list of values)\n\n  - `parse_device_number` : parses major and minor number of device\n\n### systemd\n\nThis is the module used by youki to interact with systemd, and it exposes several functions to interact:\n\n- function `booted` to check if the system was booted with systemd or not\n\n- module `controller_type`, which contains enum `ControllerType` which is used to specify cgroup controllers available on a system\n\n- module `manager`, which contains `Manager` struct, which is the cgroup manager, and contain information such as the root cgroups path, path for the specific cgroups, client to communicate with systemd etc. This also implements `CgroupManager` trait, and thus can be used for cgroups related operations.\n\n- module `dbus-native` is the native implementation for dbus connection, which is used to interact with systemd in rootless mode.\n\n### test_manager\n\nThis exposes a `TestManager` struct which can be used as dummy for cgroup testing purposes, which also implements `CgroupManager`.\n\n### v1 and v2\n\nThese two modules contains functionalities specific to cgroups version 1 and version 2. Both of these expose respective cgroup managers, which can be used to manage that type of cgroup, as well as some utility functions related to respective cgroup version, such as `get_mount_points` (for v1 and v2), `get_subsystem_mount points` (for v1), and `get_available_controllers` (for v2) etc.\n\nThe v2 module also exposes devices module, which provides functionality for working with bpf, such as load a bpf program, query info of a bpf program, attach and detach a bpf program to a cgroup, etc.\n"
  },
  {
    "path": "docs/src/user/libcontainer.md",
    "content": "# libcontainer\n\nThis crate provides functionality for creating and managing containers. Youki itself uses this crate to manage and control the containers.\n\nThis exposes several modules, each dealing with a specific aspect of working with containers.\n\n- `apparmor` : functions that deal with apparmor, which is a Linux Kernel security module to control program capabilities with per program profiles.\n\n- `capabilities` : this has functions related to setting and resetting specific capabilities, as well as to drop extra privileges from container process.\n\n- `config` : this exposes `YoukiConfig` struct, which contains a subset of the data in the `config.json`. This is the subset that is needed when starting or managing containers after creation, and rather than parsing and passing around whole `config.json`, the smaller `YoukiConfig` is passed, which is comparatively faster.\n\n- `container` : This is the core of the container module, and contains sub-modules and structs that deal with the container lifecycle including creating, starting, stopping and deleting containers.\n\n- `hooks` : exposes function `run_hooks`, which is used to run various container lifecycle hooks as specified in oci-spec.\n\n- `namespaces` : exposes `Namespaces` struct, which deals with applying namespaces to a container process.\n\n- `notify_socket` : this contains `NotifyListener` struct, which is used internally to communicate between the main youki process and the forked container processes.\n\n- `process` : a module which exposes functions related to forking the process, setting up the namespaces and starting the container process with correct namespaces.\n\n- `rootfs` : this contains modules which deal with rootfs, which is minimal filesystem that is provided to the container.\n\n- `user_ns` : this deals with running containers in with new user namespace, usually rootless containers will use this, that is running containers without needing root permissions.\n\n- `seccomp` : this deals with setting up seccomp for container process. It uses libseccomp crate in order to do that.\n\n- `signal` : this provides simple wrappers for unix signal, so that parsing them from their names or signal numbers is easier.\n\n- `syscall` : this provides a trait `Syscall`, which is used to abstract over several functionalities which need to call libc functions. This allows the other parts of library to use those functions without having to deal with implementation details.\n\n- `tty` : this deals with setting up the tty for the container process.\n\n- `utils` : provides various utility functions, such as `parse_env` to parse the env variables, `get_cgroups_path`, `create_dir_all_with_mode` etc.\n"
  },
  {
    "path": "docs/src/user/liboci_cli.md",
    "content": "# liboci-cli\n\nThis module provides the structs for command line arguments for OCI container runtimes as specified in the OCI Runtime Command Line Interface. The exposed structures derive `clap::Parser`, so that they can be directly used for parsing oci-commandline arguments.\n\n### Implemented subcommands\n\n|  Command   | liboci-cli | CLI Specification | runc | crun | youki |\n| :--------: | :--------: | :---------------: | :--: | :--: | :---: |\n|   create   |     ✅     |        ✅         |  ✅  |  ✅  |  ✅   |\n|   start    |     ✅     |        ✅         |  ✅  |  ✅  |  ✅   |\n|   state    |     ✅     |        ✅         |  ✅  |  ✅  |  ✅   |\n|    kill    |     ✅     |        ✅         |  ✅  |  ✅  |  ✅   |\n|   delete   |     ✅     |        ✅         |  ✅  |  ✅  |  ✅   |\n| checkpoint |            |                   |  ✅  |  ✅  |       |\n|   events   |     ✅     |                   |  ✅  |      |  ✅   |\n|    exec    |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|    list    |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|   pause    |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|     ps     |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|  restore   |            |                   |  ✅  |  ✅  |       |\n|   resume   |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|    run     |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|    spec    |     ✅     |                   |  ✅  |  ✅  |  ✅   |\n|   update   |            |                   |  ✅  |  ✅  |       |\n"
  },
  {
    "path": "docs/src/user/libseccomp.md",
    "content": "# libseccomp\n\nThis crate provides Rust FFI bindings to [libseccomp](https://github.com/seccomp/libseccomp). This is adapted from code generated using rust-bindgen from a C header file. This also manually fixes some of the issues that occur as rust-bindgen has some issues when dealing with C function macros.\n"
  },
  {
    "path": "docs/src/user/webassembly.md",
    "content": "# Webassembly\n\nThere are 3 things you need to do to run a WebAssembly module with youki.\n\n1. Build youki with wasm-wasmer feature flag enabled\n2. Build a container image with the WebAssembly module\n3. Run the container with youki\n\n## Build youki with `wasm-wasmedge`, `wasm-wasmer`, or `wasm-wasmtime` feature flag enabled\n\n- Run `build.sh` with `-f wasm-wasmedge` option.\n\n    ```bash\n    ./scripts/build.sh -o . -r -f wasm-wasmedge\n    ```\n\n- Run `build.sh` with `-f wasm-wasmer` option.\n\n    ```bash\n    ./scripts/build.sh -o . -r -f wasm-wasmer\n    ```\n\n- Run `build.sh` with `-f wasm-wasmtime` option.\n\n    ```bash\n    ./scripts/build.sh -o . -r -f wasm-wasmtime\n    ```\n\n## Build a container image with the WebAssembly module\n\nIf you want to run a webassembly module with youki, your config.json has to include either **runc.oci.handler** or **module.wasm.image/variant=compat\"**.\n\nIt also needs to specify a valid .wasm (webassembly binary) or .wat (webassembly test) module as entrypoint for the container. If a wat module is specified it will be compiled to a wasm module by youki before it is executed. The module also needs to be available in the root filesystem of the container obviously.\n\n```json\n\"ociVersion\": \"1.0.2-dev\",\n\"annotations\": {\n    \"run.oci.handler\": \"wasm\"\n},\n\"process\": {\n    \"args\": [\n        \"hello.wasm\",\n        \"hello\",\n        \"world\"\n    ],\n...\n}\n...\n```\n\n### Compile a sample wasm module\n\nA simple wasm module can be created by running\n\n```console\nrustup target add wasm32-wasi\ncargo new wasm-module --bin\ncd ./wasm-module\nvi src/main.rs\n```\n\n```rust\nfn main() {\n    println!(\"Printing args\");\n    for arg in std::env::args().skip(1) {\n        println!(\"{}\", arg);\n    }\n\n    println!(\"Printing envs\");\n    for envs in std::env::vars() {\n        println!(\"{:?}\", envs);\n    }  \n}\n```\n\nThen compile the program to WASI.\n\n```console\ncargo build --target wasm32-wasi\n```\n\n### Build a container image with the module\n\nCreate a Dockerfile.\n\n```console\nvi Dockerfile\n```\n\n```Dockerfile\nFROM scratch\nCOPY target/wasm32-wasi/debug/wasm-module.wasm /\nENTRYPOINT [\"wasm-module.wasm\"]\n```\n\nThen build a container image with `module.wasm.image/variant=compat` annotation. [^1]\n\n```console\nsudo buildah build --annotation \"module.wasm.image/variant=compat\" -t wasm-module .\n```\n\n## Run the wasm module with youki and podman\n\nRun podman with youki as runtime. [^1]\n\n```bash\nsudo podman --runtime /PATH/WHARE/YOU/BUILT/WITH/WASM-WASMER/youki run localhost/wasm-module 1 2 3\n```\n\n[^1]: You might need `sudo` because of [#719](https://github.com/youki-dev/youki/issues/719).\n"
  },
  {
    "path": "docs/src/youki.md",
    "content": "# Youki\n\n<p align=\"center\">\n  <img src=\"./assets/youki.png\" width=\"450\">\n</p>\n\nyouki is an implementation of the [OCI runtime-spec](https://github.com/opencontainers/runtime-spec) in Rust, similar to [runc](https://github.com/opencontainers/runc).\n\n## About the name\n\nyouki is pronounced as /joʊki/ or yoh-key.\nyouki is named after the Japanese word 'youki', which means 'a container'. In Japanese language, youki also means 'cheerful', 'merry', or 'hilarious'.\n\n## Motivation\n\nHere is why we are writing a new container runtime in Rust.\n\n- Rust is one of the best languages to implement the oci-runtime spec. Many very nice container tools are currently written in Go. However, the container runtime requires the use of system calls, which requires a bit of special handling when implemented in Go. This is too tricky (e.g. _namespaces(7)_, _fork(2)_); with Rust, it's not that tricky. And, unlike in C, Rust provides the benefit of memory safety. While Rust is not yet a major player in the container field, it has the potential to contribute a lot: something this project attempts to exemplify.\n- youki has the potential to be faster and use less memory than runc, and therefore work in environments with tight memory usage requirements. Here is a simple benchmark of a container from creation to deletion.\n\n  | Runtime |  Time (mean ± σ)   |  Range (min … max)  |\n  | :-----: | :----------------: | :-----------------: |\n  |  youki  | 198.4 ms ± 52.1 ms | 97.2 ms … 296.1 ms  |\n  |  runc   | 352.3 ms ± 53.3 ms | 248.3 ms … 772.2 ms |\n  |  crun   | 153.5 ms ± 21.6 ms | 80.9 ms … 196.6 ms  |\n\n  <details>\n  <summary>Details about the benchmark</summary>\n\n  - A command used for the benchmark\n    ```console\n    $ hyperfine --prepare 'sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' --warmup 10 --min-runs 100 'sudo ./youki create -b tutorial a && sudo ./youki start a && sudo ./youki delete -f a'\n    ```\n  - Environment\n   `console $ ./youki info Version 0.0.1 Kernel-Release 5.11.0-41-generic Kernel-Version #45-Ubuntu SMP Fri Nov 5 11:37:01 UTC 2021 Architecture x86_64 Operating System Ubuntu 21.04 Cores 12 Total Memory 32025 Cgroup setup hybrid Cgroup mounts blkio /sys/fs/cgroup/blkio cpu /sys/fs/cgroup/cpu,cpuacct cpuacct /sys/fs/cgroup/cpu,cpuacct cpuset /sys/fs/cgroup/cpuset devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer hugetlb /sys/fs/cgroup/hugetlb memory /sys/fs/cgroup/memory net_cls /sys/fs/cgroup/net_cls,net_prio net_prio /sys/fs/cgroup/net_cls,net_prio perf_event /sys/fs/cgroup/perf_event pids /sys/fs/cgroup/pids unified /sys/fs/cgroup/unified CGroup v2 controllers cpu detached cpuset detached hugetlb detached io detached memory detached pids detached device attached Namespaces enabled mount enabled uts enabled ipc enabled user enabled pid enabled network enabled cgroup enabled $ ./youki --version youki version 0.0.1 commit: 0.0.1-0-0be33bf $ runc -v runc version 1.0.0-rc93 commit: 12644e614e25b05da6fd08a38ffa0cfe1903fdec spec: 1.0.2-dev go: go1.13.15 libseccomp: 2.5.1 $ crun --version crun version 0.19.1.45-4cc7 commit: 4cc7fa1124cce75dc26e12186d9cbeabded2b710 spec: 1.0.0 +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL `\n  </details>\n\n- The development of [railcar](https://github.com/oracle/railcar) has been suspended. This project was very nice but is no longer being developed. This project is inspired by it.\n- I have fun implementing this. In fact, this may be the most important.\n"
  },
  {
    "path": "experiment/seccomp/Cargo.toml",
    "content": "[package]\nname = \"seccomp\"\nversion = \"0.0.0\"\ndescription = \"Library for seccomp\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nautoexamples = true\nkeywords = [\"youki\", \"container\", \"seccomp\"]\n\n[dependencies]\nnix = { version = \"0.29.0\", features = [\n    \"ioctl\",\n    \"socket\",\n    \"sched\",\n    \"mount\",\n    \"dir\",\n] }\nthiserror = \"1.0.57\"\nprctl = \"1.0.0\"\nanyhow = \"1.0\"\ntokio = { version = \"1\", features = [\"full\"] }\nsyscall-numbers = \"3.1.1\"\nsyscalls = { version = \"0.6.18\", features = [\"std\", \"serde\", \"aarch64\", \"x86_64\"]}"
  },
  {
    "path": "experiment/seccomp/README.md",
    "content": "This is an experimental project in order to get away from libseccomp.\nRef: https://github.com/youki-dev/youki/issues/2724\n\n```console\n$ cargo run\n```\n"
  },
  {
    "path": "experiment/seccomp/rust-toolchain.toml",
    "content": "[toolchain]\nprofile=\"default\"\nchannel=\"1.77.1\"\n"
  },
  {
    "path": "experiment/seccomp/src/instruction/arch.rs",
    "content": "use crate::instruction::Instruction;\nuse crate::instruction::*;\n\n#[derive(PartialEq, Debug)]\npub enum Arch {\n    X86,AArch64\n}\n\npub fn gen_validate(arc: &Arch) -> Vec<Instruction> {\n    let arch = match arc {\n        Arch::X86 => AUDIT_ARCH_X86_64,\n        Arch::AArch64 => AUDIT_ARCH_AARCH64\n    };\n\n    vec![\n        Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32),\n        Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, arch),\n        Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),\n    ]\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_gen_validate_x86() {\n        let bpf_prog = gen_validate(&Arch::X86);\n        assert_eq!(bpf_prog[0], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32));\n        assert_eq!(bpf_prog[1], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_X86_64));\n        assert_eq!(bpf_prog[2], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));\n    }\n\n    #[test]\n    fn test_gen_validate_aarch64() {\n        let bpf_prog = gen_validate(&Arch::AArch64);\n        assert_eq!(bpf_prog[0], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32));\n        assert_eq!(bpf_prog[1], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_AARCH64));\n        assert_eq!(bpf_prog[2], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));\n    }\n}"
  },
  {
    "path": "experiment/seccomp/src/instruction/consts.rs",
    "content": "use std::{mem::offset_of, os::raw::c_int};\n\n// BPF Instruction classes.\n// See /usr/include/linux/bpf_common.h .\n// Load operation.\npub const BPF_LD: u16 = 0x00;\n// ALU operation.\npub const BPF_ALU: u16 = 0x04;\n// Jump operation.\npub const BPF_JMP: u16 = 0x05;\n// Return operation.\npub const BPF_RET: u16 = 0x06;\n\n// BPF ld/ldx fields.\n// See /usr/include/linux/bpf_common.h .\n// Operand size is a word.\npub const BPF_W: u16 = 0x00;\n// Load from data area (where `seccomp_data` is).\npub const BPF_ABS: u16 = 0x20;\n\n// BPF alu fields.\n// See /usr/include/linux/bpf_common.h .\npub const BPF_AND: u16 = 0x50;\n\n// BPF jmp fields.\n// See /usr/include/linux/bpf_common.h .\n// Unconditional jump.\npub const BPF_JA: u16 = 0x00;\n// Jump with comparisons.\npub const BPF_JEQ: u16 = 0x10;\npub const BPF_JGT: u16 = 0x20;\npub const BPF_JGE: u16 = 0x30;\n// Test against the value in the K register.\npub const BPF_K: u16 = 0x00;\n\n// Return codes for BPF programs.\n// See /usr/include/linux/seccomp.h .\npub const SECCOMP_RET_ALLOW: u32 = 0x7fff_0000;\npub const SECCOMP_RET_ERRNO: u32 = 0x0005_0000;\npub const SECCOMP_RET_KILL_THREAD: u32 = 0x0000_0000;\npub const SECCOMP_RET_KILL_PROCESS: u32 = 0x8000_0000;\npub const SECCOMP_RET_LOG: u32 = 0x7ffc_0000;\npub const SECCOMP_RET_TRACE: u32 = 0x7ff0_0000;\npub const SECCOMP_RET_TRAP: u32 = 0x0003_0000;\npub const SECCOMP_RET_MASK: u32 = 0x0000_ffff;\npub const SECCOMP_RET_USER_NOTIF: u32 = 0x7fc00000;\n\n// Architecture identifiers.\n// See /usr/include/linux/audit.h .\npub const AUDIT_ARCH_X86_64: u32 = 62 | 0x8000_0000 | 0x4000_0000;\npub const AUDIT_ARCH_AARCH64: u32 = 183 | 0x8000_0000 | 0x4000_0000;\n\n// ```c\n// struct seccomp_data {\n//     int nr;\n//     __u32 arch;\n//     __u64 instruction_pointer;\n//     __u64 args[6];\n// };\n// ```\n\n#[repr(C)]\nstruct SeccompData {\n    nr: c_int,\n    arch: u32,\n    instruction_pointer: u64,\n    args: [u64; 6],\n}\n\npub const fn seccomp_data_arch_offset() -> u8 {\n    offset_of!(SeccompData, arch) as u8\n}\n\npub const fn seccomp_data_arg_size() -> u8 {\n    8\n}\n\npub const fn seccomp_data_args_offset() -> u8 {\n    offset_of!(SeccompData, args) as u8\n}\n\npub const SECCOMP_IOC_MAGIC: u8 = b'!';\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_seccomp_data_arch_offset() {\n        if cfg!(target_arch = \"x86_64\") {\n            assert_eq!(seccomp_data_arch_offset(), 4);\n        }\n    }\n\n    #[test]\n    fn test_seccomp_data_arg_size_offset() {\n        if cfg!(target_arch = \"x86_64\") {\n            assert_eq!(seccomp_data_arg_size(), 8);\n        }\n    }\n\n    #[test]\n    fn test_seccomp_data_args_offset() {\n        if cfg!(target_arch = \"x86_64\") {\n            assert_eq!(seccomp_data_args_offset(), 16);\n        }\n    }\n}\n"
  },
  {
    "path": "experiment/seccomp/src/instruction/inst.rs",
    "content": "use std::os::raw::{c_uchar, c_uint, c_ushort};\n\n// https://docs.kernel.org/networking/filter.html#structure\n// <linux/filter.h>: sock_filter\n#[repr(C)]\n#[derive(Clone, Debug, PartialEq, Eq)]\npub struct Instruction {\n    pub code: c_ushort,\n    pub offset_jump_true: c_uchar,\n    pub offset_jump_false: c_uchar,\n    pub multiuse_field: c_uint,\n}\n\nimpl Instruction {\n    fn new(\n        code: c_ushort,\n        jump_true: c_uchar,\n        jump_false: c_uchar,\n        multiuse_field: c_uint,\n    ) -> Self {\n        Instruction {\n            code,\n            offset_jump_true: jump_true,\n            offset_jump_false: jump_false,\n            multiuse_field,\n        }\n    }\n\n    pub fn jump(\n        code: c_ushort,\n        jump_true: c_uchar,\n        jump_false: c_uchar,\n        multiuse_field: c_uint,\n    ) -> Self {\n        Self::new(code, jump_true, jump_false, multiuse_field)\n    }\n\n    pub fn stmt(code: c_ushort, k: c_uint) -> Self {\n        Self::new(code, 0, 0, k)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::instruction::*;\n\n    #[test]\n    fn test_bpf_instructions() {\n        assert_eq!(\n            Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, 16),\n            Instruction {\n                code: 0x20,\n                offset_jump_true: 0,\n                offset_jump_false: 0,\n                multiuse_field: 16,\n            }\n        );\n        assert_eq!(\n            Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 2, 5),\n            Instruction {\n                code: 0x15,\n                offset_jump_true: 2,\n                offset_jump_false: 5,\n                multiuse_field: 10,\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "experiment/seccomp/src/instruction/mod.rs",
    "content": "mod arch;\nmod consts;\nmod inst;\n\npub use arch::{gen_validate, Arch};\npub use consts::*;\npub use inst::Instruction;\n"
  },
  {
    "path": "experiment/seccomp/src/lib.rs",
    "content": "pub mod instruction;\npub mod seccomp;\n"
  },
  {
    "path": "experiment/seccomp/src/main.rs",
    "content": "use seccomp::{\n    instruction::{*},\n    seccomp::{NotifyFd, Seccomp},\n};\n\nuse std::io::{IoSlice, IoSliceMut};\nuse std::os::fd::{IntoRawFd, OwnedFd};\nuse std::os::unix::io::{AsRawFd, FromRawFd, RawFd};\nuse std::slice;\n\nuse anyhow::Result;\nuse nix::{libc, sys::{\n    signal::Signal,\n    socket::{\n        self, ControlMessage, ControlMessageOwned, MsgFlags, SockFlag, SockType, UnixAddr,\n    },\n    stat::Mode,\n    wait::{self, WaitStatus},\n}, unistd::{close, mkdir}};\n\nuse syscall_numbers::x86_64;\nuse syscalls::syscall_args;\nuse seccomp::seccomp::{InstructionData, Rule};\n\nfn send_fd<F: AsRawFd>(sock: OwnedFd, fd: &F) -> nix::Result<()> {\n    let fd = fd.as_raw_fd();\n    let cmsgs = [ControlMessage::ScmRights(slice::from_ref(&fd))];\n\n    let iov = [IoSlice::new(b\"x\")];\n\n    socket::sendmsg::<()>(sock.into_raw_fd(), &iov, &cmsgs, MsgFlags::empty(), None)?;\n    Ok(())\n}\n\nfn recv_fd<F: FromRawFd>(sock: RawFd) -> nix::Result<Option<F>> {\n    let mut iov_buf = [];\n    let mut iov = [IoSliceMut::new(&mut iov_buf)];\n\n    let mut cmsg_buf = nix::cmsg_space!(RawFd);\n    let msg = socket::recvmsg::<UnixAddr>(sock, &mut iov, Some(&mut cmsg_buf), MsgFlags::empty())?;\n    match msg.cmsgs()?.next() {\n        Some(ControlMessageOwned::ScmRights(fds)) if !fds.is_empty() => {\n            let fd = unsafe { F::from_raw_fd(fds[0]) };\n            Ok(Some(fd))\n        }\n        _ => Ok(None),\n    }\n}\n\nasync fn handle_notifications(notify_fd: NotifyFd) -> nix::Result<()> {\n    loop {\n        println!(\"Waiting on next\");\n        let req = notify_fd.recv()?.notif;\n        let syscall_name = x86_64::sys_call_name(req.data.nr.into());\n        println!(\n            \"Got notification: id={}, pid={}, nr={:?}\",\n            req.id, req.pid, syscall_name\n        );\n\n        notify_fd.success(0, req.id)?;\n    }\n}\n\nasync fn handle_signal(pid: nix::unistd::Pid) -> Result<()> {\n    let status = wait::waitpid(pid, None)?;\n    match status {\n        WaitStatus::Signaled(_, signal, _) => {\n            if signal == Signal::SIGSYS {\n                println!(\"Got SIGSYS, seccomp filter applied successfully!\");\n                return Ok(());\n            }\n            dbg!(signal);\n            Ok(())\n        }\n        wait_status => {\n            dbg!(\"Unexpected wait status: {:?}\", wait_status);\n            Err(anyhow::anyhow!(\"Unexpected wait status: {:?}\", wait_status))\n        }\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let (sock_for_child, sock_for_parent) = socket::socketpair(\n        socket::AddressFamily::Unix,\n        SockType::Stream,\n        None,\n        SockFlag::empty(),\n    )?;\n\n    let _ = prctl::set_no_new_privileges(true);\n    let inst_data = InstructionData{\n        arc: Arch::X86,\n        def_action: SECCOMP_RET_KILL_PROCESS,\n        rule_arr: vec![\n            Rule::new(\"getcwd\".parse()?, 0,  syscall_args!(),false),\n            Rule::new(\"write\".parse()?,1, syscall_args!(libc::STDERR_FILENO as usize), false),\n            Rule::new(\"mkdir\".parse()?,0, syscall_args!(), true)\n        ]\n    };\n    let seccomp = Seccomp {filters: Vec::from(inst_data)};\n\n    tokio::spawn(async move {\n        tokio::signal::ctrl_c()\n            .await\n            .expect(\"failed to listen for event\");\n        println!(\"Received ctrl-c event. Bye\");\n        std::process::exit(0);\n    });\n\n    match unsafe { nix::unistd::fork()? } {\n        nix::unistd::ForkResult::Child => {\n            std::panic::catch_unwind(|| {\n                let notify_fd = seccomp.apply().unwrap();\n                println!(\n                    \"Seccomp applied successfully with notify fd: {:?}\",\n                    notify_fd\n                );\n                send_fd(sock_for_child, &notify_fd).unwrap();\n\n                if let Err(e) = mkdir(\"/tmp/test\", Mode::S_IRUSR | Mode::S_IWUSR) {\n                    eprintln!(\"Failed to mkdir: {}\", e);\n                } else {\n                    println!(\"mkdir succeeded\");\n                }\n\n                eprintln!(\"stderr should be banned by seccomp\");\n            })\n            .unwrap();\n\n            std::process::exit(0);\n        }\n        nix::unistd::ForkResult::Parent { child } => {\n            let notify_fd = recv_fd::<NotifyFd>(sock_for_parent.as_raw_fd())?.unwrap();\n\n            close(sock_for_child.as_raw_fd())?;\n            close(sock_for_parent.as_raw_fd())?;\n\n            tokio::spawn(async move {\n                handle_signal(child).await.unwrap();\n            });\n\n            handle_notifications(notify_fd).await?;\n        }\n    };\n\n    Ok(())\n}\n"
  },
  {
    "path": "experiment/seccomp/src/seccomp.rs",
    "content": "use core::fmt;\nuse std::{\n    mem::MaybeUninit,\n    os::{\n        raw::{c_long, c_uint, c_ulong, c_ushort, c_void},\n        unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},\n    },\n};\n\nuse std::str::FromStr;\nuse nix::{\n    errno::Errno,\n    ioctl_readwrite, ioctl_write_ptr, libc,\n    libc::{SECCOMP_FILTER_FLAG_NEW_LISTENER, SECCOMP_SET_MODE_FILTER},\n    unistd,\n};\nuse syscalls::{SyscallArgs};\nuse crate::instruction::{*};\nuse crate::instruction::{Arch, Instruction, SECCOMP_IOC_MAGIC};\n\n#[derive(Debug, thiserror::Error)]\npub enum SeccompError {\n    #[error(\"Failed to apply seccomp rules: {0}\")]\n    Apply(String),\n}\n\npub struct Seccomp {\n    pub filters: Vec<Instruction>,\n}\n\nimpl Default for Seccomp {\n    fn default() -> Self {\n        Seccomp::new()\n    }\n}\n\nimpl Seccomp {\n    pub fn new() -> Self {\n        Seccomp {\n            filters: Vec::new(),\n        }\n    }\n\n    // apply applies the seccomp rules to the current process and return a fd for seccomp notify.\n    pub fn apply(&self) -> Result<NotifyFd, SeccompError> {\n        let mut prog = Filters {\n            len: self.filters.len() as _,\n            filter: self.filters.as_ptr(),\n        };\n\n        // TODO: Address the case where don't use seccomp notify.\n        let notify_fd = unsafe {\n            seccomp(\n                SECCOMP_SET_MODE_FILTER,\n                SECCOMP_FILTER_FLAG_NEW_LISTENER,\n                &mut prog as *mut _ as *mut c_void,\n            )\n        };\n\n        Errno::result(notify_fd).map_err(|e| SeccompError::Apply(e.to_string()))?;\n        Ok(unsafe { NotifyFd::from_raw_fd(notify_fd as RawFd) })\n    }\n}\n\n#[derive(Debug)]\npub struct NotifyFd {\n    fd: RawFd,\n}\n\nimpl Drop for NotifyFd {\n    fn drop(&mut self) {\n        unistd::close(self.fd).unwrap()\n    }\n}\n\nimpl FromRawFd for NotifyFd {\n    unsafe fn from_raw_fd(fd: RawFd) -> Self {\n        NotifyFd { fd }\n    }\n}\n\nimpl IntoRawFd for NotifyFd {\n    fn into_raw_fd(self) -> RawFd {\n        let NotifyFd { fd } = self;\n        fd\n    }\n}\n\nimpl AsRawFd for NotifyFd {\n    fn as_raw_fd(&self) -> RawFd {\n        self.fd\n    }\n}\n\nimpl NotifyFd {\n    pub fn success(&self, v: i64, notify_id: u64) -> nix::Result<()> {\n        let mut resp = SeccompNotifResp {\n            id: notify_id,\n            val: v,\n            error: 0,\n            flags: 0,\n        };\n\n        unsafe { seccomp_notif_ioctl_send(self.fd, &mut resp as *mut _)? };\n\n        Ok(())\n    }\n}\n\n// TODO: Rename\n#[repr(C)]\n#[derive(Debug)]\npub struct SeccompData {\n    pub nr: libc::c_int,\n    pub arch: u32,\n    pub instruction_pointer: u64,\n    pub args: [u64; 6],\n}\n\n#[repr(C)]\n#[derive(Debug)]\npub struct SeccompNotif {\n    pub id: u64,\n    pub pid: u32,\n    pub flags: u32,\n    pub data: SeccompData,\n}\n\n#[repr(C)]\n#[derive(Debug)]\npub struct SeccompNotifResp {\n    pub id: u64,\n    pub val: i64,\n    pub error: i32,\n    pub flags: u32,\n}\n\n#[repr(C)]\n#[derive(Debug)]\npub struct SeccompNotifSizes {\n    pub seccomp_notif: u16,\n    pub seccomp_notif_resp: u16,\n    pub seccomp_data: u16,\n}\n\n#[repr(C)]\n#[derive(Debug)]\npub struct SeccompNotifAddfd {\n    pub id: u64,\n    pub flags: u32,\n    pub srcfd: u32,\n    pub newfd: u32,\n    pub newfd_flags: u32,\n}\n\nioctl_readwrite!(seccomp_notif_ioctl_recv, SECCOMP_IOC_MAGIC, 0, SeccompNotif);\nioctl_readwrite!(\n    seccomp_notif_ioctl_send,\n    SECCOMP_IOC_MAGIC,\n    1,\n    SeccompNotifResp\n);\nioctl_write_ptr!(seccomp_notif_ioctl_id_valid, SECCOMP_IOC_MAGIC, 2, u64);\nioctl_write_ptr!(\n    seccomp_notif_ioctl_addfd,\n    SECCOMP_IOC_MAGIC,\n    3,\n    SeccompNotifAddfd\n);\n\npub struct Notification<'f> {\n    pub notif: SeccompNotif,\n    pub fd: &'f NotifyFd,\n}\n\nimpl<'f> fmt::Debug for Notification<'f> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        fmt::Debug::fmt(&self.notif, f)\n    }\n}\n\nimpl NotifyFd {\n    pub fn recv(&self) -> nix::Result<Notification> {\n        let mut res = MaybeUninit::zeroed();\n        let notif = unsafe {\n            seccomp_notif_ioctl_recv(self.fd, res.as_mut_ptr())?;\n            res.assume_init()\n        };\n\n        Ok(Notification { notif, fd: self })\n    }\n}\n\nunsafe fn seccomp(op: c_uint, flags: c_ulong, args: *mut c_void) -> c_long {\n    libc::syscall(libc::SYS_seccomp, op, flags, args)\n}\n\n#[repr(C)]\nstruct Filters {\n    pub len: c_ushort,\n    pub filter: *const Instruction,\n}\n\nfn get_syscall_number(arc: &Arch, name: &str) -> Option<u64> {\n    match arc {\n        Arch::X86 => {\n            match syscalls::x86_64::Sysno::from_str(name) {\n                Ok(syscall) => Some(syscall as u64),\n                Err(_) => None,\n            }\n        },\n        Arch::AArch64 => {\n            match syscalls::aarch64::Sysno::from_str(name) {\n                Ok(syscall) => Some(syscall as u64),\n                Err(_) => None,\n            }\n        }\n    }\n}\n\n#[derive(Debug)]\npub struct InstructionData {\n    pub arc: Arch,\n    pub def_action: u32,\n    pub rule_arr: Vec<Rule>\n}\n\nimpl From<InstructionData> for Vec<Instruction> {\n    fn from(inst_data: InstructionData) -> Self {\n        let mut bpf_prog = gen_validate(&inst_data.arc);\n\n        for rule in &inst_data.rule_arr {\n            bpf_prog.append(&mut Rule::to_instruction(&inst_data.arc, inst_data.def_action, rule));\n        }\n\n        bpf_prog.append(&mut vec![Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)]);\n        bpf_prog\n    }\n}\n\n#[derive(Debug)]\npub struct Rule {\n    pub syscall: String,\n    pub arg_cnt: u8,\n    pub args: SyscallArgs,\n    pub is_notify: bool\n}\n\nimpl Rule {\n    pub fn new(syscall: String, arg_cnt: u8, args: SyscallArgs, is_notify: bool) -> Self {\n        Self {\n            syscall,\n            arg_cnt,\n            args,\n            is_notify,\n        }\n    }\n\n    pub fn to_instruction(arch: &Arch, action: u32, rule: &Rule) -> Vec<Instruction> {\n        let mut bpf_prog = gen_validate(arch);\n        bpf_prog.append(&mut vec![Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, 0)]);\n        bpf_prog.append(&mut vec![Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 1,\n                                                    get_syscall_number(arch, &rule.syscall).unwrap() as c_uint)]);\n        if rule.arg_cnt != 0 {\n            bpf_prog.append(&mut vec![Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_args_offset().into())]);\n            bpf_prog.append(&mut vec![Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, rule.args.arg0 as c_uint)]);\n        }\n\n        if rule.is_notify {\n            bpf_prog.append(&mut vec![Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF)]);\n        } else {\n            bpf_prog.append(&mut vec![Instruction::stmt(BPF_RET | BPF_K, action)]);\n        }\n        bpf_prog\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use syscalls::syscall_args;\n    use super::*;\n\n    #[test]\n    fn test_get_syscall_number_x86() {\n        let sys_num = get_syscall_number(&Arch::X86, \"read\");\n        assert_eq!(sys_num.unwrap(), 0);\n    }\n\n    #[test]\n    fn test_get_syscall_number_aarch64() {\n        let sys_num = get_syscall_number(&Arch::AArch64, \"read\");\n        assert_eq!(sys_num.unwrap(), 63);\n    }\n\n    #[test]\n    fn test_to_instruction_x86() {\n        let rule = Rule::new(\"getcwd\".parse().unwrap(), 0, syscall_args!(), false);\n        let inst = Rule::to_instruction(&Arch::X86, SECCOMP_RET_KILL_PROCESS, &rule);\n        let bpf_prog = gen_validate(&Arch::X86);\n        assert_eq!(inst[0], bpf_prog[0]);\n        assert_eq!(inst[1], bpf_prog[1]);\n        assert_eq!(inst[2], bpf_prog[2]);\n        assert_eq!(inst[3], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, 0));\n        assert_eq!(inst[4], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 1,\n                                              get_syscall_number(&Arch::X86, \"getcwd\").unwrap() as c_uint));\n        assert_eq!(inst[5], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));\n    }\n\n    #[test]\n    fn test_to_instruction_aarch64() {\n        let rule = Rule::new(\"getcwd\".parse().unwrap(), 0, syscall_args!(), false);\n        let inst = Rule::to_instruction(&Arch::AArch64, SECCOMP_RET_KILL_PROCESS, &rule);\n        let bpf_prog = gen_validate(&Arch::AArch64);\n        assert_eq!(inst[0], bpf_prog[0]);\n        assert_eq!(inst[1], bpf_prog[1]);\n        assert_eq!(inst[2], bpf_prog[2]);\n        assert_eq!(inst[3], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, 0));\n        assert_eq!(inst[4], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 0, 1,\n                                              get_syscall_number(&Arch::AArch64, \"getcwd\").unwrap() as c_uint));\n        assert_eq!(inst[5], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));\n    }\n}"
  },
  {
    "path": "experiment/selinux/Cargo.toml",
    "content": "[package]\nname = \"selinux\"\nversion = \"0.1.0\"\ndescription = \"Library for selinux\"\nlicense = \"Apache-2.0\"\nrepository = \"https://github.com/youki-dev/youki\"\nhomepage = \"https://youki-dev.github.io/youki/\"\nreadme = \"README.md\"\nauthors = [\"youki team\"]\nedition = \"2024\"\nautoexamples = true\nkeywords = [\"youki\", \"container\", \"selinux\"]\n\n[dependencies]\nnix = { version = \"0.29.0\", features = [\"process\", \"fs\", \"socket\"] }\nrustix = { version = \"0.38.34\", features = [\"fs\"] }\ntempfile = \"3.17.1\"\nthiserror = \"1.0.61\"\n"
  },
  {
    "path": "experiment/selinux/README.md",
    "content": "# SELinux for Youki\n\nThis is an experimental project to create a SELinux library in Rust.\nRef: https://github.com/youki-dev/youki/issues/2718.  \nReimplementation of [opencontainers/selinux](https://github.com/opencontainers/selinux) in Rust.  \n\n## Requirements\n\n- [Lima](https://github.com/lima-vm/lima)\n- QEMU\n- Rust and Cargo\n\n## Development Environment\n\n### Setup with Lima\n\n```console\n# Start the VM with default settings (non-interactive mode)\n$ ./lima-setup.sh\n\n# For interactive mode (when not running in CI)\n$ ./lima-setup.sh --interactive\n\n# See all available options\n$ ./lima-setup.sh --help\n```\n\n### Running the Project\n\nOnce the VM is set up:\n\n```console\n# Inside the VM, run tests\n$ ./lima-run.sh cargo test\n\n# Inside the VM, run the application\n$ ./lima-run.sh cargo run\n\n# Connect to the VM\n$ limactl shell --workdir /workdir/youki/experiment/shared youki-selinux\n\n```\n\n### Cleaning Up\n\nWhen finished with development:\n\n```console\n# Remove the Lima VM\n$ ./lima-setup.sh --cleanup\n```\n"
  },
  {
    "path": "experiment/selinux/lima-run.sh",
    "content": "#!/bin/bash\n\nset -eu -o pipefail\n\nlimactl shell --workdir /workdir/youki/experiment/selinux youki-selinux \"$@\"\n"
  },
  {
    "path": "experiment/selinux/lima-setup.sh",
    "content": "#!/bin/bash\n#\n# Lima environment setup script for SELinux development with Fedora\n# This script creates and starts a Lima VM for SELinux development\n\nset -eu -o pipefail\n\nSCRIPT_NAME=$(basename \"$0\")\nTEMP_DIR=\"\"\n\nCPUS=2\nMEMORY=\"2GiB\"\nVM_NAME=\"youki-selinux\"\nFORCE_RECREATE=0\nCLEANUP=0\nNON_INTERACTIVE=true\n\n# Logging functions\nlog_info() {\n  echo -e \"\\033[0;32m[INFO]\\033[0m $*\"\n}\n\nlog_warn() {\n  echo -e \"\\033[0;33m[WARN]\\033[0m $*\" >&2\n}\n\nlog_error() {\n  echo -e \"\\033[0;31m[ERROR]\\033[0m $*\" >&2\n}\n\n# Cleanup function\ncleanup() {\n  local exit_code=$?\n  if [[ -n \"${TEMP_DIR:-}\" && -d \"${TEMP_DIR}\" ]]; then\n    log_info \"Cleaning up temporary directory ${TEMP_DIR}...\"\n    rm -rf \"${TEMP_DIR}\"\n  fi\n  exit \"$exit_code\"\n}\ntrap cleanup EXIT\n\n# Display usage information\nusage() {\n  cat <<USAGE_EOF\nUsage: ${SCRIPT_NAME} [options]\n\nOptions:\n  -h, --help          Show this help message\n  -c, --cpus NUMBER   Set CPU cores (default: ${CPUS})\n  -m, --memory SIZE   Set memory size (default: ${MEMORY})\n  -n, --name NAME     Set VM name (default: ${VM_NAME})\n  -f, --force         Force recreate VM if it exists\n  -i, --interactive   Enable interactive mode (default: non-interactive)\n  --cleanup           Stop and remove the VM\nUSAGE_EOF\n  exit \"${1:-0}\"\n}\n\n# Parse command line arguments\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n    -h|--help)\n      usage 0\n      ;;\n    -c|--cpus)\n      if [[ -z \"$2\" || \"$2\" =~ ^- ]]; then log_error \"--cpus requires a number\"; usage 1; fi\n      CPUS=\"$2\"\n      shift 2\n      ;;\n    -m|--memory)\n      if [[ -z \"$2\" || \"$2\" =~ ^- ]]; then log_error \"--memory requires a size (e.g. 2GiB)\"; usage 1; fi\n      MEMORY=\"$2\"\n      shift 2\n      ;;\n    -n|--name)\n      if [[ -z \"$2\" || \"$2\" =~ ^- ]]; then log_error \"--name requires a value\"; usage 1; fi\n      VM_NAME=\"$2\"\n      shift 2\n      ;;\n    -f|--force)\n      FORCE_RECREATE=1\n      shift\n      ;;\n    -i|--interactive)\n      NON_INTERACTIVE=false\n      shift\n      ;;\n    --cleanup)\n      CLEANUP=1\n      shift\n      ;;\n    *)\n      log_error \"Unknown option: $1\"\n      usage 1\n      ;;\n  esac\ndone\n\nif [[ $CLEANUP -eq 1 ]]; then\n  log_info \"=== Cleaning up Youki SELinux (Fedora) Development Environment ===\"\n  if limactl list --json | grep -q \"\\\"name\\\":\\\"$VM_NAME\\\"\"; then\n    log_info \"Stopping and removing VM '$VM_NAME'...\"\n    limactl stop \"$VM_NAME\" --force || true\n    limactl delete --force \"$VM_NAME\"\n    log_info \"VM '$VM_NAME' has been removed.\"\n  else\n    log_warn \"VM '$VM_NAME' does not exist.\"\n  fi\n  exit 0\nfi\n\nlog_info \"=== Youki SELinux (Fedora) Development Environment Setup ===\"\nlog_info \"Setting up Lima VM for SELinux development with Fedora...\"\nlog_info \"Configuration:\"\nlog_info \"  - CPUs:   $CPUS\"\nlog_info \"  - Memory: $MEMORY\"\nlog_info \"  - Name:   $VM_NAME\"\nlog_info \"  - Mode:   $([ \"$NON_INTERACTIVE\" = true ] && echo \"non-interactive\" || echo \"interactive\")\"\nif [[ $FORCE_RECREATE -eq 1 ]]; then\n  log_info \"  - Force Recreate: yes\"\nfi\n\nCURRENT_SCRIPT_DIR=$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\nYOUKI_ROOT_DIR=$(cd \"$CURRENT_SCRIPT_DIR/../..\" && pwd)\nlog_info \"Youki root directory: $YOUKI_ROOT_DIR\"\nlog_info \"Script directory: $CURRENT_SCRIPT_DIR\"\n\nTEMP_DIR=$(mktemp -d)\nif [[ ! -d \"${TEMP_DIR}\" ]]; then\n  log_error \"Failed to create temporary directory\"\n  exit 1\nfi\n\nlog_info \"Generating Lima configuration for Fedora ($TEMP_DIR/lima-fedora.yaml)...\"\ncat > \"$TEMP_DIR/lima-fedora.yaml\" << 'LIMA_YAML'\n# Lima configuration for SELinux development environment with Fedora\n\n# Using official Fedora Cloud Base Images.\n# Replace with a specific version if needed, check https://getfedora.org/en/cloud/download/\n# Using Fedora 39 as Fedora 40 URL might not be stable yet\nimages:\n- location: https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-41-1.4.x86_64.qcow2\n  arch: x86_64\n  digest: sha256:6205ae0c524b4d1816dbd3573ce29b5c44ed26c9fbc874fbe48c41c89dd0bac2\n- location: https://download.fedoraproject.org/pub/fedora/linux/releases/41/Cloud/aarch64/images/Fedora-Cloud-Base-Generic-41-1.4.aarch64.qcow2\n  arch: aarch64\n  digest: sha256:085883b42c7e3b980e366a1fe006cd0ff15877f7e6e984426f3c6c67c7cc2faa\n- location: https://dl.fedoraproject.org/pub/alt/risc-v/release/41/Cloud/riscv64/images/Fedora-Cloud-Base-Generic-41.20250224-1026a2d0e311.riscv64.qcow2\n  arch: riscv64\n  digest: sha256:6a8272a858d7f1498f49ce362b34f0b9b959885f63285158947e045abfeece40\n\ncpus: __CPUS__\nmemory: \"__MEMORY__\"\n\nmounts:\n  - location: \"__YOUKI_ROOT_DIR__\"\n    mountPoint: \"/workdir/youki\"\n    writable: true\n  - location: \"~\"\n    writable: true\n  - location: \"__CURRENT_SCRIPT_DIR__\" # Mount the directory containing the script\n    mountPoint: \"/tmp/provision_scripts\" # Mount to a directory in VM\n    writable: false\n\nhostResolver:\n  hosts:\n    host.lima.internal: host.lima.internal\n\nprovision:\n  - mode: system\n    script: |\n      #!/bin/bash\n      set -eux\n      # Execute the mounted system provisioning script\n      SCRIPT_PATH=\"/tmp/provision_scripts/provision_system.sh\"\n      if [ -f \"${SCRIPT_PATH}\" ]; then\n        echo \"Executing ${SCRIPT_PATH}...\"\n        bash \"${SCRIPT_PATH}\"\n        echo \"Finished executing ${SCRIPT_PATH}.\"\n      else\n        echo \"ERROR - ${SCRIPT_PATH} not found!\"\n        exit 1\n      fi\n  - mode: user\n    script: |\n      #!/bin/bash\n      set -eux\n      USER_LOG_FILE=\"/tmp/user_provision.log\"\n      exec > >(tee -a \"${USER_LOG_FILE}\") 2>&1 # Log to file and console\n      export PATH=\"$HOME/.cargo/bin:$PATH\"\n\n      if ! command -v cargo &> /dev/null; then\n        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --default-toolchain stable\n        if [ -f \"$HOME/.cargo/env\" ]; then\n            source \"$HOME/.cargo/env\"\n        fi\n        if ! command -v cargo &> /dev/null; then\n            echo \"Cargo still not available after rustup install. Exiting.\"\n            exit 1\n        fi\n        echo \"Rust/Cargo installed successfully.\"\n        cargo --version\n      else\n        echo \"Rust/Cargo is already installed.\"\n        cargo --version\n        if [ -f \"$HOME/.cargo/env\" ]; then # Ensure sourced for current script if already present\n             source \"$HOME/.cargo/env\"\n        fi\n      fi\n      \n      echo \"Ensuring $HOME/.cargo/env is sourced in .bashrc for future logins...\"\n      if ! grep -q 'source \"$HOME/.cargo/env\"' \"$HOME/.bashrc\"; then\n        echo 'source \"$HOME/.cargo/env\"' >> \"$HOME/.bashrc\"\n      fi\nLIMA_YAML\n\nSED_EXPR=\"\"\nSED_EXPR+=\"s|__CPUS__|${CPUS}|g; \"\nSED_EXPR+=\"s|__MEMORY__|${MEMORY}|g; \"\nSED_EXPR+=\"s|__YOUKI_ROOT_DIR__|${YOUKI_ROOT_DIR}|g; \"\nSED_EXPR+=\"s|__CURRENT_SCRIPT_DIR__|${CURRENT_SCRIPT_DIR}|g; \"\n\nsed -i.bak \"${SED_EXPR}\" \"$TEMP_DIR/lima-fedora.yaml\"\n\n# Check if the instance already exists\nif limactl list --json | grep -q \"\\\"name\\\":\\\"$VM_NAME\\\"\"; then\n  if [[ $FORCE_RECREATE -eq 1 ]]; then\n    log_info \"VM '$VM_NAME' already exists. Stopping and removing...\"\n    limactl stop \"$VM_NAME\" --force || true\n    limactl delete --force \"$VM_NAME\"\n  else\n    log_info \"VM '$VM_NAME' already exists. Starting if not running...\"\n    if ! limactl list --json | grep -q \"\\\"name\\\":\\\"$VM_NAME\\\",\\\"status\\\":\\\"Running\\\"\"; then\n      limactl start \"$VM_NAME\"\n    fi\n    log_info \"SELinux (Fedora) dev environment ready: limactl shell $VM_NAME\"\n    exit 0\n  fi\nfi\n\nlog_info \"Creating and starting Lima VM '$VM_NAME'...\"\ndeclare -a CREATE_ARGS=(\"--name=$VM_NAME\")\nif [ \"$NON_INTERACTIVE\" = true ]; then\n  CREATE_ARGS+=(\"--tty=false\")\nfi\n\nlog_info \"Running: limactl create ${CREATE_ARGS[*]} \\\"$TEMP_DIR/lima-fedora.yaml\\\"\"\nif ! limactl create \"${CREATE_ARGS[@]}\" \"$TEMP_DIR/lima-fedora.yaml\"; then\n  log_error \"Failed to create Lima VM definition. Check logs.\"\n  # Even if create partially succeeded, try starting might reveal logs or state.\n  # But for robustness, let's exit if create fails clearly.\n  exit 1\nfi\n\n# Ensure the VM is started after creation\nlog_info \"Ensuring VM '$VM_NAME' is running...\"\nif ! limactl start \"$VM_NAME\"; then\n    log_error \"Failed to start Lima VM '$VM_NAME' after creation. Check logs with 'limactl logs $VM_NAME' or serial logs.\"\n    # It might be running despite the error, but build/test might fail. Exit for clarity.\n    exit 1\nfi\n\nlog_info \"SELinux (Fedora) development environment '$VM_NAME' is ready!\"\n"
  },
  {
    "path": "experiment/selinux/provision_system.sh",
    "content": "#!/bin/bash\n\nset -eux\n\nLOG_FILE=\"/tmp/system_provision.log\"\nexec > >(tee -a \"${LOG_FILE}\") 2>&1\n\nCRITICAL_DEV_PACKAGES=\"gcc gcc-c++ make libselinux-devel audit pkgconfig git\"\n\necho \"Ensuring critical development packages are installed: $CRITICAL_DEV_PACKAGES...\"\nfor pkg in $CRITICAL_DEV_PACKAGES; do\n    installed_by_rpm=false\n    if rpm -q \"$pkg\" >/dev/null 2>&1; then\n        installed_by_rpm=true\n    fi\n\n    command_exists=false\n    is_executable_pkg=false\n    cmd_to_check=$pkg\n\n    if [[ \"$pkg\" == \"gcc\" || \"$pkg\" == \"gcc-c++\" || \"$pkg\" == \"make\" || \"$pkg\" == \"git\" ]]; then\n        is_executable_pkg=true\n        if [[ \"$pkg\" == \"gcc-c++\" ]]; then cmd_to_check=\"g++\"; fi\n    fi\n    # pkgconfig provides pkg-config command\n    if [[ \"$pkg\" == \"pkgconfig\" ]]; then\n        is_executable_pkg=true\n        cmd_to_check=\"pkg-config\"\n    fi\n\n    if $is_executable_pkg; then\n        if command -v \"$cmd_to_check\" >/dev/null 2>&1; then\n            command_exists=true\n        fi\n    elif $installed_by_rpm; then # For non-executable devel libraries, rpm -q is enough\n        command_exists=true \n    fi\n\n    if $installed_by_rpm && $command_exists; then\n        echo \"Package $pkg (command: $cmd_to_check) - OK (already installed or verified).\"\n    else\n        echo \"Package $pkg (command: $cmd_to_check) - NOT FOUND or verification failed. Attempting dnf install...\"\n        if dnf install -y \"$pkg\"; then\n            echo \"dnf install -y $pkg SUCCEEDED.\"\n            if $is_executable_pkg; then\n                if ! command -v \"$cmd_to_check\" >/dev/null 2>&1; then\n                    echo \"CRITICAL - $cmd_to_check (for $pkg) still NOT FOUND after install. Exiting.\"\n                    exit 1\n                fi\n                echo \"Command $cmd_to_check (for $pkg) verified after install.\"\n            fi\n        else\n            echo \"dnf install -y $pkg FAILED. Exit code: $?. Exiting.\"\n            exit 1\n        fi\n    fi\ndone\n\necho \"Verifying SELinux status and configuration...\"\nsestatus\nif grep -q \"^SELINUX=disabled\" /etc/selinux/config; then\n    echo \"SELINUX is disabled in config. Changing to permissive.\"\n    sed -i 's/^SELINUX=disabled/SELINUX=permissive/' /etc/selinux/config\nelif ! grep -q \"^SELINUX=\" /etc/selinux/config; then\n    echo \"SELINUX line not found in config. Adding SELINUX=permissive.\"\n    echo \"SELINUX=permissive\" >> /etc/selinux/config\nelse\n    CURRENT_SELINUX_CONFIG=$(grep \"^SELINUX=\" /etc/selinux/config)\n    echo \"Current SELinux configuration in /etc/selinux/config: $CURRENT_SELINUX_CONFIG\"\nfi\necho \"Final SELinux configuration in /etc/selinux/config:\"\ngrep \"^SELINUX=\" /etc/selinux/config || echo \"PROV_SYS: SELINUX line not found\"\necho \"SELinux status and configuration verified.\"\n\necho \"Configuring passwordless sudo for the lima user...\"\nmkdir -p /etc/sudoers.d\necho \"%lima ALL=(ALL) NOPASSWD: ALL\" > /etc/sudoers.d/lima\nchmod 440 /etc/sudoers.d/lima\n"
  },
  {
    "path": "experiment/selinux/src/lib.rs",
    "content": "pub mod selinux;\npub mod selinux_label;\npub mod tools;\n\npub use selinux::SELinux;\n"
  },
  {
    "path": "experiment/selinux/src/main.rs",
    "content": "use selinux::selinux::*;\nuse selinux::selinux_label::*;\nuse std::env;\nuse std::fs::File;\n\nfn main() -> Result<(), SELinuxError> {\n    let mut selinux_instance: SELinux = SELinux::new();\n\n    if selinux_instance.get_enabled() {\n        println!(\"selinux is enabled\");\n    } else {\n        println!(\"selinux is not enabled\");\n\n        match selinux_instance.set_enforce_mode(SELinuxMode::PERMISSIVE) {\n            Ok(_) => println!(\"set selinux mode as permissive\"),\n            Err(e) => println!(\"{}\", e),\n        }\n    }\n    println!(\n        \"default enforce mode is: {}\",\n        selinux_instance.default_enforce_mode()\n    );\n    println!(\n        \"current enforce mode is: {}\",\n        selinux_instance.enforce_mode()\n    );\n\n    match selinux_instance.current_label() {\n        Ok(l) => println!(\"SELinux label of current process is: {}\", l),\n        Err(e) => println!(\"{}\", e),\n    }\n\n    // Create temporary file in a directory we're likely to have permissions for\n    let temp_dir = env::temp_dir();\n    let file_path = temp_dir.join(\"selinux_test_file.txt\");\n    let _file = match File::create(&file_path) {\n        Ok(file) => file,\n        Err(e) => {\n            println!(\"Warning: Could not create test file: {}\", e);\n            return Ok(());\n        }\n    };\n\n    println!(\"Created test file at: {}\", file_path.display());\n\n    // Try to set SELinux label but handle permission errors gracefully\n    let selinux_label =\n        SELinuxLabel::try_from(\"system_u:object_r:public_content_t:s0\".to_string())?;\n\n    match SELinux::set_file_label(&file_path, selinux_label) {\n        Ok(_) => {\n            // Only try to get the label if setting it succeeded\n            match SELinux::file_label(&file_path) {\n                Ok(label) => println!(\"File label is {}\", label),\n                Err(e) => println!(\"Could not get file label: {}\", e),\n            }\n        }\n        Err(e) => {\n            println!(\"Warning: Could not set SELinux label: {}\", e);\n            println!(\"This is expected if running without root privileges or if SELinux is not available\");\n        }\n    }\n\n    // Clean up the test file\n    if let Err(e) = std::fs::remove_file(&file_path) {\n        println!(\"Warning: Could not remove test file: {}\", e);\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "experiment/selinux/src/selinux.rs",
    "content": "use crate::selinux_label::SELinuxLabel;\nuse nix::errno::Errno;\nuse nix::sys::statfs;\nuse nix::unistd::gettid;\nuse std::collections::HashMap;\nuse std::convert::From;\nuse std::fmt;\nuse std::fs::{self, File, OpenOptions};\nuse std::io::{BufRead, BufReader, Read, Write};\nuse std::os::fd::{AsFd, AsRawFd};\nuse std::path::{Path, PathBuf};\nuse std::sync::atomic::{AtomicBool, Ordering};\n\n#[derive(Debug, Copy, Clone)]\npub enum SELinuxMode {\n    // ENFORCING constant to indicate SELinux is in enforcing mode\n    ENFORCING = 1,\n    // PERMISSIVE constant to indicate SELinux is in permissive mode\n    PERMISSIVE = 0,\n    // DISABLED constant to indicate SELinux is disabled\n    DISABLED = -1,\n}\n\nimpl From<i32> for SELinuxMode {\n    fn from(mode: i32) -> Self {\n        match mode {\n            1 => SELinuxMode::ENFORCING,\n            0 => SELinuxMode::PERMISSIVE,\n            -1 => SELinuxMode::DISABLED,\n            _ => SELinuxMode::DISABLED,\n        }\n    }\n}\n\nimpl From<&str> for SELinuxMode {\n    fn from(mode: &str) -> Self {\n        match mode {\n            \"enforcing\" => SELinuxMode::ENFORCING,\n            \"permissive\" => SELinuxMode::PERMISSIVE,\n            _ => SELinuxMode::DISABLED,\n        }\n    }\n}\n\nimpl fmt::Display for SELinuxMode {\n    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n        let s = match self {\n            SELinuxMode::ENFORCING => \"enforcing\",\n            SELinuxMode::PERMISSIVE => \"permissive\",\n            SELinuxMode::DISABLED => \"disabled\",\n        };\n        write!(f, \"{}\", s)\n    }\n}\n\npub(crate) const ERR_EMPTY_PATH: &str = \"empty path\";\nconst SELINUX_FS_MOUNT: &str = \"/sys/fs/selinux\";\nconst CONTEXT_FILE: &str = \"/usr/share/containers/selinux/contexts\";\nconst SELINUX_TYPE_TAG: &str = \"SELINUXTYPE\";\nconst SELINUX_TAG: &str = \"SELINUX\";\nconst SELINUX_DIR: &str = \"/etc/selinux/\";\nconst SELINUX_CONFIG: &str = \"config\";\n\n#[derive(Debug, thiserror::Error)]\npub enum SELinuxError {\n    #[error(\"Failed to set file label for SELinux: {0}\")]\n    SetFileLabel(String),\n    #[error(\"Failed to lset file label for SELinux: {0}\")]\n    LSetFileLabel(String),\n    #[error(\"Failed to get file label for SELinux: {0}\")]\n    FileLabel(String),\n    #[error(\"Failed to get lfile label for SELinux: {0}\")]\n    LFileLabel(String),\n    #[error(\"Failed to call is_proc_handle for SELinux: {0}\")]\n    IsProcHandle(String),\n    #[error(\"Failed to call read_con_fd for SELinux: {0}\")]\n    ReadConFd(String),\n    #[error(\"Failed to call read_con for SELinux: {0}\")]\n    ReadCon(String),\n    #[error(\"Failed to call write_con for SELinux: {0}\")]\n    WriteCon(String),\n    #[error(\"Failed to find the index for a given class: {0}\")]\n    ClassIndex(String),\n    #[error(\"Failed to call peer_label for SELinux: {0}\")]\n    PeerLabel(String),\n    #[error(\"Failed to call open_context_file for SELinux: {0}\")]\n    OpenContextFile(String),\n    #[error(\"Failed to set enforce mode of SELinux: {0}\")]\n    SetEnforceMode(String),\n    #[error(\"Failed to read config file of SELinux: {0}\")]\n    GetConfigKey(String),\n    #[error(\"Invalid format for SELinux label: {0}\")]\n    InvalidSELinuxLabel(String),\n}\n\npub struct SELinux {\n    // for attr_path()\n    have_thread_self: AtomicBool,\n    attr_path_init_done: AtomicBool,\n\n    // for selinuxfs\n    selinuxfs_init_done: AtomicBool,\n    selinuxfs: Option<PathBuf>,\n\n    // for policy_root()\n    policy_root_init_done: AtomicBool,\n    policy_root: Option<PathBuf>,\n\n    // for load_labels()\n    pub(crate) load_labels_init_done: AtomicBool,\n    pub(crate) labels: HashMap<String, SELinuxLabel>,\n\n    // for read config and get config key\n    read_config_init_done: AtomicBool,\n    configs: HashMap<String, String>,\n\n    pub(crate) read_only_file_label: Option<SELinuxLabel>,\n}\n\nimpl Default for SELinux {\n    fn default() -> Self {\n        SELinux::new()\n    }\n}\n\nimpl SELinux {\n    pub fn new() -> Self {\n        SELinux {\n            have_thread_self: AtomicBool::new(false),\n            attr_path_init_done: AtomicBool::new(false),\n\n            selinuxfs_init_done: AtomicBool::new(false),\n            selinuxfs: None,\n\n            policy_root_init_done: AtomicBool::new(false),\n            policy_root: None,\n\n            load_labels_init_done: AtomicBool::new(false),\n            labels: HashMap::new(),\n\n            read_config_init_done: AtomicBool::new(false),\n            configs: HashMap::new(),\n\n            read_only_file_label: None,\n        }\n    }\n\n    // This function returns policy_root.\n    // Directories under policy root has configuration files etc.\n    fn policy_root(&mut self) -> Option<&PathBuf> {\n        // Avoiding code conflicts and ensuring thread-safe execution once only.\n        if !self.policy_root_init_done.load(Ordering::SeqCst) {\n            let policy_root_path = Self::get_config_key(self, SELINUX_TYPE_TAG).unwrap_or_default();\n            self.policy_root = Some(PathBuf::from(policy_root_path));\n            self.policy_root_init_done.store(true, Ordering::SeqCst);\n        }\n        self.policy_root.as_ref()\n    }\n\n    // This function reads SELinux config file and returns the value with a specified key.\n    fn get_config_key(&mut self, target_key: &str) -> Result<String, SELinuxError> {\n        if !self.read_config_init_done.load(Ordering::SeqCst) {\n            let config_path = Path::new(SELINUX_DIR).join(SELINUX_CONFIG);\n            if let Ok(file) = File::open(config_path) {\n                let reader = BufReader::new(file);\n                for line in reader.lines().map_while(Result::ok) {\n                    if line.is_empty() {\n                        continue;\n                    }\n                    if (line.starts_with(';')) || (line.starts_with('#')) {\n                        continue;\n                    }\n                    let fields: Vec<&str> = line.splitn(2, '=').collect();\n                    if fields.len() < 2 {\n                        continue;\n                    }\n                    let key = fields[0].trim().to_string();\n                    let value = fields[1].trim().to_string();\n                    self.configs.insert(key, value);\n                }\n            }\n            self.read_config_init_done.store(true, Ordering::SeqCst);\n        }\n        self.configs\n            .get(target_key)\n            .cloned()\n            .filter(|s| !s.is_empty())\n            .ok_or(SELinuxError::GetConfigKey(format!(\n                \"can't find the target label in the config file: {}\",\n                target_key\n            )))\n    }\n\n    // get_enabled returns whether SELinux is enabled or not.\n    pub fn get_enabled(&mut self) -> bool {\n        match Self::get_selinux_mountpoint(self) {\n            // If there is no SELinux mountpoint, SELinux is not enabled.\n            None => false,\n            Some(_) => match Self::current_label(self) {\n                Ok(con) => {\n                    // Check whether label is \"kernel\" or not.\n                    if con.user != \"kernel\" {\n                        return true;\n                    }\n                    false\n                }\n                Err(_) => false,\n            },\n        }\n    }\n\n    // verify_selinux_fs_mount verifies if the specified mount point is\n    // properly mounted as a writable SELinux filesystem.\n    fn verify_selinux_fs_mount<P: AsRef<Path>>(mnt: P) -> bool {\n        let mnt = mnt.as_ref();\n        loop {\n            match statfs::statfs(mnt) {\n                Ok(stat) => {\n                    // In go-selinux, return false if it is not read-only,\n                    // but selinux code in SELinuxProject return true even though it is read-only.\n                    // https://github.com/SELinuxProject/selinux/blob/1f080ffd7ab24b0ad2b46f79db63d62c2ae2747c/libselinux/src/init.c#L44\n                    // Therefore, this function doesn't check whether it is read-only or not.\n\n                    // verify if the file is SELinux filesystem\n                    return stat.filesystem_type() == statfs::SELINUX_MAGIC;\n                }\n                // check again if there is an issue while calling statfs\n                Err(Errno::EAGAIN) | Err(Errno::EINTR) => continue,\n                Err(_) => return false,\n            }\n        }\n    }\n\n    // check_line_include_selinux_fs_mount_point returns a next selinuxfs mount point found,\n    // if there is one, or None in case of EOF or error.\n    fn check_line_include_selinux_fs_mount_point(line: &str) -> Option<PathBuf> {\n        if !line.contains(\" - selinuxfs \") {\n            return None;\n        }\n        // Need to return the path like /sys/fs/selinux\n        // example: 28 24 0:25 / /sys/fs/selinux rw,relatime - selinuxfs selinuxfs rw\n        let m_pos = 5;\n        let fields: Vec<&str> = line.splitn(m_pos + 1, ' ').collect();\n        if fields.len() < m_pos + 1 {\n            return None;\n        }\n        let mountpoint = fields[m_pos - 1].to_string();\n        Some(PathBuf::from(mountpoint))\n    }\n\n    // find_selinux_fs finds the SELinux filesystem mount point.\n    fn find_selinux_fs() -> Option<PathBuf> {\n        // fast path: check the default mount first\n        let selinux_fs_mount_path = PathBuf::from(SELINUX_FS_MOUNT);\n        if Self::verify_selinux_fs_mount(&selinux_fs_mount_path) {\n            return Some(selinux_fs_mount_path);\n        }\n\n        // check if selinuxfs is available before going the slow path\n        let fs = fs::read_to_string(\"/proc/filesystems\").unwrap_or_default();\n        if !fs.contains(\"\\tselinuxfs\\n\") {\n            return None;\n        }\n\n        // slow path: try to find among the mounts\n        match File::open(\"/proc/self/mountinfo\") {\n            Ok(file) => {\n                let reader = BufReader::new(file);\n                for line in reader.lines().map_while(Result::ok) {\n                    if let Some(mnt) = Self::check_line_include_selinux_fs_mount_point(&line) {\n                        if Self::verify_selinux_fs_mount(&mnt) {\n                            return Some(mnt);\n                        }\n                    }\n                }\n            }\n            Err(_) => return None,\n        }\n        None\n    }\n\n    // This function returns the path to the mountpoint of an selinuxfs\n    // filesystem or an empty string if no mountpoint is found. Selinuxfs is\n    // a proc-like pseudo-filesystem that exposes the SELinux policy API to\n    // processes. The existence of an seliuxfs mount is used to determine\n    // whether SELinux is currently enabled or not.\n    pub fn get_selinux_mountpoint(&mut self) -> Option<&PathBuf> {\n        // Avoiding code conflicts and ensuring thread-safe execution once only.\n        if !self.selinuxfs_init_done.load(Ordering::SeqCst) {\n            self.selinuxfs = Self::find_selinux_fs();\n            self.selinuxfs_init_done.store(true, Ordering::SeqCst);\n        }\n        self.selinuxfs.as_ref()\n    }\n\n    // classIndex returns the int index for an object class in the loaded policy, or an error.\n    // For example, if a class is \"file\" or \"dir\", return the corresponding index for selinux.\n    pub fn class_index(&mut self, class: &str) -> Result<i64, SELinuxError> {\n        let permpath = format!(\"class/{}/index\", class);\n        let mountpoint = Self::get_selinux_mountpoint(self)\n            .ok_or_else(|| SELinuxError::ClassIndex(\"SELinux mount point not found\".to_string()))?;\n        let indexpath = mountpoint.join(permpath);\n\n        match fs::read_to_string(indexpath) {\n            Ok(index_b) => match index_b.parse::<i64>() {\n                Ok(index) => Ok(index),\n                Err(e) => Err(SELinuxError::ClassIndex(e.to_string())),\n            },\n            Err(e) => Err(SELinuxError::ClassIndex(e.to_string())),\n        }\n    }\n\n    // This function attempts to open a selinux context file, and if it fails, it tries to open another file\n    // under policy root's directory.\n    pub(crate) fn open_context_file(&mut self) -> Result<File, SELinuxError> {\n        match File::open(CONTEXT_FILE) {\n            Ok(file) => Ok(file),\n            Err(_) => {\n                let policy_path = Self::policy_root(self).ok_or_else(|| {\n                    SELinuxError::OpenContextFile(\"can't get policy root\".to_string())\n                })?;\n                let context_on_policy_root = policy_path.join(\"contexts\").join(\"lxc_contexts\");\n                match File::open(context_on_policy_root) {\n                    Ok(file) => Ok(file),\n                    Err(e) => Err(SELinuxError::OpenContextFile(e.to_string())),\n                }\n            }\n        }\n    }\n\n    // This returns selinux enforce path by using selinux mountpoint.\n    // The enforce path dynamically changes SELinux mode at runtime,\n    // while the config file need OS to reboot after changing the config file.\n    fn selinux_enforce_path(&mut self) -> Option<PathBuf> {\n        let selinux_mountpoint = Self::get_selinux_mountpoint(self);\n        selinux_mountpoint.map(|m| m.join(\"enforce\"))\n    }\n\n    // enforce_mode returns the current SELinux mode Enforcing, Permissive, Disabled\n    pub fn enforce_mode(&mut self) -> SELinuxMode {\n        let mode = match Self::selinux_enforce_path(self) {\n            Some(enforce_path) => match fs::read_to_string(enforce_path) {\n                Ok(content) => content.trim().parse::<i32>().unwrap_or(-1),\n                Err(_) => -1,\n            },\n            None => -1,\n        };\n        SELinuxMode::from(mode)\n    }\n\n    // is_mls_enabled checks if MLS is enabled.\n    pub fn is_mls_enabled(&mut self) -> bool {\n        if let Some(mountpoint) = Self::get_selinux_mountpoint(self) {\n            let mls_path = Path::new(&mountpoint).join(\"mls\");\n            match fs::read(mls_path) {\n                Ok(enabled_b) => return enabled_b == vec![b'1'],\n                Err(_) => return false,\n            }\n        }\n        false\n    }\n\n    // This function updates the enforce mode of selinux.\n    // Disabled is not valid, since this needs to be set at boot time.\n    pub fn set_enforce_mode(&mut self, mode: SELinuxMode) -> Result<(), SELinuxError> {\n        let enforce_path = Self::selinux_enforce_path(self).ok_or_else(|| {\n            SELinuxError::SetEnforceMode(\"can't get selinux enforce path\".to_string())\n        })?;\n        fs::write(enforce_path, mode.to_string().as_bytes())\n            .map_err(|e| SELinuxError::SetEnforceMode(e.to_string()))\n    }\n\n    // This returns the systems default SELinux mode Enforcing, Permissive or Disabled.\n    // note this is just the default at boot time.\n    // enforce_mode function tells you the system current mode.\n    pub fn default_enforce_mode(&mut self) -> SELinuxMode {\n        SELinuxMode::from(\n            Self::get_config_key(self, SELINUX_TAG)\n                .unwrap_or_default()\n                .as_str(),\n        )\n    }\n\n    // write_con writes a specified value to a given file path, handling SELinux context.\n    pub fn write_con<P: AsRef<Path>>(\n        &mut self,\n        fpath: P,\n        val: &str,\n    ) -> Result<usize, SELinuxError> {\n        let path = fpath.as_ref();\n        if path.as_os_str().is_empty() {\n            return Err(SELinuxError::WriteCon(ERR_EMPTY_PATH.to_string()));\n        }\n        if val.is_empty() && !Self::get_enabled(self) {\n            return Err(SELinuxError::WriteCon(\"SELinux is not enabled\".to_string()));\n        }\n\n        let mut out = OpenOptions::new()\n            .write(true)\n            .create(false)\n            .open(fpath)\n            .map_err(|e| SELinuxError::WriteCon(format!(\"failed to open file: {}\", e)))?;\n\n        Self::is_proc_handle(&out)?;\n        match out.write(val.as_bytes()) {\n            Ok(u) => Ok(u),\n            Err(e) => Err(SELinuxError::WriteCon(format!(\n                \"failed to write in file: {}\",\n                e\n            ))),\n        }\n    }\n\n    // This function checks whether this file is on the procfs filesystem.\n    pub fn is_proc_handle(file: &File) -> Result<(), SELinuxError> {\n        loop {\n            match statfs::fstatfs(file.as_fd()) {\n                Ok(stat) if stat.filesystem_type() == statfs::PROC_SUPER_MAGIC => break,\n                Ok(_) => {\n                    return Err(SELinuxError::IsProcHandle(format!(\n                        \"file {} is not on procfs\",\n                        file.as_raw_fd()\n                    )));\n                }\n                Err(Errno::EINTR) => continue,\n                Err(err) => {\n                    return Err(SELinuxError::IsProcHandle(format!(\n                        \"fstatfs failed: {}\",\n                        err\n                    )))\n                }\n            }\n        }\n        Ok(())\n    }\n\n    // This function reads a given file descriptor into a string.\n    pub fn read_con_fd<F: AsFd + Read>(file: &mut F) -> Result<String, SELinuxError> {\n        let mut data = String::new();\n        file.read_to_string(&mut data)\n            .map_err(|e| SELinuxError::ReadConFd(e.to_string()))?;\n\n        // Remove null bytes on the end of a file.\n        let trimmed_data = data.trim_end_matches(char::from(0));\n        Ok(trimmed_data.to_string())\n    }\n\n    // read_con reads a label to a given file path, handling SELinux context.\n    pub fn read_con<P: AsRef<Path>>(fpath: P) -> Result<String, SELinuxError> {\n        let path = fpath.as_ref();\n        if path.as_os_str().is_empty() {\n            return Err(SELinuxError::ReadCon(ERR_EMPTY_PATH.to_string()));\n        }\n        let mut in_file = File::open(fpath)\n            .map_err(|e| SELinuxError::ReadCon(format!(\"failed to open file: {}\", e)))?;\n\n        Self::is_proc_handle(&in_file)?;\n        Self::read_con_fd(&mut in_file)\n    }\n\n    // attr_path determines the correct file path for accessing SELinux\n    // attributes of a process or thread in a Linux environment.\n    pub fn attr_path(&self, attr: &str) -> PathBuf {\n        // Linux >= 3.17 provides this\n        const THREAD_SELF_PREFIX: &str = \"/proc/thread-self/attr\";\n        // Avoiding code conflicts and ensuring thread-safe execution once only.\n        if !self.attr_path_init_done.load(Ordering::SeqCst) {\n            let path = PathBuf::from(THREAD_SELF_PREFIX);\n            let is_dir = path.is_dir();\n            self.have_thread_self.store(is_dir, Ordering::SeqCst);\n            self.attr_path_init_done.store(true, Ordering::SeqCst);\n        }\n        if self.have_thread_self.load(Ordering::SeqCst) {\n            return PathBuf::from(&format!(\"{}/{}\", THREAD_SELF_PREFIX, attr));\n        }\n\n        PathBuf::from(&format!(\"/proc/self/task/{}/attr/{}\", gettid(), attr))\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::selinux::*;\n    use std::fs::File;\n    use std::io::Write;\n    use std::path::Path;\n    use std::str;\n    use tempfile::NamedTempFile;\n\n    fn create_temp_file(content: &[u8], file_name: &str) {\n        let path = Path::new(file_name);\n        let mut file = File::create(path).expect(\"Failed to create file\");\n        file.write_all(content).expect(\"Failed to write to file\");\n        file.sync_all().expect(\"Failed to sync file\");\n    }\n\n    #[test]\n    fn test_read_con_fd() {\n        let content_array: Vec<&[u8]> =\n            vec![b\"Hello, world\\0\", b\"Hello, world\\0\\0\\0\", b\"Hello,\\0world\"];\n        let expected_array = [\"Hello, world\", \"Hello, world\", \"Hello,\\0world\"];\n        for (i, content) in content_array.iter().enumerate() {\n            let expected = expected_array[i];\n            let mut temp_file = NamedTempFile::new().expect(\"Failed to create temp file\");\n            temp_file\n                .write_all(content)\n                .expect(\"Failed to write to temp file\");\n            // Need to open again to get read permission.\n            let mut file = File::open(temp_file).expect(\"Failed to open file\");\n            let result = SELinux::read_con_fd(&mut file).expect(\"Failed to read file\");\n            assert_eq!(result, expected);\n        }\n    }\n\n    #[test]\n    fn test_attr_path() {\n        let selinux = SELinux::new();\n        // Test with \"/proc/thread-self/attr\" path (Linux >= 3.17)\n        let attr = \"bar\";\n        let expected_name = &format!(\"/proc/thread-self/attr/{}\", attr);\n        let expected_path = Path::new(expected_name);\n        let actual_path = selinux.attr_path(attr);\n        assert_eq!(expected_path, actual_path);\n\n        // Test with not having \"/proc/thread-self/attr\" path by setting HAVE_THREAD_SELF as false\n        selinux.attr_path_init_done.store(true, Ordering::SeqCst);\n        selinux.have_thread_self.store(false, Ordering::SeqCst);\n        let thread_id = gettid();\n        let expected_name = &format!(\"/proc/self/task/{}/attr/{}\", thread_id, attr);\n        let expected_path = Path::new(expected_name);\n        let actual_path = selinux.attr_path(attr);\n        assert_eq!(expected_path, actual_path);\n    }\n\n    #[test]\n    fn test_is_proc_handle() {\n        let filename_array = [\"/proc/self/status\", \"/tmp/testfile\"];\n        let expected_array = [true, false];\n\n        for (i, filename) in filename_array.iter().enumerate() {\n            let expected_ok = expected_array[i];\n            let path = Path::new(filename);\n            let file = match File::open(path) {\n                Ok(file) => file,\n                Err(_) => {\n                    create_temp_file(b\"\", filename);\n                    File::open(path).expect(\"failed to open file\")\n                }\n            };\n            let result = SELinux::is_proc_handle(&file);\n            if expected_ok {\n                assert!(result.is_ok(), \"Expected Ok, but got Err: {:?}\", result);\n            } else {\n                assert!(result.is_err(), \"Expected Err, but got Ok\");\n            }\n        }\n    }\n\n    #[test]\n    fn test_check_line_include_selinux_fs_mount_point() {\n        let input_array = [\n            \"28 24 0:25 / /sys/fs/selinux rw,relatime - selinuxfs selinuxfs rw\",\n            \"28 24 0:25 /\",\n            \"28 24 0:25 / /sys/fs/selinux rw,relatime selinuxfs rw\",\n        ];\n        let expected_array = [\"/sys/fs/selinux\", \"\", \"\"];\n        let succeeded_array = [true, false, false];\n\n        for (i, input) in input_array.iter().enumerate() {\n            let expected = PathBuf::from(expected_array[i]);\n            match SELinux::check_line_include_selinux_fs_mount_point(input) {\n                Some(output) => assert_eq!(expected, output),\n                None => assert!(!succeeded_array[i]),\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "experiment/selinux/src/selinux_label.rs",
    "content": "use crate::selinux::*;\nuse crate::tools::PathXattr;\nuse crate::tools::*;\nuse nix::sys::socket::getsockopt;\nuse std::convert::TryFrom;\nuse std::io::{BufRead, BufReader};\nuse std::os::fd::AsFd;\nuse std::path::Path;\nuse std::sync::atomic::Ordering;\n\nconst XATTR_NAME_SELINUX: &str = \"security.selinux\";\nconst KEY_LABEL_PATH: &str = \"/proc/self/attr/keycreate\";\n\n#[derive(Default, Clone)]\npub struct SELinuxLabel {\n    pub(crate) user: String,\n    role: String,\n    type_: String,\n    level: Option<String>,\n}\n\nimpl std::fmt::Display for SELinuxLabel {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match &self.level {\n            Some(level) => write!(f, \"{}:{}:{}:{}\", self.user, self.role, self.type_, level),\n            None => write!(f, \"{}:{}:{}\", self.user, self.role, self.type_),\n        }\n    }\n}\n\nimpl TryFrom<String> for SELinuxLabel {\n    type Error = SELinuxError;\n    fn try_from(label: String) -> Result<Self, SELinuxError> {\n        let fields: Vec<&str> = label.split(':').collect();\n        if fields.len() < 3 {\n            return Err(SELinuxError::InvalidSELinuxLabel(label));\n        }\n\n        // It is possible that input label is \"\", which means no label is set.\n        let user = fields\n            .first()\n            .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))?\n            .to_string();\n        let role = fields\n            .get(1)\n            .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))?\n            .to_string();\n        let type_ = fields\n            .get(2)\n            .ok_or(SELinuxError::InvalidSELinuxLabel(label.clone()))?\n            .to_string();\n        let level = fields.get(3).map(|&s| s.to_string());\n        Ok(SELinuxLabel {\n            user,\n            role,\n            type_,\n            level,\n        })\n    }\n}\n\n// This impl is for methods related to labels in SELinux struct.\nimpl SELinux {\n    // set_file_label sets the SELinux label for this path, following symlinks, or returns an error.\n    pub fn set_file_label<P: AsRef<Path> + PathXattr>(\n        fpath: P,\n        label: SELinuxLabel,\n    ) -> Result<(), SELinuxError> {\n        let path = fpath.as_ref();\n        if !path.exists() {\n            return Err(SELinuxError::SetFileLabel(ERR_EMPTY_PATH.to_string()));\n        }\n\n        loop {\n            match fpath.set_xattr(XATTR_NAME_SELINUX, label.to_string().as_bytes()) {\n                Ok(_) => break,\n                // When a system call is interrupted by a signal, it needs to be retried.\n                Err(XattrError::EINTR(_)) => continue,\n                Err(e) => {\n                    return Err(SELinuxError::SetFileLabel(e.to_string()));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    // lset_file_label sets the SELinux label for this path, not following symlinks,\n    // or returns an error.\n    pub fn lset_file_label<P: AsRef<Path> + PathXattr>(\n        fpath: P,\n        label: SELinuxLabel,\n    ) -> Result<(), SELinuxError> {\n        let path = fpath.as_ref();\n        if !path.is_symlink() && !path.exists() {\n            return Err(SELinuxError::LSetFileLabel(ERR_EMPTY_PATH.to_string()));\n        }\n\n        loop {\n            match fpath.lset_xattr(XATTR_NAME_SELINUX, label.to_string().as_bytes()) {\n                Ok(_) => break,\n                // When a system call is interrupted by a signal, it needs to be retried.\n                Err(XattrError::EINTR(_)) => continue,\n                Err(e) => {\n                    return Err(SELinuxError::LSetFileLabel(e.to_string()));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    // fileLabel returns the SELinux label for this path, following symlinks,\n    // or returns an error.\n    pub fn file_label<P: AsRef<Path> + PathXattr>(fpath: P) -> Result<SELinuxLabel, SELinuxError> {\n        let path = fpath.as_ref();\n        if !path.exists() {\n            return Err(SELinuxError::FileLabel(ERR_EMPTY_PATH.to_string()));\n        }\n        let label_str = fpath\n            .get_xattr(XATTR_NAME_SELINUX)\n            .map_err(|e| SELinuxError::FileLabel(e.to_string()))?;\n        SELinuxLabel::try_from(label_str)\n    }\n\n    // lfile_label returns the SELinux label for this path, not following symlinks,\n    // or returns an error.\n    pub fn lfile_label<P: AsRef<Path> + PathXattr>(fpath: P) -> Result<SELinuxLabel, SELinuxError> {\n        let path = fpath.as_ref();\n        if !path.exists() {\n            return Err(SELinuxError::LFileLabel(ERR_EMPTY_PATH.to_string()));\n        }\n        let label_str = fpath\n            .lget_xattr(XATTR_NAME_SELINUX)\n            .map_err(|e| SELinuxError::LFileLabel(e.to_string()))?;\n        SELinuxLabel::try_from(label_str)\n    }\n\n    // set_fscreate_label sets the default label the kernel which the kernel is using\n    // for file system objects.\n    pub fn set_fscreate_label(&mut self, label: SELinuxLabel) -> Result<usize, SELinuxError> {\n        return Self::write_con(\n            self,\n            self.attr_path(\"fscreate\").as_path(),\n            label.to_string().as_str(),\n        );\n    }\n\n    // fscreate_label returns the default label the kernel which the kernel is using\n    // for file system objects created by this task. \"\" indicates default.\n    pub fn fscreate_label(&self) -> Result<SELinuxLabel, SELinuxError> {\n        let label = Self::read_con(self.attr_path(\"fscreate\").as_path())?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // pid_label returns the SELinux label of the given pid, or an error.\n    pub fn pid_label(pid: i64) -> Result<SELinuxLabel, SELinuxError> {\n        let file_name = &format!(\"/proc/{}/attr/current\", pid);\n        let file_path = Path::new(file_name);\n        let label = Self::read_con(file_path)?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // exec_label returns the SELinux label that the kernel will use for any programs\n    // that are executed by the current process thread, or an error.\n    pub fn exec_label(&self) -> Result<SELinuxLabel, SELinuxError> {\n        let label = Self::read_con(self.attr_path(\"exec\").as_path())?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // set_exec_label sets the SELinux label that the kernel will use for any programs\n    // that are executed by the current process thread, or an error.\n    pub fn set_exec_label(&mut self, label: SELinuxLabel) -> Result<usize, SELinuxError> {\n        Self::write_con(\n            self,\n            self.attr_path(\"exec\").as_path(),\n            label.to_string().as_str(),\n        )\n    }\n\n    // set_task_label sets the SELinux label for the current thread, or an error.\n    // This requires the dyntransition permission because this changes the context of current thread.\n    pub fn set_task_label(&mut self, label: SELinuxLabel) -> Result<usize, SELinuxError> {\n        Self::write_con(\n            self,\n            self.attr_path(\"current\").as_path(),\n            label.to_string().as_str(),\n        )\n    }\n\n    // set_socket_label takes a process label and tells the kernel to assign the\n    // label to the next socket that gets created.\n    pub fn set_socket_label(&mut self, label: SELinuxLabel) -> Result<usize, SELinuxError> {\n        Self::write_con(\n            self,\n            self.attr_path(\"sockcreate\").as_path(),\n            label.to_string().as_str(),\n        )\n    }\n\n    // socket_label retrieves the current socket label setting.\n    pub fn socket_label(&self) -> Result<SELinuxLabel, SELinuxError> {\n        let label = Self::read_con(self.attr_path(\"sockcreate\").as_path())?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // current_label returns the SELinux label of the current process thread, or an error.\n    pub fn current_label(&self) -> Result<SELinuxLabel, SELinuxError> {\n        let label = SELinux::read_con(self.attr_path(\"current\").as_path())?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // peer_label retrieves the label of the client on the other side of a socket.\n    pub fn peer_label<F: AsFd>(fd: F) -> Result<SELinuxLabel, SELinuxError> {\n        // getsockopt manipulate options for the socket referred to by the file descriptor.\n        // https://man7.org/linux/man-pages/man2/getsockopt.2.html\n        match getsockopt(&fd, PeerSec) {\n            Ok(label) => match label.into_string() {\n                Ok(label_str) => SELinuxLabel::try_from(label_str),\n                Err(e) => Err(SELinuxError::PeerLabel(e.to_string())),\n            },\n            Err(e) => Err(SELinuxError::PeerLabel(e.to_string())),\n        }\n    }\n\n    // set_key_label takes a process label and tells the kernel to assign the\n    // label to the next kernel keyring that gets created.\n    pub fn set_key_label(&mut self, label: SELinuxLabel) -> Result<usize, SELinuxError> {\n        Self::write_con(self, Path::new(KEY_LABEL_PATH), label.to_string().as_str())\n    }\n\n    // key_label retrieves the current kernel keyring label setting\n    pub fn key_label() -> Result<SELinuxLabel, SELinuxError> {\n        let label = Self::read_con(Path::new(KEY_LABEL_PATH))?;\n        SELinuxLabel::try_from(label)\n    }\n\n    // kvm_container_labels returns the default processLabel and mountLabel to be used\n    // for kvm containers by the calling process.\n    pub fn kvm_container_labels(&mut self) -> (Option<SELinuxLabel>, Option<SELinuxLabel>) {\n        let process_label =\n            Self::label(self, \"kvm_process\").or_else(|| Self::label(self, \"process\"));\n        (process_label, Self::label(self, \"file\"))\n        // TODO: use addMcs\n    }\n\n    // init_container_labels returns the default processLabel and file labels to be\n    // used for containers running an init system like systemd by the calling process.\n    pub fn init_container_labels(&mut self) -> (Option<SELinuxLabel>, Option<SELinuxLabel>) {\n        let process_label =\n            Self::label(self, \"init_process\").or_else(|| Self::label(self, \"process\"));\n        (process_label, Self::label(self, \"file\"))\n        // TODO: use addMcs\n    }\n\n    // container_labels returns an allocated processLabel and fileLabel to be used for\n    // container labeling by the calling process.\n    pub fn container_labels(&mut self) -> (Option<SELinuxLabel>, Option<SELinuxLabel>) {\n        if !Self::get_enabled(self) {\n            return (None, None);\n        }\n        let process_label = Self::label(self, \"process\");\n        let file_label = Self::label(self, \"file\");\n\n        if process_label.is_none() || file_label.is_none() {\n            return (process_label, file_label);\n        }\n\n        let mut read_only_file_label = Self::label(self, \"ro_file\");\n        if read_only_file_label.is_none() {\n            read_only_file_label = file_label.clone();\n        }\n        self.read_only_file_label = read_only_file_label;\n\n        (process_label, file_label)\n        // TODO: use addMcs\n    }\n\n    // This function returns the value of given key on selinux context\n    fn label(&mut self, key: &str) -> Option<SELinuxLabel> {\n        if !self.load_labels_init_done.load(Ordering::SeqCst) {\n            Self::load_labels(self);\n            self.load_labels_init_done.store(true, Ordering::SeqCst);\n        }\n        self.labels.get(key).cloned()\n    }\n\n    // This function loads context file and reads labels and stores it.\n    fn load_labels(&mut self) {\n        // The context file should have pairs of key and value like below.\n        // ----------\n        // process = \"system_u:system_r:container_t:s0\"\n        // file = \"system_u:object_r:container_file_t:s0\"\n        // ----------\n        if let Ok(file) = Self::open_context_file(self) {\n            let reader = BufReader::new(file);\n            for line in reader.lines().map_while(Result::ok) {\n                let line = line.trim();\n                if line.is_empty() || line.starts_with(';') || line.starts_with('#') {\n                    continue;\n                }\n                let fields: Vec<&str> = line.splitn(2, '=').collect();\n                if fields.len() != 2 {\n                    continue;\n                }\n                let key = fields[0].trim().to_string();\n                let value = fields[1].trim_matches('\"').trim().to_string();\n                if let Ok(value_label) = SELinuxLabel::try_from(value) {\n                    self.labels.insert(key, value_label);\n                }\n            }\n        }\n    }\n\n    // format_mount_label returns a string to be used by the mount command.\n    // Using the SELinux `context` mount option.\n    // Changing labels of files on mount points with this option can never be changed.\n    // format_mount_label returns a string to be used by the mount command.\n    // The format of this string will be used to alter the labeling of the mountpoint.\n    // The string returned is suitable to be used as the options field of the mount command.\n    // If you need to have additional mount point options, you can pass them in as\n    // the first parameter. The second parameter is the label that you wish to apply\n    // to all content in the mount point.\n    pub fn format_mount_label(src: &str, mount_label: &str) -> String {\n        Self::format_mount_label_by_type(src, mount_label, \"context\")\n    }\n\n    // format_mount_label_by_type returns a string to be used by the mount command.\n    // Allow caller to specify the mount options. For example using the SELinux\n    // `fscontext` mount option would allow certain container processes to change\n    // labels of files created on the mount points, where as `context` option does not.\n    pub fn format_mount_label_by_type(src: &str, mount_label: &str, context_type: &str) -> String {\n        let mut formatted_src = src.to_owned();\n\n        if !mount_label.is_empty() {\n            if formatted_src.is_empty() {\n                formatted_src = format!(\"{}=\\\"{}\\\"\", context_type, mount_label);\n            } else {\n                formatted_src = format!(\"{},{}=\\\"{}\\\"\", formatted_src, context_type, mount_label);\n            }\n        }\n        formatted_src\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::selinux::*;\n\n    #[test]\n    fn test_format_mount_label() {\n        let src_array = [\"\", \"src\", \"src\"];\n        let mount_label_array = [\"foobar\", \"foobar\", \"\"];\n        let expected_array = [\"context=\\\"foobar\\\"\", \"src,context=\\\"foobar\\\"\", \"src\"];\n        for (i, src) in src_array.iter().enumerate() {\n            let mount_label = mount_label_array[i];\n            let expected = expected_array[i];\n            assert_eq!(SELinux::format_mount_label(src, mount_label), expected);\n        }\n    }\n\n    #[test]\n    fn test_format_mount_label_by_type() {\n        let src_array = [\"\", \"src\", \"src\"];\n        let mount_label_array = [\"foobar\", \"foobar\", \"\"];\n        let context_array = [\"fscontext\", \"fscontext\", \"rootcontext\"];\n        let expected_array = [\"fscontext=\\\"foobar\\\"\", \"src,fscontext=\\\"foobar\\\"\", \"src\"];\n        for (i, src) in src_array.iter().enumerate() {\n            let mount_label = mount_label_array[i];\n            let context = context_array[i];\n            let expected = expected_array[i];\n            assert_eq!(\n                SELinux::format_mount_label_by_type(src, mount_label, context),\n                expected\n            );\n        }\n    }\n}\n"
  },
  {
    "path": "experiment/selinux/src/tools/mod.rs",
    "content": "mod sockopt;\nmod xattr;\n\npub use sockopt::*;\npub use xattr::*;\n"
  },
  {
    "path": "experiment/selinux/src/tools/sockopt.rs",
    "content": "use nix::libc;\nuse nix::sys::socket::GetSockOpt;\nuse std::ffi::CString;\nuse std::os::fd::{AsFd, AsRawFd};\n\n#[derive(Debug, Copy, Clone)]\npub struct PeerSec;\n\n// This function implements the GetSockOpt for PeerSec, retrieving the security context label\n// of a socket file descriptor into a CString.\n// This function utilizes nix's GetSockOpt implementation.\n// https://github.com/nix-rust/nix/blob/50e4283b35f3f34e138d138fd889f7e3c424a5c2/src/sys/socket/mod.rs#L2219\nimpl GetSockOpt for PeerSec {\n    type Val = CString;\n\n    fn get<F: AsFd>(&self, fd: &F) -> nix::Result<Self::Val> {\n        let mut len: libc::socklen_t = libc::c_int::MAX as libc::socklen_t;\n        let mut buf = vec![0u8; len as usize];\n        let fd_i32 = fd.as_fd().as_raw_fd();\n\n        let ret = unsafe {\n            libc::getsockopt(\n                fd_i32,\n                libc::SOL_SOCKET,\n                libc::SO_PEERSEC,\n                buf.as_mut_ptr() as *mut libc::c_void,\n                &mut len,\n            )\n        };\n\n        if ret == -1 {\n            return Err(nix::Error::last());\n        }\n\n        buf.truncate(len as usize);\n        Ok(CString::new(buf).unwrap())\n    }\n}\n"
  },
  {
    "path": "experiment/selinux/src/tools/xattr.rs",
    "content": "use nix::libc;\nuse rustix::fs as rfs;\nuse std::path::Path;\n\n#[derive(Debug, thiserror::Error)]\npub enum XattrError {\n    #[error(\"Failed to set_xattr: {0}\")]\n    SetXattr(String),\n    #[error(\"Failed to lset_xattr: {0}\")]\n    LSetXattr(String),\n    #[error(\"Failed to get_xattr: {0}\")]\n    GetXattr(String),\n    #[error(\"Failed to lget_xattr: {0}\")]\n    LGetXattr(String),\n    #[error(\"EINTR error: {0}\")]\n    EINTR(i32),\n}\n\n// SELinux label is not so big, so we allocate 1024 bytes for the buffer.\nconst INITIAL_BUF_SIZE: usize = 1024;\n\npub trait PathXattr {\n    fn set_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError>;\n    fn lset_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError>;\n    fn get_xattr(&self, attr: &str) -> Result<String, XattrError>;\n    fn lget_xattr(&self, attr: &str) -> Result<String, XattrError>;\n}\n\nimpl<P> PathXattr for P\nwhere\n    P: AsRef<Path>,\n{\n    // function similar with setxattr in golang.org/x/sys/unix repo.\n    // set_xattr sets extended attributes on a file specified by its path.\n    fn set_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> {\n        let path = self.as_ref();\n        let op = match path.get_xattr(attr) {\n            Ok(_) => rfs::XattrFlags::REPLACE,\n            Err(_) => rfs::XattrFlags::CREATE,\n        };\n        match rfs::setxattr(path, attr, data, op) {\n            Ok(_) => Ok(()),\n            Err(e) => {\n                let errno = e.raw_os_error();\n                if errno == libc::EINTR {\n                    return Err(XattrError::EINTR(errno));\n                }\n                Err(XattrError::SetXattr(e.to_string()))\n            }\n        }\n    }\n\n    // function similar with lsetxattr in golang.org/x/sys/unix repo.\n    // lset_xattr sets extended attributes on a symbolic link.\n    fn lset_xattr(&self, attr: &str, data: &[u8]) -> Result<(), XattrError> {\n        let path = self.as_ref();\n        let op = match path.lget_xattr(attr) {\n            Ok(_) => rfs::XattrFlags::REPLACE,\n            Err(_) => rfs::XattrFlags::CREATE,\n        };\n        match rfs::lsetxattr(path, attr, data, op) {\n            Ok(_) => Ok(()),\n            Err(e) => {\n                let errno = e.raw_os_error();\n                if errno == libc::EINTR {\n                    return Err(XattrError::EINTR(errno));\n                }\n                Err(XattrError::LSetXattr(e.to_string()))\n            }\n        }\n    }\n\n    // function similar with getattr in go-selinux repo.\n    // get_xattr returns the value of an extended attribute attr set for path.\n    fn get_xattr(&self, attr: &str) -> Result<String, XattrError> {\n        let path = self.as_ref();\n        let mut buf_size = INITIAL_BUF_SIZE;\n        let mut buf = vec![0u8; buf_size];\n\n        loop {\n            match rfs::getxattr(path, attr, &mut buf) {\n                Ok(size) => {\n                    if size == buf_size {\n                        buf_size *= 2;\n                        buf.resize(buf_size, 0);\n                        continue;\n                    }\n                    let mut value = String::from_utf8_lossy(&buf[..size]).into_owned();\n                    if value.ends_with('\\x00') {\n                        value.pop();\n                    }\n                    return Ok(value);\n                }\n                Err(e) => return Err(XattrError::GetXattr(e.to_string())),\n            }\n        }\n    }\n\n    // function similar with lgetxattr in go-selinux repo.\n    // lget_xattr returns the value of an extended attribute attr set for path.\n    fn lget_xattr(&self, attr: &str) -> Result<String, XattrError> {\n        let path = self.as_ref();\n        let mut buf_size = INITIAL_BUF_SIZE;\n        let mut buf = vec![0u8; buf_size];\n\n        loop {\n            match rfs::lgetxattr(path, attr, &mut buf) {\n                Ok(size) => {\n                    if size == buf_size {\n                        buf_size *= 2;\n                        buf.resize(buf_size, 0);\n                        continue;\n                    }\n                    let mut value = String::from_utf8_lossy(&buf[..size]).into_owned();\n                    if value.ends_with('\\x00') {\n                        value.pop();\n                    }\n                    return Ok(value);\n                }\n                Err(e) => return Err(XattrError::LGetXattr(e.to_string())),\n            }\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use crate::tools::*;\n    use tempfile::NamedTempFile;\n\n    #[test]\n    fn test_set_xattr_and_get_xattr() {\n        // Because of the permission issue, \"selinux.security\" can't be used here.\n        let attr_name = \"user.test_attr\";\n        let attr_value = \"system_u:object_r:some_label_t\";\n        let temp_file = NamedTempFile::new().expect(\"Failed to create temp file\");\n        let file_path = temp_file.path();\n\n        // Verify that the first \"set_xattr\" operation succeeds, which means it doesn't have xattr yet.\n        file_path\n            .set_xattr(attr_name, attr_value.as_bytes())\n            .expect(\"Failed to set xattr\");\n        let actual = file_path.get_xattr(attr_name).expect(\"Failed to get xattr\");\n        assert_eq!(actual, attr_value);\n\n        // Verify that the second \"set_xattr\" operation succeeds, which means it already has xattr.\n        let attr_value = \"system_u:object_r:another_label_t\";\n        file_path\n            .set_xattr(attr_name, attr_value.as_bytes())\n            .expect(\"Failed to set xattr\");\n        let actual = file_path.get_xattr(attr_name).expect(\"Failed to get xattr\");\n        assert_eq!(actual, attr_value);\n    }\n    // The test of lset and lget is not implemented here because there is no root permission.\n}\n"
  },
  {
    "path": "hack/busctl.sh",
    "content": "#!/bin/sh\n\n# This hack script is the dummy busctl command used when running tests with cross containers.\n\n# The issue is that we cannot run systemd or dbus inside the test container without a lot \n# of hacks. For one specific test - test_task_addition, we need to check that the task\n# addition via systemd manager works. We mount the host dbus socket in the test container, so \n# dbus calls work, but for the initial authentication, we use busctl which needs dbus and systemd\n# to be present and running. So instead of doing all that, we simply run the container with the \n# actual test running user's uid/gid and here we echo the only relevant line from busctl's \n# output, using id to get the uid. This is a hack, but less complex than actually setting up\n# and running the systemd+dbus inside the container.\n\necho \"OwnerUID=$(id -u)\"\n"
  },
  {
    "path": "hack/debug.bt",
    "content": "#!/usr/bin/env bpftrace\n\nBEGIN\n{\n    printf(\"Tracing Youki syscalls... Hit Ctrl-C to end.\\n\");\n    printf(\"%-12s %15s %-8s %-9s %s\\n\", \"TIME\", \"COMMAND\", \"PID\", \"EVENT\", \"CONTENT\");\n}\n\ntracepoint:syscalls:sys_enter_write\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n\n    $s = str(args->buf, args->count);\n    if ($s != \"\\n\") {\n        printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"write\");\n        printf(\"fd=%d, %s\\n\", args->fd, $s);\n    }\n}\n\ntracepoint:syscalls:sys_enter_open,\ntracepoint:syscalls:sys_enter_openat\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n\t@filename[tid] = args->filename;\n}\n\n\ntracepoint:syscalls:sys_exit_open,\ntracepoint:syscalls:sys_exit_openat\n/@filename[tid]/\n{\n\t$ret = args->ret;\n\t$fd = $ret >= 0 ? $ret : -1;\n\t$errno = $ret >= 0 ? 0 : - $ret;\n\n    printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"open\");\n\tprintf(\"errno=%d, fd=%d, file=%s\\n\", $errno, $fd, str(@filename[tid]));\n\tdelete(@filename[tid]);\n}\n\ntracepoint:syscalls:sys_enter_clone3\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s\\n\", elapsed , comm, pid, \"clone3\");\n}\n\ntracepoint:syscalls:sys_enter_setns\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"setns\");\n\tprintf(\"fd=%d, flag=%d\\n\", args->fd, args->flags);\n}\n\ntracepoint:syscalls:sys_enter_capset\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s\\n\", elapsed , comm, pid, \"capset\");\n}\n\ntracepoint:syscalls:sys_enter_pivot_root\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"pivt_root\");\n\tprintf(\"new_root=%s, put_old=%s\\n\", str(args->new_root), str(args->put_old));\n}\n\ntracepoint:syscalls:sys_enter_mount\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"mount\");\n    printf(\"dev_name=%s, dir_name=%s\\n\", str(args->dev_name), str(args->dir_name));\n}\n\ntracepoint:syscalls:sys_enter_setresuid\n/comm == \"4\"|| comm == \"youki\" || comm == \"youki:[1:INTER]\" || comm == \"youki:[2:INIT]\"/\n{\n    printf(\"%-12ld %15s %-8d %-9s \", elapsed , comm, pid, \"setresuid\");\n    printf(\"ruid=%d, euid=%d, suid=%d\\n\", args->ruid, args->euid, args->suid);\n}\n\nEND\n{\n    clear(@filename);\n    printf(\"Tracing ended.\\n\");\n}\n\n"
  },
  {
    "path": "hack/set_root_login_for_vagrant.sh",
    "content": "#!/bin/bash\n\nset -x\n\n# change sshd config\n\nfile=\"$1\"\nparam[1]=\"PermitRootLogin \"\nparam[2]=\"PubkeyAuthentication\"\nparam[3]=\"PasswordAuthentication\"\nif [ -z \"${file}\" ]\nthen\n\nfile=\"/etc/ssh/sshd_config\"\nfi\n\nfor PARAM in ${param[@]}\ndo\n/usr/bin/sed -i '/^'\"${PARAM}\"'/d' ${file}\n/usr/bin/echo \"All lines beginning with '${PARAM}' were deleted from ${file}.\"\ndone\n\n/usr/bin/echo \"${param[1]} yes\" >> ${file}\n/usr/bin/echo \"'${param[1]} yes' was added to ${file}.\"\n/usr/bin/echo \"${param[2]} yes\" >> ${file}\n/usr/bin/echo \"'${param[2]} yes' was added to ${file}.\"\n/usr/bin/echo \"${param[3]} no\" >> ${file}\n/usr/bin/echo \"'${param[3]} no' was added to ${file}\"\n\n# reload config\nservice sshd reload\n"
  },
  {
    "path": "hack/stress_cargo_test.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# This is a simple script to stress test `cargo test` to rule out flaky tests.\n\nCOUNT=${1:-20}\n\nfor i in $(seq 1 ${COUNT})\ndo \n    echo \"Run test ${i} iteration...\"\n    cargo test -- --nocapture\ndone"
  },
  {
    "path": "justfile",
    "content": "alias build := youki-release\nalias youki := youki-dev\n\nKIND_CLUSTER_NAME := 'youki'\n\ncwd := justfile_directory()\n\n# build\n\n# build all binaries\nbuild-all: youki-release contest\n\n# build youki in dev mode\nyouki-dev:\n    {{ cwd }}/scripts/build.sh -o {{ cwd }} -c youki\n\n# build youki in release mode\nyouki-release:\n    {{ cwd }}/scripts/build.sh -o {{ cwd }} -r -c youki\n\n# build runtimetest binary\nruntimetest:\n    {{ cwd }}/scripts/build.sh -o {{ cwd }} -r -c runtimetest\n\n# build contest\ncontest:\n    {{ cwd }}/scripts/build.sh -o {{ cwd }} -r -c contest\n\n# install youki to /usr/local/sbin\ninstall:\n    install -D -m 0755 {{ cwd }}/youki \"${PREFIX-/usr/local/sbin}/youki\"\n\n# Tests\n\n# run integration tests\ntest-integration: test-oci test-contest\n\n# run all tests except rust-oci \ntest-all: test-basic test-features test-oci containerd-test # currently not doing rust-oci here\n\n# run basic tests\ntest-basic: test-unit test-doc\n\n# run cargo unit tests\ntest-unit:\n    {{ cwd }}/scripts/cargo.sh test --lib --bins --all --all-targets --all-features --no-fail-fast -- --test-threads=1\n\n# run cargo doc tests\ntest-doc:\n    {{ cwd }}/scripts/cargo.sh test --doc -- --test-threads=1\n\n# run permutated feature compilation tests\ntest-features:\n    {{ cwd }}/scripts/features_test.sh\n\n# run oci integration tests through runtime-tools\ntest-oci:\n    {{ cwd }}/scripts/oci_integration_tests.sh {{ cwd }}\n\n# run rust oci integration tests\ntest-contest *TESTNAME: youki-release contest\n    sudo {{ cwd }}/scripts/contest.sh {{ cwd }}/youki {{TESTNAME}}\n\n# validate rust oci integration tests on runc\nvalidate-contest-runc *TESTNAME: contest\n    sudo RUNTIME_KIND=\"runc\" {{ cwd }}/scripts/contest.sh runc {{TESTNAME}}\n\n# test podman rootless works with youki\ntest-rootless-podman:\n    {{ cwd }}/tests/rootless-tests/run.sh {{ cwd }}/youki\n\n# test docker-in-docker works with youki\ntest-dind:\n    {{ cwd }}/tests/dind/run.sh\n\n# test runc compatibility\ntest-runc-comp *RUNTIME_BINARY:\n    {{ cwd }}/tests/runc/runc_integration_test.sh {{RUNTIME_BINARY}}\n\n# run containerd integration tests\ncontainerd-test: youki-dev\n    vagrant up containerd2youki\n    vagrant provision containerd2youki --provision-with test\n\n# run containerd integration tests\nclean-containerd-test:\n    vagrant destroy containerd2youki\n\n[private]\nkind-cluster: bin-kind\n    #!/usr/bin/env bash\n    set -euo pipefail\n\n    mkdir -p tests/k8s/_out/\n    docker buildx build -f tests/k8s/Dockerfile --iidfile=tests/k8s/_out/img --load .\n    image=$(cat tests/k8s/_out/img)\n    bin/kind create cluster --name {{ KIND_CLUSTER_NAME }} --image=$image\n\n# run youki with kind\ntest-kind: kind-cluster\n    kubectl --context=kind-{{ KIND_CLUSTER_NAME }} apply -f tests/k8s/deploy.yaml\n    kubectl --context=kind-{{ KIND_CLUSTER_NAME }} wait deployment nginx-deployment --for condition=Available=True --timeout=90s\n    kubectl --context=kind-{{ KIND_CLUSTER_NAME }} get pods -o wide\n    kubectl --context=kind-{{ KIND_CLUSTER_NAME }} delete -f tests/k8s/deploy.yaml\n\n# Bin\n\n[private]\nbin-kind:\n\tdocker buildx build --output=bin/ -f tests/k8s/Dockerfile --target kind-bin .\n\n# Clean\n\n# Clean kind test env\nclean-test-kind:\n\tkind delete cluster --name {{ KIND_CLUSTER_NAME }}\n\n# misc\n\n# run bpftrace hack\nhack-bpftrace:\n    BPFTRACE_STRLEN=120 ./hack/debug.bt\n\n# a hacky benchmark method we have been using casually to compare performance\nhack-benchmark:\n    #!/usr/bin/env bash\n    set -euo pipefail\n\n    hyperfine \\\n        --prepare 'sudo sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' \\\n        --warmup 10 \\\n        --min-runs 100 \\\n        'sudo {{ cwd }}/youki create -b tutorial a && sudo {{ cwd }}/youki start a && sudo {{ cwd }}/youki delete -f a'\n\n# run linting on project\nlint:\n    {{ cwd }}/scripts/cargo.sh fmt --all -- --check\n    {{ cwd }}/scripts/cargo.sh clippy --all --all-targets --all-features -- -D warnings\n\n# run spellcheck\nspellcheck:\n    typos\n\n# run format on project\nformat:\n    {{ cwd }}/scripts/cargo.sh fmt --all\n\n# cleans up generated artifacts\nclean:\n    {{ cwd }}/scripts/clean.sh {{ cwd }}\n\n# install tools used in dev\ndev-prepare:\n    {{ cwd }}/scripts/cargo.sh install typos-cli\n\n# setup dependencies in CI\nci-prepare:\n    #!/usr/bin/env bash\n    set -euo pipefail\n\n    # Check if system is Ubuntu\n    if [[ -f /etc/lsb-release ]]; then\n        source /etc/lsb-release\n        if [[ $DISTRIB_ID == \"Ubuntu\" ]]; then\n            echo \"System is Ubuntu\"\n            apt-get -y update\n            apt-get install -y \\\n                pkg-config \\\n                libsystemd-dev \\\n                build-essential \\\n                libelf-dev \\\n                libseccomp-dev \\\n                libclang-dev \\\n                libssl-dev \\\n                criu\n            exit 0\n        fi\n    fi\n\n    echo \"Unknown system. The CI is only configured for Ubuntu. You will need to forge your own path. Good luck!\"\n    exit 1\n\nci-musl-prepare: ci-prepare\n    #!/usr/bin/env bash\n    set -euo pipefail\n\n    # Check if system is Ubuntu\n    if [[ -f /etc/lsb-release ]]; then\n        source /etc/lsb-release\n        if [[ $DISTRIB_ID == \"Ubuntu\" ]]; then\n            echo \"System is Ubuntu\"\n            apt-get -y update\n            apt-get install -y \\\n                musl-dev \\\n                musl-tools\n            exit 0\n        fi\n    fi\n\n    echo \"Unknown system. The CI is only configured for Ubuntu. You will need to forge your own path. Good luck!\"\n    exit 1\n\nversion-up version:\n    #!/usr/bin/bash\n    set -ex\n    git grep -l \"^version = .* # MARK: Version\" | xargs sed -i 's/version = \"[0-9]\\.[0-9]\\.[0-9]\" # MARK: Version/version = \"{{version}}\" # MARK: Version/g'\n    git grep -l \"} # MARK: Version\" | grep -v justfile | xargs sed -i 's/version = \"[0-9]\\.[0-9]\\.[0-9]\" } # MARK: Version/version = \"{{version}}\" } # MARK: Version/g'\n    {{ cwd }}/scripts/release_tag.sh {{version}}\n    NEXT_VERSION=$(echo {{version}} | awk -F. -v OFS=. '{$NF += 1 ; print}')\n    sed -i \"s/{{version}}/$NEXT_VERSION/g\" .tagpr\n    # Need to update the lockfile.\n    cargo check\n\ncontest-list: contest\n   {{ cwd }}/contest list\n"
  },
  {
    "path": "rust-toolchain.toml",
    "content": "[toolchain]\nprofile=\"default\"\nchannel=\"1.92.0\"\n"
  },
  {
    "path": "rustfmt.toml",
    "content": "newline_style = \"Native\"\nunstable_features = true # Cargo fmt now needs to be called with `cargo +nightly fmt`\ngroup_imports = \"StdExternalCrate\" # create three groups for std, external and local crates\n# Merge imports from the same module\n# See: https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#imports_granularity\nimports_granularity = \"Module\"\n"
  },
  {
    "path": "scripts/.gitignore",
    "content": "*.log\n*.gz"
  },
  {
    "path": "scripts/README.md",
    "content": "# Scripts\n\nThis stores various scripts that do various things. These can be intended to be used directly, or can be for using from some other scripts or Makefiles.\n\n#### Note\n\nPlease use `set -e` at the start of every script. This will ensure that the operation fails if any single command fails in that script. Without it, the script will continue after the failing command and might create a knock-on effect of incorrect results. In case you expect some step to fail, handle the failure directly rather than checking some condition to see if the command is successful in the rest of the script.\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=$(git rev-parse --show-toplevel)\nHOST_TARGET=$(rustc -Vv | grep ^host: | cut -d' ' -f2)\n\nusage_exit() {\n    echo \"Usage: $0 [-r] [-o dir] [-c crate] [-f features] [-t target] [-x]\" 1>&2\n    exit 1\n}\n\nVERSION=debug\nCRATE=\"youki\"\nTARGET=${TARGET:-$HOST_TARGET}\nCARGO=${CARGO:-}\nfeatures=\"\"\n\nwhile getopts f:ro:c:t:xh OPT; do\n    case $OPT in\n        f) features=${OPTARG}\n            ;;\n        o) output=${OPTARG}\n            ;;\n        r) VERSION=release\n            ;;\n        c) CRATE=${OPTARG}\n            ;;\n        t) TARGET=${OPTARG}\n            ;;\n        x) CARGO=cross\n            ;;\n        h) usage_exit\n            ;;\n        \\?) usage_exit\n            ;;\n    esac\ndone\n\nshift $((OPTIND - 1))\n\nOPTION=\"\"\nif [ \"${VERSION}\" = release ]; then\n    OPTION=\"--release\"\nfi\n\n# expand target shortcuts\ncase \"$TARGET\" in\n    musl)\n        TARGET=\"$(uname -m)-unknown-linux-musl\"\n        ;;\n    gnu|glibc)\n        TARGET=\"$(uname -m)-unknown-linux-gnu\"\n        ;;\n    arm64|aarch64)\n        TARGET=\"aarch64-unknown-linux-musl\"\n        ;;\n    amd64|x86_64)\n        TARGET=\"x86_64-unknown-linux-musl\"\n        ;;\nesac\n\nFEATURES=()\nif [ -n \"${features}\" ]; then\n    FEATURES=(\"--features=${features}\")\nfi\necho \"* FEATURES: ${features:-<default>}\"\necho \"* TARGET: ${TARGET}\"\n\nOUTPUT=${output:-$ROOT/bin}\nmkdir -p \"$OUTPUT\"\n\nCARGO_SH=\"$(dirname \"$0\")/cargo.sh\"\nexport CARGO_BUILD_TARGET=\"$TARGET\"\n\nif [ \"$CRATE\" == \"youki\" ]; then\n    rm -f \"${OUTPUT}/youki\"\n    \"$CARGO_SH\" build ${OPTION} \"${FEATURES[@]}\" --bin youki\n    mv \"$(\"$CARGO_SH\" --print-target-dir)/${TARGET}/${VERSION}/youki\" \"${OUTPUT}/\"\nfi\n\nif [ \"$CRATE\" == \"contest\" ]; then\n    find ${OUTPUT} -maxdepth 1 -type f -name \"contest\" -exec rm -ifv {} \\;\n    \"$CARGO_SH\" build ${OPTION} \"${FEATURES[@]}\" --bin contest\n    mv ${ROOT}/target/${TARGET}/${VERSION}/contest ${OUTPUT}/\n\n    find ${OUTPUT} -maxdepth 1 -type f -name \"runtimetest\" -exec rm -ifv {} \\;\n    CONTEST_TARGET=\"$ROOT/contest-target\"\n    CARGO_TARGET_DIR=${CONTEST_TARGET} RUSTFLAGS=\"-Ctarget-feature=+crt-static\" \"$CARGO_SH\" build ${OPTION} \"${FEATURES[@]}\" --bin runtimetest\n    mv ${CONTEST_TARGET}/${TARGET}/${VERSION}/runtimetest ${OUTPUT}/\nfi\n"
  },
  {
    "path": "scripts/cargo.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Usage:\n#   CARGO_BUILD_TARGET=<target> cargo.sh <args>...\n#\n# This script wraps `cargo` invocations, and calls either `cargo`\n# or `cross`, as appropriate. It checks the value of the\n# `CARGO_BUILD_TARGET` environment variable, and calls `cargo` if\n# the target matches the host target, and `cross` otherwise.\n#\n# Use of `cargo`/`cross` can be forced by setting the `CARGO`\n# environment variable to `cargo`/`cross`. This is useful for\n# instance to force the use of `cross` in CI even for the host\n# target.\n#\n# When cross is used, the target directory will be appended\n# `cross-<target>` to avoid libc conflicts in host binaries (e.g.,\n# build scripts, proc macros). The computed value of the target\n# directory can be obtained with `cargo.sh --print-target-dir`.\n#\n# Lastly, when using `cross` this scrips sets some configuration\n# to allow running `youki` tests inside the `cross`` container.\n# Please check the comments in this scrips to learm more about that.\n#\n# Limitations:\n#  * You **must** use the `CARGO_BUILD_TARGET` environment variable\n#    to specify the build target instead of using the `--target` CLI\n#    argument, or a configuration file like `.cargo/config.toml`.\n#  * If **must** use the `CARGO_TARGET_DIR` environment variable to\n#    specify the target directory instead of using the `--target-dir`\n#    CLI argument, or a configuration file like `.cargo/config.toml`.\n#\n\nROOT=$(git rev-parse --show-toplevel)\nHOST_TARGET=$(rustc -Vv | grep ^host: | cut -d' ' -f2)\n\nexport CARGO_BUILD_TARGET=\"${CARGO_BUILD_TARGET:-$HOST_TARGET}\"\nexport CARGO_TARGET_DIR=\"${CARGO_TARGET_DIR:-$ROOT/target}\"\n\nif [ \"$CARGO_BUILD_TARGET\" == \"$HOST_TARGET\" ]; then\n    CARGO=\"${CARGO:-cargo}\"\nelse\n    CARGO=\"${CARGO:-cross}\"\nfi\n\nif [ \"$1\" == \"fmt\" ]; then\n    # running cargo fmt fails when run through cross\n    # also cargo fmt is platform independent\n    CARGO=\"cargo\"\n\n    # Use nightly for `cargo fmt` as `rustfmt.toml` uses unstable features.\n    set -- \"+nightly\" \"$@\"\nfi\n\nif [ \"$CARGO\" == \"cross\" ]; then\n    export CROSS_BUILD_OPTS=\"--quiet\"\n    export CARGO_TARGET_DIR=\"$CARGO_TARGET_DIR/cross-$CARGO_BUILD_TARGET\"\n\n    # mount run to have access to dbus socket.\n    # mount /tmp so as shared for test_make_parent_mount_private\n    # Then there are few \"hacks\" specificallt for test_task_addition\n    # run with user same as the invoking user, so that the dbus is connected with correct user\n    # we want pid ns of host, because we will be connecting to the host dbus, and it needs task pid from host\n    # finally we need to mount the cgroup as read-only, as we need that to check if the tasks are correctly added\n    # Note: Mount `-v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro` to read the information of the created test user.\n    export CROSS_CONTAINER_OPTS=\"--privileged --user `id -u`:`id -g` --pid=host -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro -v /run:/run --mount=type=bind,source=/tmp,destination=/tmp,bind-propagation=shared\"\nfi\n\nif [ \"$1\" == \"--print-target-dir\" ]; then\n    echo \"$CARGO_TARGET_DIR\"\n    exit 0\nfi\n\n\"$CARGO\" \"$@\"\n"
  },
  {
    "path": "scripts/clean.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nROOT=$(git rev-parse --show-toplevel)\n\nfor bin in youki integration_test runtimetest test.log; do\n    if [ -f $bin ]; then\n        rm -f ${1}/$bin\n    fi\ndone\n\nrm -rf $ROOT/target $ROOT/runtimetest-target\n\nexit 0 # unconditionally return zero\n"
  },
  {
    "path": "scripts/contest.sh",
    "content": "#! /bin/sh -eu\n\nROOT=$(git rev-parse --show-toplevel)\nRUNTIME=$1\nshift\n\nif [ \"$RUNTIME\" = \"\" ]; then\n    echo \"please specify runtime\"\n    exit 1\nfi\n\nif [ ! -e $RUNTIME ]; then\n  if ! which $RUNTIME ; then\n    echo \"$RUNTIME not found\"\n    exit 1\n  fi\nfi\n\nLOGFILE=\"${ROOT}/test.log\"\n\nif [ ! -f ${ROOT}/bundle.tar.gz ]; then\n    cp ${ROOT}/tests/contest/contest/bundle.tar.gz ${ROOT}/bundle.tar.gz\nfi\ntouch ${LOGFILE}\n\nif [ $# -gt 0 ]; then\n    ${ROOT}/contest run --runtime \"$RUNTIME\" --runtimetest \"${ROOT}/runtimetest\" -t \"$@\" 2>&1 | tee \"$LOGFILE\"\nelse\n    ${ROOT}/contest run --runtime \"$RUNTIME\" --runtimetest \"${ROOT}/runtimetest\" 2>&1 | tee \"$LOGFILE\"\nfi\n\nif [ 0 -ne $(grep \"not ok\" $LOGFILE | wc -l ) ]; then\n    exit 1\nfi\n\necho \"Validation successful for runtime $RUNTIME\"\nexit 0\n\n\n"
  },
  {
    "path": "scripts/features_test.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nCARGO_SH=\"$(dirname \"$0\")/cargo.sh\"\n\ntest_package_features() {\n    echo \"[feature test] building $1 with features $2\"\n    \"$CARGO_SH\" build --no-default-features --package \"$1\" --features \"$2\"\n}\n\ntest_package_features \"libcontainer\" \"v1\"\ntest_package_features \"libcontainer\" \"v2\"\ntest_package_features \"libcontainer\" \"systemd\"\ntest_package_features \"libcontainer\" \"v2 cgroupsv2_devices\"\ntest_package_features \"libcontainer\" \"systemd cgroupsv2_devices\"\ntest_package_features \"libcontainer\" \"v1 libseccomp\"\ntest_package_features \"libcontainer\" \"v2 libseccomp\"\ntest_package_features \"libcontainer\" \"systemd libseccomp\"\ntest_package_features \"libcontainer\" \"v2 cgroupsv2_devices libseccomp\"\ntest_package_features \"libcontainer\" \"systemd cgroupsv2_devices libseccomp\"\n\ntest_package_features \"libcgroups\" \"v1\"\ntest_package_features \"libcgroups\" \"v2\"\ntest_package_features \"libcgroups\" \"systemd\"\ntest_package_features \"libcgroups\" \"v2 cgroupsv2_devices\"\ntest_package_features \"libcgroups\" \"systemd cgroupsv2_devices\"\n\ntest_features() {\n    echo \"[feature test] testing features $1\"\n    \"$CARGO_SH\" build --no-default-features --features \"$1\"\n    \"$CARGO_SH\" test run --no-default-features --features \"$1\" -- --test-threads=1\n}\n\ntest_features \"v1\"\ntest_features \"v2\"\ntest_features \"systemd\"\ntest_features \"v2 cgroupsv2_devices\"\ntest_features \"systemd cgroupsv2_devices\"\ntest_features \"v1 seccomp\"\ntest_features \"v2 seccomp\"\ntest_features \"systemd seccomp\"\ntest_features \"v2 cgroupsv2_devices seccomp\"\ntest_features \"systemd cgroupsv2_devices seccomp\"\n\nexit 0\n"
  },
  {
    "path": "scripts/oci_integration_tests.sh",
    "content": "#!/bin/bash -eu\n\nROOT=$(git rev-parse --show-toplevel)\n\nRUNTIME=${1:-.}/youki\nOCI_TEST_DIR=${ROOT}/tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools\nPATTERN=${2:-.}\ncd $OCI_TEST_DIR\n\ntest_cases=(\n  \"create/create.t\"\n  \"default/default.t\"\n  \"delete_only_create_resources/delete_only_create_resources.t\"\n  \"delete_resources/delete_resources.t\"\n  \"hooks_stdin/hooks_stdin.t\"\n  \"kill_no_effect/kill_no_effect.t\"\n  \"killsig/killsig.t\"\n  \"linux_cgroups_devices/linux_cgroups_devices.t\"\n  # This case includes checking for features that are excluded from linux kernel 5.0, so even runc doesn't pass it.\n  # ref. https://github.com/docker/cli/pull/2908\n  # \"linux_cgroups_relative_blkio/linux_cgroups_relative_blkio.t\"\n  \"linux_cgroups_relative_cpus/linux_cgroups_relative_cpus.t\"\n  \"linux_cgroups_relative_devices/linux_cgroups_relative_devices.t\"\n  \"linux_cgroups_relative_hugetlb/linux_cgroups_relative_hugetlb.t\"\n  \"linux_cgroups_relative_memory/linux_cgroups_relative_memory.t\"\n  \"linux_cgroups_relative_pids/linux_cgroups_relative_pids.t\"\n  \"linux_mount_label/linux_mount_label.t\"\n  \"linux_ns_nopath/linux_ns_nopath.t\"\n  \"linux_ns_path/linux_ns_path.t\"\n  \"linux_ns_path_type/linux_ns_path_type.t\"\n  # This test case requires that an apparmor profile named 'acme_secure_profile' has been installed on the system. It needs to allow the capabilities\n  # validated by runtime-tools otherwise the test case will fail despite the profile being available.\n  # \"linux_process_apparmor_profile/linux_process_apparmor_profile.t\"\n  # \"misc_props/misc_props.t\" runc also fails this, check out https://github.com/youki-dev/youki/pull/1347#issuecomment-1315332775\n  \"mounts/mounts.t\"\n  \"poststart/poststart.t\"\n  \"poststart_fail/poststart_fail.t\"\n  \"poststop/poststop.t\"\n  \"poststop_fail/poststop_fail.t\"\n  \"prestart/prestart.t\"\n  \"prestart_fail/prestart_fail.t\"\n  \"process_capabilities/process_capabilities.t\"\n  # Record the tests that runc also fails to pass below, maybe we will fix this by origin integration test, issue: https://github.com/youki-dev/youki/issues/56\n  # \"start/start.t\"\n  \"state/state.t\"\n\n  # The below tests have already been implemented in our integration tests, `contest`.\n  # \"delete/delete.t\"\n  # \"hooks/hooks.t\"\n  # \"hostname/hostname.t\"\n  # \"kill/kill.t\"\n  # # This case includes checking for features that are excluded from linux kernel 5.0, so even runc doesn't pass it.\n  # # ref. https://github.com/docker/cli/pull/2908\n  # # \"linux_cgroups_blkio/linux_cgroups_blkio.t\"\n  # \"linux_cgroups_cpus/linux_cgroups_cpus.t\"\n  # \"linux_cgroups_hugetlb/linux_cgroups_hugetlb.t\"\n  # \"linux_cgroups_memory/linux_cgroups_memory.t\"\n  # \"linux_cgroups_network/linux_cgroups_network.t\"\n  # \"linux_cgroups_pids/linux_cgroups_pids.t\"\n  # \"linux_cgroups_relative_network/linux_cgroups_relative_network.t\"\n  # \"linux_devices/linux_devices.t\"\n  # \"linux_masked_paths/linux_masked_paths.t\"\n  # # This test case hangs on the Github Action. Runtime-tools has an issue filed from 2019 that the clean up step hangs. Otherwise, the test case passes.\n  # # Ref: https://github.com/opencontainers/runtime-tools/issues/698\n  # # \"linux_ns_itype/linux_ns_itype.t\"\n  # \"linux_readonly_paths/linux_readonly_paths.t\"\n  # \"linux_rootfs_propagation/linux_rootfs_propagation.t\"\n  # \"linux_uid_mappings/linux_uid_mappings.t\"\n  # \"linux_seccomp/linux_seccomp.t\"\n  # \"linux_sysctl/linux_sysctl.t\"\n  # # This test case passed on local box, but not on Github Action. `runc` also fails on Github Action, so likely it is an issue with the test.\n  # # \"pidfile/pidfile.t\"\n  # \"process/process.t\"\n  # \"process_capabilities_fail/process_capabilities_fail.t\"\n  # \"process_oom_score_adj/process_oom_score_adj.t\"\n  # \"process_rlimits/process_rlimits.t\"\n  # \"process_rlimits_fail/process_rlimits_fail.t\"\n  # \"root_readonly_true/root_readonly_true.t\"\n  # \"process_user/process_user.t\"\n)\n\ncheck_environment() {\n  test_case=$1\n  if [[ $test_case =~ .*(memory|hugetlb).t ]]; then\n    if [[ ! -e \"/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes\" ]]; then\n        return 1\n    fi\n  fi\n  if [[ $test_case == \"delete_only_create_resources/delete_only_create_resources.t\" ]]; then\n    if [[ ! -e \"/sys/fs/cgroup/pids/cgrouptest/tasks\" ]]; then\n        return 1\n    fi\n  fi\n}\n\nif [[ ! -e $RUNTIME ]]; then\n  if ! which $RUNTIME ; then\n    echo \"$RUNTIME not found\"\n    exit 1\n  fi\nfi\n\nfor case in \"${test_cases[@]}\"; do\n  if [[ ! -e \"${OCI_TEST_DIR}/validation/$case\" ]]; then\n    GO111MODULE=auto GOPATH=${ROOT}/tests/oci-runtime-tests make runtimetest validation-executables\n    break\n  fi\ndone\n\n\nfor case in \"${test_cases[@]}\"; do\n  if ! check_environment $case; then\n    echo \"Skip $case because your environment doesn't support this test case\"\n    continue\n  fi\n\n  if [ $PATTERN != \".\" ] && [[ ! $case =~ $PATTERN ]]; then\n    continue\n  fi\n\n  echo \"Running $case\"\n  logfile=\"./log/$case.log\"\n  mkdir -p \"$(dirname $logfile)\"\n  sudo RUST_BACKTRACE=1 RUNTIME=${RUNTIME} ${OCI_TEST_DIR}/validation/$case >$logfile 2>&1 || (cat $logfile && exit 1)\n  if [ 0 -ne $(grep \"not ok\" $logfile | wc -l ) ]; then\n    if [ 0 -eq $(grep \"# cgroupv2 is not supported yet \" $logfile | wc -l ) ]; then\n      echo \"Skip $case because oci-runtime-tools doesn't support cgroup v2\"\n      continue;\n    fi\n    cat $logfile\n    exit 1\n  fi\n  sleep 1\ndone\n"
  },
  {
    "path": "scripts/release_tag.sh",
    "content": "#!/bin/bash\n\nTAG=${1}\n\nif [ -z \"$TAG\" ]; then\n    echo \"Error: No version number provided.\"\n    exit 1\nfi\nVERSION=${TAG##*v}\n\nSTART_MARKER=\"<!--youki release begin-->\"\nEND_MARKER=\"<!--youki release end-->\"\n\n\necho \"\\`\\`\\`console\n# curl -sSfL https://github.com/youki-dev/youki/releases/download/v${VERSION}/youki-${VERSION}-\\$(uname -m)-musl.tar.gz | tar -xzvC /usr/bin/ youki\n\\`\\`\\`\" > replace_content.txt\n\nawk -v start=\"$START_MARKER\" -v end=\"$END_MARKER\" -v newfile=\"replace_content.txt\" '\nBEGIN {printing=1}\n$0 ~ start {print;system(\"cat \" newfile);printing=0}\n$0 ~ end {printing=1}\nprinting' docs/src/user/basic_setup.md > temp.txt && mv temp.txt docs/src/user/basic_setup.md\n"
  },
  {
    "path": "tests/.gitignore",
    "content": "*_bin"
  },
  {
    "path": "tests/contest/contest/.gitignore",
    "content": "test_log.log"
  },
  {
    "path": "tests/contest/contest/Cargo.toml",
    "content": "[package]\nname = \"contest\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[dependencies]\nanyhow = \"1.0\"\nflate2 = \"1.1\"\nlibcgroups = { path = \"../../../crates/libcgroups\" }\nlibcontainer = { path = \"../../../crates/libcontainer\" }\nnix = \"0.29.0\"\nnum_cpus = \"1.17\"\noci-spec = { version = \"0.9.0\", features = [\"runtime\"] }\npnet_datalink = \"0.35.0\"\nprocfs = \"0.17.0\"\nrand = \"0.10.0\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\ntar = \"0.4\"\ntest_framework = { path = \"../test_framework\" }\nuuid = \"1.22\"\nwhich = \"8.0.2\"\ntempfile = \"3\"\nthiserror = \"2.0\"\ntracing = { version = \"0.1.44\", features = [\"attributes\"] }\ntracing-subscriber = { version = \"0.3.23\", features = [\"json\", \"env-filter\"] }\nregex = \"1\"\n\n[dependencies.clap]\nversion = \"4.5.13\"\ndefault-features = false\nfeatures = [\n    \"std\",\n    \"suggestions\",\n    \"derive\",\n    \"cargo\",\n    \"help\",\n    \"usage\",\n    \"error-context\",\n]\n"
  },
  {
    "path": "tests/contest/contest/README.md",
    "content": "# Integration test\n\nThis provides a test suite to test low level OCI spec compliant container runtime\n\n## Usage\n\n```console\n# in root folder\n$ ./build.sh\n$ cd crates/youki_integration_test\n$ cp ../youki .\n$ ./build.sh\n# currently root access is required\n$ sudo ./youki_integration_test -r ./youki\n```\n\nThis provides following commandline options :\n\n- --runtime (-r) : Required. Takes path of runtime executable to be tested. If the path is not valid, the program exits.\n- --tests (-t) : Optional. Takes a list of tests to be run, and runs only those tests. Format for it is : `test-grp-1::test-1,test-2 <space> test-grp-2 <space> test-grp-3::test-3 ...`. The test groups with no specific tests specified, (test-grp-2 in the example) , will run all of its tests, and in other cases, only selected tests will be run. Test groups not mentioned will be ignored.\n\n## Adding tests\n\nTo create and run tests, we use a custom test_framework, which resides in youki_integration_test/test_framework.\nIt provides some basic build in structs to define and run tests, but sometimes custom implementation of them might be required. For more info on the test_framework structs and how to use them, see the README.md file.\n\n### Types of tests\n\nThe main two ways to add tests are :\n\n- Use default provided Test, ConditionalTest and TestGroup structs. These are meant to be used for stateless tests, such as HugeTLB tests. Note that all these are run in parallel, so when using these defaults, please make sure there are no cross test dependencies. Currently **HugeTLB** tests are implemented in this way.\n- For stateful tests, make a custom struct, and implement the TestableGroup trait, which can be then given to TestManager. That way you can add state information in the struct, and define and control the ordering of the test. Currently **lifecycle** and **create** tests are implemented in this way. This is required, as for lifecycle tests, the commands must be run in specific order, e.g. create must be executed before run, which must be done before stop and so on. Though there is some improvement needed in these tests.\n\nWhen implementing tests, you should prefer one of these approaches. If required you can also make your own custom one.\n\nThe tests are modeled after the [OCI runtime tools](https://github.com/opencontainers/runtime-tools/tree/master/validation) tests. These tests should be taken as inspiration, but note that some of these tests are not passed successfully even by production level runtimes such as runc. The most important goal for these tests is that both youki and other runtimes are passing them successfully and to test these runtimes as carefully as possible.\n\n### Utils provided\n\nThis framework also has some test utils, meant to help doing common operations in tests, which should be used whenever possible. If you notice that you are using the same patterns over and over again, you should think about adding them to the test utils. Some notable function provided are:\n\n- generate_uuid : generates a unique id for the container\n- prepare_bundle : creates a temp directory, and sets up the bundle and default config.json. This folder is automatically deleted when dropped.\n- set_config : takes an OCI Spec struct, and saves it as config.json in the given bundle folder\n- create_container : runs the runtime command with create argument, with given id and with given bundle directory\n- kill_container: runs the runtime command with kill argument, with given id and with given bundle directory\n- delete_container : runs the runtime command with delete argument, with given id and with given bundle directory\n- get_state : runs the runtime command with state argument, with given id and with given bundle directory\n- test_outside_container : this is meant to mimic [RuntimeOutsideValidate](https://github.com/opencontainers/runtime-tools/blob/59cdde06764be8d761db120664020f0415f36045/validation/util/test.go#L263) function of original tests.\n- test_inside_container : this is meant to mimic [RuntimeInsideValidate](https://github.com/opencontainers/runtime-tools/blob/59cdde06764be8d761db120664020f0415f36045/validation/util/test.go#L180) function of original tests.\n- check_container_created: this checks if the container was created successfully.\n- test_result!: this is a macro, that allows you to convert from a Result<T,E> to a TestResult\n\nNote that even though all of the above functions are provided, most of the time the only required function is test_outside_container, as it does all the work of setting up the bundle, creating and running the container, getting the state of the container, killing the container and then deleting the container.\n\nIn case you manually call any of these functions, make sure that the folder, cgroups ,spawned processes and other stuff created are disposed correctly when the test function completes.\n\n### Test creation workflow\n\nUsually the test creation workflow will be something like :\n\n1. Create a new folder in src/tests with appropriate name.\n2. Make a function which will check if the test can be run or not on a given system. This is important, as some tests such as blkio or cgroups memory need the kernel to be configured with certain flags, without which the test cannot be run. **Note** that this is different from the test failing: the test should fail when it is capable of running, but gives unexpected results, whereas the test should be skipped / not run in the first place, when the host system does not support it.\n3. Create a function which will generate the OCI Spec required for test, using oci-spec-rs 's builder pattern. See the `make_hugetlb_spec` function in src/tests/tlb/tlb_test.rs to get an idea. Usually to understand the types of fields and which fields are available, you'll need to check [this file](https://github.com/youki-dev/oci-spec-rs/blob/main/src/runtime/linux.rs) and other source files to get an idea of what fields you need. The builder pattern is quite consistent, and using the github - vs code integration and doing a global search for what you need should do the trick.\n4. Write the test functions and whatever needed: custom structs, etc.\n5. Create a function which will create the custom/default tests, create a TestGroup/custom impl TestableGroup out of it, and return that. Make this function public, and `pub use` it from the mod.rs .\n6. In the src/main.rs, import your function, and run it to get an instance of your test group, for example see lines 58-60 ish, where lifecycle, create and huge_tlb structs are created.\n7. Add the reference of this to the test_manager in the main function.\n8. Run your tests individually, run your test group individually and then run the whole suite against youki and some other production level runtime, such as runc. As stated previously, the important thing is to make sure runc passes it as well.\n9. Make sure that the system is returned to the original state afterwards: make sure that no youki/runc/runtime process is running in the background, make sure that the /tmp (or respective on Windows/MacOS) does not have a uuid-looking directory in it, make sure that the /sys/fs/cgroup/\\* directories do not have a runtime sub-directory of uuid-looking directory in them.\n10. Commit, push and make a PR ;)\n\n### Some common issues/errors\n\nThis lists some of the things that can be tricky, and can cause issues in running tests. **In case you encounter something, please update this list**.\n\n- The create command should be waited by `wait`, and not `wait_with_output` on it after spawning if you are simply creating the container. The reason is, runtime process forks itself to create the container, and then keeps running to start the container. Thus if we try to `wait_with_output` on it, without having called `start` on it, it hangs. Trying to kill tests by `Ctrl+C` will cause the system to stay in modified state (/tmp directories, cgroup directories etc). In case you do this and need to end tests, open a new terminal and send a kill signal to the runtime process, that way that youki process will exit and the tests will continue.\n\n- The kill and state commands take time. Thus whenever running these, call `wait` or `wait_with_output` on the spawned process to make sure you do not accidentally modify the directories that these use. One example is when running tests, as temp directory deletes itself when dropped, it can cause a race condition when state, or kill command is spawned and not waited. This will cause the directory in /tmp to be deleted first in the drop, and then to get created again due to kill / state command. _In the start of this implementation this problem caused several days to be spent on debugging where the directory in /tmp is getting created from_.\n"
  },
  {
    "path": "tests/contest/contest/src/lib.rs",
    "content": "pub mod logger;\npub mod tests;\npub mod utils;\n"
  },
  {
    "path": "tests/contest/contest/src/logger.rs",
    "content": "use std::borrow::Cow;\nuse std::str::FromStr;\n\nuse anyhow::{Context, Result};\nuse tracing::metadata::LevelFilter;\n\nconst LOG_LEVEL_ENV_NAME: &str = \"YOUKI_INTEGRATION_LOG_LEVEL\";\n\n/// Initialize the logger, must be called before accessing the logger\n/// Multiple parts might call this at once, but the actual initialization\n/// is done only once due to use of OnceCell\npub fn init(debug: bool) -> Result<()> {\n    let level = detect_log_level(debug).context(\"failed to parse log level\")?;\n    tracing_subscriber::fmt().with_max_level(level).init();\n\n    Ok(())\n}\n\nfn detect_log_level(is_debug: bool) -> Result<LevelFilter> {\n    let filter: Cow<str> = if is_debug {\n        \"debug\".into()\n    } else if let Ok(level) = std::env::var(LOG_LEVEL_ENV_NAME) {\n        level.into()\n    } else {\n        \"off\".into()\n    };\n\n    Ok(LevelFilter::from_str(filter.as_ref())?)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/main.rs",
    "content": "mod tests;\nmod utils;\n\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result};\nuse clap::Parser;\nuse contest::logger;\nuse test_framework::TestManager;\nuse tests::cgroups;\n\nuse crate::tests::create_runtime::get_create_runtime_tests;\nuse crate::tests::delete::get_delete_test;\nuse crate::tests::devices::get_devices_test;\nuse crate::tests::domainname::get_domainname_tests;\nuse crate::tests::example::get_example_test;\nuse crate::tests::exec::get_exec_test;\nuse crate::tests::exec_cpu_affinity::get_exec_cpu_affinity_test;\nuse crate::tests::exec_env::get_exec_env_test;\nuse crate::tests::fd_control::get_fd_control_test;\nuse crate::tests::hooks::get_hooks_tests;\nuse crate::tests::hostname::get_hostname_test;\nuse crate::tests::intel_rdt::get_intel_rdt_test;\nuse crate::tests::io_priority::get_io_priority_test;\nuse crate::tests::kill::get_kill_test;\nuse crate::tests::kill_no_effect::get_kill_no_effect_test;\nuse crate::tests::lifecycle::{ContainerCreate, ContainerLifecycle};\nuse crate::tests::linux_masked_paths::get_linux_masked_paths_tests;\nuse crate::tests::linux_ns_itype::get_ns_itype_tests;\nuse crate::tests::memory_policy::get_linux_memory_policy_tests;\nuse crate::tests::misc_props::get_misc_props_test;\nuse crate::tests::mounts_recursive::get_mounts_recursive_test;\nuse crate::tests::net_devices::get_net_devices_test;\nuse crate::tests::no_pivot::get_no_pivot_test;\nuse crate::tests::personality::get_personality_test;\nuse crate::tests::pidfile::get_pidfile_test;\nuse crate::tests::poststart::get_poststart_tests;\nuse crate::tests::poststart_fail::get_poststart_fail_tests;\nuse crate::tests::poststop::get_poststop_tests;\nuse crate::tests::prestart::get_prestart_tests;\nuse crate::tests::prestart_fail::get_prestart_fail_tests;\nuse crate::tests::process::get_process_test;\nuse crate::tests::process_capabilities_fail::get_process_capabilities_fail_test;\nuse crate::tests::process_oom_score_adj::get_process_oom_score_adj_test;\nuse crate::tests::process_rlimits::get_process_rlimits_test;\nuse crate::tests::process_rlimits_fail::get_process_rlimits_fail_test;\nuse crate::tests::process_user::get_process_user_test;\nuse crate::tests::prohibit_symlink::get_prohibit_symlink_test;\nuse crate::tests::readonly_paths::get_ro_paths_test;\nuse crate::tests::root_readonly_true::get_root_readonly_test;\nuse crate::tests::rootfs_propagation::get_rootfs_propagation_test;\nuse crate::tests::scheduler::get_scheduler_test;\nuse crate::tests::seccomp::get_seccomp_test;\nuse crate::tests::seccomp_notify::get_seccomp_notify_test;\nuse crate::tests::sysctl::get_sysctl_test;\nuse crate::tests::tlb::get_tlb_test;\nuse crate::tests::uid_mappings::get_uid_mappings_test;\nuse crate::utils::support::{set_runtime_path, set_runtimetest_path};\n\n#[derive(Parser, Debug)]\n#[clap(version = \"0.0.1\", author = \"youki team\")]\nstruct Opts {\n    /// Enables debug output\n    #[clap(short, long)]\n    debug: bool,\n\n    #[clap(subcommand)]\n    command: SubCommand,\n}\n\n#[derive(Parser, Debug)]\nenum SubCommand {\n    /// run the integration tests\n    Run(Run),\n    /// list available integration tests\n    List,\n}\n\n#[derive(Parser, Debug)]\nstruct Run {\n    /// Path for the container runtime to be tested\n    #[clap(long)]\n    runtime: PathBuf,\n    /// Path for the runtimetest binary, which will be used to run tests inside the container\n    #[clap(long)]\n    runtimetest: PathBuf,\n    /// Selected tests to be run, format should be\n    /// space separated groups, eg\n    /// -t group1::test1,test3 group2 group3::test5\n    #[clap(short, long, num_args(1..), value_delimiter = ' ')]\n    tests: Option<Vec<String>>,\n}\n\n// parse test string given in commandline option as pair of testgroup name and tests belonging to that\nfn parse_tests(tests: &[String]) -> Vec<(&str, Option<Vec<&str>>)> {\n    let mut ret = Vec::with_capacity(tests.len());\n    for test in tests {\n        if test.contains(\"::\") {\n            let (mod_name, test_names) = test.split_once(\"::\").unwrap();\n            let _tests = test_names.split(',').collect();\n            ret.push((mod_name, Some(_tests)));\n        } else {\n            ret.push((test, None));\n        }\n    }\n    ret\n}\n\nfn main() -> Result<()> {\n    let opts: Opts = Opts::parse();\n\n    if let Err(e) = logger::init(opts.debug) {\n        eprintln!(\"logger could not be initialized: {e:?}\");\n    }\n\n    ////////// ANCHOR: register_example_test\n    let mut tm = TestManager::new();\n    let example = get_example_test();\n    tm.add_test_group(Box::new(example));\n    ////////// ANCHOR_END: register_example_test\n\n    let cl = ContainerLifecycle::new();\n    let cc = ContainerCreate::new();\n    let huge_tlb = get_tlb_test();\n    let pidfile = get_pidfile_test();\n    let ns_itype = get_ns_itype_tests();\n    let hooks = get_hooks_tests();\n    let poststart = get_poststart_tests();\n    let poststop = get_poststop_tests();\n    let poststart_fail = get_poststart_fail_tests();\n    let prestart = get_prestart_tests();\n    let create_runtime = get_create_runtime_tests();\n    let prestart_fail = get_prestart_fail_tests();\n    let cgroup_v1_pids = cgroups::pids::get_test_group();\n    let cgroup_v1_cpu = cgroups::cpu::v1::get_test_group();\n    let cgroup_v2_cpu = cgroups::cpu::v2::get_test_group();\n    let cgroup_v1_memory = cgroups::memory::get_test_group();\n    let cgroup_v1_blkio = cgroups::blkio::get_test_group();\n    let cgroup_v1_absolute_network = cgroups::network::absolute_network::get_test_group();\n    let cgroup_v1_relative_network = cgroups::network::relative_network::get_test_group();\n    let seccomp = get_seccomp_test();\n    let seccomp_notify = get_seccomp_notify_test();\n    let ro_paths = get_ro_paths_test();\n    let hostname = get_hostname_test();\n    let misc_props = get_misc_props_test();\n    let mounts_recursive = get_mounts_recursive_test();\n    let domainname = get_domainname_tests();\n    let intel_rdt = get_intel_rdt_test();\n    let sysctl = get_sysctl_test();\n    let scheduler = get_scheduler_test();\n    let memory_policy = get_linux_memory_policy_tests();\n    let io_priority_test = get_io_priority_test();\n    let delete = get_delete_test();\n    let devices = get_devices_test();\n    let root_readonly = get_root_readonly_test();\n    let process = get_process_test();\n    let process_user = get_process_user_test();\n    let process_rlimtis = get_process_rlimits_test();\n    let process_rlimits_fail = get_process_rlimits_fail_test();\n    let no_pivot = get_no_pivot_test();\n    let process_oom_score_adj = get_process_oom_score_adj_test();\n    let fd_control = get_fd_control_test();\n    let kill = get_kill_test();\n    let kill_no_effect = get_kill_no_effect_test();\n    let masked_paths = get_linux_masked_paths_tests();\n    let rootfs_propagation = get_rootfs_propagation_test();\n    let process_capabilities_fail = get_process_capabilities_fail_test();\n    let uid_mappings = get_uid_mappings_test();\n    let exec_cpu_affinity = get_exec_cpu_affinity_test();\n    let exec_env = get_exec_env_test();\n    let exec = get_exec_test();\n    let personality = get_personality_test();\n    let prohibit_symlink = get_prohibit_symlink_test();\n    let net_devices = get_net_devices_test();\n\n    tm.add_test_group(Box::new(cl));\n    tm.add_test_group(Box::new(cc));\n    tm.add_test_group(Box::new(huge_tlb));\n    tm.add_test_group(Box::new(pidfile));\n    tm.add_test_group(Box::new(ns_itype));\n    tm.add_test_group(Box::new(hooks));\n    tm.add_test_group(Box::new(poststart));\n    tm.add_test_group(Box::new(poststart_fail));\n    tm.add_test_group(Box::new(poststop));\n    tm.add_test_group(Box::new(prestart));\n    tm.add_test_group(Box::new(create_runtime));\n    tm.add_test_group(Box::new(prestart_fail));\n    tm.add_test_group(Box::new(cgroup_v1_pids));\n    tm.add_test_group(Box::new(cgroup_v1_cpu));\n    tm.add_test_group(Box::new(cgroup_v2_cpu));\n    tm.add_test_group(Box::new(cgroup_v1_memory));\n    tm.add_test_group(Box::new(cgroup_v1_absolute_network));\n    tm.add_test_group(Box::new(cgroup_v1_blkio));\n    tm.add_test_group(Box::new(cgroup_v1_relative_network));\n    tm.add_test_group(Box::new(seccomp));\n    tm.add_test_group(Box::new(seccomp_notify));\n    tm.add_test_group(Box::new(ro_paths));\n    tm.add_test_group(Box::new(hostname));\n    tm.add_test_group(Box::new(misc_props));\n    tm.add_test_group(Box::new(mounts_recursive));\n    tm.add_test_group(Box::new(domainname));\n    tm.add_test_group(Box::new(intel_rdt));\n    tm.add_test_group(Box::new(sysctl));\n    tm.add_test_group(Box::new(scheduler));\n    tm.add_test_group(Box::new(memory_policy));\n    tm.add_test_group(Box::new(delete));\n    tm.add_test_group(Box::new(devices));\n    tm.add_test_group(Box::new(root_readonly));\n    tm.add_test_group(Box::new(process));\n    tm.add_test_group(Box::new(process_user));\n    tm.add_test_group(Box::new(process_rlimtis));\n    tm.add_test_group(Box::new(process_rlimits_fail));\n    tm.add_test_group(Box::new(no_pivot));\n    tm.add_test_group(Box::new(masked_paths));\n    tm.add_test_group(Box::new(process_oom_score_adj));\n    tm.add_test_group(Box::new(fd_control));\n    tm.add_test_group(Box::new(kill));\n    tm.add_test_group(Box::new(kill_no_effect));\n    tm.add_test_group(Box::new(rootfs_propagation));\n    tm.add_test_group(Box::new(net_devices));\n    tm.add_test_group(Box::new(process_capabilities_fail));\n    tm.add_test_group(Box::new(uid_mappings));\n    tm.add_test_group(Box::new(exec_cpu_affinity));\n    tm.add_test_group(Box::new(exec_env));\n    tm.add_test_group(Box::new(exec));\n    tm.add_test_group(Box::new(personality));\n    tm.add_test_group(Box::new(prohibit_symlink));\n    tm.add_test_group(Box::new(io_priority_test));\n    tm.add_cleanup(Box::new(cgroups::cleanup_v1));\n    tm.add_cleanup(Box::new(cgroups::cleanup_v2));\n\n    match opts.command {\n        SubCommand::Run(args) => run(args, &tm).context(\"run tests\")?,\n        SubCommand::List => list(&tm).context(\"list tests\")?,\n    }\n\n    Ok(())\n}\n\nfn get_abs_path(rel_path: &Path) -> PathBuf {\n    match std::fs::canonicalize(rel_path) {\n        // path is relative or resolved correctly\n        Ok(path) => path,\n        // path is name of program which probably exists in $PATH\n        Err(_) => match which::which(rel_path) {\n            Ok(path) => path,\n            Err(e) => {\n                eprintln!(\"Error in finding path {rel_path:?} : {e}\\nexiting.\");\n                std::process::exit(66);\n            }\n        },\n    }\n}\n\nfn run(opts: Run, test_manager: &TestManager) -> Result<()> {\n    let runtime_path = get_abs_path(&opts.runtime);\n    set_runtime_path(&runtime_path);\n\n    let runtimetest_path = get_abs_path(&opts.runtimetest);\n    set_runtimetest_path(&runtimetest_path);\n\n    if let Some(tests) = opts.tests {\n        let tests_to_run = parse_tests(&tests);\n        test_manager.run_selected(tests_to_run);\n    } else {\n        test_manager.run_all();\n    }\n\n    Ok(())\n}\n\nfn list(test_manager: &TestManager) -> Result<()> {\n    for test_group in test_manager.tests_groups() {\n        println!(\"{test_group}\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/blkio.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, bail};\nuse oci_spec::runtime::{\n    LinuxBlockIo, LinuxBlockIoBuilder, LinuxBuilder, LinuxResourcesBuilder,\n    LinuxThrottleDeviceBuilder, LinuxWeightDeviceBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::{CGROUP_ROOT, check_container_created};\n\n// -----> README\n// for this test to work for all parameters, the kernel needs to be compiled with CFQ IO schedular\n// for some reason, Ubuntu and other distributions might come with a kernel compiled with mq-deadline\n// schedular, which does not expose/support options such as blkio.weight, blkio.weight_device\n// for these we can skip all the tests, or skip the corresponding tests\n// the current implementation skips corresponding tests, so one can test atleast bps and iops\n// device on such systems\n// check https://superuser.com/questions/1449688/a-couple-of-blkio-cgroup-files-are-not-present-in-linux-kernel-5\n// and https://github.com/opencontainers/runc/issues/140\n// for more info on this\n\n#[derive(Debug, Default)]\nstruct WeightDevice {\n    major: i64,\n    minor: i64,\n    weight: Option<u16>,\n    leaf_weight: Option<u16>,\n}\n\n#[derive(Debug, Default)]\nstruct ThrottleDevice {\n    major: i64,\n    minor: i64,\n    rate: u64,\n}\n\n#[derive(Debug, Default)]\nstruct BlockIO {\n    weight: u16,\n    leaf_weight: u16,\n    weight_devices: Vec<WeightDevice>,\n    throttle_read_bps_devices: Vec<ThrottleDevice>,\n    throttle_write_bps_devices: Vec<ThrottleDevice>,\n    throttle_read_iops_devices: Vec<ThrottleDevice>,\n    throttle_write_iops_devices: Vec<ThrottleDevice>,\n}\n\nfn can_run() -> bool {\n    Path::new(\"/sys/fs/cgroup/blkio\").exists()\n}\n\nfn supports_weight() -> bool {\n    Path::new(\"/sys/fs/cgroup/blkio/blkio.weight\").exists()\n}\n\nfn supports_weight_devices() -> bool {\n    Path::new(\"/sys/fs/cgroup/blkio/blkio.weight_devices\").exists()\n}\n\n// even though the mq-deadline does have these,we better check in case\n// for some system these are absent\nfn supports_throttle_bps() -> bool {\n    Path::new(\"/sys/fs/cgroup/blkio/blkio.throttle.read_bps_device\").exists()\n}\n\nfn supports_throttle_iops() -> bool {\n    Path::new(\"/sys/fs/cgroup/blkio/blkio.throttle.read_iops_device\").exists()\n}\n\nfn parse_device_data<'a>(device_type: &'static str, line: &'a str) -> Result<(i64, i64, &'a str)> {\n    let (device_id, value) = line\n        .split_once(' ')\n        .with_context(|| format!(\"invalid {device_type} device format : found {line}\"))?;\n    let (major_str, minor_str) = device_id.split_once(':').with_context(|| {\n        format!(\"invalid major-minor number format for {device_type} device : found {device_id}\")\n    })?;\n\n    let major: i64 = major_str.parse().with_context(|| {\n        format!(\"Error in parsing {device_type} device major number : found {major_str}\")\n    })?;\n    let minor: i64 = minor_str.parse().with_context(|| {\n        format!(\"Error in parsing {device_type} device minor number : found {minor_str}\")\n    })?;\n\n    Ok((major, minor, value))\n}\n\nfn create_spec(cgroup_name: &str, block_io: LinuxBlockIo) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .block_io(block_io)\n                        .build()\n                        .context(\"failed to build resource spec\")?,\n                )\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\n/// parses /sys/fs/cgroup/blkio and creates BlockIO struct\nfn get_blkio_data(path: &Path) -> Result<BlockIO> {\n    let mut device = BlockIO::default();\n\n    // we assume that if weight is present, leaf_weight will also be present\n    if supports_weight() {\n        // weight\n        let weight_path = path.join(\"blkio.weight\");\n        let weight_string = fs::read_to_string(&weight_path)\n            .with_context(|| format!(\"error in reading block io weight from {weight_path:?}\"))?;\n        device.weight = weight_string\n            .parse()\n            .with_context(|| format!(\"error in parsing block io weight : found {weight_string}\"))?;\n\n        // leaf weight\n        let leaf_weight_path = path.join(\"blkio.leaf_weight\");\n        let leaf_weight_string = fs::read_to_string(&leaf_weight_path).with_context(|| {\n            format!(\"error in reading block io leaf weight from {leaf_weight_path:?}\")\n        })?;\n        device.leaf_weight = leaf_weight_string.parse().with_context(|| {\n            format!(\"error in parsing block io weight : found {leaf_weight_string}\")\n        })?;\n    }\n\n    // weight devices section ------------\n    // we assume if device_weight is supported, then device_leaf_weight is also supported\n    if supports_weight_devices() {\n        // device weight\n        let device_weight_path = path.join(\"blkio.weight_device\");\n        let device_weight_string = fs::read_to_string(&device_weight_path).with_context(|| {\n            format!(\"error in reading block io weight device from {device_weight_path:?}\")\n        })?;\n        let mut weight_devices = Vec::new();\n        // format is  <major>:<minor>  <bytes_per_second>\n        for line in device_weight_string.lines() {\n            let (major, minor, weight_str) = parse_device_data(\"weight\", line)?;\n            weight_devices.push(WeightDevice {\n                major,\n                minor,\n                weight: Some(weight_str.parse().with_context(|| {\n                    format!(\"error in parsing weight of weight device, found {weight_str}\")\n                })?),\n                leaf_weight: None,\n            });\n        }\n\n        // device leaf weight\n        let device_leaf_weight_path = path.join(\"blkio.leaf_weight_device\");\n        let device_leaf_weight_string =\n            fs::read_to_string(&device_leaf_weight_path).with_context(|| {\n                format!(\n                    \"error in reading block io leaf weight device from {device_leaf_weight_path:?}\"\n                )\n            })?;\n\n        for line in device_leaf_weight_string.lines() {\n            let (major, minor, weight_str) = parse_device_data(\"weight\", line)?;\n            let leaf_weight: u16 = weight_str.parse().with_context(|| {\n                format!(\"error in parsing leaf weight of weight device : found {weight_str}\")\n            })?;\n            let mut found = false;\n            for dev in &mut weight_devices {\n                if dev.major == major && dev.minor == minor {\n                    dev.leaf_weight = Some(leaf_weight);\n                    found = true;\n                }\n            }\n            if !found {\n                weight_devices.push(WeightDevice {\n                    major,\n                    minor,\n                    weight: None,\n                    leaf_weight: Some(leaf_weight),\n                });\n            }\n        }\n\n        device.weight_devices = weight_devices;\n    }\n\n    // throttle devices section -----\n\n    // we assume that if read_bps is supported, write_bps is also supported\n    if supports_throttle_bps() {\n        // throttle read bps\n        let throttle_read_bps_path = path.join(\"blkio.throttle.read_bps_device\");\n        let throttle_read_bps_string =\n            fs::read_to_string(&throttle_read_bps_path).with_context(|| {\n                format!(\n                    \"error in reading block io read bps device from  {throttle_read_bps_path:?}\"\n                )\n            })?;\n        let mut throttle_devices = Vec::new();\n        for line in throttle_read_bps_string.lines() {\n            let (major, minor, rate_str) = parse_device_data(\"throttle read bps\", line)?;\n            throttle_devices.push(ThrottleDevice {\n                major,\n                minor,\n                rate: rate_str.parse().with_context(|| {\n                    format!(\"error in parsing throttle read bps rate : found {rate_str}\")\n                })?,\n            });\n        }\n        device.throttle_read_bps_devices = throttle_devices;\n\n        // throttle write bps\n        let throttle_write_bps_path = path.join(\"blkio.throttle.write_bps_device\");\n        let throttle_write_bps_string =\n            fs::read_to_string(&throttle_write_bps_path).with_context(|| {\n                format!(\n                    \"error in reading block io write bps device from {throttle_write_bps_path:?}\"\n                )\n            })?;\n        let mut throttle_devices = Vec::new();\n        for line in throttle_write_bps_string.lines() {\n            let (major, minor, rate_str) = parse_device_data(\"throttle write bps\", line)?;\n            throttle_devices.push(ThrottleDevice {\n                major,\n                minor,\n                rate: rate_str.parse().with_context(|| {\n                    format!(\"error in parsing throttle write bps rate : found {rate_str}\")\n                })?,\n            });\n        }\n        device.throttle_write_bps_devices = throttle_devices;\n    }\n\n    // we assume that is read_iops is supported, write_iops is also supported\n    if supports_throttle_iops() {\n        // throttle read iops\n        let throttle_read_iops_path = path.join(\"blkio.throttle.read_iops_device\");\n        let throttle_read_iops_string =\n            fs::read_to_string(&throttle_read_iops_path).with_context(|| {\n                format!(\n                    \"error in reading block io read iops device from  {throttle_read_iops_path:?}\"\n                )\n            })?;\n        let mut throttle_devices = Vec::new();\n        for line in throttle_read_iops_string.lines() {\n            let (major, minor, rate_str) = parse_device_data(\"throttle read iops\", line)?;\n            throttle_devices.push(ThrottleDevice {\n                major,\n                minor,\n                rate: rate_str.parse().with_context(|| {\n                    format!(\"error in parsing throttle read iops rate : found {rate_str}\")\n                })?,\n            });\n        }\n        device.throttle_read_iops_devices = throttle_devices;\n\n        // throttle write iops\n        let throttle_write_iops_path = path.join(\"blkio.throttle.write_iops_device\");\n        let throttle_write_iops_string = fs::read_to_string(&throttle_write_iops_path)\n            .with_context(|| {\n                format!(\n                    \"error in reading block io write iops device from {throttle_write_iops_path:?}\"\n                )\n            })?;\n        let mut throttle_devices = Vec::new();\n        for line in throttle_write_iops_string.lines() {\n            let (major, minor, rate_str) = parse_device_data(\"throttle write iop\", line)?;\n            throttle_devices.push(ThrottleDevice {\n                major,\n                minor,\n                rate: rate_str.parse().with_context(|| {\n                    format!(\"error in parsing throttle write iops rate : found {rate_str}\")\n                })?,\n            });\n        }\n        device.throttle_write_iops_devices = throttle_devices;\n    }\n\n    Ok(device)\n}\n\n/// validates the BlockIO structure parsed from /sys/fs/cgroup/blkio\n/// with the spec\nfn validate_block_io(cgroup_name: &str, spec: &Spec) -> Result<()> {\n    let cgroup_path = PathBuf::from(CGROUP_ROOT)\n        .join(\"blkio/runtime-test\")\n        .join(cgroup_name);\n    let block_io = get_blkio_data(&cgroup_path)?;\n\n    let resources = spec.linux().as_ref().unwrap().resources().as_ref().unwrap();\n    let spec_block_io = resources.block_io().as_ref().unwrap();\n    // weight ------\n    if supports_weight() {\n        if spec_block_io.weight().is_none() {\n            bail!(\"spec block io weight is none\");\n        }\n        if spec_block_io.weight().unwrap() != block_io.weight {\n            bail!(\n                \"block io weight is set incorrectly, expected {}, actual {}\",\n                spec_block_io.weight().unwrap(),\n                block_io.weight,\n            );\n        }\n        if spec_block_io.leaf_weight().is_none() {\n            bail!(\"spec block io leaf weight is none\");\n        }\n        if spec_block_io.leaf_weight().unwrap() != block_io.leaf_weight {\n            bail!(\n                \"block io leaf weight is set incorrectly, expected {}, actual {}\",\n                spec_block_io.leaf_weight().unwrap(),\n                block_io.leaf_weight,\n            );\n        }\n    }\n\n    // weight devices ------\n    if supports_weight_devices() {\n        for spec_device in spec_block_io.weight_device().as_ref().unwrap() {\n            let spec_major = spec_device.major();\n            let spec_minor = spec_device.minor();\n            let mut found = false;\n            for device in &block_io.weight_devices {\n                if device.major == spec_major && device.minor == spec_minor {\n                    found = true;\n                    if device.weight != spec_device.weight() {\n                        bail!(\n                            \"blkio weight is set incorrectly for device {}:{}, expected {:?}, found {:?}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.weight(),\n                            device.weight\n                        );\n                    }\n                    if device.leaf_weight != spec_device.leaf_weight() {\n                        bail!(\n                            \"blkio leaf weight is set incorrectly for device {}:{}, expected {:?}, found {:?}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.leaf_weight(),\n                            device.leaf_weight\n                        );\n                    }\n                    break;\n                }\n            }\n            if !found {\n                bail!(\n                    \"blkio weight device {}:{} not found, exists in spec\",\n                    spec_major,\n                    spec_minor\n                );\n            }\n        }\n    }\n    // throttle bps ------\n    if supports_throttle_bps() {\n        for spec_device in spec_block_io.throttle_read_bps_device().as_ref().unwrap() {\n            let spec_major = spec_device.major();\n            let spec_minor = spec_device.minor();\n            let mut found = false;\n            for device in &block_io.throttle_read_bps_devices {\n                if device.major == spec_major && device.minor == spec_minor {\n                    found = true;\n                    if device.rate != spec_device.rate() {\n                        bail!(\n                            \"blkio throttle read bps rate is set incorrectly for device {}:{}, expected {}, found {}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.rate(),\n                            device.rate\n                        );\n                    }\n                    break;\n                }\n            }\n            if !found {\n                bail!(\n                    \"blkio throttle read bps device {}:{} not found, exists in spec\",\n                    spec_major,\n                    spec_minor\n                );\n            }\n        }\n        for spec_device in spec_block_io.throttle_write_bps_device().as_ref().unwrap() {\n            let spec_major = spec_device.major();\n            let spec_minor = spec_device.minor();\n            let mut found = false;\n            for device in &block_io.throttle_write_bps_devices {\n                if device.major == spec_major && device.minor == spec_minor {\n                    found = true;\n                    if device.rate != spec_device.rate() {\n                        bail!(\n                            \"blkio throttle write bps rate is set incorrectly for device {}:{}, expected {}, found {}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.rate(),\n                            device.rate\n                        );\n                    }\n                    break;\n                }\n            }\n            if !found {\n                bail!(\n                    \"blkio throttle write bps device {}:{} not found, exists in spec\",\n                    spec_major,\n                    spec_minor\n                );\n            }\n        }\n    }\n\n    // throttle iops ------\n    if supports_throttle_iops() {\n        for spec_device in spec_block_io.throttle_read_iops_device().as_ref().unwrap() {\n            let spec_major = spec_device.major();\n            let spec_minor = spec_device.minor();\n            let mut found = false;\n            for device in &block_io.throttle_read_iops_devices {\n                if device.major == spec_major && device.minor == spec_minor {\n                    found = true;\n                    if device.rate != spec_device.rate() {\n                        bail!(\n                            \"blkio throttle read iops rate is set incorrectly for device {}:{}, expected {}, found {}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.rate(),\n                            device.rate\n                        );\n                    }\n                    break;\n                }\n            }\n            if !found {\n                bail!(\n                    \"blkio throttle read iops device {}:{} not found, exists in spec\",\n                    spec_major,\n                    spec_minor\n                );\n            }\n        }\n\n        for spec_device in spec_block_io.throttle_write_iops_device().as_ref().unwrap() {\n            let spec_major = spec_device.major();\n            let spec_minor = spec_device.minor();\n            let mut found = false;\n            for device in &block_io.throttle_write_iops_devices {\n                if device.major == spec_major && device.minor == spec_minor {\n                    found = true;\n                    if device.rate != spec_device.rate() {\n                        bail!(\n                            \"blkio throttle write iops rate is set incorrectly for device {}:{}, expected {}, found {}\",\n                            spec_major,\n                            spec_minor,\n                            spec_device.rate(),\n                            device.rate\n                        );\n                    }\n                    break;\n                }\n            }\n            if !found {\n                bail!(\n                    \"blkio throttle write iops device {}:{} not found, exists in spec\",\n                    spec_major,\n                    spec_minor\n                );\n            }\n        }\n    }\n\n    Ok(())\n}\n\nfn test_blkio(test_name: &str, rate: u64, empty: bool) -> TestResult {\n    // these \"magic\" numbers are taken from the original tests\n    // https://github.com/opencontainers/runtime-tools/blob/1684d131456a6bc99b8e96aa4a99783f21e58d79/validation/linux_cgroups_blkio/linux_cgroups_blkio.go#L13-L20\n    let weight: u16 = 500;\n    let leaf_weight: u16 = 300;\n    let major: i64 = 8;\n    let minor: i64 = 0;\n\n    let mut block_io_builder = LinuxBlockIoBuilder::default();\n\n    if !empty && supports_weight() {\n        block_io_builder = block_io_builder.weight(weight).leaf_weight(leaf_weight)\n    }\n    if supports_weight_devices() {\n        block_io_builder = block_io_builder.weight_device(vec![\n            LinuxWeightDeviceBuilder::default()\n                .major(major)\n                .minor(minor)\n                .weight(weight)\n                .build()\n                .unwrap(),\n            LinuxWeightDeviceBuilder::default()\n                .major(major)\n                .minor(minor)\n                .leaf_weight(leaf_weight)\n                .build()\n                .unwrap(),\n        ])\n    }\n    if supports_throttle_bps() {\n        block_io_builder = block_io_builder\n            .throttle_read_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(major)\n                    .minor(minor)\n                    .rate(rate)\n                    .build()\n                    .unwrap(),\n            ])\n            .throttle_write_bps_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(major)\n                    .minor(minor)\n                    .rate(rate)\n                    .build()\n                    .unwrap(),\n            ])\n    }\n    if supports_throttle_iops() {\n        block_io_builder = block_io_builder\n            .throttle_read_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(major)\n                    .minor(minor)\n                    .rate(rate)\n                    .build()\n                    .unwrap(),\n            ])\n            .throttle_write_iops_device(vec![\n                LinuxThrottleDeviceBuilder::default()\n                    .major(major)\n                    .minor(minor)\n                    .rate(rate)\n                    .build()\n                    .unwrap(),\n            ]);\n    }\n    let spec = create_spec(\n        test_name,\n        block_io_builder\n            .build()\n            .context(\"failed to build block io spec\")\n            .unwrap(),\n    )\n    .unwrap();\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(validate_block_io(test_name, &spec));\n        TestResult::Passed\n    })\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_blkio\");\n    let non_empty_100kb = ConditionalTest::new(\n        \"non_empty_100kb\",\n        Box::new(can_run),\n        Box::new(|| test_blkio(\"non_empty_100kb\", 102400, false)),\n    );\n\n    let non_empty_200kb = ConditionalTest::new(\n        \"non_empty_200kb\",\n        Box::new(can_run),\n        Box::new(|| test_blkio(\"non_empty_200kb\", 204800, false)),\n    );\n    let empty_100kb = ConditionalTest::new(\n        \"empty_100kb\",\n        Box::new(can_run),\n        Box::new(|| test_blkio(\"empty_100kb\", 102400, true)),\n    );\n    let empty_200kb = ConditionalTest::new(\n        \"empty_200kb\",\n        Box::new(can_run),\n        Box::new(|| test_blkio(\"empty_200kb\", 204800, true)),\n    );\n\n    test_group.add(vec![\n        Box::new(non_empty_100kb),\n        Box::new(non_empty_200kb),\n        Box::new(empty_100kb),\n        Box::new(empty_200kb),\n    ]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/cpu/mod.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Context, Result};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxCpu, LinuxCpuBuilder, LinuxResourcesBuilder, Spec, SpecBuilder,\n};\n\npub mod v1;\npub mod v2;\n\n#[allow(clippy::too_many_arguments)]\nfn create_cpu_spec(\n    shares: u64,\n    quota: i64,\n    period: u64,\n    idle_opt: Option<i64>,\n    cpus: &str,\n    mems: &str,\n    realtime_period_opt: Option<u64>,\n    realtime_runtime_opt: Option<i64>,\n) -> Result<LinuxCpu> {\n    let mut builder = LinuxCpuBuilder::default()\n        .shares(shares)\n        .quota(quota)\n        .period(period)\n        .cpus(cpus)\n        .mems(mems);\n\n    if let Some(idle) = idle_opt {\n        builder = builder.idle(idle);\n    }\n\n    if let Some(realtime_period) = realtime_period_opt {\n        builder = builder.realtime_period(realtime_period);\n    }\n\n    if let Some(realtime_runtime) = realtime_runtime_opt {\n        builder = builder.realtime_runtime(realtime_runtime);\n    }\n\n    builder.build().context(\"failed to build cpu spec\")\n}\n\nfn create_spec(cgroup_name: &str, case: LinuxCpu) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .cpu(case)\n                        .build()\n                        .context(\"failed to build resource spec\")?,\n                )\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn create_empty_spec(cgroup_name: &str) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .cpu(\n                            LinuxCpuBuilder::default()\n                                .build()\n                                .context(\"failed to build cpus spec\")?,\n                        )\n                        .build()\n                        .context(\"failed to build resource spec\")?,\n                )\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/cpu/v1.rs",
    "content": "use std::path::Path;\n\nuse libcgroups::common;\nuse num_cpus;\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse super::{create_cpu_spec, create_empty_spec, create_spec};\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nconst CPU_CGROUP_PREFIX: &str = \"/sys/fs/cgroup/cpu,cpuacct\";\nconst DEFAULT_REALTIME_PERIOD: u64 = 1000000;\nconst DEFAULT_REALTIME_RUNTIME: i64 = 950000;\n\nfn get_realtime_period() -> Option<u64> {\n    if Path::new(CPU_CGROUP_PREFIX)\n        .join(\"cpu.rt_period_us\")\n        .exists()\n    {\n        return Some(DEFAULT_REALTIME_PERIOD);\n    }\n    None\n}\n\nfn get_realtime_runtime() -> Option<i64> {\n    if Path::new(CPU_CGROUP_PREFIX)\n        .join(\"cpu.rt_runtime_us\")\n        .exists()\n    {\n        return Some(DEFAULT_REALTIME_RUNTIME);\n    }\n    None\n}\n\nfn test_cpu_cgroups() -> TestResult {\n    let cgroup_name = \"test_cpu_cgroups\";\n    // Kernel counts 0 as a CPU, so on a system with 8 logical cores you will need `0-7` range set.\n    let cpu_range = format!(\"0-{}\", num_cpus::get().saturating_sub(1));\n\n    let realtime_period = get_realtime_period();\n    let realtime_runtime = get_realtime_runtime();\n\n    let cases = vec![\n        test_result!(create_cpu_spec(\n            1024,\n            100000,\n            50000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            100000,\n            50000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            100000,\n            200000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            100000,\n            200000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            500000,\n            50000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            500000,\n            50000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            500000,\n            200000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            1024,\n            500000,\n            200000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            100000,\n            50000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            100000,\n            50000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            100000,\n            200000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            100000,\n            200000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            500000,\n            50000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            500000,\n            50000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            500000,\n            200000,\n            None,\n            \"0\",\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n        test_result!(create_cpu_spec(\n            2048,\n            500000,\n            200000,\n            None,\n            &cpu_range,\n            \"0\",\n            realtime_period,\n            realtime_runtime,\n        )),\n    ];\n\n    for case in cases.into_iter() {\n        let spec = test_result!(create_spec(cgroup_name, case));\n        let test_result = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n\n            TestResult::Passed\n        });\n\n        if let TestResult::Failed(_) = test_result {\n            return test_result;\n        }\n    }\n\n    TestResult::Passed\n}\n\nfn test_empty_cpu() -> TestResult {\n    let cgroup_name = \"test_empty_cpu\";\n    let spec = test_result!(create_empty_spec(cgroup_name));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        TestResult::Passed\n    })\n}\n\n// Tests if a cpu idle value can be set\nfn test_cpu_idle_set() -> TestResult {\n    let idle: i64 = 1;\n    let realtime_period = get_realtime_period();\n    let realtime_runtime = get_realtime_runtime();\n\n    let cgroup_name = \"test_cpu_idle_set\";\n\n    let cpu = test_result!(create_cpu_spec(\n        2048,\n        500000,\n        200000,\n        Some(idle),\n        \"0\",\n        \"0\",\n        realtime_period,\n        realtime_runtime,\n    ));\n\n    let spec = test_result!(create_spec(cgroup_name, cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        TestResult::Passed\n    })\n}\n\n/// Tests default idle value\nfn test_cpu_idle_default() -> TestResult {\n    let realtime_period = get_realtime_period();\n    let realtime_runtime = get_realtime_runtime();\n\n    let cgroup_name = \"test_cpu_idle_default\";\n\n    let cpu = test_result!(create_cpu_spec(\n        2048,\n        500000,\n        200000,\n        None,\n        \"0\",\n        \"0\",\n        realtime_period,\n        realtime_runtime,\n    ));\n    let spec = test_result!(create_spec(cgroup_name, cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        TestResult::Passed\n    })\n}\n\nfn can_run() -> bool {\n    Path::new(CPU_CGROUP_PREFIX).exists()\n}\n\nfn can_run_idle() -> bool {\n    const CPU: &str = \"cpu\";\n    const CGROUP_CPU_IDLE: &str = \"cpu.idle\";\n    let idle_path = Path::new(common::DEFAULT_CGROUP_ROOT)\n        .join(CPU)\n        .join(CGROUP_CPU_IDLE);\n    can_run() && idle_path.exists()\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_cpu\");\n    let linux_cgroups_cpus = ConditionalTest::new(\n        \"test_linux_cgroups_cpus\",\n        Box::new(can_run),\n        Box::new(test_cpu_cgroups),\n    );\n\n    let empty_cpu = ConditionalTest::new(\n        \"test_empty_cpu\",\n        Box::new(can_run),\n        Box::new(test_empty_cpu),\n    );\n\n    let cpu_idle_default = ConditionalTest::new(\n        \"test_cpu_idle_default\",\n        Box::new(can_run_idle),\n        Box::new(test_cpu_idle_default),\n    );\n\n    let cpu_idle_set = ConditionalTest::new(\n        \"test_cpu_idle_set\",\n        Box::new(can_run_idle),\n        Box::new(test_cpu_idle_set),\n    );\n\n    test_group.add(vec![\n        Box::new(linux_cgroups_cpus),\n        Box::new(empty_cpu),\n        Box::new(cpu_idle_set),\n        Box::new(cpu_idle_default),\n    ]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/cpu/v2.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, bail};\nuse libcgroups::common::{self, CgroupSetup, DEFAULT_CGROUP_ROOT};\nuse libcgroups::v2::controller_type::ControllerType;\nuse libcontainer::utils::PathBufExt;\nuse oci_spec::runtime::{LinuxCpuBuilder, Spec};\nuse test_framework::{ConditionalTest, TestGroup, TestResult, assert_result_eq, test_result};\nuse tracing::debug;\n\nuse super::create_spec;\nuse crate::tests::cgroups::attach_controller;\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::{CGROUP_ROOT, check_container_created};\n\nconst DEFAULT_PERIOD: u64 = 100_000;\nconst CPU: &str = \"cpu\";\nconst CGROUP_CPU_IDLE: &str = \"cpu.idle\";\n\n// SPEC: The runtime spec does not specify what should happen if the cpu weight is outside\n// of the valid range of values [1, 10000]. We assume that a value of zero means that no action\n// should be taken and a value of over 10000 (after being converted into the cgroup v2 format)\n// should be set to the maximum value (i.e. 10000).\n// It also does not specify what should happen if the cpu quota or cpu period is negative or zero.\n// We assume that a negative value means that it should be set to the default value and zero means\n// that it should be unchanged.\n\n/// Tests if a cpu idle value is successfully set\nfn test_cpu_idle_set() -> TestResult {\n    let idle: i64 = 1;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .idle(idle)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_idle_set\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_idle(\"test_cpu_idle_set\", idle));\n        TestResult::Passed\n    })\n}\n\n/// Tests default idle value is correct\nfn test_cpu_idle_default() -> TestResult {\n    let default_idle = 0;\n    let cpu = test_result!(LinuxCpuBuilder::default().build().context(\"build cpu spec\"));\n\n    let spec = test_result!(create_spec(\"test_cpu_idle_default\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_idle(\"test_cpu_idle_default\", default_idle));\n        TestResult::Passed\n    })\n}\n\n/// Tests if a cpu weight that is in the valid range [1, 10000] is successfully set\nfn test_cpu_weight_valid_set() -> TestResult {\n    let cpu_weight = 22_000u64;\n    let converted_cpu_weight = 1204u64;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .shares(cpu_weight)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_weight_valid_set\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_weight(\n            \"test_cpu_weight_valid_set\",\n            converted_cpu_weight\n        ));\n        TestResult::Passed\n    })\n}\n\n/// Tests if a cpu weight of zero is ignored\nfn test_cpu_weight_zero_ignored() -> TestResult {\n    let cpu_weight = 0u64;\n    let default_cpu_weight = 100;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .shares(cpu_weight)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_weight_zero_ignored\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_weight(\n            \"test_cpu_weight_zero_ignored\",\n            default_cpu_weight\n        ));\n        TestResult::Passed\n    })\n}\n\n/// Tests behavior when setting a cpu weight that is too high\n/// According to kernel documentation, cpu.weight must be in range [1, 10000]\n/// Modern kernels enforce this range strictly and reject out-of-range values\nfn test_cpu_weight_too_high_maximum_set() -> TestResult {\n    let cpu_weight = 500_000u64;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .shares(cpu_weight)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_weight_too_high_maximum_set\", cpu));\n    // We accept both behaviors: either container creation fails due to\n    // out-of-range value, or it succeeds with weight set to maximum (10000)\n    test_outside_container(&spec, &|data| {\n        check_container_created(&data).map_or_else(\n            |_e| TestResult::Passed, // Failure is expected on kernels that strictly enforce range\n            |_| {\n                check_cpu_weight(\"test_cpu_weight_too_high_maximum_set\", 10_000).map_or_else(\n                    |e| TestResult::Failed(anyhow::anyhow!(\"Weight check failed: {}\", e)),\n                    |_| TestResult::Passed,\n                )\n            },\n        )\n    })\n}\n\n/// Tests if a valid cpu quota (x > 0) is set successfully\nfn test_cpu_quota_valid_set() -> TestResult {\n    let cpu_quota = 250_000;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .quota(cpu_quota)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_quota_valid_set\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_max(\n            \"test_cpu_quota_valid_set\",\n            cpu_quota,\n            DEFAULT_PERIOD\n        ));\n        TestResult::Passed\n    })\n}\n\n/// Tests if the cpu quota is the default value (max) if a cpu quota of zero has been specified\nfn test_cpu_quota_zero_default_set() -> TestResult {\n    let cpu_quota = 0;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .quota(cpu_quota)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_quota_zero_default_set\", cpu));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_max(\n            \"test_cpu_quota_zero_default_set\",\n            i64::MAX,\n            DEFAULT_PERIOD\n        ));\n        TestResult::Passed\n    })\n}\n\n/// Tests if the cpu quota is the default value (max) if a negative cpu quota has been specified\nfn test_cpu_quota_negative_default_set() -> TestResult {\n    let cpu_quota = -9999;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .quota(cpu_quota)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\n        \"test_cpu_quota_negative_value_default_set\",\n        cpu\n    ));\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_max(\n            \"test_cpu_quota_negative_value_default_set\",\n            i64::MAX,\n            DEFAULT_PERIOD\n        ));\n        TestResult::Passed\n    })\n}\n\n/// Tests if a valid cpu period (x > 0) is set successfully\n/// According to kernel documentation, cpu.max format is \"$MAX $PERIOD\"\n/// Where $MAX can be \"max\" to indicate no limit\n/// If only one number is written, $MAX is updated (not $PERIOD)\n/// Therefore, to update only period while keeping quota unlimited,\n/// we must use format \"max $PERIOD\"\nfn test_cpu_period_valid_set() -> TestResult {\n    let expected_period = 250_000;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .period(expected_period)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_period_valid_set\", cpu));\n    // Set period with \"max $PERIOD\" format as required by kernel docs\n    test_result!(prepare_cpu_max(&spec, \"max\", &expected_period.to_string()));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        let data = test_result!(read_cgroup_data(\"test_cpu_period_valid_set\", \"cpu.max\"));\n        let parts: Vec<&str> = data.split_whitespace().collect();\n        if parts.len() != 2 {\n            return TestResult::Failed(anyhow::anyhow!(\"Invalid cpu.max format: {}\", data));\n        }\n        let quota = parts[0].trim();\n        let period = parts[1].trim();\n        // Verify period is set correctly\n        let actual_period = match period.parse::<u64>() {\n            Ok(p) => p,\n            Err(e) => return TestResult::Failed(anyhow::anyhow!(\"Failed to parse period: {}\", e)),\n        };\n        if actual_period != expected_period || quota != \"max\" {\n            return TestResult::Failed(anyhow::anyhow!(\n                \"expected cpu.max to be 'max {}', but was '{} {}'\",\n                expected_period,\n                quota,\n                actual_period\n            ));\n        }\n        TestResult::Passed\n    })\n}\n\n/// Tests if the cpu period is unchanged if the cpu period is unspecified. Cpu quota needs\n/// to be unchanged as well\nfn test_cpu_quota_period_unspecified_unchanged() -> TestResult {\n    let quota = 250_000;\n    let expected_period = 250_000;\n    let cpu = test_result!(LinuxCpuBuilder::default().build().context(\"build cpu spec\"));\n\n    let spec = test_result!(create_spec(\"test_cpu_period_unspecified_unchanged\", cpu));\n    test_result!(prepare_cpu_max(\n        &spec,\n        &quota.to_string(),\n        &expected_period.to_string()\n    ));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_max(\n            \"test_cpu_period_unspecified_unchanged\",\n            quota,\n            expected_period\n        ));\n        TestResult::Passed\n    })\n}\n\nfn test_cpu_period_and_quota_valid_set() -> TestResult {\n    let expected_quota = 250_000;\n    let expected_period = 250_000;\n    let cpu = test_result!(\n        LinuxCpuBuilder::default()\n            .quota(expected_quota)\n            .period(expected_period)\n            .build()\n            .context(\"build cpu spec\")\n    );\n\n    let spec = test_result!(create_spec(\"test_cpu_period_and_quota_valid_set\", cpu));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_cpu_max(\n            \"test_cpu_period_and_quota_valid_set\",\n            expected_quota,\n            expected_period\n        ));\n        TestResult::Passed\n    })\n}\n\nfn check_cpu_weight(cgroup_name: &str, expected_weight: u64) -> Result<()> {\n    let data = read_cgroup_data(cgroup_name, \"cpu.weight\")?;\n\n    let actual_weight = data\n        .parse::<u64>()\n        .with_context(|| format!(\"failed to parse {data:?}\"))?;\n    assert_result_eq!(actual_weight, expected_weight, \"unexpected cpu weight\")\n}\n\nfn check_cpu_idle(cgroup_name: &str, expected_value: i64) -> Result<()> {\n    let data = read_cgroup_data(cgroup_name, \"cpu.idle\")?;\n    assert_result_eq!(\n        data.parse::<i64>()\n            .with_context(|| format!(\"failed to parse {data:?}\"))?,\n        expected_value\n    )\n}\n\nfn check_cpu_max(cgroup_name: &str, expected_quota: i64, expected_period: u64) -> Result<()> {\n    let data = read_cgroup_data(cgroup_name, \"cpu.max\")?;\n    let parts: Vec<&str> = data.split_whitespace().collect();\n    if parts.len() != 2 {\n        bail!(\n            \"expected cpu.max to consist of 'quota period' but was {:?}\",\n            data\n        );\n    }\n\n    let quota = parts[0].trim();\n    if quota == \"max\" {\n        if expected_quota != i64::MAX {\n            bail!(\n                \"expected cpu quota to be {:?}, but was 'max'\",\n                expected_quota\n            );\n        }\n    } else {\n        let actual_quota = quota\n            .parse::<i64>()\n            .with_context(|| format!(\"failed to parse {quota:?}\"))?;\n        assert_result_eq!(expected_quota, actual_quota, \"unexpected cpu quota\")?;\n    }\n\n    let period = parts[1].trim();\n    let actual_period = period\n        .parse::<u64>()\n        .with_context(|| format!(\"failed to parse {period:?}\"))?;\n    assert_result_eq!(expected_period, actual_period, \"unexpected cpu period\")\n}\n\nfn read_cgroup_data(cgroup_name: &str, cgroup_file: &str) -> Result<String> {\n    let cgroup_path = PathBuf::from(CGROUP_ROOT)\n        .join(\"runtime-test\")\n        .join(cgroup_name)\n        .join(cgroup_file);\n\n    debug!(\"reading value from {:?}\", cgroup_path);\n    let content = fs::read_to_string(&cgroup_path)\n        .with_context(|| format!(\"failed to read {cgroup_path:?}\"))?;\n    let trimmed = content.trim();\n    Ok(trimmed.to_owned())\n}\n\n// Ensures that cpu.max is set to different values from the default (max 100000)\n// Required to catch runtimes that do not actually skip setting cpu.max if cpu\n// quota and period are not specified, but overwrite the current cpu.max settings\n// with the default values.\nfn prepare_cpu_max(spec: &Spec, quota: &str, period: &str) -> Result<()> {\n    let cgroups_path = spec\n        .linux()\n        .as_ref()\n        .and_then(|l| l.cgroups_path().as_ref())\n        .unwrap();\n\n    let full_cgroup_path = PathBuf::from(common::DEFAULT_CGROUP_ROOT).join_safely(cgroups_path)?;\n    fs::create_dir_all(&full_cgroup_path)\n        .with_context(|| format!(\"could not create cgroup {full_cgroup_path:?}\"))?;\n    attach_controller(Path::new(DEFAULT_CGROUP_ROOT), cgroups_path, \"cpu\")?;\n\n    let cpu_max_path = full_cgroup_path.join(\"cpu.max\");\n    fs::write(&cpu_max_path, format!(\"{quota} {period}\"))\n        .with_context(|| format!(\"failed to write to {cpu_max_path:?}\"))?;\n\n    Ok(())\n}\n\nfn can_run() -> bool {\n    let setup_result = common::get_cgroup_setup();\n    if !matches!(setup_result, Ok(CgroupSetup::Unified)) {\n        debug!(\"cgroup setup is not v2, was {:?}\", setup_result);\n        return false;\n    }\n\n    let controllers_result =\n        libcgroups::v2::util::get_available_controllers(common::DEFAULT_CGROUP_ROOT);\n    if controllers_result.is_err() {\n        debug!(\n            \"could not retrieve cgroup controllers: {:?}\",\n            controllers_result\n        );\n        return false;\n    }\n\n    if !controllers_result\n        .unwrap()\n        .into_iter()\n        .any(|c| c == ControllerType::Cpu)\n    {\n        debug!(\"cpu controller is not attached to the v2 hierarchy\");\n        return false;\n    }\n\n    true\n}\n\nfn can_run_idle() -> bool {\n    let idle_path = Path::new(common::DEFAULT_CGROUP_ROOT)\n        .join(CPU)\n        .join(CGROUP_CPU_IDLE);\n    can_run() && idle_path.exists()\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v2_cpu\");\n    let test_cpu_weight_valid_set = ConditionalTest::new(\n        \"test_cpu_weight_valid_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_weight_valid_set),\n    );\n\n    let test_cpu_weight_zero_ignored = ConditionalTest::new(\n        \"test_cpu_weight_zero_ignored\",\n        Box::new(can_run),\n        Box::new(test_cpu_weight_zero_ignored),\n    );\n\n    let test_cpu_weight_too_high_maximum_set = ConditionalTest::new(\n        \"test_cpu_weight_too_high_maximum_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_weight_too_high_maximum_set),\n    );\n\n    let test_cpu_quota_valid_set = ConditionalTest::new(\n        \"test_cpu_quota_valid_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_quota_valid_set),\n    );\n\n    let test_cpu_quota_zero_default_set = ConditionalTest::new(\n        \"test_cpu_quota_zero_default_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_quota_zero_default_set),\n    );\n\n    let test_cpu_quota_negative_default_set = ConditionalTest::new(\n        \"test_cpu_quota_negative_value_default_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_quota_negative_default_set),\n    );\n\n    let test_cpu_period_valid_set = ConditionalTest::new(\n        \"test_cpu_period_valid_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_period_valid_set),\n    );\n\n    let test_cpu_period_unspecified_unchanged = ConditionalTest::new(\n        \"test_cpu_period_unspecified_unchanged\",\n        Box::new(can_run),\n        Box::new(test_cpu_quota_period_unspecified_unchanged),\n    );\n\n    let test_cpu_period_and_quota_valid_set = ConditionalTest::new(\n        \"test_cpu_period_and_quota_valid_set\",\n        Box::new(can_run),\n        Box::new(test_cpu_period_and_quota_valid_set),\n    );\n\n    let test_cpu_idle_set = ConditionalTest::new(\n        \"test_cpu_idle_set\",\n        Box::new(can_run_idle),\n        Box::new(test_cpu_idle_set),\n    );\n\n    let test_cpu_idle_default = ConditionalTest::new(\n        \"test_cpu_idle_default\",\n        Box::new(can_run_idle),\n        Box::new(test_cpu_idle_default),\n    );\n\n    test_group.add(vec![\n        Box::new(test_cpu_weight_valid_set),\n        Box::new(test_cpu_weight_zero_ignored),\n        Box::new(test_cpu_weight_too_high_maximum_set),\n        Box::new(test_cpu_quota_valid_set),\n        Box::new(test_cpu_quota_zero_default_set),\n        Box::new(test_cpu_quota_negative_default_set),\n        Box::new(test_cpu_period_valid_set),\n        Box::new(test_cpu_period_unspecified_unchanged),\n        Box::new(test_cpu_period_and_quota_valid_set),\n        Box::new(test_cpu_idle_set),\n        Box::new(test_cpu_idle_default),\n    ]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/memory.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Context, Result};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxMemoryBuilder, LinuxResourcesBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nconst CGROUP_MEMORY_LIMIT: &str = \"/sys/fs/cgroup/memory/memory.limit_in_bytes\";\nconst CGROUP_MEMORY_SWAPPINESS: &str = \"/sys/fs/cgroup/memory/memory.swappiness\";\n\nfn create_spec(cgroup_name: &str, limit: i64, swappiness: u64) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .memory(\n                            LinuxMemoryBuilder::default()\n                                .limit(limit)\n                                .swappiness(swappiness)\n                                .build()\n                                .context(\"failed to build memory spec\")?,\n                        )\n                        .build()\n                        .context(\"failed to build resource spec\")?,\n                )\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn test_memory_cgroups() -> TestResult {\n    let cgroup_name = \"test_memory_cgroups\";\n\n    let cases = vec![\n        test_result!(create_spec(cgroup_name, 50593792, 10)),\n        test_result!(create_spec(cgroup_name, 50593792, 50)),\n        test_result!(create_spec(cgroup_name, 50593792, 100)),\n        test_result!(create_spec(cgroup_name, 151781376, 10)),\n        test_result!(create_spec(cgroup_name, 151781376, 50)),\n        test_result!(create_spec(cgroup_name, 151781376, 100)),\n    ];\n\n    for spec in cases.into_iter() {\n        let test_result = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n\n            TestResult::Passed\n        });\n        if let TestResult::Failed(_) = test_result {\n            return test_result;\n        }\n    }\n\n    TestResult::Passed\n}\n\nfn can_run() -> bool {\n    Path::new(CGROUP_MEMORY_LIMIT).exists() && Path::new(CGROUP_MEMORY_SWAPPINESS).exists()\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_memory\");\n    let linux_cgroups_memory = ConditionalTest::new(\n        \"test_linux_cgroups_memory\",\n        Box::new(can_run),\n        Box::new(test_memory_cgroups),\n    );\n\n    test_group.add(vec![Box::new(linux_cgroups_memory)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/mod.rs",
    "content": "use std::fs;\nuse std::path::Component::RootDir;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result};\nuse procfs::process::Process;\npub mod blkio;\npub mod cpu;\npub mod memory;\npub mod network;\npub mod pids;\n\npub fn cleanup_v1() -> Result<()> {\n    for subsystem in list_subsystem_mount_points()? {\n        let runtime_test = subsystem.join(\"runtime-test\");\n        if runtime_test.exists() {\n            fs::remove_dir(&runtime_test)\n                .with_context(|| format!(\"failed to delete {runtime_test:?}\"))?;\n        }\n    }\n\n    Ok(())\n}\n\npub fn cleanup_v2() -> Result<()> {\n    let runtime_test = Path::new(\"/sys/fs/cgroup/runtime-test\");\n    if runtime_test.exists() {\n        let _: Result<Vec<_>, _> = fs::read_dir(runtime_test)\n            .with_context(|| format!(\"failed to read {runtime_test:?}\"))?\n            .filter_map(|e| e.ok())\n            .map(|e| e.path())\n            .filter(|e| e.is_dir())\n            .map(fs::remove_dir)\n            .collect();\n\n        fs::remove_dir(runtime_test)\n            .with_context(|| format!(\"failed to delete {runtime_test:?}\"))?;\n    }\n\n    Ok(())\n}\n\npub fn list_subsystem_mount_points() -> Result<Vec<PathBuf>> {\n    Ok(Process::myself()\n        .context(\"failed to get self\")?\n        .mountinfo()\n        .context(\"failed to get mountinfo\")?\n        .into_iter()\n        .filter_map(|m| {\n            if m.fs_type == \"cgroup\" {\n                Some(m.mount_point)\n            } else {\n                None\n            }\n        })\n        .collect())\n}\n\npub fn attach_controller(cgroup_root: &Path, cgroup_path: &Path, controller: &str) -> Result<()> {\n    let mut current_path = cgroup_root.to_path_buf();\n\n    let mut components = cgroup_path\n        .components()\n        .filter(|c| c.ne(&RootDir))\n        .peekable();\n\n    write_controller(&current_path, controller)?;\n    while let Some(component) = components.next() {\n        current_path.push(component);\n        if components.peek().is_some() {\n            write_controller(&current_path, controller)?;\n        }\n    }\n\n    Ok(())\n}\n\nfn write_controller(cgroup_path: &Path, controller: &str) -> Result<()> {\n    let controller_file = cgroup_path.join(\"cgroup.subtree_control\");\n    fs::write(controller_file, format!(\"+{controller}\"))\n        .with_context(|| format!(\"failed to attach {controller} controller to {cgroup_path:?}\"))\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/network/absolute_network.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxInterfacePriorityBuilder, LinuxNamespace, LinuxNamespaceType,\n    LinuxNetworkBuilder, LinuxResourcesBuilder, Spec, SpecBuilder,\n};\nuse pnet_datalink::interfaces;\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse super::{check_network_cgroup_paths, validate_network};\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nfn create_spec(\n    cgroup_name: &str,\n    class_id: u32,\n    prio: u32,\n    if_name: &str,\n    with_net_ns: bool,\n    with_user_ns: bool,\n) -> Result<Spec> {\n    // Get default namespaces and filter them to optional exclude network or user namespaces\n    let default_namespaces: Vec<LinuxNamespace> = oci_spec::runtime::get_default_namespaces()\n        .into_iter()\n        .filter(|ns| match ns.typ() {\n            LinuxNamespaceType::Network => with_net_ns,\n            LinuxNamespaceType::User => with_user_ns,\n            _ => true,\n        })\n        .collect();\n\n    // Create the Linux Spec\n    let linux_spec = LinuxBuilder::default()\n        .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n        .resources(\n            LinuxResourcesBuilder::default()\n                .network(\n                    LinuxNetworkBuilder::default()\n                        .class_id(class_id)\n                        .priorities(vec![\n                            LinuxInterfacePriorityBuilder::default()\n                                .name(if_name)\n                                .priority(prio)\n                                .build()\n                                .context(\"failed to build network interface priority spec\")?,\n                        ])\n                        .build()\n                        .context(\"failed to build network spec\")?,\n                )\n                .build()\n                .context(\"failed to build resource spec\")?,\n        )\n        .namespaces(default_namespaces)\n        .build()\n        .context(\"failed to build linux spec\")?;\n\n    // Create the top level Spec\n    let spec = SpecBuilder::default()\n        .linux(linux_spec)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\n// Gets the loopback interface and the first ethernet/wlan interface if it exists\nfn get_network_interfaces() -> Option<(String, String)> {\n    let interfaces = interfaces();\n    let lo_if_name = interfaces.first().map(|iface| &iface.name)?;\n    let eth_if_name = interfaces.get(1).map(|iface| &iface.name)?;\n\n    Some((lo_if_name.to_string(), eth_if_name.to_string()))\n}\n\nfn test_network_cgroups() -> TestResult {\n    let cgroup_name = \"test_network_cgroups\";\n\n    let interfaces = test_result!(\n        get_network_interfaces()\n            .ok_or_else(|| anyhow!(\"Could not find network interfaces required for test\"))\n    );\n\n    let lo_if_name = &interfaces.0;\n    let eth_if_name = &interfaces.1;\n\n    let cases = vec![\n        test_result!(create_spec(cgroup_name, 255, 10, lo_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 255, 10, lo_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 255, 10, lo_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 255, 10, lo_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 255, 10, eth_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 255, 10, eth_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 255, 10, eth_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 255, 10, eth_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 255, 30, lo_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 255, 30, lo_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 255, 30, lo_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 255, 30, lo_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 255, 30, eth_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 255, 30, eth_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 255, 30, eth_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 255, 30, eth_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 550, 10, lo_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 550, 10, lo_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 550, 10, lo_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 550, 10, lo_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 550, 10, eth_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 550, 10, eth_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 550, 10, eth_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 550, 10, eth_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 550, 30, lo_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 550, 30, lo_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 550, 30, lo_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 550, 30, lo_if_name, false, false)),\n        test_result!(create_spec(cgroup_name, 550, 30, eth_if_name, true, true)),\n        test_result!(create_spec(cgroup_name, 550, 30, eth_if_name, true, false)),\n        test_result!(create_spec(cgroup_name, 550, 30, eth_if_name, false, true)),\n        test_result!(create_spec(cgroup_name, 550, 30, eth_if_name, false, false)),\n    ];\n\n    for spec in cases.into_iter() {\n        let test_result = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n            test_result!(validate_network(\n                format!(\"/runtime-test/{}\", cgroup_name).as_str(),\n                &spec\n            ));\n\n            TestResult::Passed\n        });\n        if let TestResult::Failed(_) = test_result {\n            return test_result;\n        }\n    }\n\n    TestResult::Passed\n}\n\nfn can_run() -> bool {\n    // Ensure the expected network interfaces exist on the system running the test\n    let iface_exists = get_network_interfaces().is_some();\n\n    // This is kind of annoying, network controller can be at a number of mount points\n    let cgroup_paths_exists = check_network_cgroup_paths().is_ok();\n\n    iface_exists && cgroup_paths_exists\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_network\");\n    let linux_cgroups_network = ConditionalTest::new(\n        \"test_linux_cgroups_network\",\n        Box::new(can_run),\n        Box::new(test_network_cgroups),\n    );\n\n    test_group.add(vec![Box::new(linux_cgroups_network)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/network/mod.rs",
    "content": "pub mod absolute_network;\npub mod relative_network;\n\nuse std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, anyhow, bail};\nuse oci_spec::runtime::Spec;\n\nuse crate::utils::test_utils::CGROUP_ROOT;\n\n// check for available network cgroup mount points paths, as the network controller can be multiple mount points\n// see issue:#39 for discussion\nfn check_network_cgroup_paths() -> Result<(&'static str, &'static str)> {\n    let net_cls_net_prio_independent = Path::new(\"/sys/fs/cgroup/net_cls/net_cls.classid\").exists()\n        && Path::new(\"/sys/fs/cgroup/net_prio/net_prio.ifpriomap\").exists();\n    let net_cls_net_prio = Path::new(\"/sys/fs/cgroup/net_cls,net_prio/net_cls.classid\").exists()\n        && Path::new(\"/sys/fs/cgroup/net_cls,net_prio/net_prio.ifpriomap\").exists();\n    let net_prio_net_cls = Path::new(\"/sys/fs/cgroup/net_prio,net_cls/net_cls.classid\").exists()\n        && Path::new(\"/sys/fs/cgroup/net_prio,net_cls/net_prio.ifpriomap\").exists();\n\n    if net_cls_net_prio_independent {\n        Ok((\"net_cls\", \"net_prio\"))\n    } else if net_cls_net_prio {\n        Ok((\"net_cls,net_prio\", \"net_cls,net_prio\"))\n    } else if net_prio_net_cls {\n        Ok((\"net_prio,net_cls\", \"net_prio,net_cls\"))\n    } else {\n        Err(anyhow!(\"Required cgroup paths do not exist\"))\n    }\n}\n\n// validates the Network structure parsed from /sys/fs/cgroup/net_cls,net_prio with the spec\nfn validate_network(cgroup_name: &str, spec: &Spec) -> Result<()> {\n    let (net_cls_base, net_prio_base) = check_network_cgroup_paths()?;\n    let net_cls_path = PathBuf::from(CGROUP_ROOT)\n        .join(net_cls_base)\n        .join(cgroup_name.trim_start_matches('/'))\n        .join(\"net_cls.classid\");\n    let net_prio_path = PathBuf::from(CGROUP_ROOT)\n        .join(net_prio_base)\n        .join(cgroup_name.trim_start_matches('/'))\n        .join(\"net_prio.ifpriomap\");\n\n    let resources = spec.linux().as_ref().unwrap().resources().as_ref().unwrap();\n    let spec_network = resources.network().as_ref().unwrap();\n\n    // Validate net_cls.classid\n    let classid_content = fs::read_to_string(&net_cls_path)\n        .with_context(|| format!(\"failed to read {:?}\", net_cls_path))?;\n    let expected_classid = spec_network.class_id().unwrap();\n    let actual_classid: u32 = classid_content\n        .trim()\n        .parse()\n        .with_context(|| format!(\"could not parse {:?}\", classid_content.trim()))?;\n    if expected_classid != actual_classid {\n        bail!(\n            \"expected {:?} to contain a classid of {}, but the classid was {}\",\n            net_cls_path,\n            expected_classid,\n            actual_classid\n        );\n    }\n\n    // Validate net_prio.ifpriomap\n    let ifpriomap_content = fs::read_to_string(&net_prio_path)\n        .with_context(|| format!(\"failed to read {:?}\", net_prio_path))?;\n    let expected_priorities = spec_network.priorities().as_ref().unwrap();\n    for priority in expected_priorities {\n        let expected_entry = format!(\"{} {}\", priority.name(), priority.priority());\n        if !ifpriomap_content.contains(&expected_entry) {\n            bail!(\n                \"expected {:?} to contain an entry '{}', but it was not found\",\n                net_prio_path,\n                expected_entry\n            );\n        }\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/network/relative_network.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Context, Result};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxInterfacePriorityBuilder, LinuxNetworkBuilder, LinuxResourcesBuilder, Spec,\n    SpecBuilder,\n};\nuse pnet_datalink::interfaces;\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse super::{check_network_cgroup_paths, validate_network};\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nfn create_spec(cgroup_name: &str, class_id: u32, prio: u32, if_name: &str) -> Result<Spec> {\n    // Create the Linux Spec\n    let linux_spec = LinuxBuilder::default()\n        .cgroups_path(Path::new(cgroup_name))\n        .resources(\n            LinuxResourcesBuilder::default()\n                .network(\n                    LinuxNetworkBuilder::default()\n                        .class_id(class_id)\n                        .priorities(vec![\n                            LinuxInterfacePriorityBuilder::default()\n                                .name(if_name)\n                                .priority(prio)\n                                .build()\n                                .context(\"failed to build network interface priority spec\")?,\n                        ])\n                        .build()\n                        .context(\"failed to build network spec\")?,\n                )\n                .build()\n                .context(\"failed to build resource spec\")?,\n        )\n        .build()\n        .context(\"failed to build linux spec\")?;\n\n    // Create the top level Spec\n    let spec = SpecBuilder::default()\n        .linux(linux_spec)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\n// Gets the loopback interface if it exists\nfn get_loopback_interface() -> Option<String> {\n    interfaces()\n        .into_iter()\n        .find(|iface| iface.is_loopback())\n        .map(|iface| iface.name)\n}\n\nfn test_relative_network_cgroups() -> TestResult {\n    const CGROUP_NAME: &str = \"testdir/runtime-test/container/test_relative_network_cgroups\";\n\n    const ID: u32 = 255;\n    const PRIO: u32 = 10;\n    const IF_NAME: &str = \"lo\";\n    let spec = test_result!(create_spec(CGROUP_NAME, ID, PRIO, IF_NAME));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(validate_network(CGROUP_NAME, &spec));\n        TestResult::Passed\n    })\n}\n\nfn can_run() -> bool {\n    // Ensure the expected network interfaces exist on the system running the test\n    let iface_exists = get_loopback_interface().is_some();\n\n    // This is kind of annoying, network controller can be at a number of mount points\n    let cgroup_paths_exists = check_network_cgroup_paths().is_ok();\n\n    iface_exists && cgroup_paths_exists\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_relative_network\");\n    let linux_cgroups_network = ConditionalTest::new(\n        \"test_linux_cgroups_relative_network\",\n        Box::new(can_run),\n        Box::new(test_relative_network_cgroups),\n    );\n\n    test_group.add(vec![Box::new(linux_cgroups_network)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/cgroups/pids.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\n\nuse anyhow::{Context, Result, bail};\nuse oci_spec::runtime::{LinuxBuilder, LinuxPidsBuilder, LinuxResourcesBuilder, Spec, SpecBuilder};\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::{CGROUP_ROOT, check_container_created};\n\n// SPEC: The runtime spec does not specify what the behavior should be if the limit is\n// zero or negative. We assume that the number of pids should be unlimited in this case.\n\nfn create_spec(cgroup_name: &str, limit: i64) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .cgroups_path(Path::new(\"/runtime-test\").join(cgroup_name))\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .pids(\n                            LinuxPidsBuilder::default()\n                                .limit(limit)\n                                .build()\n                                .context(\"failed to build pids spec\")?,\n                        )\n                        .build()\n                        .context(\"failed to build resource spec\")?,\n                )\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\n// Tests if a specified limit was successfully set\nfn test_positive_limit() -> TestResult {\n    let cgroup_name = \"test_positive_limit\";\n    let limit = 50;\n    let spec = test_result!(create_spec(cgroup_name, limit));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_pid_limit_set(cgroup_name, limit));\n        TestResult::Passed\n    })\n}\n\n// Tests if a specified limit of zero sets the pid limit to unlimited\nfn test_zero_limit() -> TestResult {\n    let cgroup_name = \"test_zero_limit\";\n    let limit = 0;\n    let spec = test_result!(create_spec(cgroup_name, limit));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_pids_are_unlimited(cgroup_name));\n        TestResult::Passed\n    })\n}\n\n// Tests if a specified negative limit sets the pid limit to unlimited\nfn test_negative_limit() -> TestResult {\n    let cgroup_name = \"test_negative_limit\";\n    let limit = -1;\n    let spec = test_result!(create_spec(cgroup_name, limit));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        test_result!(check_pids_are_unlimited(cgroup_name));\n        TestResult::Passed\n    })\n}\n\nfn check_pid_limit_set(cgroup_name: &str, expected: i64) -> Result<()> {\n    let cgroup_path = PathBuf::from(CGROUP_ROOT)\n        .join(\"pids/runtime-test\")\n        .join(cgroup_name)\n        .join(\"pids.max\");\n    let content = fs::read_to_string(&cgroup_path)\n        .with_context(|| format!(\"failed to read {cgroup_path:?}\"))?;\n    let trimmed = content.trim();\n\n    if trimmed.is_empty() {\n        bail!(\n            \"expected {:?} to contain a pid limit of {}, but it was empty\",\n            cgroup_path,\n            expected\n        );\n    }\n\n    if trimmed == \"max\" {\n        bail!(\n            \"expected {:?} to contain a pid limit of {}, but no limit was set\",\n            cgroup_path,\n            expected\n        );\n    }\n\n    let actual: i64 = trimmed\n        .parse()\n        .with_context(|| format!(\"could not parse {trimmed:?}\"))?;\n    if expected != actual {\n        bail!(\n            \"expected {:?} to contain a pid limit of {}, but the limit was {}\",\n            cgroup_path,\n            expected,\n            actual\n        );\n    }\n\n    Ok(())\n}\n\nfn check_pids_are_unlimited(cgroup_name: &str) -> Result<()> {\n    let cgroup_path = PathBuf::from(CGROUP_ROOT)\n        .join(\"pids/runtime-test\")\n        .join(cgroup_name)\n        .join(\"pids.max\");\n    let content = fs::read_to_string(&cgroup_path)\n        .with_context(|| format!(\"failed to read {cgroup_path:?}\"))?;\n    let trimmed = content.trim();\n\n    if trimmed.is_empty() {\n        bail!(\n            \"expected {:?} to contain a pid limit of max, but it was empty\",\n            cgroup_path\n        );\n    }\n\n    if trimmed != \"max\" {\n        bail!(\n            \"expected {:?} to contain 'max' (unlimited), but the limit was {}\",\n            cgroup_path,\n            trimmed\n        );\n    }\n\n    Ok(())\n}\n\nfn can_run() -> bool {\n    Path::new(\"/sys/fs/cgroup/pids\").exists()\n}\n\npub fn get_test_group() -> TestGroup {\n    let mut test_group = TestGroup::new(\"cgroup_v1_pids\");\n    let positive_limit = ConditionalTest::new(\n        \"positive_pid_limit\",\n        Box::new(can_run),\n        Box::new(test_positive_limit),\n    );\n    let zero_limit = ConditionalTest::new(\n        \"zero_pid_limit\",\n        Box::new(can_run),\n        Box::new(test_zero_limit),\n    );\n    let negative_limit = ConditionalTest::new(\n        \"negative_pid_limit\",\n        Box::new(can_run),\n        Box::new(test_negative_limit),\n    );\n\n    test_group.add(vec![\n        Box::new(positive_limit),\n        Box::new(zero_limit),\n        Box::new(negative_limit),\n    ]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/create_runtime/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_utils::CreateOptions;\nuse crate::utils::{create_container, delete_container, generate_uuid, prepare_bundle, set_config};\n\nconst HOOK_OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(HOOK_OUTPUT_FILE)\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn build_log_hook(host_output_file: &str) -> oci_spec::runtime::Hook {\n    HookBuilder::default()\n        .path(\"/bin/sh\")\n        .args(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\"echo 'create-runtime called' >> {host_output_file}\"),\n        ])\n        .build()\n        .expect(\"could not build hook\")\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"/bin/sh\".to_string(),\n                    \"-c\".to_string(),\n                    \"true\".to_string(),\n                ])\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .create_runtime(vec![build_log_hook(host_output_file)])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that the createRuntime hook executes during the create operation.\n/// According to the OCI spec, createRuntime hooks MUST be invoked by the runtime\n/// after the container runtime environment has been created but before pivot_root\n/// has been executed. The createRuntime hooks are called in the runtime namespace.\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid().to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n\n            let spec = get_spec(host_output_file.to_str().unwrap());\n            set_config(&bundle, &spec).unwrap();\n\n            create_container(&id, &bundle, &CreateOptions::default())\n                .unwrap()\n                .wait()\n                .unwrap();\n\n            let result = if !host_output_file.exists() {\n                TestResult::Failed(anyhow!(\n                    \"createRuntime hook did not create output file during create operation\"\n                ))\n            } else {\n                let content = fs::read_to_string(&host_output_file)\n                    .expect(\"failed to read output file after create\");\n\n                if content.contains(\"create-runtime called\") {\n                    TestResult::Passed\n                } else {\n                    TestResult::Failed(anyhow!(\n                        \"the runtime MUST run the createRuntime hooks during create. Got: '{content}'\"\n                    ))\n                }\n            };\n\n            let _ = delete_container(&id, &bundle);\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_create_runtime_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"create_runtime\");\n    tg.add(vec![Box::new(get_test(\"create_runtime\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/delete/delete_test.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::anyhow;\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::tests::lifecycle::ContainerLifecycle;\n\n// Default timeout for state transitions\nconst DEFAULT_TIMEOUT: Duration = Duration::from_secs(75);\n\n/// Test deleting a non-existent container\nfn delete_non_existed_container() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    match container.delete() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"Expected deleting a non-existent container to fail, but it succeeded\"\n        )),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    }\n}\n\n/// Test deleting a container in \"created\" state\nfn delete_created_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    // Create the container\n    match container.create() {\n        TestResult::Passed => {}\n        _ => return TestResult::Failed(anyhow!(\"Failed to create container\")),\n    }\n\n    // Wait for the container to be in created state\n    match container.wait_for_state(\"created\", DEFAULT_TIMEOUT) {\n        TestResult::Passed => {}\n        result => return result,\n    }\n\n    // Delete the container in \"created\" state\n    match container.delete() {\n        TestResult::Passed => TestResult::Passed,\n        TestResult::Failed(err) => TestResult::Failed(anyhow!(\n            \"Failed to delete container in 'created' state: {}\",\n            err\n        )),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    }\n}\n\n/// Test deleting a container in \"running\" state\nfn delete_running_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    // Create the container\n    match container.create() {\n        TestResult::Passed => {}\n        _ => return TestResult::Failed(anyhow!(\"Failed to create container\")),\n    }\n\n    // Start the container\n    match container.start() {\n        TestResult::Passed => {}\n        _ => {\n            // Clean up and return error\n            let _ = container.kill();\n            let _ = container.wait_for_state(\"stopped\", DEFAULT_TIMEOUT);\n            let _ = container.delete();\n            return TestResult::Failed(anyhow!(\"Failed to start container\"));\n        }\n    }\n\n    // Wait for running state\n    match container.wait_for_state(\"running\", DEFAULT_TIMEOUT) {\n        TestResult::Passed => {}\n        result => {\n            // Clean up and return error\n            let _ = container.kill();\n            let _ = container.wait_for_state(\"stopped\", DEFAULT_TIMEOUT);\n            let _ = container.delete();\n            return result;\n        }\n    }\n\n    // Try to delete the running container (should fail per OCI spec)\n    let delete_result = match container.delete() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"Expected deleting a running container to fail, but it succeeded\"\n        )),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    };\n\n    // Clean up\n    let _ = container.kill();\n    let _ = container.wait_for_state(\"stopped\", DEFAULT_TIMEOUT);\n    let cleanup_result = container.delete();\n\n    // Return test result\n    match delete_result {\n        TestResult::Passed => cleanup_result,\n        _ => delete_result,\n    }\n}\n\n/// Test deleting a container in \"stopped\" state\nfn delete_stopped_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    // Create the container\n    match container.create() {\n        TestResult::Passed => {}\n        _ => return TestResult::Failed(anyhow!(\"Failed to create container\")),\n    }\n\n    // Start the container\n    match container.start() {\n        TestResult::Passed => {}\n        _ => {\n            // Clean up and return error\n            let _ = container.kill();\n            let _ = container.wait_for_state(\"stopped\", DEFAULT_TIMEOUT);\n            let _ = container.delete();\n            return TestResult::Failed(anyhow!(\"Failed to start container\"));\n        }\n    }\n\n    // Stop the container\n    match container.kill() {\n        TestResult::Passed => {}\n        _ => {\n            // Clean up and return error\n            let _ = container.delete();\n            return TestResult::Failed(anyhow!(\"Failed to kill container\"));\n        }\n    }\n\n    // Wait for stopped state\n    match container.wait_for_state(\"stopped\", DEFAULT_TIMEOUT) {\n        TestResult::Passed => {}\n        result => return result,\n    }\n\n    // Delete the stopped container\n    match container.delete() {\n        TestResult::Passed => TestResult::Passed,\n        TestResult::Failed(err) => TestResult::Failed(anyhow!(\n            \"Expected deleting a stopped container to succeed, but it failed: {}\",\n            err\n        )),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    }\n}\n\n/// Create and return the delete_container test group\npub fn get_delete_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"delete\");\n\n    let delete_non_existed_container = Test::new(\n        \"delete_non_existed_container\",\n        Box::new(delete_non_existed_container),\n    );\n    let delete_created_container_test = Test::new(\n        \"delete_created_container_test\",\n        Box::new(delete_created_container_test),\n    );\n    let delete_running_container_test = Test::new(\n        \"delete_running_container_test\",\n        Box::new(delete_running_container_test),\n    );\n    let delete_stopped_container_test = Test::new(\n        \"delete_stopped_container_test\",\n        Box::new(delete_stopped_container_test),\n    );\n\n    test_group.add(vec![\n        Box::new(delete_non_existed_container),\n        Box::new(delete_created_container_test),\n        Box::new(delete_running_container_test),\n        Box::new(delete_stopped_container_test),\n    ]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/delete/mod.rs",
    "content": "mod delete_test;\npub use delete_test::get_delete_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/devices/devices_test.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxDeviceBuilder, LinuxDeviceType, ProcessBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec() -> Result<Spec> {\n    let device1 = LinuxDeviceBuilder::default()\n        .path(\"/dev/test1\")\n        .typ(LinuxDeviceType::C)\n        .major(10)\n        .minor(666)\n        .file_mode(432u32)\n        .uid(0u32)\n        .gid(0u32)\n        .build()\n        .context(\"failed to create device 1\")?;\n\n    let device2 = LinuxDeviceBuilder::default()\n        .path(\"/dev/test2\")\n        .typ(LinuxDeviceType::B)\n        .major(8)\n        .minor(666)\n        .file_mode(432u32)\n        .uid(0u32)\n        .gid(0u32)\n        .build()\n        .context(\"failed to create device 2\")?;\n\n    let device3 = LinuxDeviceBuilder::default()\n        .path(\"/dev/test3\")\n        .typ(LinuxDeviceType::P)\n        .file_mode(432u32)\n        .build()\n        .context(\"failed to create device 3\")?;\n\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"devices\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .linux(\n            LinuxBuilder::default()\n                .devices(vec![device1, device2, device3])\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn devices_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn create_spec_default_permissions() -> Result<Spec> {\n    let device = LinuxDeviceBuilder::default()\n        .path(\"/dev/kmsg\")\n        .typ(LinuxDeviceType::C)\n        .major(1)\n        .minor(11)\n        .build()\n        .context(\"failed to create device\")?;\n\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"devices\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .linux(\n            LinuxBuilder::default()\n                .devices(vec![device])\n                .build()\n                .context(\"failed to build linux spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn devices_default_permissions_test() -> TestResult {\n    let spec = test_result!(create_spec_default_permissions());\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_devices_test() -> TestGroup {\n    let mut device_test_group = TestGroup::new(\"devices\");\n\n    let test = Test::new(\"device_test\", Box::new(devices_test));\n    let test_default_permissions = Test::new(\n        \"device_default_permissions\",\n        Box::new(devices_default_permissions_test),\n    );\n\n    device_test_group.add(vec![Box::new(test), Box::new(test_default_permissions)]);\n\n    device_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/devices/mod.rs",
    "content": "mod devices_test;\npub use devices_test::get_devices_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/domainname/mod.rs",
    "content": "use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn get_spec(domainname: &str) -> Spec {\n    SpecBuilder::default()\n        .domainname(domainname)\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"runtimetest\".to_string(),\n                    \"domainname_test\".to_string(),\n                ])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn set_domainname_test() -> TestResult {\n    let spec = get_spec(\"domainname\");\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_domainname_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"domainname_test\");\n    let set_domainname_test = Test::new(\"set_domainname_test\", Box::new(set_domainname_test));\n    tg.add(vec![Box::new(set_domainname_test)]);\n\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/example/hello_world.rs",
    "content": "use anyhow::{Context, Result};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\n////////// ANCHOR: get_example_spec\nfn create_spec() -> Result<Spec> {\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(\n                    [\"runtimetest\", \"hello_world\"]\n                        .iter()\n                        .map(|s| s.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n////////// ANCHOR_END: get_example_spec\n\n////////// ANCHOR: example_test\nfn example_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n////////// ANCHOR_END: example_test\n\n////////// ANCHOR: get_example_test\npub fn get_example_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"example\");\n    let test1 = Test::new(\"hello world\", Box::new(example_test));\n    test_group.add(vec![Box::new(test1)]);\n\n    test_group\n}\n////////// ANCHOR_END: get_example_test\n"
  },
  {
    "path": "tests/contest/contest/src/tests/example/mod.rs",
    "content": "mod hello_world;\n\npub use hello_world::get_example_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec/mod.rs",
    "content": "mod mount_test;\n\nuse anyhow::{Context, Result};\nuse oci_spec::runtime::{ProcessBuilder, RootBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup};\n\nfn create_spec(process: Option<ProcessBuilder>) -> Result<Spec> {\n    let p = process.unwrap_or_default().args(\n        [\"sleep\", \"1000\"]\n            .iter()\n            .map(|s| s.to_string())\n            .collect::<Vec<String>>(),\n    );\n    SpecBuilder::default()\n        .root(RootBuilder::default().readonly(true).build().unwrap())\n        .process(p.build()?)\n        .build()\n        .context(\"failed to create spec\")\n}\n\npub fn get_exec_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"exec\");\n    let mount_test = Test::new(\"mount_test\", Box::new(mount_test::get_mount_test));\n\n    test_group.add(vec![Box::new(mount_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec/mount_test.rs",
    "content": "use anyhow::anyhow;\nuse oci_spec::runtime::ProcessBuilder;\nuse test_framework::{TestResult, test_result};\n\nuse crate::utils::test_utils::{\n    check_container_created, exec_container, start_container, test_outside_container,\n};\n\n// https://github.com/youki-dev/youki/issues/3431\n// In the issue above, we found that `exec` into a container could add duplicate mounts\n// due to `maskedPaths` and `readonlyPaths`. This is a regression test to ensure those mounts are not re-applied on `exec`.\npub(crate) fn get_mount_test() -> TestResult {\n    let spec = test_result!(super::create_spec(Some(ProcessBuilder::default())));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, _) = exec_container(id, dir, &[\"cat\", \"/proc/self/mountinfo\"], None, &[])\n            .expect(\"exec failed\");\n\n        let rootfs_lines: Vec<&str> = stdout\n            .lines()\n            .filter(|l| l.split_whitespace().nth(4) == Some(\"/\"))\n            .collect();\n\n        // rootfs readonly test\n        let rootfs_is_ro = rootfs_lines.iter().any(|l| {\n            l.split_whitespace()\n                .nth(5) // mount options\n                .is_some_and(|opts| opts.split(',').any(|o| o == \"ro\"))\n        });\n\n        // maskedPaths test\n        // /proc/acpi is default maskedPath\n        let count_proc_acpi = stdout\n            .lines()\n            .filter(|l| l.split_whitespace().nth(4) == Some(\"/proc/acpi\"))\n            .count();\n\n        // readonlyPaths test\n        // /proc/bus is default maskedPath\n        let count_proc_bus = stdout\n            .lines()\n            .filter(|l| l.split_whitespace().nth(4) == Some(\"/proc/bus\"))\n            .count();\n\n        if !rootfs_is_ro {\n            return TestResult::Failed(anyhow!(\n                \"expected root (/) to be mounted read-only; root mountinfo lines:{}\",\n                rootfs_lines.join(\"\\n\")\n            ));\n        }\n\n        if count_proc_acpi != 1 {\n            return TestResult::Failed(anyhow!(\n                \"expected exactly 1 mountinfo entry for /proc/acpi, got {}\",\n                count_proc_acpi\n            ));\n        }\n        if count_proc_bus != 1 {\n            return TestResult::Failed(anyhow!(\n                \"expected exactly 1 mountinfo entry for /proc/bus: {}\",\n                count_proc_bus\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec_cpu_affinity/exec_cpu_affinity_test.rs",
    "content": "use std::fs;\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{ExecCPUAffinityBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse regex::Regex;\nuse serde_json::{Value, json};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::{exec_container, start_container, test_outside_container};\n\nfn create_spec(initial: Option<&str>, fin: Option<&str>) -> Result<Spec> {\n    let mut builder = ExecCPUAffinityBuilder::default();\n    if let Some(i) = initial {\n        builder = builder.initial(i.to_string());\n    }\n    if let Some(f) = fin {\n        builder = builder.cpu_affinity_final(f.to_string());\n    }\n\n    let cpu_affinity = builder.build()?;\n\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"sleep\".to_string(), \"10000\".to_string()])\n                .exec_cpu_affinity(cpu_affinity)\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn test_cpu_affinity_only_initial_set_from_process_json() -> TestResult {\n    let spec = test_result!(create_spec(None, None));\n    test_outside_container(&spec, &|data| {\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let process_affinity_initial = \"0,1\";\n        let process_json = create_process(Some(process_affinity_initial), None);\n\n        let process_path = dir.join(\"process.json\");\n        if let Err(e) = fs::write(\n            &process_path,\n            serde_json::to_vec_pretty(&process_json).unwrap(),\n        ) {\n            return TestResult::Failed(anyhow!(\"failed to write process.json: {}\", e));\n        }\n\n        let (_stdout, stderr) =\n            match exec_container(id, dir, &[\"/bin/true\"], Some(&process_path), &[]) {\n                Ok(output) => output,\n                Err(e) => return TestResult::Failed(e),\n            };\n\n        let mask = affinity_mask_from_str(process_affinity_initial);\n        let pattern = format!(r\".*affinity: 0x{:x}\", mask);\n        let re = Regex::new(&pattern).unwrap();\n        if !re.is_match(&stderr) {\n            return TestResult::Failed(anyhow!(\n                \"missing expected affinity log in stderr: {}\",\n                stderr\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\nfn test_cpu_affinity_initial_and_final_set_from_process_json() -> TestResult {\n    let spec = test_result!(create_spec(None, None));\n    test_outside_container(&spec, &|data| {\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let process_affinity_initial = \"0\";\n        let process_affinity_final = \"1\";\n        let process_json =\n            create_process(Some(process_affinity_initial), Some(process_affinity_final));\n\n        let process_path = dir.join(\"process.json\");\n        if let Err(e) = fs::write(\n            &process_path,\n            serde_json::to_vec_pretty(&process_json).unwrap(),\n        ) {\n            return TestResult::Failed(anyhow!(\"failed to write process.json: {}\", e));\n        }\n\n        let (stdout, stderr) = match exec_container(\n            id,\n            dir,\n            &[\"grep\", \"Cpus_allowed_list\", \"/proc/self/status\"],\n            Some(&process_path),\n            &[],\n        ) {\n            Ok(output) => output,\n            Err(e) => return TestResult::Failed(e),\n        };\n\n        if !stdout.contains(process_affinity_final) {\n            return TestResult::Failed(anyhow!(\"unexpected Cpus_allowed_list: {}\", stdout));\n        }\n\n        let mask = affinity_mask_from_str(process_affinity_initial);\n        let pattern = format!(r\".*affinity: 0x{:x}\", mask);\n        let re = Regex::new(&pattern).unwrap();\n        if !re.is_match(&stderr) {\n            return TestResult::Failed(anyhow!(\n                \"missing expected affinity log in stderr: {}\",\n                stderr\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\nfn test_cpu_affinity_from_config_json() -> TestResult {\n    let affinity_initial = \"0\";\n    let affinity_final = \"1\";\n\n    let spec = test_result!(create_spec(Some(affinity_initial), Some(affinity_final)));\n    test_outside_container(&spec, &|data| {\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, stderr) = exec_container(\n            id,\n            dir,\n            &[\"grep\", \"Cpus_allowed_list\", \"/proc/self/status\"],\n            None,\n            &[],\n        )\n        .expect(\"exec failed\");\n\n        if !stdout.contains(affinity_final) {\n            return TestResult::Failed(anyhow!(\"unexpected Cpus_allowed_list: {}\", stdout));\n        }\n\n        let mask = affinity_mask_from_str(affinity_initial);\n        let pattern = format!(r\".*affinity: 0x{:x}\", mask);\n        let re = Regex::new(&pattern).unwrap();\n        if !re.is_match(&stderr) {\n            return TestResult::Failed(anyhow!(\n                \"missing expected affinity log in stderr: {}\",\n                stderr\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\npub fn get_exec_cpu_affinity_test() -> TestGroup {\n    let mut exec_cpu_affinity_test_group = TestGroup::new(\"exec_cpu_affinity\");\n\n    let test_cpu_affinity_only_initial_set_from_process_json = Test::new(\n        \"test_cpu_affinity_only_initial_set_from_process_json\",\n        Box::new(test_cpu_affinity_only_initial_set_from_process_json),\n    );\n    let test_cpu_affinity_initial_and_final_set_from_process_json = Test::new(\n        \"test_cpu_affinity_initial_and_final_set_from_process_json\",\n        Box::new(test_cpu_affinity_initial_and_final_set_from_process_json),\n    );\n    let test_cpu_affinity_from_config_json = Test::new(\n        \"test_cpu_affinity_from_config_json\",\n        Box::new(test_cpu_affinity_from_config_json),\n    );\n    exec_cpu_affinity_test_group.add(vec![\n        Box::new(test_cpu_affinity_only_initial_set_from_process_json),\n        Box::new(test_cpu_affinity_initial_and_final_set_from_process_json),\n        Box::new(test_cpu_affinity_from_config_json),\n    ]);\n\n    exec_cpu_affinity_test_group\n}\n\npub fn create_process(\n    cpu_affinity_initial: Option<&str>,\n    cpu_affinity_final: Option<&str>,\n) -> Value {\n    let mut exec_cpu_affinity = serde_json::Map::new();\n\n    if let Some(init) = cpu_affinity_initial {\n        exec_cpu_affinity.insert(\"initial\".to_string(), json!(init));\n    }\n    if let Some(fin) = cpu_affinity_final {\n        exec_cpu_affinity.insert(\"final\".to_string(), json!(fin));\n    }\n\n    let exec_cpu_affinity_value = Value::Object(exec_cpu_affinity);\n\n    json!({\n        \"terminal\": false,\n        \"cwd\": \"/\",\n        \"args\": [\n            \"/bin/grep\",\n            \"-F\",\n            \"Cpus_allowed_list:\",\n            \"/proc/self/status\"\n        ],\n        \"env\": [\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"TERM=xterm\"\n        ],\n        \"user\": {\n            \"uid\": 0,\n            \"gid\": 0\n        },\n        \"execCPUAffinity\": exec_cpu_affinity_value\n    })\n}\n\nfn affinity_mask_from_str(cpuset_str: &str) -> u64 {\n    let mut mask = 0u64;\n\n    for part in cpuset_str.trim().split(',') {\n        let part = part.trim();\n        if part.is_empty() {\n            continue;\n        }\n        if let Some((start, end)) = part.split_once('-') {\n            let start: usize = start.parse().unwrap();\n            let end: usize = end.parse().unwrap();\n            for i in start..=end {\n                mask |= 1 << i;\n            }\n        } else {\n            let cpu: usize = part.parse().unwrap();\n            mask |= 1 << cpu;\n        }\n    }\n\n    mask\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec_cpu_affinity/mod.rs",
    "content": "mod exec_cpu_affinity_test;\npub use exec_cpu_affinity_test::get_exec_cpu_affinity_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec_env/exec_env_test.rs",
    "content": "use std::ffi::OsStr;\nuse std::fs;\n\nuse anyhow::{Context, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse serde_json::json;\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_utils::check_container_created;\nuse crate::utils::{exec_container, start_container, test_outside_container};\n\nfn create_spec_with_env(env: Vec<String>) -> anyhow::Result<Spec> {\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"sleep\".to_string(), \"1000\".to_string()])\n                .env(env)\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\n/// Exec inherits spec env vars when no --env is passed.\nfn test_exec_inherits_spec_env() -> TestResult {\n    let spec = test_result!(create_spec_with_env(vec![\n        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".to_string(),\n        \"SPEC_VAR=from_spec\".to_string(),\n    ]));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, _) = match exec_container(id, dir, &[\"/bin/printenv\", \"SPEC_VAR\"], None, &[]) {\n            Ok(output) => output,\n            Err(e) => return TestResult::Failed(e),\n        };\n\n        if stdout.trim() != \"from_spec\" {\n            return TestResult::Failed(anyhow!(\n                \"expected SPEC_VAR=from_spec, got: {}\",\n                stdout.trim()\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\n/// --env on CLI overrides the same variable from the spec.\nfn test_cli_env_overrides_spec() -> TestResult {\n    let spec = test_result!(create_spec_with_env(vec![\n        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".to_string(),\n        \"MY_VAR=from_spec\".to_string(),\n    ]));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, _) = match exec_container(\n            id,\n            dir,\n            &[\"/bin/printenv\", \"MY_VAR\"],\n            None,\n            &[(\"MY_VAR\", \"from_cli\")],\n        ) {\n            Ok(output) => output,\n            Err(e) => return TestResult::Failed(e),\n        };\n\n        if stdout.trim() != \"from_cli\" {\n            return TestResult::Failed(anyhow!(\n                \"expected MY_VAR=from_cli (override), got: {}\",\n                stdout.trim()\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\n/// --env on CLI adds new variables alongside spec env.\nfn test_cli_env_adds_new_var() -> TestResult {\n    let spec = test_result!(create_spec_with_env(vec![\n        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".to_string(),\n        \"EXISTING=yes\".to_string(),\n    ]));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        // Exec with a new env var, print all env\n        let (stdout, _) =\n            match exec_container(id, dir, &[\"/bin/env\"], None, &[(\"NEW_VAR\", \"hello\")]) {\n                Ok(output) => output,\n                Err(e) => return TestResult::Failed(e),\n            };\n\n        if !stdout.contains(\"NEW_VAR=hello\") {\n            return TestResult::Failed(anyhow!(\n                \"expected NEW_VAR=hello in env output, got: {}\",\n                stdout\n            ));\n        }\n        if !stdout.contains(\"EXISTING=yes\") {\n            return TestResult::Failed(anyhow!(\n                \"expected EXISTING=yes (inherited) in env output, got: {}\",\n                stdout\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\n/// Env vars from process.json are used when --process is specified.\nfn test_env_from_process_json() -> TestResult {\n    let spec = test_result!(create_spec_with_env(vec![\n        \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".to_string(),\n    ]));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let process_json = json!({\n            \"terminal\": false,\n            \"cwd\": \"/\",\n            \"args\": [\"/bin/printenv\", \"PROC_VAR\"],\n            \"env\": [\n                \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n                \"PROC_VAR=from_process_json\"\n            ],\n            \"user\": {\n                \"uid\": 0,\n                \"gid\": 0\n            }\n        });\n\n        let process_path = dir.join(\"process.json\");\n        if let Err(e) = fs::write(\n            &process_path,\n            serde_json::to_vec_pretty(&process_json).unwrap(),\n        ) {\n            return TestResult::Failed(anyhow!(\"failed to write process.json: {}\", e));\n        }\n\n        let (stdout, _) = match exec_container(id, dir, &[OsStr::new(\"\")], Some(&process_path), &[])\n        {\n            Ok(output) => output,\n            Err(e) => return TestResult::Failed(e),\n        };\n\n        if stdout.trim() != \"from_process_json\" {\n            return TestResult::Failed(anyhow!(\n                \"expected PROC_VAR=from_process_json, got: {}\",\n                stdout.trim()\n            ));\n        }\n\n        TestResult::Passed\n    })\n}\n\npub fn get_exec_env_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"exec_env\");\n\n    let inherit = Test::new(\n        \"test_exec_inherits_spec_env\",\n        Box::new(test_exec_inherits_spec_env),\n    );\n    let override_test = Test::new(\n        \"test_cli_env_overrides_spec\",\n        Box::new(test_cli_env_overrides_spec),\n    );\n    let add_new = Test::new(\n        \"test_cli_env_adds_new_var\",\n        Box::new(test_cli_env_adds_new_var),\n    );\n    let process_json = Test::new(\n        \"test_env_from_process_json\",\n        Box::new(test_env_from_process_json),\n    );\n\n    test_group.add(vec![\n        Box::new(inherit),\n        Box::new(override_test),\n        Box::new(add_new),\n        Box::new(process_json),\n    ]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/exec_env/mod.rs",
    "content": "mod exec_env_test;\npub use exec_env_test::get_exec_env_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/fd_control/mod.rs",
    "content": "use std::fs;\nuse std::os::fd::{AsRawFd, RawFd};\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{ConditionalTest, Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::{CreateOptions, is_runtime_runc, test_inside_container};\n\nfn create_spec() -> Result<Spec> {\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(\n                    [\"runtimetest\", \"fd_control\"]\n                        .iter()\n                        .map(|s| s.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn open_devnull_no_cloexec() -> Result<(fs::File, RawFd)> {\n    // Rust std by default sets cloexec, so we undo it\n    let devnull = fs::File::open(\"/dev/null\")?;\n    let devnull_fd = devnull.as_raw_fd();\n    let flags = nix::fcntl::fcntl(devnull_fd, nix::fcntl::FcntlArg::F_GETFD)?;\n    let mut flags = nix::fcntl::FdFlag::from_bits_retain(flags);\n    flags.remove(nix::fcntl::FdFlag::FD_CLOEXEC);\n    nix::fcntl::fcntl(devnull_fd, nix::fcntl::FcntlArg::F_SETFD(flags))?;\n    Ok((devnull, devnull_fd))\n}\n\n// If not opening any other FDs, verify youki itself doesnt open anything that gets\n// leaked in if passing --preserve-fds with a large number\n// NOTE: this will also fail if the test harness itself starts leaking FDs\nfn only_stdio_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(\n        &spec,\n        &CreateOptions::default().with_extra_args(&[\"--preserve-fds\".as_ref(), \"100\".as_ref()]),\n        &|bundle_path| {\n            fs::write(bundle_path.join(\"num-fds\"), \"0\".as_bytes())?;\n            Ok(())\n        },\n    )\n}\n\n// If we know we have an open FD without cloexec, it should be closed if preserve-fds\n// is 0 (the default)\nfn closes_fd_test() -> TestResult {\n    // Open this before the setup function so it's kept alive for the container lifetime\n    let (_devnull, _devnull_fd) = match open_devnull_no_cloexec() {\n        Ok(v) => v,\n        Err(e) => return TestResult::Failed(anyhow!(\"failed to open dev null: {}\", e)),\n    };\n\n    let spec = test_result!(create_spec());\n    test_inside_container(\n        &spec,\n        &CreateOptions::default().with_extra_args(&[\"--preserve-fds\".as_ref(), \"0\".as_ref()]),\n        &|bundle_path| {\n            fs::write(bundle_path.join(\"num-fds\"), \"0\".as_bytes())?;\n            Ok(())\n        },\n    )\n}\n\n// Given an open FD, verify it can be passed down with preserve-fds\nfn pass_single_fd_test() -> TestResult {\n    // Open this before the setup function so it's kept alive for the container lifetime\n    let (_devnull, devnull_fd) = match open_devnull_no_cloexec() {\n        Ok(v) => v,\n        Err(e) => return TestResult::Failed(anyhow!(\"failed to open dev null: {}\", e)),\n    };\n\n    let spec = test_result!(create_spec());\n    test_inside_container(\n        &spec,\n        &CreateOptions::default().with_extra_args(&[\n            \"--preserve-fds\".as_ref(),\n            (devnull_fd - 2).to_string().as_ref(), // relative to stdio\n        ]),\n        &|bundle_path| {\n            fs::write(bundle_path.join(\"num-fds\"), \"1\".as_bytes())?;\n            Ok(())\n        },\n    )\n}\n\npub fn get_fd_control_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"fd_control\");\n    test_group.set_nonparallel(); // fds are process-wide state\n    let test_only_stdio = ConditionalTest::new(\n        \"only_stdio\",\n        // runc errors if any of the N passed FDs via preserve-fd are not currently open\n        Box::new(|| !is_runtime_runc()),\n        Box::new(only_stdio_test),\n    );\n    let test_closes_fd = Test::new(\"closes_fd\", Box::new(closes_fd_test));\n    let test_pass_single_fd = Test::new(\"pass_single_fd\", Box::new(pass_single_fd_test));\n    // adding separately as one is conditional test and others are normal\n    test_group.add(vec![Box::new(test_only_stdio)]);\n    test_group.add(vec![\n        Box::new(test_closes_fd),\n        Box::new(test_pass_single_fd),\n    ]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/hooks/invoke.rs",
    "content": "use std::fs;\nuse std::path::{Path, PathBuf};\nuse std::time::Duration;\n\nuse anyhow::{anyhow, bail};\nuse oci_spec::runtime::{Hook, HookBuilder, HooksBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::{\n    CreateOptions, LifecycleStatus, WaitTarget, create_container, delete_container, generate_uuid,\n    prepare_bundle, set_config, start_container, wait_for_state,\n};\n\nconst STATE_WAIT_TIMEOUT_SECS: u64 = 5;\nconst STATE_POLL_INTERVAL_MILLIS: u64 = 100;\n\nfn get_hook_output_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle.as_ref().join(\"bundle\").join(\"rootfs\").join(\"output\")\n}\n\nfn delete_hook_output_file(path: &Path) -> anyhow::Result<()> {\n    match fs::remove_file(path) {\n        Ok(()) => Ok(()),\n        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),\n        Err(e) => bail!(\"failed to remove output file: {}\", e),\n    }\n}\n\nfn write_log_hook(content: &str, host_output_file_path: &str) -> Hook {\n    HookBuilder::default()\n        .path(\"/bin/sh\")\n        .args(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\"echo '{content}' >> {host_output_file_path}\",),\n        ])\n        .build()\n        .expect(\"could not build hook\")\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    let write_format = |content: &str| write_log_hook(content, host_output_file);\n\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"true\".to_string()])\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .prestart(vec![\n                    write_format(\"pre-start1 called\"),\n                    write_format(\"pre-start2 called\"),\n                ])\n                .create_runtime(vec![\n                    write_format(\"create-runtime1 called\"),\n                    write_format(\"create-runtime2 called\"),\n                ])\n                .create_container(vec![\n                    write_format(\"create-container1 called\"),\n                    write_format(\"create-container2 called\"),\n                ])\n                .start_container(vec![\n                    write_format(\"start-container1 called\"),\n                    write_format(\"start-container2 called\"),\n                ])\n                .poststart(vec![\n                    write_format(\"post-start1 called\"),\n                    write_format(\"post-start2 called\"),\n                ])\n                .poststop(vec![\n                    write_format(\"post-stop1 called\"),\n                    write_format(\"post-stop2 called\"),\n                ])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn wait_for_target(id: &str, bundle_path: &Path, target: WaitTarget) {\n    wait_for_state(\n        id,\n        bundle_path,\n        target,\n        Duration::from_secs(STATE_WAIT_TIMEOUT_SECS),\n        Duration::from_millis(STATE_POLL_INTERVAL_MILLIS),\n    )\n    .unwrap();\n}\n\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid();\n            let id_str = id.to_string();\n            let bundle = prepare_bundle().unwrap();\n            let host_output_file = get_hook_output_path(&bundle);\n            let host_output_file_str = host_output_file.to_str().unwrap();\n\n            let spec = get_spec(host_output_file_str);\n\n            set_config(&bundle, &spec).unwrap();\n            create_container(&id_str, &bundle, &CreateOptions::default())\n                .unwrap()\n                .wait()\n                .unwrap();\n            wait_for_target(\n                &id_str,\n                bundle.path(),\n                WaitTarget::Status(LifecycleStatus::Created),\n            );\n            start_container(&id_str, &bundle).unwrap().wait().unwrap();\n            delete_container(&id_str, &bundle).unwrap().wait().unwrap();\n            wait_for_target(&id_str, bundle.path(), WaitTarget::Deleted);\n            let log = fs::read_to_string(&host_output_file).expect(\"cannot read output file\");\n            delete_hook_output_file(&host_output_file).unwrap();\n            let expected = \"pre-start1 called\\n\\\n                    pre-start2 called\\n\\\n                    create-runtime1 called\\n\\\n                    create-runtime2 called\\n\\\n                    create-container1 called\\n\\\n                    create-container2 called\\n\\\n                    post-start1 called\\n\\\n                    post-start2 called\\n\\\n                    post-stop1 called\\n\\\n                    post-stop2 called\\n\";\n            if log != expected {\n                return TestResult::Failed(anyhow!(\n                    \"error: hooks must be called in the listed order.\\n\\\n                    got:\\n{log}\\n\\\n                    expected:\\n{expected}\"\n                ));\n            }\n            TestResult::Passed\n        }),\n    )\n}\n\npub fn get_hooks_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"hooks\");\n    tg.add(vec![Box::new(get_test(\"hooks\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/hooks/mod.rs",
    "content": "mod invoke;\npub use invoke::get_hooks_tests;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/hostname/mod.rs",
    "content": "use oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(hostname: &str) -> Spec {\n    SpecBuilder::default()\n        .hostname(hostname)\n        .linux(\n            // Need to reset the read-only paths\n            LinuxBuilder::default()\n                .readonly_paths(vec![])\n                .build()\n                .expect(\"error in building linux config\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"set_host_name\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn hostname_test() -> TestResult {\n    let spec = create_spec(\"hostname-specific\");\n    test_inside_container(&spec, &CreateOptions::default(), &|_| {\n        // As long as the container is created, we expect the hostname to be determined\n        // by the spec, so nothing to prepare prior.\n        Ok(())\n    })\n}\n\nfn empty_hostname() -> TestResult {\n    let spec = create_spec(\"\");\n    test_inside_container(&spec, &CreateOptions::default(), &|_| {\n        // As long as the container is created, we expect the hostname to be determined\n        // by the spec, so nothing to prepare prior.\n        Ok(())\n    })\n}\n\npub fn get_hostname_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"set_host_name\");\n    let hostname_test = Test::new(\"set_host_name_test\", Box::new(hostname_test));\n    let empty_hostname_test = Test::new(\"set_empty_host_name_test\", Box::new(empty_hostname));\n    test_group.add(vec![Box::new(hostname_test), Box::new(empty_hostname_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/intel_rdt/intel_rdt_test.rs",
    "content": "use anyhow::{Context, Result};\nuse libcontainer::process::intel_rdt::find_resctrl_mount_point;\nuse oci_spec::runtime::{LinuxBuilder, LinuxIntelRdt, Spec, SpecBuilder};\nuse test_framework::{TestResult, test_result};\n\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nfn create_spec(\n    maybe_l3_cache: Option<&str>,\n    maybe_mem_bw: Option<&str>,\n    maybe_clos_id: Option<&str>,\n) -> Result<Spec> {\n    let mut intel_rdt = LinuxIntelRdt::default();\n    intel_rdt.set_l3_cache_schema(maybe_l3_cache.map(|x| x.to_owned()));\n    intel_rdt.set_mem_bw_schema(maybe_mem_bw.map(|x| x.to_owned()));\n    intel_rdt.set_clos_id(maybe_clos_id.map(|x| x.to_owned()));\n\n    // Create the Linux Spec\n    let linux_spec = LinuxBuilder::default()\n        .intel_rdt(intel_rdt)\n        .build()\n        .context(\"failed to build linux spec\")?;\n\n    // Create the top level Spec\n    let spec = SpecBuilder::default()\n        .linux(linux_spec)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\npub fn test_intel_rdt() -> TestResult {\n    let cases = vec![\n        test_result!(create_spec(Some(\"L3:0=fff\"), Some(\"MB:0=70\"), None)),\n        test_result!(create_spec(Some(\"L3:0=fff\"), None, None)),\n        test_result!(create_spec(None, Some(\"MB:0=70\"), None)),\n        test_result!(create_spec(None, None, None)),\n    ];\n\n    for spec in cases.into_iter() {\n        let test_result = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n\n            TestResult::Passed\n        });\n        if let TestResult::Failed(_) = test_result {\n            return test_result;\n        }\n    }\n\n    TestResult::Passed\n}\n\npub fn can_run() -> bool {\n    // Ensure the resctrl pseudo-filesystem is mounted.\n    let res = find_resctrl_mount_point();\n    res.is_ok()\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/intel_rdt/mod.rs",
    "content": "use test_framework::{ConditionalTest, TestGroup};\n\nuse self::intel_rdt_test::{can_run, test_intel_rdt};\n\nmod intel_rdt_test;\n\npub fn get_intel_rdt_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"intel_rdt\");\n    let intel_rdt = ConditionalTest::new(\"intel_rdt\", Box::new(can_run), Box::new(test_intel_rdt));\n\n    test_group.add(vec![Box::new(intel_rdt)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/io_priority/io_priority_test.rs",
    "content": "use anyhow::{Context, Result};\nuse oci_spec::runtime::{\n    IOPriorityClass, LinuxIOPriorityBuilder, ProcessBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(\n    io_priority_class: IOPriorityClass,\n    execute_test: &str,\n    priority: i64,\n) -> Result<Spec> {\n    let io_p = LinuxIOPriorityBuilder::default()\n        .class(io_priority_class)\n        .priority(priority)\n        .build()\n        .unwrap();\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(\n                    [\"runtimetest\", execute_test]\n                        .iter()\n                        .map(|s| s.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .io_priority(io_p)\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn io_priority_class_rt_test() -> TestResult {\n    let spec = test_result!(create_spec(\n        IOPriorityClass::IoprioClassRt,\n        \"io_priority_class_rt\",\n        1,\n    ));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn io_priority_class_be_test() -> TestResult {\n    let spec = test_result!(create_spec(\n        IOPriorityClass::IoprioClassBe,\n        \"io_priority_class_be\",\n        2,\n    ));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn io_priority_class_idle_test() -> TestResult {\n    let spec = test_result!(create_spec(\n        IOPriorityClass::IoprioClassIdle,\n        \"io_priority_class_idle\",\n        3,\n    ));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_io_priority_test() -> TestGroup {\n    let mut io_priority_group = TestGroup::new(\"set_io_priority\");\n    let io_priority_class_rt =\n        Test::new(\"io_priority_class_rt\", Box::new(io_priority_class_rt_test));\n    let io_priority_class_be =\n        Test::new(\"io_priority_class_be\", Box::new(io_priority_class_be_test));\n    let io_priority_class_idle = Test::new(\n        \"io_priority_class_idle\",\n        Box::new(io_priority_class_idle_test),\n    );\n\n    io_priority_group.add(vec![\n        Box::new(io_priority_class_rt),\n        Box::new(io_priority_class_be),\n        Box::new(io_priority_class_idle),\n    ]);\n    io_priority_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/io_priority/mod.rs",
    "content": "mod io_priority_test;\n\npub use io_priority_test::get_io_priority_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/kill/kill_test.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::tests::lifecycle::ContainerLifecycle;\n\nfn create_spec(args: &[&str]) -> Result<Spec> {\n    let args_vec: Vec<String> = args.iter().map(|&a| a.into()).collect();\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(args_vec)\n                .build()\n                .context(\"failed to build process spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n    Ok(spec)\n}\n\nfn failed_and_delete(text: String, container: ContainerLifecycle) -> TestResult {\n    let delete_result = container.delete();\n    match delete_result {\n        TestResult::Passed => TestResult::Failed(anyhow!(text)),\n        TestResult::Failed(err) => TestResult::Failed(anyhow!(\n            \"{}; also container deletion failed: {:?}\",\n            text,\n            err\n        )),\n        _ => TestResult::Failed(anyhow!(\"{}; unexpected delete result\", text)),\n    }\n}\n\nfn merge_test_results(kill_result: TestResult, delete_result: TestResult) -> TestResult {\n    match (kill_result, delete_result) {\n        (TestResult::Failed(err), _) => TestResult::Failed(err),\n        (TestResult::Passed, TestResult::Failed(err)) => {\n            TestResult::Failed(anyhow!(\"Delete failed: {:?}\", err))\n        }\n        (TestResult::Passed, TestResult::Passed) => TestResult::Passed,\n        _ => TestResult::Failed(anyhow!(\"Unexpected result\")),\n    }\n}\n\n// Killing a container with an empty ID should fail.\nfn kill_with_empty_id_test() -> TestResult {\n    let mut container = ContainerLifecycle::new();\n\n    // kill with empty id\n    container.set_id(\"\");\n    match container.kill() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"Expected killing container with empty id to fail, but was successful\"\n        )),\n        _ => TestResult::Failed(anyhow!(\n            \"Unexpected killing container with empty id test result\"\n        )),\n    }\n}\n\n// Killing a non-existent container should fail.\nfn kill_non_existed_container() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    // kill for non existed container\n    match container.kill() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"Expected killing non existed container to fail, but was successful\"\n        )),\n        _ => TestResult::Failed(anyhow!(\n            \"Unexpected killing non existed container test result\"\n        )),\n    }\n}\n\n// Create a container, then kill and delete it successfully.\nfn kill_created_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n\n    // kill created container\n    match container.create() {\n        TestResult::Passed => {}\n        _ => return failed_and_delete(\"Failed to create container\".to_string(), container),\n    }\n    let kill_result = container.kill();\n    let delete_result = container.delete();\n    merge_test_results(kill_result, delete_result)\n}\n\n// After a container stops naturally, killing it should fail, then deletion should succeed.\nfn kill_stopped_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n    let spec = create_spec(&[\"true\"]).unwrap();\n\n    // kill stopped container\n    match container.create_with_spec(spec) {\n        TestResult::Passed => {}\n        _ => return failed_and_delete(\"Failed to create container\".to_string(), container),\n    }\n    match container.start() {\n        TestResult::Passed => {}\n        _ => return failed_and_delete(\"Failed to start container\".to_string(), container),\n    }\n    container.wait_for_state(\"stopped\", Duration::from_secs(1));\n    let kill_result = match container.kill() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\"Expected failure but got success\")),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    };\n    let delete_result = container.delete();\n    merge_test_results(kill_result, delete_result)\n}\n\n// Kill a running container should succeed, then delete should succeed.\nfn kill_start_container_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n    let spec = create_spec(&[\"sleep\", \"30\"]).unwrap();\n\n    // kill start container\n    match container.create_with_spec(spec) {\n        TestResult::Passed => {}\n        _ => return failed_and_delete(\"Failed to recreate container\".to_string(), container),\n    }\n\n    match container.start() {\n        TestResult::Passed => {}\n        _ => return failed_and_delete((\"Failed to start container\").to_string(), container),\n    }\n    container.wait_for_state(\"running\", Duration::from_secs(1));\n    let kill_result = container.kill();\n    let delete_result = container.delete();\n    merge_test_results(kill_result, delete_result)\n}\n\npub fn get_kill_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"kill_container\");\n\n    let kill_with_empty_id_test =\n        Test::new(\"kill_with_empty_id_test\", Box::new(kill_with_empty_id_test));\n    let kill_non_existed_container = Test::new(\n        \"kill_non_existed_container\",\n        Box::new(kill_non_existed_container),\n    );\n    let kill_created_container_test = Test::new(\n        \"kill_created_container_test\",\n        Box::new(kill_created_container_test),\n    );\n    let kill_stopped_container_test = Test::new(\n        \"kill_stopped_container_test\",\n        Box::new(kill_stopped_container_test),\n    );\n    let kill_start_container_test = Test::new(\n        \"kill_start_container_test\",\n        Box::new(kill_start_container_test),\n    );\n    test_group.add(vec![\n        Box::new(kill_with_empty_id_test),\n        Box::new(kill_non_existed_container),\n        Box::new(kill_created_container_test),\n        Box::new(kill_stopped_container_test),\n        Box::new(kill_start_container_test),\n    ]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/kill/mod.rs",
    "content": "mod kill_test;\n\npub use kill_test::get_kill_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/kill_no_effect/kill_no_effect_test.rs",
    "content": "use std::time::Duration;\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::tests::lifecycle::ContainerLifecycle;\nuse crate::utils::get_state;\n\nfn create_spec(args: &[&str]) -> Result<Spec> {\n    let args_vec: Vec<String> = args.iter().map(|&a| a.into()).collect();\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(args_vec)\n                .build()\n                .context(\"failed to build process spec\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n    Ok(spec)\n}\n\nfn failed_and_delete(text: String, container: ContainerLifecycle) -> TestResult {\n    let delete_result = container.delete();\n    match delete_result {\n        TestResult::Passed => TestResult::Failed(anyhow!(text)),\n        TestResult::Failed(err) => TestResult::Failed(anyhow!(\n            \"{}; also container deletion failed: {:?}\",\n            text,\n            err\n        )),\n        _ => TestResult::Failed(anyhow!(\"{}; unexpected delete result\", text)),\n    }\n}\n\n// This test MUST ensure that attempting to send a signal to a container that is neither created nor running has no effect on the container and generates an error.\nfn kill_no_effect_test() -> TestResult {\n    let container = ContainerLifecycle::new();\n    let spec = create_spec(&[\"sleep\", \"1\"]).unwrap();\n\n    if !matches!(container.create_with_spec(spec), TestResult::Passed) {\n        return failed_and_delete(\"Failed to create container\".to_string(), container);\n    }\n\n    if !matches!(container.start(), TestResult::Passed) {\n        return failed_and_delete(\"Failed to start container\".to_string(), container);\n    }\n\n    container.wait_for_state(\"stopped\", Duration::from_secs(5));\n\n    // get state before kill\n    let (before_stdout, before_stderr) =\n        match get_state(container.get_id(), container.get_project_path()) {\n            Ok(v) => v,\n            _ => {\n                return failed_and_delete((\"Failed to get container state\").to_string(), container);\n            }\n        };\n    if !before_stderr.is_empty() {\n        return failed_and_delete((\"Failed to get container state\").to_string(), container);\n    }\n\n    //kill the stopped container\n    match container.kill() {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => {\n            return failed_and_delete(\n                \"Should not be able to kill a stopped container\".to_string(),\n                container,\n            );\n        }\n        _ => return failed_and_delete(\"Unexpected test result\".to_string(), container),\n    };\n\n    // get state after kill\n    let (after_stdout, after_stderr) =\n        match get_state(container.get_id(), container.get_project_path()) {\n            Ok(v) => v,\n            _ => {\n                return failed_and_delete((\"Failed to get container state\").to_string(), container);\n            }\n        };\n    if !after_stderr.is_empty() {\n        return failed_and_delete((\"Failed to get container state\").to_string(), container);\n    }\n\n    // state before and after kill should be the same\n    if before_stdout != after_stdout {\n        return TestResult::Failed(anyhow!(\n            \"container state changed after kill signal state before kill: {}\\nstate after kill: {}\",\n            before_stdout,\n            after_stdout\n        ));\n    }\n\n    //delete container after test\n    if !matches!(container.delete(), TestResult::Passed) {\n        return failed_and_delete(\"Failed to delete container\".to_string(), container);\n    }\n\n    TestResult::Passed\n}\n\npub fn get_kill_no_effect_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"kill_no_effect\");\n    let kill_no_effect_test = Test::new(\"kill_no_effect_test\", Box::new(kill_no_effect_test));\n    test_group.add(vec![Box::new(kill_no_effect_test)]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/kill_no_effect/mod.rs",
    "content": "mod kill_no_effect_test;\n\npub use kill_no_effect_test::get_kill_no_effect_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/checkpoint.rs",
    "content": "use std::path::Path;\nuse std::process::{Command, Stdio};\n\nuse anyhow::anyhow;\nuse test_framework::TestResult;\n\nuse super::get_result_from_output;\nuse crate::utils::get_runtime_path;\nuse crate::utils::test_utils::State;\n\n// Simple function to figure out the PID of the first container process\nfn get_container_pid(project_path: &Path, id: &str) -> Result<i32, TestResult> {\n    let res_state = match Command::new(get_runtime_path())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"--root\")\n        .arg(project_path.join(\"runtime\"))\n        .arg(\"state\")\n        .arg(id)\n        .spawn()\n        .expect(\"failed to execute state command\")\n        .wait_with_output()\n    {\n        Ok(o) => o,\n        Err(e) => {\n            return Err(TestResult::Failed(anyhow!(\n                \"error getting container state {}\",\n                e\n            )));\n        }\n    };\n    let stdout = match String::from_utf8(res_state.stdout) {\n        Ok(s) => s,\n        Err(e) => {\n            return Err(TestResult::Failed(anyhow!(\n                \"failed to parse container stdout {}\",\n                e\n            )));\n        }\n    };\n    let state: State = match serde_json::from_str(&stdout) {\n        Ok(v) => v,\n        Err(e) => {\n            return Err(TestResult::Failed(anyhow!(\n                \"error in parsing state of container: stdout : {}, parse error : {}\",\n                stdout,\n                e\n            )));\n        }\n    };\n\n    Ok(state.pid.unwrap_or(-1))\n}\n\n// CRIU requires a minimal network setup in the network namespace\nfn setup_network_namespace(project_path: &Path, id: &str) -> Result<(), TestResult> {\n    let pid = get_container_pid(project_path, id)?;\n\n    if let Err(e) = Command::new(\"nsenter\")\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"-t\")\n        .arg(format!(\"{pid}\"))\n        .arg(\"-a\")\n        .args(vec![\"/bin/ip\", \"link\", \"set\", \"up\", \"dev\", \"lo\"])\n        .spawn()\n        .expect(\"failed to exec ip\")\n        .wait_with_output()\n    {\n        return Err(TestResult::Failed(anyhow!(\n            \"error setting up network namespace {}\",\n            e\n        )));\n    }\n\n    Ok(())\n}\n\nfn checkpoint(\n    project_path: &Path,\n    id: &str,\n    args: Vec<&str>,\n    work_path: Option<&str>,\n) -> TestResult {\n    if let Err(e) = setup_network_namespace(project_path, id) {\n        return e;\n    }\n\n    let temp_dir = match tempfile::tempdir() {\n        Ok(td) => td,\n        Err(e) => {\n            return TestResult::Failed(anyhow::anyhow!(\n                \"failed creating temporary directory {:?}\",\n                e\n            ));\n        }\n    };\n    let checkpoint_dir = temp_dir.as_ref().join(\"checkpoint\");\n    if let Err(e) = std::fs::create_dir(&checkpoint_dir) {\n        return TestResult::Failed(anyhow::anyhow!(\n            \"failed creating checkpoint directory ({:?}): {}\",\n            &checkpoint_dir,\n            e\n        ));\n    }\n\n    let additional_args = match work_path {\n        Some(wp) => vec![\"--work-path\", wp],\n        _ => Vec::new(),\n    };\n\n    let runtime_path = get_runtime_path();\n\n    let checkpoint = Command::new(runtime_path)\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"--root\")\n        .arg(project_path.join(\"runtime\"))\n        .arg(match runtime_path {\n            _ if runtime_path.ends_with(\"youki\") => \"checkpointt\",\n            _ => \"checkpoint\",\n        })\n        .arg(\"--image-path\")\n        .arg(&checkpoint_dir)\n        .args(additional_args)\n        .args(args)\n        .arg(id)\n        .spawn()\n        .expect(\"failed to execute checkpoint command\")\n        .wait_with_output();\n\n    if let Err(e) = get_result_from_output(checkpoint) {\n        return TestResult::Failed(anyhow::anyhow!(\"failed to execute checkpoint command: {e}\"));\n    }\n\n    // Check for complete checkpoint\n    if !Path::new(&checkpoint_dir.join(\"inventory.img\")).exists() {\n        return TestResult::Failed(anyhow::anyhow!(\n            \"resulting checkpoint does not seem to be complete. {:?}/inventory.img is missing\",\n            &checkpoint_dir,\n        ));\n    }\n\n    if !Path::new(&checkpoint_dir.join(\"descriptors.json\")).exists() {\n        return TestResult::Failed(anyhow::anyhow!(\n            \"resulting checkpoint does not seem to be complete. {:?}/descriptors.json is missing\",\n            &checkpoint_dir,\n        ));\n    }\n\n    let dump_log = match work_path {\n        Some(wp) => Path::new(wp).join(\"dump.log\"),\n        _ => checkpoint_dir.join(\"dump.log\"),\n    };\n\n    if !dump_log.exists() {\n        return TestResult::Failed(anyhow::anyhow!(\n            \"resulting checkpoint log file {:?} not found.\",\n            &dump_log,\n        ));\n    }\n\n    TestResult::Passed\n}\n\npub fn checkpoint_leave_running_work_path_tmp(project_path: &Path, id: &str) -> TestResult {\n    checkpoint(project_path, id, vec![\"--leave-running\"], Some(\"/tmp/\"))\n}\n\npub fn checkpoint_leave_running(project_path: &Path, id: &str) -> TestResult {\n    checkpoint(project_path, id, vec![\"--leave-running\"], None)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/container_create.rs",
    "content": "use tempfile::TempDir;\nuse test_framework::{TestResult, TestableGroup};\n\nuse super::{create, delete, kill};\nuse crate::utils::{generate_uuid, prepare_bundle};\n\npub struct ContainerCreate {\n    project_path: TempDir,\n    container_id: String,\n}\n\nimpl Default for ContainerCreate {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl ContainerCreate {\n    pub fn new() -> Self {\n        let id = generate_uuid();\n        let temp_dir = prepare_bundle().unwrap();\n        ContainerCreate {\n            project_path: temp_dir,\n            container_id: id.to_string(),\n        }\n    }\n\n    // runtime should not create container with empty id\n    fn create_empty_id(&self) -> TestResult {\n        match create::create(self.project_path.path(), \"\") {\n            Ok(()) => TestResult::Failed(anyhow::anyhow!(\n                \"container should not have been created with empty id, but was created.\"\n            )),\n            Err(_) => TestResult::Passed,\n        }\n    }\n\n    // runtime should create container with valid id\n    fn create_valid_id(&self) -> TestResult {\n        match create::create(self.project_path.path(), &self.container_id) {\n            Ok(_) => {\n                let _ = kill::kill(self.project_path.path(), &self.container_id);\n                let _ = delete::delete(self.project_path.path(), &self.container_id);\n                TestResult::Passed\n            }\n            Err(err) => {\n                TestResult::Failed(err.context(\n                    \"container should have been created with valid id, but was not created.\",\n                ))\n            }\n        }\n    }\n\n    // runtime should not create container with is that already exists\n    fn create_duplicate_id(&self) -> TestResult {\n        let id = generate_uuid().to_string();\n        // First create which should be successful\n        if let Err(err) = create::create(self.project_path.path(), &id) {\n            return TestResult::Failed(\n                err.context(\n                    \"container should have been created with valid id, but was not created\",\n                ),\n            );\n        }\n        // Second create which should fail\n        let ret = create::create(self.project_path.path(), &id);\n        // Clean up the container from the first create. No error handling since\n        // there is nothing we can do.\n        let _ = kill::kill(self.project_path.path(), &id);\n        let _ = delete::delete(self.project_path.path(), &id);\n        match ret {\n            Ok(()) => TestResult::Failed(anyhow::anyhow!(\n                \"container should not have been created with same id, but was created.\"\n            )),\n            Err(_) => TestResult::Passed,\n        }\n    }\n}\n\nimpl TestableGroup for ContainerCreate {\n    fn get_name(&self) -> &'static str {\n        \"create\"\n    }\n\n    fn parallel(&self) -> bool {\n        true\n    }\n\n    fn run_all(&self) -> Vec<(&'static str, TestResult)> {\n        vec![\n            (\"empty_id\", self.create_empty_id()),\n            (\"valid_id\", self.create_valid_id()),\n            (\"duplicate_id\", self.create_duplicate_id()),\n        ]\n    }\n\n    fn run_selected(&self, selected: &[&str]) -> Vec<(&'static str, TestResult)> {\n        let mut ret = Vec::new();\n        for name in selected {\n            match *name {\n                \"empty_id\" => ret.push((\"empty_id\", self.create_empty_id())),\n                \"valid_id\" => ret.push((\"valid_id\", self.create_valid_id())),\n                \"duplicate_id\" => ret.push((\"duplicate_id\", self.create_duplicate_id())),\n                _ => eprintln!(\"No test named {name} in lifecycle\"),\n            };\n        }\n        ret\n    }\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/container_lifecycle.rs",
    "content": "use std::path;\nuse std::thread::sleep;\nuse std::time::Duration;\n\nuse oci_spec::runtime::Spec;\nuse test_framework::{TestResult, TestableGroup};\n\nuse super::util::criu_installed;\nuse super::{checkpoint, create, delete, exec, kill, start, state};\nuse crate::utils::{generate_uuid, prepare_bundle, set_config};\n\n// By experimenting, somewhere around 50 is enough for youki process\n// to get the kill signal and shut down\n// here we add a little buffer time as well\nconst SLEEP_TIME: Duration = Duration::from_millis(75);\n\npub struct ContainerLifecycle {\n    project_path: tempfile::TempDir,\n    container_id: String,\n}\n\nimpl Default for ContainerLifecycle {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl ContainerLifecycle {\n    pub fn new() -> Self {\n        let id = generate_uuid();\n        let bundle_dir = prepare_bundle().unwrap();\n        ContainerLifecycle {\n            project_path: bundle_dir,\n            container_id: id.to_string(),\n        }\n    }\n\n    pub fn set_id(&mut self, id: &str) {\n        self.container_id = id.to_string();\n    }\n\n    pub fn get_id(&self) -> &str {\n        &self.container_id\n    }\n\n    pub fn get_project_path(&self) -> &path::Path {\n        self.project_path.path()\n    }\n\n    pub fn create(&self) -> TestResult {\n        create::create(self.project_path.path(), &self.container_id).into()\n    }\n\n    pub fn create_with_spec(&self, spec: Spec) -> TestResult {\n        set_config(&self.project_path, &spec).unwrap();\n        create::create(self.project_path.path(), &self.container_id).into()\n    }\n\n    #[allow(dead_code)]\n    pub fn exec(&self, cmd: Vec<&str>, expected_output: Option<&str>) -> TestResult {\n        exec::exec(\n            self.project_path.path(),\n            &self.container_id,\n            cmd,\n            expected_output,\n        )\n        .into()\n    }\n\n    pub fn start(&self) -> TestResult {\n        start::start(self.project_path.path(), &self.container_id).into()\n    }\n\n    pub fn state(&self) -> TestResult {\n        state::state(self.project_path.path(), &self.container_id).into()\n    }\n\n    pub fn kill(&self) -> TestResult {\n        let ret = kill::kill(self.project_path.path(), &self.container_id);\n        // sleep a little, so the youki process actually gets the signal and shuts down\n        // otherwise, the tester moves on to next tests before the youki has gotten signal, and delete test can fail\n        sleep(SLEEP_TIME);\n        ret.into()\n    }\n\n    pub fn delete(&self) -> TestResult {\n        delete::delete(self.project_path.path(), &self.container_id).into()\n    }\n\n    pub fn checkpoint_leave_running(&self) -> TestResult {\n        if !criu_installed() {\n            return TestResult::Skipped;\n        }\n\n        checkpoint::checkpoint_leave_running(self.project_path.path(), &self.container_id)\n    }\n\n    pub fn checkpoint_leave_running_work_path_tmp(&self) -> TestResult {\n        if !criu_installed() {\n            return TestResult::Skipped;\n        }\n\n        checkpoint::checkpoint_leave_running_work_path_tmp(\n            self.project_path.path(),\n            &self.container_id,\n        )\n    }\n\n    /// Wait for the container to reach a specific state\n    pub fn wait_for_state(&self, expected_state: &str, timeout: Duration) -> TestResult {\n        use crate::tests::lifecycle::state;\n\n        match state::wait_for_state(\n            self.project_path.path(),\n            &self.container_id,\n            expected_state,\n            timeout,\n            Duration::from_millis(100),\n        ) {\n            Ok(_) => TestResult::Passed,\n            Err(e) => TestResult::Failed(anyhow::anyhow!(\n                \"Container failed to reach {} state: {}\",\n                expected_state,\n                e\n            )),\n        }\n    }\n}\n\nimpl TestableGroup for ContainerLifecycle {\n    fn get_name(&self) -> &'static str {\n        \"lifecycle\"\n    }\n\n    fn parallel(&self) -> bool {\n        true\n    }\n\n    fn run_all(&self) -> Vec<(&'static str, TestResult)> {\n        vec![\n            (\"create\", self.create()),\n            (\"start\", self.start()),\n            // (\"exec\", self.exec(vec![\"echo\", \"Hello\"], Some(\"Hello\\n\"))),\n            (\n                \"checkpoint and leave running with --work-path /tmp\",\n                self.checkpoint_leave_running_work_path_tmp(),\n            ),\n            (\n                \"checkpoint and leave running\",\n                self.checkpoint_leave_running(),\n            ),\n            (\"kill\", self.kill()),\n            (\"state\", self.state()),\n            (\"delete\", self.delete()),\n        ]\n    }\n\n    fn run_selected(&self, selected: &[&str]) -> Vec<(&'static str, TestResult)> {\n        let mut ret = Vec::new();\n        for name in selected {\n            match *name {\n                \"create\" => ret.push((\"create\", self.create())),\n                \"start\" => ret.push((\"start\", self.start())),\n                \"checkpoint_leave_running_work_path_tmp\" => ret.push((\n                    \"checkpoint and leave running with --work-path /tmp\",\n                    self.checkpoint_leave_running_work_path_tmp(),\n                )),\n                \"checkpoint_leave_running\" => ret.push((\n                    \"checkpoint and leave running\",\n                    self.checkpoint_leave_running(),\n                )),\n                \"kill\" => ret.push((\"kill\", self.kill())),\n                \"state\" => ret.push((\"state\", self.state())),\n                \"delete\" => ret.push((\"delete\", self.delete())),\n                _ => eprintln!(\"No test named {name} in lifecycle\"),\n            };\n        }\n        ret\n    }\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/create.rs",
    "content": "use std::io;\nuse std::path::Path;\nuse std::process::{Command, Stdio};\n\nuse anyhow::{Result, bail};\n\nuse crate::utils::get_runtime_path;\n\n// There are still some issues here in case we put stdout and stderr as piped\n// the youki process created halts indefinitely which is why we pass null, and\n// use wait instead of wait_with_output\npub fn create(project_path: &Path, id: &str) -> Result<()> {\n    let res = Command::new(get_runtime_path())\n        .stdin(Stdio::null())\n        .stdout(Stdio::null())\n        .stderr(Stdio::null())\n        .arg(\"--root\")\n        .arg(project_path.join(\"runtime\"))\n        .arg(\"create\")\n        .arg(\"--bundle\")\n        .arg(project_path.join(\"bundle\"))\n        .arg(id)\n        .spawn()\n        .expect(\"Cannot execute create command\")\n        .wait();\n    match res {\n        io::Result::Ok(status) => {\n            if status.success() {\n                Ok(())\n            } else {\n                bail!(\"create exited with nonzero status : {}\", status)\n            }\n        }\n        io::Result::Err(e) => bail!(\"create failed : {}\", e),\n    }\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/delete.rs",
    "content": "use std::path::Path;\n\nuse anyhow::Result;\n\nuse super::get_result_from_output;\nuse crate::utils::delete_container;\n\npub fn delete(project_path: &Path, id: &str) -> Result<()> {\n    let res = delete_container(id, project_path)\n        .expect(\"failed to execute delete command\")\n        .wait_with_output();\n    get_result_from_output(res)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/exec.rs",
    "content": "use std::path::Path;\nuse std::process::{Command, Stdio};\n\nuse anyhow::Result;\nuse test_framework::assert_result_eq;\n\nuse super::get_result_from_output;\nuse crate::utils::get_runtime_path;\n\npub fn exec(\n    project_path: &Path,\n    id: &str,\n    exec_cmd: Vec<&str>,\n    expected_output: Option<&str>,\n) -> Result<()> {\n    let res = Command::new(get_runtime_path())\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"--root\")\n        .arg(project_path.join(\"runtime\"))\n        .arg(\"exec\")\n        .arg(id)\n        .args(exec_cmd)\n        .spawn()\n        .expect(\"failed to execute exec command\")\n        .wait_with_output();\n    if let Some(expect) = expected_output {\n        let act = String::from_utf8(res.as_ref().unwrap().stdout.clone()).unwrap();\n        assert_result_eq!(expect, act.as_str(), \"unexpected stdout.\").unwrap();\n    }\n    get_result_from_output(res)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/kill.rs",
    "content": "use std::path::Path;\n\nuse anyhow::Result;\n\nuse super::get_result_from_output;\nuse crate::utils::kill_container;\n\npub fn kill(project_path: &Path, id: &str) -> Result<()> {\n    let res = kill_container(id, project_path)\n        .expect(\"failed to execute kill command\")\n        .wait_with_output();\n    get_result_from_output(res)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/mod.rs",
    "content": "mod checkpoint;\nmod container_create;\nmod container_lifecycle;\nmod create;\nmod delete;\nmod exec;\nmod kill;\nmod start;\nmod state;\nmod util;\npub use container_create::ContainerCreate;\npub use container_lifecycle::ContainerLifecycle;\npub use util::get_result_from_output;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/start.rs",
    "content": "use std::path::Path;\n\nuse anyhow::Result;\n\nuse super::get_result_from_output;\nuse crate::utils::test_utils::start_container;\n\npub fn start(project_path: &Path, id: &str) -> Result<()> {\n    let res = start_container(id, project_path)\n        .expect(\"failed to execute start command\")\n        .wait_with_output();\n    get_result_from_output(res)\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/state.rs",
    "content": "use std::path::Path;\n\nuse anyhow::{Result, anyhow, bail};\nuse serde_json::Value;\n\nuse crate::utils::get_state;\n\npub fn state(project_path: &Path, id: &str) -> Result<()> {\n    match get_state(id, project_path) {\n        Ok((stdout, stderr)) => {\n            if stderr.contains(\"Error\") || stderr.contains(\"error\") {\n                bail!(\"Error :\\nstdout : {}\\nstderr : {}\", stdout, stderr)\n            } else {\n                // confirm that the status is stopped, as this is executed after the kill command\n                if !(stdout.contains(&format!(r#\"\"id\": \"{id}\"\"#))\n                    && stdout.contains(r#\"\"status\": \"stopped\"\"#))\n                {\n                    bail!(\"Expected state stopped, got : {}\", stdout)\n                } else {\n                    Ok(())\n                }\n            }\n        }\n        Err(e) => Err(e.context(\"failed to get container state\")),\n    }\n}\n\n/// Get the container status as a string\npub fn get_container_status(project_path: &Path, id: &str) -> Result<String> {\n    match get_state(id, project_path) {\n        Ok((stdout, stderr)) => {\n            if stderr.contains(\"Error\") || stderr.contains(\"error\") {\n                bail!(\"Error :\\nstdout : {}\\nstderr : {}\", stdout, stderr)\n            } else {\n                // Parse JSON to extract status\n                match serde_json::from_str::<Value>(&stdout) {\n                    Ok(value) => {\n                        if let Some(status) = value.get(\"status\")\n                            && let Some(status_str) = status.as_str()\n                        {\n                            return Ok(status_str.to_string());\n                        }\n                        bail!(\"Failed to extract status from state output: {}\", stdout)\n                    }\n                    Err(err) => bail!(\"Failed to parse state output as JSON: {} - {}\", stdout, err),\n                }\n            }\n        }\n        Err(e) => Err(e.context(\"failed to get container state\")),\n    }\n}\n\n/// Check if a container is in a specific state\npub fn is_in_state(project_path: &Path, id: &str, expected_state: &str) -> Result<bool> {\n    match get_container_status(project_path, id) {\n        Ok(status) => Ok(status == expected_state),\n        Err(e) => {\n            // If the container doesn't exist, it's not in the expected state\n            if e.to_string().contains(\"does not exist\") {\n                return Ok(false);\n            }\n            Err(e.context(format!(\n                \"failed to check if container is in {} state\",\n                expected_state\n            )))\n        }\n    }\n}\n\n/// Wait for a container to reach a specific state with timeout\npub fn wait_for_state(\n    project_path: &Path,\n    id: &str,\n    expected_state: &str,\n    timeout: std::time::Duration,\n    poll_interval: std::time::Duration,\n) -> Result<()> {\n    let start = std::time::Instant::now();\n\n    while start.elapsed() < timeout {\n        match is_in_state(project_path, id, expected_state) {\n            Ok(true) => return Ok(()),\n            Ok(false) => std::thread::sleep(poll_interval),\n            Err(e) => {\n                // If there's a transient error, continue polling\n                if e.to_string().contains(\"does not exist\") && expected_state == \"stopped\" {\n                    // Special case: if container doesn't exist and we're waiting for stopped state,\n                    // consider it stopped (deletion after stopping)\n                    return Ok(());\n                }\n                std::thread::sleep(poll_interval);\n            }\n        }\n    }\n\n    Err(anyhow!(\n        \"Timed out waiting for container {} to reach {} state\",\n        id,\n        expected_state\n    ))\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/lifecycle/util.rs",
    "content": "use std::{io, process};\n\nuse anyhow::{Result, bail};\n\npub fn get_result_from_output(res: io::Result<process::Output>) -> Result<()> {\n    match res {\n        io::Result::Ok(output) => {\n            let stderr = String::from_utf8(output.stderr).unwrap();\n            if stderr.contains(\"Error\") || stderr.contains(\"error\") {\n                let stdout = String::from_utf8(output.stdout).unwrap();\n                bail!(\"Error :\\nstdout : {}\\nstderr : {}\", stdout, stderr)\n            } else {\n                Ok(())\n            }\n        }\n        io::Result::Err(e) => Err(anyhow::Error::new(e)),\n    }\n}\n\npub fn criu_installed() -> bool {\n    which::which(\"criu\").is_ok()\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/linux_masked_paths/masked_paths.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{anyhow, bail};\nuse nix::sys::stat::SFlag;\nuse oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn get_spec(masked_paths: Vec<PathBuf>) -> Spec {\n    let paths: Vec<String> = masked_paths\n        .iter()\n        .map(|p| p.to_string_lossy().to_string())\n        .collect();\n\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .masked_paths(paths)\n                .build()\n                .expect(\"could not build\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"masked_paths\".to_string()])\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap()\n}\n\nfn check_masked_paths() -> TestResult {\n    const MASKED_DIR: &str = \"masked-dir\";\n    const MASKED_SUBDIR: &str = \"masked-subdir\";\n    const MASKED_FILE: &str = \"masked-file\";\n\n    let masked_dir_top = PathBuf::from(MASKED_DIR);\n    let masked_file_top = PathBuf::from(MASKED_FILE);\n\n    let masked_dir_sub = masked_dir_top.join(MASKED_SUBDIR);\n    let masked_file_sub = masked_dir_top.join(MASKED_FILE);\n    let masked_file_sub_sub = masked_dir_sub.join(MASKED_FILE);\n\n    let root = PathBuf::from(\"/\");\n\n    let masked_paths = vec![\n        root.join(&masked_dir_top),\n        root.join(&masked_file_top),\n        root.join(&masked_dir_sub),\n        root.join(&masked_file_sub),\n        root.join(&masked_file_sub_sub),\n    ];\n\n    let spec = get_spec(masked_paths);\n\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::fs;\n        let test_dir = bundle_path.join(&masked_dir_sub);\n        fs::create_dir_all(&test_dir)?;\n\n        fs::File::create(test_dir.join(\"tmp\"))?;\n\n        // runtimetest cannot check the readability of empty files, so\n        // write something.\n        let test_sub_sub_file = bundle_path.join(&masked_file_sub_sub);\n        fs::File::create(&test_sub_sub_file)?;\n        fs::write(&test_sub_sub_file, b\"secrets\")?;\n\n        let test_sub_file = bundle_path.join(&masked_file_sub);\n        fs::File::create(&test_sub_file)?;\n        fs::write(&test_sub_file, b\"secrets\")?;\n\n        let test_file = bundle_path.join(MASKED_FILE);\n        fs::File::create(&test_file)?;\n        fs::write(&test_file, b\"secrets\")?;\n\n        Ok(())\n    })\n}\n\nfn check_masked_rel_paths() -> TestResult {\n    // Deliberately set a relative path to be masked,\n    // and expect an error\n    let masked_rel_path = PathBuf::from(\"../masked_rel_path\");\n    let masked_paths = vec![masked_rel_path];\n    let spec = get_spec(masked_paths);\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|_bundle_path| Ok(()));\n    // If the container creation succeeds, we expect an error since the masked paths does not support relative paths.\n    if let TestResult::Passed = res {\n        TestResult::Failed(anyhow!(\n            \"expected error in container creation with invalid symlink, found no error\"\n        ))\n    } else {\n        TestResult::Passed\n    }\n}\n\nfn check_masked_symlinks() -> TestResult {\n    // Deliberately create a masked symlink that points an invalid file,\n    // and expect an error.\n    const MASKED_SYMLINK: &str = \"masked_symlink\";\n\n    let root = PathBuf::from(\"/\");\n    let masked_paths = vec![root.join(MASKED_SYMLINK)];\n    let spec = get_spec(masked_paths);\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::{fs, io};\n        let test_file = bundle_path.join(MASKED_SYMLINK);\n        // ln -s ../masked-symlink ; readlink -f /masked-symlink; ls -L /masked-symlink\n        match std::os::unix::fs::symlink(\"../masked_symlink\", &test_file) {\n            io::Result::Ok(_) => { /* This is expected */ }\n            io::Result::Err(e) => {\n                bail!(\"error in creating symlink, to {:?} {:?}\", test_file, e);\n            }\n        }\n\n        let r_path = match fs::read_link(&test_file) {\n            io::Result::Ok(p) => p,\n            io::Result::Err(e) => {\n                bail!(\"error in reading symlink at {:?} : {:?}\", test_file, e);\n            }\n        };\n\n        // It ensures that the symlink points not to exist.\n        match fs::metadata(r_path) {\n            io::Result::Ok(md) => {\n                bail!(\n                    \"reading path {:?} should have given error, found {:?} instead\",\n                    test_file,\n                    md\n                )\n            }\n            io::Result::Err(e) => {\n                let err = e.kind();\n                if let io::ErrorKind::NotFound = err {\n                    Ok(())\n                } else {\n                    bail!(\"expected not found error, got {:?}\", err);\n                }\n            }\n        }\n    });\n\n    // If the container creation succeeds, we expect an error since the masked paths does not support symlinks.\n    if let TestResult::Passed = res {\n        TestResult::Failed(anyhow!(\n            \"expected error in container creation with invalid symlink, found no error\"\n        ))\n    } else {\n        TestResult::Passed\n    }\n}\n\nfn test_mode(mode: u32) -> TestResult {\n    const MASKED_DEVICE: &str = \"masked_device\";\n\n    let root = PathBuf::from(\"/\");\n    let masked_paths = vec![root.join(MASKED_DEVICE)];\n    let spec = get_spec(masked_paths);\n\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::os::unix::fs::OpenOptionsExt;\n        use std::{fs, io};\n        let test_file = bundle_path.join(MASKED_DEVICE);\n\n        if let io::Result::Err(e) = fs::OpenOptions::new()\n            .mode(mode)\n            .create(true)\n            .write(true)\n            .open(&test_file)\n        {\n            bail!(\n                \"could not create file {:?} with mode {:?} : {:?}\",\n                test_file,\n                mode ^ 0o666,\n                e\n            );\n        }\n\n        match fs::metadata(&test_file) {\n            io::Result::Ok(_) => Ok(()),\n            io::Result::Err(e) => {\n                let err = e.kind();\n                if let io::ErrorKind::NotFound = err {\n                    bail!(\"error in creating device node, {:?}\", e)\n                } else {\n                    Ok(())\n                }\n            }\n        }\n    })\n}\n\nfn check_masked_device_nodes() -> TestResult {\n    [\n        SFlag::S_IFBLK.bits() | 0o666,\n        SFlag::S_IFCHR.bits() | 0o666,\n        SFlag::S_IFIFO.bits() | 0o666,\n    ]\n    .iter()\n    .map(|mode| test_mode(*mode))\n    .find(|res| matches!(res, TestResult::Failed(_)))\n    .unwrap_or(TestResult::Passed)\n}\n\npub fn get_linux_masked_paths_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"masked_paths\");\n    let masked_paths_test = Test::new(\"masked_paths\", Box::new(check_masked_paths));\n    let masked_rel_paths_test = Test::new(\"masked_rel_paths\", Box::new(check_masked_rel_paths));\n    let masked_symlinks_test = Test::new(\"masked_symlinks\", Box::new(check_masked_symlinks));\n    let masked_device_nodes_test =\n        Test::new(\"masked_device_nodes\", Box::new(check_masked_device_nodes));\n    tg.add(vec![\n        Box::new(masked_paths_test),\n        Box::new(masked_rel_paths_test),\n        Box::new(masked_symlinks_test),\n        Box::new(masked_device_nodes_test),\n    ]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/linux_masked_paths/mod.rs",
    "content": "mod masked_paths;\n\npub use masked_paths::get_linux_masked_paths_tests;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/linux_ns_itype/mod.rs",
    "content": "mod ns_itype_test;\npub use ns_itype_test::get_ns_itype_tests;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/linux_ns_itype/ns_itype_test.rs",
    "content": "use anyhow::anyhow;\nuse oci_spec::runtime::{LinuxBuilder, Spec, SpecBuilder};\nuse procfs::process::Process;\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_outside_container;\n\n// get spec for the test\nfn get_spec() -> Spec {\n    let mut r = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .namespaces(\n                    // we have to remove all namespaces, so we directly\n                    // provide an empty vec here\n                    vec![],\n                )\n                // if these both are not empty, we cannot set a inherited\n                // mnt namespace, as these both require a private mnt namespace\n                .masked_paths(vec![])\n                .readonly_paths(vec![])\n                .build()\n                .expect(\"could not build spec\"),\n        )\n        .build()\n        .unwrap();\n    // We need to remove hostname to avoid test failures when not creating UTS namespace\n    r.set_hostname(None);\n    r\n}\n\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let host_proc = Process::myself().expect(\"error in getting /proc/self\");\n            let host_namespaces = match host_proc.namespaces() {\n                Ok(n) => n,\n                Err(e) => {\n                    return TestResult::Failed(anyhow!(\n                        \"error in resolving host namespaces : {}\",\n                        e\n                    ));\n                }\n            };\n            let spec = get_spec();\n            test_outside_container(&spec, &move |data| {\n                let pid = match data.state {\n                    Some(s) => s.pid.unwrap(),\n                    None => return TestResult::Failed(anyhow!(\"state command returned error\")),\n                };\n                let container_process =\n                    Process::new(pid).expect(\"error in getting /proc for container process\");\n                let container_namespaces = container_process\n                    .namespaces()\n                    .expect(\"error in getting namespaces of container process\");\n                if container_namespaces != host_namespaces {\n                    return TestResult::Failed(anyhow!(\n                        \"error : namespaces are not correctly inherited\"\n                    ));\n                }\n                TestResult::Passed\n            })\n        }),\n    )\n}\n\npub fn get_ns_itype_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"ns_itype\");\n    let tests: Vec<_> = vec![Box::new(get_test(\"ns_itype\"))];\n    tg.add(tests);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/memory_policy/memory_policy_test.rs",
    "content": "use std::fs;\n\nuse anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxMemoryPolicyBuilder, MemoryPolicyFlagType, MemoryPolicyModeType,\n    ProcessBuilder, Spec, SpecBuilder,\n};\nuse serde_json::json;\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn spec_with_runtimetest(\n    args_token: &str,\n    mp: Option<(MemoryPolicyModeType, &str, Vec<MemoryPolicyFlagType>)>,\n) -> Result<Spec> {\n    let mut linux = LinuxBuilder::default();\n\n    if let Some((mode, nodes, flags)) = mp {\n        let mp = LinuxMemoryPolicyBuilder::default()\n            .mode(mode)\n            .nodes(nodes.to_string())\n            .flags(flags)\n            .build()?;\n        linux = linux.memory_policy(mp);\n    }\n\n    SpecBuilder::default()\n        .linux(linux.build()?)\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), args_token.to_string()])\n                .build()?,\n        )\n        .build()\n        .context(\"failed to build spec\")\n}\n\nfn interleave_without_flags() -> TestResult {\n    let spec = match spec_with_runtimetest(\n        \"memory_policy\",\n        Some((MemoryPolicyModeType::MpolInterleave, \"0\", vec![])),\n    ) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn bind_static() -> TestResult {\n    let spec = match spec_with_runtimetest(\n        \"memory_policy\",\n        Some((\n            MemoryPolicyModeType::MpolBind,\n            \"0\",\n            vec![MemoryPolicyFlagType::MpolFStaticNodes],\n        )),\n    ) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn preferred_relative() -> TestResult {\n    let spec = match spec_with_runtimetest(\n        \"memory_policy\",\n        Some((\n            MemoryPolicyModeType::MpolPreferred,\n            \"0\",\n            vec![MemoryPolicyFlagType::MpolFRelativeNodes],\n        )),\n    ) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn default_with_missing_nodes_ok() -> TestResult {\n    let spec = match spec_with_runtimetest(\n        \"memory_policy\",\n        Some((MemoryPolicyModeType::MpolDefault, \"\", vec![])),\n    ) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn invalid_mode_string() -> TestResult {\n    let spec = match spec_with_runtimetest(\"memory_policy\", None) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let cfg_path = bundle.join(\"config.json\");\n        let mut v: serde_json::Value =\n            serde_json::from_str(&fs::read_to_string(&cfg_path)?).context(\"parse config.json\")?;\n        v[\"linux\"][\"memoryPolicy\"] = json!({\n            \"mode\": \"INTERLEAVE\",\n            \"nodes\": \"0\"\n        });\n        fs::write(&cfg_path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    });\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected error for invalid memory policy mode, found none\"\n        )),\n        TestResult::Skipped => TestResult::Skipped,\n    }\n}\n\nfn invalid_flag_string() -> TestResult {\n    let spec = match spec_with_runtimetest(\"memory_policy\", None) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let cfg_path = bundle.join(\"config.json\");\n        let mut v: serde_json::Value =\n            serde_json::from_str(&fs::read_to_string(&cfg_path)?).context(\"parse config.json\")?;\n        v[\"linux\"][\"memoryPolicy\"] = json!({\n            \"mode\": \"MPOL_PREFERRED\",\n            \"nodes\": \"0\",\n            \"flags\": [\"MPOL_F_RELATIVE_NODES\", \"badflag\"]\n        });\n        fs::write(&cfg_path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    });\n\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected error for invalid memory policy flag, found none\"\n        )),\n        TestResult::Skipped => TestResult::Skipped,\n    }\n}\n\nfn missing_mode_but_nodes_present() -> TestResult {\n    let spec = match spec_with_runtimetest(\"memory_policy\", None) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let cfg_path = bundle.join(\"config.json\");\n        let mut v: serde_json::Value =\n            serde_json::from_str(&fs::read_to_string(&cfg_path)?).context(\"parse config.json\")?;\n        v[\"linux\"][\"memoryPolicy\"] = json!({ \"nodes\": \"0-7\" });\n        fs::write(&cfg_path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    });\n\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => {\n            TestResult::Failed(anyhow!(\"expected error for missing mode, found none\"))\n        }\n        TestResult::Skipped => TestResult::Skipped,\n    }\n}\n\nfn syscall_invalid_arguments() -> TestResult {\n    let spec = match spec_with_runtimetest(\"memory_policy\", None) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let cfg_path = bundle.join(\"config.json\");\n        let mut v: serde_json::Value =\n            serde_json::from_str(&fs::read_to_string(&cfg_path)?).context(\"parse config.json\")?;\n        v[\"linux\"][\"memoryPolicy\"] = json!({\n            \"mode\": \"MPOL_DEFAULT\",\n            \"nodes\": \"0-7\",\n            \"flags\": [\"MPOL_F_NUMA_BALANCING\", \"MPOL_F_STATIC_NODES\", \"MPOL_F_RELATIVE_NODES\"]\n        });\n        fs::write(&cfg_path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    });\n\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected error for invalid set_mempolicy args, found none\"\n        )),\n        TestResult::Skipped => TestResult::Skipped,\n    }\n}\n\nfn bind_way_too_large_node_number() -> TestResult {\n    let spec = match spec_with_runtimetest(\"memory_policy\", None) {\n        Ok(s) => s,\n        Err(e) => return TestResult::Failed(e),\n    };\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let cfg_path = bundle.join(\"config.json\");\n        let mut v: serde_json::Value =\n            serde_json::from_str(&fs::read_to_string(&cfg_path)?).context(\"parse config.json\")?;\n        v[\"linux\"][\"memoryPolicy\"] = json!({\n            \"mode\": \"MPOL_BIND\",\n            \"nodes\": \"0-9876543210\",\n            \"flags\": []\n        });\n        fs::write(&cfg_path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    });\n\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected error for invalid memory policy node, found none\"\n        )),\n        TestResult::Skipped => TestResult::Skipped,\n    }\n}\n\npub fn get_linux_memory_policy_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"memory_policy\");\n\n    let test_interleave_without_flags = Test::new(\n        \"interleave_without_flags\",\n        Box::new(interleave_without_flags),\n    );\n    let test_bind_static = Test::new(\"bind_static\", Box::new(bind_static));\n    let test_preferred_relative = Test::new(\"preferred_relative\", Box::new(preferred_relative));\n\n    let test_default_with_missing_nodes_ok = Test::new(\n        \"default_with_missing_nodes_ok\",\n        Box::new(default_with_missing_nodes_ok),\n    );\n    let test_invalid_mode_string = Test::new(\"invalid_mode_string\", Box::new(invalid_mode_string));\n    let test_invalid_flag_string = Test::new(\"invalid_flag_string\", Box::new(invalid_flag_string));\n    let test_missing_mode_but_nodes_present = Test::new(\n        \"missing_mode_but_nodes_present\",\n        Box::new(missing_mode_but_nodes_present),\n    );\n    let test_syscall_invalid_arguments = Test::new(\n        \"syscall_invalid_arguments\",\n        Box::new(syscall_invalid_arguments),\n    );\n    let test_bind_way_too_large_node_number = Test::new(\n        \"bind_way_too_large_node_number\",\n        Box::new(bind_way_too_large_node_number),\n    );\n\n    tg.add(vec![\n        Box::new(test_interleave_without_flags),\n        Box::new(test_bind_static),\n        Box::new(test_preferred_relative),\n    ]);\n\n    tg.add(vec![\n        Box::new(test_default_with_missing_nodes_ok),\n        Box::new(test_invalid_mode_string),\n        Box::new(test_invalid_flag_string),\n        Box::new(test_missing_mode_but_nodes_present),\n        Box::new(test_syscall_invalid_arguments),\n        Box::new(test_bind_way_too_large_node_number),\n    ]);\n\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/memory_policy/mod.rs",
    "content": "mod memory_policy_test;\n\npub use memory_policy_test::get_linux_memory_policy_tests;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/misc_props/misc_props_test.rs",
    "content": "use std::fs;\nuse std::path::Path;\n\nuse anyhow::{Context, Result, anyhow, bail};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse serde_json::Value;\nuse test_framework::{ConditionalTest, TestGroup, TestResult};\n\nuse crate::utils::{CreateOptions, is_runtime_runc, test_inside_container};\n\nfn create_spec() -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"/bin/sh\".into(), \"-c\".into(), \"true\".into()])\n                .build()\n                .context(\"build process\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n    Ok(spec)\n}\n\nfn host_config_path_from_rootfs(rootfs: &Path) -> Result<std::path::PathBuf> {\n    let bundle_dir = rootfs.parent().context(\"no parent for rootfs\")?;\n    Ok(bundle_dir.join(\"config.json\"))\n}\n\nfn write_top_level_str(path: &Path, key: &str, value: &str) -> Result<()> {\n    let s = fs::read_to_string(path)?;\n    let mut v: Value = serde_json::from_str(&s)?;\n    if let Value::Object(map) = &mut v {\n        map.insert(key.to_string(), Value::String(value.to_string()));\n        fs::write(path, serde_json::to_vec_pretty(&v)?)?;\n        Ok(())\n    } else {\n        bail!(\"config.json is not a JSON object\");\n    }\n}\n\nfn annotations_unknown_key_ignored_test() -> TestResult {\n    let mut spec = create_spec().unwrap();\n\n    let mut ann = spec.annotations().clone().unwrap_or_default();\n    ann.insert(\"org.youki.misc-props.unknown\".to_string(), String::new());\n    spec.set_annotations(Some(ann));\n\n    test_inside_container(&spec, &CreateOptions::default(), &|_rootfs| Ok(()))\n}\n\nfn unknown_top_level_property_ignored_test() -> TestResult {\n    let spec = create_spec().unwrap();\n\n    test_inside_container(&spec, &CreateOptions::default(), &|rootfs| {\n        let host_cfg = host_config_path_from_rootfs(rootfs)?;\n        write_top_level_str(&host_cfg, \"unknown\", \"value\")\n    })\n}\n\nfn invalid_oci_version_must_error_test() -> TestResult {\n    let spec = create_spec().unwrap();\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|rootfs| {\n        let host_cfg = host_config_path_from_rootfs(rootfs)?;\n        write_top_level_str(&host_cfg, \"ociVersion\", \"invalid\")\n    });\n\n    match res {\n        TestResult::Failed(_) => TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected invalid ociVersion to fail, but container started successfully\"\n        )),\n        other => other,\n    }\n}\n\npub fn get_misc_props_test() -> TestGroup {\n    let mut misc_props_group = TestGroup::new(\"set_misc_props\");\n\n    let annotations_unknown_key_ignored_test = ConditionalTest::new(\n        \"annotations_unknown_key_ignored_test\",\n        Box::new(|| true),\n        Box::new(annotations_unknown_key_ignored_test),\n    );\n\n    let unknown_top_level_property_ignored_test = ConditionalTest::new(\n        \"unknown_top_level_property_ignored_test\",\n        Box::new(|| true),\n        Box::new(unknown_top_level_property_ignored_test),\n    );\n\n    // runc does not perform strict ociVersion validation,\n    // so this negative test is skipped on runc.\n    // youki does validate spec.version as 1.x.y, so we run this test on youki.\n    let invalid_oci_version_must_error_test = ConditionalTest::new(\n        \"invalid_oci_version_must_error_test\",\n        Box::new(|| !is_runtime_runc()),\n        Box::new(invalid_oci_version_must_error_test),\n    );\n\n    misc_props_group.add(vec![\n        Box::new(annotations_unknown_key_ignored_test),\n        Box::new(unknown_top_level_property_ignored_test),\n        Box::new(invalid_oci_version_must_error_test),\n    ]);\n    misc_props_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/misc_props/mod.rs",
    "content": "mod misc_props_test;\n\npub use misc_props_test::get_misc_props_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/mod.rs",
    "content": "pub mod cgroups;\npub mod create_runtime;\npub mod delete;\npub mod devices;\npub mod domainname;\npub mod example;\npub mod exec;\npub mod exec_cpu_affinity;\npub mod exec_env;\npub mod fd_control;\npub mod hooks;\npub mod hostname;\npub mod intel_rdt;\npub mod io_priority;\npub mod kill;\npub mod kill_no_effect;\npub mod lifecycle;\npub mod linux_masked_paths;\npub mod linux_ns_itype;\npub mod memory_policy;\npub mod misc_props;\npub mod mounts_recursive;\npub mod net_devices;\npub mod no_pivot;\npub mod personality;\npub mod pidfile;\npub mod poststart;\npub mod poststart_fail;\npub mod poststop;\npub mod prestart;\npub mod prestart_fail;\npub mod process;\npub mod process_capabilities_fail;\npub mod process_oom_score_adj;\npub mod process_rlimits;\npub mod process_rlimits_fail;\npub mod process_user;\npub mod prohibit_symlink;\npub mod readonly_paths;\npub mod root_readonly_true;\npub mod rootfs_propagation;\npub mod scheduler;\npub mod seccomp;\npub mod seccomp_notify;\npub mod sysctl;\npub mod tlb;\npub mod uid_mappings;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/mounts_recursive/mod.rs",
    "content": "use std::collections::hash_set::HashSet;\nuse std::fs;\nuse std::fs::{File, copy, create_dir, write};\nuse std::os::unix::fs::symlink;\nuse std::os::unix::prelude::PermissionsExt;\nuse std::path::{Path, PathBuf};\nuse std::str::FromStr;\n\nuse anyhow::anyhow;\nuse nix::mount::{MsFlags, mount, umount};\nuse nix::sys::stat::{Mode, SFlag, makedev, mknod};\nuse nix::unistd::{Uid, chown};\nuse oci_spec::runtime::{\n    Capability, LinuxBuilder, LinuxCapabilitiesBuilder, Mount, ProcessBuilder, Spec, SpecBuilder,\n    get_default_mounts,\n};\nuse tempfile::TempDir;\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nconst MOUNT_DEST: &str = \"/mnt\";\n\nfn get_spec(added_mounts: Vec<Mount>, process_args: Vec<String>) -> Spec {\n    let mut mounts = get_default_mounts();\n    for mount in added_mounts {\n        mounts.push(mount);\n    }\n\n    let caps = vec![\n        Capability::Chown,\n        Capability::DacOverride,\n        Capability::Fsetid,\n        Capability::Fowner,\n        Capability::Mknod,\n        Capability::NetRaw,\n        Capability::Setgid,\n        Capability::Setuid,\n        Capability::Setfcap,\n        Capability::Setpcap,\n        Capability::NetBindService,\n        Capability::SysChroot,\n        Capability::Kill,\n        Capability::AuditWrite,\n    ];\n    let mut cap_bounding = HashSet::new();\n    let mut cap_effective = HashSet::new();\n    let mut cap_permitted = HashSet::new();\n\n    for cap in caps {\n        cap_bounding.insert(cap);\n        cap_effective.insert(cap);\n        cap_permitted.insert(cap);\n    }\n\n    SpecBuilder::default()\n        .mounts(mounts)\n        .linux(\n            // Need to reset the read-only paths\n            LinuxBuilder::default()\n                .readonly_paths(vec![])\n                .build()\n                .expect(\"error in building linux config\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(process_args)\n                .capabilities(\n                    LinuxCapabilitiesBuilder::default()\n                        .bounding(cap_bounding)\n                        .effective(cap_effective)\n                        .permitted(cap_permitted)\n                        .build()\n                        .unwrap(),\n                )\n                .rlimits(vec![])\n                .no_new_privileges(false)\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap()\n}\n\nfn setup_mount(mount_dir: &Path, sub_mount_dir: &Path) -> anyhow::Result<()> {\n    create_dir(mount_dir)?;\n    mount::<Path, Path, str, str>(None, mount_dir, Some(\"tmpfs\"), MsFlags::empty(), None)?;\n    create_dir(sub_mount_dir)?;\n    mount::<Path, Path, str, str>(None, sub_mount_dir, Some(\"tmpfs\"), MsFlags::empty(), None)?;\n    Ok(())\n}\n\nfn setup_remount(mount_dir: &Path, sub_mount_dir: &Path, flag: MsFlags) -> anyhow::Result<()> {\n    mount::<Path, Path, str, str>(None, mount_dir, None, MsFlags::MS_REMOUNT | flag, None)?;\n    mount::<Path, Path, str, str>(None, sub_mount_dir, None, MsFlags::MS_REMOUNT | flag, None)?;\n    Ok(())\n}\n\nfn clean_mount(mount_dir: &Path, sub_mount_dir: &Path) -> anyhow::Result<()> {\n    umount(sub_mount_dir)?;\n    umount(mount_dir)?;\n    fs::remove_dir_all(mount_dir)?;\n    Ok(())\n}\n\n// Helper for mounts_recursive tests.\n// - `mount_options`: OCI mount options (e.g. [\"rbind\", \"rro\"]).\n// - `process_args`: If `None`, uses the default [\"runtimetest\", \"mounts_recursive\"].\n// - `setup_extra`: Hook to perform additional host-side setup before starting the container.\n//   It takes three parameters:\n//   - `bundle_path`: Bundle root (e.g. used to copy files from bundle/bin).\n//   - `dir_path`: Host directory that will be mounted at /mnt.\n//   - `subdir_path`: Host subdirectory that will be mounted at /mnt/mount_subdir.\nfn check_recursive<F>(\n    mount_options: Vec<String>,\n    process_args: Option<Vec<String>>,\n    setup_extra: F,\n) -> TestResult\nwhere\n    F: Fn(&Path, &Path, &Path) -> anyhow::Result<()>,\n{\n    let test_base_dir = TempDir::new().unwrap();\n    let dir_path = test_base_dir.path().join(\"mount_dir\");\n    let subdir_path = dir_path.join(\"mount_subdir\");\n    let mount_dest_path = PathBuf::from_str(MOUNT_DEST).unwrap();\n\n    let mut mount_spec = Mount::default();\n    mount_spec\n        .set_destination(mount_dest_path)\n        .set_typ(None)\n        .set_source(Some(dir_path.clone()))\n        .set_options(Some(mount_options));\n\n    let process_args = process_args\n        .unwrap_or_else(|| vec![\"runtimetest\".to_string(), \"mounts_recursive\".to_string()]);\n\n    let spec = get_spec(vec![mount_spec], process_args);\n\n    let result = test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        setup_mount(&dir_path, &subdir_path).map_err(|e| anyhow!(\"setup_mount failed: {e:?}\"))?;\n        setup_extra(bundle_path, &dir_path, &subdir_path)\n            .map_err(|e| anyhow!(\"setup_extra failed: {e:?}\"))?;\n        Ok(())\n    });\n\n    // Even if clean_mount fails, we do not treat the test as failed.\n    if let Err(e) = clean_mount(&dir_path, &subdir_path) {\n        eprintln!(\n            \"clean_mount failed (mount_dir={}, sub_mount_dir={}): {e:?}\",\n            dir_path.display(),\n            subdir_path.display(),\n        );\n    };\n\n    result\n}\n\n// Mount Recursive test\n// Host:\n//   - <tmp>/mount_dir is a mount point (tmpfs)\n//   - <tmp>/mount_dir/mount_subdir is a submount (tmpfs)\n//\n// rbind:\n//   - Bind-mount <tmp>/mount_dir into the container at /mnt (rbind)\n//\n// In the container, we should observe:\n//   - mount at /mnt\n//   - submount at /mnt/mount_subdir\n//\n// Key check: the mount options/attributes must be applied to SUBMOUNTS.\n\n// rro_test\n// rro makes both /mnt and /mnt/mount_subdir read-only (including submounts).\nfn check_recursive_readonly() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rro\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir: &Path| {\n            write(subdir.join(\"bar\"), b\"bar\\n\").map_err(|e| anyhow!(\"write bar failed: {e:?}\"))?;\n            Ok(())\n        },\n    )\n}\n\n// rnosuid_test\nfn check_recursive_nosuid() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnosuid\".to_string()];\n    let mount_dest_path = PathBuf::from_str(MOUNT_DEST).unwrap();\n    let executable_file_name = \"whoami\";\n    check_recursive(\n        mount_options,\n        Some(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\n                \"{}; {}\",\n                mount_dest_path.join(executable_file_name).to_str().unwrap(),\n                mount_dest_path\n                    .join(\"mount_subdir/whoami\")\n                    .to_str()\n                    .unwrap()\n            ),\n        ]),\n        |bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            let executable_file_path = bundle_path.join(\"bin\").join(executable_file_name);\n            let in_container_executable_file_path = dir_path.join(executable_file_name);\n            let in_container_executable_subdir_file_path = subdir_path.join(executable_file_name);\n\n            copy(&executable_file_path, &in_container_executable_file_path)?;\n            copy(\n                &executable_file_path,\n                &in_container_executable_subdir_file_path,\n            )?;\n\n            let in_container_executable_file = File::open(&in_container_executable_file_path)?;\n            let in_container_executable_subdir_file =\n                File::open(&in_container_executable_subdir_file_path)?;\n\n            let mut in_container_executable_file_perm =\n                in_container_executable_file.metadata()?.permissions();\n            let mut in_container_executable_subdir_file_perm = in_container_executable_subdir_file\n                .metadata()?\n                .permissions();\n\n            // Change file user to nonexistent uid and set suid.\n            // if rnosuid is applied, whoami command is executed as root.\n            // but if not adapted, whoami command is executed as uid 1200 and make an error.\n            chown(\n                &in_container_executable_file_path,\n                Some(Uid::from_raw(1200)),\n                None,\n            )\n            .unwrap();\n            chown(\n                &in_container_executable_subdir_file_path,\n                Some(Uid::from_raw(1200)),\n                None,\n            )\n            .unwrap();\n            in_container_executable_file_perm\n                .set_mode(in_container_executable_file_perm.mode() | Mode::S_ISUID.bits());\n            in_container_executable_subdir_file_perm\n                .set_mode(in_container_executable_subdir_file_perm.mode() | Mode::S_ISUID.bits());\n\n            in_container_executable_file\n                .set_permissions(in_container_executable_file_perm.clone())?;\n            in_container_executable_subdir_file\n                .set_permissions(in_container_executable_subdir_file_perm.clone())?;\n            Ok(())\n        },\n    )\n}\n\n// rsuid_test\n// Mounting with 'rsuid' honors SUID bits on this mount (i.e., SUID can take effect).\n// Note: The bits remain set in the inode (e.g., ls -l still shows 's').\nfn check_recursive_rsuid() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rsuid\".to_string()];\n    let mount_dest_path = PathBuf::from_str(MOUNT_DEST).unwrap();\n    let executable_file_name = \"whoami\";\n    let result = check_recursive(\n        mount_options,\n        Some(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\n                \"{}; {}\",\n                mount_dest_path.join(executable_file_name).to_str().unwrap(),\n                mount_dest_path\n                    .join(\"mount_subdir/whoami\")\n                    .to_str()\n                    .unwrap()\n            ),\n        ]),\n        |bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            let executable_file_path = bundle_path.join(\"bin\").join(executable_file_name);\n            let in_container_executable_file_path = dir_path.join(executable_file_name);\n            let in_container_executable_subdir_file_path = subdir_path.join(executable_file_name);\n            copy(&executable_file_path, &in_container_executable_file_path)?;\n            copy(\n                &executable_file_path,\n                &in_container_executable_subdir_file_path,\n            )?;\n\n            let in_container_executable_file = File::open(&in_container_executable_file_path)?;\n            let in_container_executable_subdir_file =\n                File::open(&in_container_executable_subdir_file_path)?;\n            let mut in_container_executable_file_perm =\n                in_container_executable_file.metadata()?.permissions();\n            let mut in_container_executable_subdir_file_perm = in_container_executable_subdir_file\n                .metadata()?\n                .permissions();\n\n            // Change file user to nonexistent uid and set suid.\n            // if rsuid is applied, whoami command is executed as 1200 and make an error.\n            chown(\n                &in_container_executable_file_path,\n                Some(Uid::from_raw(1200)),\n                None,\n            )\n            .unwrap();\n            chown(\n                &in_container_executable_subdir_file_path,\n                Some(Uid::from_raw(1200)),\n                None,\n            )\n            .unwrap();\n            in_container_executable_file_perm\n                .set_mode(in_container_executable_file_perm.mode() | Mode::S_ISUID.bits());\n            in_container_executable_subdir_file_perm\n                .set_mode(in_container_executable_subdir_file_perm.mode() | Mode::S_ISUID.bits());\n\n            in_container_executable_file\n                .set_permissions(in_container_executable_file_perm.clone())?;\n            in_container_executable_subdir_file\n                .set_permissions(in_container_executable_subdir_file_perm.clone())?;\n            Ok(())\n        },\n    );\n\n    match result {\n        TestResult::Failed(e) => {\n            let msg = e.to_string();\n            // This error message may vary depending on the environment.\n            if msg.contains(\"whoami: unknown uid 1200\") {\n                TestResult::Passed\n            } else {\n                TestResult::Failed(anyhow!(\n                    \"whoami failed, but not with expected message. error: {}\",\n                    msg\n                ))\n            }\n        }\n        // TestResult::Passed,\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"Expected execute a non-existent user to fail, but it succeeded\"\n        )),\n        _ => TestResult::Failed(anyhow!(\"Unexpected test result\")),\n    }\n}\n\n// rnoexec_test\nfn check_recursive_noexec() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnoexec\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            let executable_file_name = \"echo\";\n            let executable_file_path = bundle_path.join(\"bin\").join(executable_file_name);\n            let in_container_executable_file_path = dir_path.join(executable_file_name);\n            let in_container_executable_subdir_file_path = subdir_path.join(executable_file_name);\n\n            copy(&executable_file_path, in_container_executable_file_path)?;\n            copy(\n                &executable_file_path,\n                in_container_executable_subdir_file_path,\n            )?;\n\n            Ok(())\n        },\n    )\n}\n\n// rexec_test\nfn check_recursive_rexec() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rexec\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            let executable_file_name = \"echo\";\n            let executable_file_path = bundle_path.join(\"bin\").join(executable_file_name);\n            let in_container_executable_file_path = dir_path.join(executable_file_name);\n            let in_container_executable_subdir_file_path = subdir_path.join(executable_file_name);\n\n            copy(&executable_file_path, in_container_executable_file_path)?;\n            copy(\n                &executable_file_path,\n                in_container_executable_subdir_file_path,\n            )?;\n\n            Ok(())\n        },\n    )\n}\n\n// rdiratime_test\n// rdiratime If set in attr_clr, removes the restriction that prevented updating access time for directories.\nfn check_recursive_rdiratime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rdiratime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, _subdir_path: &Path| Ok(()),\n    )\n}\n\n// rnodiratime_test\n// If set in attr_set, prevents updating access time for directories on this mount\nfn check_recursive_rnodiratime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnodiratime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, _subdir_path: &Path| Ok(()),\n    )\n}\n\n// rdev_test\nfn check_recursive_rdev() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rdev\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir_path: &Path| {\n            let dev = makedev(1, 3);\n            mknod(\n                &subdir_path.join(\"null\"),\n                SFlag::S_IFCHR,\n                Mode::from_bits_truncate(0o666),\n                dev,\n            )\n            .expect(\"create null device\");\n            Ok(())\n        },\n    )\n}\n\n// rnodev_test\nfn check_recursive_rnodev() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnodev\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir_path: &Path| {\n            let dev = makedev(1, 3);\n            mknod(\n                &subdir_path.join(\"null\"),\n                SFlag::S_IFCHR,\n                Mode::from_bits_truncate(0o666),\n                dev,\n            )\n            .expect(\"create null device\");\n            Ok(())\n        },\n    )\n}\n\n// rrw_test\nfn check_recursive_readwrite() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rrw\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir_path: &Path| {\n            write(subdir_path.join(\"bar\"), b\"bar\\n\").unwrap();\n            Ok(())\n        },\n    )\n}\n\n// rrelatime_test\nfn check_recursive_rrelatime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rrelatime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            setup_remount(dir_path, subdir_path, MsFlags::MS_STRICTATIME)\n                .map_err(|e| anyhow!(\"setup_remount failed: {e:?}\"))?;\n            Ok(())\n        },\n    )\n}\n\n// rnorelatime_test\nfn check_recursive_rnorelatime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnorelatime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            // The container implementation treats `norelatime` as clearing the `relatime` flag.\n            // In this test, the mount is configured with `strictatime`, so once `relatime` is cleared, the mount ends up using `strictatime`.\n            setup_remount(dir_path, subdir_path, MsFlags::MS_STRICTATIME)\n                .map_err(|e| anyhow!(\"setup_remount failed: {e:?}\"))?;\n            Ok(())\n        },\n    )\n}\n\n// rnoatime_test\nfn check_recursive_rnoatime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnoatime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, _subdir_path: &Path| Ok(()),\n    )\n}\n\n// rstrictatime_test\nfn check_recursive_rstrictatime() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rstrictatime\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, _subdir_path: &Path| Ok(()),\n    )\n}\n\n// rnosymfollow_test\nfn check_recursive_rnosymfollow() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rnosymfollow\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir_path: &Path| {\n            let original_file_path = format!(\"{}/{}\", subdir_path.to_str().unwrap(), \"file\");\n            let _ = File::create(&original_file_path)?;\n            let link_file_path = format!(\"{}/{}\", subdir_path.to_str().unwrap(), \"link\");\n\n            symlink(original_file_path, link_file_path)?;\n            Ok(())\n        },\n    )\n}\n\n// rsymfollow_test\nfn check_recursive_rsymfollow() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"rsymfollow\".to_string()];\n    check_recursive(\n        mount_options,\n        None,\n        |_bundle_path: &Path, _dir_path: &Path, subdir_path: &Path| {\n            let original_file_path = format!(\"{}/{}\", subdir_path.to_str().unwrap(), \"file\");\n            let _ = File::create(&original_file_path)?;\n            let link_file_path = format!(\"{}/{}\", subdir_path.to_str().unwrap(), \"link\");\n            symlink(original_file_path, link_file_path)?;\n            Ok(())\n        },\n    )\n}\n\n// rbind_ro_test\n// If we specify `rbind` and `ro`, the top-level mount becomes read-only, but the setting is not applied recursively to submounts.\n// ref: https://github.com/opencontainers/runc/blob/main/tests/integration/mounts_recursive.bats#L34\nfn check_rbind_ro_is_readonly_but_not_recursively() -> TestResult {\n    let mount_options = vec![\"rbind\".to_string(), \"ro\".to_string()];\n    check_recursive(\n        mount_options,\n        Some(vec![\n            \"runtimetest\".to_string(),\n            \"mounts_recursive_rbind_ro\".to_string(),\n        ]),\n        |_bundle_path: &Path, dir_path: &Path, subdir_path: &Path| {\n            write(dir_path.join(\"bar\"), b\"bar\\n\").unwrap();\n            write(subdir_path.join(\"bar\"), b\"bar\\n\").unwrap();\n            Ok(())\n        },\n    )\n}\n\n// this mount test how to work?\n// 1. Create mount_options based on the mount properties of the test\n// 2. Create OCI Spec content, container one process is runtimetest,(runtimetest is cargo model, file path `tests/runtimetest/`)\n// 3. inside container to check if the actual mount matches the spec, (spec https://man7.org/linux/man-pages/man2/mount_setattr.2.html),\n//    eg. tests/runtimetest/src/tests.rs\npub fn get_mounts_recursive_test() -> TestGroup {\n    let rro_test = Test::new(\"rro_test\", Box::new(check_recursive_readonly));\n    let rnosuid_test = Test::new(\"rnosuid_test\", Box::new(check_recursive_nosuid));\n    let rsuid_test = Test::new(\"rsuid_test\", Box::new(check_recursive_rsuid));\n    let rnoexec_test = Test::new(\"rnoexec_test\", Box::new(check_recursive_noexec));\n    let rnodiratime_test = Test::new(\"rnodiratime_test\", Box::new(check_recursive_rnodiratime));\n    let rdiratime_test = Test::new(\"rdiratime_test\", Box::new(check_recursive_rdiratime));\n    let rdev_test = Test::new(\"rdev_test\", Box::new(check_recursive_rdev));\n    let rnodev_test = Test::new(\"rnodev_test\", Box::new(check_recursive_rnodev));\n    let rrw_test = Test::new(\"rrw_test\", Box::new(check_recursive_readwrite));\n    let rexec_test = Test::new(\"rexec_test\", Box::new(check_recursive_rexec));\n    let rrelatime_test = Test::new(\"rrelatime_test\", Box::new(check_recursive_rrelatime));\n    let rnorelatime_test = Test::new(\"rnorelatime_test\", Box::new(check_recursive_rnorelatime));\n    let rnoatime_test = Test::new(\"rnoatime_test\", Box::new(check_recursive_rnoatime));\n    let rstrictatime_test = Test::new(\"rstrictatime_test\", Box::new(check_recursive_rstrictatime));\n    let rnosymfollow_test = Test::new(\"rnosymfollow_test\", Box::new(check_recursive_rnosymfollow));\n    let rsymfollow_test = Test::new(\"rsymfollow_test\", Box::new(check_recursive_rsymfollow));\n    let rbind_ro_test = Test::new(\n        \"rbind_ro_test\",\n        Box::new(check_rbind_ro_is_readonly_but_not_recursively),\n    );\n\n    let mut tg = TestGroup::new(\"mounts_recursive\");\n    tg.add(vec![\n        Box::new(rro_test),\n        Box::new(rnosuid_test),\n        Box::new(rsuid_test),\n        Box::new(rnoexec_test),\n        Box::new(rdiratime_test),\n        Box::new(rnodiratime_test),\n        Box::new(rdev_test),\n        Box::new(rnodev_test),\n        Box::new(rrw_test),\n        Box::new(rexec_test),\n        Box::new(rrelatime_test),\n        Box::new(rnorelatime_test),\n        Box::new(rnoatime_test),\n        Box::new(rstrictatime_test),\n        Box::new(rnosymfollow_test),\n        Box::new(rsymfollow_test),\n        Box::new(rbind_ro_test),\n    ]);\n\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/net_devices/mod.rs",
    "content": "mod net_devices_test;\npub use net_devices_test::get_net_devices_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/net_devices/net_devices_test.rs",
    "content": "use std::collections::HashMap;\nuse std::path::Path;\n\nuse anyhow::{Result, anyhow};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxNamespaceBuilder, LinuxNamespaceType, LinuxNetDevice, LinuxNetDeviceBuilder,\n    ProcessBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_utils::{\n    CreateOptions, check_container_created, exec_container, start_container,\n};\nuse crate::utils::{test_inside_container, test_outside_container};\n\nfn create_unique_name(prefix: &str) -> String {\n    let random_part: u16 = rand::random();\n    format!(\"{}{}\", prefix, random_part)\n}\n\nfn create_netns(name: &str) -> Result<()> {\n    // Ensure /run/netns mount propagation is shared before creating netns\n    // This is needed in case previous tests changed mount propagation to private\n    let _ = std::process::Command::new(\"mount\")\n        .args(vec![\"--make-shared\", \"/\"])\n        .output();\n\n    let output = std::process::Command::new(\"ip\")\n        .args(vec![\"netns\", \"add\", name])\n        .output()?;\n    if !output.status.success() {\n        return Err(anyhow!(\n            \"Failed to create netns: {}\",\n            String::from_utf8_lossy(&output.stderr)\n        ));\n    }\n\n    Ok(())\n}\n\nfn cleanup_netns(name: &str) -> Result<()> {\n    // Ensure /run/netns mount propagation is shared before deleting netns\n    // This is needed in case previous tests changed mount propagation to private\n    let _ = std::process::Command::new(\"mount\")\n        .args(vec![\"--make-shared\", \"/\"])\n        .output();\n\n    let output = std::process::Command::new(\"ip\")\n        .args(vec![\"netns\", \"del\", name])\n        .output()?;\n    if output.status.success() {\n        Ok(())\n    } else {\n        Err(anyhow!(\n            \"Failed to cleanup netns: {}\",\n            String::from_utf8_lossy(&output.stderr)\n        ))\n    }\n}\n\nfn create_dummy_device(name: &str) -> Result<()> {\n    let output = std::process::Command::new(\"ip\")\n        .args(vec![\"link\", \"add\", name, \"type\", \"dummy\"])\n        .output()?;\n\n    if output.status.success() {\n        Ok(())\n    } else {\n        Err(anyhow!(\n            \"Failed to create dummy device: {}\",\n            String::from_utf8_lossy(&output.stderr)\n        ))\n    }\n}\n\nfn delete_dummy_device(name: &str) -> Result<()> {\n    let output = std::process::Command::new(\"ip\")\n        .args(vec![\"link\", \"del\", name])\n        .output()?;\n\n    if output.status.success() {\n        Ok(())\n    } else {\n        Err(anyhow!(\n            \"Failed to delete dummy device: {}\",\n            String::from_utf8_lossy(&output.stderr)\n        ))\n    }\n}\n\n/// RAII guard for dummy network devices that automatically cleans up on drop\nstruct DummyDevice {\n    name: String,\n}\n\nimpl DummyDevice {\n    fn create(name: String) -> Result<Self> {\n        create_dummy_device(&name)?;\n        Ok(Self { name })\n    }\n}\n\nimpl Drop for DummyDevice {\n    fn drop(&mut self) {\n        let _ = delete_dummy_device(&self.name);\n    }\n}\n\n/// RAII guard for network namespaces that automatically cleans up on drop\nstruct NetNamespace {\n    name: String,\n}\n\nimpl NetNamespace {\n    fn create(name: String) -> Result<Self> {\n        create_netns(&name)?;\n        Ok(Self { name })\n    }\n}\n\nimpl Drop for NetNamespace {\n    fn drop(&mut self) {\n        let _ = cleanup_netns(&self.name);\n    }\n}\n\nfn check_device_exists(name: &str) -> Result<bool> {\n    let out = std::process::Command::new(\"ip\")\n        .args(vec![\"link\", \"show\", name])\n        .output()?;\n    Ok(out.status.success())\n}\n\nfn create_spec(net_devices: HashMap<String, LinuxNetDevice>) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .net_devices(net_devices)\n                .build()\n                .unwrap(),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"net_devices\".to_string()])\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap()\n}\n\nfn create_spec_without_runtimetest(net_devices: HashMap<String, LinuxNetDevice>) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .net_devices(net_devices)\n                .build()\n                .unwrap(),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"sleep\".to_string(), \"infinity\".to_string()])\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap()\n}\n\nfn create_spec_with_netns(net_devices: HashMap<String, LinuxNetDevice>, netns: String) -> Spec {\n    let mut spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .net_devices(net_devices)\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap();\n\n    // Get default namespaces and retain all except network\n    let mut namespaces = spec\n        .linux()\n        .as_ref()\n        .and_then(|l| l.namespaces().as_ref())\n        .cloned()\n        .unwrap_or_default();\n\n    // Remove existing network namespace if any\n    namespaces.retain(|ns| ns.typ() != LinuxNamespaceType::Network);\n\n    // Add network namespace with custom path\n    namespaces.push(\n        LinuxNamespaceBuilder::default()\n            .typ(LinuxNamespaceType::Network)\n            .path(netns)\n            .build()\n            .unwrap(),\n    );\n\n    // Update spec with modified namespaces\n    if let Some(linux) = spec.linux_mut() {\n        linux.set_namespaces(Some(namespaces));\n    }\n\n    spec\n}\n\nfn check_net_device() -> TestResult {\n    let device_name = create_unique_name(\"dummy\");\n\n    if let Err(e) = create_dummy_device(&device_name) {\n        return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e));\n    }\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(device_name.clone(), LinuxNetDevice::default());\n    let spec = create_spec(net_devices);\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()));\n\n    match check_device_exists(&device_name) {\n        Ok(true) => {\n            if let Err(e) = delete_dummy_device(&device_name) {\n                return TestResult::Failed(anyhow!(\"Failed to delete device: {}\", e));\n            }\n            TestResult::Failed(anyhow!(\"The device still exists after test\"))\n        }\n        Ok(false) => TestResult::Passed,\n        Err(e) => {\n            if let Err(e) = delete_dummy_device(&device_name) {\n                return TestResult::Failed(anyhow!(\"Failed to delete device: {}\", e));\n            }\n            TestResult::Failed(anyhow!(\"Failed to check device: {}\", e))\n        }\n    }\n}\n\nfn check_net_device_rename() -> TestResult {\n    let device_name = create_unique_name(\"rename\");\n    let device_name_rename = create_unique_name(\"renamed\");\n\n    if let Err(e) = create_dummy_device(&device_name) {\n        return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e));\n    }\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(\n        device_name.clone(),\n        LinuxNetDeviceBuilder::default()\n            .name(&device_name_rename)\n            .build()\n            .unwrap(),\n    );\n    let spec = create_spec(net_devices);\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()));\n\n    match check_device_exists(&device_name) {\n        Ok(true) => {\n            if let Err(e) = delete_dummy_device(&device_name) {\n                return TestResult::Failed(anyhow!(\"Failed to delete device: {}\", e));\n            }\n            TestResult::Failed(anyhow!(\"The device still exists after test\"))\n        }\n        Ok(false) => TestResult::Passed,\n        Err(e) => {\n            if let Err(e) = delete_dummy_device(&device_name) {\n                return TestResult::Failed(anyhow!(\"Failed to delete device: {}\", e));\n            }\n            TestResult::Failed(anyhow!(\"Failed to check device: {}\", e))\n        }\n    }\n}\n\nfn check_net_devices() -> TestResult {\n    let device_name1 = create_unique_name(\"dummy\");\n    let device_name2 = create_unique_name(\"dummy\");\n\n    let _device1 = match DummyDevice::create(device_name1.clone()) {\n        Ok(dev) => dev,\n        Err(e) => return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e)),\n    };\n\n    let _device2 = match DummyDevice::create(device_name2.clone()) {\n        Ok(dev) => dev,\n        Err(e) => return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e)),\n    };\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(device_name1.clone(), LinuxNetDevice::default());\n    net_devices.insert(device_name2.clone(), LinuxNetDevice::default());\n    let spec = create_spec(net_devices);\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()));\n\n    let mut result = TestResult::Passed;\n\n    match check_device_exists(&device_name1) {\n        Ok(true) => {\n            result = TestResult::Failed(anyhow!(\"The device1 still exists after test\"));\n        }\n        Ok(false) => {}\n        Err(e) => {\n            result = TestResult::Failed(anyhow!(\"Failed to check device1: {}\", e));\n        }\n    }\n\n    match check_device_exists(&device_name2) {\n        Ok(true) => {\n            if let TestResult::Passed = result {\n                result = TestResult::Failed(anyhow!(\"The device2 still exists after test\"));\n            }\n        }\n        Ok(false) => {}\n        Err(e) => {\n            if let TestResult::Passed = result {\n                result = TestResult::Failed(anyhow!(\"Failed to check device2: {}\", e));\n            }\n        }\n    }\n\n    // Devices will be automatically cleaned up when _device1 and _device2 go out of scope\n\n    result\n}\n\nfn check_empty_net_devices() -> TestResult {\n    let device_name = create_unique_name(\"empty\");\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(device_name.clone(), LinuxNetDevice::default());\n    let spec = create_spec(net_devices);\n    let result = test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()));\n\n    // If the container creation succeeds, we expect an error since the masked paths does not support symlinks.\n    if let TestResult::Passed = result {\n        TestResult::Failed(anyhow!(\n            \"expected error in container creation with invalid net device, found no error\"\n        ))\n    } else {\n        TestResult::Passed\n    }\n}\n\nfn check_back_device() -> TestResult {\n    let netns_name = create_unique_name(\"back\");\n    let device_name = create_unique_name(\"back\");\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(\n        device_name.clone(),\n        LinuxNetDeviceBuilder::default()\n            .name(&device_name)\n            .build()\n            .unwrap(),\n    );\n\n    let _netns = match NetNamespace::create(netns_name.clone()) {\n        Ok(ns) => ns,\n        Err(e) => return TestResult::Failed(anyhow!(\"Failed to create netns: {}\", e)),\n    };\n\n    let _device = match DummyDevice::create(device_name.clone()) {\n        Ok(dev) => dev,\n        Err(e) => return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e)),\n    };\n\n    let spec = create_spec_with_netns(\n        net_devices,\n        Path::new(\"/run/netns\")\n            .join(&netns_name)\n            .to_str()\n            .unwrap()\n            .to_string(),\n    );\n    let test_result = test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n        TestResult::Passed\n    });\n    if let TestResult::Failed(_) = test_result {\n        return test_result;\n    }\n\n    // Move the device back to the original namespace\n    let move_result = std::process::Command::new(\"ip\")\n        .args(vec![\n            \"netns\",\n            \"exec\",\n            &netns_name,\n            \"ip\",\n            \"link\",\n            \"set\",\n            \"dev\",\n            &device_name,\n            \"netns\",\n            \"1\",\n        ])\n        .output();\n\n    if let Err(e) = move_result {\n        // Try to delete the device from the namespace before cleaning up\n        let _ = std::process::Command::new(\"ip\")\n            .args(vec![\n                \"netns\",\n                \"exec\",\n                &netns_name,\n                \"ip\",\n                \"link\",\n                \"del\",\n                &device_name,\n            ])\n            .output();\n        return TestResult::Failed(anyhow!(\"Failed to move device back: {}\", e));\n    }\n\n    // Check that the device exists\n    if let Err(e) = check_device_exists(&device_name) {\n        return TestResult::Failed(anyhow!(\"Failed to check device: {}\", e));\n    }\n\n    // Cleanup will happen automatically when _device and _netns go out of scope\n\n    TestResult::Passed\n}\n\nfn check_address() -> TestResult {\n    let device_name = create_unique_name(\"addr\");\n    const DUMMY_ADDRESS: &str = \"244.178.44.111/24\";\n\n    let mut net_devices = HashMap::new();\n    net_devices.insert(\n        device_name.clone(),\n        LinuxNetDeviceBuilder::default()\n            .name(&device_name)\n            .build()\n            .unwrap(),\n    );\n\n    if let Err(e) = create_dummy_device(&device_name) {\n        return TestResult::Failed(anyhow!(\"Failed to create dummy device: {}\", e));\n    }\n\n    // Add address to the device\n    if let Err(e) = std::process::Command::new(\"ip\")\n        .args(vec![\"addr\", \"add\", DUMMY_ADDRESS, \"dev\", &device_name])\n        .output()\n    {\n        return TestResult::Failed(anyhow!(\"Failed to add address: {}\", e));\n    }\n\n    let spec = create_spec_without_runtimetest(net_devices);\n    let test_result = test_outside_container(&spec, &|data| {\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, _) = exec_container(id, dir, &[\"ip\", \"addr\"], None, &[]).expect(\"exec failed\");\n\n        if !stdout.contains(&device_name) || !stdout.contains(DUMMY_ADDRESS) {\n            return TestResult::Failed(anyhow!(\"unexpected : {}\", stdout));\n        }\n\n        TestResult::Passed\n    });\n    if let TestResult::Failed(_) = test_result {\n        return test_result;\n    }\n\n    TestResult::Passed\n}\n\npub fn get_net_devices_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"net_devices\");\n    test_group.set_nonparallel();\n    let net_device_test = Test::new(\"net_device\", Box::new(check_net_device));\n    let net_device_rename_test = Test::new(\"net_device_rename\", Box::new(check_net_device_rename));\n    let net_devices_test = Test::new(\"net_devices\", Box::new(check_net_devices));\n    let empty_net_devices_test = Test::new(\"empty_net_devices\", Box::new(check_empty_net_devices));\n    let back_device_test = Test::new(\"back_device\", Box::new(check_back_device));\n    let address_test = Test::new(\"address\", Box::new(check_address));\n    test_group.add(vec![\n        Box::new(net_device_test),\n        Box::new(net_device_rename_test),\n        Box::new(net_devices_test),\n        Box::new(empty_net_devices_test),\n        Box::new(back_device_test),\n        Box::new(address_test),\n    ]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/no_pivot/mod.rs",
    "content": "use anyhow::{Context, Result};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_utils::{CreateOptions, test_inside_container};\n\nfn create_spec() -> Result<Spec> {\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"no_pivot\".to_string()])\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn no_pivot_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(\n        &spec,\n        &CreateOptions::default().with_no_pivot_root(),\n        &|_| Ok(()),\n    )\n}\n\npub fn get_no_pivot_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"no_pivot\");\n    let no_pivot_test = Test::new(\"no_pivot_test\", Box::new(no_pivot_test));\n    test_group.add(vec![Box::new(no_pivot_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/personality/mod.rs",
    "content": "use anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxPersonalityBuilder, LinuxPersonalityDomain, ProcessBuilder, Spec,\n    SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_utils::{\n    check_container_created, exec_container, start_container, test_outside_container,\n};\n\nfn create_spec(domain: LinuxPersonalityDomain) -> Result<Spec> {\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(\n                    [\"sleep\", \"1000\"]\n                        .iter()\n                        .map(|s| s.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .build()\n                .context(\"failed to create process\")?,\n        )\n        .linux(\n            LinuxBuilder::default()\n                .personality(\n                    LinuxPersonalityBuilder::default()\n                        .domain(domain)\n                        .build()\n                        .context(\"failed to create personality\")?,\n                )\n                .build()\n                .context(\"failed to create linux\")?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn personality_for_linux(domain: LinuxPersonalityDomain, expect: &str) -> TestResult {\n    let spec = test_result!(create_spec(domain));\n\n    test_outside_container(&spec, &|data| {\n        test_result!(check_container_created(&data));\n\n        let id = &data.id;\n        let dir = &data.bundle;\n\n        let start_result = start_container(id, dir).unwrap().wait().unwrap();\n        if !start_result.success() {\n            return TestResult::Failed(anyhow!(\"container start failed\"));\n        }\n\n        let (stdout, _) =\n            exec_container(id, dir, &[\"uname\", \"-m\"], None, &[]).expect(\"exec failed\");\n\n        if !stdout.contains(expect) {\n            return TestResult::Failed(anyhow!(\"unexpected personality: {}\", stdout));\n        }\n\n        TestResult::Passed\n    })\n}\n\nfn personality_for_linux32() -> TestResult {\n    personality_for_linux(LinuxPersonalityDomain::PerLinux32, \"i686\")\n}\n\nfn personality_for_linux64() -> TestResult {\n    personality_for_linux(LinuxPersonalityDomain::PerLinux, \"x86_64\")\n}\n\npub fn get_personality_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"personality\");\n    let personality_for_linux32 =\n        Test::new(\"personality_for_linux32\", Box::new(personality_for_linux32));\n    test_group.add(vec![Box::new(personality_for_linux32)]);\n\n    let personality_for_linux64 =\n        Test::new(\"personality_for_linux64\", Box::new(personality_for_linux64));\n    test_group.add(vec![Box::new(personality_for_linux64)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/pidfile/mod.rs",
    "content": "mod pidfile_test;\npub use pidfile_test::get_pidfile_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/pidfile/pidfile_test.rs",
    "content": "use std::fs::File;\n\nuse anyhow::anyhow;\nuse test_framework::{Test, TestGroup, TestResult};\nuse uuid::Uuid;\n\nuse crate::utils::{\n    CreateOptions, State, create_container, delete_container, generate_uuid, get_state,\n    kill_container, prepare_bundle,\n};\n\n#[inline]\nfn cleanup(id: &Uuid, bundle: &tempfile::TempDir) {\n    let str_id = id.to_string();\n    kill_container(&str_id, bundle).unwrap().wait().unwrap();\n    delete_container(&str_id, bundle).unwrap().wait().unwrap();\n}\n\nfn test_pidfile() -> TestResult {\n    // create id for the container and pidfile\n    let container_id = generate_uuid();\n\n    // create temp dir for bundle and for storing the pid\n    let bundle = prepare_bundle().unwrap();\n    let pidfile_dir = tempfile::tempdir().unwrap();\n    let pidfile_path = pidfile_dir.as_ref().join(\"pidfile\");\n    let _ = File::create(&pidfile_path).unwrap();\n\n    // start the container\n    create_container(\n        &container_id.to_string(),\n        &bundle,\n        &CreateOptions::default().with_extra_args(&[\"--pid-file\".as_ref(), pidfile_path.as_ref()]),\n    )\n    .unwrap()\n    .wait()\n    .unwrap();\n\n    let (out, err) = get_state(&container_id.to_string(), &bundle).unwrap();\n\n    if !err.is_empty() {\n        cleanup(&container_id, &bundle);\n        return TestResult::Failed(anyhow!(\"error in state : {}\", err));\n    }\n\n    let state: State = serde_json::from_str(&out).unwrap();\n\n    if state.id != container_id.to_string() {\n        cleanup(&container_id, &bundle);\n        return TestResult::Failed(anyhow!(\n            \"error in state : id not matched ,expected {} got {}\",\n            container_id,\n            state.id\n        ));\n    }\n\n    if state.status != \"created\" {\n        cleanup(&container_id, &bundle);\n        return TestResult::Failed(anyhow!(\n            \"error in state : status not matched ,expected 'created' got {}\",\n            state.status\n        ));\n    }\n\n    // get pid from the pidfile\n    let pidfile: i32 = std::fs::read_to_string(pidfile_dir.as_ref().join(\"pidfile\"))\n        .unwrap()\n        .parse()\n        .unwrap();\n\n    // get pid from the state\n    if state.pid.unwrap() != pidfile {\n        cleanup(&container_id, &bundle);\n        return TestResult::Failed(anyhow!(\n            \"error : pid not matched ,expected {} as per state, but got {} from pidfile instead\",\n            state.pid.unwrap(),\n            pidfile\n        ));\n    }\n\n    cleanup(&container_id, &bundle);\n    TestResult::Passed\n}\n\npub fn get_pidfile_test() -> TestGroup {\n    let pidfile = Test::new(\"pidfile\", Box::new(test_pidfile));\n    let mut tg = TestGroup::new(\"pidfile\");\n    tg.add(vec![Box::new(pidfile)]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/poststart/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_utils::CreateOptions;\nuse crate::utils::{\n    create_container, delete_container, generate_uuid, is_runtime_runc, prepare_bundle, set_config,\n};\n\nconst CONTAINER_OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle.as_ref().join(\"bundle\").join(\"rootfs\").join(\"output\")\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn write_process_command() -> Vec<String> {\n    vec![\n        \"/bin/sh\".to_string(),\n        \"-c\".to_string(),\n        format!(\"echo 'process called' >> {}\", CONTAINER_OUTPUT_FILE),\n    ]\n}\n\nfn write_poststart_hook(host_output_file: &str) -> oci_spec::runtime::Hook {\n    HookBuilder::default()\n        .path(\"/bin/sh\")\n        .args(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\"echo 'post-start called' >> {host_output_file}\"),\n        ])\n        .build()\n        .expect(\"could not build hook\")\n}\n\nfn wait_for_file_content(\n    file_path: &PathBuf,\n    expected_content: &str,\n    timeout: std::time::Duration,\n    poll_interval: std::time::Duration,\n) -> anyhow::Result<()> {\n    let start = std::time::Instant::now();\n\n    while start.elapsed() < timeout {\n        if file_path.exists()\n            && let Ok(contents) = fs::read_to_string(file_path)\n            && contents.contains(expected_content)\n        {\n            return Ok(());\n        }\n        std::thread::sleep(poll_interval);\n    }\n\n    let actual_content = fs::read_to_string(file_path).expect(\"failed to read output file\");\n\n    Err(anyhow!(\n        \"Timed out waiting for file {} to contain '{expected_content}', but got: '{actual_content}'\",\n        file_path.display(),\n    ))\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(write_process_command())\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .poststart(vec![write_poststart_hook(host_output_file)])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that the poststart hook executes in the correct order.\n/// The poststart hook should execute after the container process has started.\n/// This is validated by having both the process and hook write to the same file in sequence.\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid();\n            let id_str = id.to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n            let host_output_file_str = host_output_file.to_str().unwrap();\n\n            let spec = get_spec(host_output_file_str);\n            set_config(&bundle, &spec).unwrap();\n\n            create_container(&id_str, &bundle, &CreateOptions::default())\n                .unwrap()\n                .wait()\n                .unwrap();\n\n            if !is_runtime_runc() && host_output_file.exists() {\n                // runc behaviour is incorrect in this case\n                // https://github.com/opencontainers/runc/issues/4347\n                let content = fs::read_to_string(&host_output_file)\n                    .expect(\"failed to read output file after create\");\n                if !content.is_empty() {\n                    let _ = delete_container(&id_str, &bundle);\n                    delete_output_file(&host_output_file);\n                    let has_poststart = content.contains(\"post-start called\");\n                    let has_process = content.contains(\"process called\");\n                    return match (has_poststart, has_process) {\n                        (true, _) => TestResult::Failed(anyhow!(\n                            \"The post-start hooks MUST NOT be called before the `start` operation\"\n                        )),\n                        (false, true) => TestResult::Failed(anyhow!(\n                            \"The user-specified program (from process) MUST NOT be run before the `start` operation\"\n                        )),\n                        (false, false) => TestResult::Failed(anyhow!(\n                            \"file {} should not exist after create, but has content: '{content}'\",\n                            host_output_file.display(),\n                        )),\n                    };\n                }\n            }\n\n            crate::utils::start_container(&id_str, &bundle)\n                .unwrap()\n                .wait()\n                .unwrap();\n\n            let wait_result = wait_for_file_content(\n                &host_output_file,\n                \"process called\",\n                std::time::Duration::from_secs(5),\n                std::time::Duration::from_millis(100),\n            );\n\n            let result = if let Err(e) = wait_result {\n                TestResult::Failed(anyhow!(\"Container process execution failed: {e}\"))\n            } else if !host_output_file.exists() {\n                TestResult::Failed(anyhow!(\n                    \"Expected output file {} does not exist. Neither the container process nor poststart hook created it\",\n                    host_output_file.display()\n                ))\n            } else {\n                let contents =\n                    fs::read_to_string(&host_output_file).expect(\"failed to read output file\");\n                match contents.as_str() {\n                    // Order of the execution between the process logic and post-start hook logic\n                    // is not guaranteed, so both outcomes are acceptable\n                    \"process called\\npost-start called\\n\" => TestResult::Passed,\n                    \"post-start called\\nprocess called\\n\" => TestResult::Passed,\n                    \"process called\\n\" => {\n                        TestResult::Failed(anyhow!(\"The runtime MUST run the post-start hook\"))\n                    }\n                    \"post-start called\\n\" => TestResult::Failed(anyhow!(\n                        \"The runtime MUST run the user-specified program, as specified by `process`\"\n                    )),\n                    _ => TestResult::Failed(anyhow!(\"unsupported output: {contents}\")),\n                }\n            };\n\n            let _ = delete_container(&id_str, &bundle);\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_poststart_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"poststart\");\n    tg.add(vec![Box::new(get_test(\"poststart\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/poststart_fail/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{ConditionalTest, TestGroup, TestResult};\n\nuse crate::utils::test_utils::CreateOptions;\nuse crate::utils::{\n    create_container, delete_container, generate_uuid, is_runtime_runc, prepare_bundle, set_config,\n    start_container,\n};\n\nconst HOOK_OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(HOOK_OUTPUT_FILE)\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"/bin/sh\".to_string(),\n                    \"-c\".to_string(),\n                    \"true\".to_string(),\n                ])\n                .cwd(\"/\")\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .poststart(vec![\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_1 called' >> {host_output_file}\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_2 called' >> {host_output_file}; exit 1\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_3 called' >> {host_output_file}\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                ])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that when a poststart hook fails, subsequent hooks are not executed.\n///\n/// Validates that the runtime stops executing remaining poststart hooks after one fails,\n/// and returns an error (exit code 1). This test creates 3 hooks where `hook_2` fails,\n/// then verifies that `hook_1` and `hook_2` ran but `hook_3` did not.\n///\n/// The test is skipped for runc, because the runc behaviour is incorrect and doesn't match the\n/// OCI spec behaviour which is implemented in youki\n/// <https://github.com/opencontainers/runc/issues/4347>\nfn get_test(test_name: &'static str) -> ConditionalTest {\n    ConditionalTest::new(\n        test_name,\n        Box::new(|| !is_runtime_runc()),\n        Box::new(move || {\n            let id = generate_uuid().to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n\n            let spec = get_spec(host_output_file.to_str().unwrap());\n            set_config(&bundle, &spec).unwrap();\n\n            let create_result =\n                create_container(&id, &bundle, &CreateOptions::default()).map(|mut cmd| cmd.wait());\n\n            let create_failed = match create_result {\n                Err(_) => true,\n                Ok(Ok(status)) if !status.success() => true,\n                _ => false,\n            };\n\n            if create_failed {\n                let _ = delete_container(&id, &bundle);\n                delete_output_file(&host_output_file);\n                return TestResult::Failed(anyhow!(\"runtime failed at create\"));\n            }\n\n            if let Ok(mut cmd) = start_container(&id, &bundle) {\n                let code = cmd.wait().unwrap().code().unwrap();\n                if code != 1 {\n                    let _ = delete_container(&id, &bundle);\n                    delete_output_file(&host_output_file);\n                    return TestResult::Failed(anyhow!(\n                        \"start should exit with code 1, got {code}\"\n                    ));\n                }\n            }\n\n            let result = if host_output_file.exists() {\n                let content =\n                    fs::read_to_string(&host_output_file).expect(\"failed to read output file\");\n\n                if !content.contains(\"hook_1 called\") {\n                    TestResult::Failed(anyhow!(\"first successful poststart hook did not run\"))\n                } else if !content.contains(\"hook_2 called\") {\n                    TestResult::Failed(anyhow!(\"the failing poststart hook did not run\"))\n                } else if content.contains(\"hook_3 called\") {\n                    TestResult::Failed(anyhow!(\n                        \"the hook after the failed hook was executed, but it shouldn't have\"\n                    ))\n                } else {\n                    TestResult::Passed\n                }\n            } else {\n                TestResult::Failed(anyhow!(\n                    \"no poststart hooks ran (output file doesn't exist)\"\n                ))\n            };\n\n            let _ = delete_container(&id, &bundle);\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_poststart_fail_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"poststart_fail\");\n    tg.add(vec![Box::new(get_test(\"poststart_fail\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/poststop/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    Hook, HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::{\n    CreateOptions, create_container, delete_container, generate_uuid, prepare_bundle, set_config,\n    start_container, wait_for_file_content,\n};\n\nconst POSTSTOP_OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(POSTSTOP_OUTPUT_FILE)\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn write_process_command() -> Vec<String> {\n    vec![\n        \"/bin/sh\".to_string(),\n        \"-c\".to_string(),\n        format!(\"echo 'process called' >> {POSTSTOP_OUTPUT_FILE}\"),\n    ]\n}\n\nfn write_poststop_hook(host_output_file: &str) -> Hook {\n    HookBuilder::default()\n        .path(\"/bin/sh\")\n        .args(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\"echo 'post-stop called' >> {host_output_file}\"),\n        ])\n        .build()\n        .expect(\"could not build hook\")\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(write_process_command())\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .poststop(vec![write_poststop_hook(host_output_file)])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that the poststop hook executes in the correct order.\n/// The poststop hooks should execute after the container process has ended.\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid();\n            let id_str = id.to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n            let host_output_file_str = host_output_file.to_str().unwrap();\n\n            let spec = get_spec(host_output_file_str);\n            set_config(&bundle, &spec).unwrap();\n\n            create_container(&id_str, &bundle, &CreateOptions::default())\n                .unwrap()\n                .wait()\n                .unwrap();\n\n            start_container(&id_str, &bundle).unwrap().wait().unwrap();\n\n            let wait_result = wait_for_file_content(\n                &host_output_file,\n                \"process called\",\n                std::time::Duration::from_secs(5),\n                std::time::Duration::from_millis(100),\n            );\n\n            let result = if let Err(e) = wait_result {\n                TestResult::Failed(anyhow!(\"Container process execution failed: {e}\"))\n            } else {\n                let contents =\n                    fs::read_to_string(&host_output_file).expect(\"failed to read output file\");\n                match contents.as_str() {\n                    \"process called\\n\" => TestResult::Passed,\n                    \"post-stop called\\n\"\n                    | \"post-stop called\\nprocess called\\n\"\n                    | \"process called\\npost-stop called\\n\" => TestResult::Failed(anyhow!(\n                        \"The post-stop hooks MUST be called after the container is deleted\"\n                    )),\n                    _ => TestResult::Failed(anyhow!(\"Unsupported output information: {contents}\")),\n                }\n            };\n\n            delete_container(&id_str, &bundle).unwrap().wait().unwrap();\n\n            if let TestResult::Failed(_) = result {\n                delete_output_file(&host_output_file);\n                return result;\n            }\n\n            let wait_result = wait_for_file_content(\n                &host_output_file,\n                \"post-stop called\",\n                std::time::Duration::from_secs(5),\n                std::time::Duration::from_millis(100),\n            );\n            let result = if let Err(e) = wait_result {\n                TestResult::Failed(anyhow!(\"post-stop hook execution failed: {e}\"))\n            } else {\n                let contents =\n                    fs::read_to_string(&host_output_file).expect(\"failed to read output file\");\n                match contents.as_str() {\n                    \"process called\\npost-stop called\\n\" => TestResult::Passed,\n                    \"post-stop called\" => TestResult::Failed(anyhow!(\n                        \"The runtime MUST run the user-specified program, as specified by `process`\"\n                    )),\n                    \"process called\\n\" => TestResult::Failed(anyhow!(\n                        \"The poststop hooks MUST be invoked by the runtime.\"\n                    )),\n                    \"post-stop called\\nprocess called\\n\" => TestResult::Failed(anyhow!(\n                        \"The post-stop should called after the user-specified program command is executed\"\n                    )),\n                    _ => TestResult::Failed(anyhow!(\"Unsupported output information: {contents}\")),\n                }\n            };\n\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_poststop_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"poststop\");\n    tg.add(vec![Box::new(get_test(\"poststop\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/prestart/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_utils::CreateOptions;\nuse crate::utils::{create_container, delete_container, generate_uuid, prepare_bundle, set_config};\n\nconst HOOK_OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(HOOK_OUTPUT_FILE)\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn write_prestart_hook(host_output_file: &str) -> oci_spec::runtime::Hook {\n    HookBuilder::default()\n        .path(\"/bin/sh\")\n        .args(vec![\n            \"sh\".to_string(),\n            \"-c\".to_string(),\n            format!(\"echo 'pre-start called' >> {host_output_file}\"),\n        ])\n        .build()\n        .expect(\"could not build hook\")\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"/bin/sh\".to_string(),\n                    \"-c\".to_string(),\n                    \"true\".to_string(),\n                ])\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .prestart(vec![write_prestart_hook(host_output_file)])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that the prestart hook executes during the create operation.\n/// According to the OCI spec, prestart hooks MUST be invoked by the runtime\n/// after the container has been created but before the user-specified program\n/// is executed (which happens during start).\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid().to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n\n            let spec = get_spec(host_output_file.to_str().unwrap());\n            set_config(&bundle, &spec).unwrap();\n\n            create_container(&id, &bundle, &CreateOptions::default())\n                .unwrap()\n                .wait()\n                .unwrap();\n\n            let result = if !host_output_file.exists() {\n                TestResult::Failed(anyhow!(\n                    \"prestart hook did not create output file during create operation\"\n                ))\n            } else {\n                let content = fs::read_to_string(&host_output_file)\n                    .expect(\"failed to read output file after create\");\n\n                if content.contains(\"pre-start called\") {\n                    TestResult::Passed\n                } else {\n                    TestResult::Failed(anyhow!(\n                        \"the runtime MUST run the pre-start hooks during create. Got: '{content}'\"\n                    ))\n                }\n            };\n\n            let _ = delete_container(&id, &bundle);\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_prestart_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"prestart\");\n    tg.add(vec![Box::new(get_test(\"prestart\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/prestart_fail/mod.rs",
    "content": "use std::fs;\nuse std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    HookBuilder, HooksBuilder, ProcessBuilder, RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_utils::CreateOptions;\nuse crate::utils::{create_container, delete_container, generate_uuid, prepare_bundle, set_config};\n\nconst OUTPUT_FILE: &str = \"output\";\n\nfn get_output_file_path(bundle: &tempfile::TempDir) -> PathBuf {\n    bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(OUTPUT_FILE)\n}\n\nfn delete_output_file(path: &PathBuf) {\n    if path.exists() {\n        fs::remove_file(path).expect(\"failed to remove output file\");\n    }\n}\n\nfn get_spec(host_output_file: &str) -> Spec {\n    SpecBuilder::default()\n        .root(\n            RootBuilder::default()\n                .path(\"rootfs\")\n                .readonly(false)\n                .build()\n                .expect(\"failed to create root\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"/bin/sh\".to_string(),\n                    \"-c\".to_string(),\n                    format!(\"echo 'process called' >> {OUTPUT_FILE}\"),\n                ])\n                .cwd(\"/\")\n                .build()\n                .unwrap(),\n        )\n        .hooks(\n            HooksBuilder::default()\n                .prestart(vec![\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_1 called' >> {host_output_file}\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_2 called' >> {host_output_file}; exit 1\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                    HookBuilder::default()\n                        .path(\"/bin/sh\")\n                        .args(vec![\n                            \"sh\".to_string(),\n                            \"-c\".to_string(),\n                            format!(\"echo 'hook_3 called' >> {host_output_file}\"),\n                        ])\n                        .build()\n                        .expect(\"could not build hook\"),\n                ])\n                .build()\n                .expect(\"could not build hooks\"),\n        )\n        .build()\n        .unwrap()\n}\n\n/// Tests that when a prestart hook fails, the runtime generates an error, stops the container, and\n/// subsequent hooks are not executed.\n///\n/// According to the OCI spec: \"If any prestart hook fails, the runtime MUST generate an error,\n/// stop the container, and continue the lifecycle at step 12.\" This test creates 3 hooks where\n/// hook_2 fails, then verifies that hook_1 and hook_2 ran in order, hook_3 did not run, and the\n/// container process was never started.\nfn get_test(test_name: &'static str) -> Test {\n    Test::new(\n        test_name,\n        Box::new(move || {\n            let id = generate_uuid().to_string();\n            let bundle = prepare_bundle().unwrap();\n\n            let host_output_file = get_output_file_path(&bundle);\n\n            let spec = get_spec(host_output_file.to_str().unwrap());\n            set_config(&bundle, &spec).unwrap();\n\n            let create_result =\n                create_container(&id, &bundle, &CreateOptions::default()).map(|mut cmd| cmd.wait());\n\n            let create_failed = match create_result {\n                Err(_) => true,\n                Ok(Ok(status)) if !status.success() => true,\n                _ => false,\n            };\n\n            if !create_failed {\n                let _ = delete_container(&id, &bundle);\n                delete_output_file(&host_output_file);\n                return TestResult::Failed(anyhow!(\n                    \"container creation should fail when a prestart hook fails\"\n                ));\n            }\n\n            let result = if !host_output_file.exists() {\n                TestResult::Failed(anyhow!(\"No prestart hooks ran (output file doesn't exist)\"))\n            } else {\n                let content =\n                    fs::read_to_string(&host_output_file).expect(\"failed to read output file\");\n                let lines: Vec<&str> = content.lines().collect();\n\n                if lines.contains(&\"process called\") {\n                    TestResult::Failed(anyhow!(\n                        \"container process must not run when a prestart hook fails\"\n                    ))\n                } else {\n                    let expected = vec![\"hook_1 called\", \"hook_2 called\"];\n                    if lines != expected {\n                        TestResult::Failed(anyhow!(\n                            \"expected hooks to run in order {:?}, but got {:?}\",\n                            expected,\n                            lines\n                        ))\n                    } else {\n                        TestResult::Passed\n                    }\n                }\n            };\n\n            let _ = delete_container(&id, &bundle);\n            delete_output_file(&host_output_file);\n            result\n        }),\n    )\n}\n\npub fn get_prestart_fail_tests() -> TestGroup {\n    let mut tg = TestGroup::new(\"prestart_fail\");\n    tg.add(vec![Box::new(get_test(\"prestart_fail\"))]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process/mod.rs",
    "content": "mod process_test;\npub use process_test::get_process_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process/process_test.rs",
    "content": "use std::fs;\n\nuse anyhow::{Context, Ok, Result, bail};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec() -> Result<Spec> {\n    let mut process = ProcessBuilder::default()\n        .args(vec![\"runtimetest\".to_string(), \"process\".to_string()])\n        .cwd(\"/test\")\n        .build()\n        .expect(\"error in creating process config\");\n    let mut env = process.env().clone().unwrap();\n    env.push(\"testa=valuea\".to_string());\n    env.push(\"testb=123\".to_string());\n    process.set_env(Some(env));\n\n    let spec = SpecBuilder::default()\n        .process(process)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn process_test() -> TestResult {\n    let spec = test_result!(create_spec());\n\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        match fs::create_dir(bundle.join(\"test\")) {\n            Result::Ok(_) => { /*This is expected*/ }\n            Err(e) => {\n                bail!(e)\n            }\n        }\n\n        Ok(())\n    })\n}\n\npub fn get_process_test() -> TestGroup {\n    let mut process_test_group = TestGroup::new(\"process\");\n\n    let test = Test::new(\"process_test\", Box::new(process_test));\n    process_test_group.add(vec![Box::new(test)]);\n\n    process_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_capabilities_fail/mod.rs",
    "content": "mod process_capabilities_fail_test;\npub use process_capabilities_fail_test::get_process_capabilities_fail_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_capabilities_fail/process_capabilities_fail_test.rs",
    "content": "use std::fs;\nuse std::fs::OpenOptions;\nuse std::io::Write;\n\nuse anyhow::{Context, Ok, Result, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse serde_json::Value;\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec() -> Result<Spec> {\n    let process = ProcessBuilder::default()\n        .args(vec![\"sleep\".to_string(), \"1m\".to_string()])\n        .build()\n        .expect(\"error in creating process config\");\n\n    let spec = SpecBuilder::default()\n        .process(process)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn process_capabilities_fail_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    let result = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let spec_path = bundle.join(\"../config.json\");\n        let spec_str = fs::read_to_string(spec_path.clone()).unwrap();\n\n        let mut spec_json: Value = serde_json::from_str(&spec_str)?;\n\n        // Before container creation, replace the spec's capability with an invalid one.\n        let capability_paths = vec![\n            \"/process/capabilities/bounding\",\n            \"/process/capabilities/effective\",\n        ];\n        for path in &capability_paths {\n            if let Some(array) = spec_json.pointer_mut(path)\n                && let Some(arr) = array.as_array_mut()\n            {\n                for cap in arr.iter_mut() {\n                    *cap = Value::String(\"TEST_CAP\".to_string());\n                }\n            }\n        }\n\n        let updated_spec_str = serde_json::to_string_pretty(&spec_json)?;\n\n        let mut file = OpenOptions::new()\n            .write(true)\n            .truncate(true)\n            .open(spec_path)?;\n        file.write_all(updated_spec_str.as_bytes())?;\n\n        Ok(())\n    });\n\n    // Check the test result: Fail if the container was created successfully (because it should fail)\n    match result {\n        TestResult::Failed(e) => {\n            let err_str = format!(\"{:?}\", e);\n\n            // youki: error from rust deserialization when loading config.json with invalid capability\n            let is_invalid_variant_error = err_str.contains(\"no variant for TEST_CAP\");\n\n            // runc: warning when TEST_CAP is unknown or unsupported and ignored\n            let is_runc_cap_warning =\n                err_str.contains(\"ignoring unknown or unavailable capabilities: [TEST_CAP]\");\n\n            if is_invalid_variant_error || is_runc_cap_warning {\n                TestResult::Passed\n            } else {\n                TestResult::Failed(anyhow!(\"unexpected error: {e:?}\"))\n            }\n        }\n        TestResult::Skipped => TestResult::Failed(anyhow!(\"test was skipped unexpectedly.\")),\n        TestResult::Passed => {\n            TestResult::Failed(anyhow!(\"container creation succeeded unexpectedly.\"))\n        }\n    }\n}\n\npub fn get_process_capabilities_fail_test() -> TestGroup {\n    let mut process_capabilities_fail_test_group = TestGroup::new(\"process_capabilities_fail\");\n    let test = Test::new(\n        \"process_capabilities_fail_test\",\n        Box::new(process_capabilities_fail_test),\n    );\n    process_capabilities_fail_test_group.add(vec![Box::new(test)]);\n\n    process_capabilities_fail_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_oom_score_adj/mod.rs",
    "content": "mod process_oom_score_adj_test;\npub use process_oom_score_adj_test::get_process_oom_score_adj_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_oom_score_adj/process_oom_score_adj_test.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse rand::RngExt;\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn generate_random_number() -> i32 {\n    let mut rng = rand::rng();\n    rng.random_range(300..=700)\n}\n\nfn create_spec() -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"runtimetest\".to_string(),\n                    \"process_oom_score_adj\".to_string(),\n                ])\n                .oom_score_adj(generate_random_number())\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn process_oom_score_adj_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_process_oom_score_adj_test() -> TestGroup {\n    let mut process_oom_score_adj_test_group = TestGroup::new(\"process_oom_score_adj\");\n\n    let test = Test::new(\n        \"process_oom_score_adj\",\n        Box::new(process_oom_score_adj_test),\n    );\n    process_oom_score_adj_test_group.add(vec![Box::new(test)]);\n\n    process_oom_score_adj_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_rlimits/mod.rs",
    "content": "mod process_rlimits_test;\npub use process_rlimits_test::get_process_rlimits_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_rlimits/process_rlimits_test.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{\n    PosixRlimit, PosixRlimitBuilder, PosixRlimitType, ProcessBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nconst GIGABYTES: u64 = 1024 * 1024 * 1024;\n\nfn create_rlimit(\n    rlimit_type: PosixRlimitType,\n    hard_val: u64,\n    soft_val: u64,\n) -> Result<PosixRlimit> {\n    let rlimit = PosixRlimitBuilder::default()\n        .typ(rlimit_type)\n        .hard(hard_val)\n        .soft(soft_val)\n        .build()?;\n    Ok(rlimit)\n}\n\n#[allow(clippy::identity_op)]\nfn create_spec() -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"runtimetest\".to_string(),\n                    \"process_rlimits\".to_string(),\n                ])\n                .rlimits(vec![\n                    create_rlimit(PosixRlimitType::RlimitAs, 2 * GIGABYTES, 1 * GIGABYTES).unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitCore, 4 * GIGABYTES, 3 * GIGABYTES)\n                        .unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitData, 6 * GIGABYTES, 5 * GIGABYTES)\n                        .unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitFsize, 8 * GIGABYTES, 7 * GIGABYTES)\n                        .unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitStack, 10 * GIGABYTES, 9 * GIGABYTES)\n                        .unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitCpu, 120, 60).unwrap(),\n                    create_rlimit(PosixRlimitType::RlimitNofile, 4000, 3000).unwrap(),\n                ])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn process_rlimits_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_process_rlimits_test() -> TestGroup {\n    let mut process_rlimits_test_group = TestGroup::new(\"process_rlimits\");\n\n    let test = Test::new(\"process_rlimits_test\", Box::new(process_rlimits_test));\n    process_rlimits_test_group.add(vec![Box::new(test)]);\n\n    process_rlimits_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_rlimits_fail/mod.rs",
    "content": "mod process_rlimits_fail_test;\npub use process_rlimits_fail_test::get_process_rlimits_fail_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_rlimits_fail/process_rlimits_fail_test.rs",
    "content": "use anyhow::{Context, Result, anyhow};\nuse oci_spec::runtime::{PosixRlimitBuilder, PosixRlimitType, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\n/// Creates a spec with an invalid rlimit value.\n///\n/// According to the OCI Runtime Spec, \"The runtime MUST generate an error for any values\n/// which cannot be mapped to a relevant kernel interface.\"\n///\n/// While the original Go test in runtime-tools validates this by using an invalid rlimit type\n/// (RLIMIT_TEST), this implementation takes a different approach due to Rust's type safety:\n/// - Uses a valid rlimit type (RLIMIT_NOFILE)\n/// - Sets its value to u64::MAX, which exceeds the system's maximum allowed value\n///   defined in /proc/sys/fs/nr_open\n/// - This causes the kernel to reject the value with EPERM\n///\n/// See `man 2 setrlimit` for more details:\n/// > EPERM The caller tried to increase the hard RLIMIT_NOFILE limit above\n/// > the maximum defined by /proc/sys/fs/nr_open\n/// > See also: https://docs.kernel.org/admin-guide/sysctl/fs.html#nr-open\nfn create_spec() -> Result<Spec> {\n    let invalid_rlimit = PosixRlimitBuilder::default()\n        .typ(PosixRlimitType::RlimitNofile)\n        .hard(u64::MAX) // Exceeds /proc/sys/fs/nr_open limit\n        .soft(u64::MAX) // Exceeds /proc/sys/fs/nr_open limit\n        .build()?;\n\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"runtimetest\".to_string(),\n                    \"process_rlimits\".to_string(),\n                ])\n                .rlimits(vec![invalid_rlimit])\n                .build()\n                .context(\"failed to create process config\")?,\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn process_rlimits_fail_test() -> TestResult {\n    let spec = test_result!(create_spec());\n    match test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) {\n        TestResult::Passed => TestResult::Failed(anyhow!(\n            \"expected test with invalid rlimit value to fail, but it passed instead\"\n        )),\n        _ => TestResult::Passed,\n    }\n}\n\npub fn get_process_rlimits_fail_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"process_rlimits_fail\");\n    let test = Test::new(\n        \"process_rlimits_fail_test\",\n        Box::new(process_rlimits_fail_test),\n    );\n    test_group.add(vec![Box::new(test)]);\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_user/mod.rs",
    "content": "mod process_user_test;\npub use process_user_test::get_process_user_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/process_user/process_user_test.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder, UserBuilder};\nuse rand::RngExt;\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\n// Generates a Vec<u32> with a random number of elements (between 5 and 15),\n// where each element is a random u32 value between 0 and 65535.\nfn generate_unique_random_vec() -> Vec<u32> {\n    let mut rng = rand::rng();\n    let vec_size = rng.random_range(5..=10);\n    let mut ret = Vec::new();\n    while ret.len() < vec_size {\n        let rand = rng.random_range(100..=200);\n        if !ret.contains(&rand) {\n            ret.push(rand);\n        }\n    }\n    ret\n}\n\nfn create_spec(gids: Vec<u32>) -> Result<Spec> {\n    let umask = 0o002;\n    let user = UserBuilder::default()\n        .uid(10u32)\n        .gid(10u32)\n        .additional_gids(gids)\n        .umask(umask as u32)\n        .build()?;\n\n    let spec = SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"process_user\".to_string()])\n                .user(user)\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n    Ok(spec)\n}\n\nfn process_user_test_unique_gids() -> TestResult {\n    let gids = generate_unique_random_vec();\n    let spec = test_result!(create_spec(gids));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn process_user_test_duplicate_gids() -> TestResult {\n    let mut gids = generate_unique_random_vec();\n    let duplicate = gids[0];\n    gids.push(duplicate);\n    let spec = test_result!(create_spec(gids));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_process_user_test() -> TestGroup {\n    let mut process_user_test_group = TestGroup::new(\"process_user\");\n\n    let test1 = Test::new(\n        \"process_user_unique_gids_test\",\n        Box::new(process_user_test_unique_gids),\n    );\n    let test2 = Test::new(\n        \"process_user_duplicate_gids_test\",\n        Box::new(process_user_test_duplicate_gids),\n    );\n    process_user_test_group.add(vec![Box::new(test1), Box::new(test2)]);\n\n    process_user_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/prohibit_symlink/mod.rs",
    "content": "mod prohibit_symlink_test;\n\npub use prohibit_symlink_test::get_prohibit_symlink_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/prohibit_symlink/prohibit_symlink_test.rs",
    "content": "use std::fs;\nuse std::os::unix::fs::symlink;\n\nuse anyhow::{Context, Ok, Result, anyhow};\nuse oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec() -> Result<Spec> {\n    let process = ProcessBuilder::default()\n        .args(vec![\"sleep\".to_string(), \"3000\".to_string()])\n        .build()\n        .expect(\"error in creating process config\");\n\n    let spec = SpecBuilder::default()\n        .process(process)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn prohibit_symlink_test(path: String) -> TestResult {\n    let spec = test_result!(create_spec());\n    let result = test_inside_container(&spec, &CreateOptions::default(), &|bundle| {\n        let symlink_path = bundle.join(path.clone());\n        fs::create_dir_all(&symlink_path)?;\n\n        let link = bundle.join(path.clone());\n\n        // delete existing directory or file\n        if link.exists() {\n            let md = fs::symlink_metadata(&link)?;\n            if md.file_type().is_dir() {\n                fs::remove_dir_all(&link)?;\n            } else {\n                fs::remove_file(&link)?;\n            }\n        }\n\n        // create symbolic link\n        symlink(&symlink_path, &link)?;\n        Ok(())\n    });\n\n    match result {\n        TestResult::Failed(e) => {\n            let err_str = format!(\"{:?}\", e);\n            if err_str.contains(\"must be mounted on ordinary directory\") {\n                TestResult::Passed\n            } else {\n                TestResult::Failed(anyhow!(\n                    \"unexpected error (expected substring not found): {err_str}\"\n                ))\n            }\n        }\n        TestResult::Skipped => TestResult::Failed(anyhow!(\"test was skipped unexpectedly.\")),\n        TestResult::Passed => {\n            TestResult::Failed(anyhow!(\"container creation succeeded unexpectedly.\"))\n        }\n    }\n}\n\nfn prohibit_symlink_proc_test() -> TestResult {\n    prohibit_symlink_test(\"proc\".to_string())\n}\n\nfn prohibit_symlink_sys_test() -> TestResult {\n    prohibit_symlink_test(\"sys\".to_string())\n}\n\npub fn get_prohibit_symlink_test() -> TestGroup {\n    let mut prohibit_symlink_test_group = TestGroup::new(\"prohibit_symlink\");\n\n    let prohibit_symlink_proc_test = Test::new(\n        \"prohibit_symlink_proc_test\",\n        Box::new(prohibit_symlink_proc_test),\n    );\n    let prohibit_symlink_sys_test = Test::new(\n        \"prohibit_symlink_sys_test\",\n        Box::new(prohibit_symlink_sys_test),\n    );\n    prohibit_symlink_test_group.add(vec![\n        Box::new(prohibit_symlink_proc_test),\n        Box::new(prohibit_symlink_sys_test),\n    ]);\n\n    prohibit_symlink_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/readonly_paths/mod.rs",
    "content": "mod readonly_paths_tests;\npub use readonly_paths_tests::get_ro_paths_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/readonly_paths/readonly_paths_tests.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::{anyhow, bail};\nuse nix::sys::stat::SFlag;\nuse oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn get_spec(readonly_paths: Vec<String>) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .readonly_paths(readonly_paths)\n                .build()\n                .expect(\"could not build\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\n                    \"runtimetest\".to_string(),\n                    \"readonly_paths\".to_string(),\n                ])\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap()\n}\n\nfn check_readonly_paths() -> TestResult {\n    // here we abbreviate 'readonly' as ro for variable names,\n    // purely for ease of writing\n\n    let ro_dir = \"readonly_dir\";\n    let ro_subdir = \"readonly_subdir\";\n    let ro_file = \"readonly_file\";\n\n    // in the runtime-tools tests, they start these with a '/',\n    // but in that case, when joined with any path later,\n    // the '/' takes preference, and path is not actually joined\n    // eg : (test).join(t1) = test/t1\n    //      (test).join(.t1) = /t1\n    // which is not what we want, so we leave them without '/'\n    let ro_dir_top = PathBuf::from(ro_dir);\n    let ro_file_top = PathBuf::from(ro_file);\n\n    let ro_dir_sub = ro_dir_top.join(ro_subdir);\n    let ro_file_sub = ro_dir_top.join(ro_file);\n    let ro_file_sub_sub = ro_dir_sub.join(ro_file);\n\n    let root = PathBuf::from(\"/\");\n\n    let ro_paths = vec![\n        root.join(&ro_dir_top).to_string_lossy().to_string(),\n        root.join(ro_file_top).to_string_lossy().to_string(),\n        root.join(&ro_dir_sub).to_string_lossy().to_string(),\n        root.join(&ro_file_sub).to_string_lossy().to_string(),\n        root.join(&ro_file_sub_sub).to_string_lossy().to_string(),\n    ];\n\n    let spec = get_spec(ro_paths);\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::{fs, io};\n        let test_dir = bundle_path.join(&ro_dir_sub);\n\n        match fs::create_dir_all(&test_dir) {\n            io::Result::Ok(_) => { /*This is expected*/ }\n            io::Result::Err(e) => {\n                bail!(e)\n            }\n        }\n\n        match fs::File::create(test_dir.join(\"tmp\")) {\n            io::Result::Ok(_) => { /*This is expected*/ }\n            io::Result::Err(e) => {\n                bail!(e)\n            }\n        }\n\n        let test_sub_sub_file = bundle_path.join(&ro_file_sub_sub);\n        match fs::File::create(test_sub_sub_file) {\n            io::Result::Ok(_) => { /*This is expected*/ }\n            io::Result::Err(e) => {\n                bail!(e)\n            }\n        }\n\n        let test_sub_file = bundle_path.join(&ro_file_sub);\n        match fs::File::create(test_sub_file) {\n            io::Result::Ok(_) => { /*This is expected*/ }\n            io::Result::Err(e) => {\n                bail!(e)\n            }\n        }\n\n        let test_file = bundle_path.join(ro_file);\n        match fs::File::create(test_file) {\n            io::Result::Ok(_) => { /*This is expected*/ }\n            io::Result::Err(e) => {\n                bail!(e)\n            }\n        }\n\n        Ok(())\n    })\n}\n\nfn check_readonly_rel_path() -> TestResult {\n    let ro_rel_path = \"readonly_relpath\";\n    let ro_paths = vec![ro_rel_path.to_string()];\n    let spec = get_spec(ro_paths);\n\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::{fs, io};\n        let test_file = bundle_path.join(ro_rel_path);\n\n        match fs::metadata(&test_file) {\n            io::Result::Ok(md) => {\n                bail!(\n                    \"reading path {:?} should have given error, found {:?} instead\",\n                    test_file,\n                    md\n                )\n            }\n            io::Result::Err(e) => {\n                let err = e.kind();\n                if let io::ErrorKind::NotFound = err {\n                    Ok(())\n                } else {\n                    bail!(\"expected not found error, got {:?}\", err);\n                }\n            }\n        }\n    })\n}\n\nfn check_readonly_symlinks() -> TestResult {\n    let root = PathBuf::from(\"/\");\n    let ro_symlink = \"readonly_symlink\";\n    let ro_paths = vec![root.join(ro_symlink).to_string_lossy().to_string()];\n\n    let spec = get_spec(ro_paths);\n\n    let res = test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::{fs, io};\n        let test_file = bundle_path.join(ro_symlink);\n\n        match std::os::unix::fs::symlink(\"../readonly_symlink\", &test_file) {\n            io::Result::Ok(_) => { /* This is expected */ }\n            io::Result::Err(e) => {\n                bail!(\"error in creating symlink, to {:?} {:?}\", test_file, e);\n            }\n        }\n        let r_path = match fs::read_link(&test_file) {\n            io::Result::Ok(p) => p,\n            io::Result::Err(e) => {\n                bail!(\"error in reading symlink at {:?} : {:?}\", test_file, e);\n            }\n        };\n\n        match fs::metadata(r_path) {\n            io::Result::Ok(md) => {\n                bail!(\n                    \"reading symlink for {:?} should have given error, found {:?} instead\",\n                    test_file,\n                    md\n                )\n            }\n            io::Result::Err(e) => {\n                let err = e.kind();\n                if let io::ErrorKind::NotFound = err {\n                    Ok(())\n                } else {\n                    bail!(\"expected not found error, got {:?}\", err);\n                }\n            }\n        }\n    });\n    if let TestResult::Passed = res {\n        TestResult::Failed(anyhow!(\n            \"expected error in container creation with invalid symlink, found no error\"\n        ))\n    } else {\n        TestResult::Passed\n    }\n}\n\nfn test_node(mode: u32) -> TestResult {\n    let root = PathBuf::from(\"/\");\n    let ro_device = \"readonly_device\";\n    let ro_paths = vec![root.join(ro_device).to_string_lossy().to_string()];\n\n    let spec = get_spec(ro_paths);\n\n    test_inside_container(&spec, &CreateOptions::default(), &|bundle_path| {\n        use std::os::unix::fs::OpenOptionsExt;\n        use std::{fs, io};\n        let test_file = bundle_path.join(ro_device);\n\n        let mut opts = fs::OpenOptions::new();\n        opts.mode(mode);\n        opts.create(true);\n        if let io::Result::Err(e) = fs::OpenOptions::new()\n            .mode(mode)\n            .create(true)\n            .write(true)\n            .open(&test_file)\n        {\n            bail!(\n                \"could not create device node at {:?} with mode {}, got error {:?}\",\n                test_file,\n                mode ^ 0o666,\n                e\n            );\n        }\n\n        match fs::metadata(&test_file) {\n            io::Result::Ok(_) => Ok(()),\n            io::Result::Err(e) => {\n                bail!(\"error in creating device node, {:?}\", e)\n            }\n        }\n    })\n}\n\nfn check_readonly_device_nodes() -> TestResult {\n    let modes = [\n        SFlag::S_IFBLK.bits() | 0o666,\n        SFlag::S_IFCHR.bits() | 0o666,\n        SFlag::S_IFIFO.bits() | 0o666,\n    ];\n    for mode in modes {\n        let res = test_node(mode);\n        if let TestResult::Failed(_) = res {\n            return res;\n        }\n        std::thread::sleep(std::time::Duration::from_millis(1000));\n    }\n    TestResult::Passed\n}\n\npub fn get_ro_paths_test() -> TestGroup {\n    let ro_paths = Test::new(\"readonly_paths\", Box::new(check_readonly_paths));\n    let ro_rel_paths = Test::new(\"readonly_rel_paths\", Box::new(check_readonly_rel_path));\n    let ro_symlinks = Test::new(\"readonly_symlinks\", Box::new(check_readonly_symlinks));\n    let ro_device_nodes = Test::new(\n        \"readonly_device_nodes\",\n        Box::new(check_readonly_device_nodes),\n    );\n    let mut tg = TestGroup::new(\"readonly_paths\");\n    tg.add(vec![\n        Box::new(ro_paths),\n        Box::new(ro_rel_paths),\n        Box::new(ro_symlinks),\n        Box::new(ro_device_nodes),\n    ]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/root_readonly_true/mod.rs",
    "content": "mod root_readonly_tests;\npub use root_readonly_tests::get_root_readonly_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/root_readonly_true/root_readonly_tests.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{ProcessBuilder, RootBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(readonly: bool) -> Result<Spec> {\n    let spec = SpecBuilder::default()\n        .root(RootBuilder::default().readonly(readonly).build().unwrap())\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"root_readonly\".to_string()])\n                .build()\n                .expect(\"error in creating config\"),\n        )\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn root_readonly_true_test() -> TestResult {\n    let spec_true = test_result!(create_spec(true));\n    test_inside_container(&spec_true, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn root_readonly_false_test() -> TestResult {\n    let spec_false = test_result!(create_spec(false));\n    test_inside_container(&spec_false, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_root_readonly_test() -> TestGroup {\n    let mut root_readonly_test_group = TestGroup::new(\"root_readonly\");\n\n    let test_true = Test::new(\"root_readonly_true_test\", Box::new(root_readonly_true_test));\n    let test_false = Test::new(\n        \"root_readonly_false_test\",\n        Box::new(root_readonly_false_test),\n    );\n    root_readonly_test_group.add(vec![Box::new(test_true), Box::new(test_false)]);\n\n    root_readonly_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/rootfs_propagation/mod.rs",
    "content": "mod rootfs_propagation_test;\npub use rootfs_propagation_test::get_rootfs_propagation_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/rootfs_propagation/rootfs_propagation_test.rs",
    "content": "use anyhow::{Context, Ok, Result};\nuse oci_spec::runtime::{\n    Capability, LinuxBuilder, LinuxCapabilitiesBuilder, LinuxSeccompBuilder, ProcessBuilder,\n    RootBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(propagation: String) -> Result<Spec> {\n    let root = RootBuilder::default()\n        .readonly(false)\n        .build()\n        .context(\"failed to build root\")?;\n\n    let capabilities = LinuxCapabilitiesBuilder::default()\n        .bounding([Capability::SysAdmin])\n        .effective([Capability::SysAdmin])\n        .inheritable([Capability::SysAdmin])\n        .permitted([Capability::SysAdmin])\n        .ambient([Capability::SysAdmin])\n        .build()\n        .context(\"failed to build linux capabilities\")?;\n\n    let process = ProcessBuilder::default()\n        .args(vec![\n            \"runtimetest\".to_string(),\n            \"rootfs_propagation\".to_string(),\n        ])\n        .capabilities(capabilities)\n        .build()\n        .context(\"failed to build process\")?;\n\n    let seccomp = LinuxSeccompBuilder::default()\n        .build()\n        .context(\"failed to build seccomp\")?;\n\n    let linux = LinuxBuilder::default()\n        .rootfs_propagation(propagation)\n        .seccomp(seccomp)\n        .build()\n        .context(\"failed to build linux spec\")?;\n\n    let spec = SpecBuilder::default()\n        .root(root)\n        .linux(linux)\n        .process(process)\n        .build()\n        .context(\"failed to build spec\")?;\n\n    Ok(spec)\n}\n\nfn rootfs_propagation_shared_test() -> TestResult {\n    let spec = test_result!(create_spec(\"shared\".to_string()));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn rootfs_propagation_slave_test() -> TestResult {\n    let spec = test_result!(create_spec(\"slave\".to_string()));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn rootfs_propagation_private_test() -> TestResult {\n    let spec = test_result!(create_spec(\"private\".to_string()));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn rootfs_propagation_unbindable_test() -> TestResult {\n    let spec = test_result!(create_spec(\"unbindable\".to_string()));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_rootfs_propagation_test() -> TestGroup {\n    let mut rootfs_propagation_test_group = TestGroup::new(\"rootfs_propagation\");\n\n    let rootfs_propagation_shared_test = Test::new(\n        \"rootfs_propagation_shared_test\",\n        Box::new(rootfs_propagation_shared_test),\n    );\n    let rootfs_propagation_slave_test = Test::new(\n        \"rootfs_propagation_slave_test\",\n        Box::new(rootfs_propagation_slave_test),\n    );\n    let rootfs_propagation_private_test = Test::new(\n        \"rootfs_propagation_private_test\",\n        Box::new(rootfs_propagation_private_test),\n    );\n    let rootfs_propagation_unbindable_test = Test::new(\n        \"rootfs_propagation_unbindable_test\",\n        Box::new(rootfs_propagation_unbindable_test),\n    );\n    rootfs_propagation_test_group.add(vec![\n        Box::new(rootfs_propagation_shared_test),\n        Box::new(rootfs_propagation_slave_test),\n        Box::new(rootfs_propagation_private_test),\n        Box::new(rootfs_propagation_unbindable_test),\n    ]);\n\n    rootfs_propagation_test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/scheduler/mod.rs",
    "content": "mod scheduler_policy;\n\npub use scheduler_policy::get_scheduler_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/scheduler/scheduler_policy.rs",
    "content": "use anyhow::{Context, Result};\nuse oci_spec::runtime::{\n    LinuxSchedulerPolicy, ProcessBuilder, SchedulerBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(policy: LinuxSchedulerPolicy, execute_test: &str) -> Result<Spec> {\n    let sc = SchedulerBuilder::default()\n        .policy(policy)\n        .nice(1i32)\n        .build()\n        .unwrap();\n    SpecBuilder::default()\n        .process(\n            ProcessBuilder::default()\n                .args(\n                    [\"runtimetest\", execute_test]\n                        .iter()\n                        .map(|s| s.to_string())\n                        .collect::<Vec<String>>(),\n                )\n                .scheduler(sc)\n                .build()?,\n        )\n        .build()\n        .context(\"failed to create spec\")\n}\n\nfn scheduler_policy_other_test() -> TestResult {\n    let spec = test_result!(create_spec(\n        LinuxSchedulerPolicy::SchedOther,\n        \"scheduler_policy_other\"\n    ));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\nfn scheduler_policy_batch_test() -> TestResult {\n    let spec = test_result!(create_spec(\n        LinuxSchedulerPolicy::SchedBatch,\n        \"scheduler_policy_batch\"\n    ));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_scheduler_test() -> TestGroup {\n    let mut scheduler_policy_group = TestGroup::new(\"set_scheduler_policy\");\n    let policy_fifo_test = Test::new(\"policy_other\", Box::new(scheduler_policy_other_test));\n    let policy_rr_test = Test::new(\"policy_batch\", Box::new(scheduler_policy_batch_test));\n\n    scheduler_policy_group.add(vec![Box::new(policy_fifo_test), Box::new(policy_rr_test)]);\n    scheduler_policy_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/seccomp/mod.rs",
    "content": "use oci_spec::runtime::{\n    LinuxBuilder, LinuxSeccomp, LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder,\n    ProcessBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(seccomp: LinuxSeccomp) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .seccomp(seccomp)\n                .build()\n                .expect(\"error in building linux config\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"seccomp\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn seccomp_test() -> TestResult {\n    let spec = create_spec(\n        LinuxSeccompBuilder::default()\n            .default_action(LinuxSeccompAction::ScmpActAllow)\n            .syscalls(vec![\n                LinuxSyscallBuilder::default()\n                    .names(vec![String::from(\"getcwd\")])\n                    .action(LinuxSeccompAction::ScmpActErrno)\n                    .build()\n                    .unwrap(),\n            ])\n            .build()\n            .unwrap(),\n    );\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_seccomp_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"seccomp\");\n    let seccomp_test = Test::new(\"seccomp_test\", Box::new(seccomp_test));\n    test_group.add(vec![Box::new(seccomp_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/seccomp_notify/mod.rs",
    "content": "use std::path::PathBuf;\nuse std::sync::mpsc::{self, Receiver, Sender};\nuse std::thread;\n\nuse anyhow::{Result, anyhow, bail};\nuse oci_spec::runtime::{\n    Arch, LinuxBuilder, LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder, SpecBuilder,\n};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_outside_container;\n\nmod seccomp_agent;\n\nconst SECCOMP_LISTENER_PATH: &str = \"/tmp/youki_seccomp_agent.unix\";\nconst SECCOMP_METADATA: &str = \"Hello World! This is an opaque seccomp metadata string\";\n\nfn get_seccomp_listener() -> PathBuf {\n    let seccomp_listener_path = PathBuf::from(SECCOMP_LISTENER_PATH);\n    // We will have to clean up leftover unix domain socket from previous runs.\n    if seccomp_listener_path.exists() {\n        std::fs::remove_file(&seccomp_listener_path)\n            .expect(\"failed to clean up existing seccomp listener\");\n    }\n\n    seccomp_listener_path\n}\n\nfn test_seccomp_notify() -> Result<()> {\n    let seccomp_listener_path = get_seccomp_listener();\n    let seccomp_meta = String::from(SECCOMP_METADATA);\n    // Create a spec to include seccomp notify. We will need to have at least\n    // one syscall set to seccomp notify. We also need to set seccomp listener\n    // path and metadata.\n    let spec = SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .seccomp(\n                    LinuxSeccompBuilder::default()\n                        .default_action(LinuxSeccompAction::ScmpActAllow)\n                        .architectures(vec![Arch::ScmpArchX86_64])\n                        .listener_path(&seccomp_listener_path)\n                        .listener_metadata(seccomp_meta)\n                        .syscalls(vec![\n                            LinuxSyscallBuilder::default()\n                                .names(vec![String::from(\"getcwd\")])\n                                .action(LinuxSeccompAction::ScmpActNotify)\n                                .build()\n                                .unwrap(),\n                        ])\n                        .build()\n                        .unwrap(),\n                )\n                .build()\n                .unwrap(),\n        )\n        .build()\n        .unwrap();\n\n    // two threads. One run container life cycle. Another one run seccomp agent...\n    let (sender, receiver): (\n        Sender<seccomp_agent::SeccompAgentResult>,\n        Receiver<seccomp_agent::SeccompAgentResult>,\n    ) = mpsc::channel();\n    // We have to launch the seccomp agent before we launch the container.\n    // Otherwise, the container creation will be blocked on trying to send to\n    // the seccomp listener and never returns.\n    let child = thread::spawn(move || {\n        let res = seccomp_agent::recv_seccomp_listener(&seccomp_listener_path);\n        sender\n            .send(res)\n            .expect(\"failed to send seccomp agent result back to main thread\");\n    });\n    if let TestResult::Failed(err) = test_outside_container(&spec, &move |data| {\n        let (container_process_state, _) = receiver\n            .recv()\n            .expect(\"failed to receive from channel\")\n            .expect(\"failed to receive from seccomp listener\");\n\n        let state = match data.state {\n            Some(s) => s,\n            None => return TestResult::Failed(anyhow!(\"state command returned error\")),\n        };\n\n        if state.id != *container_process_state.state().id() {\n            return TestResult::Failed(anyhow!(\"container id doesn't match\"));\n        }\n\n        if state.pid.unwrap() != *container_process_state.pid() {\n            return TestResult::Failed(anyhow!(\"container process id doesn't match\"));\n        }\n\n        if SECCOMP_METADATA != container_process_state.metadata().as_deref().unwrap_or(\"\") {\n            return TestResult::Failed(anyhow!(\"seccomp listener metadata doesn't match\"));\n        }\n\n        TestResult::Passed\n    }) {\n        bail!(\"failed to run test outside container: {:?}\", err);\n    }\n\n    if let Err(err) = child.join() {\n        bail!(\"seccomp listener child thread fails: {:?}\", err);\n    }\n\n    Ok(())\n}\n\npub fn get_seccomp_notify_test() -> TestGroup {\n    let seccomp_notify_test = Test::new(\n        \"seccomp_notify\",\n        Box::new(|| match test_seccomp_notify() {\n            Ok(_) => TestResult::Passed,\n            Err(err) => TestResult::Failed(err),\n        }),\n    );\n    let mut tg = TestGroup::new(\"seccomp_notify\");\n    tg.add(vec![Box::new(seccomp_notify_test)]);\n\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/seccomp_notify/seccomp_agent.rs",
    "content": "use std::io::IoSliceMut;\nuse std::os::fd::{AsFd, AsRawFd};\nuse std::os::unix::prelude::RawFd;\nuse std::path::Path;\n\nuse anyhow::{Context, Result, bail};\nuse nix::sys::socket::{self, Backlog, UnixAddr};\nuse nix::unistd;\nuse oci_spec::runtime::ContainerProcessState;\n\nconst DEFAULT_BUFFER_SIZE: usize = 4096;\n\npub type SeccompAgentResult = Result<(ContainerProcessState, RawFd)>;\n\n// Receive information from seccomp notify listener. We will receive 2 items, 1\n// container process state and 1 seccomp notify fd. This function will only\n// receive one connection from the listener and will terminate all socket when\n// returning, since we only expect at most 1 connection to the listener based on\n// the spec.\npub fn recv_seccomp_listener(seccomp_listener: &Path) -> SeccompAgentResult {\n    let addr = socket::UnixAddr::new(seccomp_listener)?;\n    let socket = socket::socket(\n        socket::AddressFamily::Unix,\n        socket::SockType::Stream,\n        socket::SockFlag::empty(),\n        None,\n    )\n    .context(\"failed to create seccomp listener socket\")?;\n\n    socket::bind(socket.as_raw_fd(), &addr).context(\"failed to bind to seccomp listener socket\")?;\n    // Force the backlog to be 1 so in the case of an error, only one connection\n    // from clients will be waiting.\n    socket::listen(&socket.as_fd(), Backlog::new(1)?)\n        .context(\"failed to listen on seccomp listener\")?;\n    let conn = match socket::accept(socket.as_raw_fd()) {\n        Ok(conn) => conn,\n        Err(e) => {\n            bail!(\"failed to accept connection: {}\", e);\n        }\n    };\n    let mut cmsgspace = nix::cmsg_space!([RawFd; 1]);\n    let mut buf = vec![0u8; DEFAULT_BUFFER_SIZE];\n    let mut iov = [IoSliceMut::new(&mut buf)];\n    let msg = match socket::recvmsg::<UnixAddr>(\n        conn,\n        &mut iov,\n        Some(&mut cmsgspace),\n        socket::MsgFlags::MSG_CMSG_CLOEXEC,\n    ) {\n        Ok(msg) => msg,\n        Err(e) => {\n            let _ = unistd::close(conn);\n            bail!(\"failed to receive message: {}\", e);\n        }\n    };\n\n    // We received the message correctly here, so we can now safely close the socket and connection.\n    let _ = unistd::close(conn);\n    drop(socket);\n    // We are expecting 1 SCM_RIGHTS message with 1 fd.\n    let cmsg = msg\n        .cmsgs()?\n        .next()\n        .context(\"expecting at least 1 SCM_RIGHTS message\")?;\n    let fd = match cmsg {\n        socket::ControlMessageOwned::ScmRights(fds) => {\n            if fds.len() != 1 {\n                bail!(\"expecting 1 fds, but received: {:?}\", fds);\n            }\n\n            fds[0]\n        }\n        _ => {\n            bail!(\n                \"expecting 1 SCM_RIGHTS message, but received {:?} instead\",\n                cmsg\n            );\n        }\n    };\n\n    // We have to truncate the message to the correct size, so serde can\n    // deserialized the data correctly.\n    if msg.bytes >= DEFAULT_BUFFER_SIZE {\n        bail!(\"received more than the DEFAULT_BUFFER_SIZE\");\n    }\n    let msg_bytes = msg.bytes;\n\n    buf.truncate(msg_bytes);\n\n    let container_process_state: ContainerProcessState = serde_json::from_slice(&buf[..])\n        .context(\"failed to parse the received message as container process state\")?;\n\n    Ok((container_process_state, fd))\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/sysctl/mod.rs",
    "content": "use std::collections::HashMap;\n\nuse oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder};\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\nfn create_spec(sysctl: HashMap<String, String>) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .sysctl(sysctl)\n                .build()\n                .expect(\"error in building linux config\"),\n        )\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"sysctl\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn sysctl_test() -> TestResult {\n    let spec = create_spec(HashMap::from([(\n        \"net.ipv4.ip_forward\".to_string(),\n        \"1\".to_string(),\n    )]));\n    test_inside_container(&spec, &CreateOptions::default(), &|_| {\n        // As long as the container is created, we expect the kernel parameters to be determined by\n        // the spec, so nothing to prepare prior.\n        Ok(())\n    })\n}\n\npub fn get_sysctl_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"sysctl\");\n    let sysctl_test = Test::new(\"sysctl_test\", Box::new(sysctl_test));\n    test_group.add(vec![Box::new(sysctl_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/tlb/mod.rs",
    "content": "mod tlb_test;\npub use tlb_test::get_tlb_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/tlb/tlb_test.rs",
    "content": "use std::path::PathBuf;\n\nuse anyhow::anyhow;\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxHugepageLimitBuilder, LinuxResourcesBuilder, Spec, SpecBuilder,\n};\nuse test_framework::{ConditionalTest, TestGroup, TestResult, test_result};\n\nuse crate::utils::test_outside_container;\nuse crate::utils::test_utils::check_container_created;\n\nfn check_hugetlb() -> bool {\n    PathBuf::from(\"/sys/fs/cgroup/hugetlb\").exists()\n}\n\nfn check_hugetlb_rsvd() -> bool {\n    let sizes = get_tlb_sizes();\n    for size in sizes.iter() {\n        let rsvd_path = format!(\n            \"/sys/fs/cgroup/hugetlb/hugetlb.{}.rsvd.limit_in_bytes\",\n            size\n        );\n        if !PathBuf::from(rsvd_path).exists() {\n            return false;\n        }\n    }\n    true\n}\n\nfn make_hugetlb_spec(page_size: &str, limit: i64) -> Spec {\n    SpecBuilder::default()\n        .linux(\n            LinuxBuilder::default()\n                .resources(\n                    LinuxResourcesBuilder::default()\n                        .hugepage_limits(vec![\n                            LinuxHugepageLimitBuilder::default()\n                                .page_size(page_size.to_owned())\n                                .limit(limit)\n                                .build()\n                                .expect(\"could not build\"),\n                        ])\n                        .build()\n                        .unwrap(),\n                )\n                .build()\n                .expect(\"could not build\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn test_wrong_tlb() -> TestResult {\n    // 3 MB pagesize is wrong, as valid values must be a power of 2\n    let page = \"3MB\";\n    let limit = 100 * 3 * 1024 * 1024;\n    let spec = make_hugetlb_spec(page, limit);\n    test_outside_container(&spec, &|data| {\n        match data.create_result {\n            Err(e) => TestResult::Failed(anyhow!(e)),\n            Ok(res) => {\n                if data.state.is_some() {\n                    return TestResult::Failed(anyhow!(\n                        \"stdout of state command was non-empty : {:?}\",\n                        data.state\n                    ));\n                }\n                if data.state_err.is_empty() {\n                    return TestResult::Failed(anyhow!(\"stderr of state command was empty\"));\n                }\n                if res.success() {\n                    // The operation should not have succeeded as pagesize was not power of 2\n                    TestResult::Failed(anyhow!(\"invalid page size of {} was allowed\", page))\n                } else {\n                    TestResult::Passed\n                }\n            }\n        }\n    })\n}\n\nfn extract_page_size(dir_name: &str) -> String {\n    let name_stripped = dir_name.strip_prefix(\"hugepages-\").unwrap();\n    let size = name_stripped.strip_suffix(\"kB\").unwrap();\n    let size: u64 = size.parse().unwrap();\n\n    if size >= (1 << 20) {\n        (size >> 20).to_string() + \"GB\"\n    } else if size >= (1 << 10) {\n        (size >> 10).to_string() + \"MB\"\n    } else {\n        size.to_string() + \"KB\"\n    }\n}\n\nfn get_tlb_sizes() -> Vec<String> {\n    let mut sizes = Vec::new();\n    for hugetlb_entry in std::fs::read_dir(\"/sys/kernel/mm/hugepages\")\n        .expect(\"error in reading /sys/kernel/mm/hugepages\")\n    {\n        let hugetlb_entry = hugetlb_entry.expect(\"error in reading /sys/kernel/mm/hugepages entry\");\n        if !hugetlb_entry.path().is_dir() {\n            continue;\n        }\n\n        let dir_name = hugetlb_entry.file_name();\n        let dir_name = dir_name.to_str().unwrap();\n\n        sizes.push(extract_page_size(dir_name));\n    }\n    sizes\n}\n\nfn validate_tlb(id: &str, size: &str, limit: i64) -> TestResult {\n    let root = \"/sys/fs/cgroup/hugetlb\";\n    let path = format!(\"{root}/{id}/hugetlb.{size}.limit_in_bytes\");\n    let val_str = std::fs::read_to_string(path).unwrap();\n    let val: i64 = val_str.trim().parse().unwrap();\n    if val == limit {\n        TestResult::Passed\n    } else {\n        TestResult::Failed(anyhow!(\n            \"page limit not set correctly : for size {}, expected {}, got {}\",\n            size,\n            limit,\n            val\n        ))\n    }\n}\n\nfn validate_rsvd_tlb(id: &str, size: &str, limit: i64) -> TestResult {\n    let root = \"/sys/fs/cgroup/hugetlb\";\n    let path = format!(\"{root}/{id}/hugetlb.{size}.rsvd.limit_in_bytes\");\n    let val_str = std::fs::read_to_string(path).unwrap();\n    let val: i64 = val_str.trim().parse().unwrap();\n    if val == limit {\n        TestResult::Passed\n    } else {\n        TestResult::Failed(anyhow!(\n            \"page limit not set correctly : for size {}, expected {}, got {}\",\n            size,\n            limit,\n            val\n        ))\n    }\n}\n\nfn test_valid_tlb() -> TestResult {\n    // When setting the limit just for checking if writing works, the amount of memory\n    // requested does not matter, as all unsigned integers will be accepted.\n    // Use 1GiB as an example\n    let limit: i64 = 1 << 30;\n    let tlb_sizes = get_tlb_sizes();\n    for size in tlb_sizes.iter() {\n        let spec = make_hugetlb_spec(size, limit);\n        let res = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n\n            let r = validate_tlb(&data.id, size, limit);\n            if matches!(r, TestResult::Failed(_)) {\n                return r;\n            }\n            TestResult::Passed\n        });\n        if matches!(res, TestResult::Failed(_)) {\n            return res;\n        }\n    }\n    TestResult::Passed\n}\n\nfn test_valid_rsvd_tlb() -> TestResult {\n    let limit: i64 = 1 << 30;\n    let tlb_sizes = get_tlb_sizes();\n    for size in tlb_sizes.iter() {\n        let spec = make_hugetlb_spec(size, limit);\n        let res = test_outside_container(&spec, &|data| {\n            test_result!(check_container_created(&data));\n            // Currentle, we write the same value to both limit_in_bytes and rsvd.limit_in_bytes\n            let non_rsvd = validate_tlb(&data.id, size, limit);\n            let rsvd = validate_rsvd_tlb(&data.id, size, limit);\n            if matches!(non_rsvd, TestResult::Failed(_)) {\n                return non_rsvd;\n            } else if matches!(rsvd, TestResult::Failed(_)) {\n                return rsvd;\n            }\n            TestResult::Passed\n        });\n        if matches!(res, TestResult::Failed(_)) {\n            return res;\n        }\n    }\n    TestResult::Passed\n}\n\npub fn get_tlb_test() -> TestGroup {\n    let wrong_tlb = ConditionalTest::new(\n        \"invalid_tlb\",\n        Box::new(check_hugetlb),\n        Box::new(test_wrong_tlb),\n    );\n    let valid_tlb = ConditionalTest::new(\n        \"valid_tlb\",\n        Box::new(check_hugetlb),\n        Box::new(test_valid_tlb),\n    );\n    let valid_rsvd_tlb = ConditionalTest::new(\n        \"valid_rsvd_tlb\",\n        Box::new(check_hugetlb_rsvd),\n        Box::new(test_valid_rsvd_tlb),\n    );\n    let mut tg = TestGroup::new(\"huge_tlb\");\n    tg.add(vec![\n        Box::new(wrong_tlb),\n        Box::new(valid_tlb),\n        Box::new(valid_rsvd_tlb),\n    ]);\n    tg\n}\n"
  },
  {
    "path": "tests/contest/contest/src/tests/uid_mappings/mod.rs",
    "content": "mod uid_mappings_test;\npub use uid_mappings_test::get_uid_mappings_test;\n"
  },
  {
    "path": "tests/contest/contest/src/tests/uid_mappings/uid_mappings_test.rs",
    "content": "use std::vec;\n\nuse oci_spec::runtime::{\n    LinuxBuilder, LinuxIdMapping, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder,\n    LinuxNamespaceType, ProcessBuilder, Spec, SpecBuilder,\n};\nuse rand::RngExt;\nuse test_framework::{Test, TestGroup, TestResult};\n\nuse crate::utils::test_inside_container;\nuse crate::utils::test_utils::CreateOptions;\n\n// The `host_id` is randomly chosen between 1000 and 2000, while the `size`\n// is randomly chosen between 100 and 2500. The `container_id` is fixed at 0.\nfn generate_random_id_mappings() -> Vec<LinuxIdMapping> {\n    let mut rng = rand::rng();\n\n    let host_id: u32 = rng.random_range(1000..=2000);\n    let size: u32 = rng.random_range(100..=2500);\n\n    // container_id 0 must exist, otherwise container creation will fail\n    let id_mapping = LinuxIdMappingBuilder::default()\n        .host_id(host_id)\n        .container_id(0_u32)\n        .size(size)\n        .build()\n        .expect(\"error in building LinuxIdMapping\");\n\n    vec![id_mapping]\n}\n\nfn create_spec(uid_mappings: Vec<LinuxIdMapping>, gid_mappings: Vec<LinuxIdMapping>) -> Spec {\n    let mut namespaces: Vec<LinuxNamespace> = oci_spec::runtime::get_default_namespaces();\n    let userns = LinuxNamespaceBuilder::default()\n        .typ(LinuxNamespaceType::User)\n        .build()\n        .unwrap();\n    namespaces.push(userns);\n\n    let linux_builder = LinuxBuilder::default()\n        .namespaces(namespaces)\n        .uid_mappings(uid_mappings)\n        .gid_mappings(gid_mappings)\n        .build()\n        .expect(\"error in building linux config\");\n\n    SpecBuilder::default()\n        .linux(linux_builder)\n        .process(\n            ProcessBuilder::default()\n                .args(vec![\"runtimetest\".to_string(), \"uid_mappings\".to_string()])\n                .build()\n                .expect(\"error in creating process config\"),\n        )\n        .build()\n        .unwrap()\n}\n\nfn uid_mappings_test() -> TestResult {\n    let uid_mappings = generate_random_id_mappings();\n    let gid_mappings = generate_random_id_mappings();\n\n    let spec = create_spec(uid_mappings, gid_mappings);\n    test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(()))\n}\n\npub fn get_uid_mappings_test() -> TestGroup {\n    let mut test_group = TestGroup::new(\"uid_mappings\");\n    let uid_mappings_test = Test::new(\"uid_mappings\", Box::new(uid_mappings_test));\n\n    test_group.add(vec![Box::new(uid_mappings_test)]);\n\n    test_group\n}\n"
  },
  {
    "path": "tests/contest/contest/src/utils/mod.rs",
    "content": "pub mod support;\npub mod test_utils;\n\npub use support::{\n    generate_uuid, get_runtime_path, get_runtimetest_path, is_runtime_runc, prepare_bundle,\n    set_config, wait_for_file_content,\n};\npub use test_utils::{\n    CreateOptions, LifecycleStatus, State, WaitTarget, create_container, delete_container,\n    exec_container, get_state, kill_container, start_container, test_inside_container,\n    test_outside_container, wait_for_state,\n};\n"
  },
  {
    "path": "tests/contest/contest/src/utils/support.rs",
    "content": "use std::fs::File;\nuse std::path::{Path, PathBuf};\nuse std::sync::OnceLock;\nuse std::{env, fs};\n\nuse anyhow::{Context, Result, anyhow};\nuse flate2::read::GzDecoder;\nuse oci_spec::runtime::{Process, Spec};\nuse rand::RngExt;\nuse tar::Archive;\nuse tempfile::TempDir;\nuse uuid::Uuid;\n\nstatic RUNTIME_PATH: OnceLock<PathBuf> = OnceLock::new();\nstatic RUNTIMETEST_PATH: OnceLock<PathBuf> = OnceLock::new();\n\npub fn set_runtime_path(path: &Path) {\n    RUNTIME_PATH.set(path.to_owned()).unwrap();\n}\n\npub fn get_runtime_path() -> &'static PathBuf {\n    RUNTIME_PATH.get().expect(\"Runtime path is not set\")\n}\n\npub fn set_runtimetest_path(path: &Path) {\n    RUNTIMETEST_PATH.set(path.to_owned()).unwrap();\n}\n\npub fn get_runtimetest_path() -> &'static PathBuf {\n    RUNTIMETEST_PATH.get().expect(\"Runtimetest path is not set\")\n}\n\n#[allow(dead_code)]\npub fn get_project_path() -> PathBuf {\n    let current_dir_path_result = env::current_dir();\n    match current_dir_path_result {\n        Ok(path_buf) => path_buf,\n        Err(e) => panic!(\"directory is not found, {e}\"),\n    }\n}\n\n/// This will generate the UUID needed when creating the container.\npub fn generate_uuid() -> Uuid {\n    let mut rng = rand::rng();\n    const CHARSET: &[u8] = b\"0123456789abcdefABCDEF\";\n\n    let rand_string: String = (0..32)\n        .map(|_| {\n            let idx = rng.random_range(0..CHARSET.len());\n            CHARSET[idx] as char\n        })\n        .collect();\n\n    match Uuid::parse_str(&rand_string) {\n        Ok(uuid) => uuid,\n        Err(e) => panic!(\"can not parse uuid, {e}\"),\n    }\n}\n\n/// Creates a bundle directory in a temp directory\npub fn prepare_bundle() -> Result<TempDir> {\n    let temp_dir = tempfile::tempdir()?;\n    let tar_file_name = \"bundle.tar.gz\";\n    let tar_source = std::env::current_dir()?.join(tar_file_name);\n    let tar_target = temp_dir.as_ref().join(tar_file_name);\n    std::fs::copy(&tar_source, &tar_target)\n        .with_context(|| format!(\"could not copy {tar_source:?} to {tar_target:?}\"))?;\n\n    let tar_gz = File::open(&tar_source)?;\n    let tar = GzDecoder::new(tar_gz);\n    let mut archive = Archive::new(tar);\n    archive.unpack(&temp_dir).with_context(|| {\n        format!(\n            \"failed to unpack {:?} to {:?}\",\n            tar_source,\n            temp_dir.as_ref()\n        )\n    })?;\n\n    let mut spec = Spec::default();\n    let mut process = Process::default();\n    process.set_args(Some(vec![\"sleep\".into(), \"10\".into()]));\n    spec.set_process(Some(process));\n    set_config(&temp_dir, &spec).unwrap();\n\n    Ok(temp_dir)\n}\n\n/// Sets the config.json file as per given spec\npub fn set_config<P: AsRef<Path>>(project_path: P, config: &Spec) -> Result<()> {\n    let path = project_path.as_ref().join(\"bundle\").join(\"config.json\");\n    config.save(path)?;\n    Ok(())\n}\n\npub fn is_runtime_runc() -> bool {\n    match std::env::var(\"RUNTIME_KIND\") {\n        Err(_) => false,\n        Ok(s) => s == \"runc\",\n    }\n}\n\npub fn wait_for_file_content(\n    file_path: &PathBuf,\n    expected_content: &str,\n    timeout: std::time::Duration,\n    poll_interval: std::time::Duration,\n) -> anyhow::Result<()> {\n    let start = std::time::Instant::now();\n\n    while start.elapsed() < timeout {\n        if file_path.exists()\n            && let Ok(contents) = fs::read_to_string(file_path)\n            && contents.contains(expected_content)\n        {\n            return Ok(());\n        }\n        std::thread::sleep(poll_interval);\n    }\n\n    let actual_content = fs::read_to_string(file_path)\n        .unwrap_or_else(|_| \"(file does not exist or cannot be read)\".to_string());\n\n    Err(anyhow!(\n        \"Timed out waiting for file {} to contain '{expected_content}', but got: '{actual_content}'\",\n        file_path.display(),\n    ))\n}\n"
  },
  {
    "path": "tests/contest/contest/src/utils/test_utils.rs",
    "content": "//! Contains utility functions for testing\n//! Similar to https://github.com/opencontainers/runtime-tools/blob/master/validation/util/test.go\nuse std::collections::HashMap;\nuse std::ffi::OsStr;\nuse std::path::{Path, PathBuf};\nuse std::process::{Child, Command, ExitStatus, Stdio};\nuse std::thread::sleep;\nuse std::time::Duration;\n\nuse anyhow::{Context, Result, anyhow, bail};\nuse nix::mount::{MntFlags, umount2};\nuse oci_spec::runtime::{LinuxNamespaceType, Spec};\nuse serde::{Deserialize, Serialize};\nuse test_framework::{TestResult, test_result};\nuse thiserror::Error;\n\nuse super::{generate_uuid, get_runtime_path, get_runtimetest_path, prepare_bundle, set_config};\n\nconst SLEEP_TIME: Duration = Duration::from_millis(150);\npub const CGROUP_ROOT: &str = \"/sys/fs/cgroup\";\n\n#[derive(Error, Debug)]\npub enum ContainerStateError {\n    #[error(\"Failed to parse lifecycle status\")]\n    ParseLifecycleStatus(#[source] serde_json::Error),\n    #[error(\"Container does not exist\")]\n    ContainerNotFound,\n    #[error(transparent)]\n    Other(#[from] anyhow::Error),\n}\n\n#[derive(Serialize, Deserialize, Debug, Clone)]\n#[serde(rename_all = \"camelCase\")]\npub struct State {\n    pub oci_version: String,\n    pub id: String,\n    pub status: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub pid: Option<i32>,\n    pub bundle: PathBuf,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub annotations: Option<HashMap<String, String>>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub created: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub creator: Option<u32>,\n    pub use_systemd: Option<bool>,\n}\n\n#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]\n#[serde(rename_all = \"lowercase\")]\npub enum LifecycleStatus {\n    Creating,\n    Created,\n    Running,\n    Stopped,\n}\n\n#[derive(Debug, PartialEq, Eq, Clone, Copy)]\npub enum WaitTarget {\n    Status(LifecycleStatus),\n    // the state after the container is deleted\n    // this state isn't in the runtime spec, but is useful for tests that wait for deletion\n    Deleted,\n}\n\n#[derive(Debug)]\npub struct ContainerData {\n    pub id: String,\n    pub state: Option<State>,\n    pub state_err: String,\n    pub create_result: std::io::Result<ExitStatus>,\n    pub bundle: PathBuf,\n}\n\n#[derive(Debug, Default)]\npub struct CreateOptions<'a> {\n    extra_args: &'a [&'a OsStr],\n    no_pivot: bool,\n}\n\nimpl<'a> CreateOptions<'a> {\n    pub fn with_extra_args(mut self, extra_args: &'a [&'a OsStr]) -> Self {\n        self.extra_args = extra_args;\n        self\n    }\n\n    pub fn with_no_pivot_root(mut self) -> Self {\n        self.no_pivot = true;\n        self\n    }\n}\n\nfn create_container_command<P: AsRef<Path>>(id: &str, dir: P, options: &CreateOptions) -> Command {\n    let mut command = Command::new(get_runtime_path());\n    command\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"--root\")\n        .arg(dir.as_ref().join(\"runtime\"))\n        .arg(\"create\")\n        .arg(id)\n        .arg(\"--bundle\")\n        .arg(dir.as_ref().join(\"bundle\"))\n        .args(options.extra_args);\n    if options.no_pivot {\n        command.arg(\"--no-pivot\");\n    }\n    command\n}\n\n/// Starts the runtime with given directory as root directory\npub fn create_container<P: AsRef<Path>>(\n    id: &str,\n    dir: P,\n    options: &CreateOptions,\n) -> Result<Child> {\n    let res = create_container_command(id, dir, options)\n        .spawn()\n        .context(\"could not create container\")?;\n    Ok(res)\n}\n\n/// Sends a kill command to the given container process\npub fn kill_container<P: AsRef<Path>>(id: &str, dir: P) -> Result<Child> {\n    let res = runtime_command(dir)\n        .arg(\"kill\")\n        .arg(id)\n        .arg(\"9\")\n        .spawn()\n        .context(\"could not kill container\")?;\n    Ok(res)\n}\n\npub fn delete_container<P: AsRef<Path>>(id: &str, dir: P) -> Result<Child> {\n    let res = runtime_command(dir)\n        .arg(\"delete\")\n        .arg(id)\n        .spawn()\n        .context(\"could not delete container\")?;\n    Ok(res)\n}\n\npub fn get_state<P: AsRef<Path>>(id: &str, dir: P) -> Result<(String, String)> {\n    sleep(SLEEP_TIME);\n    let output = runtime_command(dir)\n        .arg(\"state\")\n        .arg(id)\n        .spawn()\n        .context(\"could not get container state\")?\n        .wait_with_output()\n        .context(\"failed while waiting for state command\")?;\n    let stderr = String::from_utf8(output.stderr).context(\"failed to parse std error stream\")?;\n    let stdout = String::from_utf8(output.stdout).context(\"failed to parse std output stream\")?;\n    Ok((stdout, stderr))\n}\n\n/// Get the container status as a LifecycleStatus\npub fn get_container_status<P: AsRef<Path>>(\n    id: &str,\n    dir: P,\n) -> Result<LifecycleStatus, ContainerStateError> {\n    let (stdout, stderr) = get_state(id, &dir).map_err(|e| {\n        if e.to_string().contains(\"does not exist\") {\n            ContainerStateError::ContainerNotFound\n        } else {\n            ContainerStateError::Other(e)\n        }\n    })?;\n\n    if stderr.contains(\"does not exist\") {\n        return Err(ContainerStateError::ContainerNotFound);\n    }\n\n    if stderr.contains(\"Error\") || stderr.contains(\"error\") {\n        return Err(ContainerStateError::Other(anyhow!(\n            \"Error :\\nstdout : {}\\nstderr : {}\",\n            stdout,\n            stderr\n        )));\n    }\n\n    let value = serde_json::from_str::<serde_json::Value>(&stdout).map_err(|err| {\n        ContainerStateError::Other(anyhow!(\n            \"Failed to parse state output as JSON: {} - {}\",\n            stdout,\n            err\n        ))\n    })?;\n\n    let status = value.get(\"status\").ok_or_else(|| {\n        ContainerStateError::Other(anyhow!(\n            \"Failed to extract status from state output: {}\",\n            stdout\n        ))\n    })?;\n\n    serde_json::from_value::<LifecycleStatus>(status.clone())\n        .map_err(ContainerStateError::ParseLifecycleStatus)\n}\n\n/// Check if a container matches the expected wait target\n///\n/// Returns `true` if the container state matches the expected target, `false` otherwise.\n/// When `WaitTarget::Deleted` is specified, returns `true` if the container does not exist.\npub fn is_in_state<P: AsRef<Path>>(\n    id: &str,\n    dir: P,\n    expected_target: WaitTarget,\n) -> Result<bool, ContainerStateError> {\n    match (get_container_status(id, &dir), expected_target) {\n        (Ok(status), WaitTarget::Status(expected_status)) => Ok(status == expected_status),\n        (Ok(_), WaitTarget::Deleted) => Ok(false),\n        (Err(ContainerStateError::ContainerNotFound), WaitTarget::Deleted) => Ok(true),\n        (Err(ContainerStateError::ContainerNotFound), WaitTarget::Status(_)) => Ok(false),\n        (Err(e), _) => Err(e),\n    }\n}\n\n/// Wait for a container to reach a specific wait target with timeout\npub fn wait_for_state<P: AsRef<Path>>(\n    id: &str,\n    dir: P,\n    expected_target: WaitTarget,\n    timeout: Duration,\n    poll_interval: Duration,\n) -> Result<()> {\n    let start = std::time::Instant::now();\n    let deadline = start + timeout;\n\n    while std::time::Instant::now() < deadline {\n        match is_in_state(id, &dir, expected_target) {\n            Ok(true) => return Ok(()),\n            Ok(false) | Err(ContainerStateError::ParseLifecycleStatus(_)) => {\n                std::thread::sleep(poll_interval)\n            }\n            Err(e) => {\n                return Err(anyhow::Error::from(e).context(format!(\n                    \"Failed to wait for container {} to reach {:?} target\",\n                    id, expected_target\n                )));\n            }\n        }\n    }\n\n    bail!(\n        \"Timed out waiting for container {} to reach {:?} target\",\n        id,\n        expected_target\n    )\n}\n\npub fn start_container<P: AsRef<Path>>(id: &str, dir: P) -> Result<Child> {\n    let res = runtime_command(dir)\n        .arg(\"start\")\n        .arg(id)\n        .spawn()\n        .context(\"could not start container\")?;\n    Ok(res)\n}\n\nfn runtime_command<P: AsRef<Path>>(dir: P) -> Command {\n    let mut command = Command::new(get_runtime_path());\n    command\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .arg(\"--root\")\n        .arg(dir.as_ref().join(\"runtime\"));\n    command\n}\n\npub fn test_outside_container(\n    spec: &Spec,\n    execute_test: &dyn Fn(ContainerData) -> TestResult,\n) -> TestResult {\n    let id = generate_uuid();\n    let id_str = id.to_string();\n    let bundle = prepare_bundle().unwrap();\n    set_config(&bundle, spec).unwrap();\n    let options = CreateOptions::default();\n    let create_result = create_container(&id_str, &bundle, &options).unwrap().wait();\n    let (out, err) = get_state(&id_str, &bundle).unwrap();\n    let state: Option<State> = serde_json::from_str(&out).ok();\n    let data = ContainerData {\n        id: id.to_string(),\n        state,\n        state_err: err,\n        create_result,\n        bundle: bundle.path().to_path_buf(),\n    };\n    let test_result = execute_test(data);\n    // this is to unmount the mounted rootfs. The issue here is that for ns_itype test\n    // we do not create mount namespace, which results in mounting the actual root on bundle\n    // thus the deletion in tempdir on drop fails and the tempdir remains. So, we check if there\n    // is no mount namespace in the spec's namespaces, and if there is no mount namespace,\n    // we manually unmount the rootfs so tmpdir deletion can succeed and cleanup is done.\n    let ns = spec.linux().as_ref().and_then(|l| l.namespaces().clone());\n    if let Some(ns) = ns\n        && !ns.iter().any(|n| n.typ() == LinuxNamespaceType::Mount)\n    {\n        umount2(&bundle.path().join(\"bundle/rootfs\"), MntFlags::MNT_DETACH).unwrap();\n    }\n\n    kill_container(&id_str, &bundle).unwrap().wait().unwrap();\n    delete_container(&id_str, &bundle).unwrap().wait().unwrap();\n    test_result\n}\n\n// mostly needs a name that better expresses what this actually does\npub fn test_inside_container(\n    spec: &Spec,\n    options: &CreateOptions,\n    setup_for_test: &dyn Fn(&Path) -> Result<()>,\n) -> TestResult {\n    let id = generate_uuid();\n    let id_str = id.to_string();\n    let bundle = prepare_bundle().unwrap();\n\n    set_config(&bundle, spec).unwrap();\n\n    // This will do the required setup for the test\n    test_result!(setup_for_test(\n        &bundle.as_ref().join(\"bundle\").join(\"rootfs\")\n    ));\n\n    // as we have to run runtimetest inside the container, and is expects\n    // the config.json to be at path /config.json we save it there\n    let path = bundle\n        .as_ref()\n        .join(\"bundle\")\n        .join(\"rootfs\")\n        .join(\"config.json\");\n    spec.save(path).unwrap();\n\n    let runtimetest_path = get_runtimetest_path();\n    // The config will directly use runtime as the command to be run, so we have to\n    // save the runtimetest binary at its /bin\n    std::fs::copy(\n        runtimetest_path,\n        bundle\n            .as_ref()\n            .join(\"bundle\")\n            .join(\"rootfs\")\n            .join(\"bin\")\n            .join(\"runtimetest\"),\n    )\n    .unwrap();\n    let create_process = create_container(&id_str, &bundle, options).unwrap();\n    // here we do not wait for the process by calling wait() as in the test_outside_container\n    // function because we need the output of the runtimetest. If we call wait, it will return\n    // and we won't have an easy way of getting the stdio of the runtimetest.\n    // Thus to make sure the container is created, we just wait for sometime, and\n    // assume that the create command was successful. If it wasn't we can catch that error\n    // in the start_container, as we can not start a non-created container anyways\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    match start_container(&id_str, &bundle)\n        .unwrap()\n        .wait_with_output()\n    {\n        Ok(c) => c,\n        Err(e) => {\n            // given that start has failed, we can be pretty sure that create has either failed\n            // or completed already, so we wait on it so it does not become a zombie process\n            let _ = create_process.wait_with_output();\n            return TestResult::Failed(anyhow!(\"container start failed : {:?}\", e));\n        }\n    };\n\n    let create_output = create_process\n        .wait_with_output()\n        .context(\"getting output after starting the container failed\")\n        .unwrap();\n\n    let stdout = String::from_utf8_lossy(&create_output.stdout);\n    if !stdout.is_empty() {\n        println!(\n            \"{:?}\",\n            anyhow!(\"container stdout was not empty, found : {}\", stdout)\n        )\n    }\n    let stderr = String::from_utf8_lossy(&create_output.stderr);\n    if !stderr.is_empty() {\n        return TestResult::Failed(anyhow!(\n            \"container stderr was not empty, found : {}\",\n            stderr\n        ));\n    }\n\n    let (out, err) = get_state(&id_str, &bundle).unwrap();\n    if !err.is_empty() {\n        return TestResult::Failed(anyhow!(\n            \"error in getting state after starting the container : {}\",\n            err\n        ));\n    }\n\n    let state: State = match serde_json::from_str(&out) {\n        Ok(v) => v,\n        Err(e) => {\n            return TestResult::Failed(anyhow!(\n                \"error in parsing state of container after start in test_inside_container : stdout : {}, parse error : {}\",\n                out,\n                e\n            ));\n        }\n    };\n    if state.status != \"stopped\" {\n        return TestResult::Failed(anyhow!(\n            \"error : unexpected container status in test_inside_runtime : expected stopped, got {}, container state : {:?}\",\n            state.status,\n            state\n        ));\n    }\n    kill_container(&id_str, &bundle).unwrap().wait().unwrap();\n    delete_container(&id_str, &bundle).unwrap().wait().unwrap();\n    TestResult::Passed\n}\n\npub fn check_container_created(data: &ContainerData) -> Result<()> {\n    match &data.create_result {\n        Ok(exit_status) => {\n            if !exit_status.success() {\n                bail!(\n                    \"container creation was not successful. Exit code was {:?}\",\n                    exit_status.code()\n                )\n            }\n\n            if !data.state_err.is_empty() {\n                bail!(\n                    \"container state could not be retrieved successfully. Error was {}\",\n                    data.state_err\n                );\n            }\n\n            if data.state.is_none() {\n                bail!(\"container state could not be retrieved\");\n            }\n\n            let container_state = data.state.as_ref().unwrap();\n            if container_state.id != data.id {\n                bail!(\n                    \"container state contains container id {}, but expected was {}\",\n                    container_state.id,\n                    data.id\n                );\n            }\n\n            if container_state.status != \"created\" {\n                bail!(\n                    \"expected container to be in state created, but was in state {}\",\n                    container_state.status\n                );\n            }\n\n            Ok(())\n        }\n        Err(e) => Err(anyhow!(\"{}\", e)),\n    }\n}\n\npub fn exec_container<P: AsRef<Path>>(\n    id: &str,\n    dir: P,\n    args: &[impl AsRef<OsStr>],\n    process_path: Option<&Path>,\n    env: &[(&str, &str)],\n) -> Result<(String, String)> {\n    let mut command = runtime_command(&dir);\n    command.arg(\"--debug\").arg(\"exec\");\n\n    if let Some(path) = process_path {\n        command.arg(\"--process\").arg(path);\n    }\n\n    for (k, v) in env {\n        command.arg(\"--env\").arg(format!(\"{k}={v}\"));\n    }\n\n    command.arg(id);\n\n    if process_path.is_none() {\n        command.args(args);\n    }\n\n    let output = command.output().context(\"failed to run exec\")?;\n\n    let stdout = String::from_utf8_lossy(&output.stdout).to_string();\n    let stderr = String::from_utf8_lossy(&output.stderr).to_string();\n\n    if !output.status.success() {\n        bail!(\n            \"exec failed with status: {:?}, stderr: {}\",\n            output.status,\n            stderr\n        );\n    }\n\n    Ok((stdout, stderr))\n}\n"
  },
  {
    "path": "tests/contest/runtimetest/.cargo/config.toml",
    "content": "[target.x86_64-unknown-linux-gnu]\nrustflags = [\"-C\", \"target-feature=+crt-static\"]\n"
  },
  {
    "path": "tests/contest/runtimetest/Cargo.toml",
    "content": "[package]\nname = \"runtimetest\"\nversion = \"0.0.1\"\nedition = \"2024\"\n\n[dependencies]\noci-spec = { version = \"0.9.0\", features = [\"runtime\"] }\nnix = \"0.29.0\"\nanyhow = \"1.0\"\nlibc = \"0.2.180\" # TODO (YJDoc2) upgrade to latest\nnc = \"0.9.7\"\ntempfile = \"3\"\nnetlink-packet-route = \"0.26.0\"\nnetlink-packet-core = \"0.8.1\"\nnetlink-sys = \"0.8.8\"\n"
  },
  {
    "path": "tests/contest/runtimetest/README.md",
    "content": "# Runtime test\n\nThis is the binary which runs the tests inside the container process, and checks that constraints and restrictions are upheld from inside the container. This is supposed to be rust version of [runtimetest command](https://github.com/opencontainers/runtime-tools/tree/master/cmd/runtimetest) from runtime tools.\n\nThis is primarily used from the `test_inside_container` function related tests in the integration tests.\n\n## Conventions\n\nThe main function will call the different tests functions, one by one to check that all required guarantees hold. This might be parallelized in future, but initially the tests are run serially.\n\nThe path of config spec will always be /spec.json , and this is fixed so that no additional env or cmd arg is required, and we don't need to depend on clap or manual parsing for that.\n\nMake sure to consider failure cases, and try not to panic from any functions. If any error occur, or if some test fails, then it should write the error to the stderr, and return. The integration test will check stderr to be empty as an indication of all tests passing, and in case stderr is not empty, it will consider some test to be failing, and show the error as the contents of stderr. Thus make sure to include enough information in stderr message from failing tests to understand what failed in which test.\nThere is currently no convention of explicit indication of tests passing, the passing test may write `OK` or something similar to stdout, but as of now, the stdout will be completely ignored by integration test.\n\n## Special Notes\n\nThis package must be compiled as a statically linked binary, as otherwise the rust compile will make it dynamically link to /lib64/ld-linux-x86-64.so , which is not available inside the container, and thus making the binary not usable inside the container process.\n\n**Note** that the dynamically linked binary does not give a `segmentation fault` or similar error when tried to run inside the container, but instead gives `no such file or directory found` or `executable not found` error, even though the executable exists in the container. This made this tricky to debug correctly when originally developing, so if you decide on chaining the compilation or configuration of this , please make absolutely sure that the changes work and do not accidentally break something.\n\nyou can use\n\n```bash\nreadelf -l path/to/binary | grep \"program interpreter\"  # should give empty output\nfile path/to/binary                                     # should specify statically linked in output\n```\n\nto find out if the binary is dynamically or statically linked.\n\nReading the Readme of integration tests can be helpful to understand how the integration tests and the runtime tests interoperate with one another.\n\nsee\n\n<https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables>\n<https://superuser.com/questions/248512/why-do-i-get-command-not-found-when-the-binary-file-exists>\n<https://doc.rust-lang.org/cargo/reference/config.html>\n\nfor more info\n"
  },
  {
    "path": "tests/contest/runtimetest/src/main.rs",
    "content": "mod tests;\nmod utils;\n\nuse std::env;\nuse std::path::PathBuf;\n\nuse oci_spec::runtime::IOPriorityClass::{IoprioClassBe, IoprioClassIdle, IoprioClassRt};\nuse oci_spec::runtime::Spec;\n\nconst SPEC_PATH: &str = \"/config.json\";\n\nfn get_spec() -> Spec {\n    let path = PathBuf::from(SPEC_PATH);\n    match Spec::load(path) {\n        Ok(spec) => spec,\n        Err(e) => {\n            eprintln!(\"Error in loading spec, {e:?}\");\n            std::process::exit(66);\n        }\n    }\n}\n\n////////// ANCHOR: example_runtimetest_main\nfn main() {\n    let spec = get_spec();\n    let args: Vec<String> = env::args().collect();\n    let execute_test = match args.get(1) {\n        Some(execute_test) => execute_test.to_string(),\n        None => return eprintln!(\"error due to execute test name not found\"),\n    };\n\n    match &*execute_test {\n        \"hello_world\" => tests::hello_world(&spec),\n        ////////// ANCHOR_END: example_runtimetest_main\n        \"readonly_paths\" => tests::validate_readonly_paths(&spec),\n        \"masked_paths\" => tests::validate_masked_paths(&spec),\n        \"set_host_name\" => tests::validate_hostname(&spec),\n        \"mounts_recursive\" => tests::validate_mounts_recursive(&spec),\n        \"mounts_recursive_rbind_ro\" => tests::validate_mounts_recursive_rbind_ro(),\n        \"domainname_test\" => tests::validate_domainname(&spec),\n        \"seccomp\" => tests::validate_seccomp(&spec),\n        \"sysctl\" => tests::validate_sysctl(&spec),\n        \"scheduler_policy_other\" => tests::validate_scheduler_policy(&spec),\n        \"scheduler_policy_batch\" => tests::validate_scheduler_policy(&spec),\n        \"io_priority_class_rt\" => tests::test_io_priority_class(&spec, IoprioClassRt),\n        \"io_priority_class_be\" => tests::test_io_priority_class(&spec, IoprioClassBe),\n        \"io_priority_class_idle\" => tests::test_io_priority_class(&spec, IoprioClassIdle),\n        \"memory_policy\" => tests::validate_memory_policy(&spec),\n        \"devices\" => tests::validate_devices(&spec),\n        \"root_readonly\" => tests::test_validate_root_readonly(&spec),\n        \"process\" => tests::validate_process(&spec),\n        \"process_user\" => tests::validate_process_user(&spec),\n        \"process_rlimits\" => tests::validate_process_rlimits(&spec),\n        \"no_pivot\" => tests::validate_rootfs(),\n        \"process_oom_score_adj\" => tests::validate_process_oom_score_adj(&spec),\n        \"fd_control\" => tests::validate_fd_control(&spec),\n        \"rootfs_propagation\" => tests::validate_rootfs_propagation(&spec),\n        \"uid_mappings\" => tests::validate_uid_mappings(&spec),\n        \"net_devices\" => tests::validate_net_devices(&spec),\n        _ => eprintln!(\"error due to unexpected execute test name: {execute_test}\"),\n    }\n}\n"
  },
  {
    "path": "tests/contest/runtimetest/src/tests.rs",
    "content": "use std::env;\nuse std::ffi::OsStr;\nuse std::fs::{self, File, read_dir};\nuse std::io::{self, BufRead};\nuse std::os::linux::fs::MetadataExt;\nuse std::os::unix::fs::{FileTypeExt, PermissionsExt};\nuse std::path::Path;\n\nuse anyhow::{Result, bail};\nuse netlink_packet_core::{NLM_F_DUMP, NLM_F_REQUEST, NetlinkMessage, NetlinkPayload};\nuse netlink_packet_route::RouteNetlinkMessage;\nuse netlink_packet_route::address::AddressMessage;\nuse netlink_packet_route::link::{LinkAttribute, LinkMessage};\nuse netlink_sys::Socket;\nuse netlink_sys::protocols::NETLINK_ROUTE;\nuse nix::errno::Errno;\nuse nix::libc;\nuse nix::mount::{MsFlags, mount};\nuse nix::sys::resource::{Resource, getrlimit};\nuse nix::sys::stat::{Mode, umask};\nuse nix::sys::utsname;\nuse nix::unistd::{Gid, Uid, getcwd, getgid, getgroups, getuid};\nuse oci_spec::runtime::IOPriorityClass::{self, IoprioClassBe, IoprioClassIdle, IoprioClassRt};\nuse oci_spec::runtime::MemoryPolicyFlagType::*;\nuse oci_spec::runtime::{\n    LinuxDevice, LinuxDeviceType, LinuxIdMapping, LinuxSchedulerPolicy, MemoryPolicyModeType,\n    PosixRlimit, PosixRlimitType, Spec,\n};\nuse tempfile::Builder;\n\nuse crate::utils::{\n    self, test_dir_read_access, test_dir_write_access, test_read_access, test_write_access,\n};\n\n////////// ANCHOR: example_hello_world\npub fn hello_world(_spec: &Spec) {\n    println!(\"Hello world\");\n}\n////////// ANCHOR_END: example_hello_world\n\npub fn validate_readonly_paths(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    let ro_paths = match linux.readonly_paths() {\n        Some(p) => p,\n        None => {\n            eprintln!(\"in readonly paths, expected some readonly paths to be set, found none\");\n            return;\n        }\n    };\n\n    if ro_paths.is_empty() {\n        return;\n    }\n\n    // TODO when https://github.com/rust-lang/rust/issues/86442 stabilizes,\n    // change manual matching of i32 to e.kind() and match statement\n    for path in ro_paths {\n        if let std::io::Result::Err(e) = test_read_access(path) {\n            let errno = Errno::from_raw(e.raw_os_error().unwrap());\n            // In the integration tests we test for both existing and non-existing readonly paths\n            // to be specified in the spec, so we allow ENOENT here\n            if errno == Errno::ENOENT {\n                /* This is expected */\n            } else {\n                eprintln!(\n                    \"in readonly paths, error in testing read access for path {path} : {e:?}\"\n                );\n                return;\n            }\n        } else {\n            /* Expected */\n        }\n\n        if let std::io::Result::Err(e) = test_write_access(path) {\n            let errno = Errno::from_raw(e.raw_os_error().unwrap());\n            // In the integration tests we test for both existing and non-existing readonly paths\n            // being specified in the spec, so we allow ENOENT, and we expect EROFS as the paths\n            // should be read-only\n            if errno == Errno::ENOENT || errno == Errno::EROFS {\n                /* This is expected */\n            } else {\n                eprintln!(\n                    \"in readonly paths, error in testing write access for path {path} : {e:?}\"\n                );\n                return;\n            }\n        } else {\n            eprintln!(\"in readonly paths, path {path} expected to not be writable, found writable\");\n            return;\n        }\n    }\n}\n\npub fn validate_hostname(spec: &Spec) {\n    if let Some(expected_hostname) = spec.hostname() {\n        if expected_hostname.is_empty() {\n            // Skipping empty hostname\n            return;\n        }\n        let actual_hostname = nix::unistd::gethostname().expect(\"failed to get current hostname\");\n        let actual_hostname = actual_hostname.to_str().unwrap();\n        if actual_hostname != expected_hostname {\n            eprintln!(\n                \"Unexpected hostname, expected: {expected_hostname:?} found: {actual_hostname:?}\"\n            );\n        }\n    }\n}\n\npub fn validate_domainname(spec: &Spec) {\n    if let Some(expected_domainname) = spec.domainname() {\n        if expected_domainname.is_empty() {\n            return;\n        }\n\n        let uname_info = utsname::uname().unwrap();\n        let actual_domainname = uname_info.domainname();\n        if actual_domainname.to_str().unwrap() != expected_domainname {\n            eprintln!(\n                \"Unexpected domainname, expected: {:?} found: {:?}\",\n                expected_domainname,\n                actual_domainname.to_str().unwrap()\n            );\n        }\n    }\n}\n\n// Run argument test recursively for files after base_dir\nfn do_test_mounts_recursive(base_dir: &Path, test_fn: &dyn Fn(&Path) -> Result<()>) -> Result<()> {\n    let dirs = read_dir(base_dir).unwrap();\n    for dir in dirs {\n        let dir = dir.unwrap();\n        let f_type = dir.file_type().unwrap();\n        if f_type.is_dir() {\n            do_test_mounts_recursive(dir.path().as_path(), test_fn)?;\n        }\n\n        if f_type.is_file() {\n            test_fn(dir.path().as_path())?;\n        }\n    }\n\n    Ok(())\n}\n\npub fn validate_mounts_recursive(spec: &Spec) {\n    if let Some(mounts) = spec.mounts() {\n        for mount in mounts {\n            if let Some(options) = mount.options() {\n                let subdir_path = mount.destination().join(\"mount_subdir\");\n                let null_device_path = subdir_path.join(\"null\");\n                for option in options {\n                    match option.as_str() {\n                        \"rro\" => {\n                            if let Err(e) =\n                                do_test_mounts_recursive(mount.destination(), &|test_file_path| {\n                                    if utils::test_write_access(test_file_path.to_str().unwrap())\n                                        .is_ok()\n                                    {\n                                        // Return Err if writeable\n                                        bail!(\n                                            \"path {:?} expected to be read-only, found writable\",\n                                            test_file_path\n                                        );\n                                    }\n                                    Ok(())\n                                })\n                            {\n                                eprintln!(\"error in testing rro recursive mounting : {e}\");\n                            }\n                        }\n                        \"rrw\" => {\n                            if let Err(e) =\n                                do_test_mounts_recursive(mount.destination(), &|test_file_path| {\n                                    if utils::test_write_access(test_file_path.to_str().unwrap())\n                                        .is_err()\n                                    {\n                                        // Return Err if not writeable\n                                        bail!(\n                                            \"path {:?} expected to be  writable, found read-only\",\n                                            test_file_path\n                                        );\n                                    }\n                                    Ok(())\n                                })\n                            {\n                                eprintln!(\"error in testing rrw recursive mounting : {e}\");\n                            }\n                        }\n                        \"rnoexec\" => {\n                            if let Err(e) = do_test_mounts_recursive(\n                                mount.destination(),\n                                &|test_file_path| {\n                                    if utils::test_file_executable(test_file_path.to_str().unwrap())\n                                        .is_ok()\n                                    {\n                                        bail!(\n                                            \"path {:?} expected to be not executable, found executable\",\n                                            test_file_path\n                                        );\n                                    }\n                                    Ok(())\n                                },\n                            ) {\n                                eprintln!(\"error in testing rnoexec recursive mounting: {e}\");\n                            }\n                        }\n                        \"rexec\" => {\n                            if let Err(e) = do_test_mounts_recursive(\n                                mount.destination(),\n                                &|test_file_path| {\n                                    if let Err(ee) = utils::test_file_executable(\n                                        test_file_path.to_str().unwrap(),\n                                    ) {\n                                        bail!(\n                                            \"path {:?} expected to be executable, found not executable, error: {ee}\",\n                                            test_file_path\n                                        );\n                                    }\n                                    Ok(())\n                                },\n                            ) {\n                                eprintln!(\"error in testing rexec recursive mounting: {e}\");\n                            }\n                        }\n                        \"rdiratime\" => {\n                            let rest =\n                                utils::test_dir_update_access_time(subdir_path.to_str().unwrap());\n                            if let Err(e) = rest {\n                                eprintln!(\"error in testing rdiratime recursive mounting: {e}\");\n                            }\n                        }\n                        \"rnodiratime\" => {\n                            let rest = utils::test_dir_not_update_access_time(\n                                subdir_path.to_str().unwrap(),\n                            );\n                            if let Err(e) = rest {\n                                eprintln!(\"error in testing rnodiratime recursive mounting: {e}\");\n                            }\n                        }\n                        \"rdev\" => {\n                            let rest =\n                                utils::test_device_access(null_device_path.to_str().unwrap());\n                            if let Err(e) = rest {\n                                eprintln!(\"error in testing rdev recursive mounting: {e}\");\n                            }\n                        }\n                        \"rnodev\" => {\n                            let rest =\n                                utils::test_device_access(null_device_path.to_str().unwrap());\n                            if rest.is_ok() {\n                                // because /mnt/mount_subdir/null device not access,so rest is err\n                                eprintln!(\"error in testing rnodev recursive mounting\");\n                            }\n                        }\n                        \"rrelatime\" => {\n                            if let Err(e) =\n                                utils::test_mount_releatime_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rrelatime, found not rrelatime, error: {e}\"\n                                );\n                            }\n                        }\n                        \"rnorelatime\" => {\n                            if let Err(e) =\n                                utils::test_mount_norelatime_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rnorelatime, found not rnorelatime, error: {e}\"\n                                );\n                            }\n                        }\n                        \"rnoatime\" => {\n                            if let Err(e) =\n                                utils::test_mount_rnoatime_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rnoatime, found not rnoatime, error: {e}\"\n                                );\n                            }\n                        }\n                        \"rstrictatime\" => {\n                            if let Err(e) =\n                                utils::test_mount_rstrictatime_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rstrictatime, found not rstrictatime, error: {e}\"\n                                );\n                            }\n                        }\n                        \"rnosymfollow\" => {\n                            if let Err(e) =\n                                utils::test_mount_rnosymfollow_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rnosymfollow, found not rnosymfollow, error: {e}\"\n                                );\n                            }\n                        }\n                        \"rsymfollow\" => {\n                            if let Err(e) =\n                                utils::test_mount_rsymfollow_option(subdir_path.to_str().unwrap())\n                            {\n                                eprintln!(\n                                    \"path expected to be rsymfollow, found not rsymfollow, error: {e}\"\n                                );\n                            }\n                        }\n                        _ => {}\n                    }\n                }\n            }\n        }\n    }\n}\n\npub fn validate_mounts_recursive_rbind_ro() {\n    let path = Path::new(\"/mnt\").join(\"bar\");\n    if matches!(test_write_access(path.to_str().unwrap()), Ok(())) {\n        eprintln!(\n            \"in readonly paths, path expected to not be writable, found writable: {}\",\n            path.display()\n        );\n        return;\n    }\n\n    let sub_path = Path::new(\"/mnt/mount_subdir\").join(\"bar\");\n    if let Err(e) = test_write_access(sub_path.to_str().unwrap()) {\n        eprintln!(\n            \"subpath expected to be writable, found read-only: {} err: {}\",\n            sub_path.display(),\n            e\n        );\n    }\n}\n\npub fn validate_seccomp(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    if linux.seccomp().is_some() {\n        if let Err(errno) = getcwd() {\n            if errno != Errno::EPERM {\n                eprintln!(\n                    \"'getcwd()' failed with unexpected error code '{errno}', expected  'EPERM'\"\n                );\n            }\n        } else {\n            eprintln!(\n                \"'getcwd()' syscall succeeded. It was expected to fail due to seccomp policies.\"\n            );\n        }\n    }\n}\n\npub fn validate_sysctl(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    if let Some(expected_linux_sysctl) = linux.sysctl() {\n        for (key, expected_value) in expected_linux_sysctl {\n            let key_path = Path::new(\"/proc/sys\").join(key.replace('.', \"/\"));\n            let actual_value = match fs::read(&key_path) {\n                Ok(actual_value_bytes) => String::from_utf8_lossy(&actual_value_bytes)\n                    .trim()\n                    .to_string(),\n                Err(e) => {\n                    return eprintln!(\n                        \"error due to fail to read the file {key_path:?}, error: {e}\"\n                    );\n                }\n            };\n            if &actual_value != expected_value {\n                eprintln!(\n                    \"Unexpected kernel parameter, expected: {expected_value} found: {actual_value}\"\n                );\n            }\n        }\n    }\n}\n\npub fn validate_scheduler_policy(spec: &Spec) {\n    let proc = spec.process().as_ref().unwrap();\n    let sc = proc.scheduler().as_ref().unwrap();\n    let mut get_sched_attr = nc::sched_attr_t {\n        size: 0,\n        sched_policy: 0,\n        sched_flags: 0,\n        sched_nice: 0,\n        sched_priority: 0,\n        sched_runtime: 0,\n        sched_deadline: 0,\n        sched_period: 0,\n        sched_util_min: 0,\n        sched_util_max: 0,\n    };\n    unsafe {\n        match nc::sched_getattr(0, &mut get_sched_attr, 0) {\n            Ok(_) => {}\n            Err(e) => {\n                return eprintln!(\"error due to fail to get sched attr error: {e}\");\n            }\n        };\n    }\n    let sp = get_sched_attr.sched_policy;\n    let want_sp: u32 = match *sc.policy() {\n        LinuxSchedulerPolicy::SchedOther => 0,\n        LinuxSchedulerPolicy::SchedFifo => 1,\n        LinuxSchedulerPolicy::SchedRr => 2,\n        LinuxSchedulerPolicy::SchedBatch => 3,\n        LinuxSchedulerPolicy::SchedIso => 4,\n        LinuxSchedulerPolicy::SchedIdle => 5,\n        LinuxSchedulerPolicy::SchedDeadline => 6,\n    };\n    if sp != want_sp {\n        return eprintln!(\"error due to sched_policy want {want_sp}, got {sp}\");\n    }\n    let sn = get_sched_attr.sched_nice;\n    let want_sn = sc.nice().unwrap();\n    if sn != want_sn {\n        eprintln!(\"error due to sched_nice want {want_sn}, got {sn}\")\n    }\n}\n\npub fn validate_devices(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    if let Some(devices) = linux.devices() {\n        for (i, device) in devices.iter().enumerate() {\n            validate_device(\n                device,\n                &format!(\n                    \"{} (linux.devices[{}])\",\n                    device.path().as_path().to_str().unwrap(),\n                    i\n                ),\n            );\n        }\n    }\n}\n\nfn validate_device(device: &LinuxDevice, description: &str) {\n    let file_data = match fs::metadata(device.path()) {\n        Ok(data) => data,\n        Err(e) => {\n            if e.kind() == std::io::ErrorKind::NotFound {\n                eprintln!(\n                    \"error due to device not being present in path: {:?}\",\n                    device.path()\n                );\n            } else {\n                eprintln!(\n                    \"error due to fail to get metadata for device path {:?}, error: {}\",\n                    device.path(),\n                    e\n                );\n            }\n            return;\n        }\n    };\n\n    let mut expected_type = device.typ();\n    if expected_type == LinuxDeviceType::U {\n        expected_type = LinuxDeviceType::C;\n    }\n\n    let file_type = file_data.file_type();\n    let actual_type = if file_type.is_char_device() {\n        LinuxDeviceType::C\n    } else if file_type.is_block_device() {\n        LinuxDeviceType::B\n    } else if file_type.is_fifo() {\n        LinuxDeviceType::P\n    } else {\n        LinuxDeviceType::U\n    };\n\n    if actual_type != expected_type {\n        eprintln!(\"error due to device type want {expected_type:?}, got {actual_type:?}\");\n    }\n\n    if actual_type != LinuxDeviceType::P {\n        let dev = file_data.st_rdev();\n        let major = (dev >> 8) & 0xfff;\n        let minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);\n        if major != device.major() as u64 {\n            eprintln!(\n                \"error due to device major want {}, got {}\",\n                device.major(),\n                major\n            );\n        }\n        if minor != device.minor() as u64 {\n            eprintln!(\n                \"error due to device minor want {}, got {}\",\n                device.minor(),\n                minor\n            );\n        }\n    }\n\n    let expected_permissions = device.file_mode().unwrap_or(0o666);\n    let actual_permissions = file_data.permissions().mode() & 0o777;\n    if actual_permissions != expected_permissions {\n        eprintln!(\n            \"error due to device file mode want {expected_permissions:?}, got {actual_permissions:?}\"\n        );\n    }\n\n    if description == \"/dev/console (default device)\" {\n        eprintln!(\"we need the major/minor from the controlling TTY\");\n    }\n\n    if let Some(expected_uid) = device.uid()\n        && file_data.st_uid() != expected_uid\n    {\n        eprintln!(\n            \"error due to device uid want {}, got {}\",\n            expected_uid,\n            file_data.st_uid()\n        );\n    }\n\n    if let Some(expected_gid) = device.gid()\n        && file_data.st_gid() != expected_gid\n    {\n        eprintln!(\n            \"error due to device gid want {}, got {}\",\n            expected_gid,\n            file_data.st_gid()\n        );\n    }\n}\n\npub fn test_io_priority_class(spec: &Spec, io_priority_class: IOPriorityClass) {\n    let io_priority_spec = spec\n        .process()\n        .as_ref()\n        .unwrap()\n        .io_priority()\n        .as_ref()\n        .unwrap();\n    if io_priority_spec.class() != io_priority_class {\n        let io_class = io_priority_spec.class();\n        return eprintln!(\"error io_priority class want {io_priority_class:?}, got {io_class:?}\");\n    }\n\n    let io_priority_who_progress: libc::c_int = 1;\n    let io_priority_who_pid = 0;\n    let res = unsafe {\n        libc::syscall(\n            libc::SYS_ioprio_get,\n            io_priority_who_progress,\n            io_priority_who_pid,\n        )\n    };\n    if let Err(e) = Errno::result(res) {\n        return eprintln!(\"error ioprio_get error {e}\");\n    }\n\n    // ref: https://docs.kernel.org/block/ioprio.html\n    let class = res as u16 >> 13;\n    let priority = res as u16 & 0xFF;\n\n    let expected_class = match io_priority_class {\n        IoprioClassRt => 1,\n        IoprioClassBe => 2,\n        IoprioClassIdle => 3,\n    };\n    if class != expected_class {\n        return eprintln!(\n            \"error ioprio_get class expected {io_priority_class:?} ({expected_class}), got {class}\"\n        );\n    }\n\n    // these number mappings are arbitrary, we set the priority in test cases io_priority_test.rs file\n    let expected_priority = match io_priority_class {\n        IoprioClassRt => 1,\n        IoprioClassBe => 2,\n        IoprioClassIdle => 3,\n    };\n    if priority != expected_priority {\n        eprintln!(\"error ioprio_get expected priority {expected_priority:?}, got {priority}\")\n    }\n}\n\nfn parse_node_string(nodes: &str) -> Vec<u32> {\n    let mut out = Vec::new();\n    let s = nodes.trim();\n    if s.is_empty() {\n        return out;\n    }\n    for part in s.split(',') {\n        let p = part.trim();\n        if p.is_empty() {\n            continue;\n        }\n        if let Some(dash) = p.find('-') {\n            let start = p[..dash].trim().parse::<u32>();\n            let end = p[dash + 1..].trim().parse::<u32>();\n            match (start, end) {\n                (Ok(a), Ok(b)) if a <= b => {\n                    for n in a..=b {\n                        out.push(n);\n                    }\n                }\n                _ => {\n                    // invalid token, ignore\n                }\n            }\n        } else if let Ok(n) = p.parse::<u32>() {\n            out.push(n);\n        }\n    }\n    out\n}\n\nfn mems_allowed_list() -> Option<Vec<u32>> {\n    let s = std::fs::read_to_string(\"/proc/self/status\").ok()?;\n    let line = s.lines().find(|l| l.starts_with(\"Mems_allowed_list:\"))?;\n    let list = line.split_once(':')?.1.trim();\n    let mut nodes = parse_node_string(list);\n    nodes.sort_unstable();\n    nodes.dedup();\n    Some(nodes)\n}\n\nstruct ExpectedNodeSets {\n    physical_from_spec: Vec<u32>,\n    effective_allowed: Vec<u32>,\n}\n\nfn expected_node_sets(spec: &Spec) -> Option<ExpectedNodeSets> {\n    let linux = spec.linux().as_ref()?;\n    let mp = linux.memory_policy().as_ref()?;\n    let nodes_spec = mp.nodes().as_deref().unwrap_or(\"\").trim();\n    let nodes = parse_node_string(nodes_spec);\n\n    let mut relative = false;\n    let mut _static = false;\n    if let Some(flags) = mp.flags() {\n        for f in flags {\n            match f {\n                MpolFRelativeNodes => relative = true,\n                MpolFStaticNodes => _static = true,\n                _ => {}\n            }\n        }\n    }\n\n    let mems = mems_allowed_list().unwrap_or_default();\n\n    let physical_from_spec: Vec<u32> = if relative {\n        nodes\n            .into_iter()\n            .filter_map(|i| mems.get(i as usize).copied())\n            .collect()\n    } else {\n        nodes\n    };\n\n    let mut effective: Vec<u32> = physical_from_spec\n        .iter()\n        .copied()\n        .filter(|n| mems.contains(n))\n        .collect();\n\n    effective.sort_unstable();\n    effective.dedup();\n\n    Some(ExpectedNodeSets {\n        physical_from_spec,\n        effective_allowed: effective,\n    })\n}\n\nfn nodes_in_numa_maps_policy(policy_field: &str) -> Vec<u32> {\n    if let Some((_, nodes_spec)) = policy_field.rsplit_once(':') {\n        let mut v = parse_node_string(nodes_spec);\n        v.sort_unstable();\n        v.dedup();\n        return v;\n    }\n    Vec::new()\n}\n\npub fn validate_memory_policy(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    let memory_policy = linux.memory_policy();\n    let expected_mode = memory_policy.as_ref().map(|p| p.mode());\n\n    let numa_maps_content = match fs::read_to_string(\"/proc/self/numa_maps\") {\n        Ok(content) => content,\n        Err(e) => {\n            eprintln!(\"failed to read /proc/self/numa_maps: {}\", e);\n            return;\n        }\n    };\n\n    let policy_entries: Vec<(&str, &str)> = numa_maps_content\n        .lines()\n        .filter_map(|line| {\n            if line.trim().is_empty() {\n                return None;\n            }\n            let mut parts = line.split_whitespace();\n            parts.next()?;\n            let policy_field = parts.next()?;\n            Some((policy_field, line))\n        })\n        .collect();\n\n    if policy_entries.is_empty() {\n        eprintln!(\"no parsable entries found in /proc/self/numa_maps\");\n        return;\n    }\n\n    let fallback_entry = policy_entries[0];\n    let default_policy_field = fallback_entry.0;\n    let find_with_substring = |needle: &str| -> (&str, &str) {\n        policy_entries\n            .iter()\n            .copied()\n            .find(|(policy, _)| policy.contains(needle))\n            .unwrap_or(fallback_entry)\n    };\n\n    let (nodes_is_empty, has_static_flag, has_relative_flag) = if let Some(p) = memory_policy {\n        let nodes_is_empty = p.nodes().as_ref().is_none_or(|n| n.trim().is_empty());\n        let mut has_static = false;\n        let mut has_relative = false;\n        if let Some(flags) = p.flags() {\n            for f in flags {\n                match f {\n                    MpolFStaticNodes => has_static = true,\n                    MpolFRelativeNodes => has_relative = true,\n                    _ => {}\n                }\n            }\n        }\n        (nodes_is_empty, has_static, has_relative)\n    } else {\n        (true, false, false)\n    };\n\n    match expected_mode {\n        Some(MemoryPolicyModeType::MpolDefault) => {\n            let (policy_field, _) = find_with_substring(\"default\");\n            if !policy_field.contains(\"default\") {\n                eprintln!(\"expected default policy, but found: {}\", policy_field);\n            }\n        }\n        Some(MemoryPolicyModeType::MpolInterleave) => {\n            let (policy_field, full_line) = find_with_substring(\"interleave\");\n            if !policy_field.contains(\"interleave\") {\n                eprintln!(\"expected interleave policy, but found: {}\", policy_field);\n            }\n            if let Some(expect) = expected_node_sets(spec) {\n                let got_nodes = nodes_in_numa_maps_policy(policy_field);\n                if got_nodes != expect.effective_allowed {\n                    eprintln!(\n                        \"expected interleave nodes {:?}, got {:?} (line: {})\",\n                        expect.effective_allowed, got_nodes, full_line\n                    );\n                }\n            }\n        }\n        Some(MemoryPolicyModeType::MpolBind) => {\n            let (policy_field, full_line) = find_with_substring(\"bind\");\n            if !policy_field.contains(\"bind\") {\n                eprintln!(\"expected bind policy, but found: {}\", policy_field);\n            }\n            if has_static_flag && !policy_field.contains(\"static\") {\n                eprintln!(\"expected bind static, but found: {}\", policy_field);\n            }\n            if let Some(expect) = expected_node_sets(spec) {\n                let got_nodes = nodes_in_numa_maps_policy(policy_field);\n                if got_nodes != expect.effective_allowed {\n                    eprintln!(\n                        \"expected bind nodes {:?}, got {:?} (line: {})\",\n                        expect.effective_allowed, got_nodes, full_line\n                    );\n                }\n            }\n        }\n        Some(MemoryPolicyModeType::MpolPreferred) => {\n            if nodes_is_empty {\n                let (policy_field, _) = find_with_substring(\"local\");\n                if !policy_field.contains(\"local\") {\n                    eprintln!(\n                        \"expected preferred(empty)->local, but found: {}\",\n                        policy_field\n                    );\n                }\n            } else {\n                let (policy_field, full_line) = find_with_substring(\"prefer\");\n                if let Some(expect) = expected_node_sets(spec) {\n                    let prefer = expect.physical_from_spec.first().copied();\n                    let mems = mems_allowed_list().unwrap_or_default();\n                    if let Some(prefer_node) = prefer {\n                        if mems.contains(&prefer_node) {\n                            let got_nodes = nodes_in_numa_maps_policy(policy_field);\n                            if !(policy_field.contains(\"prefer\") && got_nodes == vec![prefer_node])\n                            {\n                                eprintln!(\n                                    \"expected prefer {} within mems_allowed, got {} (line: {})\",\n                                    prefer_node, policy_field, full_line\n                                );\n                            }\n                        } else if !policy_field.contains(\"local\") {\n                            eprintln!(\n                                \"expected local fallback (preferred disallowed), got {} (line: {})\",\n                                policy_field, full_line\n                            );\n                        }\n                    }\n                } else if !policy_field.contains(\"prefer\") {\n                    eprintln!(\"expected preferred policy, but found: {}\", policy_field);\n                }\n                if has_relative_flag && !policy_field.contains(\"relative\") {\n                    eprintln!(\"expected preferred relative, but found: {}\", policy_field);\n                }\n            }\n        }\n        Some(MemoryPolicyModeType::MpolLocal) => {\n            let (policy_field, _) = find_with_substring(\"local\");\n            if !policy_field.contains(\"local\") {\n                eprintln!(\"expected local policy, but found: {}\", policy_field);\n            }\n        }\n        Some(_) => {\n            println!(\n                \"memory policy {} applied (non-strict check)\",\n                default_policy_field\n            );\n        }\n        None => {\n            if !policy_entries.iter().any(|(policy_field, _)| {\n                policy_field.contains(\"default\") || policy_field.contains(\"local\")\n            }) {\n                eprintln!(\n                    \"expected default/local with no expected policy, got: {}\",\n                    default_policy_field\n                );\n            }\n        }\n    }\n}\n\npub fn test_validate_root_readonly(spec: &Spec) {\n    let root = spec.root().as_ref().unwrap();\n    if root.readonly().unwrap() {\n        if let Err(e) = test_dir_write_access(\"/\") {\n            let errno = Errno::from_raw(e.raw_os_error().unwrap());\n            if errno == Errno::EROFS {\n                /* This is expected */\n            } else {\n                eprintln!(\n                    \"readonly root filesystem, error in testing write access for path /, error: {}\",\n                    errno\n                );\n            }\n        }\n        if let Err(e) = test_dir_read_access(\"/\") {\n            eprintln!(\n                \"readonly root filesystem, but error in testing read access for path /, error: {}\",\n                e\n            );\n        }\n    } else {\n        if let Err(e) = test_dir_write_access(\"/\") {\n            eprintln!(\n                \"readonly root filesystem is false, but error in testing write access for path /, error: {}\",\n                e\n            );\n        }\n        if let Err(e) = test_dir_read_access(\"/\") {\n            eprintln!(\n                \"readonly root filesystem is false, but error in testing read access for path /, error: {}\",\n                e\n            );\n        }\n    }\n}\n\npub fn validate_process(spec: &Spec) {\n    let process = spec.process().as_ref().unwrap();\n    let expected_cwd = process.cwd();\n    let cwd = &getcwd().unwrap();\n\n    if expected_cwd != cwd {\n        eprintln!(\n            \"error due to spec cwd want {:?}, got {:?}\",\n            expected_cwd, cwd\n        )\n    }\n\n    for env_str in process.env().as_ref().unwrap().iter() {\n        match env_str.split_once(\"=\") {\n            Some((env_key, expected_val)) => {\n                let actual_val = env::var(env_key).unwrap();\n                if actual_val != expected_val {\n                    eprintln!(\n                        \"error due to spec environment value of {:?} want {:?}, got {:?}\",\n                        env_key, expected_val, actual_val\n                    )\n                }\n            }\n            None => {\n                eprintln!(\n                    \"spec env value is not correct : expected key=value format, got {env_str}\"\n                )\n            }\n        }\n    }\n}\n\npub fn validate_process_user(spec: &Spec) {\n    let process = spec.process().as_ref().unwrap();\n    let expected_uid = Uid::from(process.user().uid());\n    let expected_gid = Gid::from(process.user().gid());\n    let expected_umask = Mode::from_bits(process.user().umask().unwrap()).unwrap();\n\n    let uid = getuid();\n    let gid = getgid();\n    // The umask function not only gets the current mask, but also has the ability to set a new mask,\n    // so we need to set it back after getting the latest value.\n    let current_umask = umask(nix::sys::stat::Mode::empty());\n    umask(current_umask);\n\n    if expected_uid != uid {\n        eprintln!(\"error due to uid want {}, got {}\", expected_uid, uid)\n    }\n\n    if expected_gid != gid {\n        eprintln!(\"error due to gid want {}, got {}\", expected_gid, gid)\n    }\n\n    if let Err(e) = validate_additional_gids(process.user().additional_gids().as_ref().unwrap()) {\n        eprintln!(\"error additional gids {e}\");\n    }\n\n    if expected_umask != current_umask {\n        eprintln!(\n            \"error due to umask want {:?}, got {:?}\",\n            expected_umask, current_umask\n        )\n    }\n}\n\n// validate_additional_gids function is used to validate additional groups of user\nfn validate_additional_gids(expected_gids: &Vec<u32>) -> Result<()> {\n    let current_gids = getgroups().unwrap();\n\n    if expected_gids.len() != current_gids.len() {\n        bail!(\n            \"error : additional group mismatch, want {:?}, got {:?}\",\n            expected_gids,\n            current_gids\n        );\n    }\n\n    for gid in expected_gids {\n        if !current_gids.contains(&Gid::from_raw(*gid)) {\n            bail!(\n                \"error : additional gid {} is not in current groups, expected {:?}, got {:?}\",\n                gid,\n                expected_gids,\n                current_gids\n            );\n        }\n    }\n\n    Ok(())\n}\n\npub fn validate_process_rlimits(spec: &Spec) {\n    let process = spec.process().as_ref().unwrap();\n    let spec_rlimits: &Vec<PosixRlimit> = process.rlimits().as_ref().unwrap();\n\n    for spec_rlimit in spec_rlimits.iter() {\n        let (soft_limit, hard_limit) = getrlimit(change_resource_type(spec_rlimit.typ())).unwrap();\n        if spec_rlimit.hard() != hard_limit {\n            eprintln!(\n                \"error type of {:?} hard rlimit expected {:?} , got {:?}\",\n                spec_rlimit.typ(),\n                spec_rlimit.hard(),\n                hard_limit\n            )\n        }\n\n        if spec_rlimit.soft() != soft_limit {\n            eprintln!(\n                \"error type of {:?} soft rlimit expected {:?} , got {:?}\",\n                spec_rlimit.typ(),\n                spec_rlimit.soft(),\n                soft_limit\n            )\n        }\n    }\n}\n\nfn change_resource_type(resource_type: PosixRlimitType) -> Resource {\n    match resource_type {\n        PosixRlimitType::RlimitCpu => Resource::RLIMIT_CPU,\n        PosixRlimitType::RlimitFsize => Resource::RLIMIT_FSIZE,\n        PosixRlimitType::RlimitData => Resource::RLIMIT_DATA,\n        PosixRlimitType::RlimitStack => Resource::RLIMIT_STACK,\n        PosixRlimitType::RlimitCore => Resource::RLIMIT_CORE,\n        PosixRlimitType::RlimitRss => Resource::RLIMIT_RSS,\n        PosixRlimitType::RlimitNproc => Resource::RLIMIT_NPROC,\n        PosixRlimitType::RlimitNofile => Resource::RLIMIT_NOFILE,\n        PosixRlimitType::RlimitMemlock => Resource::RLIMIT_MEMLOCK,\n        PosixRlimitType::RlimitAs => Resource::RLIMIT_AS,\n        PosixRlimitType::RlimitLocks => Resource::RLIMIT_LOCKS,\n        PosixRlimitType::RlimitSigpending => Resource::RLIMIT_SIGPENDING,\n        PosixRlimitType::RlimitMsgqueue => Resource::RLIMIT_MSGQUEUE,\n        PosixRlimitType::RlimitNice => Resource::RLIMIT_NICE,\n        PosixRlimitType::RlimitRtprio => Resource::RLIMIT_RTPRIO,\n        PosixRlimitType::RlimitRttime => Resource::RLIMIT_RTTIME,\n    }\n}\n\n// the validate_rootfs function is used to validate the rootfs of the container is\n// as expected. This function is used in the no_pivot test to validate the rootfs\npub fn validate_rootfs() {\n    // list the first level directories in the rootfs\n    let mut entries = fs::read_dir(\"/\")\n        .unwrap()\n        .filter_map(|entry| {\n            entry.ok().and_then(|e| {\n                let path = e.path();\n                if path.is_dir() {\n                    path.file_name()\n                        .and_then(|name| name.to_str().map(|s| s.to_owned()))\n                } else {\n                    None\n                }\n            })\n        })\n        .collect::<Vec<String>>();\n    // sort the entries to make the test deterministic\n    entries.sort();\n\n    // this is the list of directories that we expect to find in the rootfs\n    let mut expected = vec![\n        \"bin\", \"dev\", \"etc\", \"home\", \"proc\", \"root\", \"sys\", \"tmp\", \"usr\", \"var\",\n    ];\n    // sort the expected entries to make the test deterministic\n    expected.sort();\n\n    // compare the expected entries with the actual entries\n    if entries != expected {\n        eprintln!(\"error due to rootfs want {expected:?}, got {entries:?}\");\n    }\n}\n\npub fn validate_process_oom_score_adj(spec: &Spec) {\n    let process = spec.process().as_ref().unwrap();\n    let expected_value = process.oom_score_adj().unwrap();\n\n    let pid = std::process::id();\n    let oom_score_adj_path = format!(\"/proc/{}/oom_score_adj\", pid);\n\n    let actual_value = fs::read_to_string(oom_score_adj_path)\n        .unwrap_or_else(|_| panic!(\"Failed to read file content\"));\n\n    if actual_value.trim() != expected_value.to_string() {\n        eprintln!(\"Unexpected oom_score_adj, expected: {expected_value} found: {actual_value}\");\n    }\n}\n\npub fn validate_fd_control(_spec: &Spec) {\n    // --preserve-fds does not get passed via the spec so we have to communicate information\n    // via the root filesystem\n    let expected_num_fds: usize = fs::read_to_string(\"/num-fds\").unwrap().parse().unwrap();\n\n    let mut entries = vec![];\n    let stdio: &[&OsStr] = &[\"0\".as_ref(), \"1\".as_ref(), \"2\".as_ref()];\n    for entry in fs::read_dir(\"/proc/self/fd\").unwrap() {\n        let entry = entry.unwrap();\n        let name = entry.file_name();\n        if stdio.contains(&name.as_os_str()) {\n            // Ignore stdio\n            continue;\n        }\n        entries.push((entry.path(), fs::read_link(entry.path())))\n    }\n\n    // NOTE: we do this in a separate loop so we can filter out the dirfd used behind\n    // the scenes in 'fs::read_dir'. It is important to *not* store the full DirEntry\n    // type, as that keeps the dirfd open.\n    let mut fd_details = vec![];\n    let mut found_dirfd = false;\n    for (path, linkpath) in &entries {\n        // The difference between metadata.unwrap() and fs::metadata is that the latter\n        // will now try to follow the symlink\n        match fs::metadata(path) {\n            Ok(m) => fd_details.push((path, linkpath, m)),\n            Err(e) if e.kind() == std::io::ErrorKind::NotFound && !found_dirfd => {\n                // Expected for the dirfd\n                found_dirfd = true\n            }\n            Err(e) => {\n                eprintln!(\"unexpected error reading metadata: {}\", e)\n            }\n        }\n    }\n\n    if fd_details.len() != expected_num_fds {\n        eprintln!(\"mismatched fds inside container! {:?}\", fd_details);\n    }\n}\n\npub fn validate_masked_paths(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    let masked_paths = match linux.masked_paths() {\n        Some(p) => p,\n        None => {\n            eprintln!(\"in masked paths, expected some masked paths to be set, found none\");\n            return;\n        }\n    };\n\n    for path in masked_paths.iter().map(Path::new) {\n        if !path.is_absolute() {\n            eprintln!(\"in masked paths, the path must be absolute.\")\n        }\n        match test_read_access(path) {\n            Ok(true) => {\n                eprintln!(\n                    \"in masked paths, expected path {:?} to be masked, but was found readable\",\n                    path.iter().as_path(),\n                );\n                return;\n            }\n            Ok(false) => { /* This is expected */ }\n            Err(e) => {\n                let errno = Errno::from_raw(e.raw_os_error().unwrap());\n                if errno == Errno::ENOENT {\n                    /* This is expected */\n                } else {\n                    eprintln!(\n                        \"in masked paths, error in testing read access for path {:?} : {errno:?}\",\n                        path.iter().as_path(),\n                    );\n                    return;\n                }\n            }\n        }\n    }\n}\n\npub fn validate_rootfs_propagation(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n    let propagation = linux.rootfs_propagation().as_ref().unwrap();\n\n    let target_dir = Builder::new()\n        .prefix(\"target\")\n        .tempdir()\n        .expect(\"create target directory\");\n    let target_path = target_dir.path();\n\n    match propagation.as_str() {\n        \"shared\" | \"slave\" | \"private\" => {\n            if let Err(e) = mount(\n                Some(\"/\"),\n                target_dir.path(),\n                None::<&str>,\n                MsFlags::MS_BIND | MsFlags::MS_REC,\n                None::<&str>,\n            ) {\n                eprintln!(\"bind-mount / {}: {}\", target_dir.path().display(), e);\n            }\n\n            let mount_dir = Builder::new()\n                .prefix(\"mount\")\n                .tempdir()\n                .expect(\"create mount directory\");\n            let test_dir = Builder::new()\n                .prefix(\"test\")\n                .tempdir()\n                .expect(\"create test directory\");\n            let tmpfile_path = test_dir.path().join(\"example\");\n            let _file = File::create(&tmpfile_path).expect(\"create temp file\");\n\n            mount(\n                Some(test_dir.path()),\n                mount_dir.path(),\n                None::<&str>,\n                MsFlags::MS_BIND | MsFlags::MS_REC,\n                None::<&str>,\n            )\n            .map_err(|e| {\n                format!(\n                    \"Failed to bind-mount {} to {}: {}\",\n                    test_dir.path().display(),\n                    mount_dir.path().display(),\n                    e\n                )\n            })\n            .unwrap();\n\n            let target_file = target_path\n                .join(mount_dir.path().strip_prefix(\"/\").unwrap())\n                .join(tmpfile_path.file_name().unwrap());\n            let file_visible = target_file.exists();\n\n            match propagation.as_str() {\n                \"shared\" => {\n                    if !file_visible {\n                        eprintln!(\n                            \"Error: shared root propagation failed to expose {:?}\",\n                            target_file\n                        );\n                    }\n                }\n                \"slave\" | \"private\" => {\n                    if file_visible {\n                        eprintln!(\n                            \"Error: {} root propagation unexpectedly exposed {:?}\",\n                            propagation, target_file\n                        );\n                    }\n                }\n                _ => unreachable!(),\n            }\n        }\n        \"unbindable\" => {\n            if let Err(e) = mount(\n                Some(\"/\"),\n                target_dir.path(),\n                None::<&str>,\n                MsFlags::MS_BIND | MsFlags::MS_REC,\n                None::<&str>,\n            ) && e != nix::errno::Errno::EINVAL\n            {\n                eprintln!(\"Error occurred during mount: {}\", e);\n            }\n        }\n        _ => {\n            eprintln!(\"Unrecognized rootfsPropagation: {}\", propagation);\n        }\n    }\n}\n\nfn validate_id_mappings(expected_id_mappings: &[LinuxIdMapping], path: &str, property: &str) {\n    let file = match File::open(path) {\n        Ok(f) => f,\n        Err(e) => {\n            eprintln!(\"Failed to open {}: {}\", path, e);\n            return;\n        }\n    };\n\n    let reader = io::BufReader::new(file);\n    let lines: Vec<String> = reader.lines().map_while(|line| line.ok()).collect();\n\n    if expected_id_mappings.len() != lines.len() {\n        eprintln!(\n            \"Mismatch in {}: expected {} lines, found {}\",\n            property,\n            expected_id_mappings.len(),\n            lines.len()\n        );\n    }\n\n    for (expected, line) in expected_id_mappings.iter().zip(lines.iter()) {\n        let parts: Vec<&str> = line.split_whitespace().collect();\n\n        // \"man 7 user_namespaces\" explains the format of uid_map and gid_map:\n        // <container_id> <host_id> <map_size>\n        if parts.len() != 3 {\n            eprintln!(\"Unexpected format in {}: {}\", path, line);\n            continue;\n        }\n\n        let actual_container_id = parts.first().unwrap().parse::<u32>().unwrap();\n        let actual_host_id = parts.get(1).unwrap().parse::<u32>().unwrap();\n        let actual_map_size = parts.get(2).unwrap().parse::<u32>().unwrap();\n\n        if !(actual_host_id == expected.host_id()\n            && actual_container_id == expected.container_id()\n            && actual_map_size == expected.size())\n        {\n            eprintln!(\n                \"Unexpected {}, expected: ({} {} {}) found: ({} {} {})\",\n                property,\n                expected.container_id(),\n                expected.host_id(),\n                expected.size(),\n                actual_container_id,\n                actual_host_id,\n                actual_map_size\n            );\n        }\n    }\n}\n\npub fn validate_uid_mappings(spec: &Spec) {\n    let linux = spec.linux().as_ref().unwrap();\n\n    let expected_uid_mappings = linux.uid_mappings().as_ref().unwrap();\n    validate_id_mappings(expected_uid_mappings, \"/proc/self/uid_map\", \"uid_mappings\");\n\n    let expected_gid_mappings = linux.gid_mappings().as_ref().unwrap();\n    validate_id_mappings(expected_gid_mappings, \"/proc/self/gid_map\", \"gid_mappings\");\n}\n\npub fn validate_net_devices(spec: &Spec) {\n    let mut socket = Socket::new(NETLINK_ROUTE).unwrap();\n    socket.bind_auto().unwrap();\n    let linux = spec.linux().as_ref().unwrap();\n    if let Some(net_devices) = linux.net_devices() {\n        for (name, net_device) in net_devices {\n            let net_device_name = net_device\n                .name()\n                .as_ref()\n                .filter(|d| !d.is_empty())\n                .map_or(name.clone(), |d| d.to_string());\n\n            let mut message = LinkMessage::default();\n            message\n                .attributes\n                .push(LinkAttribute::IfName(net_device_name.clone()));\n\n            let mut req = NetlinkMessage::from(RouteNetlinkMessage::GetLink(message));\n            req.header.flags = NLM_F_REQUEST;\n            req.finalize();\n\n            let mut send_buf = vec![0; req.header.length as usize];\n            req.serialize(&mut send_buf[..]);\n            socket.send(&send_buf[..], 0).unwrap();\n\n            let mut receive_buf = vec![0u8; 4096];\n            let n_received = socket.recv(&mut &mut receive_buf[..], 0).unwrap();\n            let bytes = &receive_buf[..n_received];\n            let rx_packet = <NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes).unwrap();\n\n            let index = match rx_packet.payload {\n                NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewLink(link)) => {\n                    println!(\"network device {} is present\", net_device_name);\n                    link.header.index\n                }\n                _ => {\n                    eprintln!(\"network device {} is not present\", net_device_name);\n                    continue;\n                }\n            };\n\n            let mut message = AddressMessage::default();\n            message.header.index = index;\n            let mut req = NetlinkMessage::from(RouteNetlinkMessage::GetAddress(message));\n            req.header.flags = NLM_F_REQUEST | NLM_F_DUMP;\n            req.finalize();\n\n            let mut send_buf = vec![0; req.header.length as usize];\n            req.serialize(&mut send_buf[..]);\n            socket.send(&send_buf[..], 0).unwrap();\n\n            let mut receive_buf = vec![0u8; 4096];\n            let n_received = socket.recv(&mut &mut receive_buf[..], 0).unwrap();\n            let bytes = &receive_buf[..n_received];\n            let rx_packet = <NetlinkMessage<RouteNetlinkMessage>>::deserialize(bytes).unwrap();\n\n            match rx_packet.payload {\n                NetlinkPayload::InnerMessage(RouteNetlinkMessage::NewAddress(_address)) => {\n                    println!(\"address is present for network device {}\", net_device_name);\n                }\n                _ => {\n                    eprintln!(\n                        \"address is not present for network device {}\",\n                        net_device_name\n                    );\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "tests/contest/runtimetest/src/utils.rs",
    "content": "use std::fs;\nuse std::fs::{OpenOptions, symlink_metadata};\nuse std::io::Read;\nuse std::os::unix::prelude::MetadataExt;\nuse std::path::{Path, PathBuf};\nuse std::process::Command;\n\nuse nix::sys::stat::{SFlag, stat};\n\n// It means the file or directory is readable\ntype Readable = bool;\n\nfn test_file_read_access<P: AsRef<Path>>(path: P) -> Result<Readable, std::io::Error> {\n    let mut file = OpenOptions::new().create(false).read(true).open(path)?;\n\n    // Create a buffer with a capacity of 1 byte\n    let mut buffer = [0u8; 1];\n    match file.read(&mut buffer) {\n        // Our contest tests only use non-empty files for read-access\n        // tests. So if we get an EOF on the first read or zero bytes, the runtime did\n        // successfully block readability.\n        Ok(0) => Ok(false),\n        Ok(_) => Ok(true),\n        Err(e) => Err(e),\n    }\n}\n\npub fn test_dir_read_access<P: AsRef<Path>>(path: P) -> Result<Readable, std::io::Error> {\n    let entries = std::fs::read_dir(path);\n\n    match entries {\n        Ok(mut entries_iter) => {\n            // Get the first entry\n            match entries_iter.next() {\n                Some(entry) => {\n                    match entry {\n                        Ok(_) => Ok(true),   // If the entry is Ok, then it's readable\n                        Err(_) => Ok(false), // If the entry is Err, then it's not readable\n                    }\n                }\n                None => Ok(false), // If there's an error, then it's not readable, or otherwise, it may indicate different conditions.\n            }\n        }\n        Err(e) => Err(e),\n    }\n}\n\nfn is_file_like(mode: u32) -> bool {\n    // for this please refer\n    // https://stackoverflow.com/questions/40163270/what-is-s-isreg-and-what-does-it-do\n    // https://linux.die.net/man/2/stat\n    mode & SFlag::S_IFREG.bits() != 0 || mode & SFlag::S_IFCHR.bits() != 0\n}\n\nfn is_dir(mode: u32) -> bool {\n    mode & SFlag::S_IFDIR.bits() != 0\n}\n\npub fn test_read_access<P: AsRef<Path>>(path: P) -> Result<Readable, std::io::Error> {\n    let path_ref = path.as_ref();\n    let fstat = stat(path_ref)?;\n    let mode = fstat.st_mode;\n    if is_file_like(mode) {\n        // we have a file or a char/block device\n        return test_file_read_access(path);\n    } else if is_dir(mode) {\n        return test_dir_read_access(path);\n    }\n\n    Err(std::io::Error::other(format!(\n        \"cannot test read access for {:?}, has mode {mode:x}\",\n        path_ref\n    )))\n}\n\nfn test_file_write_access(path: &str) -> Result<(), std::io::Error> {\n    let _ = std::fs::OpenOptions::new().write(true).open(path)?;\n    Ok(())\n}\n\npub fn test_dir_write_access(path: &str) -> Result<(), std::io::Error> {\n    let _ = std::fs::OpenOptions::new()\n        .create(true)\n        .truncate(true)\n        .write(true)\n        .open(PathBuf::from(path).join(\"test.txt\"))?;\n    Ok(())\n}\n\npub fn test_write_access(path: &str) -> Result<(), std::io::Error> {\n    let fstat = stat(path)?;\n    let mode = fstat.st_mode;\n    if is_file_like(mode) {\n        // we have a file or a char/block device\n        return test_file_write_access(path);\n    } else if is_dir(mode) {\n        return test_dir_write_access(path);\n    }\n\n    Err(std::io::Error::other(format!(\n        \"cannot test write access for {path:?}, has mode {mode:x}\"\n    )))\n}\n\npub fn test_file_executable(path: &str) -> Result<(), std::io::Error> {\n    let fstat = stat(path)?;\n    let mode = fstat.st_mode;\n    if is_file_like(mode) {\n        Command::new(path).output()?;\n        return Ok(());\n    }\n\n    Err(std::io::Error::other(format!(\n        \"{path:?} is directory, so cannot execute\"\n    )))\n}\n\npub fn test_dir_update_access_time(path: &str) -> Result<(), std::io::Error> {\n    let metadata = fs::metadata(PathBuf::from(path))?;\n    let rest = metadata.accessed();\n    let first_access_time = rest.unwrap();\n    // execute ls command to update access time\n    Command::new(\"ls\")\n        .arg(path)\n        .output()\n        .expect(\"execute ls command error\");\n    // second get access time\n    let metadata = fs::metadata(PathBuf::from(path))?;\n    let rest = metadata.accessed();\n    let second_access_time = rest.unwrap();\n    if first_access_time == second_access_time {\n        return Err(std::io::Error::other(format!(\n            \"cannot update access time for path {path:?}\"\n        )));\n    }\n    Ok(())\n}\n\npub fn test_dir_not_update_access_time(path: &str) -> Result<(), std::io::Error> {\n    let metadata = fs::metadata(PathBuf::from(path))?;\n    let rest = metadata.accessed();\n    let first_access_time = rest.unwrap();\n    // execute ls command to update access time\n    Command::new(\"ls\")\n        .arg(path)\n        .output()\n        .expect(\"execute ls command error\");\n    // second get access time\n    let metadata = fs::metadata(PathBuf::from(path))?;\n    let rest = metadata.accessed();\n    let second_access_time = rest.unwrap();\n    if first_access_time != second_access_time {\n        return Err(std::io::Error::other(format!(\n            \"cannot update access time for path {path:?}\"\n        )));\n    }\n    Ok(())\n}\n\npub fn test_device_access(path: &str) -> Result<(), std::io::Error> {\n    OpenOptions::new().read(true).open(path)?;\n    Ok(())\n}\n\n// https://man7.org/linux/man-pages/man2/mount_setattr.2.html\n// When a file is accessed via this mount, update the\n// file's last access time (atime) only if the current\n// value of atime is less than or equal to the file's\n// last modification time (mtime) or last status\n// change time (ctime).\n// case:\n// 1. create test.txt file, get one atime\n// 2. cat a.txt, get two atime; check atime whether update, conditions are met atime less than or equal mtime or ctime\n// 3. cat a.txt, get three atime, check now two atime whether equal three atime\npub fn test_mount_releatime_option(path: &str) -> Result<(), std::io::Error> {\n    let test_file_path = PathBuf::from(path).join(\"test.txt\");\n    Command::new(\"touch\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()?;\n    let one_metadata = fs::metadata(test_file_path.clone())?;\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n\n    // execute cat command to update access time\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let two_metadata = fs::metadata(test_file_path.clone())?;\n\n    if one_metadata.atime() == two_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"not update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n\n    // execute cat command to update access time\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let three_metadata = fs::metadata(test_file_path.clone())?;\n    if two_metadata.atime() != three_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n\n    Ok(())\n}\n\n// case: because filesystem having relatime option\n// 1. create test.txt file, get one atime\n// 2. cat a.txt, get two atime; check atime whether update\n// 3. cat a.txt, get three atime, check now two atime whether equal three atime\npub fn test_mount_norelatime_option(path: &str) -> Result<(), std::io::Error> {\n    let test_file_path = PathBuf::from(path).join(\"noreleatime.txt\");\n    Command::new(\"touch\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()?;\n    let one_metadata = fs::metadata(test_file_path.clone())?;\n\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    // execute cat command to update access time\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let two_metadata = fs::metadata(test_file_path.clone())?;\n\n    if one_metadata.atime() == two_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"not update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n\n    // execute cat command to update access time\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let three_metadata = fs::metadata(test_file_path.clone())?;\n\n    if two_metadata.atime() == three_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"not update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n    Ok(())\n}\n\n// Do not update access times for (all types of) files on this mount.\n// case:\n// 1. touch rnoatime.txt file, get atime\n// 2. cat rnoatime.txt, check atime whether update, if update return error, else return Ok\npub fn test_mount_rnoatime_option(path: &str) -> Result<(), std::io::Error> {\n    let test_file_path = PathBuf::from(path).join(\"rnoatime.txt\");\n    Command::new(\"touch\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()?;\n    let one_metadata = fs::metadata(test_file_path.clone())?;\n\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n\n    // execute cat command to update access time\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let two_metadata = fs::metadata(test_file_path.clone())?;\n\n    if one_metadata.atime() != two_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"update access time for file {:?}, expected not update\",\n            test_file_path.to_str()\n        )));\n    }\n    Ok(())\n}\n\n// Always update the last access time (atime) when files are accessed on this mount.\npub fn test_mount_rstrictatime_option(path: &str) -> Result<(), std::io::Error> {\n    let test_file_path = PathBuf::from(path).join(\"rstrictatime.txt\");\n    Command::new(\"touch\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()?;\n    let one_metadata = fs::metadata(test_file_path.clone())?;\n\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    // execute cat command to update access time\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let two_metadata = fs::metadata(test_file_path.clone())?;\n\n    if one_metadata.atime() == two_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"not update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n\n    // execute cat command to update access time\n    std::thread::sleep(std::time::Duration::from_millis(1000));\n    Command::new(\"cat\")\n        .arg(test_file_path.to_str().unwrap())\n        .output()\n        .expect(\"execute cat command error\");\n    let three_metadata = fs::metadata(test_file_path.clone())?;\n\n    if two_metadata.atime() == three_metadata.atime() {\n        return Err(std::io::Error::other(format!(\n            \"not update access time for file {:?}\",\n            test_file_path.to_str()\n        )));\n    }\n    Ok(())\n}\n\npub fn test_mount_rnosymfollow_option(dir: &str) -> Result<(), std::io::Error> {\n    let link = format!(\"{}/link\", dir);\n\n    let md = symlink_metadata(&link)?;\n    if !md.file_type().is_symlink() {\n        return Err(std::io::Error::other(\"link is not a symlink\"));\n    }\n\n    match fs::metadata(&link) {\n        Ok(_) => Err(std::io::Error::other(\n            \"expected ELOOP (nosymfollow), but symlink was followed\",\n        )),\n        Err(e) if e.raw_os_error() == Some(libc::ELOOP) => Ok(()),\n        Err(e) => Err(std::io::Error::other(format!(\n            \"expected ELOOP, but got: {e}\"\n        ))),\n    }\n}\n\npub fn test_mount_rsymfollow_option(dir: &str) -> Result<(), std::io::Error> {\n    let link = format!(\"{}/link\", dir);\n\n    let md = symlink_metadata(&link)?;\n    if !md.file_type().is_symlink() {\n        return Err(std::io::Error::other(\"link is not a symlink\"));\n    }\n\n    match fs::metadata(&link) {\n        Ok(_) => Ok(()),\n        Err(e) if e.raw_os_error() == Some(libc::ELOOP) => {\n            Err(std::io::Error::other(format!(\"unexpected ELOOP: {e}\")))\n        }\n        // Any error other than ELOOP indicates that nosymfollow is not being enforced, so we consider the result OK.\n        Err(_) => Ok(()),\n    }\n}\n"
  },
  {
    "path": "tests/contest/test_framework/Cargo.toml",
    "content": "[package]\nname = \"test_framework\"\nversion = \"0.0.1\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\nanyhow = \"1.0.102\"\ncrossbeam = \"0.8.4\"\nnum_cpus = \"1.17\"\n"
  },
  {
    "path": "tests/contest/test_framework/README.md",
    "content": "# Test Framework for the Integration test\n\nThis is a simple test framework which provides various structs to setup and run tests.\n\n## Docs\n\nOne important thing to note here, is that all structs provided by default, TestGroup and TestManager run the individual test cases, and test groups , respectively, in parallel. Also the default Test, ConditionalTest and TestGroup structs are meant for stateless tests. For stateful tests, or for tests which need to be run in serial, please implement respective traits on custom structs.\n\nThis crate provides following things.\n\n#### TestResult\n\nA Simple enum, similar to Rust Result, but Ok has no associated value, and has Skip variant to indicate that a test is skipped.\n\n#### Trait Testable\n\nThis trait indicates that something can be used as a test. This is the smallest individual unit of the framework.\n\nThe implementor must implement three functions :\n\n- get_name : returns name of the test.\n- can_run : returns boolean indicating that if the particular test can be run or not. Defaults to returning true.\n- run : runs the actual test, and returns a TestResult.\n\n#### Trait TestableGroup\n\nThis trait indicates that something is a group of multiple tests. Primarily used for grouping tests, as well as providing namespacing.\n\nThe implementor must implement three functions :\n\n- get_name : returns name of the test group\n- run_all : run all of the tests belonging to this group, and return vector of tuples, each pair having name of test and its result.\n- run_selected : takes slice of test names which are to be run, and should run only those tests. Return vector of tuples, each pair having name of test and its result.\n\n#### Struct Test\n\nProvides a simple template for a simple test, implements Testable. This is intended to quickly create tests which are always run, and do not require state information. The new function takes name and Boxed function, which is the test function.\n\n#### Struct ConditionalTest\n\nProvides a simple template for test which is to be run conditionally. Implements Testable, and is intended to be used for stateless tests, which may or may not run depending on some condition (system config, env var etc.) The new function takes name, a Boxed function which is condition function, which returns a boolean indicating if the test can be run or not, and another Boxed function, which is the test function.\n\n#### Struct TestGroup\n\nProvides a simple template for a test group. This implement TestableGroup. The new function takes the name of the test group, and add function takes vector of Testables. This is intended to used for grouping of simple, stateless tests.\n\n#### Struct TestManager\n\nThis is the core manager for running of the tests. This stores test groups, controls running of them, and printing of results. It has following functions :\n\n- add_test_group : adds a TestableGroup.\n- run_all : runs all the tests in all test groups which can be run (whose can_run returns true) and prints their results to stdout\n- run_selected : takes a vector of tuples of the form (group-name, optional vector of test names) . Then runs only selected tests. If the optional vector is not present (None) then runs all tests in the group, or else runs only the selected tests from the group.\n"
  },
  {
    "path": "tests/contest/test_framework/src/conditional_test.rs",
    "content": "//! Contains definition for a tests which should be conditionally run\nuse crate::testable::{TestResult, Testable};\n\n// type aliases for test function signature\ntype TestFn = dyn Fn() -> TestResult + Sync + Send;\n// type alias for function signature for function which checks if a test can be run or not\ntype CheckFn = dyn Fn() -> bool + Sync + Send;\n\n/// Basic Template structure for tests which need to be run conditionally\npub struct ConditionalTest {\n    /// name of the test\n    name: &'static str,\n    /// actual test function\n    test_fn: Box<TestFn>,\n    /// function to check if a test can be run or not\n    check_fn: Box<CheckFn>,\n}\n\nimpl ConditionalTest {\n    /// Create a new condition test\n    pub fn new(name: &'static str, check_fn: Box<CheckFn>, test_fn: Box<TestFn>) -> Self {\n        ConditionalTest {\n            name,\n            check_fn,\n            test_fn,\n        }\n    }\n}\n\nimpl Testable for ConditionalTest {\n    fn get_name(&self) -> &'static str {\n        self.name\n    }\n\n    fn can_run(&self) -> bool {\n        (self.check_fn)()\n    }\n\n    fn run(&self) -> TestResult {\n        (self.test_fn)()\n    }\n}\n"
  },
  {
    "path": "tests/contest/test_framework/src/lib.rs",
    "content": "mod conditional_test;\nmod test;\nmod test_group;\nmod test_manager;\npub mod testable;\npub use conditional_test::ConditionalTest;\npub use test::Test;\npub use test_group::TestGroup;\npub use test_manager::TestManager;\npub use testable::{TestResult, Testable, TestableGroup};\n"
  },
  {
    "path": "tests/contest/test_framework/src/test.rs",
    "content": "//! Contains definition for a simple and commonly usable test structure\nuse crate::testable::{TestResult, Testable};\n\n// type alias for the test function\ntype TestFn = dyn Sync + Send + Fn() -> TestResult;\n\n/// Basic Template structure for a test\npub struct Test {\n    /// name of the test\n    name: &'static str,\n    /// Actual test function\n    test_fn: Box<TestFn>,\n}\n\nimpl Test {\n    /// create new test\n    pub fn new(name: &'static str, test_fn: Box<TestFn>) -> Self {\n        Test { name, test_fn }\n    }\n}\n\nimpl Testable for Test {\n    fn get_name(&self) -> &'static str {\n        self.name\n    }\n\n    fn run(&self) -> TestResult {\n        (self.test_fn)()\n    }\n}\n"
  },
  {
    "path": "tests/contest/test_framework/src/test_group.rs",
    "content": "//! Contains structure for a test group\nuse std::collections::BTreeMap;\n\nuse crossbeam::thread;\n\nuse crate::testable::{TestResult, Testable, TestableGroup};\n\n/// Stores tests belonging to a group\npub struct TestGroup {\n    /// name of the test group\n    name: &'static str,\n    /// can the test group be executed in parallel (both the tests\n    /// within it, and alongside other test groups)\n    parallel: bool,\n    /// tests belonging to this group\n    tests: BTreeMap<&'static str, Box<dyn Testable + Sync + Send>>,\n}\n\nimpl TestGroup {\n    /// create a new test group\n    pub fn new(name: &'static str) -> Self {\n        TestGroup {\n            name,\n            parallel: true,\n            tests: BTreeMap::new(),\n        }\n    }\n\n    /// mark the test group as unsuitable for parallel execution\n    pub fn set_nonparallel(&mut self) {\n        self.parallel = false\n    }\n\n    /// add a test to the group\n    pub fn add(&mut self, tests: Vec<Box<impl Testable + Sync + Send + 'static>>) {\n        tests.into_iter().for_each(|t| {\n            self.tests.insert(t.get_name(), t);\n        });\n    }\n}\n\nimpl TestableGroup for TestGroup {\n    /// get name of the test group\n    fn get_name(&self) -> &'static str {\n        self.name\n    }\n\n    /// can this test group be executed (within itself, and alongside other groups)\n    fn parallel(&self) -> bool {\n        self.parallel\n    }\n\n    /// run all the test from the test group\n    fn run_all(&self) -> Vec<(&'static str, TestResult)> {\n        let mut ret = Vec::with_capacity(self.tests.len());\n        if self.parallel {\n            thread::scope(|s| {\n                let mut collector = Vec::with_capacity(self.tests.len());\n                for (_, t) in self.tests.iter() {\n                    let _t = s.spawn(move |_| {\n                        if t.can_run() {\n                            (t.get_name(), t.run())\n                        } else {\n                            (t.get_name(), TestResult::Skipped)\n                        }\n                    });\n                    collector.push(_t);\n                }\n                for handle in collector {\n                    ret.push(handle.join().unwrap());\n                }\n            })\n            .unwrap();\n        } else {\n            for (_, t) in self.tests.iter() {\n                ret.push(if t.can_run() {\n                    (t.get_name(), t.run())\n                } else {\n                    (t.get_name(), TestResult::Skipped)\n                });\n            }\n        }\n        ret\n    }\n\n    /// run selected test from the group\n    fn run_selected(&self, selected: &[&str]) -> Vec<(&'static str, TestResult)> {\n        let selected_tests = self\n            .tests\n            .iter()\n            .filter(|(name, _)| selected.contains(name));\n        let mut ret = Vec::with_capacity(selected.len());\n        if self.parallel {\n            thread::scope(|s| {\n                let mut collector = Vec::with_capacity(selected.len());\n                for (_, t) in selected_tests {\n                    let _t = s.spawn(move |_| {\n                        if t.can_run() {\n                            (t.get_name(), t.run())\n                        } else {\n                            (t.get_name(), TestResult::Skipped)\n                        }\n                    });\n                    collector.push(_t);\n                }\n                for handle in collector {\n                    ret.push(handle.join().unwrap());\n                }\n            })\n            .unwrap();\n        } else {\n            for (_, t) in selected_tests {\n                ret.push(if t.can_run() {\n                    (t.get_name(), t.run())\n                } else {\n                    (t.get_name(), TestResult::Skipped)\n                });\n            }\n        }\n        ret\n    }\n}\n"
  },
  {
    "path": "tests/contest/test_framework/src/test_manager.rs",
    "content": "//! This exposes the main control wrapper to control the tests\nuse std::collections::BTreeMap;\n\nuse anyhow::Result;\nuse crossbeam::thread;\n\nuse crate::testable::{TestResult, TestableGroup};\n\ntype TestableGroupType = dyn TestableGroup + Sync + Send;\n\n/// This manages all test groups, and thus the tests\npub struct TestManager {\n    test_groups: BTreeMap<&'static str, Box<TestableGroupType>>,\n    cleanup: Vec<Box<dyn Fn() -> Result<()>>>,\n}\n\nimpl Default for TestManager {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl TestManager {\n    /// Create new TestManager\n    pub fn new() -> Self {\n        TestManager {\n            test_groups: BTreeMap::new(),\n            cleanup: Vec::new(),\n        }\n    }\n\n    /// add a test group to the test manager\n    pub fn add_test_group(&mut self, tg: Box<TestableGroupType>) {\n        self.test_groups.insert(tg.get_name(), tg);\n    }\n\n    pub fn add_cleanup(&mut self, cleaner: Box<dyn Fn() -> Result<()>>) {\n        self.cleanup.push(cleaner)\n    }\n\n    /// Prints the given test results, usually used to print\n    /// results of a test group\n    fn print_test_result(&self, name: &str, res: &[(&'static str, TestResult)]) {\n        println!(\"# Start group {name}\");\n        let len = res.len();\n        for (idx, (name, res)) in res.iter().enumerate() {\n            print!(\"{} / {} : {} : \", idx + 1, len, name);\n            match res {\n                TestResult::Passed => {\n                    println!(\"ok\");\n                }\n                TestResult::Skipped => {\n                    println!(\"skipped\");\n                }\n                TestResult::Failed(e) => {\n                    println!(\"not ok\\n\\t{e}\");\n                }\n            }\n        }\n        println!(\"# End group {name}\\n\");\n    }\n    /// Run all tests from all tests group\n    pub fn run_all(&self) {\n        // Collect all parallel test groups\n        let parallel_groups: Vec<_> = self\n            .test_groups\n            .iter()\n            .filter(|(_, tg)| tg.parallel())\n            .collect();\n\n        let batch_size = num_cpus::get().max(1);\n\n        for batch in parallel_groups.chunks(batch_size) {\n            thread::scope(|s| {\n                let mut collector = Vec::with_capacity(batch.len());\n                for (name, tg) in batch {\n                    let r = s.spawn(move |_| tg.run_all());\n                    collector.push((name, r));\n                }\n\n                for (name, handle) in collector {\n                    let result = handle.join().unwrap();\n                    self.print_test_result(name, &result);\n                }\n            })\n            .unwrap();\n        }\n\n        for (name, tg) in &self.test_groups {\n            if tg.parallel() {\n                continue;\n            }\n            self.print_test_result(name, &tg.run_all());\n        }\n\n        for cleaner in &self.cleanup {\n            if let Err(e) = cleaner() {\n                print!(\"Failed to cleanup: {e}\");\n            }\n        }\n    }\n\n    /// Run only selected tests\n    pub fn run_selected(&self, tests: Vec<(&str, Option<Vec<&str>>)>) {\n        thread::scope(|s| {\n            let mut collector = Vec::with_capacity(tests.len());\n            for (test_group_name, tests) in &tests {\n                if let Some(tg) = self.test_groups.get(test_group_name) {\n                    if !tg.parallel() {\n                        continue;\n                    }\n                    let r = match tests {\n                        None => s.spawn(move |_| tg.run_all()),\n                        Some(tests) => s.spawn(move |_| tg.run_selected(tests)),\n                    };\n                    collector.push((test_group_name, r));\n                } else {\n                    eprintln!(\"Error : Test Group {test_group_name} not found, skipping\");\n                }\n            }\n            for (name, handle) in collector {\n                self.print_test_result(name, &handle.join().unwrap());\n            }\n        })\n        .unwrap();\n        for (test_group_name, tests) in &tests {\n            if let Some(tg) = self.test_groups.get(test_group_name) {\n                if tg.parallel() {\n                    continue;\n                }\n                self.print_test_result(\n                    test_group_name,\n                    &match tests {\n                        None => tg.run_all(),\n                        Some(tests) => tg.run_selected(tests),\n                    },\n                );\n            } else {\n                // We've already printed errors for not finding tests\n            }\n        }\n\n        for cleaner in &self.cleanup {\n            if let Err(e) = cleaner() {\n                print!(\"Failed to cleanup: {e}\");\n            }\n        }\n    }\n\n    pub fn tests_groups(&self) -> Vec<String> {\n        self.test_groups.iter().map(|tg| tg.0.to_string()).collect()\n    }\n}\n"
  },
  {
    "path": "tests/contest/test_framework/src/testable.rs",
    "content": "//! Contains Basic setup for testing, testable trait and its result type\nuse std::fmt::Debug;\n\nuse anyhow::{Error, Result, bail};\n\n#[derive(Debug)]\n/// Enum indicating result of the test. This is like an extended std::result,\n/// which includes a Skip variant to indicate that a test was skipped, and the Ok variant has no associated value\npub enum TestResult {\n    /// Test was ok\n    Passed,\n    /// Test needed to be skipped\n    Skipped,\n    /// Test was error\n    Failed(Error),\n}\n\nimpl<T> From<Result<T>> for TestResult {\n    fn from(result: Result<T>) -> Self {\n        match result {\n            Ok(_) => TestResult::Passed,\n            Err(err) => TestResult::Failed(err),\n        }\n    }\n}\n\n/// This trait indicates that something can be run as a test, or is 'testable'\n/// This forms the basis of the framework, as all places where tests are done,\n/// expect structs which implement this\npub trait Testable {\n    fn get_name(&self) -> &'static str;\n    fn can_run(&self) -> bool {\n        true\n    }\n    fn run(&self) -> TestResult;\n}\n\n/// This trait indicates that something forms a group of tests.\n/// Test groups are used to group tests in sensible manner as well as provide namespacing to tests\npub trait TestableGroup {\n    fn get_name(&self) -> &'static str;\n    fn parallel(&self) -> bool;\n    fn run_all(&self) -> Vec<(&'static str, TestResult)>;\n    fn run_selected(&self, selected: &[&str]) -> Vec<(&'static str, TestResult)>;\n}\n\n#[macro_export]\nmacro_rules! test_result {\n    ($e:expr $(,)?) => {\n        match $e {\n            core::result::Result::Ok(val) => val,\n            core::result::Result::Err(err) => {\n                return $crate::testable::TestResult::Failed(err);\n            }\n        }\n    };\n}\n\n#[macro_export]\nmacro_rules! assert_result_eq {\n    ($expected:expr, $actual:expr $(,)?) => ({\n        match (&$expected, &$actual) {\n            (expected_val, actual_val) => {\n                if !(*expected_val == *actual_val) {\n                   test_framework::testable::assert_failed(&*expected_val, &*actual_val, std::option::Option::None)\n                } else {\n                    Ok(())\n                }\n            }\n        }\n    });\n    ($expected:expr, $actual:expr, $($arg:tt)+) => ({\n        match (&$expected, &$actual) {\n            (expected_val, actual_val) => {\n                if !(*expected_val == *actual_val) {\n                    test_framework::testable::assert_failed(&*expected_val, &*actual_val, std::option::Option::Some(format_args!($($arg)+)))\n                } else {\n                    Ok(())\n                }\n            }\n        }\n    });\n}\n\n#[doc(hidden)]\npub fn assert_failed<T, U>(\n    expected: &T,\n    actual: &U,\n    args: Option<std::fmt::Arguments<'_>>,\n) -> Result<()>\nwhere\n    T: Debug + ?Sized,\n    U: Debug + ?Sized,\n{\n    match args {\n        Some(args) => {\n            bail!(\n                r#\"assertion failed:\n            expected: `{:?}`,\n            actual: `{:?}`: {}\"#,\n                expected,\n                actual,\n                args\n            )\n        }\n        None => {\n            bail!(\n                r#\"assertion failed:\n            expected: `{:?}`,\n            actual: `{:?}`\"#,\n                expected,\n                actual\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "tests/dind/daemon.json",
    "content": "{\n  \"runtimes\": {\n    \"youki\": {\n      \"path\": \"/usr/bin/youki\"\n    }\n  }\n}"
  },
  {
    "path": "tests/dind/run.sh",
    "content": "#!/bin/bash\nset -e\n\nROOT=$(git rev-parse --show-toplevel)\n\ndocker run --privileged -dq \\\n  --name youki-test-dind \\\n  -v $ROOT/youki:/usr/bin/youki \\\n  -v $ROOT/tests/dind/daemon.json:/etc/docker/daemon.json \\\n  docker:dind > /dev/null\n\ntrap \"docker rm -f youki-test-dind > /dev/null\" EXIT\n\n# wait for docker to start\ntimeout 30s \\\n  grep -q -m1 \"/var/run/docker.sock\" \\\n    <(docker logs -f youki-test-dind 2>&1)\n\ndocker exec -i youki-test-dind \\\n  docker run -q --runtime=youki hello-world\n"
  },
  {
    "path": "tests/k8s/Dockerfile",
    "content": "# syntax=docker/dockerfile:1.4\n\nARG KIND_NODE_VERSION=v1.34.0\n\nFROM kindest/node:${KIND_NODE_VERSION} AS kind-base\n\nFROM kind-base AS youki-build\n# Install build dependencies\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n    pkg-config \\\n    libsystemd-dev \\\n    build-essential \\\n    libelf-dev \\\n    libseccomp-dev \\\n    libclang-dev \\\n    libssl-dev \\\n    && rm -rf /var/lib/apt/lists/*\n# Install Rust\nRUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh && sh /tmp/rustup.sh -y --profile=minimal\nENV PATH=\"/root/.cargo/bin:${PATH}\"\nWORKDIR /youki\n# Copy source code and build with required features\nCOPY . .\nRUN cargo build --release -p youki --features \"v1 v2\"\n\nFROM scratch AS shim\nCOPY --from=youki-build /youki/target/release/youki /\n\nFROM kind-base AS kind-fetch\nARG TARGETARCH\nARG KIND_VERSION=v0.30.0\nRUN curl -sSLf https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-${TARGETARCH} > /root/kind && chmod +x /root/kind\n\nFROM scratch AS kind-bin\nCOPY --from=kind-fetch /root/kind /kind\n\nFROM kind-base\nRUN <<EOF\nset -e\necho '[plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.youki]' >> /etc/containerd/config.toml\necho '  runtime_type = \"io.containerd.runc.v2\"' >> /etc/containerd/config.toml\necho '   [plugins.\"io.containerd.grpc.v1.cri\".containerd.runtimes.youki.options]' >> /etc/containerd/config.toml\necho '     BinaryName = \"/usr/local/bin/youki\"' >> /etc/containerd/config.toml\nsed -i 's,SystemdCgroup = true,,' /etc/containerd/config.toml\nEOF\n# Install runtime dependency for youki\nRUN apt-get update && apt-get install -y --no-install-recommends libseccomp2 \\\n    && rm -rf /var/lib/apt/lists/*\nCOPY --link --from=shim /* /usr/local/bin/\n\n"
  },
  {
    "path": "tests/k8s/deploy.yaml",
    "content": "apiVersion: node.k8s.io/v1\nkind: RuntimeClass\nmetadata:\n  name: youki\nhandler: youki\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  replicas: 2\n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      runtimeClassName: youki\n      containers:\n      - name: nginx\n        image: nginx:alpine\n        ports:\n        - containerPort: 80\n"
  },
  {
    "path": "tests/rootless-tests/run.sh",
    "content": "# This is a temporary test-collection for validating youki runs correctly with podman in rootless mode\n# This will be moved to a proper rust based test crate, similar to rust-integration tests soon\n\nset -ex\n\nruntime=$1\n\npodman rm --force --ignore create-test # remove if existing\n\npodman create --runtime $runtime --name create-test hello-world\nlog=$(podman start -a create-test)\necho $log | grep \"This message shows that your installation appears to be working correctly\"\npodman rm --force --ignore create-test\n\nrand=$(head -c 10 /dev/random | base64)\n\nlog=$(podman run --runtime $runtime fedora echo \"$rand\")\necho $log | grep $rand\n\npodman kill exec-test || true # ignore failure for killing\npodman rm --force --ignore exec-test\npodman run -d --runtime $runtime --name exec-test busybox sleep 10m\n\nrand=$(head -c 10 /dev/random | base64)\n\nlog=$(podman exec --runtime $runtime exec-test echo \"$rand\")\necho $log | grep $rand\n\nCGROUP_SUB_PATH=$(podman inspect exec-test | jq .[0].State.CgroupPath | tr -d \"\\\"\")\nCGROUP_PATH=\"/sys/fs/cgroup$CGROUP_SUB_PATH/cgroup.procs\"\n\n# assert we have exactly one process in the cgroup\ntest $(cat $CGROUP_PATH | wc -l) -eq 1\n# assert pid match\ntest $(cat $CGROUP_PATH) -eq $(podman inspect exec-test | jq .[0].State.Pid)\n\npodman exec -d --runtime $runtime exec-test sleep 5m\n\n# we cannot exactly check the pid of tenant here, instead just check that there are\n# two processes in the same cgroup now\ntest $(cat $CGROUP_PATH | wc -l) -eq 2\n\npodman kill exec-test\npodman rm --force --ignore exec-test\n"
  },
  {
    "path": "tests/runc/runc_integration_test.sh",
    "content": "#!/bin/bash -u\n\nRUNTIME=${1:-./youki}\nROOT=$(git rev-parse --show-toplevel)\nRUNC_DIR=\"${ROOT}/tests/runc/src/github.com/opencontainers/runc\"\nPATTERN_FILE=\"${ROOT}/${2:-tests/runc/runc_test_pattern}\"\n\nif [[ ! -f \"$RUNC_DIR/Makefile\" ]]; then\n  echo \"error: Makefile not found under: $RUNC_DIR\" >&2\n  echo \"please run: git submodule update --init --recursive\" >&2\n  exit 1\nfi\n\nif [[ ! -x \"$RUNTIME\" ]]; then\n  echo \"$RUNTIME binary not found\"\n  exit 1\nfi\ncp \"$RUNTIME\" \"$RUNC_DIR/runc\"\nchmod +x \"$RUNC_DIR/runc\"\n\ncd \"$RUNC_DIR\"\n\nsudo make test-binaries\n\nreadarray -t TEST_NAMES < \"$PATTERN_FILE\"\nfor name in \"${TEST_NAMES[@]}\"; do\n  if [[ $name =~ ^\\[skip\\] ]]; then\n    echo \"skip: $name\"\n    continue\n  fi\n\n  # escape [](){}+?*.,'\n  TEST_CASE=$(echo \"$name\" | sed 's/\\\\/\\\\\\\\/g; s/\\[/\\\\[/g; s/\\]/\\\\]/g; s/(/\\\\(/g; s/)/\\\\)/g; s/+/\\\\+/g; s/?/\\\\?/g; s/*/\\\\*/g; s/\\./\\\\./g; s/{/\\\\{/g; s/}/\\\\}/g; s/,/\\\\,/g;')\n  echo $TEST_CASE\n  sudo -E PATH=\"$PATH\" script -q -e -c \"bats  -f \\\"^$TEST_CASE$\\\" -t tests/integration\"\ndone\n"
  },
  {
    "path": "tests/runc/runc_test_pattern",
    "content": "[skip]runc run no capability\n[skip]runc run with unknown capability\n[skip]runc run with new privileges\n[skip]runc run with some capabilities\n[skip]runc exec --cap\n[skip]runc exec --cap [ambient is set from spec]\n[skip]runc run [ambient caps not set in inheritable result in a warning]\n[skip]runc exec (cgroup v2, ro cgroupfs, new cgroupns) does not chown cgroup # skip test requires systemd\n[skip]runc exec (cgroup v2, rw cgroupfs, inherit cgroupns) does not chown cgroup # skip test requires systemd\n[skip]runc exec (cgroup v2, rw cgroupfs, new cgroupns) does chown cgroup # skip test requires systemd\nrunc create (no limits + no cgrouppath + no permission) succeeds\n[skip]runc create (rootless + no limits + cgrouppath + no permission) fails with permission error # skip test requires rootless\n[skip]runc create (rootless + limits + no cgrouppath + no permission) fails with informative error # skip test requires rootless\n[skip]runc create (limits + cgrouppath + permission on the cgroup dir) succeeds\nrunc exec (limits + cgrouppath + permission on the cgroup dir) succeeds\n[skip]runc exec (cgroup v2 + init process in non-root cgroup) succeeds\n[skip]runc run (cgroup v1 + unified resources should fail) # skip test requires cgroups_v1\n[skip]runc run (blkio weight)\n[skip]runc run (per-device io weight for bfq) # skip BFQ scheduler not available\n[skip]runc run (per-device multiple iops via unified)\nrunc run (cpu.idle)\n[skip]runc run (hugetlb limits)\n[skip]runc run (cgroup v2 resources.unified only)\n[skip]runc run (cgroup v2 resources.unified swap)\nrunc run (cgroup v2 resources.unified override)\n[skip]runc run (cgroupv2 mount inside container)\n[skip]runc exec (cgroup v1+hybrid joins correct cgroup) # skip test requires cgroups_hybrid\n[skip]runc exec should refuse a paused container\n[skip]runc exec --ignore-paused\n[skip]runc run/create should error for a non-empty cgroup\n[skip]runc run/create should refuse pre-existing frozen cgroup # This test hangs when running with Youki\n[skip]checkpoint and restore # skip test requires criu\n[skip]checkpoint and restore (bind mount, destination is symlink) # skip test requires criu\n[skip]checkpoint and restore (with --debug) # skip test requires criu\n[skip]checkpoint and restore (cgroupns) # skip test requires criu\n[skip]checkpoint --pre-dump (bad --parent-path) # skip test requires criu\n[skip]checkpoint --pre-dump and restore # skip test requires criu\n[skip]checkpoint --lazy-pages and restore # skip test requires criu\n[skip]checkpoint and restore in external network namespace # skip test requires criu\n[skip]checkpoint and restore with container specific CRIU config # skip test requires criu\n[skip]checkpoint and restore with nested bind mounts # skip test requires criu\n[skip]checkpoint then restore into a different cgroup (via --manage-cgroups-mode ignore) # skip test requires criu\n[skip]checkpoint/restore and exec # skip test requires criu\n[skip]runc exec [CPU affinity, only initial set from process.json]\n[skip]runc exec [CPU affinity, initial and final set from process.json]\n[skip]runc exec [CPU affinity, initial and final set from config.json]\nrunc create\n[skip]runc create exec\nrunc create --pid-file\nrunc create --pid-file with new CWD\nrunc create [shared pidns + rootless]\nrunc exec --user with no access to cwd\n[skip]runc create sets up user before chdir to cwd if needed # skip test requires rootless\nrunc create can chdir if runc has access\n[skip]global --debug\n[skip]global --debug to --log\n[skip]global --debug to --log --log-format 'text'\n[skip]global --debug to --log --log-format 'json'\n[skip]runc delete\nrunc delete --force\nrunc delete --force ignore not exist\nrunc delete [host pidns + init gone]\nrunc delete --force [host pidns + init gone]\nrunc delete --force [paused container]\n[skip]runc delete --force in cgroupv1 with subcgroups # skip test requires cgroups_v1\n[skip]runc delete --force in cgroupv2 with subcgroups\n[skip]runc delete removes failed systemd unit # skip requires systemd >= v244\n[skip]runc run [redundant default /dev/tty]\nrunc run [redundant default /dev/ptmx]\n[skip]runc run/update [device cgroup deny]\n[skip]runc run [device cgroup allow rw char device]\n[skip]runc run [device cgroup allow rm block device]\n[skip]runc exec vs systemctl daemon-reload # skip test requires systemd\n[skip]runc run [devices vs systemd NeedDaemonReload] # skip requires systemd >= v230\nnon-empty HOME env is used\n[skip]empty HOME env var is overridden\n[skip]empty HOME env var is overridden with multiple overrides\n[skip]env var HOME is set only once # This test hangs when running with Youki\nenv var override is set only once\nenv var override\n[skip]env var with new-line is honored\n[skip]events --stats\n[skip]events --stats with psi data # This test hangs when running with Youki\n[skip]events --interval default # This test hangs when running with Youki\n[skip]events --interval 1s\n[skip]events --interval 100ms\n[skip]events oom # This test hangs when running with Youki\nrunc exec\n[skip]runc exec [exit codes] # This test hangs when running with Youki\nrunc exec --pid-file\nrunc exec --pid-file with new CWD\n[skip]runc exec ls -la\nrunc exec ls -la with --cwd\nrunc exec --env\n[skip]runc exec --user\nrunc exec --user vs /dev/null ownership\n[skip]runc exec --additional-gids\n[skip]runc exec --preserve-fds\n[skip]runc --debug exec\n[skip]runc --debug --log exec\n[skip]runc exec --cgroup sub-cgroups [v1] # skip test requires cgroups_v1\n[skip]runc exec --cgroup subcgroup [v2]\n[skip]runc exec [execve error]\n[skip]runc exec check default home\n[skip]runc -h\n[skip]runc command -h\n[skip]runc foo -h\n[skip]runc create [second createRuntime hook fails]\n[skip]runc create [hook fails]\n[skip]runc run [hook fails]\n[skip]runc run [startContainer hook should inherit process environment]\nrunc run [hook's argv is preserved]\n[skip]runc run (hooks library tests)\nrunc run [host mount ns + hooks]\n[skip]simple idmap mount [userns]\n[skip]simple idmap mount [no userns]\n[skip]write to an idmap mount [userns] # This test hangs when running with Youki\n[skip]write to an idmap mount [no userns]\n[skip]idmap mount with propagation flag [userns]\n[skip]idmap mount with relative path [userns]\n[skip]idmap mount with bind mount [userns]\n[skip]idmap mount with bind mount [no userns]\n[skip]two idmap mounts (same mapping) with two bind mounts [userns]\n[skip]same idmap mount (different mappings) [userns]\n[skip]same idmap mount (different mappings) [no userns]\n[skip]multiple idmap mounts (different mappings) [userns]\n[skip]multiple idmap mounts (different mappings) [no userns]\n[skip]idmap mount (complicated mapping) [userns] # This test hangs when running with Youki\n[skip]idmap mount (complicated mapping) [no userns]\n[skip]idmap mount (non-recursive idmap) [userns]\n[skip]idmap mount (non-recursive idmap) [no userns]\n[skip]idmap mount (idmap flag) [userns]\n[skip]idmap mount (idmap flag) [no userns]\n[skip]idmap mount (ridmap flag) [userns]\n[skip]idmap mount (ridmap flag) [no userns]\n[skip]idmap mount (idmap flag, implied mapping) [userns]\n[skip]idmap mount (ridmap flag, implied mapping) [userns]\n[skip]idmap mount (idmap flag, implied mapping, userns join) [userns]\n[skip]ioprio_set is applied to process group\n[skip]kill detached busybox\n[skip]kill KILL [host pidns]\n[skip]kill KILL [host pidns + init gone]\nkill KILL [shared pidns]\n[skip]list\n[skip]mask paths [file]\n[skip]mask paths [directory]\n[skip]mask paths [prohibit symlink /proc]\n[skip]mask paths [prohibit symlink /sys]\n[skip]runc run [tmpcopyup]\n[skip]runc run [bind mount] # This test hangs when running with Youki\nrunc run [ro tmpfs mount]\n[skip]runc run [ro /dev mount]\nrunc run [tmpfs mount with absolute symlink]\n[skip]runc run [/proc is a symlink]\nrunc run [setgid / + mkdirall]\nrunc run [ro /sys/fs/cgroup mounts]\n[skip]runc run [ro /sys/fs/cgroup mounts + cgroupns]\n[skip]runc run [mount order, container bind-mount source]\n[skip]runc run [mount order, container bind-mount source] (userns)\n[skip]runc run [mount order, container idmap source]\n[skip]runc run [mount order, container idmap source] (userns)\nrunc run [rootfsPropagation shared]\n[skip]runc run [rbind,ro mount is read-only but not recursively]\nrunc run [rbind,rro mount is recursively read-only]\nrunc run [rbind,ro,rro mount is recursively read-only too]\nrunc run [mount(8)-like behaviour: --bind with no options]\n[skip]runc run [mount(8)-unlike behaviour: --bind with clearing flag]\n[skip]runc run [implied-rw bind mount of a ro fuse sshfs mount]\n[skip]runc run [explicit-rw bind mount of a ro fuse sshfs mount]\n[skip]runc run [dev,exec,suid,atime bind mount of a nodev,nosuid,noexec,noatime fuse sshfs mount]\n[skip]runc run [ro bind mount of a nodev,nosuid,noexec fuse sshfs mount]\n[skip]runc run [ro,symfollow bind mount of a rw,nodev,nosymfollow fuse sshfs mount]\n[skip]runc run [ro,noexec bind mount of a nosuid,noatime fuse sshfs mount]\n[skip]runc run [bind mount {no,rel,strict}atime semantics]\nrunc run --no-pivot must not expose bare /proc\nrunc pause and resume\nrunc pause and resume with nonexist container\n[skip]runc run personality for i686\n[skip]runc run personality with exec for i686\nrunc run personality for x86_64\n[skip]runc run personality with exec for x86_64\n[skip]runc create [ --pidfd-socket ] \n[skip]runc run [ --pidfd-socket ] \n[skip]runc exec [ --pidfd-socket ] [cgroups_v1]  # skip test requires cgroups_v1\n[skip]runc exec [ --pidfd-socket ] [cgroups_v2] \nps\nps -f json\n[skip]ps -e -x\nps after the container stopped\n[skip]runc run with RLIMIT_NOFILE(The same as system's hard value)\nrunc run with RLIMIT_NOFILE(Bigger than system's hard value)\nrunc run with RLIMIT_NOFILE(Smaller than system's hard value)\n[skip]runc exec with RLIMIT_NOFILE(The same as system's hard value)\n[skip]runc exec with RLIMIT_NOFILE(Bigger than system's hard value)\n[skip]runc exec with RLIMIT_NOFILE(Smaller than system's hard value)\nglobal --root\nrunc run\n[skip]runc run --keep\n[skip]runc run --keep (check cgroup exists)\nrunc run [hostname domainname]\n[skip]runc run with tmpfs\n[skip]runc run with tmpfs perms\n[skip]runc run [/proc/self/exe clone]\n[skip]runc run [joining existing container namespaces]\n[skip]runc run [execve error]\nrunc run check default home\n[skip]scheduler is applied\n[skip]scheduler vs cpus\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY old kernel) # skip requires kernel < 5.6\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)\nrunc run [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)\n[skip]runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges false)\n[skip]runc exec [seccomp] (SCMP_ACT_NOTIFY noNewPrivileges true)\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges false)\nrunc run [seccomp] (SCMP_ACT_NOTIFY important syscalls noNewPrivileges true)\nrunc run [seccomp] (ignore listener path if no notify act)\nrunc run [seccomp] (SCMP_ACT_NOTIFY empty listener path and notify act)\nrunc run [seccomp] (SCMP_ACT_NOTIFY wrong listener path)\nrunc run [seccomp] (SCMP_ACT_NOTIFY wrong abstract listener path)\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY kill seccompagent)\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY no seccompagent)\n[skip]runc run [seccomp] (SCMP_ACT_NOTIFY error chmod)\nrunc run [seccomp] (SCMP_ACT_NOTIFY write)\nrunc run [seccomp] (SCMP_ACT_NOTIFY startContainer hook)\nrunc run [seccomp] (SCMP_ACT_NOTIFY example config)\n[skip]runc run [seccomp -ENOSYS handling]\nrunc run [seccomp defaultErrnoRet=ENXIO]\n[skip]runc run [seccomp] (SCMP_ACT_ERRNO default)\n[skip]runc run [seccomp] (SCMP_ACT_ERRNO explicit errno)\n[skip]runc run [seccomp] (SECCOMP_FILTER_FLAG_*)\nrunc run [seccomp] (SCMP_ACT_KILL)\n[skip]runc run [seccomp] (startContainer hook)\n[skip]runc run (no selinux label) # skip requires SELinux enabled\n[skip]runc run (custom selinux label) # skip requires SELinux enabled\n[skip]runc run (session keyring security label) # skip requires SELinux enabled\n[skip]runc exec (session keyring security label) # skip requires SELinux enabled\n[skip]runc run (session keyring security label + userns) # skip requires SELinux enabled\n[skip]runc exec (session keyring security label + userns) # skip requires SELinux enabled\nspec generation cwd\nspec generation --bundle\nspec validator\nrunc start\nrunc run detached\nrunc run detached ({u,g}id != 0)\nrunc run detached --pid-file\nrunc run detached --pid-file with new CWD\nrunc run\nrunc run ({u,g}id != 0)\n[skip]runc run as user with no exec bit but CAP_DAC_OVERRIDE set\nrunc run with rootfs set to .\nrunc run --pid-file\nrunc run [rootless with host pidns]\nrunc run [redundant seccomp rules]\nstate (kill + delete)\nstate (pause + resume)\nrunc run [timens offsets with no timens]\n[skip]runc run [timens with no offsets]\n[skip]runc run [simple timens]\n[skip]runc exec [simple timens]\n[skip]runc run [simple timens + userns]\n[skip]runc run [stdin not a tty]\n[skip]runc run [tty ptsname]\n[skip]runc run [tty owner]\n[skip]runc run [tty owner] ({u,g}id != 0)\n[skip]runc exec [stdin not a tty]\n[skip]runc exec [tty ptsname]\n[skip]runc exec [tty owner]\n[skip]runc exec [tty owner] ({u,g}id != 0)\n[skip]runc exec [tty consolesize]\nrunc create [terminal=false]\nrunc run [terminal=false]\nrunc run -d [terminal=false]\n[skip]umask\n[skip]update cgroup v1/v2 common limits\n[skip]update cgroup cpu limits\n[skip]cpu burst\nset cpu period with no quota\n[skip]set cpu period with no quota (invalid period)\nset cpu quota with no period\n[skip]update cpu period with no previous period/quota set\n[skip]update cpu quota with no previous period/quota set\n[skip]update cpu period in a pod cgroup with pod limit set # skip test requires cgroups_v1\n[skip]update cgroup cpu.idle\n[skip]update cgroup cpu.idle via systemd v252+ # skip requires systemd >= v252\nupdate cgroup v2 resources via unified map\nupdate cpuset parameters via resources.CPU\nupdate cpuset parameters via v2 unified map\n[skip]update cpuset cpus range via v2 unified map # skip test requires systemd\n[skip]update rt period and runtime # skip test requires cgroups_v1\nupdate devices [minimal transition rules]\nupdate paused container\n[skip]update memory vs CheckBeforeUpdate\nuserns with simple mount\n[skip]userns with 2 inaccessible mounts\n[skip]userns with inaccessible mount + exec\n[skip]userns with bind mount before a cgroupfs mount # skip test requires cgroups_v1\n[skip]userns join other container userns\n[skip]userns join other container userns[selinux enabled] # skip requires SELinux enabled and in enforcing mode\n[skip]userns join other container userns [bind-mounted nsfd]\n[skip]userns join external namespaces [wrong userns owner]\n[skip]runc version"
  },
  {
    "path": "tools/wasm-sample/Cargo.toml",
    "content": "[package]\nname = \"wasm-sample\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\n"
  },
  {
    "path": "tools/wasm-sample/README.md",
    "content": "This is a simple wasm module for testing purposes. It prints out the arguments given to the program and all environment variables. You can compile the module with \n\n```\ncargo build --target wasm32-wasi\n```\n\nIf you want youki to execute the module you must copy it to the root file system of the container and reference it in the args of the config.json. You must also ensure that the annotations contain `\"run.oci.handler\": \"wasm\"` and that youki has been compiled with one of the supported wasm runtimes. For further information please check the [documentation](https://youki-dev.github.io/youki/user/webassembly.html).\n\n```\n\"ociVersion\": \"1.0.2-dev\",\n\t\"annotations\": {\n\t\t\"run.oci.handler\": \"wasm\"\n\t},\n\t\"process\": {\n\t\t\"terminal\": true,\n\t\t\"user\": {\n\t\t\t\"uid\": 0,\n\t\t\t\"gid\": 0\n\t\t},\n\t\t\"args\": [\n\t\t\t\"/wasm-sample.wasm\",\n\t\t\t\"hello\",\n\t\t\t\"wasm\"\n\t\t],\n```\n"
  },
  {
    "path": "tools/wasm-sample/src/main.rs",
    "content": "fn main() {\n    println!(\"Printing args\");\n    for arg in std::env::args().skip(1) {\n        println!(\"{arg}\");\n    }\n\n    println!(\"Printing envs\");\n    for envs in std::env::vars() {\n        println!(\"{envs:?}\");\n    }\n}\n"
  }
]