├── .cargo └── config.toml ├── .codecov.yml ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yml │ ├── FEATURE_REQ.yml │ └── config.yml ├── dependabot.yml ├── grcov.yml ├── pull_request_template.md ├── release.yml └── workflows │ ├── basic.yml │ ├── benchmark_execution_time.yml │ ├── dependabot_auto.yaml │ ├── docs.yaml │ ├── e2e.yaml │ ├── integration_tests_validation.yaml │ ├── label.yaml │ ├── podman_tests.yaml │ ├── release.yaml │ ├── selinux.yaml │ ├── tagpr.yaml │ └── update_version_config.yaml ├── .gitignore ├── .gitmodules ├── .tagpr ├── .typos.toml ├── CHANGELOG.md ├── CODE-OF-CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── LICENSE ├── MigrationGuide.md ├── README.md ├── SECURITY.md ├── Vagrantfile ├── Vagrantfile.containerd2youki ├── Vagrantfile.podmane2e ├── Vagrantfile.root ├── crates ├── .gitignore ├── libcgroups │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ ├── bpf.rs │ │ └── rules.json │ └── src │ │ ├── common.rs │ │ ├── lib.rs │ │ ├── stats.rs │ │ ├── stub │ │ ├── systemd │ │ │ ├── manager.rs │ │ │ └── mod.rs │ │ ├── v1 │ │ │ ├── manager.rs │ │ │ └── mod.rs │ │ └── v2 │ │ │ ├── manager.rs │ │ │ └── mod.rs │ │ ├── systemd │ │ ├── controller.rs │ │ ├── controller_type.rs │ │ ├── cpu.rs │ │ ├── cpuset.rs │ │ ├── dbus_native │ │ │ ├── client.rs │ │ │ ├── dbus.rs │ │ │ ├── message.rs │ │ │ ├── mod.rs │ │ │ ├── proxy.rs │ │ │ ├── serialize.rs │ │ │ └── utils.rs │ │ ├── manager.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── pids.rs │ │ └── unified.rs │ │ ├── test.rs │ │ ├── test_manager.rs │ │ ├── v1 │ │ ├── blkio.rs │ │ ├── controller.rs │ │ ├── controller_type.rs │ │ ├── cpu.rs │ │ ├── cpuacct.rs │ │ ├── cpuset.rs │ │ ├── devices.rs │ │ ├── freezer.rs │ │ ├── hugetlb.rs │ │ ├── manager.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── network_classifier.rs │ │ ├── network_priority.rs │ │ ├── perf_event.rs │ │ ├── pids.rs │ │ └── util.rs │ │ └── v2 │ │ ├── controller.rs │ │ ├── controller_type.rs │ │ ├── cpu.rs │ │ ├── cpuset.rs │ │ ├── devices │ │ ├── bpf.rs │ │ ├── controller.rs │ │ ├── emulator.rs │ │ ├── mocks.rs │ │ ├── mod.rs │ │ └── program.rs │ │ ├── freezer.rs │ │ ├── hugetlb.rs │ │ ├── io.rs │ │ ├── manager.rs │ │ ├── memory.rs │ │ ├── mod.rs │ │ ├── pids.rs │ │ ├── unified.rs │ │ └── util.rs ├── libcontainer │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ ├── apparmor.rs │ │ ├── capabilities.rs │ │ ├── channel.rs │ │ ├── config.rs │ │ ├── container │ │ │ ├── builder.rs │ │ │ ├── builder_impl.rs │ │ │ ├── container.rs │ │ │ ├── container_checkpoint.rs │ │ │ ├── container_delete.rs │ │ │ ├── container_events.rs │ │ │ ├── container_kill.rs │ │ │ ├── container_pause.rs │ │ │ ├── container_resume.rs │ │ │ ├── container_start.rs │ │ │ ├── init_builder.rs │ │ │ ├── mod.rs │ │ │ ├── state.rs │ │ │ └── tenant_builder.rs │ │ ├── error.rs │ │ ├── hooks.rs │ │ ├── lib.rs │ │ ├── namespaces.rs │ │ ├── notify_socket.rs │ │ ├── process │ │ │ ├── args.rs │ │ │ ├── channel.rs │ │ │ ├── container_intermediate_process.rs │ │ │ ├── container_main_process.rs │ │ │ ├── fork.rs │ │ │ ├── init │ │ │ │ ├── context.rs │ │ │ │ ├── error.rs │ │ │ │ ├── mod.rs │ │ │ │ └── process.rs │ │ │ ├── intel_rdt.rs │ │ │ ├── message.rs │ │ │ ├── mod.rs │ │ │ └── seccomp_listener.rs │ │ ├── rootfs │ │ │ ├── device.rs │ │ │ ├── mod.rs │ │ │ ├── mount.rs │ │ │ ├── rootfs.rs │ │ │ ├── symlink.rs │ │ │ └── utils.rs │ │ ├── seccomp │ │ │ ├── fixture │ │ │ │ └── config.json │ │ │ └── mod.rs │ │ ├── signal.rs │ │ ├── syscall │ │ │ ├── linux.rs │ │ │ ├── mod.rs │ │ │ ├── syscall.rs │ │ │ └── test.rs │ │ ├── test_utils.rs │ │ ├── tty.rs │ │ ├── user_ns.rs │ │ ├── utils.rs │ │ └── workload │ │ │ ├── default.rs │ │ │ └── mod.rs │ └── tests │ │ └── as_sibling.rs ├── liboci-cli │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── checkpoint.rs │ │ ├── create.rs │ │ ├── delete.rs │ │ ├── events.rs │ │ ├── exec.rs │ │ ├── features.rs │ │ ├── info.rs │ │ ├── kill.rs │ │ ├── lib.rs │ │ ├── list.rs │ │ ├── pause.rs │ │ ├── ps.rs │ │ ├── resume.rs │ │ ├── run.rs │ │ ├── spec.rs │ │ ├── start.rs │ │ ├── state.rs │ │ └── update.rs └── youki │ ├── Cargo.toml │ ├── build.rs │ └── src │ ├── commands │ ├── checkpoint.rs │ ├── completion.rs │ ├── create.rs │ ├── delete.rs │ ├── events.rs │ ├── exec.rs │ ├── features.rs │ ├── info.rs │ ├── kill.rs │ ├── list.rs │ ├── mod.rs │ ├── pause.rs │ ├── ps.rs │ ├── resume.rs │ ├── run.rs │ ├── spec_json.rs │ ├── start.rs │ ├── state.rs │ └── update.rs │ ├── main.rs │ ├── observability.rs │ ├── rootpath.rs │ └── workload │ ├── executor.rs │ ├── mod.rs │ ├── wasmedge.rs │ ├── wasmer.rs │ └── wasmtime.rs ├── cross ├── Dockerfile.gnu └── Dockerfile.musl ├── docs ├── .drawio.svg ├── .gitignore ├── archive │ ├── youki.png │ ├── youki_flat.png │ ├── youki_flat.svg │ └── youki_flat_full.png ├── book.toml ├── demo.gif ├── doc-draft.md ├── src │ ├── SUMMARY.md │ ├── assets │ │ ├── control_flow.drawio.svg │ │ ├── rust_oci_tests.png │ │ └── youki.png │ ├── community │ │ ├── chat.md │ │ ├── contributing.md │ │ ├── governance.md │ │ ├── introduction.md │ │ └── maintainer.md │ ├── developer │ │ ├── basics.md │ │ ├── crate_specific_information.md │ │ ├── debugging.md │ │ ├── documentation_mdbook.md │ │ ├── e2e │ │ │ ├── containerd_integration_test_using_youki.md │ │ │ ├── e2e_tests.md │ │ │ ├── integration_test.md │ │ │ ├── runtime_tools.md │ │ │ ├── runtimetest.md │ │ │ ├── rust_oci_test.md │ │ │ └── test_framework.md │ │ ├── good_places_to_start.md │ │ ├── introduction.md │ │ ├── libcgroups.md │ │ ├── libcontainer.md │ │ ├── liboci_cli.md │ │ ├── libseccomp.md │ │ ├── repo_structure.md │ │ ├── runtimetest.md │ │ ├── unwritten_rules.md │ │ └── youki.md │ ├── user │ │ ├── basic_setup.md │ │ ├── basic_usage.md │ │ ├── crates.md │ │ ├── introduction.md │ │ ├── libcgroups.md │ │ ├── libcontainer.md │ │ ├── liboci_cli.md │ │ ├── libseccomp.md │ │ └── webassembly.md │ └── youki.md └── youki.png ├── experiment ├── seccomp │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── rust-toolchain.toml │ └── src │ │ ├── instruction │ │ ├── arch.rs │ │ ├── consts.rs │ │ ├── inst.rs │ │ └── mod.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ └── seccomp.rs └── selinux │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── lima-run.sh │ ├── lima-setup.sh │ ├── provision_system.sh │ └── src │ ├── lib.rs │ ├── main.rs │ ├── selinux.rs │ ├── selinux_label.rs │ └── tools │ ├── mod.rs │ ├── sockopt.rs │ └── xattr.rs ├── hack ├── busctl.sh ├── debug.bt ├── set_root_login_for_vagrant.sh └── stress_cargo_test.sh ├── justfile ├── rust-toolchain.toml ├── rustfmt.toml ├── scripts ├── .gitignore ├── README.md ├── build.sh ├── cargo.sh ├── clean.sh ├── contest.sh ├── features_test.sh ├── oci_integration_tests.sh └── release_tag.sh ├── tests ├── .gitignore ├── contest │ ├── contest │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── bundle.tar.gz │ │ └── src │ │ │ ├── lib.rs │ │ │ ├── logger.rs │ │ │ ├── main.rs │ │ │ ├── tests │ │ │ ├── cgroups │ │ │ │ ├── blkio.rs │ │ │ │ ├── cpu │ │ │ │ │ ├── mod.rs │ │ │ │ │ ├── v1.rs │ │ │ │ │ └── v2.rs │ │ │ │ ├── memory.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── network │ │ │ │ │ ├── absolute_network.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── relative_network.rs │ │ │ │ └── pids.rs │ │ │ ├── delete │ │ │ │ ├── delete_test.rs │ │ │ │ └── mod.rs │ │ │ ├── devices │ │ │ │ ├── devices_test.rs │ │ │ │ └── mod.rs │ │ │ ├── domainname │ │ │ │ └── mod.rs │ │ │ ├── example │ │ │ │ ├── hello_world.rs │ │ │ │ └── mod.rs │ │ │ ├── fd_control │ │ │ │ └── mod.rs │ │ │ ├── hooks │ │ │ │ ├── invoke.rs │ │ │ │ └── mod.rs │ │ │ ├── hostname │ │ │ │ └── mod.rs │ │ │ ├── intel_rdt │ │ │ │ ├── intel_rdt_test.rs │ │ │ │ └── mod.rs │ │ │ ├── io_priority │ │ │ │ ├── io_priority_test.rs │ │ │ │ └── mod.rs │ │ │ ├── kill │ │ │ │ ├── kill_test.rs │ │ │ │ └── mod.rs │ │ │ ├── lifecycle │ │ │ │ ├── checkpoint.rs │ │ │ │ ├── container_create.rs │ │ │ │ ├── container_lifecycle.rs │ │ │ │ ├── create.rs │ │ │ │ ├── delete.rs │ │ │ │ ├── exec.rs │ │ │ │ ├── kill.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── start.rs │ │ │ │ ├── state.rs │ │ │ │ └── util.rs │ │ │ ├── linux_masked_paths │ │ │ │ ├── masked_paths.rs │ │ │ │ └── mod.rs │ │ │ ├── linux_ns_itype │ │ │ │ ├── mod.rs │ │ │ │ └── ns_itype_test.rs │ │ │ ├── mod.rs │ │ │ ├── mounts_recursive │ │ │ │ └── mod.rs │ │ │ ├── no_pivot │ │ │ │ └── mod.rs │ │ │ ├── pidfile │ │ │ │ ├── mod.rs │ │ │ │ └── pidfile_test.rs │ │ │ ├── process │ │ │ │ ├── mod.rs │ │ │ │ └── process_test.rs │ │ │ ├── process_capabilities_fail │ │ │ │ ├── mod.rs │ │ │ │ └── process_capabilities_fail_test.rs │ │ │ ├── process_oom_score_adj │ │ │ │ ├── mod.rs │ │ │ │ └── process_oom_score_adj_test.rs │ │ │ ├── process_rlimits │ │ │ │ ├── mod.rs │ │ │ │ └── process_rlimits_test.rs │ │ │ ├── process_rlimits_fail │ │ │ │ ├── mod.rs │ │ │ │ └── process_rlimits_fail_test.rs │ │ │ ├── process_user │ │ │ │ ├── mod.rs │ │ │ │ └── process_user_test.rs │ │ │ ├── readonly_paths │ │ │ │ ├── mod.rs │ │ │ │ └── readonly_paths_tests.rs │ │ │ ├── root_readonly_true │ │ │ │ ├── mod.rs │ │ │ │ └── root_readonly_tests.rs │ │ │ ├── rootfs_propagation │ │ │ │ ├── mod.rs │ │ │ │ └── rootfs_propagation_test.rs │ │ │ ├── scheduler │ │ │ │ ├── mod.rs │ │ │ │ └── scheduler_policy.rs │ │ │ ├── seccomp │ │ │ │ └── mod.rs │ │ │ ├── seccomp_notify │ │ │ │ ├── mod.rs │ │ │ │ └── seccomp_agent.rs │ │ │ ├── sysctl │ │ │ │ └── mod.rs │ │ │ └── tlb │ │ │ │ ├── mod.rs │ │ │ │ └── tlb_test.rs │ │ │ └── utils │ │ │ ├── mod.rs │ │ │ ├── support.rs │ │ │ └── test_utils.rs │ ├── runtimetest │ │ ├── .cargo │ │ │ └── config.toml │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ ├── main.rs │ │ │ ├── tests.rs │ │ │ └── utils.rs │ └── test_framework │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ ├── conditional_test.rs │ │ ├── lib.rs │ │ ├── test.rs │ │ ├── test_group.rs │ │ ├── test_manager.rs │ │ └── testable.rs ├── dind │ ├── daemon.json │ └── run.sh ├── k8s │ ├── Dockerfile │ └── deploy.yaml └── rootless-tests │ └── run.sh └── tools └── wasm-sample ├── Cargo.toml ├── README.md └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [profile.release] 2 | strip = "symbols" -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | codecov: 3 | notify: 4 | after_n_builds: 1 5 | require_ci_to_pass: false 6 | 7 | coverage: 8 | precision: 2 9 | round: down 10 | range: 50..75 11 | 12 | comment: 13 | layout: "header, diff" 14 | behavior: default 15 | require_changes: false 16 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VARIANT="bullseye" 2 | FROM mcr.microsoft.com/vscode/devcontainers/rust:1-${VARIANT} 3 | 4 | # Install docker with youki 5 | COPY < 3 | 4 | ## Type of Change 5 | 6 | - [ ] Bug fix (non-breaking change that fixes an issue) 7 | - [ ] New feature (non-breaking change that adds functionality) 8 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 9 | - [ ] Documentation update 10 | - [ ] Refactoring (no functional changes) 11 | - [ ] Performance improvement 12 | - [ ] Test updates 13 | - [ ] CI/CD related changes 14 | - [ ] Other (please describe): 15 | 16 | ## Testing 17 | 18 | - [ ] Added new unit tests 19 | - [ ] Added new integration tests 20 | - [ ] Ran existing test suite 21 | - [ ] Tested manually (please provide steps) 22 | 23 | ## Related Issues 24 | 26 | Fixes # 27 | 28 | ## Additional Context 29 | 30 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | authors: 4 | - dependabot 5 | categories: 6 | - title: 💪 Improvements 7 | labels: 8 | - kind/feature 9 | - title: 💥 Breaking Changes 10 | labels: 11 | - breaking change 12 | - title: 🐛 Bug Fixes 13 | labels: 14 | - kind/bug 15 | - title: 📖 Documentation improvements 16 | labels: 17 | - kind/documentation 18 | - title: 🧪 Test improvements and Misc Fixes 19 | labels: 20 | - kind/test 21 | - kind/cleanup 22 | - title: Other Changes 23 | labels: 24 | - "*" 25 | -------------------------------------------------------------------------------- /.github/workflows/dependabot_auto.yaml: -------------------------------------------------------------------------------- 1 | name: 🤖 Dependabot automation 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | 8 | permissions: 9 | pull-requests: write 10 | contents: write 11 | repository-projects: write 12 | 13 | jobs: 14 | dependabot-automation: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.actor == 'dependabot[bot]' }} 17 | steps: 18 | - name: Dependabot metadata 19 | id: metadata 20 | uses: dependabot/fetch-metadata@v1.3.5 21 | with: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | - name: Approve & enable auto-merge for Dependabot PR 24 | if: | 25 | steps.metadata.outputs.update-type == 'version-update:semver-patch' 26 | run: | 27 | gh pr review --approve "$PR_URL" 28 | gh pr edit "$PR_URL" -t "(auto merged) $PR_TITLE" 29 | env: 30 | PR_URL: ${{ github.event.pull_request.html_url }} 31 | PR_TITLE: ${{ github.event.pull_request.title }} 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | - name: Automerge 34 | id: automerge 35 | uses: pascalgn/automerge-action@v0.15.6 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | MERGE_LABELS: dependencies 39 | MERGE_REQUIRED_APPROVALS: 1 40 | MERGE_RETRY_SLEEP: 300000 41 | MERGE_DELETE_BRANCH: true 42 | MERGE_FILTER_AUTHOR: dependabot[bot] 43 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: 📓 Deploy the documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | changes: 10 | runs-on: ubuntu-24.04 11 | timeout-minutes: 15 12 | outputs: 13 | dirs: ${{ steps.filter.outputs.changes }} 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: dorny/paths-filter@v2 17 | id: filter 18 | with: 19 | filters: | 20 | docs: docs/** 21 | deploy: 22 | needs: [changes] 23 | if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} 24 | runs-on: ubuntu-24.04 25 | timeout-minutes: 15 26 | concurrency: 27 | group: ${{ github.workflow }}-${{ github.ref }} 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Setup mdBook 31 | uses: peaceiris/actions-mdbook@v1 32 | with: 33 | mdbook-version: "latest" 34 | - name: Build mdbook 35 | working-directory: ./docs 36 | run: mdbook build 37 | - name: Deploy 38 | uses: peaceiris/actions-gh-pages@v3 39 | if: ${{ github.ref == 'refs/heads/main' }} 40 | with: 41 | github_token: ${{ secrets.GITHUB_TOKEN }} 42 | publish_dir: ./docs/book 43 | -------------------------------------------------------------------------------- /.github/workflows/label.yaml: -------------------------------------------------------------------------------- 1 | name: 🏷️ Pull Request Labels 2 | 3 | on: 4 | pull_request: 5 | types: [opened, labeled, unlabeled, synchronize] 6 | jobs: 7 | label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: mheap/github-action-required-labels@v5 14 | with: 15 | mode: exactly 16 | count: 1 17 | labels: "kind/feature, kind/bug, kind/documentation, kind/test, kind/cleanup, dependencies, kind/experimental" 18 | -------------------------------------------------------------------------------- /.github/workflows/selinux.yaml: -------------------------------------------------------------------------------- 1 | name: 🧪 SELinux Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | changes: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | dirs: ${{ steps.filter.outputs.changes }} 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | - name: Check for changes 21 | uses: dorny/paths-filter@v2 22 | id: filter 23 | with: 24 | filters: | 25 | selinux: experiment/selinux/** 26 | 27 | test: 28 | name: SELinux Lima Tests 29 | needs: [changes] 30 | if: ${{ !contains(needs.changes.outputs.dirs, '[]') }} 31 | runs-on: ubuntu-latest 32 | permissions: 33 | contents: read 34 | steps: 35 | - name: Checkout code 36 | uses: actions/checkout@v4 37 | 38 | - name: Set up Lima 39 | uses: lima-vm/lima-actions/setup@v1 40 | id: lima-setup 41 | 42 | - name: Cache Lima images 43 | uses: actions/cache@v4 44 | with: 45 | path: ~/.cache/lima 46 | key: lima-${{ steps.lima-setup.outputs.version }}-selinux 47 | 48 | - name: Create Lima VM 49 | working-directory: experiment/selinux 50 | run: | 51 | chmod +x ./lima-setup.sh 52 | ./lima-setup.sh --cpus 2 --memory 2GiB 53 | 54 | - name: Run tests 55 | working-directory: experiment/selinux 56 | run: | 57 | ./lima-run.sh cargo test 58 | 59 | - name: Run application 60 | working-directory: experiment/selinux 61 | run: | 62 | ./lima-run.sh cargo run 63 | 64 | - name: Clean up 65 | if: always() 66 | working-directory: experiment/selinux 67 | run: | 68 | ./lima-setup.sh --cleanup 69 | -------------------------------------------------------------------------------- /.github/workflows/tagpr.yaml: -------------------------------------------------------------------------------- 1 | name: 🚀 Tagpr for GitHub Actions 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | tagpr: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | - name: Install just 14 | uses: taiki-e/install-action@just 15 | - uses: Songmu/tagpr@v1 16 | id: tagpr 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | - name: Trigger Release Workflow(only when tagged) 20 | uses: actions/github-script@v6 21 | if: "steps.tagpr.outputs.tag != ''" 22 | with: 23 | script: | 24 | github.rest.actions.createWorkflowDispatch({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | workflow_id: 'release.yaml', 28 | ref: "refs/tags/${{ steps.tagpr.outputs.tag }}", 29 | }) 30 | -------------------------------------------------------------------------------- /.github/workflows/update_version_config.yaml: -------------------------------------------------------------------------------- 1 | name: 🤖 Automated Update tagpr config 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to release' 8 | required: true 9 | jobs: 10 | config: 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Update tagpr config 18 | run: | 19 | cat << EOF > .tagpr 20 | [tagpr] 21 | vPrefix = true 22 | releaseBranch = main 23 | versionFile = justfile 24 | command = just version-up ${{ github.event.inputs.version }} 25 | release = false 26 | changelog = true 27 | EOF 28 | - uses: peter-evans/create-pull-request@v5 29 | with: 30 | title: 'config: Automated Tagpr Update for ${{ github.event.inputs.version }}' 31 | add-paths: | 32 | .tagpr 33 | body: | 34 | 35 | Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action 36 | commit-message: | 37 | 🤖 update tagpr config using robot. 38 | branch: tagpr-${{ github.event.inputs.version }} 39 | base: main 40 | signoff: true 41 | delete-branch: true 42 | token: ${{ secrets.GITHUB_TOKEN }} 43 | committer: github-actions[bot] 44 | author: github-actions[bot] 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /tutorial 2 | .idea/ 3 | 4 | **/target 5 | /contest-target 6 | /bin 7 | .vagrant/ 8 | 9 | tags 10 | tags.lock 11 | tags.temp 12 | 13 | /youki 14 | /runtimetest 15 | /contest 16 | 17 | .vscode 18 | 19 | *~ 20 | 21 | /bundle.tar.gz 22 | /test.log 23 | 24 | /tests/k8s/_out/ 25 | replace_content.txt 26 | /e2e/k8s/_out/ 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools"] 2 | path = tests/oci-runtime-tests/src/github.com/opencontainers/runtime-tools 3 | url = https://github.com/opencontainers/runtime-tools.git 4 | ignore = dirty 5 | -------------------------------------------------------------------------------- /.tagpr: -------------------------------------------------------------------------------- 1 | [tagpr] 2 | vPrefix = true 3 | releaseBranch = main 4 | versionFile = - 5 | command = just version-up 0.5.4 6 | release = false 7 | changelog = true 8 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # Configuration Reference: 2 | # - https://github.com/crate-ci/typos/blob/927308c726b1fba730f7aaa8bde602148b82004d/docs/reference.md 3 | 4 | [files] 5 | extend-exclude = [ 6 | "**/*.svg", 7 | "tests/oci-runtime-tests/**", 8 | "CHANGELOG.md" 9 | ] 10 | 11 | [default.extend-identifiers] 12 | # This is a cgroup slice ID used in examples. It is easier to ignore this 13 | # instance than write a regex. 14 | 569d5ce3afe1074769f67 = "569d5ce3afe1074769f67" 15 | 16 | [type.rust.extend-words] 17 | ser = "ser" 18 | flate = "flate" 19 | clos = "clos" 20 | Setted = "Setted" 21 | hve = "hve" 22 | typ = "typ" 23 | 24 | [type.md.extend-words] 25 | # This is used as "yoh-key" in markdown files to describe the pronunciation 26 | # for Youki 27 | "yoh" = "yoh" 28 | "typ" = "typ" 29 | -------------------------------------------------------------------------------- /CODE-OF-CONDUCT.md: -------------------------------------------------------------------------------- 1 | Move to youki-dev.github.io/youki/community/governance.html#code-of-conduct 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["crates/*", "tests/contest/*", "tools/*"] 4 | exclude = ["experiment/seccomp", "experiment/selinux"] 5 | 6 | [profile.release] 7 | lto = true 8 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | default-target = "x86_64-unknown-linux-gnu" 3 | env.passthrough = ["XDG_RUNTIME_DIR"] 4 | 5 | [target.aarch64-unknown-linux-gnu] 6 | dockerfile = "cross/Dockerfile.gnu" 7 | 8 | [target.x86_64-unknown-linux-gnu] 9 | dockerfile = "cross/Dockerfile.gnu" 10 | 11 | [target.aarch64-unknown-linux-musl] 12 | dockerfile = "cross/Dockerfile.musl" 13 | 14 | [target.x86_64-unknown-linux-musl] 15 | dockerfile = "cross/Dockerfile.musl" 16 | -------------------------------------------------------------------------------- /MigrationGuide.md: -------------------------------------------------------------------------------- 1 | # Migration Guide 2 | 3 | This contains information for migrating library versions. 4 | 5 | ## v0.2.0 -> v0.3.0 6 | 7 | ### libcgroups 8 | - Switched from dbus-rs to a native dbus implementation see [#2208](https://github.com/containers/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. 9 | 10 | ## v0.1.0 -> v0.2.0 11 | 12 | ### libcontainer 13 | 14 | - 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 : 15 | - rootless (module name) -> user_ns 16 | - Rootless.rootless_id_mapper -> UserNamespaceConfig.id_mapper 17 | - LibcontainerError::Rootless -> LibcontainerError::UserNamespace 18 | - ContainerBuilderImpl.rootless -> ContainerBuilderImpl.user_ns_config 19 | - ContainerArgs.rootless -> ContainerArgs.user_ns_config 20 | 21 | - 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. 22 | 23 | - 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. 24 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security and Disclosure Information Policy for the Youki Project 2 | 3 | The Youki Project follows the [Security and Disclosure Information Policy](https://github.com/containers/common/blob/main/SECURITY.md) for the Containers Projects. 4 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "fedora/33-cloud-base" 6 | config.vm.synced_folder '.', '/vagrant', disabled: true 7 | 8 | config.vm.provider "virtualbox" do |v| 9 | v.memory = 2048 10 | v.cpus = 2 11 | end 12 | config.vm.provision "shell", inline: <<-SHELL 13 | set -e -u -o pipefail 14 | yum update -y 15 | yum install -y git gcc docker wget pkg-config systemd-devel dbus-devel elfutils-libelf-devel libseccomp-devel clang-devel openssl-devel 16 | grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0" 17 | service docker start 18 | SHELL 19 | 20 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 21 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 22 | echo "export PATH=$PATH:$HOME/.cargo/bin" >> ~/.bashrc 23 | 24 | git clone https://github.com/containers/youki 25 | SHELL 26 | end 27 | -------------------------------------------------------------------------------- /Vagrantfile.containerd2youki: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | GO_VERSION = "1.20.12" 5 | CONTAINERD_VERSION = "1.7.11" 6 | 7 | Vagrant.configure("2") do |config| 8 | config.vm.box = "generic/ubuntu2204" 9 | config.vm.synced_folder '.', '/vagrant/youki', disabled: false 10 | 11 | config.vm.provider "virtualbox" do |v| 12 | v.memory = 4096 13 | v.cpus = 4 14 | end 15 | 16 | config.vm.provision "bootstrap", type: "shell" do |s| 17 | s.inline = <<-SHELL 18 | set -e -u -o pipefail 19 | export DEBIAN_FRONTEND=noninteractive 20 | apt-get update && apt-get install -y \ 21 | make \ 22 | pkg-config \ 23 | libsystemd-dev \ 24 | libdbus-glib-1-dev \ 25 | build-essential \ 26 | libelf-dev \ 27 | libseccomp-dev \ 28 | libbtrfs-dev \ 29 | btrfs-progs 30 | wget --quiet https://go.dev/dl/go#{GO_VERSION}.linux-amd64.tar.gz -O /tmp/go#{GO_VERSION}.linux-amd64.tar.gz 31 | rm -rf /usr/local/go && tar -C /usr/local -xzf /tmp/go#{GO_VERSION}.linux-amd64.tar.gz 32 | echo "export PATH=$PATH:/usr/local/go/bin" >> ~/.bashrc 33 | echo "export GOPATH=$HOME/go" >> ~/.bashrc 34 | export PATH=$PATH:$HOME/.cargo/bin:/usr/local/go/bin 35 | export GOPATH=$HOME/go 36 | 37 | git clone https://github.com/containerd/containerd \ 38 | /root/go/src/github.com/containerd/containerd -b v#{CONTAINERD_VERSION} 39 | 40 | cd /root/go/src/github.com/containerd/containerd 41 | make 42 | make binaries 43 | make install 44 | ./script/setup/install-cni 45 | ./script/setup/install-critools 46 | rm -rf /bin/runc /sbin/runc /usr/sbin/runc /usr/bin/runc 47 | ln -s /vagrant/youki/youki /usr/bin/runc 48 | SHELL 49 | end 50 | 51 | config.vm.provision "test", type: "shell" do |s| 52 | s.inline = <<-SHELL 53 | export RUNC_FLAVOR=crun 54 | cd /root/go/src/github.com/containerd/containerd/ 55 | export PATH=$PATH:$HOME/.cargo/bin:/usr/local/go/bin 56 | make TEST_RUNTIME=io.containerd.runc.v2 TESTFLAGS="-timeout 120m" integration | tee result.txt 57 | grep "FAIL: " result.txt || true 58 | SHELL 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /Vagrantfile.root: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "fedora/33-cloud-base" 6 | config.vm.synced_folder '.', '/vagrant', disabled: true 7 | config.vm.define "rootful" 8 | 9 | config.vm.provider "virtualbox" do |v| 10 | v.memory = 2048 11 | v.cpus = 2 12 | end 13 | config.vm.provision "shell", path: "./hack/set_root_login_for_vagrant.sh" 14 | config.vm.provision "shell", inline: <<-SHELL 15 | set -e -u -o pipefail 16 | yum update -y 17 | yum install -y git gcc docker wget pkg-config systemd-devel dbus-devel elfutils-libelf-devel libseccomp-devel clang-devel openssl-devel 18 | grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0" 19 | service docker start 20 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y 21 | echo "export PATH=$PATH:$HOME/.cargo/bin" >> ~/.bashrc 22 | SHELL 23 | config.ssh.username = 'root' 24 | config.ssh.insert_key = 'true' 25 | end 26 | -------------------------------------------------------------------------------- /crates/.gitignore: -------------------------------------------------------------------------------- 1 | *_bin -------------------------------------------------------------------------------- /crates/libcgroups/.gitignore: -------------------------------------------------------------------------------- 1 | debug/ 2 | target/ 3 | **/*.rs.bk 4 | 5 | -------------------------------------------------------------------------------- /crates/libcgroups/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libcgroups" 3 | version = "0.5.3" # MARK: Version 4 | description = "Library for cgroup" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | rust-version = "1.58.1" 12 | autoexamples = true 13 | keywords = ["youki", "container", "cgroups"] 14 | 15 | [features] 16 | default = ["v1", "v2", "systemd"] 17 | v1 = [] 18 | v2 = [] 19 | systemd = ["v2", "nix/socket", "nix/uio"] 20 | cgroupsv2_devices = ["rbpf", "libbpf-sys", "errno", "libc", "nix/dir"] 21 | 22 | [dependencies] 23 | nix = { version = "0.29.0", features = ["signal", "user", "fs"] } 24 | procfs = "0.17.0" 25 | oci-spec = { version = "~0.8.1", features = ["runtime"] } 26 | fixedbitset = "0.5.7" 27 | serde = { version = "1.0", features = ["derive"] } 28 | rbpf = { version = "0.3.0", optional = true } 29 | libbpf-sys = { version = "1.5.1", optional = true } 30 | errno = { version = "0.3.12", optional = true } 31 | libc = { version = "0.2.172", optional = true } 32 | thiserror = "2.0.12" 33 | tracing = { version = "0.1.41", features = ["attributes"] } 34 | 35 | [dev-dependencies] 36 | anyhow = "1.0" 37 | oci-spec = { version = "~0.8.1", features = ["proptests", "runtime"] } 38 | quickcheck = "1" 39 | mockall = { version = "0.13.1", features = [] } 40 | clap = "4.1.6" 41 | serde = { version = "1.0", features = ["derive"] } 42 | serde_json = "1.0" 43 | env_logger = "0.11" 44 | serial_test = "3.1.1" 45 | tempfile = "3" 46 | -------------------------------------------------------------------------------- /crates/libcgroups/README.md: -------------------------------------------------------------------------------- 1 | # libcgroups 2 | -------------------------------------------------------------------------------- /crates/libcgroups/examples/rules.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "allow": false, 4 | "access": "rwm" 5 | }, 6 | { 7 | "allow": true, 8 | "type": "c", 9 | "access": "rwm" 10 | }, 11 | { 12 | "allow": true, 13 | "type": "b", 14 | "major": 8, 15 | "access": "rm" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /crates/libcgroups/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Control groups provide a way of controlling groups of processes. 2 | //! Examples: controlling resource limits, execution priority, measuring resource usage, 3 | //! freezing, checkpointing and restarting groups of processes. 4 | #[cfg(test)] 5 | #[macro_use] 6 | extern crate quickcheck; 7 | 8 | #[cfg(test)] 9 | #[allow(unused_imports)] 10 | #[macro_use] 11 | extern crate mockall; 12 | 13 | mod test; 14 | 15 | pub mod common; 16 | pub mod stats; 17 | #[cfg(feature = "systemd")] 18 | pub mod systemd; 19 | #[cfg(not(feature = "systemd"))] 20 | #[path = "stub/systemd/mod.rs"] 21 | pub mod systemd; 22 | pub mod test_manager; 23 | #[cfg(feature = "v1")] 24 | pub mod v1; 25 | #[cfg(not(feature = "v1"))] 26 | #[path = "stub/v1/mod.rs"] 27 | pub mod v1; 28 | #[cfg(feature = "v2")] 29 | pub mod v2; 30 | #[cfg(not(feature = "v2"))] 31 | #[path = "stub/v2/mod.rs"] 32 | pub mod v2; 33 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/systemd/manager.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{AnyCgroupManager, CgroupManager}; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum SystemdManagerError { 5 | #[error("systemd cgroup feature is required, but was not enabled during compile time")] 6 | NotEnabled, 7 | } 8 | 9 | pub struct Manager {} 10 | 11 | impl Manager { 12 | pub fn any(self) -> AnyCgroupManager { 13 | AnyCgroupManager::Systemd(Box::new(self)) 14 | } 15 | } 16 | 17 | impl CgroupManager for Manager { 18 | type Error = SystemdManagerError; 19 | 20 | fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> { 21 | Err(SystemdManagerError::NotEnabled) 22 | } 23 | 24 | fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> { 25 | Err(SystemdManagerError::NotEnabled) 26 | } 27 | 28 | fn remove(&self) -> Result<(), Self::Error> { 29 | Err(SystemdManagerError::NotEnabled) 30 | } 31 | 32 | fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> { 33 | Err(SystemdManagerError::NotEnabled) 34 | } 35 | 36 | fn stats(&self) -> Result { 37 | Err(SystemdManagerError::NotEnabled) 38 | } 39 | 40 | fn get_all_pids(&self) -> Result, Self::Error> { 41 | Err(SystemdManagerError::NotEnabled) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/systemd/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/v1/manager.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{AnyCgroupManager, CgroupManager}; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum V1ManagerError { 5 | #[error("v1 cgroup feature is required, but was not enabled during compile time")] 6 | NotEnabled, 7 | } 8 | 9 | pub struct Manager {} 10 | 11 | impl Manager { 12 | pub fn any(self) -> AnyCgroupManager { 13 | crate::common::AnyCgroupManager::V1(self) 14 | } 15 | } 16 | 17 | impl CgroupManager for Manager { 18 | type Error = V1ManagerError; 19 | 20 | fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> { 21 | Err(V1ManagerError::NotEnabled) 22 | } 23 | 24 | fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> { 25 | Err(V1ManagerError::NotEnabled) 26 | } 27 | 28 | fn remove(&self) -> Result<(), Self::Error> { 29 | Err(V1ManagerError::NotEnabled) 30 | } 31 | 32 | fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> { 33 | Err(V1ManagerError::NotEnabled) 34 | } 35 | 36 | fn stats(&self) -> Result { 37 | Err(V1ManagerError::NotEnabled) 38 | } 39 | 40 | fn get_all_pids(&self) -> Result, Self::Error> { 41 | Err(V1ManagerError::NotEnabled) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/v1/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/v2/manager.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{AnyCgroupManager, CgroupManager}; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum V2ManagerError { 5 | #[error("v2 cgroup feature is required, but was not enabled during compile time")] 6 | NotEnabled, 7 | } 8 | 9 | pub struct Manager {} 10 | 11 | impl Manager { 12 | pub fn any(self) -> AnyCgroupManager { 13 | crate::common::AnyCgroupManager::V2(self) 14 | } 15 | } 16 | 17 | impl CgroupManager for Manager { 18 | type Error = V2ManagerError; 19 | 20 | fn add_task(&self, _pid: nix::unistd::Pid) -> Result<(), Self::Error> { 21 | Err(V2ManagerError::NotEnabled) 22 | } 23 | 24 | fn apply(&self, _controller_opt: &crate::common::ControllerOpt) -> Result<(), Self::Error> { 25 | Err(V2ManagerError::NotEnabled) 26 | } 27 | 28 | fn remove(&self) -> Result<(), Self::Error> { 29 | Err(V2ManagerError::NotEnabled) 30 | } 31 | 32 | fn freeze(&self, _state: crate::common::FreezerState) -> Result<(), Self::Error> { 33 | Err(V2ManagerError::NotEnabled) 34 | } 35 | 36 | fn stats(&self) -> Result { 37 | Err(V2ManagerError::NotEnabled) 38 | } 39 | 40 | fn get_all_pids(&self) -> Result, Self::Error> { 41 | Err(V2ManagerError::NotEnabled) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /crates/libcgroups/src/stub/v2/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | -------------------------------------------------------------------------------- /crates/libcgroups/src/systemd/controller.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::dbus_native::serialize::Variant; 4 | use crate::common::ControllerOpt; 5 | 6 | pub(super) trait Controller { 7 | type Error; 8 | 9 | fn apply( 10 | options: &ControllerOpt, 11 | systemd_version: u32, 12 | properties: &mut HashMap<&str, Variant>, 13 | ) -> Result<(), Self::Error>; 14 | } 15 | -------------------------------------------------------------------------------- /crates/libcgroups/src/systemd/controller_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub enum ControllerType { 4 | Cpu, 5 | CpuSet, 6 | Io, 7 | Memory, 8 | Pids, 9 | } 10 | 11 | impl Display for ControllerType { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | let print = match self { 14 | ControllerType::Cpu => "cpu", 15 | ControllerType::CpuSet => "cpuset", 16 | ControllerType::Io => "io", 17 | ControllerType::Memory => "memory", 18 | ControllerType::Pids => "pids", 19 | }; 20 | 21 | write!(f, "{print}") 22 | } 23 | } 24 | 25 | impl AsRef for ControllerType { 26 | fn as_ref(&self) -> &str { 27 | match self { 28 | ControllerType::Cpu => "cpu", 29 | ControllerType::CpuSet => "cpuset", 30 | ControllerType::Io => "io", 31 | ControllerType::Memory => "memory", 32 | ControllerType::Pids => "pids", 33 | } 34 | } 35 | } 36 | 37 | pub const CONTROLLER_TYPES: &[ControllerType] = &[ 38 | ControllerType::Cpu, 39 | ControllerType::CpuSet, 40 | ControllerType::Io, 41 | ControllerType::Memory, 42 | ControllerType::Pids, 43 | ]; 44 | -------------------------------------------------------------------------------- /crates/libcgroups/src/systemd/dbus_native/client.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | 4 | use super::serialize::Variant; 5 | use super::utils::SystemdClientError; 6 | 7 | pub trait SystemdClient { 8 | #[allow(dead_code)] 9 | fn is_system(&self) -> bool; 10 | 11 | fn transient_unit_exists(&self, unit_name: &str) -> bool; 12 | 13 | fn start_transient_unit( 14 | &self, 15 | container_name: &str, 16 | pid: u32, 17 | parent: &str, 18 | unit_name: &str, 19 | ) -> Result<(), SystemdClientError>; 20 | 21 | fn stop_transient_unit(&self, unit_name: &str) -> Result<(), SystemdClientError>; 22 | 23 | fn set_unit_properties( 24 | &self, 25 | unit_name: &str, 26 | properties: &HashMap<&str, Variant>, 27 | ) -> Result<(), SystemdClientError>; 28 | 29 | fn systemd_version(&self) -> Result; 30 | 31 | fn control_cgroup_root(&self) -> Result; 32 | 33 | fn add_process_to_unit( 34 | &self, 35 | unit_name: &str, 36 | subcgroup: &str, 37 | pid: u32, 38 | ) -> Result<(), SystemdClientError>; 39 | } 40 | -------------------------------------------------------------------------------- /crates/libcgroups/src/systemd/dbus_native/mod.rs: -------------------------------------------------------------------------------- 1 | // see https://dbus.freedesktop.org/doc/dbus-specification.html and 2 | // https://dbus.freedesktop.org/doc/api/html/structDBusHeader.html 3 | 4 | pub mod client; 5 | pub mod dbus; 6 | pub mod message; 7 | pub mod proxy; 8 | pub mod serialize; 9 | pub mod utils; 10 | -------------------------------------------------------------------------------- /crates/libcgroups/src/systemd/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | mod controller; 4 | pub mod controller_type; 5 | mod cpu; 6 | mod cpuset; 7 | mod dbus_native; 8 | pub mod manager; 9 | mod memory; 10 | mod pids; 11 | mod unified; 12 | 13 | /// Checks if the system was booted with systemd 14 | pub fn booted() -> bool { 15 | fs::symlink_metadata("/run/systemd/system") 16 | .map(|p| p.is_dir()) 17 | .unwrap_or_default() 18 | } 19 | 20 | #[macro_export] 21 | macro_rules! recast { 22 | ($v:ident, $t:ty) => {{ 23 | let mut buf = Vec::new(); 24 | $v.serialize(&mut buf); 25 | let mut ctr = 0; 26 | let ret = <$t>::deserialize(&buf, &mut ctr); 27 | ret 28 | }}; 29 | } 30 | -------------------------------------------------------------------------------- /crates/libcgroups/src/test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use std::io::Write; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use anyhow::{Context, Result}; 7 | 8 | pub fn setup(cgroup_file: &str) -> (tempfile::TempDir, PathBuf) { 9 | let tmp = tempfile::tempdir().expect("create temp directory for test"); 10 | let cgroup_file = set_fixture(tmp.path(), cgroup_file, "") 11 | .unwrap_or_else(|_| panic!("set test fixture for {cgroup_file}")); 12 | 13 | (tmp, cgroup_file) 14 | } 15 | 16 | pub fn set_fixture(temp_dir: &Path, filename: &str, val: &str) -> Result { 17 | let full_path = temp_dir.join(filename); 18 | 19 | std::fs::OpenOptions::new() 20 | .create(true) 21 | .write(true) 22 | .truncate(true) 23 | .open(&full_path) 24 | .with_context(|| format!("failed to open {full_path:?}"))? 25 | .write_all(val.as_bytes()) 26 | .with_context(|| format!("failed to write to {full_path:?}"))?; 27 | 28 | Ok(full_path) 29 | } 30 | -------------------------------------------------------------------------------- /crates/libcgroups/src/test_manager.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::convert::Infallible; 3 | 4 | use nix::unistd::Pid; 5 | 6 | use crate::common::{CgroupManager, ControllerOpt, FreezerState}; 7 | use crate::stats::Stats; 8 | 9 | #[derive(Debug)] 10 | pub struct TestManager { 11 | add_task_args: RefCell>, 12 | pub apply_called: RefCell, 13 | } 14 | 15 | impl Default for TestManager { 16 | fn default() -> Self { 17 | Self { 18 | add_task_args: RefCell::new(vec![]), 19 | apply_called: RefCell::new(false), 20 | } 21 | } 22 | } 23 | 24 | impl CgroupManager for TestManager { 25 | type Error = Infallible; 26 | 27 | fn add_task(&self, pid: Pid) -> Result<(), Infallible> { 28 | self.add_task_args.borrow_mut().push(pid); 29 | Ok(()) 30 | } 31 | 32 | // NOTE: The argument cannot be stored due to lifetime. 33 | fn apply(&self, _controller_opt: &ControllerOpt) -> Result<(), Infallible> { 34 | *self.apply_called.borrow_mut() = true; 35 | Ok(()) 36 | } 37 | 38 | fn remove(&self) -> Result<(), Infallible> { 39 | unimplemented!() 40 | } 41 | 42 | fn freeze(&self, _state: FreezerState) -> Result<(), Infallible> { 43 | unimplemented!() 44 | } 45 | 46 | fn stats(&self) -> Result { 47 | unimplemented!() 48 | } 49 | 50 | fn get_all_pids(&self) -> Result, Infallible> { 51 | unimplemented!() 52 | } 53 | } 54 | 55 | impl TestManager { 56 | pub fn get_add_task_args(&self) -> Vec { 57 | self.add_task_args.borrow_mut().clone() 58 | } 59 | 60 | pub fn apply_called(&self) -> bool { 61 | *self.apply_called.borrow_mut() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v1/controller.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::Path; 3 | 4 | use nix::unistd::Pid; 5 | 6 | use crate::common::{self, ControllerOpt, WrapIoResult, WrappedIoError, CGROUP_PROCS}; 7 | 8 | pub(super) trait Controller { 9 | type Error: From; 10 | type Resource; 11 | 12 | /// Adds a new task specified by its pid to the cgroup 13 | fn add_task(pid: Pid, cgroup_path: &Path) -> Result<(), Self::Error> { 14 | fs::create_dir_all(cgroup_path).wrap_create_dir(cgroup_path)?; 15 | common::write_cgroup_file(cgroup_path.join(CGROUP_PROCS), pid)?; 16 | Ok(()) 17 | } 18 | 19 | /// Applies resource restrictions to the cgroup 20 | fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error>; 21 | 22 | /// Checks if the controller needs to handle this request 23 | fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource>; 24 | } 25 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v1/controller_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] 4 | pub enum ControllerType { 5 | Cpu, 6 | CpuAcct, 7 | CpuSet, 8 | Devices, 9 | HugeTlb, 10 | Pids, 11 | PerfEvent, 12 | Memory, 13 | Blkio, 14 | NetworkPriority, 15 | NetworkClassifier, 16 | Freezer, 17 | } 18 | 19 | impl Display for ControllerType { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | let print = match *self { 22 | Self::Cpu => "cpu", 23 | Self::CpuAcct => "cpuacct", 24 | Self::CpuSet => "cpuset", 25 | Self::Devices => "devices", 26 | Self::HugeTlb => "hugetlb", 27 | Self::Pids => "pids", 28 | Self::PerfEvent => "perf_event", 29 | Self::Memory => "memory", 30 | Self::Blkio => "blkio", 31 | Self::NetworkPriority => "net_prio", 32 | Self::NetworkClassifier => "net_cls", 33 | Self::Freezer => "freezer", 34 | }; 35 | 36 | write!(f, "{print}") 37 | } 38 | } 39 | 40 | impl AsRef for ControllerType { 41 | fn as_ref(&self) -> &str { 42 | match *self { 43 | Self::Cpu => "cpu", 44 | Self::CpuAcct => "cpuacct", 45 | Self::CpuSet => "cpuset", 46 | Self::Devices => "devices", 47 | Self::HugeTlb => "hugetlb", 48 | Self::Pids => "pids", 49 | Self::PerfEvent => "perf_event", 50 | Self::Memory => "memory", 51 | Self::Blkio => "blkio", 52 | Self::NetworkPriority => "net_prio", 53 | Self::NetworkClassifier => "net_cls", 54 | Self::Freezer => "freezer", 55 | } 56 | } 57 | } 58 | 59 | pub const CONTROLLERS: &[ControllerType] = &[ 60 | ControllerType::Cpu, 61 | ControllerType::CpuAcct, 62 | ControllerType::CpuSet, 63 | ControllerType::Devices, 64 | ControllerType::HugeTlb, 65 | ControllerType::Memory, 66 | ControllerType::Pids, 67 | ControllerType::PerfEvent, 68 | ControllerType::Blkio, 69 | ControllerType::NetworkPriority, 70 | ControllerType::NetworkClassifier, 71 | ControllerType::Freezer, 72 | ]; 73 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v1/mod.rs: -------------------------------------------------------------------------------- 1 | mod blkio; 2 | mod controller; 3 | mod controller_type; 4 | mod cpu; 5 | mod cpuacct; 6 | mod cpuset; 7 | mod devices; 8 | mod freezer; 9 | mod hugetlb; 10 | pub mod manager; 11 | mod memory; 12 | mod network_classifier; 13 | mod network_priority; 14 | pub mod perf_event; 15 | mod pids; 16 | pub mod util; 17 | pub use controller_type::ControllerType; 18 | pub use manager::Manager; 19 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v1/network_classifier.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use oci_spec::runtime::LinuxNetwork; 4 | 5 | use super::controller::Controller; 6 | use crate::common::{self, ControllerOpt, WrappedIoError}; 7 | 8 | pub struct NetworkClassifier {} 9 | 10 | impl Controller for NetworkClassifier { 11 | type Error = WrappedIoError; 12 | type Resource = LinuxNetwork; 13 | 14 | fn apply(controller_opt: &ControllerOpt, cgroup_root: &Path) -> Result<(), Self::Error> { 15 | tracing::debug!("Apply NetworkClassifier cgroup config"); 16 | 17 | if let Some(network) = Self::needs_to_handle(controller_opt) { 18 | Self::apply(cgroup_root, network)?; 19 | } 20 | 21 | Ok(()) 22 | } 23 | 24 | fn needs_to_handle<'a>(controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> { 25 | controller_opt.resources.network().as_ref() 26 | } 27 | } 28 | 29 | impl NetworkClassifier { 30 | fn apply(root_path: &Path, network: &LinuxNetwork) -> Result<(), WrappedIoError> { 31 | if let Some(class_id) = network.class_id() { 32 | common::write_cgroup_file(root_path.join("net_cls.classid"), class_id)?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use oci_spec::runtime::LinuxNetworkBuilder; 42 | 43 | use super::*; 44 | use crate::test::set_fixture; 45 | 46 | #[test] 47 | fn test_apply_network_classifier() { 48 | let tmp = tempfile::tempdir().unwrap(); 49 | set_fixture(tmp.path(), "net_cls.classid", "0").expect("set fixture for classID"); 50 | 51 | let id = 0x100001u32; 52 | let network = LinuxNetworkBuilder::default() 53 | .class_id(id) 54 | .priorities(vec![]) 55 | .build() 56 | .unwrap(); 57 | 58 | NetworkClassifier::apply(tmp.path(), &network).expect("apply network classID"); 59 | 60 | let content = std::fs::read_to_string(tmp.path().join("net_cls.classid")) 61 | .expect("Read classID contents"); 62 | assert_eq!(id.to_string(), content); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v1/perf_event.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use super::controller::Controller; 4 | use crate::common::{ControllerOpt, WrappedIoError}; 5 | 6 | pub struct PerfEvent {} 7 | 8 | impl Controller for PerfEvent { 9 | type Error = WrappedIoError; 10 | type Resource = (); 11 | 12 | fn apply(_controller_opt: &ControllerOpt, _cgroup_root: &Path) -> Result<(), Self::Error> { 13 | Ok(()) 14 | } 15 | //no need to handle any case 16 | fn needs_to_handle<'a>(_controller_opt: &'a ControllerOpt) -> Option<&'a Self::Resource> { 17 | None 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use std::fs; 24 | 25 | use nix::unistd::Pid; 26 | 27 | use super::*; 28 | use crate::common::CGROUP_PROCS; 29 | use crate::test::setup; 30 | 31 | #[test] 32 | fn test_add_task() { 33 | let (tmp, procs) = setup(CGROUP_PROCS); 34 | let pid = Pid::from_raw(1000); 35 | 36 | PerfEvent::add_task(pid, tmp.path()).expect("apply perf_event"); 37 | 38 | let content = fs::read_to_string(procs) 39 | .unwrap_or_else(|_| panic!("read {CGROUP_PROCS} file content")); 40 | assert_eq!(content, "1000"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/controller.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use crate::common::ControllerOpt; 4 | 5 | pub(super) trait Controller { 6 | type Error; 7 | 8 | fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error>; 9 | } 10 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/controller_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 4 | pub enum ControllerType { 5 | Cpu, 6 | CpuSet, 7 | Io, 8 | Memory, 9 | HugeTlb, 10 | Pids, 11 | } 12 | 13 | impl Display for ControllerType { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | let print = match self { 16 | Self::Cpu => "cpu", 17 | Self::CpuSet => "cpuset", 18 | Self::Io => "io", 19 | Self::Memory => "memory", 20 | Self::HugeTlb => "hugetlb", 21 | Self::Pids => "pids", 22 | }; 23 | 24 | write!(f, "{print}") 25 | } 26 | } 27 | 28 | pub const CONTROLLER_TYPES: &[ControllerType] = &[ 29 | ControllerType::Cpu, 30 | ControllerType::CpuSet, 31 | ControllerType::HugeTlb, 32 | ControllerType::Io, 33 | ControllerType::Memory, 34 | ControllerType::Pids, 35 | ]; 36 | 37 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 38 | pub enum PseudoControllerType { 39 | Devices, 40 | Freezer, 41 | Unified, 42 | } 43 | 44 | impl Display for PseudoControllerType { 45 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 46 | let print = match self { 47 | Self::Devices => "devices", 48 | Self::Freezer => "freezer", 49 | Self::Unified => "unified", 50 | }; 51 | 52 | write!(f, "{print}") 53 | } 54 | } 55 | 56 | pub const PSEUDO_CONTROLLER_TYPES: &[PseudoControllerType] = &[ 57 | PseudoControllerType::Devices, 58 | PseudoControllerType::Freezer, 59 | PseudoControllerType::Unified, 60 | ]; 61 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/cpuset.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use oci_spec::runtime::LinuxCpu; 4 | 5 | use super::controller::Controller; 6 | use crate::common::{self, ControllerOpt, WrappedIoError}; 7 | 8 | const CGROUP_CPUSET_CPUS: &str = "cpuset.cpus"; 9 | const CGROUP_CPUSET_MEMS: &str = "cpuset.mems"; 10 | 11 | pub struct CpuSet {} 12 | 13 | impl Controller for CpuSet { 14 | type Error = WrappedIoError; 15 | 16 | fn apply(controller_opt: &ControllerOpt, cgroup_path: &Path) -> Result<(), Self::Error> { 17 | if let Some(cpuset) = &controller_opt.resources.cpu() { 18 | Self::apply(cgroup_path, cpuset)?; 19 | } 20 | 21 | Ok(()) 22 | } 23 | } 24 | 25 | impl CpuSet { 26 | fn apply(path: &Path, cpuset: &LinuxCpu) -> Result<(), WrappedIoError> { 27 | if let Some(cpus) = &cpuset.cpus() { 28 | common::write_cgroup_file_str(path.join(CGROUP_CPUSET_CPUS), cpus)?; 29 | } 30 | 31 | if let Some(mems) = &cpuset.mems() { 32 | common::write_cgroup_file_str(path.join(CGROUP_CPUSET_MEMS), mems)?; 33 | } 34 | 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use std::fs; 42 | 43 | use oci_spec::runtime::LinuxCpuBuilder; 44 | 45 | use super::*; 46 | use crate::test::setup; 47 | 48 | #[test] 49 | fn test_set_cpus() { 50 | // arrange 51 | let (tmp, cpus) = setup(CGROUP_CPUSET_CPUS); 52 | let cpuset = LinuxCpuBuilder::default() 53 | .cpus("1-3".to_owned()) 54 | .build() 55 | .unwrap(); 56 | 57 | // act 58 | CpuSet::apply(tmp.path(), &cpuset).expect("apply cpuset"); 59 | 60 | // assert 61 | let content = fs::read_to_string(cpus) 62 | .unwrap_or_else(|_| panic!("read {CGROUP_CPUSET_CPUS} file content")); 63 | assert_eq!(content, "1-3"); 64 | } 65 | 66 | #[test] 67 | fn test_set_mems() { 68 | // arrange 69 | let (tmp, mems) = setup(CGROUP_CPUSET_MEMS); 70 | let cpuset = LinuxCpuBuilder::default() 71 | .mems("1-3".to_owned()) 72 | .build() 73 | .unwrap(); 74 | 75 | // act 76 | CpuSet::apply(tmp.path(), &cpuset).expect("apply cpuset"); 77 | 78 | // assert 79 | let content = fs::read_to_string(mems) 80 | .unwrap_or_else(|_| panic!("read {CGROUP_CPUSET_MEMS} file content")); 81 | assert_eq!(content, "1-3"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/devices/mocks.rs: -------------------------------------------------------------------------------- 1 | // Here we duplicate the signatures of external functions and apply 2 | // the mockall::automock macro to generate mock modules for use 3 | // in tests, allowing us to exercise code paths without eg making syscalls 4 | 5 | #[cfg_attr(test, automock())] 6 | pub mod libc { 7 | #[cfg(target_env = "musl")] 8 | #[allow(non_camel_case_types)] 9 | pub type __rlimit_resource_t = libc::c_int; 10 | 11 | #[cfg(not(target_env = "musl"))] 12 | #[allow(non_camel_case_types)] 13 | pub type __rlimit_resource_t = libc::__rlimit_resource_t; 14 | 15 | pub fn setrlimit(_resource: __rlimit_resource_t, _rlim: *const libc::rlimit) -> libc::c_int { 16 | unimplemented!(); 17 | } 18 | } 19 | 20 | #[cfg_attr(test, automock())] 21 | pub mod libbpf_sys { 22 | pub fn bpf_prog_load( 23 | _type_: libbpf_sys::bpf_prog_type, 24 | _name: *const ::std::os::raw::c_char, 25 | _license: *const ::std::os::raw::c_char, 26 | _insns: *const libbpf_sys::bpf_insn, 27 | _insns_cnt: libbpf_sys::size_t, 28 | _opts: *const libbpf_sys::bpf_prog_load_opts, 29 | ) -> ::std::os::raw::c_int { 30 | unimplemented!(); 31 | } 32 | 33 | pub fn bpf_prog_query( 34 | _target_fd: ::std::os::raw::c_int, 35 | _type_: libbpf_sys::bpf_attach_type, 36 | _query_flags: libbpf_sys::__u32, 37 | _attach_flags: *mut libbpf_sys::__u32, 38 | _prog_ids: *mut libbpf_sys::__u32, 39 | _prog_cnt: *mut libbpf_sys::__u32, 40 | ) -> ::std::os::raw::c_int { 41 | unimplemented!(); 42 | } 43 | 44 | pub fn bpf_prog_get_fd_by_id(_id: libbpf_sys::__u32) -> ::std::os::raw::c_int { 45 | unimplemented!(); 46 | } 47 | 48 | pub fn bpf_prog_detach2( 49 | _prog_fd: ::std::os::raw::c_int, 50 | _attachable_fd: ::std::os::raw::c_int, 51 | _type_: libbpf_sys::bpf_attach_type, 52 | ) -> ::std::os::raw::c_int { 53 | unimplemented!(); 54 | } 55 | 56 | pub fn bpf_prog_attach( 57 | _prog_fd: ::std::os::raw::c_int, 58 | _attachable_fd: ::std::os::raw::c_int, 59 | _type_: libbpf_sys::bpf_attach_type, 60 | _flags: ::std::os::raw::c_uint, 61 | ) -> ::std::os::raw::c_int { 62 | unimplemented!(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/devices/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bpf; 2 | pub mod controller; 3 | pub mod emulator; 4 | pub mod program; 5 | 6 | #[cfg(test)] 7 | #[allow(clippy::too_many_arguments)] 8 | pub mod mocks; 9 | 10 | pub use controller::Devices; 11 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/mod.rs: -------------------------------------------------------------------------------- 1 | mod controller; 2 | pub mod controller_type; 3 | mod cpu; 4 | mod cpuset; 5 | #[cfg(feature = "cgroupsv2_devices")] 6 | pub mod devices; 7 | mod freezer; 8 | mod hugetlb; 9 | mod io; 10 | pub mod manager; 11 | mod memory; 12 | mod pids; 13 | mod unified; 14 | pub mod util; 15 | -------------------------------------------------------------------------------- /crates/libcgroups/src/v2/util.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use procfs::process::Process; 4 | use procfs::ProcError; 5 | 6 | use super::controller_type::ControllerType; 7 | use crate::common::{self, WrappedIoError}; 8 | 9 | pub const CGROUP_CONTROLLERS: &str = "cgroup.controllers"; 10 | pub const CGROUP_SUBTREE_CONTROL: &str = "cgroup.subtree_control"; 11 | 12 | #[derive(thiserror::Error, Debug)] 13 | pub enum V2UtilError { 14 | #[error("io error: {0}")] 15 | WrappedIo(#[from] WrappedIoError), 16 | #[error("proc error: {0}")] 17 | Proc(#[from] ProcError), 18 | #[error("could not find mountpoint for unified")] 19 | CouldNotFind, 20 | #[error("cannot get available controllers. {0} does not exist")] 21 | DoesNotExist(PathBuf), 22 | } 23 | 24 | // Reads the `/proc/self/mountinfo` to get the mount point of this cgroup 25 | pub fn get_unified_mount_point() -> Result { 26 | Process::myself()? 27 | .mountinfo()? 28 | .into_iter() 29 | .find(|m| m.fs_type == "cgroup2") 30 | .map(|m| m.mount_point) 31 | .ok_or(V2UtilError::CouldNotFind) 32 | } 33 | 34 | /// Reads the `{root_path}/cgroup.controllers` file to get the list of the controllers that are 35 | /// available in this cgroup 36 | pub fn get_available_controllers>( 37 | root_path: P, 38 | ) -> Result, V2UtilError> { 39 | let root_path = root_path.as_ref(); 40 | let controllers_path = root_path.join(CGROUP_CONTROLLERS); 41 | if !controllers_path.exists() { 42 | return Err(V2UtilError::DoesNotExist(controllers_path)); 43 | } 44 | 45 | let mut controllers = Vec::new(); 46 | for controller in common::read_cgroup_file(controllers_path)?.split_whitespace() { 47 | match controller { 48 | "cpu" => controllers.push(ControllerType::Cpu), 49 | "cpuset" => controllers.push(ControllerType::CpuSet), 50 | "hugetlb" => controllers.push(ControllerType::HugeTlb), 51 | "io" => controllers.push(ControllerType::Io), 52 | "memory" => controllers.push(ControllerType::Memory), 53 | "pids" => controllers.push(ControllerType::Pids), 54 | tpe => tracing::warn!("Controller {} is not yet implemented.", tpe), 55 | } 56 | } 57 | 58 | Ok(controllers) 59 | } 60 | -------------------------------------------------------------------------------- /crates/libcontainer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libcontainer" 3 | version = "0.5.3" # MARK: Version 4 | description = "Library for container control" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | rust-version = "1.63.0" 12 | keywords = ["youki", "container", "cgroups"] 13 | 14 | [features] 15 | default = ["systemd", "v2", "v1", "libseccomp"] 16 | libseccomp = ["dep:libseccomp"] 17 | systemd = ["libcgroups/systemd", "v2"] 18 | v2 = ["libcgroups/v2"] 19 | v1 = ["libcgroups/v1"] 20 | cgroupsv2_devices = ["libcgroups/cgroupsv2_devices"] 21 | 22 | [dependencies] 23 | caps = "0.5.5" 24 | chrono = { version = "0.4", default-features = false, features = [ 25 | "clock", 26 | "serde", 27 | ] } 28 | fastrand = "^2.3.0" 29 | libc = "0.2.172" 30 | nix = { version = "0.29.0", features = [ 31 | "socket", 32 | "sched", 33 | "mount", 34 | "mman", 35 | "resource", 36 | "dir", 37 | "term", 38 | "hostname", 39 | ] } 40 | oci-spec = { version = "0.8.1", features = ["runtime"] } 41 | once_cell = "1.21.3" 42 | procfs = "0.17.0" 43 | prctl = "1.0.0" 44 | libcgroups = { path = "../libcgroups", default-features = false, version = "0.5.3" } # MARK: Version 45 | libseccomp = { version = "0.3.0", optional = true } 46 | serde = { version = "1.0", features = ["derive"] } 47 | serde_json = "1.0" 48 | rust-criu = "0.4.0" 49 | regex = { version = "1.10.6", default-features = false, features = ["std", "unicode-perl"] } 50 | thiserror = "2.0.12" 51 | tracing = { version = "0.1.41", features = ["attributes"] } 52 | safe-path = "0.1.0" 53 | nc = "0.9.6" 54 | 55 | [dev-dependencies] 56 | oci-spec = { version = "~0.8.1", features = ["proptests", "runtime"] } 57 | quickcheck = "1" 58 | serial_test = "3.1.1" 59 | tempfile = "3" 60 | anyhow = "1.0" 61 | rand = "0.9.1" 62 | scopeguard = "1" 63 | -------------------------------------------------------------------------------- /crates/libcontainer/README.md: -------------------------------------------------------------------------------- 1 | # libcontainer 2 | 3 | ### Building with musl 4 | 5 | In 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. 6 | 7 | Do 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. 8 | 9 | Next you will also need the `+nightly` flags when building with `rustup` and `cargo`. 10 | 11 | ```bash 12 | # Add rustup +nightly musl to toolchain 13 | rustup +nightly target add $(uname -m)-unknown-linux-musl 14 | 15 | # Build rustup +nightly stdlib with musl 16 | rustup +nightly toolchain install nightly-$(uname -m)-unknown-linux-musl 17 | 18 | # Build musl standard library 19 | cargo +nightly build -Zbuild-std --target $(uname -m)-unknown-linux-musl --no-default-features -F v2 20 | 21 | cargo +nightly build --target $(uname -m)-unknown-linux-musl --no-default-features -F v2 22 | ``` 23 | -------------------------------------------------------------------------------- /crates/libcontainer/src/apparmor.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self}; 2 | use std::path::Path; 3 | 4 | use crate::utils; 5 | 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum AppArmorError { 8 | #[error("failed to apply AppArmor profile")] 9 | ActivateProfile { 10 | path: std::path::PathBuf, 11 | profile: String, 12 | source: std::io::Error, 13 | }, 14 | #[error(transparent)] 15 | EnsureProcfs(#[from] utils::EnsureProcfsError), 16 | } 17 | 18 | type Result = std::result::Result; 19 | 20 | const ENABLED_PARAMETER_PATH: &str = "/sys/module/apparmor/parameters/enabled"; 21 | 22 | /// Checks if AppArmor has been enabled on the system. 23 | pub fn is_enabled() -> std::result::Result { 24 | let aa_enabled = fs::read_to_string(ENABLED_PARAMETER_PATH)?; 25 | Ok(aa_enabled.starts_with('Y')) 26 | } 27 | 28 | /// Applies an AppArmor profile to the container. 29 | pub fn apply_profile(profile: &str) -> Result<()> { 30 | if profile.is_empty() { 31 | return Ok(()); 32 | } 33 | 34 | // Try the module specific subdirectory. This is the recommended way to configure 35 | // LSMs since Linux 5.1. AppArmor has such a directory since Linux 5.8. 36 | if activate_profile(Path::new("/proc/self/attr/apparmor/exec"), profile).is_ok() { 37 | return Ok(()); 38 | } 39 | 40 | // try the legacy interface 41 | activate_profile(Path::new("/proc/self/attr/exec"), profile) 42 | } 43 | 44 | fn activate_profile(path: &Path, profile: &str) -> Result<()> { 45 | utils::ensure_procfs(path).map_err(AppArmorError::EnsureProcfs)?; 46 | fs::write(path, format!("exec {profile}")).map_err(|err| AppArmorError::ActivateProfile { 47 | path: path.to_owned(), 48 | profile: profile.to_owned(), 49 | source: err, 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /crates/libcontainer/src/container/container_events.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | use std::time::Duration; 3 | 4 | use libcgroups::common::CgroupManager; 5 | 6 | use super::{Container, ContainerStatus}; 7 | use crate::error::LibcontainerError; 8 | 9 | impl Container { 10 | /// Displays container events 11 | /// 12 | /// # Example 13 | /// 14 | /// ```no_run 15 | /// use libcontainer::container::builder::ContainerBuilder; 16 | /// use libcontainer::syscall::syscall::SyscallType; 17 | /// 18 | /// # fn main() -> anyhow::Result<()> { 19 | /// let mut container = ContainerBuilder::new( 20 | /// "74f1a4cb3801".to_owned(), 21 | /// SyscallType::default(), 22 | /// ) 23 | /// .as_init("/var/run/docker/bundle") 24 | /// .build()?; 25 | /// 26 | /// container.events(5000, false)?; 27 | /// # Ok(()) 28 | /// # } 29 | /// ``` 30 | pub fn events(&mut self, interval: u32, stats: bool) -> Result<(), LibcontainerError> { 31 | self.refresh_status()?; 32 | if !self.state.status.eq(&ContainerStatus::Running) { 33 | tracing::error!(id = ?self.id(), status = ?self.state.status, "container is not running"); 34 | return Err(LibcontainerError::IncorrectStatus); 35 | } 36 | 37 | let cgroup_manager = 38 | libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig { 39 | cgroup_path: self.spec()?.cgroup_path, 40 | systemd_cgroup: self.systemd(), 41 | container_name: self.id().to_string(), 42 | })?; 43 | match stats { 44 | true => { 45 | let stats = cgroup_manager.stats()?; 46 | println!( 47 | "{}", 48 | serde_json::to_string_pretty(&stats) 49 | .map_err(LibcontainerError::OtherSerialization)? 50 | ); 51 | } 52 | false => loop { 53 | let stats = cgroup_manager.stats()?; 54 | println!( 55 | "{}", 56 | serde_json::to_string_pretty(&stats) 57 | .map_err(LibcontainerError::OtherSerialization)? 58 | ); 59 | thread::sleep(Duration::from_secs(interval as u64)); 60 | }, 61 | } 62 | 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/libcontainer/src/container/container_pause.rs: -------------------------------------------------------------------------------- 1 | use libcgroups::common::{CgroupManager, FreezerState}; 2 | 3 | use super::{Container, ContainerStatus}; 4 | use crate::error::LibcontainerError; 5 | 6 | impl Container { 7 | /// Suspends all processes within the container 8 | /// 9 | /// # Example 10 | /// 11 | /// ```no_run 12 | /// use libcontainer::container::builder::ContainerBuilder; 13 | /// use libcontainer::syscall::syscall::SyscallType; 14 | /// 15 | /// # fn main() -> anyhow::Result<()> { 16 | /// let mut container = ContainerBuilder::new( 17 | /// "74f1a4cb3801".to_owned(), 18 | /// SyscallType::default(), 19 | /// ) 20 | /// .as_init("/var/run/docker/bundle") 21 | /// .build()?; 22 | /// 23 | /// container.pause()?; 24 | /// # Ok(()) 25 | /// # } 26 | /// ``` 27 | pub fn pause(&mut self) -> Result<(), LibcontainerError> { 28 | self.refresh_status()?; 29 | 30 | if !self.can_pause() { 31 | tracing::error!(status = ?self.status(), id = ?self.id(), "cannot pause container"); 32 | return Err(LibcontainerError::IncorrectStatus); 33 | } 34 | 35 | let cmanager = 36 | libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig { 37 | cgroup_path: self.spec()?.cgroup_path, 38 | systemd_cgroup: self.systemd(), 39 | container_name: self.id().to_string(), 40 | })?; 41 | cmanager.freeze(FreezerState::Frozen)?; 42 | 43 | tracing::debug!("saving paused status"); 44 | self.set_status(ContainerStatus::Paused).save()?; 45 | 46 | tracing::debug!("container {} paused", self.id()); 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/libcontainer/src/container/container_resume.rs: -------------------------------------------------------------------------------- 1 | use libcgroups::common::{CgroupManager, FreezerState}; 2 | 3 | use super::{Container, ContainerStatus}; 4 | use crate::error::LibcontainerError; 5 | 6 | impl Container { 7 | /// Resumes all processes within the container 8 | /// 9 | /// # Example 10 | /// 11 | /// ```no_run 12 | /// use libcontainer::container::builder::ContainerBuilder; 13 | /// use libcontainer::syscall::syscall::SyscallType; 14 | /// 15 | /// # fn main() -> anyhow::Result<()> { 16 | /// let mut container = ContainerBuilder::new( 17 | /// "74f1a4cb3801".to_owned(), 18 | /// SyscallType::default(), 19 | /// ) 20 | /// .as_init("/var/run/docker/bundle") 21 | /// .build()?; 22 | /// 23 | /// container.resume()?; 24 | /// # Ok(()) 25 | /// # } 26 | /// ``` 27 | pub fn resume(&mut self) -> Result<(), LibcontainerError> { 28 | self.refresh_status()?; 29 | // check if container can be resumed : 30 | // for example, a running process cannot be resumed 31 | if !self.can_resume() { 32 | tracing::error!(status = ?self.status(), id = ?self.id(), "cannot resume container"); 33 | return Err(LibcontainerError::IncorrectStatus); 34 | } 35 | 36 | let cmanager = 37 | libcgroups::common::create_cgroup_manager(libcgroups::common::CgroupConfig { 38 | cgroup_path: self.spec()?.cgroup_path, 39 | systemd_cgroup: self.systemd(), 40 | container_name: self.id().to_string(), 41 | })?; 42 | // resume the frozen container 43 | cmanager.freeze(FreezerState::Thawed)?; 44 | 45 | tracing::debug!("saving running status"); 46 | self.set_status(ContainerStatus::Running).save()?; 47 | 48 | tracing::debug!("container {} resumed", self.id()); 49 | Ok(()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/libcontainer/src/container/mod.rs: -------------------------------------------------------------------------------- 1 | //! Container management 2 | /// This crate is responsible for the creation of containers. It provides a builder that can 3 | /// be used to configure and create containers. We distinguish between an init container for which 4 | /// namespaces and cgroups will be created (usually) and a tenant container process that will move 5 | /// into the existing namespaces and cgroups of the initial container process (e.g. used to implement 6 | /// the exec command). 7 | pub mod builder; 8 | mod builder_impl; 9 | #[allow(clippy::module_inception)] 10 | mod container; 11 | mod container_checkpoint; 12 | mod container_delete; 13 | mod container_events; 14 | mod container_kill; 15 | mod container_pause; 16 | mod container_resume; 17 | mod container_start; 18 | pub mod init_builder; 19 | pub mod state; 20 | pub mod tenant_builder; 21 | pub use container::{CheckpointOptions, Container}; 22 | pub use container_checkpoint::CheckpointError; 23 | pub use state::{ContainerProcessState, ContainerStatus, State}; 24 | -------------------------------------------------------------------------------- /crates/libcontainer/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod apparmor; 2 | pub mod capabilities; 3 | pub mod channel; 4 | pub mod config; 5 | pub mod container; 6 | pub mod error; 7 | pub mod hooks; 8 | pub mod namespaces; 9 | pub mod notify_socket; 10 | pub mod process; 11 | pub mod rootfs; 12 | #[cfg(feature = "libseccomp")] 13 | pub mod seccomp; 14 | pub mod signal; 15 | pub mod syscall; 16 | pub mod test_utils; 17 | pub mod tty; 18 | pub mod user_ns; 19 | pub mod utils; 20 | pub mod workload; 21 | 22 | // Because the `libcontainer` api uses the oci_spec who resides in a different 23 | // crate, we re-export the version of oci_spec this crate uses. 24 | // Ref: https://github.com/containers/youki/issues/2066 25 | // Ref: https://github.com/rust-lang/api-guidelines/discussions/176 26 | pub use oci_spec; 27 | -------------------------------------------------------------------------------- /crates/libcontainer/src/process/args.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::prelude::RawFd; 2 | use std::path::PathBuf; 3 | use std::rc::Rc; 4 | 5 | use libcgroups::common::CgroupConfig; 6 | use oci_spec::runtime::Spec; 7 | 8 | use crate::container::Container; 9 | use crate::notify_socket::NotifyListener; 10 | use crate::syscall::syscall::SyscallType; 11 | use crate::user_ns::UserNamespaceConfig; 12 | use crate::workload::Executor; 13 | #[derive(Debug, Copy, Clone)] 14 | pub enum ContainerType { 15 | InitContainer, 16 | TenantContainer { exec_notify_fd: RawFd }, 17 | } 18 | 19 | #[derive(Clone)] 20 | pub struct ContainerArgs { 21 | /// Indicates if an init or a tenant container should be created 22 | pub container_type: ContainerType, 23 | /// Interface to operating system primitives 24 | pub syscall: SyscallType, 25 | /// OCI compliant runtime spec 26 | pub spec: Rc, 27 | /// Root filesystem of the container 28 | pub rootfs: PathBuf, 29 | /// Socket to communicate the file descriptor of the ptty 30 | pub console_socket: Option, 31 | /// The Unix Domain Socket to communicate container start 32 | pub notify_listener: NotifyListener, 33 | /// File descriptors preserved/passed to the container init process. 34 | pub preserve_fds: i32, 35 | /// Container state 36 | pub container: Option, 37 | /// Options for new namespace creation 38 | pub user_ns_config: Option, 39 | /// Cgroup Manager Config 40 | pub cgroup_config: CgroupConfig, 41 | /// If the container is to be run in detached mode 42 | pub detached: bool, 43 | /// Manage the functions that actually run on the container 44 | pub executor: Box, 45 | /// If do not use pivot root to jail process inside rootfs 46 | pub no_pivot: bool, 47 | // RawFd set to stdin of the container init process. 48 | pub stdin: Option, 49 | // RawFd set to stdout of the container init process. 50 | pub stdout: Option, 51 | // RawFd set to stderr of the container init process. 52 | pub stderr: Option, 53 | // Indicate if the init process should be a sibling of the main process. 54 | pub as_sibling: bool, 55 | } 56 | -------------------------------------------------------------------------------- /crates/libcontainer/src/process/init/context.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::Path; 3 | 4 | use oci_spec::runtime; 5 | 6 | use super::Result; 7 | use crate::container::Container; 8 | use crate::error::MissingSpecError; 9 | use crate::namespaces::Namespaces; 10 | use crate::process::args::ContainerArgs; 11 | use crate::syscall::Syscall; 12 | use crate::{notify_socket, utils}; 13 | 14 | pub(crate) struct InitContext<'a> { 15 | pub(crate) spec: &'a runtime::Spec, 16 | pub(crate) linux: &'a runtime::Linux, 17 | pub(crate) process: &'a runtime::Process, 18 | pub(crate) rootfs: &'a Path, 19 | pub(crate) envs: HashMap, 20 | pub(crate) ns: Namespaces, 21 | pub(crate) syscall: Box, 22 | pub(crate) notify_listener: &'a notify_socket::NotifyListener, 23 | pub(crate) hooks: Option<&'a runtime::Hooks>, 24 | pub(crate) container: Option<&'a Container>, 25 | pub(crate) rootfs_ro: bool, 26 | } 27 | 28 | impl<'a> InitContext<'a> { 29 | pub fn try_from(args: &'a ContainerArgs) -> Result { 30 | let spec = args.spec.as_ref(); 31 | let linux = spec.linux().as_ref().ok_or(MissingSpecError::Linux)?; 32 | let process = spec.process().as_ref().ok_or(MissingSpecError::Process)?; 33 | let envs: HashMap = 34 | utils::parse_env(process.env().as_ref().unwrap_or(&vec![])); 35 | let rootfs = spec.root().as_ref().ok_or(MissingSpecError::Root)?; 36 | let rootfs_ro = rootfs.readonly().unwrap_or(false); 37 | 38 | Ok(Self { 39 | spec, 40 | linux, 41 | process, 42 | rootfs: &args.rootfs, 43 | envs, 44 | rootfs_ro, 45 | ns: Namespaces::try_from(linux.namespaces().as_ref())?, 46 | syscall: args.syscall.create_syscall(), 47 | notify_listener: &args.notify_listener, 48 | hooks: spec.hooks().as_ref(), 49 | container: args.container.as_ref(), 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/libcontainer/src/process/init/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | pub mod error; 3 | pub mod process; 4 | 5 | pub use process::container_init_process; 6 | 7 | type Result = std::result::Result; 8 | -------------------------------------------------------------------------------- /crates/libcontainer/src/process/message.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Used as a wrapper for messages to be sent between child and parent processes 6 | #[derive(Debug, Serialize, Deserialize, Clone)] 7 | pub enum Message { 8 | IntermediateReady(i32), 9 | InitReady, 10 | WriteMapping, 11 | MappingWritten, 12 | SeccompNotify, 13 | SeccompNotifyDone, 14 | ExecFailed(String), 15 | OtherError(String), 16 | } 17 | 18 | impl fmt::Display for Message { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | Message::IntermediateReady(pid) => write!(f, "IntermediateReady({})", pid), 22 | Message::InitReady => write!(f, "InitReady"), 23 | Message::WriteMapping => write!(f, "WriteMapping"), 24 | Message::MappingWritten => write!(f, "MappingWritten"), 25 | Message::SeccompNotify => write!(f, "SeccompNotify"), 26 | Message::SeccompNotifyDone => write!(f, "SeccompNotifyDone"), 27 | Message::ExecFailed(s) => write!(f, "ExecFailed({})", s), 28 | Message::OtherError(s) => write!(f, "OtherError({})", s), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/libcontainer/src/process/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides a thin wrapper around fork syscall, 2 | //! with enums and functions specific to youki implemented 3 | 4 | pub mod args; 5 | pub mod channel; 6 | pub mod container_intermediate_process; 7 | pub mod container_main_process; 8 | mod fork; 9 | pub mod init; 10 | pub mod intel_rdt; 11 | mod message; 12 | #[cfg(feature = "libseccomp")] 13 | mod seccomp_listener; 14 | -------------------------------------------------------------------------------- /crates/libcontainer/src/rootfs/mod.rs: -------------------------------------------------------------------------------- 1 | //! During kernel initialization, a minimal replica of the ramfs filesystem is 2 | //! loaded, called rootfs. Most systems mount another filesystem over it 3 | 4 | #[allow(clippy::module_inception)] 5 | pub(crate) mod rootfs; 6 | pub use rootfs::RootFS; 7 | 8 | pub mod device; 9 | pub use device::Device; 10 | 11 | pub(super) mod mount; 12 | pub(super) mod symlink; 13 | 14 | pub mod utils; 15 | 16 | #[derive(Debug, thiserror::Error)] 17 | pub enum RootfsError { 18 | #[error("failed syscall")] 19 | Syscall(#[from] crate::syscall::SyscallError), 20 | #[error(transparent)] 21 | MissingSpec(#[from] crate::error::MissingSpecError), 22 | #[error("unknown rootfs propagation")] 23 | UnknownRootfsPropagation(String), 24 | #[error(transparent)] 25 | Symlink(#[from] symlink::SymlinkError), 26 | #[error(transparent)] 27 | Mount(#[from] mount::MountError), 28 | #[error(transparent)] 29 | Device(#[from] device::DeviceError), 30 | } 31 | 32 | type Result = std::result::Result; 33 | -------------------------------------------------------------------------------- /crates/libcontainer/src/syscall/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains a wrapper of syscalls for unit tests 2 | //! This provides a uniform interface for rest of Youki 3 | //! to call syscalls required for container management 4 | 5 | pub mod linux; 6 | #[allow(clippy::module_inception)] 7 | pub mod syscall; 8 | pub mod test; 9 | 10 | pub use syscall::Syscall; 11 | #[derive(Debug, thiserror::Error)] 12 | pub enum SyscallError { 13 | #[error("unexpected mount attr option: {0}")] 14 | UnexpectedMountRecursiveOption(String), 15 | #[error(transparent)] 16 | Nix(#[from] nix::Error), 17 | #[error(transparent)] 18 | IO(#[from] std::io::Error), 19 | #[error("failed to set capabilities: {0}")] 20 | SetCaps(#[from] caps::errors::CapsError), 21 | } 22 | 23 | type Result = std::result::Result; 24 | -------------------------------------------------------------------------------- /crates/liboci-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "liboci-cli" 3 | version = "0.5.3" # MARK: Version 4 | description = "Parse command line arguments for OCI container runtimes" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | keywords = ["youki", "container", "oci"] 12 | 13 | [dependencies.clap] 14 | version = "4.1.6" 15 | default-features = false 16 | features = ["std", "suggestions", "derive", "cargo", "help", "usage", "error-context"] 17 | -------------------------------------------------------------------------------- /crates/liboci-cli/README.md: -------------------------------------------------------------------------------- 1 | # liboci-cli 2 | 3 | This is a crate to parse command line arguments for OCI container 4 | runtimes as specified in the [OCI Runtime Command Line 5 | Interface](https://github.com/opencontainers/runtime-tools/blob/master/docs/command-line-interface.md). 6 | 7 | ## Implemented subcommands 8 | 9 | | Command | liboci-cli | CLI Specification | runc | crun | youki | 10 | | :--------: | :--------: | :---------------: | :--: | :--: | :---: | 11 | | create | ✅ | ✅ | ✅ | ✅ | ✅ | 12 | | start | ✅ | ✅ | ✅ | ✅ | ✅ | 13 | | state | ✅ | ✅ | ✅ | ✅ | ✅ | 14 | | kill | ✅ | ✅ | ✅ | ✅ | ✅ | 15 | | delete | ✅ | ✅ | ✅ | ✅ | ✅ | 16 | | checkpoint | | | ✅ | ✅ | | 17 | | events | ✅ | | ✅ | | ✅ | 18 | | exec | ✅ | | ✅ | ✅ | ✅ | 19 | | features | ✅ | | ✅ | | | 20 | | list | ✅ | | ✅ | ✅ | ✅ | 21 | | pause | ✅ | | ✅ | ✅ | ✅ | 22 | | ps | ✅ | | ✅ | ✅ | ✅ | 23 | | restore | | | ✅ | ✅ | | 24 | | resume | ✅ | | ✅ | ✅ | ✅ | 25 | | run | ✅ | | ✅ | ✅ | ✅ | 26 | | spec | ✅ | | ✅ | ✅ | ✅ | 27 | | update | | | ✅ | ✅ | | 28 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/checkpoint.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | /// Checkpoint a running container 6 | /// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-checkpoint.8.md 7 | #[derive(Parser, Debug)] 8 | pub struct Checkpoint { 9 | /// Path for saving criu image files 10 | #[clap(long, default_value = "checkpoint")] 11 | pub image_path: PathBuf, 12 | /// Path for saving work files and logs 13 | #[clap(long)] 14 | pub work_path: Option, 15 | /// Path for previous criu image file in pre-dump 16 | #[clap(long)] 17 | pub parent_path: Option, 18 | /// Leave the process running after checkpointing 19 | #[clap(long)] 20 | pub leave_running: bool, 21 | /// Allow open tcp connections 22 | #[clap(long)] 23 | pub tcp_established: bool, 24 | /// Allow external unix sockets 25 | #[clap(long)] 26 | pub ext_unix_sk: bool, 27 | /// Allow shell jobs 28 | #[clap(long)] 29 | pub shell_job: bool, 30 | /// Use lazy migration mechanism 31 | #[clap(long)] 32 | pub lazy_pages: bool, 33 | /// Pass a file descriptor fd to criu 34 | #[clap(long)] 35 | pub status_fd: Option, // TODO: Is u32 the right type? 36 | /// Start a page server at the given URL 37 | #[clap(long)] 38 | pub page_server: Option, 39 | /// Allow file locks 40 | #[clap(long)] 41 | pub file_locks: bool, 42 | /// Do a pre-dump 43 | #[clap(long)] 44 | pub pre_dump: bool, 45 | /// Cgroups mode 46 | #[clap(long)] 47 | pub manage_cgroups_mode: Option, 48 | /// Checkpoint a namespace, but don't save its properties 49 | #[clap(long)] 50 | pub empty_ns: bool, 51 | /// Enable auto-deduplication 52 | #[clap(long)] 53 | pub auto_dedup: bool, 54 | 55 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 56 | pub container_id: String, 57 | } 58 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/create.rs: -------------------------------------------------------------------------------- 1 | //! Handles the creation of a new container 2 | use std::path::PathBuf; 3 | 4 | use clap::Parser; 5 | 6 | /// Create a container 7 | /// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-create.8.md 8 | #[derive(Parser, Debug)] 9 | pub struct Create { 10 | /// Path to the bundle directory, containing config.json and root filesystem 11 | #[clap(short, long, default_value = ".")] 12 | pub bundle: PathBuf, 13 | /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal 14 | #[clap(short, long)] 15 | pub console_socket: Option, 16 | /// File to write pid of the container created 17 | // note that in the end, container is just another process 18 | #[clap(short, long)] 19 | pub pid_file: Option, 20 | /// Do not use pivot rool to jail process inside rootfs 21 | #[clap(long)] 22 | pub no_pivot: bool, 23 | /// Do not create a new session keyring for the container. 24 | #[clap(long)] 25 | pub no_new_keyring: bool, 26 | /// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) 27 | #[clap(long, default_value = "0")] 28 | pub preserve_fds: i32, 29 | 30 | /// Name of the container instance to be started 31 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 32 | pub container_id: String, 33 | } 34 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/delete.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Release any resources held by the container 4 | #[derive(Parser, Debug)] 5 | pub struct Delete { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | /// forces deletion of the container if it is still running (using SIGKILL) 9 | #[clap(short, long)] 10 | pub force: bool, 11 | } 12 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/events.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Show resource statistics for the container 4 | #[derive(Parser, Debug)] 5 | pub struct Events { 6 | /// Sets the stats collection interval in seconds (default: 5s) 7 | #[clap(long, default_value = "5")] 8 | pub interval: u32, 9 | /// Display the container stats only once 10 | #[clap(long)] 11 | pub stats: bool, 12 | /// Name of the container instance 13 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 14 | pub container_id: String, 15 | } 16 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/features.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Return the features list for a container 4 | /// This subcommand was introduced in runc by 5 | /// https://github.com/opencontainers/runc/pull/3296 6 | /// It is documented here: 7 | /// https://github.com/opencontainers/runtime-spec/blob/main/features-linux.md 8 | #[derive(Parser, Debug)] 9 | pub struct Features {} 10 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/info.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Show information about the system 4 | #[derive(Parser, Debug)] 5 | pub struct Info {} 6 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/kill.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Send the specified signal to the container 4 | #[derive(Parser, Debug)] 5 | pub struct Kill { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | pub signal: String, 9 | #[clap(short, long)] 10 | pub all: bool, 11 | } 12 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/list.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// List created containers 4 | #[derive(Parser, Debug)] 5 | pub struct List { 6 | /// Specify the format (default or table) 7 | #[clap(long, default_value = "table")] 8 | pub format: String, 9 | 10 | /// Only display container IDs 11 | #[clap(long, short)] 12 | pub quiet: bool, 13 | } 14 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/pause.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Suspend the processes within the container 4 | #[derive(Parser, Debug)] 5 | pub struct Pause { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | } 9 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/ps.rs: -------------------------------------------------------------------------------- 1 | use clap::{self, Parser}; 2 | 3 | /// Display the processes inside the container 4 | #[derive(Parser, Debug)] 5 | pub struct Ps { 6 | /// format to display processes: table or json (default: "table") 7 | #[clap(short, long, default_value = "table")] 8 | pub format: String, 9 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 10 | pub container_id: String, 11 | /// options will be passed to the ps utility 12 | #[clap(last = true)] 13 | pub ps_options: Vec, 14 | } 15 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/resume.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Resume the processes within the container 4 | #[derive(Parser, Debug)] 5 | pub struct Resume { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | } 9 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/run.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | /// Create a container and immediately start it 6 | #[derive(Parser, Debug)] 7 | pub struct Run { 8 | /// Path to the bundle directory, containing config.json and root filesystem 9 | #[clap(short, long, default_value = ".")] 10 | pub bundle: PathBuf, 11 | /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal 12 | #[clap(short, long)] 13 | pub console_socket: Option, 14 | /// File to write pid of the container created 15 | // note that in the end, container is just another process 16 | #[clap(short, long)] 17 | pub pid_file: Option, 18 | /// Disable the use of the subreaper used to reap reparented processes 19 | #[clap(long)] 20 | pub no_subreaper: bool, 21 | /// Do not use pivot root to jail process inside rootfs 22 | #[clap(long)] 23 | pub no_pivot: bool, 24 | /// Do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key. 25 | #[clap(long)] 26 | pub no_new_keyring: bool, 27 | /// Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total) 28 | #[clap(long, default_value = "0")] 29 | pub preserve_fds: i32, 30 | // Keep container's state directory and cgroup 31 | #[clap(long)] 32 | pub keep: bool, 33 | /// name of the container instance to be started 34 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 35 | pub container_id: String, 36 | /// Detach from the container process 37 | #[clap(short, long)] 38 | pub detach: bool, 39 | } 40 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/spec.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | /// Command generates a config.json 6 | #[derive(Parser, Debug)] 7 | pub struct Spec { 8 | /// Set path to the root of the bundle directory 9 | #[clap(long, short)] 10 | pub bundle: Option, 11 | 12 | /// Generate a configuration for a rootless container 13 | #[clap(long)] 14 | pub rootless: bool, 15 | } 16 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/start.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Start a previously created container 4 | #[derive(Parser, Debug)] 5 | pub struct Start { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | } 9 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/state.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Show the container state 4 | #[derive(Parser, Debug)] 5 | pub struct State { 6 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 7 | pub container_id: String, 8 | } 9 | -------------------------------------------------------------------------------- /crates/liboci-cli/src/update.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::Parser; 4 | 5 | /// Update running container resource constraints 6 | #[derive(Parser, Debug)] 7 | pub struct Update { 8 | /// Read the new resource limits from the given json file. Use - to read from stdin. 9 | /// If this option is used, all other options are ignored. 10 | #[clap(short, long)] 11 | pub resources: Option, 12 | 13 | /// Set a new I/O weight 14 | #[clap(long)] 15 | pub blkio_weight: Option, 16 | 17 | /// Set CPU CFS period to be used for hardcapping (in microseconds) 18 | #[clap(long)] 19 | pub cpu_period: Option, 20 | 21 | /// Set CPU usage limit within a given period (in microseconds) 22 | #[clap(long)] 23 | pub cpu_quota: Option, 24 | 25 | /// Set CPU realtime period to be used for hardcapping (in microseconds) 26 | #[clap(long)] 27 | pub cpu_rt_period: Option, 28 | 29 | /// Set CPU realtime hardcap limit (in microseconds) 30 | #[clap(long)] 31 | pub cpu_rt_runtime: Option, 32 | 33 | /// Set CPU shares (relative weight vs. other containers) 34 | #[clap(long)] 35 | pub cpu_share: Option, 36 | 37 | /// Set CPU(s) to use. The list can contain commas and ranges. For example: 0-3,7 38 | #[clap(long)] 39 | pub cpuset_cpus: Option, 40 | 41 | /// Set memory node(s) to use. The list format is the same as for --cpuset-cpus. 42 | #[clap(long)] 43 | pub cpuset_mems: Option, 44 | 45 | /// Set memory limit to num bytes. 46 | #[clap(long)] 47 | pub memory: Option, 48 | 49 | /// Set memory reservation (or soft limit) to num bytes. 50 | #[clap(long)] 51 | pub memory_reservation: Option, 52 | 53 | /// Set total memory + swap usage to num bytes. Use -1 to unset the limit (i.e. use unlimited swap). 54 | #[clap(long)] 55 | pub memory_swap: Option, 56 | 57 | /// Set the maximum number of processes allowed in the container 58 | #[clap(long)] 59 | pub pids_limit: Option, 60 | 61 | /// Set the value for Intel RDT/CAT L3 cache schema. 62 | #[clap(long)] 63 | pub l3_cache_schema: Option, 64 | 65 | /// Set the Intel RDT/MBA memory bandwidth schema. 66 | #[clap(long)] 67 | pub mem_bw_schema: Option, 68 | 69 | #[clap(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)] 70 | pub container_id: String, 71 | } 72 | -------------------------------------------------------------------------------- /crates/youki/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "youki" 3 | version = "0.5.3" # MARK: Version 4 | description = "A container runtime written in Rust" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "../../README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | build = "build.rs" 12 | keywords = ["youki", "container"] 13 | 14 | [features] 15 | systemd = ["libcgroups/systemd", "libcontainer/systemd", "v2"] 16 | v2 = ["libcgroups/v2", "libcontainer/v2"] 17 | v1 = ["libcgroups/v1", "libcontainer/v1"] 18 | cgroupsv2_devices = ["libcgroups/cgroupsv2_devices", "libcontainer/cgroupsv2_devices"] 19 | seccomp = ["libcontainer/libseccomp"] 20 | 21 | wasm-wasmer = ["wasmer", "wasmer-wasix"] 22 | wasm-wasmedge = ["wasmedge-sdk/standalone", "wasmedge-sdk/static"] 23 | wasm-wasmtime = ["wasmtime", "wasi-common"] 24 | 25 | [dependencies.clap] 26 | version = "4.1.6" 27 | default-features = false 28 | features = ["std", "suggestions", "derive", "cargo", "help", "usage", "error-context"] 29 | 30 | [dependencies] 31 | anyhow = "1.0.98" 32 | chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } 33 | libcgroups = { path = "../libcgroups", default-features = false, version = "0.5.3" } # MARK: Version 34 | libcontainer = { path = "../libcontainer", default-features = false, version = "0.5.3" } # MARK: Version 35 | liboci-cli = { path = "../liboci-cli", version = "0.5.3" } # MARK: Version 36 | nix = "0.29.0" 37 | pentacle = "1.1.0" 38 | procfs = "0.17.0" 39 | serde_json = "1.0" 40 | tabwriter = "1" 41 | clap_complete = "4.1.3" 42 | caps = "0.5.5" 43 | wasmer = { version = "4.0.0", optional = true } 44 | wasmer-wasix = { version = "0.9.0", optional = true } 45 | wasmedge-sdk = { version = "0.14.0", optional = true } 46 | wasmtime = { version = "31.0.0", optional = true } 47 | wasi-common = { version = "31.0.0", optional = true } 48 | tracing = { version = "0.1.41", features = ["attributes"] } 49 | tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } 50 | tracing-journald = "0.3.1" 51 | 52 | [dev-dependencies] 53 | serial_test = "3.1.1" 54 | tempfile = "3" 55 | scopeguard = "1.2.0" 56 | 57 | [build-dependencies] 58 | anyhow = "1.0.98" 59 | vergen-gitcl = { version = "1.0.8", features = ["build"] } 60 | -------------------------------------------------------------------------------- /crates/youki/build.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use vergen_gitcl::{Emitter, GitclBuilder}; 3 | 4 | pub fn main() -> Result<()> { 5 | if Emitter::default() 6 | .add_instructions(&GitclBuilder::all_git()?)? 7 | .emit() 8 | .is_err() 9 | { 10 | // currently we only inject git sha, so just this 11 | // else we will need to think of more elegant way to check 12 | // what failed, and what needs to be added 13 | println!("cargo:rustc-env=VERGEN_GIT_SHA=unknown"); 14 | } 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /crates/youki/src/commands/checkpoint.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality of pause container command 2 | use std::path::PathBuf; 3 | 4 | use anyhow::{Context, Result}; 5 | use liboci_cli::Checkpoint; 6 | 7 | use crate::commands::load_container; 8 | 9 | pub fn checkpoint(args: Checkpoint, root_path: PathBuf) -> Result<()> { 10 | tracing::debug!("start checkpointing container {}", args.container_id); 11 | let mut container = load_container(root_path, &args.container_id)?; 12 | let opts = libcontainer::container::CheckpointOptions { 13 | ext_unix_sk: args.ext_unix_sk, 14 | file_locks: args.file_locks, 15 | image_path: args.image_path, 16 | leave_running: args.leave_running, 17 | shell_job: args.shell_job, 18 | tcp_established: args.tcp_established, 19 | work_path: args.work_path, 20 | }; 21 | container 22 | .checkpoint(&opts) 23 | .with_context(|| format!("failed to checkpoint container {}", args.container_id)) 24 | } 25 | -------------------------------------------------------------------------------- /crates/youki/src/commands/completion.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use anyhow::Result; 4 | use clap::{Command, Parser}; 5 | use clap_complete::{generate, Shell}; 6 | 7 | #[derive(Debug, Parser)] 8 | /// Generate scripts for shell completion 9 | pub struct Completion { 10 | #[clap(long = "shell", short = 's', value_enum)] 11 | pub shell: Shell, 12 | } 13 | 14 | pub fn completion(args: Completion, app: &mut Command) -> Result<()> { 15 | generate( 16 | args.shell, 17 | app, 18 | app.get_name().to_string(), 19 | &mut io::stdout(), 20 | ); 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /crates/youki/src/commands/create.rs: -------------------------------------------------------------------------------- 1 | //! Handles the creation of a new container 2 | use std::path::PathBuf; 3 | 4 | use anyhow::Result; 5 | use libcontainer::container::builder::ContainerBuilder; 6 | use libcontainer::syscall::syscall::SyscallType; 7 | use liboci_cli::Create; 8 | 9 | use crate::workload::executor::default_executor; 10 | 11 | // One thing to note is that in the end, container is just another process in Linux 12 | // it has specific/different control group, namespace, using which program executing in it 13 | // can be given impression that is is running on a complete system, but on the system which 14 | // it is running, it is just another process, and has attributes such as pid, file descriptors, etc. 15 | // associated with it like any other process. 16 | pub fn create(args: Create, root_path: PathBuf, systemd_cgroup: bool) -> Result<()> { 17 | ContainerBuilder::new(args.container_id.clone(), SyscallType::default()) 18 | .with_executor(default_executor()) 19 | .with_pid_file(args.pid_file.as_ref())? 20 | .with_console_socket(args.console_socket.as_ref()) 21 | .with_root_path(root_path)? 22 | .with_preserved_fds(args.preserve_fds) 23 | .validate_id()? 24 | .as_init(&args.bundle) 25 | .with_systemd(systemd_cgroup) 26 | .with_detach(true) 27 | .with_no_pivot(args.no_pivot) 28 | .build()?; 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /crates/youki/src/commands/delete.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{Context, Result}; 4 | use liboci_cli::Delete; 5 | 6 | use crate::commands::{container_exists, load_container}; 7 | 8 | pub fn delete(args: Delete, root_path: PathBuf) -> Result<()> { 9 | tracing::debug!("start deleting {}", args.container_id); 10 | if !container_exists(&root_path, &args.container_id)? && args.force { 11 | return Ok(()); 12 | } 13 | 14 | let mut container = load_container(root_path, &args.container_id)?; 15 | container 16 | .delete(args.force) 17 | .with_context(|| format!("failed to delete container {}", args.container_id)) 18 | } 19 | -------------------------------------------------------------------------------- /crates/youki/src/commands/events.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::{Context, Result}; 4 | use liboci_cli::Events; 5 | 6 | use crate::commands::load_container; 7 | 8 | pub fn events(args: Events, root_path: PathBuf) -> Result<()> { 9 | let mut container = load_container(root_path, &args.container_id)?; 10 | container 11 | .events(args.interval, args.stats) 12 | .with_context(|| format!("failed to get events from container {}", args.container_id)) 13 | } 14 | -------------------------------------------------------------------------------- /crates/youki/src/commands/exec.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Result; 4 | use libcontainer::container::builder::ContainerBuilder; 5 | use libcontainer::syscall::syscall::SyscallType; 6 | use liboci_cli::Exec; 7 | use nix::sys::wait::{waitpid, WaitStatus}; 8 | 9 | use crate::workload::executor::default_executor; 10 | 11 | pub fn exec(args: Exec, root_path: PathBuf) -> Result { 12 | // TODO: not all values from exec are used here. We need to support 13 | // the remaining ones. 14 | let user = args.user.map(|(u, _)| u); 15 | let group = args.user.and_then(|(_, g)| g); 16 | 17 | let pid = ContainerBuilder::new(args.container_id.clone(), SyscallType::default()) 18 | .with_executor(default_executor()) 19 | .with_root_path(root_path)? 20 | .with_console_socket(args.console_socket.as_ref()) 21 | .with_pid_file(args.pid_file.as_ref())? 22 | .validate_id()? 23 | .as_tenant() 24 | .with_detach(args.detach) 25 | .with_cwd(args.cwd.as_ref()) 26 | .with_env(args.env.clone().into_iter().collect()) 27 | .with_process(args.process.as_ref()) 28 | .with_no_new_privs(args.no_new_privs) 29 | .with_container_args(args.command.clone()) 30 | .with_additional_gids(args.additional_gids) 31 | .with_user(user) 32 | .with_group(group) 33 | .build()?; 34 | 35 | // See https://github.com/containers/youki/pull/1252 for a detailed explanation 36 | // basically, if there is any error in starting exec, the build above will return error 37 | // however, if the process does start, and detach is given, we do not wait for it 38 | // if not detached, then we wait for it using waitpid below 39 | if args.detach { 40 | return Ok(0); 41 | } 42 | 43 | match waitpid(pid, None)? { 44 | WaitStatus::Exited(_, status) => Ok(status), 45 | WaitStatus::Signaled(_, sig, _) => Ok(sig as i32), 46 | _ => Ok(0), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/youki/src/commands/kill.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality of kill container command 2 | use std::convert::TryInto; 3 | use std::path::PathBuf; 4 | 5 | use anyhow::{anyhow, Result}; 6 | use libcontainer::container::ContainerStatus; 7 | use libcontainer::signal::Signal; 8 | use liboci_cli::Kill; 9 | 10 | use crate::commands::load_container; 11 | 12 | pub fn kill(args: Kill, root_path: PathBuf) -> Result<()> { 13 | let mut container = load_container(root_path, &args.container_id)?; 14 | let signal: Signal = args.signal.as_str().try_into()?; 15 | match container.kill(signal, args.all) { 16 | Ok(_) => Ok(()), 17 | Err(e) => { 18 | // see https://github.com/containers/youki/issues/1314 19 | if container.status() == ContainerStatus::Stopped { 20 | return Err(anyhow!(e).context("container not running")); 21 | } 22 | Err(anyhow!(e).context("failed to kill container")) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/youki/src/commands/list.rs: -------------------------------------------------------------------------------- 1 | //! Contains Functionality of list container command 2 | use std::fmt::Write as _; 3 | use std::io::Write; 4 | use std::path::PathBuf; 5 | use std::{fs, io}; 6 | 7 | use anyhow::Result; 8 | use chrono::{DateTime, Local}; 9 | use libcontainer::container::state::State; 10 | use libcontainer::container::Container; 11 | use liboci_cli::List; 12 | use tabwriter::TabWriter; 13 | 14 | /// lists all existing containers 15 | pub fn list(_: List, root_path: PathBuf) -> Result<()> { 16 | let root_path = fs::canonicalize(root_path)?; 17 | let mut content = String::new(); 18 | // all containers' data is stored in their respective dir in root directory 19 | // so we iterate through each and print the various info 20 | for container_dir in fs::read_dir(root_path)? { 21 | let container_dir = container_dir?.path(); 22 | let state_file = State::file_path(&container_dir); 23 | if !state_file.exists() { 24 | continue; 25 | } 26 | 27 | let container = Container::load(container_dir)?; 28 | let pid = if let Some(pid) = container.pid() { 29 | pid.to_string() 30 | } else { 31 | "".to_owned() 32 | }; 33 | 34 | let user_name = container.creator().unwrap_or_default(); 35 | 36 | let created = if let Some(utc) = container.created() { 37 | let local: DateTime = DateTime::from(utc); 38 | local.to_rfc3339_opts(chrono::SecondsFormat::Secs, false) 39 | } else { 40 | "".to_owned() 41 | }; 42 | 43 | let _ = writeln!( 44 | content, 45 | "{}\t{}\t{}\t{}\t{}\t{}", 46 | container.id(), 47 | pid, 48 | container.status(), 49 | container.bundle().display(), 50 | created, 51 | user_name.to_string_lossy() 52 | ); 53 | } 54 | 55 | let mut tab_writer = TabWriter::new(io::stdout()); 56 | writeln!(&mut tab_writer, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tCREATOR")?; 57 | write!(&mut tab_writer, "{content}")?; 58 | tab_writer.flush()?; 59 | 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /crates/youki/src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use anyhow::{bail, Context, Result}; 5 | use libcgroups::common::AnyCgroupManager; 6 | use libcontainer::container::Container; 7 | 8 | pub mod checkpoint; 9 | pub mod completion; 10 | pub mod create; 11 | pub mod delete; 12 | pub mod events; 13 | pub mod exec; 14 | pub mod features; 15 | pub mod info; 16 | pub mod kill; 17 | pub mod list; 18 | pub mod pause; 19 | pub mod ps; 20 | pub mod resume; 21 | pub mod run; 22 | pub mod spec_json; 23 | pub mod start; 24 | pub mod state; 25 | pub mod update; 26 | 27 | fn construct_container_root>(root_path: P, container_id: &str) -> Result { 28 | // resolves relative paths, symbolic links etc. and get complete path 29 | let root_path = fs::canonicalize(&root_path).with_context(|| { 30 | format!( 31 | "failed to canonicalize {} for container {}", 32 | root_path.as_ref().display(), 33 | container_id 34 | ) 35 | })?; 36 | // the state of the container is stored in a directory named after the container id 37 | Ok(root_path.join(container_id)) 38 | } 39 | 40 | fn load_container>(root_path: P, container_id: &str) -> Result { 41 | let container_root = construct_container_root(root_path, container_id)?; 42 | if !container_root.exists() { 43 | bail!("container {} does not exist.", container_id) 44 | } 45 | 46 | Container::load(container_root) 47 | .with_context(|| format!("could not load state for container {container_id}")) 48 | } 49 | 50 | fn container_exists>(root_path: P, container_id: &str) -> Result { 51 | let container_root = construct_container_root(root_path, container_id)?; 52 | Ok(container_root.exists()) 53 | } 54 | 55 | fn create_cgroup_manager>( 56 | root_path: P, 57 | container_id: &str, 58 | ) -> Result { 59 | let container = load_container(root_path, container_id)?; 60 | Ok(libcgroups::common::create_cgroup_manager( 61 | libcgroups::common::CgroupConfig { 62 | cgroup_path: container.spec()?.cgroup_path, 63 | systemd_cgroup: container.systemd(), 64 | container_name: container.id().to_string(), 65 | }, 66 | )?) 67 | } 68 | -------------------------------------------------------------------------------- /crates/youki/src/commands/pause.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality of pause container command 2 | use std::path::PathBuf; 3 | 4 | use anyhow::{Context, Result}; 5 | use liboci_cli::Pause; 6 | 7 | use crate::commands::load_container; 8 | 9 | // Pausing a container indicates suspending all processes in given container 10 | // This uses Freezer cgroup to suspend and resume processes 11 | // For more information see : 12 | // https://man7.org/linux/man-pages/man7/cgroups.7.html 13 | // https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt 14 | pub fn pause(args: Pause, root_path: PathBuf) -> Result<()> { 15 | tracing::debug!("start pausing container {}", args.container_id); 16 | let mut container = load_container(root_path, &args.container_id)?; 17 | container 18 | .pause() 19 | .with_context(|| format!("failed to pause container {}", args.container_id)) 20 | } 21 | -------------------------------------------------------------------------------- /crates/youki/src/commands/ps.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::Command; 3 | 4 | use anyhow::{bail, Result}; 5 | use libcgroups::common::CgroupManager; 6 | use liboci_cli::Ps; 7 | 8 | use crate::commands::create_cgroup_manager; 9 | 10 | pub fn ps(args: Ps, root_path: PathBuf) -> Result<()> { 11 | let cmanager = create_cgroup_manager(root_path, &args.container_id)?; 12 | 13 | let pids: Vec = cmanager 14 | .get_all_pids()? 15 | .iter() 16 | .map(|pid| pid.as_raw()) 17 | .collect(); 18 | 19 | if args.format == "json" { 20 | println!("{}", serde_json::to_string(&pids)?); 21 | } else if args.format == "table" { 22 | let default_ps_options = vec![String::from("-ef")]; 23 | let ps_options = if args.ps_options.is_empty() { 24 | &default_ps_options 25 | } else { 26 | &args.ps_options 27 | }; 28 | let output = Command::new("ps").args(ps_options).output()?; 29 | if !output.status.success() { 30 | println!("{}", std::str::from_utf8(&output.stderr)?); 31 | } else { 32 | let lines = std::str::from_utf8(&output.stdout)?; 33 | let lines: Vec<&str> = lines.split('\n').collect(); 34 | let pid_index = get_pid_index(lines[0])?; 35 | println!("{}", &lines[0]); 36 | for line in &lines[1..] { 37 | if line.is_empty() { 38 | continue; 39 | } 40 | let fields: Vec<&str> = line.split_whitespace().collect(); 41 | let pid: i32 = fields[pid_index].parse()?; 42 | if pids.contains(&pid) { 43 | println!("{line}"); 44 | } 45 | } 46 | } 47 | } 48 | Ok(()) 49 | } 50 | 51 | fn get_pid_index(title: &str) -> Result { 52 | let titles = title.split_whitespace(); 53 | 54 | for (index, name) in titles.enumerate() { 55 | if name == "PID" { 56 | return Ok(index); 57 | } 58 | } 59 | bail!("could't find PID field in ps output"); 60 | } 61 | -------------------------------------------------------------------------------- /crates/youki/src/commands/resume.rs: -------------------------------------------------------------------------------- 1 | //! Contains functionality of resume container command 2 | use std::path::PathBuf; 3 | 4 | use anyhow::{Context, Result}; 5 | use liboci_cli::Resume; 6 | 7 | use crate::commands::load_container; 8 | 9 | // Resuming a container indicates resuming all processes in given container from paused state 10 | // This uses Freezer cgroup to suspend and resume processes 11 | // For more information see : 12 | // https://man7.org/linux/man-pages/man7/cgroups.7.html 13 | // https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt 14 | pub fn resume(args: Resume, root_path: PathBuf) -> Result<()> { 15 | tracing::debug!("start resuming container {}", args.container_id); 16 | let mut container = load_container(root_path, &args.container_id)?; 17 | container 18 | .resume() 19 | .with_context(|| format!("failed to resume container {}", args.container_id)) 20 | } 21 | -------------------------------------------------------------------------------- /crates/youki/src/commands/start.rs: -------------------------------------------------------------------------------- 1 | //! Starts execution of the container 2 | 3 | use std::path::PathBuf; 4 | 5 | use anyhow::{Context, Result}; 6 | use liboci_cli::Start; 7 | 8 | use crate::commands::load_container; 9 | 10 | pub fn start(args: Start, root_path: PathBuf) -> Result<()> { 11 | let mut container = load_container(root_path, &args.container_id)?; 12 | container 13 | .start() 14 | .with_context(|| format!("failed to start container {}", args.container_id)) 15 | } 16 | -------------------------------------------------------------------------------- /crates/youki/src/commands/state.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Result; 4 | use liboci_cli::State; 5 | 6 | use crate::commands::load_container; 7 | 8 | pub fn state(args: State, root_path: PathBuf) -> Result<()> { 9 | let container = load_container(root_path, &args.container_id)?; 10 | println!("{}", serde_json::to_string_pretty(&container.state)?); 11 | std::process::exit(0); 12 | } 13 | -------------------------------------------------------------------------------- /crates/youki/src/commands/update.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::{fs, io}; 3 | 4 | use anyhow::Result; 5 | use libcgroups::common::{CgroupManager, ControllerOpt}; 6 | use libcgroups::{self}; 7 | use libcontainer::oci_spec::runtime::{LinuxPidsBuilder, LinuxResources, LinuxResourcesBuilder}; 8 | use liboci_cli::Update; 9 | 10 | use crate::commands::create_cgroup_manager; 11 | 12 | pub fn update(args: Update, root_path: PathBuf) -> Result<()> { 13 | let cmanager = create_cgroup_manager(root_path, &args.container_id)?; 14 | 15 | let linux_res: LinuxResources; 16 | if let Some(resources_path) = args.resources { 17 | linux_res = if resources_path.to_string_lossy() == "-" { 18 | serde_json::from_reader(io::stdin())? 19 | } else { 20 | let file = fs::File::open(resources_path)?; 21 | let reader = io::BufReader::new(file); 22 | serde_json::from_reader(reader)? 23 | }; 24 | } else { 25 | let mut builder = LinuxResourcesBuilder::default(); 26 | if let Some(new_pids_limit) = args.pids_limit { 27 | builder = builder.pids(LinuxPidsBuilder::default().limit(new_pids_limit).build()?); 28 | } 29 | linux_res = builder.build()?; 30 | } 31 | 32 | cmanager.apply(&ControllerOpt { 33 | resources: &linux_res, 34 | disable_oom_killer: false, 35 | oom_score_adj: None, 36 | freezer_state: None, 37 | })?; 38 | Ok(()) 39 | } 40 | -------------------------------------------------------------------------------- /crates/youki/src/workload/executor.rs: -------------------------------------------------------------------------------- 1 | use libcontainer::oci_spec::runtime::Spec; 2 | use libcontainer::workload::{Executor, ExecutorError, ExecutorValidationError}; 3 | 4 | #[derive(Clone)] 5 | pub struct DefaultExecutor {} 6 | 7 | impl Executor for DefaultExecutor { 8 | fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> { 9 | #[cfg(feature = "wasm-wasmer")] 10 | match super::wasmer::get_executor().exec(spec) { 11 | Ok(_) => return Ok(()), 12 | Err(ExecutorError::CantHandle(_)) => (), 13 | Err(err) => return Err(err), 14 | } 15 | #[cfg(feature = "wasm-wasmedge")] 16 | match super::wasmedge::get_executor().exec(spec) { 17 | Ok(_) => return Ok(()), 18 | Err(ExecutorError::CantHandle(_)) => (), 19 | Err(err) => return Err(err), 20 | } 21 | #[cfg(feature = "wasm-wasmtime")] 22 | match super::wasmtime::get_executor().exec(spec) { 23 | Ok(_) => return Ok(()), 24 | Err(ExecutorError::CantHandle(_)) => (), 25 | Err(err) => return Err(err), 26 | } 27 | 28 | // Leave the default executor as the last option, which executes normal 29 | // container workloads. 30 | libcontainer::workload::default::get_executor().exec(spec) 31 | } 32 | 33 | fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> { 34 | #[cfg(feature = "wasm-wasmer")] 35 | match super::wasmer::get_executor().validate(spec) { 36 | Ok(_) => return Ok(()), 37 | Err(ExecutorValidationError::CantHandle(_)) => (), 38 | Err(err) => return Err(err), 39 | } 40 | #[cfg(feature = "wasm-wasmedge")] 41 | match super::wasmedge::get_executor().validate(spec) { 42 | Ok(_) => return Ok(()), 43 | Err(ExecutorValidationError::CantHandle(_)) => (), 44 | Err(err) => return Err(err), 45 | } 46 | #[cfg(feature = "wasm-wasmtime")] 47 | match super::wasmtime::get_executor().validate(spec) { 48 | Ok(_) => return Ok(()), 49 | Err(ExecutorValidationError::CantHandle(_)) => (), 50 | Err(err) => return Err(err), 51 | } 52 | 53 | libcontainer::workload::default::get_executor().validate(spec) 54 | } 55 | } 56 | 57 | pub fn default_executor() -> DefaultExecutor { 58 | DefaultExecutor {} 59 | } 60 | -------------------------------------------------------------------------------- /crates/youki/src/workload/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod executor; 2 | #[cfg(feature = "wasm-wasmedge")] 3 | mod wasmedge; 4 | #[cfg(feature = "wasm-wasmer")] 5 | mod wasmer; 6 | #[cfg(feature = "wasm-wasmtime")] 7 | mod wasmtime; 8 | -------------------------------------------------------------------------------- /cross/Dockerfile.gnu: -------------------------------------------------------------------------------- 1 | ARG CROSS_BASE_IMAGE 2 | ARG CROSS_DEB_ARCH 3 | FROM $CROSS_BASE_IMAGE 4 | 5 | ARG CROSS_DEB_ARCH 6 | RUN dpkg --add-architecture ${CROSS_DEB_ARCH} && \ 7 | apt-get -y update && \ 8 | apt-get install -y pkg-config \ 9 | # dependencies required to build libsecccomp-rs 10 | libseccomp-dev:${CROSS_DEB_ARCH} \ 11 | # dependencies required to build libbpf-sys 12 | libelf-dev:${CROSS_DEB_ARCH} \ 13 | zlib1g-dev:${CROSS_DEB_ARCH} \ 14 | # dependencies to build wasmedge-sys 15 | libzstd-dev:${CROSS_DEB_ARCH} 16 | 17 | COPY hack/busctl.sh /bin/busctl 18 | RUN chmod +x /bin/busctl 19 | -------------------------------------------------------------------------------- /cross/Dockerfile.musl: -------------------------------------------------------------------------------- 1 | ARG CROSS_BASE_IMAGE 2 | FROM $CROSS_BASE_IMAGE 3 | 4 | COPY --from=jorgeprendes420/apk-anywhere / / 5 | ENV MARCH=${CROSS_CMAKE_SYSTEM_PROCESSOR} 6 | RUN apk-init ${MARCH} ${CROSS_SYSROOT} 7 | 8 | RUN apk-${MARCH} add \ 9 | # dependencies required to build libsecccomp-rs 10 | libseccomp-static libseccomp-dev \ 11 | # dependencies required to build libbpf-sys 12 | libelf-static elfutils-dev \ 13 | zlib-dev zlib-static \ 14 | # dependencies to build wasmedge-sys 15 | g++ zstd-static 16 | 17 | # configure libsecccomp-rs to use static linking 18 | ENV LIBSECCOMP_LINK_TYPE="static" 19 | ENV LIBSECCOMP_LIB_PATH="${CROSS_SYSROOT}/lib" 20 | 21 | # configure wasmedge-sys to link stdc++ statically 22 | ENV WASMEDGE_DEP_STDCXX_LINK_TYPE="static" 23 | ENV WASMEDGE_DEP_STDCXX_LIB_PATH="${CROSS_SYSROOT}/lib" 24 | 25 | COPY hack/busctl.sh /bin/busctl 26 | RUN chmod +x /bin/busctl 27 | 28 | # wasmedge-sys (through llvm) needs some symbols defined in libgcc 29 | RUN mkdir /.cargo && cat <<'EOF' > /.cargo/config.toml 30 | [target.'cfg(target_env = "musl")'] 31 | rustflags = ["-Clink-arg=-lgcc"] 32 | EOF 33 | 34 | RUN apt-get -y update && \ 35 | apt-get install -y pkg-config 36 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | -------------------------------------------------------------------------------- /docs/archive/youki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/archive/youki.png -------------------------------------------------------------------------------- /docs/archive/youki_flat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/archive/youki_flat.png -------------------------------------------------------------------------------- /docs/archive/youki_flat_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/archive/youki_flat_full.png -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Yashodhan Joshi"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "Youki User and Developer Documentation" 7 | -------------------------------------------------------------------------------- /docs/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/demo.gif -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [Youki](./youki.md) 4 | 5 | --- 6 | 7 | - [User Documentation](./user/introduction.md) 8 | - [Basic Setup](./user/basic_setup.md) 9 | - [Basic Usage](./user/basic_usage.md) 10 | - [Crates provided](./user/crates.md) 11 | - [libcgroups](./user/libcgroups.md) 12 | - [libcontainer](./user/libcontainer.md) 13 | - [liboci-cli](./user/liboci_cli.md) 14 | - [libseccomp](./user/libseccomp.md) 15 | - [Webassembly](./user/webassembly.md) 16 | 17 | --- 18 | 19 | - [Community](./community/introduction.md) 20 | - [Maintainer](./community/maintainer.md) 21 | - [Governance](./community/governance.md) 22 | - [Contributing](./community/contributing.md) 23 | - [Chat](./community/chat.md) 24 | 25 | --- 26 | 27 | - [Developer Documentation](./developer/introduction.md) 28 | - [Basics](./developer/basics.md) 29 | - [Unwritten Rules](./developer/unwritten_rules.md) 30 | - [Good places to start](./developer/good_places_to_start.md) 31 | - [This Documentation](./developer/documentation_mdbook.md) 32 | - [Repository Structure](./developer/repo_structure.md) 33 | - [Debugging](./developer/debugging.md) 34 | - [Crate Specific Information](./developer/crate_specific_information.md) 35 | - [libcgroups](./developer/libcgroups.md) 36 | - [libcontainer](./developer/libcontainer.md) 37 | - [liboci-cli](./developer/liboci_cli.md) 38 | - [libseccomp](./developer/libseccomp.md) 39 | - [youki](./developer/youki.md) 40 | - [e2e tests](./developer/e2e/e2e_tests.md) 41 | - [rust oci tests](./developer/e2e/rust_oci_test.md) 42 | - [integration_test](./developer/e2e/integration_test.md) 43 | - [test_framework](./developer/e2e/test_framework.md) 44 | - [runtimetest](./developer/e2e/runtimetest.md) 45 | - [containerd integration test](./developer/e2e/containerd_integration_test_using_youki.md) 46 | - [runtime tools](./developer/e2e/runtime_tools.md) 47 | -------------------------------------------------------------------------------- /docs/src/assets/rust_oci_tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/src/assets/rust_oci_tests.png -------------------------------------------------------------------------------- /docs/src/assets/youki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/src/assets/youki.png -------------------------------------------------------------------------------- /docs/src/community/chat.md: -------------------------------------------------------------------------------- 1 | # Chat 2 | Please join our chat find help or discuss issues: 3 | - [Discord invite](https://discord.gg/zHnyXKSQFD) 4 | -------------------------------------------------------------------------------- /docs/src/community/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Developer Certificate of Origin 4 | 5 | Every commit must be signed off with the `Signed-off-by: REAL NAME ` line. 6 | Use the `git commit -s` command to add the Signed-off-by line. 7 | 8 | ## Licensing 9 | 10 | Youki is licensed under the terms of [Apache License 2.0](https://github.com/containers/youki/blob/main/LICENSE). 11 | 12 | ## Sending pull requests 13 | 14 | Pull 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). 15 | 16 | ## Merging pull requests 17 | 18 | Committers can merge pull requests. 19 | A Committer shouldn’t merge their own pull requests without approval by at least one other Committer. 20 | -------------------------------------------------------------------------------- /docs/src/community/governance.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Youki follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | 5 | # Maintainership 6 | 7 | Youki is governed by Maintainers who are elected from active contributors. 8 | Youki will remain [vendor-neutral](https://contribute.cncf.io/maintainers/community/vendor-neutrality/). 9 | Maintainers are [here](./maintainer.md). 10 | 11 | ## Roles 12 | 13 | Maintainers consist of the following roles: 14 | 15 | - Committer (Full maintainership) 16 | Committers have full write accesses to repos. 17 | 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. 18 | 19 | - Reviewer (Limited maintainership) 20 | 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. 21 | A Reviewer is considered as a candidate to become a Committer. 22 | 23 | ## Addition and promotion of Maintainers 24 | 25 | A contributor who have made significant contributions in quality and in quantity can be directly invited as a Committer. 26 | A proposal to add or promote a Maintainer must be approved by 2/3 of the Committers who vote within 7 days. 27 | Voting needs a minimum of 2 approvals. The proposer can vote too. Votes from the same company will be counted as one vote. 28 | A proposal should be made via a GitHub pull request to the file containing the list of Maintainer defined above. 29 | Before submitting the pull request, the proposer should reach out to the Committers to check their willingness to support the proposal. 30 | 31 | ## Removal and demotion of Maintainers 32 | 33 | A 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. 34 | 35 | A 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. 36 | Voting needs a minimum of 2 approvals. The proposer can vote too. Votes from the same company will be counted as one vote. 37 | 38 | A proposal should be made via a GitHub pull request to the file containing the list of Maintainer defined above. 39 | In the special case of removing a harmful Maintainer, this process can take place via a private discussion. 40 | Before submitting the pull request, the proposer should reach out to the Committers to check their willingness. 41 | -------------------------------------------------------------------------------- /docs/src/community/introduction.md: -------------------------------------------------------------------------------- 1 | # Community 2 | 3 | - [Maintainer](./maintainer.md) 4 | - [Governance](./goversance.md) 5 | - [Contributing](./contributing.md) 6 | - [Chat](./chat.md) 7 | -------------------------------------------------------------------------------- /docs/src/community/maintainer.md: -------------------------------------------------------------------------------- 1 | # Current Maintainers 2 | 3 | | Name | Role | GitHub ID | Affiliation | 4 | |-----------------|-----------|----------------------------------------------------|--------------------| 5 | | Toru Komatsu | Committer | [@utam0k](https://github.com/utam0k) | Preferred Networks | 6 | | Thomas Schubart | Committer | [@Furisto](https://github.com/Furisto) | Gitpod | 7 | | Yashodhan | Committer | [@YJDoc2](https://github.com/YJDoc2) | Independent | 8 | | Eric Fang | Committer | [@yihuaf](https://github.com/yihuaf) | Independent | 9 | | Sascha Grunert | Committer | [@saschagrunert](https://github.com/saschagrunert) | Red Hat | 10 | | Jorge Prendes | Committer | [@jprendes](https://github.com/jprendes) | Microsoft | 11 | -------------------------------------------------------------------------------- /docs/src/developer/crate_specific_information.md: -------------------------------------------------------------------------------- 1 | # Crate Specific Information 2 | 3 | This section contains subsections for each individual crate in the youki workspace. Each of the subsection will have information and resources on that particular crate. 4 | 5 | In 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. 6 | -------------------------------------------------------------------------------- /docs/src/developer/documentation_mdbook.md: -------------------------------------------------------------------------------- 1 | # This Documentation 2 | 3 | This 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/). 4 | 5 | Please 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. 6 | 7 | Currently 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. 8 | 9 | When 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. 10 | 11 | If you want to test it using gh-pages on your own fork, you can use following steps in the docs directory. 12 | 13 | ```console 14 | git worktree prune 15 | # Do this if you are running this command first time after booting, 16 | # As after shutdown /tmp files are removed 17 | git branch -D gh-pages && git worktree add /tmp/book -b gh-pages 18 | mdbook build 19 | rm -rf /tmp/book/* # this won't delete the .git directory 20 | cp -rp book/* /tmp/book/ 21 | cd /tmp/book 22 | git add -A 23 | git commit 'new book message' 24 | git push -f origin gh-pages 25 | cd - 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/src/developer/e2e/containerd_integration_test_using_youki.md: -------------------------------------------------------------------------------- 1 | # containerd integration test using youki 2 | 3 | ## local 4 | 5 | ```console 6 | just containerd-test 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/src/developer/e2e/e2e_tests.md: -------------------------------------------------------------------------------- 1 | # e2e tests 2 | 3 | There are various e2e tests: 4 | 5 | - [rust oci tests](./rust_oci_test.md) 6 | 7 | This is youki's original integration to verify the behavior of the low-level container runtime. 8 | 9 | - [containerd integration test](./containerd_integration_test_using_youki.md) 10 | 11 | This is the method that containerd's integration test runs with youki. 12 | 13 | - [runtime tools](./runtime_tools.md) 14 | 15 | This is the method to run the runtime tools that OCI manages as a test tool to verify meeting the OCI Runtime Spec 16 | -------------------------------------------------------------------------------- /docs/src/developer/e2e/runtime_tools.md: -------------------------------------------------------------------------------- 1 | # runtime tools 2 | 3 | ## local 4 | 5 | ```console 6 | $ git submodule update --init --recursive 7 | $ just test-oci 8 | ``` 9 | -------------------------------------------------------------------------------- /docs/src/developer/e2e/rust_oci_test.md: -------------------------------------------------------------------------------- 1 | # Contest 2 | 3 | This is youki's original integration to verify the behavior of the low-level container runtime. 4 | 5 | ![Overview](../../assets/rust_oci_tests.png) 6 | 7 | # How to run 8 | 9 | ```console 10 | just test-contest 11 | ``` 12 | 13 | # How to write 14 | 15 | We will not go into detail here, but will explain how to write and add a new test case based on an example test. 16 | 17 |
18 | Fully the code of the example test 19 |

20 | 21 | ```rust,no_run,noplayground 22 | {{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs}} 23 | ``` 24 | 25 |

26 |
27 | 28 | 29 | 1. Build the OCI Runtime Spec you want to verify 30 | 31 | This testing framework automatically places [runtimetest](./runtimetest.md) in the container. 32 | In other words, you can test the processes you want to execute within a container by writing them in runtimetest. 33 | Therefore, it is common practice here to write an OCI Runtime Spec that executes `runtimetest`. 34 | 35 | ```rust,no_run,noplayground 36 | {{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:get_example_spec}} 37 | ``` 38 | 39 | 2. Prepare a function that returns a `TestResult`, which represents the result of the test. 40 | 41 | ```rust,no_run,noplayground 42 | {{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:example_test}} 43 | ``` 44 | 45 | 3. Create a `TestGroup` and register a test case you created 46 | 47 | ```rust,no_run,noplayground 48 | {{#include ../../../../tests/contest/contest/src/tests/example/hello_world.rs:get_example_test}} 49 | ``` 50 | 51 | 4. Register the `TestGroup` you created to a `TestManager` 52 | 53 | ```rust,no_run,noplayground 54 | {{#include ../../../../tests/contest/contest/src/main.rs:register_example_test}} 55 | ``` 56 | 57 | 5. Write the validation you want to run within a test container 58 | ```rust,no_run,noplayground 59 | {{#include ../../../../tests/contest/runtimetest/src/main.rs:example_runtimetest_main}} 60 | ``` 61 | ```rust,no_run,noplayground 62 | {{#include ../../../../tests/contest/runtimetest/src/tests.rs:example_hello_world}} 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/src/developer/e2e/test_framework.md: -------------------------------------------------------------------------------- 1 | # test_framework 2 | 3 | **Note** that these tests resides in /tests/test_framework at the time of writing. 4 | 5 | This 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. 6 | 7 | By 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. 8 | 9 | Sometimes 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/containers/youki/tree/main/crates/test_framework). 10 | -------------------------------------------------------------------------------- /docs/src/developer/good_places_to_start.md: -------------------------------------------------------------------------------- 1 | # Good places to start 2 | 3 | First of all, welcome to youki! Hope you have fun while developing and contributing :) 4 | 5 | This 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/containers/youki/issues). You can find issues with labels `good fist issue` or `help wanted` and start working on them. 6 | 7 | You 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. 8 | 9 | --- 10 | 11 | This lists known parts of youki that can be good for beginners at the time of the writing. Please update as things change. 12 | 13 | #### Documentation Comments 14 | 15 | Currently 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. 16 | 17 | If 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. 18 | 19 | #### Integration Tests 20 | 21 | You 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. 22 | 23 | Thus 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/containers/youki/issues/361). 24 | -------------------------------------------------------------------------------- /docs/src/developer/introduction.md: -------------------------------------------------------------------------------- 1 | # Developer Documentation 2 | 3 | This 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. 4 | 5 | Also, Thank you! If you have any issues or doubts, you can ask them on youki's discord server [here](https://discord.gg/h7R3HgWUct). 6 | 7 | This section is split into following parts 8 | 9 | - Basics : This contains general resources and information that you wold need to work with any parts of youki. 10 | 11 | - 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. 12 | 13 | - Good Places to Start : This section will contain some suggestions about the areas that will be a good place to start for new contributors. 14 | 15 | - Crate specific Information : This is split into one sections for each crate, and will contains information and resources specific for that crate. 16 | 17 | Happy Contributing! 18 | -------------------------------------------------------------------------------- /docs/src/developer/liboci_cli.md: -------------------------------------------------------------------------------- 1 | # liboci-cli 2 | 3 | This 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. 4 | 5 | This primarily uses the [crate clap-v3](https://docs.rs/clap/latest/clap/index.html) for parsing the actual commandline arguments given to the runtime. 6 | 7 | You 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. 8 | -------------------------------------------------------------------------------- /docs/src/developer/libseccomp.md: -------------------------------------------------------------------------------- 1 | # libseccomp 2 | 3 | Seccomp 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. 4 | 5 | This 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). 6 | -------------------------------------------------------------------------------- /docs/src/developer/repo_structure.md: -------------------------------------------------------------------------------- 1 | # Repository Structure 2 | 3 | This 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. 4 | 5 | ### .github 6 | 7 | Contains workflows and files needed by those workflows. 8 | 9 | ### crates 10 | 11 | This is the core of youki. This contains various libraries that are developed alongside of youki and the youki binary itself. 12 | 13 | ### docs 14 | 15 | The 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. 16 | 17 | ### hack 18 | 19 | As 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. 20 | 21 | ### Scripts 22 | 23 | Contains 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. 24 | 25 | ### tests 26 | 27 | This 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. 28 | -------------------------------------------------------------------------------- /docs/src/developer/unwritten_rules.md: -------------------------------------------------------------------------------- 1 | # Unwritten Rule 2 | 3 | This 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. 4 | 5 | ## Conventions to follow 6 | 7 | #### Errors 8 | 9 | Youki 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. 10 | 11 | Also 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/containers/youki/issues/313). Whenever you write error messages, please follow this convention to keep them uniform. 12 | 13 | #### Logs 14 | 15 | youki 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. 16 | 17 | #### Comments 18 | 19 | Make 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`. 20 | 21 | #### Scripts 22 | 23 | In 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. 24 | 25 | #### Update This Documentation 26 | 27 | Keep 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. 28 | -------------------------------------------------------------------------------- /docs/src/user/crates.md: -------------------------------------------------------------------------------- 1 | # Crates provided 2 | 3 | Youki 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. 4 | 5 | For more information on how to add a sub-crate as a dependency in your project, see [Basic Usage](./basic_usage.md). 6 | 7 | The 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. 8 | -------------------------------------------------------------------------------- /docs/src/user/introduction.md: -------------------------------------------------------------------------------- 1 | # User Documentation 2 | 3 | This 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. 4 | 5 | This is divided into following sub-sections : 6 | 7 | - Basic Setup : This explains how the dependencies and setup required to compile and run youki 8 | - Basic Usage : This explains using youki itself as a low-level container runtime, or using one of the crates as dependencies 9 | - crates : This section provides brief explanation of the member crates of youki repo workspace 10 | - libcgroups 11 | - libcontainer 12 | - liboci-cli 13 | - libseccomp 14 | - Webassembly : This explains how to use webassembly module with youki. 15 | -------------------------------------------------------------------------------- /docs/src/user/liboci_cli.md: -------------------------------------------------------------------------------- 1 | # liboci-cli 2 | 3 | This 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. 4 | 5 | ### Implemented subcommands 6 | 7 | | Command | liboci-cli | CLI Specification | runc | crun | youki | 8 | | :--------: | :--------: | :---------------: | :--: | :--: | :---: | 9 | | create | ✅ | ✅ | ✅ | ✅ | ✅ | 10 | | start | ✅ | ✅ | ✅ | ✅ | ✅ | 11 | | state | ✅ | ✅ | ✅ | ✅ | ✅ | 12 | | kill | ✅ | ✅ | ✅ | ✅ | ✅ | 13 | | delete | ✅ | ✅ | ✅ | ✅ | ✅ | 14 | | checkpoint | | | ✅ | ✅ | | 15 | | events | ✅ | | ✅ | | ✅ | 16 | | exec | ✅ | | ✅ | ✅ | ✅ | 17 | | list | ✅ | | ✅ | ✅ | ✅ | 18 | | pause | ✅ | | ✅ | ✅ | ✅ | 19 | | ps | ✅ | | ✅ | ✅ | ✅ | 20 | | restore | | | ✅ | ✅ | | 21 | | resume | ✅ | | ✅ | ✅ | ✅ | 22 | | run | ✅ | | ✅ | ✅ | ✅ | 23 | | spec | ✅ | | ✅ | ✅ | ✅ | 24 | | update | | | ✅ | ✅ | | 25 | -------------------------------------------------------------------------------- /docs/src/user/libseccomp.md: -------------------------------------------------------------------------------- 1 | # libseccomp 2 | 3 | This 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. 4 | -------------------------------------------------------------------------------- /docs/youki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/docs/youki.png -------------------------------------------------------------------------------- /experiment/seccomp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seccomp" 3 | version = "0.0.0" 4 | description = "Library for seccomp" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | autoexamples = true 12 | keywords = ["youki", "container", "seccomp"] 13 | 14 | [dependencies] 15 | nix = { version = "0.29.0", features = [ 16 | "ioctl", 17 | "socket", 18 | "sched", 19 | "mount", 20 | "dir", 21 | ] } 22 | thiserror = "1.0.57" 23 | prctl = "1.0.0" 24 | anyhow = "1.0" 25 | tokio = { version = "1", features = ["full"] } 26 | syscall-numbers = "3.1.1" 27 | syscalls = { version = "0.6.18", features = ["std", "serde", "aarch64", "x86_64"]} -------------------------------------------------------------------------------- /experiment/seccomp/README.md: -------------------------------------------------------------------------------- 1 | This is an experimental project in order to get away from libseccomp. 2 | Ref: https://github.com/containers/youki/issues/2724 3 | 4 | ```console 5 | $ cargo run 6 | ``` 7 | -------------------------------------------------------------------------------- /experiment/seccomp/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | profile="default" 3 | channel="1.77.1" 4 | -------------------------------------------------------------------------------- /experiment/seccomp/src/instruction/arch.rs: -------------------------------------------------------------------------------- 1 | use crate::instruction::Instruction; 2 | use crate::instruction::*; 3 | 4 | #[derive(PartialEq, Debug)] 5 | pub enum Arch { 6 | X86,AArch64 7 | } 8 | 9 | pub fn gen_validate(arc: &Arch) -> Vec { 10 | let arch = match arc { 11 | Arch::X86 => AUDIT_ARCH_X86_64, 12 | Arch::AArch64 => AUDIT_ARCH_AARCH64 13 | }; 14 | 15 | vec![ 16 | Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32), 17 | Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, arch), 18 | Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS), 19 | ] 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::*; 25 | 26 | #[test] 27 | fn test_gen_validate_x86() { 28 | let bpf_prog = gen_validate(&Arch::X86); 29 | assert_eq!(bpf_prog[0], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32)); 30 | assert_eq!(bpf_prog[1], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_X86_64)); 31 | assert_eq!(bpf_prog[2], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS)); 32 | } 33 | 34 | #[test] 35 | fn test_gen_validate_aarch64() { 36 | let bpf_prog = gen_validate(&Arch::AArch64); 37 | assert_eq!(bpf_prog[0], Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, seccomp_data_arch_offset() as u32)); 38 | assert_eq!(bpf_prog[1], Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_AARCH64)); 39 | assert_eq!(bpf_prog[2], Instruction::stmt(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS)); 40 | } 41 | } -------------------------------------------------------------------------------- /experiment/seccomp/src/instruction/inst.rs: -------------------------------------------------------------------------------- 1 | use std::os::raw::{c_uchar, c_uint, c_ushort}; 2 | 3 | // https://docs.kernel.org/networking/filter.html#structure 4 | // : sock_filter 5 | #[repr(C)] 6 | #[derive(Clone, Debug, PartialEq, Eq)] 7 | pub struct Instruction { 8 | pub code: c_ushort, 9 | pub offset_jump_true: c_uchar, 10 | pub offset_jump_false: c_uchar, 11 | pub multiuse_field: c_uint, 12 | } 13 | 14 | impl Instruction { 15 | fn new( 16 | code: c_ushort, 17 | jump_true: c_uchar, 18 | jump_false: c_uchar, 19 | multiuse_field: c_uint, 20 | ) -> Self { 21 | Instruction { 22 | code, 23 | offset_jump_true: jump_true, 24 | offset_jump_false: jump_false, 25 | multiuse_field, 26 | } 27 | } 28 | 29 | pub fn jump( 30 | code: c_ushort, 31 | jump_true: c_uchar, 32 | jump_false: c_uchar, 33 | multiuse_field: c_uint, 34 | ) -> Self { 35 | Self::new(code, jump_true, jump_false, multiuse_field) 36 | } 37 | 38 | pub fn stmt(code: c_ushort, k: c_uint) -> Self { 39 | Self::new(code, 0, 0, k) 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | use crate::instruction::*; 47 | 48 | #[test] 49 | fn test_bpf_instructions() { 50 | assert_eq!( 51 | Instruction::stmt(BPF_LD | BPF_W | BPF_ABS, 16), 52 | Instruction { 53 | code: 0x20, 54 | offset_jump_true: 0, 55 | offset_jump_false: 0, 56 | multiuse_field: 16, 57 | } 58 | ); 59 | assert_eq!( 60 | Instruction::jump(BPF_JMP | BPF_JEQ | BPF_K, 10, 2, 5), 61 | Instruction { 62 | code: 0x15, 63 | offset_jump_true: 2, 64 | offset_jump_false: 5, 65 | multiuse_field: 10, 66 | } 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /experiment/seccomp/src/instruction/mod.rs: -------------------------------------------------------------------------------- 1 | mod arch; 2 | mod consts; 3 | mod inst; 4 | 5 | pub use arch::{gen_validate, Arch}; 6 | pub use consts::*; 7 | pub use inst::Instruction; 8 | -------------------------------------------------------------------------------- /experiment/seccomp/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | pub mod seccomp; 3 | -------------------------------------------------------------------------------- /experiment/selinux/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "selinux" 3 | version = "0.1.0" 4 | description = "Library for selinux" 5 | license = "Apache-2.0" 6 | repository = "https://github.com/containers/youki" 7 | homepage = "https://youki-dev.github.io/youki/" 8 | readme = "README.md" 9 | authors = ["youki team"] 10 | edition = "2021" 11 | autoexamples = true 12 | keywords = ["youki", "container", "selinux"] 13 | 14 | [dependencies] 15 | nix = { version = "0.29.0", features = ["process", "fs", "socket"] } 16 | rustix = { version = "0.38.34", features = ["fs"] } 17 | tempfile = "3.17.1" 18 | thiserror = "1.0.61" 19 | -------------------------------------------------------------------------------- /experiment/selinux/README.md: -------------------------------------------------------------------------------- 1 | # SELinux for Youki 2 | 3 | This is an experimental project to create a SELinux library in Rust. 4 | Ref: https://github.com/containers/youki/issues/2718. 5 | Reimplementation of [opencontainers/selinux](https://github.com/opencontainers/selinux) in Rust. 6 | 7 | ## Requirements 8 | 9 | - [Lima](https://github.com/lima-vm/lima) 10 | - QEMU 11 | - Rust and Cargo 12 | 13 | ## Development Environment 14 | 15 | ### Setup with Lima 16 | 17 | ```console 18 | # Start the VM with default settings (non-interactive mode) 19 | $ ./lima-setup.sh 20 | 21 | # For interactive mode (when not running in CI) 22 | $ ./lima-setup.sh --interactive 23 | 24 | # See all available options 25 | $ ./lima-setup.sh --help 26 | ``` 27 | 28 | ### Running the Project 29 | 30 | Once the VM is set up: 31 | 32 | ```console 33 | # Inside the VM, run tests 34 | $ ./lima-run.sh cargo test 35 | 36 | # Inside the VM, run the application 37 | $ ./lima-run.sh cargo run 38 | 39 | # Connect to the VM 40 | $ limactl shell --workdir /workdir/youki/experiment/shared youki-selinux 41 | 42 | ``` 43 | 44 | ### Cleaning Up 45 | 46 | When finished with development: 47 | 48 | ```console 49 | # Remove the Lima VM 50 | $ ./lima-setup.sh --cleanup 51 | ``` 52 | -------------------------------------------------------------------------------- /experiment/selinux/lima-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu -o pipefail 4 | 5 | limactl shell --workdir /workdir/youki/experiment/selinux youki-selinux "$@" 6 | -------------------------------------------------------------------------------- /experiment/selinux/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod selinux; 2 | pub mod selinux_label; 3 | pub mod tools; 4 | 5 | pub use selinux::SELinux; 6 | -------------------------------------------------------------------------------- /experiment/selinux/src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | mod sockopt; 2 | mod xattr; 3 | 4 | pub use sockopt::*; 5 | pub use xattr::*; 6 | -------------------------------------------------------------------------------- /experiment/selinux/src/tools/sockopt.rs: -------------------------------------------------------------------------------- 1 | use nix::libc; 2 | use nix::sys::socket::GetSockOpt; 3 | use std::ffi::CString; 4 | use std::os::fd::{AsFd, AsRawFd}; 5 | 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct PeerSec; 8 | 9 | // This function implements the GetSockOpt for PeerSec, retrieving the security context label 10 | // of a socket file descriptor into a CString. 11 | // This function utilizes nix's GetSockOpt implementation. 12 | // https://github.com/nix-rust/nix/blob/50e4283b35f3f34e138d138fd889f7e3c424a5c2/src/sys/socket/mod.rs#L2219 13 | impl GetSockOpt for PeerSec { 14 | type Val = CString; 15 | 16 | fn get(&self, fd: &F) -> nix::Result { 17 | let mut len: libc::socklen_t = libc::c_int::MAX as libc::socklen_t; 18 | let mut buf = vec![0u8; len as usize]; 19 | let fd_i32 = fd.as_fd().as_raw_fd(); 20 | 21 | let ret = unsafe { 22 | libc::getsockopt( 23 | fd_i32, 24 | libc::SOL_SOCKET, 25 | libc::SO_PEERSEC, 26 | buf.as_mut_ptr() as *mut libc::c_void, 27 | &mut len, 28 | ) 29 | }; 30 | 31 | if ret == -1 { 32 | return Err(nix::Error::last()); 33 | } 34 | 35 | buf.truncate(len as usize); 36 | Ok(CString::new(buf).unwrap()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /hack/busctl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This hack script is the dummy busctl command used when running tests with cross containers. 4 | 5 | # The issue is that we cannot run systemd or dbus inside the test container without a lot 6 | # of hacks. For one specific test - test_task_addition, we need to check that the task 7 | # addition via systemd manager works. We mount the host dbus socket in the test container, so 8 | # dbus calls work, but for the initial authentication, we use busctl which needs dbus and systemd 9 | # to be present and running. So instead of doing all that, we simply run the container with the 10 | # actual test running user's uid/gid and here we echo the only relevant line from busctl's 11 | # output, using id to get the uid. This is a hack, but less complex than actually setting up 12 | # and running the systemd+dbus inside the container. 13 | 14 | echo "OwnerUID=$(id -u)" 15 | -------------------------------------------------------------------------------- /hack/set_root_login_for_vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | # change sshd config 6 | 7 | file="$1" 8 | param[1]="PermitRootLogin " 9 | param[2]="PubkeyAuthentication" 10 | param[3]="PasswordAuthentication" 11 | if [ -z "${file}" ] 12 | then 13 | 14 | file="/etc/ssh/sshd_config" 15 | fi 16 | 17 | for PARAM in ${param[@]} 18 | do 19 | /usr/bin/sed -i '/^'"${PARAM}"'/d' ${file} 20 | /usr/bin/echo "All lines beginning with '${PARAM}' were deleted from ${file}." 21 | done 22 | 23 | /usr/bin/echo "${param[1]} yes" >> ${file} 24 | /usr/bin/echo "'${param[1]} yes' was added to ${file}." 25 | /usr/bin/echo "${param[2]} yes" >> ${file} 26 | /usr/bin/echo "'${param[2]} yes' was added to ${file}." 27 | /usr/bin/echo "${param[3]} no" >> ${file} 28 | /usr/bin/echo "'${param[3]} no' was added to ${file}" 29 | 30 | # reload config 31 | service sshd reload 32 | -------------------------------------------------------------------------------- /hack/stress_cargo_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | # This is a simple script to stress test `cargo test` to rule out flaky tests. 5 | 6 | COUNT=${1:-20} 7 | 8 | for i in $(seq 1 ${COUNT}) 9 | do 10 | echo "Run test ${i} iteration..." 11 | cargo test -- --nocapture 12 | done -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | profile="default" 3 | channel="1.85.0" 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Native" 2 | unstable_features = true # Cargo fmt now needs to be called with `cargo +nightly fmt` 3 | group_imports = "StdExternalCrate" # create three groups for std, external and local crates 4 | # Merge imports from the same module 5 | # See: https://rust-lang.github.io/rustfmt/?version=v1.4.38&search=#imports_granularity 6 | imports_granularity = "Module" 7 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.gz -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Scripts 2 | 3 | This 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. 4 | 5 | #### Note 6 | 7 | Please 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. 8 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | ROOT=$(git rev-parse --show-toplevel) 5 | 6 | for bin in youki integration_test runtimetest test.log; do 7 | if [ -f $bin ]; then 8 | rm -f ${1}/$bin 9 | fi 10 | done 11 | 12 | rm -rf $ROOT/target $ROOT/runtimetest-target 13 | 14 | exit 0 # unconditionally return zero 15 | -------------------------------------------------------------------------------- /scripts/contest.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh -eu 2 | 3 | ROOT=$(git rev-parse --show-toplevel) 4 | RUNTIME=$1 5 | shift 6 | 7 | if [ "$RUNTIME" = "" ]; then 8 | echo "please specify runtime" 9 | exit 1 10 | fi 11 | 12 | if [ ! -e $RUNTIME ]; then 13 | if ! which $RUNTIME ; then 14 | echo "$RUNTIME not found" 15 | exit 1 16 | fi 17 | fi 18 | 19 | LOGFILE="${ROOT}/test.log" 20 | 21 | if [ ! -f ${ROOT}/bundle.tar.gz ]; then 22 | cp ${ROOT}/tests/contest/contest/bundle.tar.gz ${ROOT}/bundle.tar.gz 23 | fi 24 | touch ${LOGFILE} 25 | 26 | if [ $# -gt 0 ]; then 27 | ${ROOT}/contest run --runtime "$RUNTIME" --runtimetest "${ROOT}/runtimetest" -t "$@" > "$LOGFILE" 28 | else 29 | ${ROOT}/contest run --runtime "$RUNTIME" --runtimetest "${ROOT}/runtimetest" > "$LOGFILE" 30 | fi 31 | 32 | if [ 0 -ne $(grep "not ok" $LOGFILE | wc -l ) ]; then 33 | cat $LOGFILE 34 | exit 1 35 | fi 36 | 37 | echo "Validation successful for runtime $RUNTIME" 38 | exit 0 39 | 40 | 41 | -------------------------------------------------------------------------------- /scripts/features_test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | CARGO_SH="$(dirname "$0")/cargo.sh" 5 | 6 | test_package_features() { 7 | echo "[feature test] building $1 with features $2" 8 | "$CARGO_SH" build --no-default-features --package "$1" --features "$2" 9 | } 10 | 11 | test_package_features "libcontainer" "v1" 12 | test_package_features "libcontainer" "v2" 13 | test_package_features "libcontainer" "systemd" 14 | test_package_features "libcontainer" "v2 cgroupsv2_devices" 15 | test_package_features "libcontainer" "systemd cgroupsv2_devices" 16 | test_package_features "libcontainer" "v1 libseccomp" 17 | test_package_features "libcontainer" "v2 libseccomp" 18 | test_package_features "libcontainer" "systemd libseccomp" 19 | test_package_features "libcontainer" "v2 cgroupsv2_devices libseccomp" 20 | test_package_features "libcontainer" "systemd cgroupsv2_devices libseccomp" 21 | 22 | test_package_features "libcgroups" "v1" 23 | test_package_features "libcgroups" "v2" 24 | test_package_features "libcgroups" "systemd" 25 | test_package_features "libcgroups" "v2 cgroupsv2_devices" 26 | test_package_features "libcgroups" "systemd cgroupsv2_devices" 27 | 28 | test_features() { 29 | echo "[feature test] testing features $1" 30 | "$CARGO_SH" build --no-default-features --features "$1" 31 | "$CARGO_SH" test run --no-default-features --features "$1" -- --test-threads=1 32 | } 33 | 34 | test_features "v1" 35 | test_features "v2" 36 | test_features "systemd" 37 | test_features "v2 cgroupsv2_devices" 38 | test_features "systemd cgroupsv2_devices" 39 | test_features "v1 seccomp" 40 | test_features "v2 seccomp" 41 | test_features "systemd seccomp" 42 | test_features "v2 cgroupsv2_devices seccomp" 43 | test_features "systemd cgroupsv2_devices seccomp" 44 | 45 | exit 0 46 | -------------------------------------------------------------------------------- /scripts/release_tag.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TAG=${1} 4 | 5 | if [ -z "$TAG" ]; then 6 | echo "Error: No version number provided." 7 | exit 1 8 | fi 9 | VERSION=${TAG##*v} 10 | 11 | START_MARKER="" 12 | END_MARKER="" 13 | 14 | 15 | echo "\`\`\`console 16 | # curl -sSfL https://github.com/containers/youki/releases/download/v${VERSION}/youki-${VERSION}-\$(uname -m)-musl.tar.gz | tar -xzvC /usr/bin/ youki 17 | \`\`\`" > replace_content.txt 18 | 19 | awk -v start="$START_MARKER" -v end="$END_MARKER" -v newfile="replace_content.txt" ' 20 | BEGIN {printing=1} 21 | $0 ~ start {print;system("cat " newfile);printing=0} 22 | $0 ~ end {printing=1} 23 | printing' docs/src/user/basic_setup.md > temp.txt && mv temp.txt docs/src/user/basic_setup.md 24 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *_bin -------------------------------------------------------------------------------- /tests/contest/contest/.gitignore: -------------------------------------------------------------------------------- 1 | test_log.log -------------------------------------------------------------------------------- /tests/contest/contest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contest" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | flate2 = "1.1" 9 | libcgroups = { path = "../../../crates/libcgroups" } 10 | libcontainer = { path = "../../../crates/libcontainer" } 11 | nix = "0.29.0" 12 | num_cpus = "1.17" 13 | oci-spec = { version = "0.8.1", features = ["runtime"] } 14 | once_cell = "1.21.3" 15 | pnet_datalink = "0.35.0" 16 | procfs = "0.17.0" 17 | rand = "0.9.1" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | tar = "0.4" 21 | test_framework = { path = "../test_framework" } 22 | uuid = "1.16" 23 | which = "7.0.2" 24 | tempfile = "3" 25 | scopeguard = "1.2.0" 26 | tracing = { version = "0.1.41", features = ["attributes"]} 27 | tracing-subscriber = { version = "0.3.19", features = ["json", "env-filter"] } 28 | 29 | [dependencies.clap] 30 | version = "4.1.6" 31 | default-features = false 32 | features = ["std", "suggestions", "derive", "cargo", "help", "usage", "error-context"] 33 | 34 | -------------------------------------------------------------------------------- /tests/contest/contest/bundle.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/youki-dev/youki/4368e62c3e05ae05c1de124a03a35405737955d9/tests/contest/contest/bundle.tar.gz -------------------------------------------------------------------------------- /tests/contest/contest/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod logger; 2 | pub mod tests; 3 | pub mod utils; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/logger.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::str::FromStr; 3 | 4 | use anyhow::{Context, Result}; 5 | use tracing::metadata::LevelFilter; 6 | 7 | const LOG_LEVEL_ENV_NAME: &str = "YOUKI_INTEGRATION_LOG_LEVEL"; 8 | 9 | /// Initialize the logger, must be called before accessing the logger 10 | /// Multiple parts might call this at once, but the actual initialization 11 | /// is done only once due to use of OnceCell 12 | pub fn init(debug: bool) -> Result<()> { 13 | let level = detect_log_level(debug).context("failed to parse log level")?; 14 | tracing_subscriber::fmt().with_max_level(level).init(); 15 | 16 | Ok(()) 17 | } 18 | 19 | fn detect_log_level(is_debug: bool) -> Result { 20 | let filter: Cow = if is_debug { 21 | "debug".into() 22 | } else if let Ok(level) = std::env::var(LOG_LEVEL_ENV_NAME) { 23 | level.into() 24 | } else { 25 | "off".into() 26 | }; 27 | 28 | Ok(LevelFilter::from_str(filter.as_ref())?) 29 | } 30 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/delete/mod.rs: -------------------------------------------------------------------------------- 1 | mod delete_test; 2 | pub use delete_test::get_delete_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/devices/devices_test.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Ok, Result}; 2 | use oci_spec::runtime::{ 3 | LinuxBuilder, LinuxDeviceBuilder, LinuxDeviceType, ProcessBuilder, Spec, SpecBuilder, 4 | }; 5 | use test_framework::{test_result, Test, TestGroup, TestResult}; 6 | 7 | use crate::utils::test_inside_container; 8 | use crate::utils::test_utils::CreateOptions; 9 | 10 | fn create_spec() -> Result { 11 | let device1 = LinuxDeviceBuilder::default() 12 | .path("/dev/test1") 13 | .typ(LinuxDeviceType::C) 14 | .major(10) 15 | .minor(666) 16 | .file_mode(432u32) 17 | .uid(0u32) 18 | .gid(0u32) 19 | .build() 20 | .context("failed to create device 1")?; 21 | 22 | let device2 = LinuxDeviceBuilder::default() 23 | .path("/dev/test2") 24 | .typ(LinuxDeviceType::B) 25 | .major(8) 26 | .minor(666) 27 | .file_mode(432u32) 28 | .uid(0u32) 29 | .gid(0u32) 30 | .build() 31 | .context("failed to create device 2")?; 32 | 33 | let device3 = LinuxDeviceBuilder::default() 34 | .path("/dev/test3") 35 | .typ(LinuxDeviceType::P) 36 | .major(8) 37 | .minor(666) 38 | .file_mode(432u32) 39 | .build() 40 | .context("failed to create device 3")?; 41 | 42 | let spec = SpecBuilder::default() 43 | .process( 44 | ProcessBuilder::default() 45 | .args(vec!["runtimetest".to_string(), "devices".to_string()]) 46 | .build() 47 | .expect("error in creating process config"), 48 | ) 49 | .linux( 50 | LinuxBuilder::default() 51 | .devices(vec![device1, device2, device3]) 52 | .build() 53 | .context("failed to build linux spec")?, 54 | ) 55 | .build() 56 | .context("failed to build spec")?; 57 | 58 | Ok(spec) 59 | } 60 | 61 | fn devices_test() -> TestResult { 62 | let spec = test_result!(create_spec()); 63 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 64 | } 65 | 66 | pub fn get_devices_test() -> TestGroup { 67 | let mut device_test_group = TestGroup::new("devices"); 68 | 69 | let test = Test::new("device_test", Box::new(devices_test)); 70 | device_test_group.add(vec![Box::new(test)]); 71 | 72 | device_test_group 73 | } 74 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/devices/mod.rs: -------------------------------------------------------------------------------- 1 | mod devices_test; 2 | pub use devices_test::get_devices_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/domainname/mod.rs: -------------------------------------------------------------------------------- 1 | use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; 2 | use test_framework::{ConditionalTest, TestGroup, TestResult}; 3 | 4 | use crate::utils::test_utils::CreateOptions; 5 | use crate::utils::{is_runtime_runc, test_inside_container}; 6 | 7 | fn get_spec(domainname: &str) -> Spec { 8 | SpecBuilder::default() 9 | .domainname(domainname) 10 | .process( 11 | ProcessBuilder::default() 12 | .args(vec![ 13 | "runtimetest".to_string(), 14 | "domainname_test".to_string(), 15 | ]) 16 | .build() 17 | .expect("error in creating process config"), 18 | ) 19 | .build() 20 | .unwrap() 21 | } 22 | 23 | fn set_domainname_test() -> TestResult { 24 | let spec = get_spec("domainname"); 25 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 26 | } 27 | 28 | pub fn get_domainname_tests() -> TestGroup { 29 | let mut tg = TestGroup::new("domainname_test"); 30 | let set_domainname_test = ConditionalTest::new( 31 | "set_domainname_test", 32 | Box::new(|| !is_runtime_runc()), 33 | Box::new(set_domainname_test), 34 | ); 35 | tg.add(vec![Box::new(set_domainname_test)]); 36 | 37 | tg 38 | } 39 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/example/hello_world.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; 3 | use test_framework::{test_result, Test, TestGroup, TestResult}; 4 | 5 | use crate::utils::test_inside_container; 6 | use crate::utils::test_utils::CreateOptions; 7 | 8 | ////////// ANCHOR: get_example_spec 9 | fn create_spec() -> Result { 10 | SpecBuilder::default() 11 | .process( 12 | ProcessBuilder::default() 13 | .args( 14 | ["runtimetest", "hello_world"] 15 | .iter() 16 | .map(|s| s.to_string()) 17 | .collect::>(), 18 | ) 19 | .build()?, 20 | ) 21 | .build() 22 | .context("failed to create spec") 23 | } 24 | ////////// ANCHOR_END: get_example_spec 25 | 26 | ////////// ANCHOR: example_test 27 | fn example_test() -> TestResult { 28 | let spec = test_result!(create_spec()); 29 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 30 | } 31 | ////////// ANCHOR_END: example_test 32 | 33 | ////////// ANCHOR: get_example_test 34 | pub fn get_example_test() -> TestGroup { 35 | let mut test_group = TestGroup::new("example"); 36 | let test1 = Test::new("hello world", Box::new(example_test)); 37 | test_group.add(vec![Box::new(test1)]); 38 | 39 | test_group 40 | } 41 | ////////// ANCHOR_END: get_example_test 42 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/example/mod.rs: -------------------------------------------------------------------------------- 1 | mod hello_world; 2 | 3 | pub use hello_world::get_example_test; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/hooks/mod.rs: -------------------------------------------------------------------------------- 1 | mod invoke; 2 | pub use invoke::get_hooks_tests; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/hostname/mod.rs: -------------------------------------------------------------------------------- 1 | use oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder}; 2 | use test_framework::{Test, TestGroup, TestResult}; 3 | 4 | use crate::utils::test_inside_container; 5 | use crate::utils::test_utils::CreateOptions; 6 | 7 | fn create_spec(hostname: &str) -> Spec { 8 | SpecBuilder::default() 9 | .hostname(hostname) 10 | .linux( 11 | // Need to reset the read-only paths 12 | LinuxBuilder::default() 13 | .readonly_paths(vec![]) 14 | .build() 15 | .expect("error in building linux config"), 16 | ) 17 | .process( 18 | ProcessBuilder::default() 19 | .args(vec!["runtimetest".to_string(), "set_host_name".to_string()]) 20 | .build() 21 | .expect("error in creating process config"), 22 | ) 23 | .build() 24 | .unwrap() 25 | } 26 | 27 | fn hostname_test() -> TestResult { 28 | let spec = create_spec("hostname-specific"); 29 | test_inside_container(&spec, &CreateOptions::default(), &|_| { 30 | // As long as the container is created, we expect the hostname to be determined 31 | // by the spec, so nothing to prepare prior. 32 | Ok(()) 33 | }) 34 | } 35 | 36 | fn empty_hostname() -> TestResult { 37 | let spec = create_spec(""); 38 | test_inside_container(&spec, &CreateOptions::default(), &|_| { 39 | // As long as the container is created, we expect the hostname to be determined 40 | // by the spec, so nothing to prepare prior. 41 | Ok(()) 42 | }) 43 | } 44 | 45 | pub fn get_hostname_test() -> TestGroup { 46 | let mut test_group = TestGroup::new("set_host_name"); 47 | let hostname_test = Test::new("set_host_name_test", Box::new(hostname_test)); 48 | let empty_hostname_test = Test::new("set_empty_host_name_test", Box::new(empty_hostname)); 49 | test_group.add(vec![Box::new(hostname_test), Box::new(empty_hostname_test)]); 50 | 51 | test_group 52 | } 53 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/intel_rdt/intel_rdt_test.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use libcontainer::process::intel_rdt::find_resctrl_mount_point; 3 | use oci_spec::runtime::{LinuxBuilder, LinuxIntelRdt, Spec, SpecBuilder}; 4 | use test_framework::{test_result, TestResult}; 5 | 6 | use crate::utils::test_outside_container; 7 | use crate::utils::test_utils::check_container_created; 8 | 9 | fn create_spec( 10 | maybe_l3_cache: Option<&str>, 11 | maybe_mem_bw: Option<&str>, 12 | maybe_clos_id: Option<&str>, 13 | ) -> Result { 14 | let mut intel_rdt = LinuxIntelRdt::default(); 15 | intel_rdt.set_l3_cache_schema(maybe_l3_cache.map(|x| x.to_owned())); 16 | intel_rdt.set_mem_bw_schema(maybe_mem_bw.map(|x| x.to_owned())); 17 | intel_rdt.set_clos_id(maybe_clos_id.map(|x| x.to_owned())); 18 | 19 | // Create the Linux Spec 20 | let linux_spec = LinuxBuilder::default() 21 | .intel_rdt(intel_rdt) 22 | .build() 23 | .context("failed to build linux spec")?; 24 | 25 | // Create the top level Spec 26 | let spec = SpecBuilder::default() 27 | .linux(linux_spec) 28 | .build() 29 | .context("failed to build spec")?; 30 | 31 | Ok(spec) 32 | } 33 | 34 | pub fn test_intel_rdt() -> TestResult { 35 | let cases = vec![ 36 | test_result!(create_spec(Some("L3:0=fff"), Some("MB:0=70"), None)), 37 | test_result!(create_spec(Some("L3:0=fff"), None, None)), 38 | test_result!(create_spec(None, Some("MB:0=70"), None)), 39 | test_result!(create_spec(None, None, None)), 40 | ]; 41 | 42 | for spec in cases.into_iter() { 43 | let test_result = test_outside_container(&spec, &|data| { 44 | test_result!(check_container_created(&data)); 45 | 46 | TestResult::Passed 47 | }); 48 | if let TestResult::Failed(_) = test_result { 49 | return test_result; 50 | } 51 | } 52 | 53 | TestResult::Passed 54 | } 55 | 56 | pub fn can_run() -> bool { 57 | // Ensure the resctrl pseudo-filesystem is mounted. 58 | let res = find_resctrl_mount_point(); 59 | res.is_ok() 60 | } 61 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/intel_rdt/mod.rs: -------------------------------------------------------------------------------- 1 | use test_framework::{ConditionalTest, TestGroup}; 2 | 3 | use self::intel_rdt_test::{can_run, test_intel_rdt}; 4 | 5 | mod intel_rdt_test; 6 | 7 | pub fn get_intel_rdt_test() -> TestGroup { 8 | let mut test_group = TestGroup::new("intel_rdt"); 9 | let intel_rdt = ConditionalTest::new("intel_rdt", Box::new(can_run), Box::new(test_intel_rdt)); 10 | 11 | test_group.add(vec![Box::new(intel_rdt)]); 12 | 13 | test_group 14 | } 15 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/io_priority/mod.rs: -------------------------------------------------------------------------------- 1 | mod io_priority_test; 2 | 3 | pub use io_priority_test::get_io_priority_test; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/kill/mod.rs: -------------------------------------------------------------------------------- 1 | mod kill_test; 2 | 3 | pub use kill_test::get_kill_test; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/create.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::Path; 3 | use std::process::{Command, Stdio}; 4 | 5 | use anyhow::{bail, Result}; 6 | 7 | use crate::utils::get_runtime_path; 8 | 9 | // There are still some issues here in case we put stdout and stderr as piped 10 | // the youki process created halts indefinitely which is why we pass null, and 11 | // use wait instead of wait_with_output 12 | pub fn create(project_path: &Path, id: &str) -> Result<()> { 13 | let res = Command::new(get_runtime_path()) 14 | .stdin(Stdio::null()) 15 | .stdout(Stdio::null()) 16 | .stderr(Stdio::null()) 17 | .arg("--root") 18 | .arg(project_path.join("runtime")) 19 | .arg("create") 20 | .arg("--bundle") 21 | .arg(project_path.join("bundle")) 22 | .arg(id) 23 | .spawn() 24 | .expect("Cannot execute create command") 25 | .wait(); 26 | match res { 27 | io::Result::Ok(status) => { 28 | if status.success() { 29 | Ok(()) 30 | } else { 31 | bail!("create exited with nonzero status : {}", status) 32 | } 33 | } 34 | io::Result::Err(e) => bail!("create failed : {}", e), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/delete.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | use super::get_result_from_output; 6 | use crate::utils::delete_container; 7 | 8 | pub fn delete(project_path: &Path, id: &str) -> Result<()> { 9 | let res = delete_container(id, project_path) 10 | .expect("failed to execute delete command") 11 | .wait_with_output(); 12 | get_result_from_output(res) 13 | } 14 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/exec.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | use std::process::{Command, Stdio}; 3 | 4 | use anyhow::Result; 5 | use test_framework::assert_result_eq; 6 | 7 | use super::get_result_from_output; 8 | use crate::utils::get_runtime_path; 9 | 10 | pub fn exec( 11 | project_path: &Path, 12 | id: &str, 13 | exec_cmd: Vec<&str>, 14 | expected_output: Option<&str>, 15 | ) -> Result<()> { 16 | let res = Command::new(get_runtime_path()) 17 | .stdout(Stdio::piped()) 18 | .stderr(Stdio::piped()) 19 | .arg("--root") 20 | .arg(project_path.join("runtime")) 21 | .arg("exec") 22 | .arg(id) 23 | .args(exec_cmd) 24 | .spawn() 25 | .expect("failed to execute exec command") 26 | .wait_with_output(); 27 | if let Some(expect) = expected_output { 28 | let act = String::from_utf8(res.as_ref().unwrap().stdout.clone()).unwrap(); 29 | assert_result_eq!(expect, act.as_str(), "unexpected stdout.").unwrap(); 30 | } 31 | get_result_from_output(res) 32 | } 33 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/kill.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | use super::get_result_from_output; 6 | use crate::utils::kill_container; 7 | 8 | pub fn kill(project_path: &Path, id: &str) -> Result<()> { 9 | let res = kill_container(id, project_path) 10 | .expect("failed to execute kill command") 11 | .wait_with_output(); 12 | get_result_from_output(res) 13 | } 14 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/mod.rs: -------------------------------------------------------------------------------- 1 | mod checkpoint; 2 | mod container_create; 3 | mod container_lifecycle; 4 | mod create; 5 | mod delete; 6 | mod exec; 7 | mod kill; 8 | mod start; 9 | mod state; 10 | mod util; 11 | pub use container_create::ContainerCreate; 12 | pub use container_lifecycle::ContainerLifecycle; 13 | pub use util::get_result_from_output; 14 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/start.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | 5 | use super::get_result_from_output; 6 | use crate::utils::test_utils::start_container; 7 | 8 | pub fn start(project_path: &Path, id: &str) -> Result<()> { 9 | let res = start_container(id, project_path) 10 | .expect("failed to execute start command") 11 | .wait_with_output(); 12 | get_result_from_output(res) 13 | } 14 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/lifecycle/util.rs: -------------------------------------------------------------------------------- 1 | use std::{io, process}; 2 | 3 | use anyhow::{bail, Result}; 4 | 5 | pub fn get_result_from_output(res: io::Result) -> Result<()> { 6 | match res { 7 | io::Result::Ok(output) => { 8 | let stderr = String::from_utf8(output.stderr).unwrap(); 9 | if stderr.contains("Error") || stderr.contains("error") { 10 | let stdout = String::from_utf8(output.stdout).unwrap(); 11 | bail!("Error :\nstdout : {}\nstderr : {}", stdout, stderr) 12 | } else { 13 | Ok(()) 14 | } 15 | } 16 | io::Result::Err(e) => Err(anyhow::Error::new(e)), 17 | } 18 | } 19 | 20 | pub fn criu_installed() -> bool { 21 | which::which("criu").is_ok() 22 | } 23 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/linux_masked_paths/mod.rs: -------------------------------------------------------------------------------- 1 | mod masked_paths; 2 | 3 | pub use masked_paths::get_linux_masked_paths_tests; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/linux_ns_itype/mod.rs: -------------------------------------------------------------------------------- 1 | mod ns_itype_test; 2 | pub use ns_itype_test::get_ns_itype_tests; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cgroups; 2 | pub mod delete; 3 | pub mod devices; 4 | pub mod domainname; 5 | pub mod example; 6 | pub mod fd_control; 7 | pub mod hooks; 8 | pub mod hostname; 9 | pub mod intel_rdt; 10 | pub mod io_priority; 11 | pub mod kill; 12 | pub mod lifecycle; 13 | pub mod linux_masked_paths; 14 | pub mod linux_ns_itype; 15 | pub mod mounts_recursive; 16 | pub mod no_pivot; 17 | pub mod pidfile; 18 | pub mod process; 19 | pub mod process_capabilities_fail; 20 | pub mod process_oom_score_adj; 21 | pub mod process_rlimits; 22 | pub mod process_rlimits_fail; 23 | pub mod process_user; 24 | pub mod readonly_paths; 25 | pub mod root_readonly_true; 26 | pub mod rootfs_propagation; 27 | pub mod scheduler; 28 | pub mod seccomp; 29 | pub mod seccomp_notify; 30 | pub mod sysctl; 31 | pub mod tlb; 32 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/no_pivot/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; 3 | use test_framework::{test_result, Test, TestGroup, TestResult}; 4 | 5 | use crate::utils::test_utils::{test_inside_container, CreateOptions}; 6 | 7 | fn create_spec() -> Result { 8 | SpecBuilder::default() 9 | .process( 10 | ProcessBuilder::default() 11 | .args(vec!["runtimetest".to_string(), "no_pivot".to_string()]) 12 | .build()?, 13 | ) 14 | .build() 15 | .context("failed to create spec") 16 | } 17 | 18 | fn no_pivot_test() -> TestResult { 19 | let spec = test_result!(create_spec()); 20 | test_inside_container( 21 | &spec, 22 | &CreateOptions::default().with_no_pivot_root(), 23 | &|_| Ok(()), 24 | ) 25 | } 26 | 27 | pub fn get_no_pivot_test() -> TestGroup { 28 | let mut test_group = TestGroup::new("no_pivot"); 29 | let no_pivot_test = Test::new("no_pivot_test", Box::new(no_pivot_test)); 30 | test_group.add(vec![Box::new(no_pivot_test)]); 31 | 32 | test_group 33 | } 34 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/pidfile/mod.rs: -------------------------------------------------------------------------------- 1 | mod pidfile_test; 2 | pub use pidfile_test::get_pidfile_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_test; 2 | pub use process_test::get_process_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process/process_test.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | 3 | use anyhow::{bail, Context, Ok, Result}; 4 | use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; 5 | use test_framework::{test_result, Test, TestGroup, TestResult}; 6 | 7 | use crate::utils::test_inside_container; 8 | use crate::utils::test_utils::CreateOptions; 9 | 10 | fn create_spec() -> Result { 11 | let mut process = ProcessBuilder::default() 12 | .args(vec!["runtimetest".to_string(), "process".to_string()]) 13 | .cwd("/test") 14 | .build() 15 | .expect("error in creating process config"); 16 | let mut env = process.env().clone().unwrap(); 17 | env.push("testa=valuea".to_string()); 18 | env.push("testb=123".to_string()); 19 | process.set_env(Some(env)); 20 | 21 | let spec = SpecBuilder::default() 22 | .process(process) 23 | .build() 24 | .context("failed to build spec")?; 25 | 26 | Ok(spec) 27 | } 28 | 29 | fn process_test() -> TestResult { 30 | let spec = test_result!(create_spec()); 31 | 32 | test_inside_container(&spec, &CreateOptions::default(), &|bundle| { 33 | match fs::create_dir(bundle.join("test")) { 34 | Result::Ok(_) => { /*This is expected*/ } 35 | Err(e) => { 36 | bail!(e) 37 | } 38 | } 39 | 40 | Ok(()) 41 | }) 42 | } 43 | 44 | pub fn get_process_test() -> TestGroup { 45 | let mut process_test_group = TestGroup::new("process"); 46 | 47 | let test = Test::new("process_test", Box::new(process_test)); 48 | process_test_group.add(vec![Box::new(test)]); 49 | 50 | process_test_group 51 | } 52 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_capabilities_fail/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_capabilities_fail_test; 2 | pub use process_capabilities_fail_test::get_process_capabilities_fail_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_oom_score_adj/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_oom_score_adj_test; 2 | pub use process_oom_score_adj_test::get_process_oom_score_adj_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_oom_score_adj/process_oom_score_adj_test.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Ok, Result}; 2 | use oci_spec::runtime::{ProcessBuilder, Spec, SpecBuilder}; 3 | use rand::Rng; 4 | use test_framework::{test_result, Test, TestGroup, TestResult}; 5 | 6 | use crate::utils::test_inside_container; 7 | use crate::utils::test_utils::CreateOptions; 8 | 9 | fn generate_random_number() -> i32 { 10 | let mut rng = rand::rng(); 11 | rng.random_range(300..=700) 12 | } 13 | 14 | fn create_spec() -> Result { 15 | let spec = SpecBuilder::default() 16 | .process( 17 | ProcessBuilder::default() 18 | .args(vec![ 19 | "runtimetest".to_string(), 20 | "process_oom_score_adj".to_string(), 21 | ]) 22 | .oom_score_adj(generate_random_number()) 23 | .build() 24 | .expect("error in creating process config"), 25 | ) 26 | .build() 27 | .context("failed to build spec")?; 28 | 29 | Ok(spec) 30 | } 31 | 32 | fn process_oom_score_adj_test() -> TestResult { 33 | let spec = test_result!(create_spec()); 34 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 35 | } 36 | 37 | pub fn get_process_oom_score_adj_test() -> TestGroup { 38 | let mut process_oom_score_adj_test_group = TestGroup::new("process_oom_score_adj"); 39 | 40 | let test = Test::new( 41 | "process_oom_score_adj", 42 | Box::new(process_oom_score_adj_test), 43 | ); 44 | process_oom_score_adj_test_group.add(vec![Box::new(test)]); 45 | 46 | process_oom_score_adj_test_group 47 | } 48 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_rlimits/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_rlimits_test; 2 | pub use process_rlimits_test::get_process_rlimits_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_rlimits_fail/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_rlimits_fail_test; 2 | pub use process_rlimits_fail_test::get_process_rlimits_fail_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/process_user/mod.rs: -------------------------------------------------------------------------------- 1 | mod process_user_test; 2 | pub use process_user_test::get_process_user_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/readonly_paths/mod.rs: -------------------------------------------------------------------------------- 1 | mod readonly_paths_tests; 2 | pub use readonly_paths_tests::get_ro_paths_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/root_readonly_true/mod.rs: -------------------------------------------------------------------------------- 1 | mod root_readonly_tests; 2 | pub use root_readonly_tests::get_root_readonly_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/root_readonly_true/root_readonly_tests.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Ok, Result}; 2 | use oci_spec::runtime::{ProcessBuilder, RootBuilder, Spec, SpecBuilder}; 3 | use test_framework::{test_result, Test, TestGroup, TestResult}; 4 | 5 | use crate::utils::test_inside_container; 6 | use crate::utils::test_utils::CreateOptions; 7 | 8 | fn create_spec(readonly: bool) -> Result { 9 | let spec = SpecBuilder::default() 10 | .root(RootBuilder::default().readonly(readonly).build().unwrap()) 11 | .process( 12 | ProcessBuilder::default() 13 | .args(vec!["runtimetest".to_string(), "root_readonly".to_string()]) 14 | .build() 15 | .expect("error in creating config"), 16 | ) 17 | .build() 18 | .context("failed to build spec")?; 19 | 20 | Ok(spec) 21 | } 22 | 23 | fn root_readonly_true_test() -> TestResult { 24 | let spec_true = test_result!(create_spec(true)); 25 | test_inside_container(&spec_true, &CreateOptions::default(), &|_| Ok(())) 26 | } 27 | 28 | fn root_readonly_false_test() -> TestResult { 29 | let spec_false = test_result!(create_spec(false)); 30 | test_inside_container(&spec_false, &CreateOptions::default(), &|_| Ok(())) 31 | } 32 | 33 | pub fn get_root_readonly_test() -> TestGroup { 34 | let mut root_readonly_test_group = TestGroup::new("root_readonly"); 35 | 36 | let test_true = Test::new("root_readonly_true_test", Box::new(root_readonly_true_test)); 37 | let test_false = Test::new( 38 | "root_readonly_false_test", 39 | Box::new(root_readonly_false_test), 40 | ); 41 | root_readonly_test_group.add(vec![Box::new(test_true), Box::new(test_false)]); 42 | 43 | root_readonly_test_group 44 | } 45 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/rootfs_propagation/mod.rs: -------------------------------------------------------------------------------- 1 | mod rootfs_propagation_test; 2 | pub use rootfs_propagation_test::get_rootfs_propagation_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/scheduler/mod.rs: -------------------------------------------------------------------------------- 1 | mod scheduler_policy; 2 | 3 | pub use scheduler_policy::get_scheduler_test; 4 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/scheduler/scheduler_policy.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use oci_spec::runtime::{ 3 | LinuxSchedulerPolicy, ProcessBuilder, SchedulerBuilder, Spec, SpecBuilder, 4 | }; 5 | use test_framework::{test_result, ConditionalTest, TestGroup, TestResult}; 6 | 7 | use crate::utils::test_utils::CreateOptions; 8 | use crate::utils::{is_runtime_runc, test_inside_container}; 9 | 10 | fn create_spec(policy: LinuxSchedulerPolicy, execute_test: &str) -> Result { 11 | let sc = SchedulerBuilder::default() 12 | .policy(policy) 13 | .nice(1i32) 14 | .build() 15 | .unwrap(); 16 | SpecBuilder::default() 17 | .process( 18 | ProcessBuilder::default() 19 | .args( 20 | ["runtimetest", execute_test] 21 | .iter() 22 | .map(|s| s.to_string()) 23 | .collect::>(), 24 | ) 25 | .scheduler(sc) 26 | .build()?, 27 | ) 28 | .build() 29 | .context("failed to create spec") 30 | } 31 | 32 | fn scheduler_policy_other_test() -> TestResult { 33 | let spec = test_result!(create_spec( 34 | LinuxSchedulerPolicy::SchedOther, 35 | "scheduler_policy_other" 36 | )); 37 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 38 | } 39 | 40 | fn scheduler_policy_batch_test() -> TestResult { 41 | let spec = test_result!(create_spec( 42 | LinuxSchedulerPolicy::SchedBatch, 43 | "scheduler_policy_batch" 44 | )); 45 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 46 | } 47 | 48 | pub fn get_scheduler_test() -> TestGroup { 49 | let mut scheduler_policy_group = TestGroup::new("set_scheduler_policy"); 50 | let policy_fifo_test = ConditionalTest::new( 51 | "policy_other", 52 | Box::new(|| !is_runtime_runc()), 53 | Box::new(scheduler_policy_other_test), 54 | ); 55 | let policy_rr_test = ConditionalTest::new( 56 | "policy_batch", 57 | Box::new(|| !is_runtime_runc()), 58 | Box::new(scheduler_policy_batch_test), 59 | ); 60 | 61 | scheduler_policy_group.add(vec![Box::new(policy_fifo_test), Box::new(policy_rr_test)]); 62 | scheduler_policy_group 63 | } 64 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/seccomp/mod.rs: -------------------------------------------------------------------------------- 1 | use oci_spec::runtime::{ 2 | LinuxBuilder, LinuxSeccomp, LinuxSeccompAction, LinuxSeccompBuilder, LinuxSyscallBuilder, 3 | ProcessBuilder, Spec, SpecBuilder, 4 | }; 5 | use test_framework::{Test, TestGroup, TestResult}; 6 | 7 | use crate::utils::test_inside_container; 8 | use crate::utils::test_utils::CreateOptions; 9 | 10 | fn create_spec(seccomp: LinuxSeccomp) -> Spec { 11 | SpecBuilder::default() 12 | .linux( 13 | LinuxBuilder::default() 14 | .seccomp(seccomp) 15 | .build() 16 | .expect("error in building linux config"), 17 | ) 18 | .process( 19 | ProcessBuilder::default() 20 | .args(vec!["runtimetest".to_string(), "seccomp".to_string()]) 21 | .build() 22 | .expect("error in creating process config"), 23 | ) 24 | .build() 25 | .unwrap() 26 | } 27 | 28 | fn seccomp_test() -> TestResult { 29 | let spec = create_spec( 30 | LinuxSeccompBuilder::default() 31 | .default_action(LinuxSeccompAction::ScmpActAllow) 32 | .syscalls(vec![LinuxSyscallBuilder::default() 33 | .names(vec![String::from("getcwd")]) 34 | .action(LinuxSeccompAction::ScmpActErrno) 35 | .build() 36 | .unwrap()]) 37 | .build() 38 | .unwrap(), 39 | ); 40 | test_inside_container(&spec, &CreateOptions::default(), &|_| Ok(())) 41 | } 42 | 43 | pub fn get_seccomp_test() -> TestGroup { 44 | let mut test_group = TestGroup::new("seccomp"); 45 | let seccomp_test = Test::new("seccomp_test", Box::new(seccomp_test)); 46 | test_group.add(vec![Box::new(seccomp_test)]); 47 | 48 | test_group 49 | } 50 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/sysctl/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use oci_spec::runtime::{LinuxBuilder, ProcessBuilder, Spec, SpecBuilder}; 4 | use test_framework::{Test, TestGroup, TestResult}; 5 | 6 | use crate::utils::test_inside_container; 7 | use crate::utils::test_utils::CreateOptions; 8 | 9 | fn create_spec(sysctl: HashMap) -> Spec { 10 | SpecBuilder::default() 11 | .linux( 12 | LinuxBuilder::default() 13 | .sysctl(sysctl) 14 | .build() 15 | .expect("error in building linux config"), 16 | ) 17 | .process( 18 | ProcessBuilder::default() 19 | .args(vec!["runtimetest".to_string(), "sysctl".to_string()]) 20 | .build() 21 | .expect("error in creating process config"), 22 | ) 23 | .build() 24 | .unwrap() 25 | } 26 | 27 | fn sysctl_test() -> TestResult { 28 | let spec = create_spec(HashMap::from([( 29 | "net.ipv4.ip_forward".to_string(), 30 | "1".to_string(), 31 | )])); 32 | test_inside_container(&spec, &CreateOptions::default(), &|_| { 33 | // As long as the container is created, we expect the kernel parameters to be determined by 34 | // the spec, so nothing to prepare prior. 35 | Ok(()) 36 | }) 37 | } 38 | 39 | pub fn get_sysctl_test() -> TestGroup { 40 | let mut test_group = TestGroup::new("sysctl"); 41 | let sysctl_test = Test::new("sysctl_test", Box::new(sysctl_test)); 42 | test_group.add(vec![Box::new(sysctl_test)]); 43 | 44 | test_group 45 | } 46 | -------------------------------------------------------------------------------- /tests/contest/contest/src/tests/tlb/mod.rs: -------------------------------------------------------------------------------- 1 | mod tlb_test; 2 | pub use tlb_test::get_tlb_test; 3 | -------------------------------------------------------------------------------- /tests/contest/contest/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod support; 2 | pub mod test_utils; 3 | 4 | pub use support::{ 5 | generate_uuid, get_runtime_path, get_runtimetest_path, is_runtime_runc, prepare_bundle, 6 | set_config, 7 | }; 8 | pub use test_utils::{ 9 | create_container, delete_container, get_state, kill_container, test_inside_container, 10 | test_outside_container, CreateOptions, State, 11 | }; 12 | -------------------------------------------------------------------------------- /tests/contest/runtimetest/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | rustflags = ["-C", "target-feature=+crt-static"] 3 | -------------------------------------------------------------------------------- /tests/contest/runtimetest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "runtimetest" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | oci-spec = { version = "0.8.1", features = ["runtime"] } 8 | nix = "0.29.0" 9 | anyhow = "1.0" 10 | libc = "0.2.172" # TODO (YJDoc2) upgrade to latest 11 | nc = "0.9.6" 12 | tempfile = "3" 13 | -------------------------------------------------------------------------------- /tests/contest/test_framework/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_framework" 3 | version = "0.0.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.98" 10 | crossbeam = "0.8.4" 11 | -------------------------------------------------------------------------------- /tests/contest/test_framework/src/conditional_test.rs: -------------------------------------------------------------------------------- 1 | //! Contains definition for a tests which should be conditionally run 2 | use crate::testable::{TestResult, Testable}; 3 | 4 | // type aliases for test function signature 5 | type TestFn = dyn Fn() -> TestResult + Sync + Send; 6 | // type alias for function signature for function which checks if a test can be run or not 7 | type CheckFn = dyn Fn() -> bool + Sync + Send; 8 | 9 | /// Basic Template structure for tests which need to be run conditionally 10 | pub struct ConditionalTest { 11 | /// name of the test 12 | name: &'static str, 13 | /// actual test function 14 | test_fn: Box, 15 | /// function to check if a test can be run or not 16 | check_fn: Box, 17 | } 18 | 19 | impl ConditionalTest { 20 | /// Create a new condition test 21 | pub fn new(name: &'static str, check_fn: Box, test_fn: Box) -> Self { 22 | ConditionalTest { 23 | name, 24 | check_fn, 25 | test_fn, 26 | } 27 | } 28 | } 29 | 30 | impl Testable for ConditionalTest { 31 | fn get_name(&self) -> &'static str { 32 | self.name 33 | } 34 | 35 | fn can_run(&self) -> bool { 36 | (self.check_fn)() 37 | } 38 | 39 | fn run(&self) -> TestResult { 40 | (self.test_fn)() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/contest/test_framework/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod conditional_test; 2 | mod test; 3 | mod test_group; 4 | mod test_manager; 5 | pub mod testable; 6 | pub use conditional_test::ConditionalTest; 7 | pub use test::Test; 8 | pub use test_group::TestGroup; 9 | pub use test_manager::TestManager; 10 | pub use testable::{TestResult, Testable, TestableGroup}; 11 | -------------------------------------------------------------------------------- /tests/contest/test_framework/src/test.rs: -------------------------------------------------------------------------------- 1 | //! Contains definition for a simple and commonly usable test structure 2 | use crate::testable::{TestResult, Testable}; 3 | 4 | // type alias for the test function 5 | type TestFn = dyn Sync + Send + Fn() -> TestResult; 6 | 7 | /// Basic Template structure for a test 8 | pub struct Test { 9 | /// name of the test 10 | name: &'static str, 11 | /// Actual test function 12 | test_fn: Box, 13 | } 14 | 15 | impl Test { 16 | /// create new test 17 | pub fn new(name: &'static str, test_fn: Box) -> Self { 18 | Test { name, test_fn } 19 | } 20 | } 21 | 22 | impl Testable for Test { 23 | fn get_name(&self) -> &'static str { 24 | self.name 25 | } 26 | 27 | fn run(&self) -> TestResult { 28 | (self.test_fn)() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/dind/daemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimes": { 3 | "youki": { 4 | "path": "/usr/bin/youki" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /tests/dind/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ROOT=$(git rev-parse --show-toplevel) 5 | 6 | docker run --privileged -dq \ 7 | --name youki-test-dind \ 8 | -v $ROOT/youki:/usr/bin/youki \ 9 | -v $ROOT/tests/dind/daemon.json:/etc/docker/daemon.json \ 10 | docker:dind > /dev/null 11 | 12 | trap "docker rm -f youki-test-dind > /dev/null" EXIT 13 | 14 | # wait for docker to start 15 | timeout 30s \ 16 | grep -q -m1 "/var/run/docker.sock" \ 17 | <(docker logs -f youki-test-dind 2>&1) 18 | 19 | docker exec -i youki-test-dind \ 20 | docker run -q --runtime=youki hello-world 21 | -------------------------------------------------------------------------------- /tests/k8s/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | ARG KIND_NODE_VERSION=v1.23.13 4 | 5 | FROM kindest/node:${KIND_NODE_VERSION} AS kind-base 6 | 7 | FROM kind-base AS shim-build 8 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rustup.sh && sh /tmp/rustup.sh -y --profile=minimal 9 | ENV PATH="/root/.cargo/bin:${PATH}" 10 | WORKDIR /shim 11 | COPY ./youki /shim/youki 12 | 13 | FROM scratch AS shim 14 | COPY --from=shim-build /shim/youki / 15 | 16 | FROM kind-base AS kind-fetch 17 | ARG TARGETARCH 18 | ARG KIND_VERSION=v0.17.0 19 | RUN curl -sSLf https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-${TARGETARCH} > /root/kind && chmod +x /root/kind 20 | 21 | FROM scratch AS kind-bin 22 | COPY --from=kind-fetch /root/kind /kind 23 | 24 | FROM kind-base 25 | RUN <> /etc/containerd/config.toml 28 | echo ' runtime_type = "io.containerd.runc.v2"' >> /etc/containerd/config.toml 29 | echo ' [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.youki.options]' >> /etc/containerd/config.toml 30 | echo ' BinaryName = "/usr/local/bin/youki"' >> /etc/containerd/config.toml 31 | sed -i 's,SystemdCgroup = true,,' /etc/containerd/config.toml 32 | EOF 33 | COPY justfile justfile 34 | RUN curl -o just.tar.gz -L https://github.com/casey/just/releases/download/1.14.0/just-1.14.0-x86_64-unknown-linux-musl.tar.gz 35 | RUN tar zxvf just.tar.gz just 36 | RUN ./just ci-prepare 37 | COPY --link --from=shim /* /usr/local/bin/ 38 | 39 | -------------------------------------------------------------------------------- /tests/k8s/deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: node.k8s.io/v1 2 | kind: RuntimeClass 3 | metadata: 4 | name: youki 5 | handler: youki 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: nginx-deployment 11 | spec: 12 | selector: 13 | matchLabels: 14 | app: nginx 15 | replicas: 2 16 | template: 17 | metadata: 18 | labels: 19 | app: nginx 20 | spec: 21 | runtimeClassName: youki 22 | containers: 23 | - name: nginx 24 | image: nginx:alpine 25 | ports: 26 | - containerPort: 80 27 | -------------------------------------------------------------------------------- /tests/rootless-tests/run.sh: -------------------------------------------------------------------------------- 1 | # This is a temporary test-collection for validating youki runs correctly with podman in rootless mode 2 | # This will be moved to a proper rust based test crate, similar to rust-integration tests soon 3 | 4 | set -ex 5 | 6 | runtime=$1 7 | 8 | podman rm --force --ignore create-test # remove if existing 9 | 10 | podman create --runtime $runtime --name create-test hello-world 11 | log=$(podman start -a create-test) 12 | echo $log | grep "This message shows that your installation appears to be working correctly" 13 | podman rm --force --ignore create-test 14 | 15 | rand=$(head -c 10 /dev/random | base64) 16 | 17 | log=$(podman run --runtime $runtime fedora echo "$rand") 18 | echo $log | grep $rand 19 | 20 | podman kill exec-test || true # ignore failure for killing 21 | podman rm --force --ignore exec-test 22 | podman run -d --runtime $runtime --name exec-test busybox sleep 10m 23 | 24 | rand=$(head -c 10 /dev/random | base64) 25 | 26 | log=$(podman exec --runtime $runtime exec-test echo "$rand") 27 | echo $log | grep $rand 28 | 29 | CGROUP_SUB_PATH=$(podman inspect exec-test | jq .[0].State.CgroupPath | tr -d "\"") 30 | CGROUP_PATH="/sys/fs/cgroup$CGROUP_SUB_PATH/cgroup.procs" 31 | 32 | # assert we have exactly one process in the cgroup 33 | test $(cat $CGROUP_PATH | wc -l) -eq 1 34 | # assert pid match 35 | test $(cat $CGROUP_PATH) -eq $(podman inspect exec-test | jq .[0].State.Pid) 36 | 37 | podman exec -d --runtime $runtime exec-test sleep 5m 38 | 39 | # we cannot exactly check the pid of tenant here, instead just check that there are 40 | # two processes in the same cgroup now 41 | test $(cat $CGROUP_PATH | wc -l) -eq 2 42 | 43 | podman kill exec-test 44 | podman rm --force --ignore exec-test 45 | -------------------------------------------------------------------------------- /tools/wasm-sample/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-sample" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /tools/wasm-sample/README.md: -------------------------------------------------------------------------------- 1 | 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 2 | 3 | ``` 4 | cargo build --target wasm32-wasi 5 | ``` 6 | 7 | If 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). 8 | 9 | ``` 10 | "ociVersion": "1.0.2-dev", 11 | "annotations": { 12 | "run.oci.handler": "wasm" 13 | }, 14 | "process": { 15 | "terminal": true, 16 | "user": { 17 | "uid": 0, 18 | "gid": 0 19 | }, 20 | "args": [ 21 | "/wasm-sample.wasm", 22 | "hello", 23 | "wasm" 24 | ], 25 | ``` 26 | -------------------------------------------------------------------------------- /tools/wasm-sample/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Printing args"); 3 | for arg in std::env::args().skip(1) { 4 | println!("{arg}"); 5 | } 6 | 7 | println!("Printing envs"); 8 | for envs in std::env::vars() { 9 | println!("{envs:?}"); 10 | } 11 | } 12 | --------------------------------------------------------------------------------