[
  {
    "path": ".codecov.yml",
    "content": "---\ncodecov:\n  notify:\n    after_n_builds: 1\n    require_ci_to_pass: false\n\ncoverage:\n  precision: 2\n  round: down\n  range: 50..75\n\ncomment:\n  layout: \"header, diff\"\n  behavior: default\n  require_changes: false\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n\t\"name\": \"oci-spec-rs\",\n\t\"image\": \"mcr.microsoft.com/devcontainers/rust:1-bullseye\",\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"settings\": {\n\t\t\t\t\"files.watcherExclude\": {\n\t\t\t\t\t\"**/target/**\": true\n\t\t\t\t},\n\t\t\t\t\"rust-analyzer.checkOnSave.command\": \"clippy\"\n\t\t\t},\n\t\t\t\"extensions\": [\n\t\t\t\t\"rust-lang.rust-analyzer\",\n\t\t\t\t\"tamasfe.even-better-toml\"\n\t\t\t]\n\t\t}\n\t}\n}"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--  Thanks for sending a pull request!\n\nPlease be aware that we're following the Kubernetes guidelines of contributing\nto this project. This means that we have to use this mandatory template for all\nof our pull requests.\n\nPlease also make sure you've read and understood our contributing guidelines\n(https://github.com/containers/oci-spec-rs/blob/main/CONTRIBUTING.md) as well as\nensuring that all your commits are signed with `git commit -s`.\n\nHere are some additional tips for you:\n\n- If this is your first time, please read our contributor guidelines:\n  https://git.k8s.io/community/contributors/guide#your-first-contribution and\n  developer guide\n  https://git.k8s.io/community/contributors/devel/development.md#development-guide\n- Please label this pull request according to what type of issue you are\n  addressing, especially if this is a release targeted pull request. For\n  reference on required PR/issue labels, read here:\n  https://git.k8s.io/community/contributors/devel/sig-release/release.md#issuepr-kind-label\n- If you want *faster* PR reviews, read how:\n  https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews\n- If the PR is unfinished, see how to mark it:\n  https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests\n-->\n\n#### What type of PR is this?\n\n<!--\nUncomment only one `/kind <>` line, hit enter to put that in a new line, and\nremove leading whitespace from that line:\n-->\n\n<!--\n/kind api-change\n/kind bug\n/kind ci\n/kind cleanup\n/kind dependency-change\n/kind deprecation\n/kind design\n/kind documentation\n/kind failing-test\n/kind feature\n/kind flake\n/kind other\n-->\n\n#### What this PR does / why we need it:\n\n#### Which issue(s) this PR fixes:\n\n<!--\nAutomatically closes linked issue when PR is merged.\nUsage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.\n-->\n\n<!--\nFixes #\nor\nNone\n-->\n\n#### Special notes for your reviewer:\n\n#### Does this PR introduce a user-facing change?\n\n<!--\nIf no, just write `None` in the release-note block below. If yes, a release note\nis required: Enter your extended release note in the block below. If the PR\nrequires additional action from users switching to the new release, include the\nstring \"action required\".\n\nFor more information on release notes see:\nhttps://git.k8s.io/community/contributors/guide/release-notes.md\n-->\n\n```release-note\n\n```\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      interval: daily\n      time: \"11:00\"\n    open-pull-requests-limit: 10\n    allow:\n      - dependency-type: direct\n      - dependency-type: indirect\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 10\n    labels:\n      - \"kind/test\"\n    groups:\n      actions:\n        update-types:\n          - \"major\"\n          - \"minor\"\n          - \"patch\"\n"
  },
  {
    "path": ".github/grcov.yml",
    "content": "branch: true\nignore-not-existing: true\nllvm: true\nfilter: covered\noutput-type: lcov\noutput-path: ./lcov.info\nprefix-dir: /home/user/build/\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    authors:\n      - dependabot\n  categories:\n    - title: Breaking Changes\n      labels:\n        - breaking change\n    - title: Runtime Specification\n      labels:\n        - kind/runtime\n    - title: Image Format Specification\n      labels:\n        - kind/image\n    - title: Distribution Specification\n      labels:\n        - kind/distribution\n    - title: Test improvements and Misc Fixes\n      labels:\n        - kind/test\n        - kind/cleanup\n    - title: Other Changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\non:\n  pull_request: {}\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n      - run: cargo build --all-features\n\n  doc:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-doc-${{ hashFiles('**/Cargo.lock') }}\n      - run: cargo doc --all-features --no-deps\n\n  lint-clippy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }}\n      - name: Select Toolchain\n        uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9\n        with:\n          toolchain: nightly\n          components: clippy\n      - name: Clippy Lint\n        run: cargo clippy --all --all-features -- -D warnings\n\n  lint-rustfmt:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Rustfmt\n        run: cargo fmt && git diff --exit-code\n\n  test-unit:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Install cargo-llvm-cov\n        uses: taiki-e/install-action@cargo-llvm-cov\n      - name: Generate code coverage\n        run: cargo llvm-cov --all-features --lcov --output-path lcov.info\n      - name: Upload Results\n        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0\n        with:\n          files: lcov.info\n"
  },
  {
    "path": ".github/workflows/cross.yml",
    "content": "name: cross\non:\n  pull_request: {}\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        target:\n          - x86_64-unknown-linux-gnu\n          - aarch64-unknown-linux-gnu\n          - powerpc64le-unknown-linux-gnu\n          - s390x-unknown-linux-gnu\n          - x86_64-pc-windows-gnu\n          - x86_64-unknown-freebsd\n          - x86_64-pc-solaris\n    name: ${{matrix.target}}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cross-${{matrix.target}}-${{ hashFiles('**/Cargo.lock') }}\n      - name: Install cross-rs\n        run: |\n          cargo install cross --git https://github.com/cross-rs/cross\n          cross --version\n      - name: Ensure the latest base image\n        run: docker pull ghcr.io/cross-rs/${{matrix.target}}:main\n      - name: Build for ${{matrix.target}}\n        run: cross build -v --target ${{matrix.target}}\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: gh-pages\non:\n  push:\n    branches:\n      - main\nenv:\n  CARGO_TERM_COLOR: always\njobs:\n  update:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n      - name: Build Documentation\n        run: cargo doc --all-features\n      - name: Deploy Documentation\n        uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0\n        with:\n          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}\n          publish_branch: gh-pages\n          publish_dir: ./target/doc\n          force_orphan: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags: [ \"v[0-9]+.[0-9]+.[0-9]+*\" ]\n\njobs:\n  publish:\n    name: Publish Packages\n    runs-on: ubuntu-latest\n    env:\n      CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Cache\n        uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5\n        with:\n          path: |\n            ~/.cargo/registry\n            ~/.cargo/git\n            target\n          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}\n      - name: Publish\n        run: cargo publish --no-verify\n"
  },
  {
    "path": ".github/workflows/security-audit.yml",
    "content": "name: Security audit\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  push:\n    paths:\n      - \"**/Cargo.toml\"\n      - \"**/Cargo.lock\"\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  audit:\n    permissions:\n      checks: write # for rustsec/audit-check to create check\n      contents: read # for actions/checkout to fetch code\n      issues: write # for rustsec/audit-check to create issues\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Generate lockfile\n        run: cargo generate-lockfile\n      - uses: rustsec/audit-check@69366f33c96575abad1ee0dba8212993eecbe998 # v2.0.0\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n"
  },
  {
    "path": ".rustfmt.toml",
    "content": "max_width = 100\nhard_tabs = false\ntab_spaces = 4\nnewline_style = \"Auto\"\nuse_small_heuristics = \"Default\"\nfn_call_width = 60\nattr_fn_like_width = 70\nstruct_lit_width = 18\nstruct_variant_width = 35\narray_width = 60\nchain_width = 60\nsingle_line_if_else_max_width = 50\nreorder_imports = true\nreorder_modules = true\nremove_nested_parens = true\nmatch_arm_leading_pipes = \"Never\"\nfn_params_layout = \"Tall\"\nedition = \"2018\"\nmerge_derives = true\nuse_try_shorthand = false\nuse_field_init_shorthand = false\nforce_explicit_abi = true\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nWe follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).\n\nPlease contact one of the [project maintainers](MAINTAINERS.md) or the [CNCF\nCode of Conduct Committee](mailto:conduct@cncf.io) in order to report violations\nof the Code of Conduct.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to oci-spec-rs\n\nWe'd love to have you join the community! Below summarizes the processes\nthat we follow.\n\n## Topics\n\n- [Reporting Issues](#reporting-issues)\n- [Submitting Pull Requests](#submitting-pull-requests)\n- [Communications](#communications)\n\n## Reporting Issues\n\nBefore reporting an issue, check our backlog of\n[open issues](https://github.com/containers/oci-spec-rs/issues)\nto see if someone else has already reported it. If so, feel free to add\nyour scenario, or additional information, to the discussion. Or simply\n\"subscribe\" to it to be notified when it is updated.\n\nIf you find a new issue with the project we'd love to hear about it! The most\nimportant aspect of a bug report is that it includes enough information for\nus to reproduce it. So, please include as much detail as possible and try\nto remove the extra stuff that doesn't really relate to the issue itself.\nThe easier it is for us to reproduce it, the faster it'll be fixed!\n\nPlease don't include any private/sensitive information in your issue!\n\n## Submitting Pull Requests\n\nNo Pull Request (PR) is too small! Typos, additional comments in the code,\nnew test cases, bug fixes, new features, more documentation, ... it's all\nwelcome!\n\nWhile bug fixes can first be identified via an \"issue\", that is not required.\nIt's ok to just open up a PR with the fix, but make sure you include the same\ninformation you would have included in an issue - like how to reproduce it.\n\nPRs for new features should include some background on what use cases the\nnew code is trying to address. When possible and when it makes sense, try to break-up\nlarger PRs into smaller ones - it's easier to review smaller\ncode changes. But only if those smaller ones make sense as stand-alone PRs.\n\nRegardless of the type of PR, all PRs should include:\n\n- well documented code changes\n- additional testcases. Ideally, they should fail w/o your code change applied\n- documentation changes\n\nSquash your commits into logical pieces of work that might want to be reviewed\nseparate from the rest of the PRs. But, squashing down to just one commit is ok\ntoo since in the end the entire PR will be reviewed anyway. When in doubt,\nsquash.\n\nTest your changes by running:\n\n```console\n$ make clippy\n```\n\nAnd you can run the test suite if you have access to elevated permissions:\n\n```console\n# cargo test\n```\n\nPRs that fix issues should include a reference like `Closes #XXXX` in the\ncommit message so that github will automatically close the referenced issue\nwhen the PR is merged.\n\nMost PRs will be reviewed by the [OWNERS](OWNERS).\n\n### Sign your PRs\n\nThe sign-off is a line at the end of the explanation for the patch. Your\nsignature certifies that you wrote the patch or otherwise have the right to pass\nit on as an open-source patch. The rules are simple: if you can certify\nthe below (from [developercertificate.org](http://developercertificate.org/)):\n\n```\nDeveloper Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n```\n\nThen you just add a line to every git commit message:\n\n    Signed-off-by: John Doe <john.doe@email.com>\n\nUse your real name (sorry, no pseudonyms or anonymous contributions.)\n\nIf you set your `user.name` and `user.email` git configs, you can sign your\ncommit automatically with `git commit -s`.\n\n## Communications\n\nFor discussions around issues/bugs and features, you can use the GitHub\n[issues](https://github.com/containers/oci-spec-rs/issues) and\n[PRs](https://github.com/containers/oci-spec-rs/pulls) tracking system.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"oci-spec\"\nversion = \"0.9.0\"\nedition = \"2021\"\nauthors = [\n    \"Furisto\",\n    \"Sascha Grunert <sgrunert@redhat.com>\",\n    \"Toru Komatsu <k0ma@utam0k.jp>\",\n]\ndescription = \"Open Container Initiative Specifications in Rust\"\ndocumentation = \"https://docs.rs/oci-spec\"\nreadme = \"README.md\"\nhomepage = \"https://github.com/youki-dev/oci-spec-rs\"\nrepository = \"https://github.com/youki-dev/oci-spec-rs\"\nlicense = \"Apache-2.0\"\nkeywords = [\"oci\", \"spec\", \"container\", \"runtime\", \"image\"]\ncategories = [\"api-bindings\"]\n\n[features]\ndefault = [\"distribution\", \"image\", \"runtime\"]\nproptests = [\"quickcheck\"]\ndistribution = []\nimage = []\nruntime = []\n\n[dependencies]\nconst_format = \"0.2\"\nserde = { version = \"1.0.129\", features = [\"derive\"] }\nthiserror = \"2.0.0\"\nserde_json = \"1.0.66\"\nquickcheck = { version = \"1.0.3\", optional = true }\nderive_builder = \"0.20.0\"\ngetset = \"0.1.3\"\nstrum = \"0.27.0\"\nstrum_macros = \"0.27.0\"\nregex = \"1\"\n\n[dev-dependencies]\ntempfile = \"3.23.0\"\nrstest = \"0.26.1\"\nserde_json = { version = \"1.0.66\", features = [\"preserve_order\"] }\n"
  },
  {
    "path": "GOVERNANCE.md",
    "content": "# oci-spec-rs Project Governance\n\nThe oci-spec-rs project is dedicated to creating an OCI Runtime, Image and\nDistribution specification in Rust. This governance explains how the project is\nrun.\n\n- [Values](#values)\n- [Maintainers](#maintainers)\n- [Becoming a Maintainer](#becoming-a-maintainer)\n- [Meetings](#meetings)\n- [CNCF Resources](#cncf-resources)\n- [Code of Conduct Enforcement](#code-of-conduct)\n- [Security Response Team](#security-response-team)\n- [Voting](#voting)\n- [Modifications](#modifying-this-charter)\n\n## Values\n\nThe oci-spec-rs project and its leadership embrace the following values:\n\n- Openness: Communication and decision-making happens in the open and is\n  discoverable for future reference. As much as possible, all discussions and\n  work take place in public forums and open repositories.\n\n- Fairness: All stakeholders have the opportunity to provide feedback and submit\n  contributions, which will be considered on their merits.\n\n- Community over Product or Company: Sustaining and growing our community takes\n  priority over shipping code or sponsors' organizational goals. Each\n  contributor participates in the project as an individual.\n\n- Inclusivity: We innovate through different perspectives and skill sets, which\n  can only be accomplished in a welcoming and respectful environment.\n\n- Participation: Responsibilities within the project are earned through\n  participation, and there is a clear path up the contributor ladder into\n  leadership positions.\n\n## Maintainers\n\noci-spec-rs Maintainers have write access to the project GitHub repository.\nThey can merge their own patches or patches from others. The current maintainers\ncan be found in [MAINTAINERS.md](MAINTAINERS.md). Maintainers collectively\nmanage the project's resources and contributors.\n\nThis privilege is granted with some expectation of responsibility: maintainers\nare people who care about the oci-spec-rs project and want to help it grow and\nimprove. A maintainer is not just someone who can make changes, but someone who\nhas demonstrated their ability to collaborate with the team, get the most\nknowledgeable people to review code and docs, contribute high-quality code, and\nfollow through to fix issues (in code or tests).\n\nA maintainer is a contributor to the project's success and a citizen helping the\nproject succeed.\n\nThe collective team of all Maintainers is known as the Maintainer Council, which\nis the governing body for the project.\n\n### Becoming a Maintainer\n\nTo become a Maintainer you need to demonstrate the following:\n\n- commitment to the project:\n  - participate in discussions, contributions, code and documentation reviews\n    for 3 months or more,\n  - perform reviews for at least 10 non-trivial pull requests,\n  - contribute at least 5 non-trivial pull requests and have them merged,\n- ability to write quality code and/or documentation,\n- ability to collaborate with the team,\n- understanding of how the team works (policies, processes for testing and code\n  review, etc),\n- understanding of the project's code base and coding and documentation style.\n\nA new Maintainer must be proposed by an existing maintainer by opening an issue\nwithin this repository. A simple majority vote of existing Maintainers approves\nthe application. Maintainers nominations will be evaluated without prejudice to\nemployer or demographics.\n\nMaintainers who are selected will be granted the necessary GitHub rights.\n\n### Removing a Maintainer\n\nMaintainers may resign at any time if they feel that they will not be able to\ncontinue fulfilling their project duties.\n\nMaintainers may also be removed after being inactive, failure to fulfill their\nMaintainer responsibilities, violating the Code of Conduct, or other reasons.\nInactivity is defined as a period of very low or no activity in the project\nfor a year or more, with no definite schedule to return to full Maintainer\nactivity.\n\nA Maintainer may be removed at any time by a 2/3 vote of the remaining\nmaintainers.\n\nDepending on the reason for removal, a Maintainer may be converted to Emeritus\nstatus. Emeritus Maintainers will still be consulted on some project matters,\nand can be rapidly returned to Maintainer status if their availability changes.\n\n## Meetings\n\nThere are no public meetings planned for this particular project.\n\nMaintainers may have closed meetings in order to discuss security reports or\nCode of Conduct violations. Such meetings should be scheduled by any Maintainer\non receipt of a security issue or CoC report. All current Maintainers must be\ninvited to such closed meetings, except for any Maintainer who is accused of a\nCoC violation.\n\n## CNCF Resources\n\nAny Maintainer may suggest a request for CNCF resources, either as issue or\ndiscussion within this repository or during a meeting. A simple majority of\nMaintainers approves the request. The Maintainers may also choose to delegate\nworking with the CNCF to non-Maintainer community members, who will then be\nadded to the [CNCF's Maintainer\nList](https://github.com/cncf/foundation/blob/main/project-maintainers.csv)\nfor that purpose.\n\n## Code of Conduct\n\n[Code of Conduct](CODE_OF_CONDUCT.md) violations by community members will be\ndiscussed and resolved by the Maintainers privately. If a Maintainer is directly\ninvolved in the report, the Maintainers will instead designate two Maintainers\nto work with the CNCF Code of Conduct Committee in resolving it.\n\n## Security Response Team\n\nThe Maintainers will appoint a Security Response Team to handle security\nreports. This committee may simply consist of the Maintainer Council themselves.\nIf this responsibility is delegated, the Maintainers will appoint a team of at\nleast two contributors to handle it. The Maintainers will review who is assigned\nto this at least once a year.\n\nThe Security Response Team is responsible for handling all reports of security\nholes and breaches according to the [security policy](SECURITY.md).\n\n## Voting\n\nWhile most business in oci-spec-rs is conducted by \"[lazy\nconsensus](https://community.apache.org/committers/lazyConsensus.html)\",\nperiodically the Maintainers may need to vote on specific actions or changes. A\nvote can be taken on an GitHub issue or discussion within the project.\n\nMost votes require a simple majority of all Maintainers to succeed, except where\notherwise noted. Two-thirds majority votes mean at least two-thirds of all\nexisting maintainers.\n\n## Modifying this Charter\n\nChanges to this Governance and its supporting documents may be approved by a 2/3\nvote of the Maintainers.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2021] [youki team]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "The current Maintainers Group for the oci-spec-rs project consists of:\n\n| Name            | Employer           | GitHub handle    | Responsibilities        |\n| --------------- | ------------------ | ---------------- | ----------------------- |\n| Colin Walters   | Red Hat            | @cgwalters       | Approver and Maintainer |\n| Flavio Castelli | SUSE               | @flavio          | Approver and Maintainer |\n| Sascha Grunert  | Red Hat            | @saschagrunert   | Approver and Maintainer |\n| Taylor Thomas   | Cosmonic           | @thomastaylor312 | Approver and Maintainer |\n| Toru Komatsu    | Preferred Networks | @utam0k          | Approver and Maintainer |\n| Eric Fang       | Independent        | @yihuaf          | Maintainer              |\n| Jorge Prendes   | Microsoft          | @jprendes        | Maintainer              |\n| Thomas Schubart | Gitpod             | @Furisto         | Maintainer              |\n| Yashodhan       | Independent        | @YJDoc2          | Maintainer              |\n\nThis list must be kept in sync with the [CNCF Project Maintainers list](https://github.com/cncf/foundation/blob/master/project-maintainers.csv).\n\nSee [the project Governance](GOVERNANCE.md) for how maintainers are selected and replaced.\n"
  },
  {
    "path": "OWNERS",
    "content": "filters:\n  .*:\n    reviewers:\n    - oci-spec-rs-reviewers\n    approvers:\n    - oci-spec-rs-approvers\n    - youki-maintainers\n"
  },
  {
    "path": "OWNERS_ALIASES",
    "content": "aliases:\n  oci-spec-rs-approvers:\n    - cgwalters\n    - flavio\n    - saschagrunert\n    - thomastaylor312\n    - utam0k\n  youki-maintainers:\n    - Furisto\n    - jprendes\n    - yihuaf\n    - YJDoc2\n  oci-spec-rs-reviewers: []\n"
  },
  {
    "path": "README.md",
    "content": "# oci-spec-rs\n\n[![ci](https://github.com/containers/oci-spec-rs/workflows/ci/badge.svg)](https://github.com/containers/oci-spec-rs/actions)\n[![gh-pages](https://github.com/containers/oci-spec-rs/workflows/gh-pages/badge.svg)](https://github.com/containers/oci-spec-rs/actions)\n[![crates.io](https://img.shields.io/crates/v/oci-spec.svg)](https://crates.io/crates/oci-spec)\n[![codecov](https://codecov.io/gh/containers/oci-spec-rs/branch/main/graph/badge.svg)](https://codecov.io/gh/containers/oci-spec-rs)\n[![docs](https://img.shields.io/badge/docs-main-blue.svg)](https://containers.github.io/oci-spec-rs/oci_spec/index.html)\n[![docs.rs](https://docs.rs/oci-spec/badge.svg)](https://docs.rs/oci-spec)\n[![dependencies](https://deps.rs/repo/github/containers/oci-spec-rs/status.svg)](https://deps.rs/repo/github/containers/oci-spec-rs)\n[![license](https://img.shields.io/github/license/containers/oci-spec-rs.svg)](https://github.com/containers/oci-spec-rs/blob/master/LICENSE)\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs?ref=badge_shield)\n\n### Open Container Initiative (OCI) Specifications for Rust\n\nThis library provides a convenient way to interact with the specifications defined by the [Open Container Initiative (OCI)](https://opencontainers.org). \n\n- [Image Format Specification](https://github.com/opencontainers/image-spec/blob/main/spec.md)\n- [Runtime Specification](https://github.com/opencontainers/runtime-spec/blob/master/spec.md)\n- [Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md)\n\n```toml\n[dependencies]\noci-spec = \"0.9.0\"\n```\n*Compiler support: requires rustc 1.54+*\n\nIf you want to propose or cut a new release, then please follow our \n[release process documentation](./release.md).\n\n## Image Format Spec Examples\n- Load image manifest from filesystem\n```rust no_run\nuse oci_spec::image::ImageManifest;\n\nlet image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\nassert_eq!(image_manifest.layers().len(), 5);\n```\n\n- Create new image manifest using builder\n```rust no_run\nuse std::str::FromStr;\nuse oci_spec::image::{\n    Descriptor, \n    DescriptorBuilder, \n    ImageManifest, \n    ImageManifestBuilder, \n    MediaType, \n    Sha256Digest,\n    SCHEMA_VERSION\n};\n\nlet config = DescriptorBuilder::default()\n            .media_type(MediaType::ImageConfig)\n            .size(7023u64)\n            .digest(Sha256Digest::from_str(\"b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\").unwrap())\n            .build()\n            .expect(\"build config descriptor\");\n\nlet layers: Vec<Descriptor> = [\n    (\n        32654u64,\n        \"9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0\",\n    ),\n    (\n        16724,\n        \"3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b\",\n    ),\n    (\n        73109,\n        \"ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736\",\n    ),\n]\n    .iter()\n    .map(|l| {\n    DescriptorBuilder::default()\n        .media_type(MediaType::ImageLayerGzip)\n        .size(l.0)\n        .digest(Sha256Digest::from_str(l.1).unwrap())\n        .build()\n        .expect(\"build layer\")\n    })\n    .collect();\n\nlet image_manifest = ImageManifestBuilder::default()\n    .schema_version(SCHEMA_VERSION)\n    .config(config)\n    .layers(layers)\n    .build()\n    .expect(\"build image manifest\");\n\nimage_manifest.to_file_pretty(\"my-manifest.json\").unwrap();\n```\n\n- Content of my-manifest.json\n```json\n{\n  \"schemaVersion\": 2,\n  \"config\": {\n    \"mediaType\": \"application/vnd.oci.image.config.v1+json\",\n    \"digest\": \"sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\",\n    \"size\": 7023\n  },\n  \"layers\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0\",\n      \"size\": 32654\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b\",\n      \"size\": 16724\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736\",\n      \"size\": 73109\n    }\n  ]\n}\n```\n\n## Distribution Spec Examples\n- Create a list of repositories \n```rust\nuse oci_spec::distribution::RepositoryListBuilder;\n\nlet list = RepositoryListBuilder::default()\n            .repositories(vec![\"busybox\".to_owned()])\n            .build().unwrap();\n```\n\n# Contributing\nThis project welcomes your PRs and issues. Should you wish to work on an issue, please claim it first by commenting on the \nissue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.\n\n\n## License\n[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fyouki-dev%2Foci-spec-rs?ref=badge_large)"
  },
  {
    "path": "SECURITY.md",
    "content": "# oci-spec-rs Security\n\nSecurity is taken seriously and has high priority across all related projects to\nensure users can trust this project for their systems.\n\nWe're extremely grateful for security researchers and users that report\nvulnerabilities to the community. All reports are thoroughly investigated by a\nset of community volunteers.\n\n## Report a Vulnerability\n\n<!-- TODO: the mailing list has to be requested -->\n\nTo make a report, email the vulnerability to the private\n[cncf-oci-spec-rs-security@lists.cncf.io](mailto:cncf-crio-security@lists.cncf.io) list\nwith the security details.\n\nYou can expect an initial response to the report within 3 business days.\nPossible fixes for vulnerabilities will be then discussed via the mail thread\nand can be considered as automatically embargoed until they got merged into all\nrelated branches. A project approver or reviewer (as defined in the\n[OWNERS](./OWNERS) file) will coordinate how the pull requests and patches are\nbeing incorporated into the repository without breaking the embargo.\n\n### When Should I Report a Vulnerability?\n\n- You think you discovered a potential security vulnerability\n- You are unsure how a vulnerability affects this project\n- You think you discovered a vulnerability in another project that oci-spec-rs\n  depends on (for projects with their own vulnerability reporting and disclosure\n  process, please report it directly there)\n\n### When Should I NOT Report a Vulnerability?\n\n- You need help tuning components for security\n- You need help applying security related updates\n- Your issue is not security related\n"
  },
  {
    "path": "SECURITY_CONTACTS",
    "content": "# Defined below are the security contacts for this repo.\n#\n# They are the contact point for the Product Security Team to reach out\n# to for triaging and handling of incoming issues.\n#\n# The below names agree to abide by the\n# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)\n# and will be removed and replaced if they violate that agreement.\n#\n# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE\n# INSTRUCTIONS AT ./SECURITY.md\n\ncgwalters\nflavio\nsaschagrunert\nthomastaylor312\nutam0k\n"
  },
  {
    "path": "hack/release",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nVERSION=${1:-}\n\nif [[ -z \"$VERSION\" ]]; then\n    echo \"Please provide a version to be released\"\n    exit 1\nfi\n\ngit checkout -b \"release-$VERSION\"\nsed -Ei \"s/^(version = ).*/\\1\\\"$VERSION\\\"/g\" Cargo.toml\nsed -Ei \"s/^(oci-spec = ).*/\\1\\\"$VERSION\\\"/g\" README.md\n\nVERSION_DEV_FILES=(\n    src/distribution/version.rs\n    src/image/version.rs\n    src/runtime/version.rs\n)\n\nfor FILE in \"${VERSION_DEV_FILES[@]}\"; do\n    sed -Ei \"s/^(pub const VERSION_DEV: &str = ).*/\\1\\\"\\\";/g\" \"$FILE\"\n    sed -Ei 's/(assert_eq!\\(version\\(\\), \"[0-9]+\\.[0-9]+\\.[0-9]+).*(\"\\.to_string\\(\\)\\))/\\1\\2/g' \"$FILE\"\ndone\ngit add .\ngit commit -sm \"Bump to $VERSION\"\n\nfor FILE in \"${VERSION_DEV_FILES[@]}\"; do\n    sed -Ei \"s/^(pub const VERSION_DEV: &str = ).*/\\1\\\"-dev\\\";/g\" \"$FILE\"\n    sed -Ei 's/(assert_eq!\\(version\\(\\), \"[0-9]+\\.[0-9]+\\.[0-9]+).*(\"\\.to_string\\(\\)\\))/\\1-dev\\2/g' \"$FILE\"\ndone\ngit add .\ngit commit -sm \"Back to dev\"\n"
  },
  {
    "path": "release.md",
    "content": "# Releasing a new version of oci-spec-rs\n\nA new release of this crate can be proposed by running the version bump script:\n\n```console\n./hack/release x.y.z\n```\n\nPush the changes to your fork and draft a new GitHub Pull Request (PR) which\nshould now contain 2 commits, one which bumps the release version and another\none to turn it _back to dev_.\n\nIf the PR got merged, then create a new tag pointing to the first commit of that\nPR (named _Bump to x.y.z_). The changelog can be created by using GitHub's\nrelease note creation feature via the user interface.\n"
  },
  {
    "path": "src/distribution/error.rs",
    "content": "//! Error types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::Getters;\nuse serde::{Deserialize, Serialize};\nuse std::fmt::{self, Display, Formatter};\nuse strum_macros::{Display as StrumDisplay, EnumString};\nuse thiserror::Error;\n\n/// The string returned by and ErrorResponse error.\npub const ERR_REGISTRY: &str = \"distribution: registry returned error\";\n\n/// Unique identifier representing error code.\n#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\npub enum ErrorCode {\n    /// Blob unknown to registry.\n    BlobUnknown,\n    /// Blob upload invalid.\n    BlobUploadInvalid,\n    /// Blob upload unknown to registry.\n    BlobUploadUnknown,\n    /// Provided digest did not match uploaded content.\n    DigestInvalid,\n    /// Blob unknown to registry.\n    ManifestBlobUnknown,\n    /// Manifest invalid.\n    ManifestInvalid,\n    /// Manifest unknown.\n    ManifestUnknown,\n    /// Invalid repository name.\n    NameInvalid,\n    /// Repository name not known to registry.\n    NameUnknown,\n    /// Provided length did not match content length.\n    SizeInvalid,\n    /// Authentication required.\n    Unauthorized,\n    /// Requested access to the resource is denied.\n    Denied,\n    /// The operation is unsupported.\n    Unsupported,\n    /// Too many requests.\n    #[serde(rename = \"TOOMANYREQUESTS\")]\n    TooManyRequests,\n}\n\n#[derive(Builder, Clone, Debug, Deserialize, Eq, Error, Getters, PartialEq, Serialize)]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\")]\n/// ErrorResponse is returned by a registry on an invalid request.\npub struct ErrorResponse {\n    /// Available errors within the response.\n    errors: Vec<ErrorInfo>,\n}\n\nimpl Display for ErrorResponse {\n    fn fmt(&self, f: &mut Formatter) -> fmt::Result {\n        write!(f, \"{ERR_REGISTRY}\")\n    }\n}\n\nimpl ErrorResponse {\n    /// Returns the ErrorInfo slice for the response.\n    pub fn detail(&self) -> &[ErrorInfo] {\n        &self.errors\n    }\n}\n\n#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\")]\n/// Describes a server error returned from a registry.\npub struct ErrorInfo {\n    /// The code field MUST be a unique identifier, containing only uppercase alphabetic\n    /// characters and underscores.\n    code: ErrorCode,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[builder(default = \"None\")]\n    /// The message field is OPTIONAL, and if present, it SHOULD be a human readable string or\n    /// MAY be empty.\n    message: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\", with = \"json_string\")]\n    #[builder(default = \"None\")]\n    /// The detail field is OPTIONAL and MAY contain arbitrary JSON data providing information\n    /// the client can use to resolve the issue.\n    detail: Option<String>,\n}\n\nmod json_string {\n    use std::str::FromStr;\n\n    use serde::{Deserialize, Deserializer, Serialize, Serializer};\n\n    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        use serde::de::Error;\n\n        let opt = Option::<serde_json::Value>::deserialize(deserializer)?;\n\n        if let Some(data) = opt {\n            let data = serde_json::to_string(&data).map_err(Error::custom)?;\n            return Ok(Some(data));\n        }\n\n        Ok(None)\n    }\n\n    pub fn serialize<S>(target: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: Serializer,\n    {\n        use serde::ser::Error;\n\n        match target {\n            Some(data) => {\n                if let Ok(json_value) = serde_json::Value::from_str(data) {\n                    json_value.serialize(serializer)\n                } else {\n                    Err(Error::custom(\"invalid JSON\"))\n                }\n            }\n            _ => unreachable!(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::error::Result;\n\n    #[test]\n    fn error_response_success() -> Result<()> {\n        let response = ErrorResponseBuilder::default().errors(vec![]).build()?;\n        assert!(response.detail().is_empty());\n        assert_eq!(response.to_string(), ERR_REGISTRY);\n        Ok(())\n    }\n\n    #[test]\n    fn error_response_failure() {\n        assert!(ErrorResponseBuilder::default().build().is_err());\n    }\n\n    #[test]\n    fn error_info_success() -> Result<()> {\n        let info = ErrorInfoBuilder::default()\n            .code(ErrorCode::BlobUnknown)\n            .build()?;\n        assert_eq!(info.code(), &ErrorCode::BlobUnknown);\n        assert!(info.message().is_none());\n        assert!(info.detail().is_none());\n        Ok(())\n    }\n\n    #[test]\n    fn error_info_failure() {\n        assert!(ErrorInfoBuilder::default().build().is_err());\n    }\n\n    #[test]\n    fn error_info_serialize_success() -> Result<()> {\n        let error_info = ErrorInfoBuilder::default()\n            .code(ErrorCode::Unauthorized)\n            .detail(String::from(\"{ \\\"key\\\": \\\"value\\\" }\"))\n            .build()?;\n\n        assert!(serde_json::to_string(&error_info).is_ok());\n        Ok(())\n    }\n\n    #[test]\n    fn error_info_serialize_failure() -> Result<()> {\n        let error_info = ErrorInfoBuilder::default()\n            .code(ErrorCode::Unauthorized)\n            .detail(String::from(\"abcd\"))\n            .build()?;\n\n        assert!(serde_json::to_string(&error_info).is_err());\n        Ok(())\n    }\n\n    #[test]\n    fn error_info_deserialize_success() -> Result<()> {\n        let error_info_str = r#\"\n        {\n            \"code\": \"MANIFEST_UNKNOWN\",\n            \"message\": \"manifest tagged by \\\"lates\\\" is not found\",\n            \"detail\": {\n                \"Tag\": \"lates\"\n            }\n        }\"#;\n\n        let error_info: ErrorInfo = serde_json::from_str(error_info_str)?;\n        assert_eq!(error_info.detail().as_ref().unwrap(), \"{\\\"Tag\\\":\\\"lates\\\"}\");\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/distribution/mod.rs",
    "content": "//! [OCI distribution spec](https://github.com/opencontainers/distribution-spec) types and definitions.\n//!\n//! The Open Container Initiative Distribution Specification (a.k.a. \"OCI Distribution Spec\")\n//! defines an API protocol to facilitate and standardize the distribution of content.\n//!\n//! While OCI Image is the most prominent, the specification is designed to be agnostic of content\n//! types. Concepts such as \"manifests\" and \"digests\", are currently defined in the [Open Container\n//! Initiative Image Format Specification](https://github.com/opencontainers/image-spec) (a.k.a.\n//! \"OCI Image Spec\").\n//!\n//! To support other artifact types, please see the [Open Container Initiative Artifact Authors\n//! Guide](https://github.com/opencontainers/artifacts) (a.k.a. \"OCI Artifacts\").\n\nmod error;\nmod reference;\nmod repository;\nmod tag;\nmod version;\n\npub use error::*;\npub use reference::*;\npub use repository::*;\npub use tag::*;\npub use version::*;\n"
  },
  {
    "path": "src/distribution/reference.rs",
    "content": "use std::fmt;\nuse std::str::FromStr;\nuse std::{convert::TryFrom, sync::OnceLock};\n\nuse regex::{Regex, RegexBuilder};\nuse serde::{Deserialize, Serialize};\nuse thiserror::Error;\n\n/// NAME_TOTAL_LENGTH_MAX is the maximum total number of characters in a repository name.\nconst NAME_TOTAL_LENGTH_MAX: usize = 255;\n\nconst DOCKER_HUB_DOMAIN_LEGACY: &str = \"index.docker.io\";\nconst DOCKER_HUB_DOMAIN: &str = \"docker.io\";\nconst DOCKER_HUB_OFFICIAL_REPO_NAME: &str = \"library\";\nconst DEFAULT_TAG: &str = \"latest\";\n/// REFERENCE_REGEXP is the full supported format of a reference. The regexp\n/// is anchored and has capturing groups for name, tag, and digest components.\nconst REFERENCE_REGEXP: &str = r\"^((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:(?:\\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(?::[0-9]+)?/)?[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?(?:(?:/[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?)+)?)(?::([\\w][\\w.-]{0,127}))?(?:@([A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}))?$\";\n\nfn reference_regexp() -> &'static Regex {\n    static RE: OnceLock<Regex> = OnceLock::new();\n    RE.get_or_init(|| {\n        RegexBuilder::new(REFERENCE_REGEXP)\n            .size_limit(10 * (1 << 21))\n            .build()\n            .unwrap()\n    })\n}\n\n/// Reasons that parsing a string as a Reference can fail.\n#[derive(Debug, Error, PartialEq, Eq)]\npub enum ParseError {\n    /// Will be returned if digest is ill-formed\n    #[error(\"invalid checksum digest format\")]\n    DigestInvalidFormat,\n    /// Will be returned if digest does not have a correct length\n    #[error(\"invalid checksum digest length\")]\n    DigestInvalidLength,\n    /// Will be returned for an unknown digest algorithm\n    #[error(\"unsupported digest algorithm\")]\n    DigestUnsupported,\n    /// Will be returned for an uppercase character in repository name\n    #[error(\"repository name must be lowercase\")]\n    NameContainsUppercase,\n    /// Will be returned if a name is empty\n    #[error(\"repository name must have at least one component\")]\n    NameEmpty,\n    /// Will be returned if a name is too long\n    #[error(\"repository name must not be more than {NAME_TOTAL_LENGTH_MAX} characters\")]\n    NameTooLong,\n    /// Will be returned if a reference is ill-formed\n    #[error(\"invalid reference format\")]\n    ReferenceInvalidFormat,\n    /// Will be returned if a tag is ill-formed\n    #[error(\"invalid tag format\")]\n    TagInvalidFormat,\n}\n\n/// Reference provides a general type to represent any way of referencing images within an OCI registry.\n///\n/// # Examples\n///\n/// Parsing a tagged image reference:\n///\n/// ```\n/// use oci_spec::distribution::Reference;\n///\n/// let reference: Reference = \"docker.io/library/hello-world:latest\".parse().unwrap();\n///\n/// assert_eq!(\"docker.io/library/hello-world:latest\", reference.whole().as_str());\n/// assert_eq!(\"docker.io\", reference.registry());\n/// assert_eq!(\"library/hello-world\", reference.repository());\n/// assert_eq!(Some(\"latest\"), reference.tag());\n/// assert_eq!(None, reference.digest());\n/// ```\n#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)]\npub struct Reference {\n    registry: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    mirror_registry: Option<String>,\n    repository: String,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    tag: Option<String>,\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    digest: Option<String>,\n}\n\nimpl Reference {\n    /// Create a Reference with a registry, repository and tag.\n    pub fn with_tag(registry: String, repository: String, tag: String) -> Self {\n        Self {\n            registry,\n            mirror_registry: None,\n            repository,\n            tag: Some(tag),\n            digest: None,\n        }\n    }\n\n    /// Create a Reference with a registry, repository and digest.\n    pub fn with_digest(registry: String, repository: String, digest: String) -> Self {\n        Self {\n            registry,\n            mirror_registry: None,\n            repository,\n            tag: None,\n            digest: Some(digest),\n        }\n    }\n\n    /// Create a new instance of [`Reference`] with a registry, repository, tag and digest.\n    ///\n    /// This is useful when you need to reference an image by both its semantic version (tag)\n    /// and its content-addressable digest for immutability.\n    ///\n    /// # Examples\n    ///\n    /// ```\n    /// use oci_spec::distribution::Reference;\n    ///\n    /// let reference = Reference::with_tag_and_digest(\n    ///     \"docker.io\".to_string(),\n    ///     \"library/nginx\".to_string(),\n    ///     \"1.21\".to_string(),\n    ///     \"sha256:abc123...\".to_string(),\n    /// );\n    /// ```\n    pub fn with_tag_and_digest(\n        registry: String,\n        repository: String,\n        tag: String,\n        digest: String,\n    ) -> Self {\n        Self {\n            registry,\n            mirror_registry: None,\n            repository,\n            tag: Some(tag),\n            digest: Some(digest),\n        }\n    }\n\n    /// Clone the Reference for the same image with a new digest.\n    pub fn clone_with_digest(&self, digest: String) -> Self {\n        Self {\n            registry: self.registry.clone(),\n            mirror_registry: self.mirror_registry.clone(),\n            repository: self.repository.clone(),\n            tag: None,\n            digest: Some(digest),\n        }\n    }\n\n    /// Set a pull mirror registry for this reference.\n    ///\n    /// The mirror registry will be used to resolve the image, the original registry\n    /// is available via the [`Reference::namespace`] function.\n    ///\n    /// The original registry will be sent with the `ns` query parameter to the mirror registry.\n    /// The `ns` query parameter is currently not part of the stable OCI Distribution Spec yet,\n    /// but is being discussed to be added and is already used by some other implementations\n    /// (for example containerd). So be aware that this feature might not work with all registries.\n    ///\n    /// Since this is not part of the stable OCI Distribution Spec yet, this feature is exempt from\n    /// semver backwards compatibility guarantees and might change in the future.\n    #[doc(hidden)]\n    pub fn set_mirror_registry(&mut self, registry: String) {\n        self.mirror_registry = Some(registry);\n    }\n\n    /// Resolve the registry address of a given `Reference`.\n    ///\n    /// Some registries, such as docker.io, uses a different address for the actual\n    /// registry. This function implements such redirection.\n    ///\n    /// If a mirror registry is set, it will be used instead of the original registry.\n    pub fn resolve_registry(&self) -> &str {\n        match (self.registry(), self.mirror_registry.as_deref()) {\n            (_, Some(mirror_registry)) => mirror_registry,\n            (\"docker.io\", None) => \"index.docker.io\",\n            (registry, None) => registry,\n        }\n    }\n\n    /// Returns the name of the registry.\n    pub fn registry(&self) -> &str {\n        &self.registry\n    }\n\n    /// Returns the name of the repository.\n    pub fn repository(&self) -> &str {\n        &self.repository\n    }\n\n    /// Returns the object's tag, if present.\n    pub fn tag(&self) -> Option<&str> {\n        self.tag.as_deref()\n    }\n\n    /// Returns the object's digest, if present.\n    pub fn digest(&self) -> Option<&str> {\n        self.digest.as_deref()\n    }\n\n    /// Returns the original registry when pulled via a mirror.\n    ///\n    /// Since this is not part of the stable OCI Distribution Spec yet, this feature is exempt from\n    /// semver backwards compatibility guarantees and might change in the future.\n    #[doc(hidden)]\n    pub fn namespace(&self) -> Option<&str> {\n        if self.mirror_registry.is_some() {\n            Some(self.registry())\n        } else {\n            None\n        }\n    }\n\n    /// Returns the whole reference.\n    pub fn whole(&self) -> String {\n        self.to_string()\n    }\n}\n\nimpl fmt::Display for Reference {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        let mut not_empty = false;\n        if !self.registry().is_empty() {\n            write!(f, \"{}\", self.registry())?;\n            not_empty = true;\n        }\n        if !self.repository().is_empty() {\n            if not_empty {\n                write!(f, \"/\")?;\n            }\n            write!(f, \"{}\", self.repository())?;\n            not_empty = true;\n        }\n        if let Some(t) = self.tag() {\n            if not_empty {\n                write!(f, \":\")?;\n            }\n            write!(f, \"{t}\")?;\n            not_empty = true;\n        }\n        if let Some(d) = self.digest() {\n            if not_empty {\n                write!(f, \"@\")?;\n            }\n            write!(f, \"{d}\")?;\n        }\n        Ok(())\n    }\n}\n\nimpl FromStr for Reference {\n    type Err = ParseError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Reference::try_from(s)\n    }\n}\n\nimpl TryFrom<&str> for Reference {\n    type Error = ParseError;\n\n    fn try_from(s: &str) -> Result<Self, Self::Error> {\n        if s.is_empty() {\n            return Err(ParseError::NameEmpty);\n        }\n        let captures = match reference_regexp().captures(s) {\n            Some(caps) => caps,\n            None => {\n                return Err(ParseError::ReferenceInvalidFormat);\n            }\n        };\n        let name = &captures[1];\n        let mut tag = captures.get(2).map(|m| m.as_str().to_owned());\n        let digest = captures.get(3).map(|m| m.as_str().to_owned());\n        if tag.is_none() && digest.is_none() {\n            tag = Some(DEFAULT_TAG.into());\n        }\n        let (registry, repository) = split_domain(name);\n        let reference = Reference {\n            registry,\n            mirror_registry: None,\n            repository,\n            tag,\n            digest,\n        };\n        if reference.repository().len() > NAME_TOTAL_LENGTH_MAX {\n            return Err(ParseError::NameTooLong);\n        }\n        // Digests much always be hex-encoded, ensuring that their hex portion will always be\n        // size*2\n        if let Some(digest) = reference.digest() {\n            match digest.split_once(':') {\n                None => return Err(ParseError::DigestInvalidFormat),\n                Some((\"sha256\", digest)) => {\n                    if digest.len() != 64 {\n                        return Err(ParseError::DigestInvalidLength);\n                    }\n                }\n                Some((\"sha384\", digest)) => {\n                    if digest.len() != 96 {\n                        return Err(ParseError::DigestInvalidLength);\n                    }\n                }\n                Some((\"sha512\", digest)) => {\n                    if digest.len() != 128 {\n                        return Err(ParseError::DigestInvalidLength);\n                    }\n                }\n                Some((_, _)) => return Err(ParseError::DigestUnsupported),\n            }\n        }\n        Ok(reference)\n    }\n}\n\nimpl TryFrom<String> for Reference {\n    type Error = ParseError;\n    fn try_from(string: String) -> Result<Self, Self::Error> {\n        TryFrom::try_from(string.as_str())\n    }\n}\n\nimpl From<Reference> for String {\n    fn from(reference: Reference) -> Self {\n        reference.whole()\n    }\n}\n\n/// Splits a repository name to domain and remotename string.\n/// If no valid domain is found, the default domain is used. Repository name\n/// needs to be already validated before.\n///\n/// This function is a Rust rewrite of the official Go code used by Docker:\n/// https://github.com/distribution/distribution/blob/41a0452eea12416aaf01bceb02a924871e964c67/reference/normalize.go#L87-L104\nfn split_domain(name: &str) -> (String, String) {\n    let mut domain: String;\n    let mut remainder: String;\n\n    match name.split_once('/') {\n        None => {\n            domain = DOCKER_HUB_DOMAIN.into();\n            remainder = name.into();\n        }\n        Some((left, right)) => {\n            if !(left.contains('.') || left.contains(':')) && left != \"localhost\" {\n                domain = DOCKER_HUB_DOMAIN.into();\n                remainder = name.into();\n            } else {\n                domain = left.into();\n                remainder = right.into();\n            }\n        }\n    }\n    if domain == DOCKER_HUB_DOMAIN_LEGACY {\n        domain = DOCKER_HUB_DOMAIN.into();\n    }\n    if domain == DOCKER_HUB_DOMAIN && !remainder.contains('/') {\n        remainder = format!(\"{DOCKER_HUB_OFFICIAL_REPO_NAME}/{remainder}\");\n    }\n\n    (domain, remainder)\n}\n\n#[cfg(test)]\nmod test {\n    use super::*;\n\n    mod parse {\n        use super::*;\n        use rstest::rstest;\n\n        #[rstest(input, registry, repository, tag, digest, whole,\n            case(\"busybox\", \"docker.io\", \"library/busybox\", Some(\"latest\"), None, \"docker.io/library/busybox:latest\"),\n            case(\"test.com:tag\", \"docker.io\", \"library/test.com\", Some(\"tag\"), None, \"docker.io/library/test.com:tag\"),\n            case(\"test.com:5000\", \"docker.io\", \"library/test.com\", Some(\"5000\"), None, \"docker.io/library/test.com:5000\"),\n            case(\"test.com/repo:tag\", \"test.com\", \"repo\", Some(\"tag\"), None, \"test.com/repo:tag\"),\n            case(\"test:5000/repo\", \"test:5000\", \"repo\", Some(\"latest\"), None, \"test:5000/repo:latest\"),\n            case(\"test:5000/repo:tag\", \"test:5000\", \"repo\", Some(\"tag\"), None, \"test:5000/repo:tag\"),\n            case(\"test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"test:5000\", \"repo\", None, Some(\"sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"), \"test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"),\n            case(\"test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"test:5000\", \"repo\", Some(\"tag\"), Some(\"sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"), \"test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"),\n            case(\"lowercase:Uppercase\", \"docker.io\", \"library/lowercase\", Some(\"Uppercase\"), None, \"docker.io/library/lowercase:Uppercase\"),\n            case(\"sub-dom1.foo.com/bar/baz/quux\", \"sub-dom1.foo.com\", \"bar/baz/quux\", Some(\"latest\"), None, \"sub-dom1.foo.com/bar/baz/quux:latest\"),\n            case(\"sub-dom1.foo.com/bar/baz/quux:some-long-tag\", \"sub-dom1.foo.com\", \"bar/baz/quux\", Some(\"some-long-tag\"), None, \"sub-dom1.foo.com/bar/baz/quux:some-long-tag\"),\n            case(\"b.gcr.io/test.example.com/my-app:test.example.com\", \"b.gcr.io\", \"test.example.com/my-app\", Some(\"test.example.com\"), None, \"b.gcr.io/test.example.com/my-app:test.example.com\"),\n            // ☃.com in punycode\n            case(\"xn--n3h.com/myimage:xn--n3h.com\", \"xn--n3h.com\", \"myimage\", Some(\"xn--n3h.com\"), None, \"xn--n3h.com/myimage:xn--n3h.com\"),\n            // 🐳.com in punycode\n            case(\"xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \"xn--7o8h.com\", \"myimage\", Some(\"xn--7o8h.com\"), Some(\"sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"), \"xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"),\n            case(\"foo_bar.com:8080\", \"docker.io\", \"library/foo_bar.com\", Some(\"8080\"), None, \"docker.io/library/foo_bar.com:8080\" ),\n            case(\"foo/foo_bar.com:8080\", \"docker.io\", \"foo/foo_bar.com\", Some(\"8080\"), None, \"docker.io/foo/foo_bar.com:8080\"),\n            case(\"opensuse/leap:15.3\", \"docker.io\", \"opensuse/leap\", Some(\"15.3\"), None, \"docker.io/opensuse/leap:15.3\"),\n        )]\n        fn parse_good_reference(\n            input: &str,\n            registry: &str,\n            repository: &str,\n            tag: Option<&str>,\n            digest: Option<&str>,\n            whole: &str,\n        ) {\n            println!(\"input: {}\", input);\n            let reference = Reference::try_from(input).expect(\"could not parse reference\");\n            println!(\"{} -> {:?}\", input, reference);\n            assert_eq!(registry, reference.registry());\n            assert_eq!(repository, reference.repository());\n            assert_eq!(tag, reference.tag());\n            assert_eq!(digest, reference.digest());\n            assert_eq!(whole, reference.whole());\n        }\n\n        #[rstest(input, err,\n            case(\"\", ParseError::NameEmpty),\n            case(\":justtag\", ParseError::ReferenceInvalidFormat),\n            case(\"@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", ParseError::ReferenceInvalidFormat),\n            case(\"repo@sha256:ffffffffffffffffffffffffffffffffff\", ParseError::DigestInvalidLength),\n            case(\"validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", ParseError::DigestUnsupported),\n            // FIXME: should really pass a ParseError::NameContainsUppercase, but \"invalid format\" is good enough for now.\n            case(\"Uppercase:tag\", ParseError::ReferenceInvalidFormat),\n            // FIXME: \"Uppercase\" is incorrectly handled as a domain-name here, and therefore passes.\n            // https://github.com/docker/distribution/blob/master/reference/reference_test.go#L104-L109\n            // case(\"Uppercase/lowercase:tag\", ParseError::NameContainsUppercase),\n            // FIXME: should really pass a ParseError::NameContainsUppercase, but \"invalid format\" is good enough for now.\n            case(\"test:5000/Uppercase/lowercase:tag\", ParseError::ReferenceInvalidFormat),\n            case(\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\", ParseError::NameTooLong),\n            case(\"aa/asdf$$^/aa\", ParseError::ReferenceInvalidFormat)\n        )]\n        fn parse_bad_reference(input: &str, err: ParseError) {\n            assert_eq!(Reference::try_from(input).unwrap_err(), err)\n        }\n\n        #[rstest(\n            input,\n            registry,\n            resolved_registry,\n            whole,\n            case(\n                \"busybox\",\n                \"docker.io\",\n                \"index.docker.io\",\n                \"docker.io/library/busybox:latest\"\n            ),\n            case(\"test.com/repo:tag\", \"test.com\", \"test.com\", \"test.com/repo:tag\"),\n            case(\"test:5000/repo\", \"test:5000\", \"test:5000\", \"test:5000/repo:latest\"),\n            case(\n                \"sub-dom1.foo.com/bar/baz/quux\",\n                \"sub-dom1.foo.com\",\n                \"sub-dom1.foo.com\",\n                \"sub-dom1.foo.com/bar/baz/quux:latest\"\n            ),\n            case(\n                \"b.gcr.io/test.example.com/my-app:test.example.com\",\n                \"b.gcr.io\",\n                \"b.gcr.io\",\n                \"b.gcr.io/test.example.com/my-app:test.example.com\"\n            )\n        )]\n        fn test_mirror_registry(input: &str, registry: &str, resolved_registry: &str, whole: &str) {\n            let mut reference = Reference::try_from(input).expect(\"could not parse reference\");\n            assert_eq!(resolved_registry, reference.resolve_registry());\n            assert_eq!(registry, reference.registry());\n            assert_eq!(None, reference.namespace());\n            assert_eq!(whole, reference.whole());\n\n            reference.set_mirror_registry(\"docker.mirror.io\".to_owned());\n            assert_eq!(\"docker.mirror.io\", reference.resolve_registry());\n            assert_eq!(registry, reference.registry());\n            assert_eq!(Some(registry), reference.namespace());\n            assert_eq!(whole, reference.whole());\n        }\n\n        #[rstest(\n            expected, registry, repository, tag, digest,\n            case(\n                \"docker.io/foo/bar:1.2@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", \n                \"docker.io\", \n                \"foo/bar\", \n                \"1.2\", \n                \"sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"\n            )\n        )]\n        fn test_create_reference_from_tag_and_digest(\n            expected: &str,\n            registry: &str,\n            repository: &str,\n            tag: &str,\n            digest: &str,\n        ) {\n            let reference = Reference::with_tag_and_digest(\n                registry.to_string(),\n                repository.to_string(),\n                tag.to_string(),\n                digest.to_string(),\n            );\n            assert_eq!(expected, reference.to_string());\n        }\n    }\n}\n"
  },
  {
    "path": "src/distribution/repository.rs",
    "content": "//! Repository types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// RepositoryList returns a catalog of repositories maintained on the registry.\npub struct RepositoryList {\n    /// The items of the RepositoryList.\n    repositories: Vec<String>,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::error::Result;\n\n    #[test]\n    fn repository_list_success() -> Result<()> {\n        let list = RepositoryListBuilder::default()\n            .repositories(vec![])\n            .build()?;\n        assert!(list.repositories().is_empty());\n        Ok(())\n    }\n\n    #[test]\n    fn repository_list_failure() {\n        assert!(RepositoryListBuilder::default().build().is_err());\n    }\n}\n"
  },
  {
    "path": "src/distribution/tag.rs",
    "content": "//! Tag types of the distribution spec.\n\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Serialize};\n\n#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// A list of tags for a given repository.\npub struct TagList {\n    /// The namespace of the repository.\n    name: String,\n\n    /// Each tags on the repository.\n    tags: Vec<String>,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::error::Result;\n\n    #[test]\n    fn tag_list_success() -> Result<()> {\n        let list = TagListBuilder::default()\n            .name(\"name\")\n            .tags(vec![])\n            .build()?;\n        assert!(list.tags().is_empty());\n        assert_eq!(list.name(), \"name\");\n        Ok(())\n    }\n\n    #[test]\n    fn tag_list_failure() {\n        assert!(TagListBuilder::default().build().is_err());\n    }\n}\n"
  },
  {
    "path": "src/distribution/version.rs",
    "content": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality in a backwards-compatible manner\npub const VERSION_MINOR: u32 = 0;\n\n/// Backwards-compatible bug fixes.\npub const VERSION_PATCH: u32 = 0;\n\n/// Indicates development branch. Releases will be empty string.\npub const VERSION_DEV: &str = \"-dev\";\n\n/// Retrieve the version as static str representation.\npub const VERSION: &str = formatcp!(\"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_DEV}\");\n\n/// Retrieve the version as string representation.\n///\n/// Use [`VERSION`] instead.\n#[deprecated]\npub fn version() -> String {\n    VERSION.to_owned()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    #[allow(deprecated)]\n    fn version_test() {\n        assert_eq!(version(), \"1.0.0-dev\".to_string())\n    }\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "//! Error types of the crate.\n\nuse std::{borrow::Cow, io};\nuse thiserror::Error;\n\n/// Spezialized result type for oci spec operations. It is\n/// used for any operation that might produce an error. This\n/// typedef is generally used to avoid writing out\n/// [OciSpecError] directly and is otherwise a direct mapping\n/// to [Result](std::result::Result).\npub type Result<T> = std::result::Result<T, OciSpecError>;\n\n/// Error type for oci spec errors.\n#[derive(Error, Debug)]\npub enum OciSpecError {\n    /// Will be returned if an error occurs that cannot\n    /// be mapped to a more specialized error variant.\n    #[error(\"{0}\")]\n    Other(String),\n\n    /// Will be returned when an error happens during\n    /// io operations.\n    #[error(\"io operation failed\")]\n    Io(#[from] io::Error),\n\n    /// Will be returned when an error happens during\n    /// serialization or deserialization.\n    #[error(\"serde failed\")]\n    SerDe(#[from] serde_json::Error),\n\n    /// Builder specific errors.\n    #[error(\"uninitialized field\")]\n    Builder(#[from] derive_builder::UninitializedFieldError),\n}\n\npub(crate) fn oci_error<'a, M>(message: M) -> OciSpecError\nwhere\n    M: Into<Cow<'a, str>>,\n{\n    let message = message.into();\n    match message {\n        Cow::Borrowed(s) => OciSpecError::Other(s.to_owned()),\n        Cow::Owned(s) => OciSpecError::Other(s),\n    }\n}\n"
  },
  {
    "path": "src/image/annotations.rs",
    "content": "/// AnnotationCreated is the annotation key for the date and time on which the\n/// image was built (date-time string as defined by RFC 3339).\npub const ANNOTATION_CREATED: &str = \"org.opencontainers.image.created\";\n\n/// AnnotationAuthors is the annotation key for the contact details of the\n/// people or organization responsible for the image (freeform string).\npub const ANNOTATION_AUTHORS: &str = \"org.opencontainers.image.authors\";\n\n/// AnnotationURL is the annotation key for the URL to find more information on\n/// the image.\npub const ANNOTATION_URL: &str = \"org.opencontainers.image.url\";\n\n/// AnnotationDocumentation is the annotation key for the URL to get\n/// documentation on the image.\npub const ANNOTATION_DOCUMENTATION: &str = \"org.opencontainers.image.documentation\";\n\n/// AnnotationSource is the annotation key for the URL to get source code for\n/// building the image.\npub const ANNOTATION_SOURCE: &str = \"org.opencontainers.image.source\";\n\n/// AnnotationVersion is the annotation key for the version of the packaged\n/// software. The version MAY match a label or tag in the source code\n/// repository. The version MAY be Semantic versioning-compatible.\npub const ANNOTATION_VERSION: &str = \"org.opencontainers.image.version\";\n\n/// AnnotationRevision is the annotation key for the source control revision\n/// identifier for the packaged software.\npub const ANNOTATION_REVISION: &str = \"org.opencontainers.image.revision\";\n\n/// AnnotationVendor is the annotation key for the name of the distributing\n/// entity, organization or individual.\npub const ANNOTATION_VENDOR: &str = \"org.opencontainers.image.vendor\";\n\n/// AnnotationLicenses is the annotation key for the license(s) under which\n/// contained software is distributed as an SPDX License Expression.\npub const ANNOTATION_LICENSES: &str = \"org.opencontainers.image.licenses\";\n\n/// AnnotationRefName is the annotation key for the name of the reference for a\n/// target. SHOULD only be considered valid when on descriptors on `index.json`\n/// within image layout.\npub const ANNOTATION_REF_NAME: &str = \"org.opencontainers.image.ref.name\";\n\n/// AnnotationTitle is the annotation key for the human-readable title of the\n/// image.\npub const ANNOTATION_TITLE: &str = \"org.opencontainers.image.title\";\n\n/// AnnotationDescription is the annotation key for the human-readable\n/// description of the software packaged in the image.\npub const ANNOTATION_DESCRIPTION: &str = \"org.opencontainers.image.description\";\n\n/// AnnotationBaseImageDigest is the annotation key for the digest of the\n/// image's base image.\npub const ANNOTATION_BASE_IMAGE_DIGEST: &str = \"org.opencontainers.image.base.digest\";\n\n/// AnnotationBaseImageName is the annotation key for the image reference of the\n/// image's base image.\npub const ANNOTATION_BASE_IMAGE_NAME: &str = \"org.opencontainers.image.base.name\";\n"
  },
  {
    "path": "src/image/artifact.rs",
    "content": "use super::{Descriptor, MediaType};\nuse crate::error::{OciSpecError, Result};\nuse derive_builder::Builder;\nuse getset::{Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    io::{Read, Write},\n    path::Path,\n};\n\n#[derive(\n    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// The OCI Artifact manifest describes content addressable artifacts\n/// in order to store them along side container images in a registry.\npub struct ArtifactManifest {\n    /// This property MUST be used and contain the media type\n    /// `application/vnd.oci.artifact.manifest.v1+json`.\n    #[getset(get = \"pub\")]\n    #[builder(default = \"MediaType::ArtifactManifest\")]\n    #[builder(setter(skip))]\n    media_type: MediaType,\n\n    /// This property SHOULD be used and contain\n    /// the mediaType of the referenced artifact.\n    /// If defined, the value MUST comply with RFC 6838,\n    /// including the naming requirements in its section 4.2,\n    /// and MAY be registered with IANA.\n    #[getset(get = \"pub\", set = \"pub\")]\n    artifact_type: MediaType,\n\n    /// This OPTIONAL property is an array of objects and each item\n    /// in the array MUST be a descriptor. Each descriptor represents\n    /// an artifact of any IANA mediaType. The list MAY be ordered\n    /// for certain artifact types like scan results.\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    blobs: Vec<Descriptor>,\n\n    /// This OPTIONAL property specifies a descriptor of another manifest.\n    /// This value, used by the referrers API, indicates a relationship\n    /// to the specified manifest.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    subject: Option<Descriptor>,\n\n    /// This OPTIONAL property contains additional metadata for the artifact\n    /// manifest. This OPTIONAL property MUST use the annotation rules.\n    /// See Pre-Defined Annotation Keys. Annotations MAY be used to filter\n    /// the response from the referrers API.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    annotations: Option<HashMap<String, String>>,\n}\n\nimpl ArtifactManifest {\n    /// Attempts to load an image manifest from a file.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::Io] if the file does not exist\n    /// - [OciSpecError::SerDe] if the image manifest cannot be deserialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// ```\n    pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {\n        crate::from_file(path)\n    }\n\n    /// Attempts to load an image manifest from a stream.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the manifest cannot be deserialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    /// use std::fs::File;\n    ///\n    /// let reader = File::open(\"manifest.json\").unwrap();\n    /// let artifact_manifest = ArtifactManifest::from_reader(reader).unwrap();\n    /// ```\n    pub fn from_reader<R: Read>(reader: R) -> Result<Self> {\n        crate::from_reader(reader)\n    }\n\n    /// Attempts to write an image manifest to a file as JSON. If the file already exists, it\n    /// will be overwritten.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// artifact_manifest.to_file(\"my-manifest.json\").unwrap();\n    /// ```\n    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        crate::to_file(&self, path, false)\n    }\n\n    /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it\n    /// will be overwritten.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// artifact_manifest.to_file_pretty(\"my-manifest.json\").unwrap();\n    /// ```\n    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        crate::to_file(&self, path, true)\n    }\n\n    /// Attempts to write an image manifest to a stream as JSON.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// artifact_manifest.to_writer(&mut writer);\n    /// ```\n    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {\n        crate::to_writer(&self, writer, false)\n    }\n\n    /// Attempts to write an image manifest to a stream as pretty printed JSON.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// artifact_manifest.to_writer_pretty(&mut writer);\n    /// ```\n    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {\n        crate::to_writer(&self, writer, true)\n    }\n\n    /// Attempts to write an image manifest to a string as JSON.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// let json_str = artifact_manifest.to_string().unwrap();\n    /// ```\n    pub fn to_string(&self) -> Result<String> {\n        crate::to_string(&self, false)\n    }\n\n    /// Attempts to write an image manifest to a string as pretty printed JSON.\n    ///\n    /// # Errors\n    ///\n    /// - [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration cannot be serialized.\n    ///\n    /// # Example\n    ///\n    /// ``` no_run\n    /// use oci_spec::image::ArtifactManifest;\n    ///\n    /// let artifact_manifest = ArtifactManifest::from_file(\"manifest.json\").unwrap();\n    /// let json_str = artifact_manifest.to_string_pretty().unwrap();\n    /// ```\n    pub fn to_string_pretty(&self) -> Result<String> {\n        crate::to_string(&self, true)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::image::{DescriptorBuilder, Sha256Digest};\n    use std::{path::PathBuf, str::FromStr};\n\n    fn get_manifest_path() -> PathBuf {\n        PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"test/data/artifact_manifest.json\")\n    }\n\n    fn create_manifest() -> ArtifactManifest {\n        let blob = DescriptorBuilder::default()\n            .media_type(MediaType::Other(\"application/gzip\".to_string()))\n            .size(123u64)\n            .digest(\n                Sha256Digest::from_str(\n                    \"87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630\",\n                )\n                .unwrap(),\n            )\n            .build()\n            .unwrap();\n        let subject = DescriptorBuilder::default()\n            .media_type(MediaType::ImageManifest)\n            .size(1234u64)\n            .digest(\n                Sha256Digest::from_str(\n                    \"cc06a2839488b8bd2a2b99dcdc03d5cfd818eed72ad08ef3cc197aac64c0d0a0\",\n                )\n                .unwrap(),\n            )\n            .build()\n            .unwrap();\n        let annotations = HashMap::from([\n            (\n                \"org.opencontainers.artifact.created\".to_string(),\n                \"2022-01-01T14:42:55Z\".to_string(),\n            ),\n            (\"org.example.sbom.format\".to_string(), \"json\".to_string()),\n        ]);\n        ArtifactManifestBuilder::default()\n            .artifact_type(MediaType::Other(\n                \"application/vnd.example.sbom.v1\".to_string(),\n            ))\n            .blobs(vec![blob])\n            .subject(subject)\n            .annotations(annotations)\n            .build()\n            .unwrap()\n    }\n\n    #[test]\n    fn load_manifest_from_file() {\n        // arrange\n        let manifest_path = get_manifest_path();\n        let expected = create_manifest();\n\n        // act\n        let actual = ArtifactManifest::from_file(manifest_path).expect(\"from file\");\n\n        // assert\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "src/image/config.rs",
    "content": "use super::{Arch, Os};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, to_writer,\n};\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};\n#[cfg(test)]\nuse std::collections::BTreeMap;\nuse std::{\n    collections::HashMap,\n    fmt::Display,\n    io::{Read, Write},\n    path::Path,\n};\n\n/// In theory, this key is not standard.  In practice, it's used by at least the\n/// RHEL UBI images for a long time.\npub const LABEL_VERSION: &str = \"version\";\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// The image configuration is associated with an image and describes some\n/// basic information about the image such as date created, author, as\n/// well as execution/runtime configuration like its entrypoint, default\n/// arguments, networking, and volumes.\npub struct ImageConfiguration {\n    /// An combined date and time at which the image was created,\n    /// formatted as defined by [RFC 3339, section 5.6.](https://tools.ietf.org/html/rfc3339#section-5.6)\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    created: Option<String>,\n    /// Gives the name and/or email address of the person or entity\n    /// which created and is responsible for maintaining the image.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    author: Option<String>,\n    /// The CPU architecture which the binaries in this\n    /// image are built to run on. Configurations SHOULD use, and\n    /// implementations SHOULD understand, values listed in the Go\n    /// Language document for [GOARCH](https://golang.org/doc/install/source#environment).\n    architecture: Arch,\n    /// The name of the operating system which the image is built to run on.\n    /// Configurations SHOULD use, and implementations SHOULD understand,\n    /// values listed in the Go Language document for [GOOS](https://golang.org/doc/install/source#environment).\n    os: Os,\n    /// This OPTIONAL property specifies the version of the operating\n    /// system targeted by the referenced blob. Implementations MAY refuse\n    /// to use manifests where os.version is not known to work with\n    /// the host OS version. Valid values are\n    /// implementation-defined. e.g. 10.0.14393.1066 on windows.\n    #[serde(rename = \"os.version\", skip_serializing_if = \"Option::is_none\")]\n    os_version: Option<String>,\n    /// This OPTIONAL property specifies an array of strings,\n    /// each specifying a mandatory OS feature. When os is windows, image\n    /// indexes SHOULD use, and implementations SHOULD understand\n    /// the following values:\n    /// - win32k: image requires win32k.sys on the host (Note: win32k.sys is\n    ///   missing on Nano Server)\n    #[serde(rename = \"os.features\", skip_serializing_if = \"Option::is_none\")]\n    os_features: Option<Vec<String>>,\n    /// The variant of the specified CPU architecture. Configurations SHOULD\n    /// use, and implementations SHOULD understand, variant values\n    /// listed in the [Platform Variants](https://github.com/opencontainers/image-spec/blob/main/image-index.md#platform-variants) table.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    variant: Option<String>,\n    /// The execution parameters which SHOULD be used as a base when\n    /// running a container using the image. This field can be None, in\n    /// which case any execution parameters should be specified at\n    /// creation of the container.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    config: Option<Config>,\n    /// The rootfs key references the layer content addresses used by the\n    /// image. This makes the image config hash depend on the\n    /// filesystem hash.\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    rootfs: RootFs,\n    /// Describes the history of each layer. The array is ordered from first\n    /// to last.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    history: Option<Vec<History>>,\n}\n\nimpl ImageConfiguration {\n    /// Attempts to load an image configuration from a file.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)\n    /// if the file does not exist or an\n    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image configuration\n    /// cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_index = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// ```\n    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageConfiguration> {\n        from_file(path)\n    }\n\n    /// Attempts to load an image configuration from a stream.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)\n    /// if the image configuration cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    /// use std::fs::File;\n    ///\n    /// let reader = File::open(\"config.json\").unwrap();\n    /// let image_index = ImageConfiguration::from_reader(reader).unwrap();\n    /// ```\n    pub fn from_reader<R: Read>(reader: R) -> Result<ImageConfiguration> {\n        from_reader(reader)\n    }\n\n    /// Attempts to write an image configuration to a file as JSON. If the file already exists, it\n    /// will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_index = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// image_index.to_file(\"my-config.json\").unwrap();\n    /// ```\n    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, false)\n    }\n\n    /// Attempts to write an image configuration to a file as pretty printed JSON. If the file\n    /// already exists, it will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_index = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// image_index.to_file_pretty(\"my-config.json\").unwrap();\n    /// ```\n    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, true)\n    }\n\n    /// Attempts to write an image configuration to a stream as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_index = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_index.to_writer(&mut writer);\n    /// ```\n    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, false)\n    }\n\n    /// Attempts to write an image configuration to a stream as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_index = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_index.to_writer_pretty(&mut writer);\n    /// ```\n    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, true)\n    }\n\n    /// Attempts to write an image configuration to a string as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_configuration = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// let json_str = image_configuration.to_string().unwrap();\n    /// ```\n    pub fn to_string(&self) -> Result<String> {\n        to_string(&self, false)\n    }\n\n    /// Attempts to write an image configuration to a string as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageConfiguration;\n    ///\n    /// let image_configuration = ImageConfiguration::from_file(\"config.json\").unwrap();\n    /// let json_str = image_configuration.to_string_pretty().unwrap();\n    /// ```\n    pub fn to_string_pretty(&self) -> Result<String> {\n        to_string(&self, true)\n    }\n\n    /// Extract the labels of the configuration, if present.\n    pub fn labels_of_config(&self) -> Option<&HashMap<String, String>> {\n        self.config().as_ref().and_then(|c| c.labels().as_ref())\n    }\n\n    /// Retrieve the version number associated with this configuration.  This will try\n    /// to use several well-known label keys.\n    pub fn version(&self) -> Option<&str> {\n        let labels = self.labels_of_config();\n        if let Some(labels) = labels {\n            for k in [super::ANNOTATION_VERSION, LABEL_VERSION] {\n                if let Some(v) = labels.get(k) {\n                    return Some(v.as_str());\n                }\n            }\n        }\n        None\n    }\n\n    /// Extract the value of a given annotation on the configuration, if present.\n    pub fn get_config_annotation(&self, key: &str) -> Option<&str> {\n        self.labels_of_config()\n            .and_then(|v| v.get(key).map(|s| s.as_str()))\n    }\n}\n\n/// This ToString trait is automatically implemented for any type which implements the Display trait.\n/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,\n/// and you get the ToString implementation for free.\nimpl Display for ImageConfiguration {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Serde serialization never fails since this is\n        // a combination of String and enums.\n        write!(\n            f,\n            \"{}\",\n            self.to_string_pretty()\n                .expect(\"ImageConfiguration JSON conversion failed\")\n        )\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"PascalCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// The execution parameters which SHOULD be used as a base when\n/// running a container using the image.\npub struct Config {\n    /// The username or UID which is a platform-specific\n    /// structure that allows specific control over which\n    /// user the process run as. This acts as a default\n    /// value to use when the value is not specified when\n    /// creating a container. For Linux based systems, all\n    /// of the following are valid: user, uid, user:group,\n    /// uid:gid, uid:group, user:gid. If group/gid is not\n    /// specified, the default group and supplementary\n    /// groups of the given user/uid in /etc/passwd from\n    /// the container are applied.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    user: Option<String>,\n    /// A set of ports to expose from a container running this\n    /// image. Its keys can be in the format of: port/tcp, port/udp,\n    /// port with the default protocol being tcp if not specified.\n    /// These values act as defaults and are merged with any\n    /// specified when creating a container.\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        deserialize_with = \"deserialize_as_vec\",\n        serialize_with = \"serialize_as_map\"\n    )]\n    exposed_ports: Option<Vec<String>>,\n    /// Entries are in the format of VARNAME=VARVALUE. These\n    /// values act as defaults and are merged with any\n    /// specified when creating a container.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    env: Option<Vec<String>>,\n    /// A list of arguments to use as the command to execute\n    /// when the container starts. These values act as defaults\n    /// and may be replaced by an entrypoint specified when\n    /// creating a container.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    entrypoint: Option<Vec<String>>,\n    /// Default arguments to the entrypoint of the container.\n    /// These values act as defaults and may be replaced by any\n    /// specified when creating a container. If an Entrypoint\n    /// value is not specified, then the first entry of the Cmd\n    /// array SHOULD be interpreted as the executable to run.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    cmd: Option<Vec<String>>,\n    /// A set of directories describing where the process is\n    /// likely to write data specific to a container instance.\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        deserialize_with = \"deserialize_as_vec\",\n        serialize_with = \"serialize_as_map\"\n    )]\n    volumes: Option<Vec<String>>,\n    /// Sets the current working directory of the entrypoint process\n    /// in the container. This value acts as a default and may be\n    /// replaced by a working directory specified when creating\n    /// a container.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    working_dir: Option<String>,\n    /// The field contains arbitrary metadata for the container.\n    /// This property MUST use the annotation rules.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    labels: Option<HashMap<String, String>>,\n    /// The field contains the system call signal that will be\n    /// sent to the container to exit. The signal can be a signal\n    /// name in the format SIGNAME, for instance SIGKILL or SIGRTMIN+3.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    stop_signal: Option<String>,\n}\n\n// Some fields of the image configuration are a json serialization of a\n// Go map[string]struct{} leading to the following json:\n// {\n//    \"ExposedPorts\": {\n//       \"8080/tcp\": {},\n//       \"443/tcp\": {},\n//    }\n// }\n// Instead we treat this as a list\n#[derive(Deserialize, Serialize)]\nstruct GoMapSerde {}\n\nfn deserialize_as_vec<'de, D>(deserializer: D) -> std::result::Result<Option<Vec<String>>, D::Error>\nwhere\n    D: Deserializer<'de>,\n{\n    // ensure stable order of keys in json document for comparison between expected and actual\n    #[cfg(test)]\n    let opt = Option::<BTreeMap<String, GoMapSerde>>::deserialize(deserializer)?;\n    #[cfg(not(test))]\n    let opt = Option::<HashMap<String, GoMapSerde>>::deserialize(deserializer)?;\n\n    if let Some(data) = opt {\n        let vec: Vec<String> = data.keys().cloned().collect();\n        return Ok(Some(vec));\n    }\n\n    Ok(None)\n}\n\nfn serialize_as_map<S>(\n    target: &Option<Vec<String>>,\n    serializer: S,\n) -> std::result::Result<S::Ok, S::Error>\nwhere\n    S: Serializer,\n{\n    match target {\n        Some(values) => {\n            // ensure stable order of keys in json document for comparison between expected and actual\n            #[cfg(test)]\n            let map: BTreeMap<_, _> = values.iter().map(|v| (v, GoMapSerde {})).collect();\n            #[cfg(not(test))]\n            let map: HashMap<_, _> = values.iter().map(|v| (v, GoMapSerde {})).collect();\n\n            let mut map_ser = serializer.serialize_map(Some(map.len()))?;\n            for (key, value) in map {\n                map_ser.serialize_entry(key, &value)?;\n            }\n            map_ser.end()\n        }\n        _ => unreachable!(),\n    }\n}\n\n#[derive(\n    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// RootFs references the layer content addresses used by the image.\npub struct RootFs {\n    /// MUST be set to layers.\n    #[serde(rename = \"type\")]\n    typ: String,\n    /// An array of layer content hashes (DiffIDs), in order\n    /// from first to last.\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    diff_ids: Vec<String>,\n}\n\nimpl Default for RootFs {\n    fn default() -> Self {\n        Self {\n            typ: \"layers\".to_owned(),\n            diff_ids: Default::default(),\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    CopyGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Describes the history of a layer.\npub struct History {\n    /// A combined date and time at which the layer was created,\n    /// formatted as defined by [RFC 3339, section 5.6.](https://tools.ietf.org/html/rfc3339#section-5.6).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    created: Option<String>,\n    /// The author of the build point.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    author: Option<String>,\n    /// The command which created the layer.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    created_by: Option<String>,\n    /// A custom message set when creating the layer.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    comment: Option<String>,\n    /// This field is used to mark if the history item created\n    /// a filesystem diff. It is set to true if this history item\n    /// doesn't correspond to an actual layer in the rootfs section\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    empty_layer: Option<bool>,\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{fs, path::PathBuf};\n\n    use super::*;\n    use crate::image::{ANNOTATION_CREATED, ANNOTATION_VERSION};\n\n    fn create_base_config() -> ConfigBuilder {\n        ConfigBuilder::default()\n            .user(\"alice\".to_owned())\n            .exposed_ports(vec![\"8080/tcp\".to_owned()])\n            .env(vec![\n                \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".to_owned(),\n                \"FOO=oci_is_a\".to_owned(),\n                \"BAR=well_written_spec\".to_owned(),\n            ])\n            .entrypoint(vec![\"/bin/my-app-binary\".to_owned()])\n            .cmd(vec![\n                \"--foreground\".to_owned(),\n                \"--config\".to_owned(),\n                \"/etc/my-app.d/default.cfg\".to_owned(),\n            ])\n            .volumes(vec![\n                \"/var/job-result-data\".to_owned(),\n                \"/var/log/my-app-logs\".to_owned(),\n            ])\n            .working_dir(\"/home/alice\".to_owned())\n    }\n\n    fn create_base_imgconfig(conf: Config) -> ImageConfigurationBuilder {\n        ImageConfigurationBuilder::default()\n            .created(\"2015-10-31T22:22:56.015925234Z\".to_owned())\n            .author(\"Alyssa P. Hacker <alyspdev@example.com>\".to_owned())\n            .architecture(Arch::Amd64)\n            .os(Os::Linux)\n            .config(conf\n            )\n            .rootfs(RootFsBuilder::default()\n            .diff_ids(vec![\n                \"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1\".to_owned(),\n                \"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\".to_owned(),\n            ])\n            .build()\n            .expect(\"build rootfs\"))\n            .history(vec![\n                HistoryBuilder::default()\n                .created(\"2015-10-31T22:22:54.690851953Z\".to_owned())\n                .created_by(\"/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /\".to_owned())\n                .build()\n                .expect(\"build history\"),\n                HistoryBuilder::default()\n                .created(\"2015-10-31T22:22:55.613815829Z\".to_owned())\n                .created_by(\"/bin/sh -c #(nop) CMD [\\\"sh\\\"]\".to_owned())\n                .empty_layer(true)\n                .build()\n                .expect(\"build history\"),\n            ])\n    }\n\n    fn create_config() -> ImageConfiguration {\n        create_base_imgconfig(create_base_config().build().expect(\"config\"))\n            .build()\n            .expect(\"build configuration\")\n    }\n\n    /// A config with some additions (labels)\n    fn create_imgconfig_v1() -> ImageConfiguration {\n        let labels = [\n            (ANNOTATION_CREATED, \"2023-09-16T19:22:18.014Z\"),\n            (ANNOTATION_VERSION, \"42.27\"),\n        ]\n        .into_iter()\n        .map(|(k, v)| (k.to_owned(), v.to_owned()));\n        let config = create_base_config()\n            .labels(labels.collect::<HashMap<_, _>>())\n            .build()\n            .unwrap();\n        create_base_imgconfig(config).build().unwrap()\n    }\n\n    fn get_config_path() -> PathBuf {\n        PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"test/data/config.json\")\n    }\n\n    #[test]\n    fn load_configuration_from_file() {\n        // arrange\n        let config_path = get_config_path();\n        let expected = create_config();\n\n        // act\n        let actual = ImageConfiguration::from_file(config_path).expect(\"from file\");\n\n        // assert\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn test_helpers() {\n        let config = create_imgconfig_v1();\n        assert_eq!(config.labels_of_config().unwrap().len(), 2);\n        assert_eq!(\n            config.get_config_annotation(ANNOTATION_CREATED).unwrap(),\n            \"2023-09-16T19:22:18.014Z\"\n        );\n    }\n\n    #[test]\n    fn load_configuration_from_reader() {\n        // arrange\n        let reader = fs::read(get_config_path()).expect(\"read config\");\n\n        // act\n        let actual = ImageConfiguration::from_reader(&*reader).expect(\"from reader\");\n        println!(\"{actual:#?}\");\n\n        // assert\n        let expected = create_config();\n        println!(\"{expected:#?}\");\n\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_config_to_file() {\n        // arrange\n        let tmp = std::env::temp_dir().join(\"save_config_to_file\");\n        fs::create_dir_all(&tmp).expect(\"create test directory\");\n        let config = create_config();\n        let config_path = tmp.join(\"config.json\");\n\n        // act\n        config\n            .to_file_pretty(&config_path)\n            .expect(\"write config to file\");\n\n        // assert\n        let actual = fs::read_to_string(config_path).expect(\"read actual\");\n        let expected = fs::read_to_string(get_config_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_config_to_writer() {\n        // arrange\n        let config = create_config();\n        let mut actual = Vec::new();\n\n        // act\n        config.to_writer_pretty(&mut actual).expect(\"to writer\");\n        let actual = String::from_utf8(actual).unwrap();\n\n        // assert\n        let expected = fs::read_to_string(get_config_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_config_to_string() {\n        // arrange\n        let config = create_config();\n\n        // act\n        let actual = config.to_string_pretty().expect(\"to string\");\n\n        // assert\n        let expected = fs::read_to_string(get_config_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn optional_history_field_absent() {\n        let json = r#\"{\n            \"architecture\": \"amd64\",\n            \"os\": \"linux\",\n            \"rootfs\": {\n                \"type\": \"layers\",\n                \"diff_ids\": [\"sha256:abc123\"]\n            }\n        }\"#;\n\n        let config: ImageConfiguration =\n            serde_json::from_str(json).expect(\"deserialize without history\");\n        assert!(config.history().is_none());\n    }\n\n    #[test]\n    fn serialize_without_history() {\n        let config = ImageConfigurationBuilder::default()\n            .architecture(Arch::Amd64)\n            .os(Os::Linux)\n            .rootfs(\n                RootFsBuilder::default()\n                    .diff_ids(vec![\"sha256:abc123\".to_owned()])\n                    .build()\n                    .expect(\"build rootfs\"),\n            )\n            .build()\n            .expect(\"build config\");\n\n        let json = config.to_string().expect(\"serialize\");\n        assert!(!json.contains(\"history\"));\n    }\n\n    #[test]\n    fn builder_without_history() {\n        let config = ImageConfigurationBuilder::default()\n            .architecture(Arch::Amd64)\n            .os(Os::Linux)\n            .rootfs(\n                RootFsBuilder::default()\n                    .diff_ids(vec![\"sha256:abc123\".to_owned()])\n                    .build()\n                    .expect(\"build rootfs\"),\n            )\n            .build()\n            .expect(\"build config\");\n\n        assert!(config.history().is_none());\n    }\n}\n"
  },
  {
    "path": "src/image/descriptor.rs",
    "content": "use super::{Arch, Digest, MediaType, Os};\nuse crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\n#[derive(\n    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// A Content Descriptor (or simply Descriptor) describes the disposition of\n/// the targeted content. It includes the type of the content, a content\n/// identifier (digest), and the byte-size of the raw content.\n/// Descriptors SHOULD be embedded in other formats to securely reference\n/// external content.\npub struct Descriptor {\n    /// This REQUIRED property contains the media type of the referenced\n    /// content. Values MUST comply with RFC 6838, including the naming\n    /// requirements in its section 4.2.\n    #[getset(get = \"pub\", set = \"pub\")]\n    media_type: MediaType,\n    /// This REQUIRED property is the digest of the targeted content,\n    /// conforming to the requirements outlined in Digests. Retrieved\n    /// content SHOULD be verified against this digest when consumed via\n    /// untrusted sources.\n    #[getset(get = \"pub\", set = \"pub\")]\n    digest: Digest,\n    /// This REQUIRED property specifies the size, in bytes, of the raw\n    /// content. This property exists so that a client will have an\n    /// expected size for the content before processing. If the\n    /// length of the retrieved content does not match the specified\n    /// length, the content SHOULD NOT be trusted.\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    size: u64,\n    /// This OPTIONAL property specifies a list of URIs from which this\n    /// object MAY be downloaded. Each entry MUST conform to [RFC 3986](https://tools.ietf.org/html/rfc3986).\n    /// Entries SHOULD use the http and https schemes, as defined\n    /// in [RFC 7230](https://tools.ietf.org/html/rfc7230#section-2.7).\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    urls: Option<Vec<String>>,\n    /// This OPTIONAL property contains arbitrary metadata for this\n    /// descriptor. This OPTIONAL property MUST use the annotation\n    /// rules.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    annotations: Option<HashMap<String, String>>,\n    /// This OPTIONAL property describes the minimum runtime requirements of\n    /// the image. This property SHOULD be present if its target is\n    /// platform-specific.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    platform: Option<Platform>,\n    /// This OPTIONAL property contains the type of an artifact when the descriptor points to an\n    /// artifact. This is the value of the config descriptor mediaType when the descriptor\n    /// references an image manifest. If defined, the value MUST comply with RFC 6838, including\n    /// the naming requirements in its section 4.2, and MAY be registered with IANA.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    artifact_type: Option<MediaType>,\n    /// This OPTIONAL property contains an embedded representation of the referenced content.\n    /// Values MUST conform to the Base 64 encoding, as defined in RFC 4648. The decoded data MUST\n    /// be identical to the referenced content and SHOULD be verified against the digest and size\n    /// fields by content consumers. See Embedded Content for when this is appropriate.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    data: Option<String>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// Describes the minimum runtime requirements of the image.\npub struct Platform {\n    /// This REQUIRED property specifies the CPU architecture.\n    /// Image indexes SHOULD use, and implementations SHOULD understand,\n    /// values listed in the Go Language document for GOARCH.\n    architecture: Arch,\n    /// This REQUIRED property specifies the operating system.\n    /// Image indexes SHOULD use, and implementations SHOULD understand,\n    /// values listed in the Go Language document for GOOS.\n    os: Os,\n    /// This OPTIONAL property specifies the version of the operating system\n    /// targeted by the referenced blob. Implementations MAY refuse to use\n    /// manifests where os.version is not known to work with the host OS\n    /// version. Valid values are implementation-defined. e.g.\n    /// 10.0.14393.1066 on windows.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[builder(default)]\n    os_version: Option<String>,\n    /// This OPTIONAL property specifies an array of strings, each\n    /// specifying a mandatory OS feature. When os is windows, image\n    /// indexes SHOULD use, and implementations SHOULD understand\n    /// the following values:\n    /// - win32k: image requires win32k.sys on the host (Note: win32k.sys is\n    ///   missing on Nano Server)\n    ///\n    /// When os is not windows, values are implementation-defined and SHOULD\n    /// be submitted to this specification for standardization.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[builder(default)]\n    os_features: Option<Vec<String>>,\n    /// This OPTIONAL property specifies the variant of the CPU.\n    /// Image indexes SHOULD use, and implementations SHOULD understand,\n    /// variant values listed in the [Platform Variants]\n    /// (<https://github.com/opencontainers/image-spec/blob/main/image-index.md#platform-variants>)\n    /// table.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[builder(default)]\n    variant: Option<String>,\n    /// This property is RESERVED for future versions of the specification.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[builder(default)]\n    features: Option<Vec<String>>,\n}\n\nimpl Descriptor {\n    /// Construct a new descriptor with the required fields.\n    pub fn new(media_type: MediaType, size: u64, digest: impl Into<Digest>) -> Self {\n        Self {\n            media_type,\n            size,\n            digest: digest.into(),\n            urls: Default::default(),\n            annotations: Default::default(),\n            platform: Default::default(),\n            artifact_type: Default::default(),\n            data: Default::default(),\n        }\n    }\n\n    /// Return a view of [`Self::digest()`] that has been parsed as a valid SHA-256.\n    pub fn as_digest_sha256(&self) -> Option<&str> {\n        match self.digest.algorithm() {\n            super::DigestAlgorithm::Sha256 => Some(self.digest.digest()),\n            _ => None,\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::str::FromStr;\n\n    use super::*;\n\n    #[test]\n    fn test_deserialize() {\n        let descriptor_str = r#\"{\n            \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n            \"digest\":\"sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356\",\n            \"size\":769,\n            \"annotations\":{\"org.opencontainers.image.created\": \"2023-10-11T22:37:26Z\"},\n            \"artifactType\":\"application/spdx+json\"}\"#;\n        let descriptor: Descriptor = serde_json::from_str(descriptor_str).unwrap();\n        assert_eq!(descriptor.media_type, MediaType::ImageManifest);\n        assert_eq!(\n            descriptor.digest,\n            Digest::from_str(\n                \"sha256:c2b8beca588702777e5f35dafdbeae9ec16c2bab802331f81cacd2a92f1d5356\"\n            )\n            .unwrap()\n        );\n        assert_eq!(descriptor.size, 769);\n        assert_eq!(\n            descriptor\n                .annotations\n                .unwrap()\n                .get(\"org.opencontainers.image.created\"),\n            Some(&\"2023-10-11T22:37:26Z\".to_string())\n        );\n        assert_eq!(\n            descriptor.artifact_type.unwrap(),\n            MediaType::Other(\"application/spdx+json\".to_string())\n        );\n    }\n\n    #[test]\n    fn test_malformed_digest() {\n        let descriptor_str = r#\"{\n            \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n            \"digest\":\"../blah:this-is-an-attack\",\n            \"size\":769,\n            \"annotations\":{\"org.opencontainers.image.created\": \"2023-10-11T22:37:26Z\"},\n            \"artifactType\":\"application/spdx+json\"}\"#;\n        assert!(serde_json::from_str::<Descriptor>(descriptor_str).is_err());\n    }\n}\n"
  },
  {
    "path": "src/image/digest.rs",
    "content": "//! Functionality corresponding to <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>.\n\nuse std::fmt::Display;\nuse std::str::FromStr;\n\n/// A digest algorithm; at the current time only SHA-256\n/// is widely used and supported in the ecosystem. Other\n/// SHA variants are included as they are noted in the\n/// standards. Other digest algorithms may be added\n/// in the future, so this structure is marked as non-exhaustive.\n#[non_exhaustive]\n#[derive(Clone, Debug, PartialEq, Eq, Hash)]\npub enum DigestAlgorithm {\n    /// The SHA-256 algorithm.\n    Sha256,\n    /// The SHA-384 algorithm.\n    Sha384,\n    /// The SHA-512 algorithm.\n    Sha512,\n    /// Any other algorithm. Note that it is possible\n    /// that other algorithms will be added as enum members.\n    /// If you want to try to handle those, consider also\n    /// comparing against [`Self::as_ref<str>`].\n    Other(Box<str>),\n}\n\nimpl AsRef<str> for DigestAlgorithm {\n    fn as_ref(&self) -> &str {\n        match self {\n            DigestAlgorithm::Sha256 => \"sha256\",\n            DigestAlgorithm::Sha384 => \"sha384\",\n            DigestAlgorithm::Sha512 => \"sha512\",\n            DigestAlgorithm::Other(o) => o,\n        }\n    }\n}\n\nimpl Display for DigestAlgorithm {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(self.as_ref())\n    }\n}\n\nimpl DigestAlgorithm {\n    /// Return the length of the digest in hexadecimal ASCII characters.\n    pub const fn digest_hexlen(&self) -> Option<u32> {\n        match self {\n            DigestAlgorithm::Sha256 => Some(64),\n            DigestAlgorithm::Sha384 => Some(96),\n            DigestAlgorithm::Sha512 => Some(128),\n            DigestAlgorithm::Other(_) => None,\n        }\n    }\n}\n\nimpl From<&str> for DigestAlgorithm {\n    fn from(value: &str) -> Self {\n        match value {\n            \"sha256\" => Self::Sha256,\n            \"sha384\" => Self::Sha384,\n            \"sha512\" => Self::Sha512,\n            o => Self::Other(o.into()),\n        }\n    }\n}\n\nfn char_is_lowercase_ascii_hex(c: char) -> bool {\n    matches!(c, '0'..='9' | 'a'..='f')\n}\n\n/// algorithm-component ::= [a-z0-9]+\nfn char_is_algorithm_component(c: char) -> bool {\n    matches!(c, 'a'..='z' | '0'..='9')\n}\n\n/// encoded ::= [a-zA-Z0-9=_-]+\nfn char_is_encoded(c: char) -> bool {\n    char_is_algorithm_component(c) || matches!(c, 'A'..='Z' | '=' | '_' | '-')\n}\n\n/// A parsed pair of algorithm:digest as defined\n/// by <https://github.com/opencontainers/image-spec/blob/main/descriptor.md#digests>\n///\n/// ```\n/// # fn main() -> Result<(), Box<dyn std::error::Error>> {\n/// use std::str::FromStr;\n/// use oci_spec::image::{Digest, DigestAlgorithm};\n/// let d = Digest::from_str(\"sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\")?;\n/// assert_eq!(d.algorithm(), &DigestAlgorithm::Sha256);\n/// assert_eq!(d.digest(), \"6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\");\n/// let d = Digest::from_str(\"multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8\")?;\n/// assert_eq!(d.algorithm(), &DigestAlgorithm::from(\"multihash+base58\"));\n/// assert_eq!(d.digest(), \"QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8\");\n/// # Ok(())\n/// # }\n/// ```\n\n#[derive(Clone, Debug, Eq, PartialEq, Hash)]\npub struct Digest {\n    /// The algorithm; we need to hold a copy of this\n    /// right now as we ended up returning a reference\n    /// from the accessor. It probably would have been\n    /// better to have both borrowed/owned DigestAlgorithm\n    /// versions and our accessor just returns a borrowed version.\n    algorithm: DigestAlgorithm,\n    value: Box<str>,\n    split: usize,\n}\n\nimpl AsRef<str> for Digest {\n    fn as_ref(&self) -> &str {\n        &self.value\n    }\n}\n\nimpl Display for Digest {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(self.as_ref())\n    }\n}\n\nimpl<'de> serde::Deserialize<'de> for Digest {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let s = String::deserialize(deserializer)?;\n        Self::from_str(&s).map_err(serde::de::Error::custom)\n    }\n}\n\nimpl serde::ser::Serialize for Digest {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        let v = self.to_string();\n        serializer.serialize_str(&v)\n    }\n}\n\nimpl Digest {\n    const ALGORITHM_SEPARATOR: &'static [char] = &['+', '.', '_', '-'];\n    /// The algorithm name (e.g. sha256, sha512)\n    pub fn algorithm(&self) -> &DigestAlgorithm {\n        &self.algorithm\n    }\n\n    /// The algorithm digest component. When this is one of the\n    /// SHA family (SHA-256, SHA-384, etc.) the digest value\n    /// is guaranteed to be a valid length with only lowercase hexadecimal\n    /// characters. For example with SHA-256, the length is 64.\n    pub fn digest(&self) -> &str {\n        &self.value[self.split + 1..]\n    }\n}\n\nimpl FromStr for Digest {\n    type Err = crate::OciSpecError;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Digest::try_from(s)\n    }\n}\n\nimpl TryFrom<String> for Digest {\n    type Error = crate::OciSpecError;\n\n    fn try_from(s: String) -> Result<Self, Self::Error> {\n        let s = s.into_boxed_str();\n        let Some(split) = s.find(':') else {\n            return Err(crate::OciSpecError::Other(\"missing ':' in digest\".into()));\n        };\n        let (algorithm, value) = s.split_at(split);\n        let value = &value[1..];\n\n        // algorithm ::= algorithm-component (algorithm-separator algorithm-component)*\n        let algorithm_parts = algorithm.split(Self::ALGORITHM_SEPARATOR);\n        for part in algorithm_parts {\n            if part.is_empty() {\n                return Err(crate::OciSpecError::Other(\n                    \"Empty algorithm component\".into(),\n                ));\n            }\n            if !part.chars().all(char_is_algorithm_component) {\n                return Err(crate::OciSpecError::Other(format!(\n                    \"Invalid algorithm component: {part}\"\n                )));\n            }\n        }\n\n        if value.is_empty() {\n            return Err(crate::OciSpecError::Other(\"Empty algorithm value\".into()));\n        }\n        if !value.chars().all(char_is_encoded) {\n            return Err(crate::OciSpecError::Other(format!(\n                \"Invalid encoded value {value}\"\n            )));\n        }\n\n        let algorithm = DigestAlgorithm::from(algorithm);\n        if let Some(expected) = algorithm.digest_hexlen() {\n            let found = value.len();\n            if expected as usize != found {\n                return Err(crate::OciSpecError::Other(format!(\n                    \"Invalid digest length {found} expected {expected}\"\n                )));\n            }\n            let is_all_hex = value.chars().all(char_is_lowercase_ascii_hex);\n            if !is_all_hex {\n                return Err(crate::OciSpecError::Other(format!(\n                    \"Invalid non-hexadecimal character in digest: {value}\"\n                )));\n            }\n        }\n        Ok(Self {\n            algorithm,\n            value: s,\n            split,\n        })\n    }\n}\n\nimpl TryFrom<&str> for Digest {\n    type Error = crate::OciSpecError;\n\n    fn try_from(string: &str) -> Result<Self, Self::Error> {\n        TryFrom::try_from(string.to_owned())\n    }\n}\n\n/// A SHA-256 digest, guaranteed to be 64 lowercase hexadecimal ASCII characters.\n#[derive(Clone, Debug, Eq, PartialEq, Hash)]\npub struct Sha256Digest {\n    digest: Box<str>,\n}\n\nimpl From<Sha256Digest> for Digest {\n    fn from(value: Sha256Digest) -> Self {\n        Self {\n            algorithm: DigestAlgorithm::Sha256,\n            value: format!(\"sha256:{}\", value.digest()).into(),\n            split: 6,\n        }\n    }\n}\n\nimpl AsRef<str> for Sha256Digest {\n    fn as_ref(&self) -> &str {\n        self.digest()\n    }\n}\n\nimpl Display for Sha256Digest {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.write_str(self.digest())\n    }\n}\n\nimpl FromStr for Sha256Digest {\n    type Err = crate::OciSpecError;\n\n    fn from_str(digest: &str) -> Result<Self, Self::Err> {\n        let alg = DigestAlgorithm::Sha256;\n        let v = format!(\"{alg}:{digest}\");\n        let d = Digest::from_str(&v)?;\n        match d.algorithm {\n            DigestAlgorithm::Sha256 => Ok(Self {\n                digest: d.digest().into(),\n            }),\n            o => Err(crate::OciSpecError::Other(format!(\n                \"Expected algorithm sha256 but found {o}\",\n            ))),\n        }\n    }\n}\n\nimpl Sha256Digest {\n    /// The SHA-256 digest, guaranteed to be 64 lowercase hexadecimal characters.\n    pub fn digest(&self) -> &str {\n        &self.digest\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_digest_invalid() {\n        let invalid = [\n            \"\",\n            \"foo\",\n            \":\",\n            \"blah+\",\n            \"_digest:somevalue\",\n            \":blah\",\n            \"blah:\",\n            \"FooBar:123abc\",\n            \"^:foo\",\n            \"bar^baz:blah\",\n            \"sha256:123456*78\",\n            \"sha256:6c3c624b58dbbcd3c0dd82b4z53f04194d1247c6eebdaab7c610cf7d66709b3b\", // has a z in the middle\n            \"sha384:x\",\n            \"sha384:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\",\n            \"sha512:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\",\n        ];\n        for case in invalid {\n            assert!(\n                Digest::from_str(case).is_err(),\n                \"Should have failed to parse: {case}\"\n            )\n        }\n    }\n\n    const VALID_DIGEST_SHA256: &str =\n        \"sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\";\n    const VALID_DIGEST_SHA384: &str =\n        \"sha384:6c3c624b58dbbcd4d1247c6eebdaab7c610cf7d66709b3b3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b\";\n    const VALID_DIGEST_SHA512: &str =\n        \"sha512:6c3c624b58dbbcd3c0dd826c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3bb4c53f04194d1247c6eebdaab7c610cf7d66709b3b\";\n\n    #[test]\n    fn test_digest_valid() {\n        let cases = [\"foo:bar\", \"xxhash:42\"];\n        for case in cases {\n            Digest::from_str(case).unwrap();\n        }\n\n        let d = Digest::try_from(\"multihash+base58:QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8\")\n            .unwrap();\n        assert_eq!(d.algorithm(), &DigestAlgorithm::from(\"multihash+base58\"));\n        assert_eq!(d.digest(), \"QmRZxt2b1FVZPNqd8hsiykDL3TdBDeTSPX9Kv46HmX4Gx8\");\n    }\n\n    #[test]\n    fn test_sha256_valid() {\n        let expected_value = VALID_DIGEST_SHA256.split_once(':').unwrap().1;\n        let d = Digest::from_str(VALID_DIGEST_SHA256).unwrap();\n        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha256);\n        assert_eq!(d.digest(), expected_value);\n        let base_digest = d.clone();\n        assert_eq!(base_digest.digest(), expected_value);\n    }\n\n    #[test]\n    fn test_sha384_valid() {\n        let expected_value = VALID_DIGEST_SHA384.split_once(':').unwrap().1;\n        let d = Digest::from_str(VALID_DIGEST_SHA384).unwrap();\n        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha384);\n        assert_eq!(d.digest(), expected_value);\n        // Verify we can cheaply coerce to a string\n        assert_eq!(d.as_ref(), VALID_DIGEST_SHA384);\n        let base_digest = d.clone();\n        assert_eq!(base_digest.digest(), expected_value);\n    }\n\n    #[test]\n    fn test_sha512_valid() {\n        let expected_value = VALID_DIGEST_SHA512.split_once(':').unwrap().1;\n        let d = Digest::from_str(VALID_DIGEST_SHA512).unwrap();\n        assert_eq!(d.algorithm(), &DigestAlgorithm::Sha512);\n        assert_eq!(d.digest(), expected_value);\n        let base_digest = d.clone();\n        assert_eq!(base_digest.digest(), expected_value);\n    }\n\n    #[test]\n    fn test_sha256() {\n        let digest = VALID_DIGEST_SHA256.split_once(':').unwrap().1;\n        let v = Sha256Digest::from_str(digest).unwrap();\n        assert_eq!(v.digest(), digest);\n    }\n}\n"
  },
  {
    "path": "src/image/index.rs",
    "content": "use super::{Descriptor, MediaType};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, to_writer,\n};\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    fmt::Display,\n    io::{Read, Write},\n    path::Path,\n};\n\n/// The expected schema version; equals 2 for compatibility with older versions of Docker.\npub const SCHEMA_VERSION: u32 = 2;\n\n#[derive(\n    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// The image index is a higher-level manifest which points to specific\n/// image manifests, ideal for one or more platforms. While the use of\n/// an image index is OPTIONAL for image providers, image consumers\n/// SHOULD be prepared to process them.\npub struct ImageIndex {\n    /// This REQUIRED property specifies the image manifest schema version.\n    /// For this version of the specification, this MUST be 2 to ensure\n    /// backward compatibility with older versions of Docker. The\n    /// value of this field will not change. This field MAY be\n    /// removed in a future version of the specification.\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    schema_version: u32,\n    /// This property is reserved for use, to maintain compatibility. When\n    /// used, this field contains the media type of this document,\n    /// which differs from the descriptor use of mediaType.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    media_type: Option<MediaType>,\n    /// This OPTIONAL property contains the type of an artifact when the manifest is used for an\n    /// artifact. If defined, the value MUST comply with RFC 6838, including the naming\n    /// requirements in its section 4.2, and MAY be registered with IANA.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    artifact_type: Option<MediaType>,\n    /// This REQUIRED property contains a list of manifests for specific\n    /// platforms. While this property MUST be present, the size of\n    /// the array MAY be zero.\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    manifests: Vec<Descriptor>,\n    /// This OPTIONAL property specifies a descriptor of another manifest. This value, used by the\n    /// referrers API, indicates a relationship to the specified manifest.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    subject: Option<Descriptor>,\n    /// This OPTIONAL property contains arbitrary metadata for the image\n    /// index. This OPTIONAL property MUST use the annotation rules.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    annotations: Option<HashMap<String, String>>,\n}\n\nimpl ImageIndex {\n    /// Attempts to load an image index from a file.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)\n    /// if the file does not exist or an\n    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image index\n    /// cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// ```\n    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageIndex> {\n        from_file(path)\n    }\n\n    /// Attempts to load an image index from a stream.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)\n    /// if the index cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    /// use std::fs::File;\n    ///\n    /// let reader = File::open(\"index.json\").unwrap();\n    /// let image_index = ImageIndex::from_reader(reader).unwrap();\n    /// ```\n    pub fn from_reader<R: Read>(reader: R) -> Result<ImageIndex> {\n        from_reader(reader)\n    }\n\n    /// Attempts to write an image index to a file as JSON. If the file already exists, it\n    /// will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image index cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// image_index.to_file(\"my-index.json\").unwrap();\n    /// ```\n    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, false)\n    }\n\n    /// Attempts to write an image index to a file as pretty printed JSON. If the file\n    /// already exists, it will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image index cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// image_index.to_file_pretty(\"my-index.json\").unwrap();\n    /// ```\n    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, true)\n    }\n\n    /// Attempts to write an image index to a stream as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image index cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_index.to_writer(&mut writer);\n    /// ```\n    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, false)\n    }\n\n    /// Attempts to write an image index to a stream as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image index cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_index.to_writer_pretty(&mut writer);\n    /// ```\n    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, true)\n    }\n\n    /// Attempts to write an image index to a string as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// let json_str = image_index.to_string().unwrap();\n    /// ```\n    pub fn to_string(&self) -> Result<String> {\n        to_string(&self, false)\n    }\n\n    /// Attempts to write an image index to a string as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageIndex;\n    ///\n    /// let image_index = ImageIndex::from_file(\"index.json\").unwrap();\n    /// let json_str = image_index.to_string_pretty().unwrap();\n    /// ```\n    pub fn to_string_pretty(&self) -> Result<String> {\n        to_string(&self, true)\n    }\n}\n\nimpl Default for ImageIndex {\n    fn default() -> Self {\n        Self {\n            schema_version: SCHEMA_VERSION,\n            media_type: Default::default(),\n            manifests: Default::default(),\n            annotations: Default::default(),\n            artifact_type: Default::default(),\n            subject: Default::default(),\n        }\n    }\n}\n\n/// This ToString trait is automatically implemented for any type which implements the Display trait.\n/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,\n/// and you get the ToString implementation for free.\nimpl Display for ImageIndex {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Serde serialization never fails since this is\n        // a combination of String and enums.\n        write!(\n            f,\n            \"{}\",\n            self.to_string_pretty()\n                .expect(\"ImageIndex to JSON conversion failed\")\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::str::FromStr;\n    use std::{fs, path::PathBuf};\n\n    use super::*;\n    use crate::image::{Arch, Os, Sha256Digest};\n    use crate::image::{DescriptorBuilder, PlatformBuilder};\n\n    fn create_index() -> ImageIndex {\n        let ppc_manifest = DescriptorBuilder::default()\n            .media_type(MediaType::ImageManifest)\n            .digest(\n                Sha256Digest::from_str(\n                    \"e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n                )\n                .unwrap(),\n            )\n            .size(7143u64)\n            .platform(\n                PlatformBuilder::default()\n                    .architecture(Arch::PowerPC64le)\n                    .os(Os::Linux)\n                    .build()\n                    .expect(\"build ppc64le platform\"),\n            )\n            .build()\n            .expect(\"build ppc manifest descriptor\");\n\n        let amd64_manifest = DescriptorBuilder::default()\n            .media_type(MediaType::ImageManifest)\n            .digest(\n                Sha256Digest::from_str(\n                    \"5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n                )\n                .unwrap(),\n            )\n            .size(7682u64)\n            .platform(\n                PlatformBuilder::default()\n                    .architecture(Arch::Amd64)\n                    .os(Os::Linux)\n                    .build()\n                    .expect(\"build amd64 platform\"),\n            )\n            .build()\n            .expect(\"build amd64 manifest descriptor\");\n\n        ImageIndexBuilder::default()\n            .schema_version(SCHEMA_VERSION)\n            .manifests(vec![ppc_manifest, amd64_manifest])\n            .build()\n            .expect(\"build image index\")\n    }\n\n    fn get_index_path() -> PathBuf {\n        PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"test/data/index.json\")\n    }\n\n    #[test]\n    fn load_index_from_file() {\n        // arrange\n        let index_path = get_index_path();\n\n        // act\n        let actual = ImageIndex::from_file(index_path).expect(\"from file\");\n\n        // assert\n        let expected = create_index();\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn load_index_from_reader() {\n        // arrange\n        let reader = fs::read(get_index_path()).expect(\"read index\");\n\n        // act\n        let actual = ImageIndex::from_reader(&*reader).expect(\"from reader\");\n\n        // assert\n        let expected = create_index();\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_index_to_file() {\n        // arrange\n        let tmp = std::env::temp_dir().join(\"save_index_to_file\");\n        fs::create_dir_all(&tmp).expect(\"create test directory\");\n        let index = create_index();\n        let index_path = tmp.join(\"index.json\");\n\n        // act\n        index\n            .to_file_pretty(&index_path)\n            .expect(\"write index to file\");\n\n        // assert\n        let actual = fs::read_to_string(index_path).expect(\"read actual\");\n        let expected = fs::read_to_string(get_index_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_index_to_writer() {\n        // arrange\n        let mut actual = Vec::new();\n        let index = create_index();\n\n        // act\n        index.to_writer_pretty(&mut actual).expect(\"to writer\");\n\n        // assert\n        let expected = fs::read(get_index_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_index_to_string() {\n        // arrange\n        let index = create_index();\n\n        // act\n        let actual = index.to_string_pretty().expect(\"to string\");\n\n        // assert\n        let expected = fs::read_to_string(get_index_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "src/image/manifest.rs",
    "content": "use super::{Descriptor, MediaType};\nuse crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, to_writer,\n};\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    fmt::Display,\n    io::{Read, Write},\n    path::Path,\n};\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Unlike the image index, which contains information about a set of images\n/// that can span a variety of architectures and operating systems, an image\n/// manifest provides a configuration and set of layers for a single\n/// container image for a specific architecture and operating system.\npub struct ImageManifest {\n    /// This REQUIRED property specifies the image manifest schema version.\n    /// For this version of the specification, this MUST be 2 to ensure\n    /// backward compatibility with older versions of Docker. The\n    /// value of this field will not change. This field MAY be\n    /// removed in a future version of the specification.\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    schema_version: u32,\n    /// This property is reserved for use, to maintain compatibility. When\n    /// used, this field contains the media type of this document,\n    /// which differs from the descriptor use of mediaType.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    media_type: Option<MediaType>,\n    /// This OPTIONAL property contains the type of an artifact when the manifest is used for an\n    /// artifact. This MUST be set when config.mediaType is set to the empty value. If defined, the\n    /// value MUST comply with RFC 6838, including the naming requirements in its section 4.2, and\n    /// MAY be registered with IANA. Implementations storing or copying image manifests MUST NOT\n    /// error on encountering an artifactType that is unknown to the implementation.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    artifact_type: Option<MediaType>,\n    /// This REQUIRED property references a configuration object for a\n    /// container, by digest. Beyond the descriptor requirements,\n    /// the value has the following additional restrictions:\n    /// The media type descriptor property has additional restrictions for\n    /// config. Implementations MUST support at least the following\n    /// media types:\n    /// - application/vnd.oci.image.config.v1+json\n    ///\n    /// Manifests concerned with portability SHOULD use one of the above\n    /// media types.\n    #[getset(get = \"pub\", set = \"pub\")]\n    config: Descriptor,\n    /// Each item in the array MUST be a descriptor. The array MUST have the\n    /// base layer at index 0. Subsequent layers MUST then follow in\n    /// stack order (i.e. from `layers[0]` to `layers[len(layers)-1]`).\n    /// The final filesystem layout MUST match the result of applying\n    /// the layers to an empty directory. The ownership, mode, and other\n    /// attributes of the initial empty directory are unspecified.\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    layers: Vec<Descriptor>,\n    /// This OPTIONAL property specifies a descriptor of another manifest. This value, used by the\n    /// referrers API, indicates a relationship to the specified manifest.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    subject: Option<Descriptor>,\n    /// This OPTIONAL property contains arbitrary metadata for the image\n    /// manifest. This OPTIONAL property MUST use the annotation\n    /// rules.\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    #[builder(default)]\n    annotations: Option<HashMap<String, String>>,\n}\n\nimpl ImageManifest {\n    /// Attempts to load an image manifest from a file.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)\n    /// if the file does not exist or an\n    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the image manifest\n    /// cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// ```\n    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<ImageManifest> {\n        from_file(path)\n    }\n\n    /// Attempts to load an image manifest from a stream.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)\n    /// if the manifest cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    /// use std::fs::File;\n    ///\n    /// let reader = File::open(\"manifest.json\").unwrap();\n    /// let image_manifest = ImageManifest::from_reader(reader).unwrap();\n    /// ```\n    pub fn from_reader<R: Read>(reader: R) -> Result<ImageManifest> {\n        from_reader(reader)\n    }\n\n    /// Attempts to write an image manifest to a file as JSON. If the file already exists, it\n    /// will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image manifest cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// image_manifest.to_file(\"my-manifest.json\").unwrap();\n    /// ```\n    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, false)\n    }\n\n    /// Attempts to write an image manifest to a file as pretty printed JSON. If the file already exists, it\n    /// will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image manifest cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// image_manifest.to_file_pretty(\"my-manifest.json\").unwrap();\n    /// ```\n    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, true)\n    }\n\n    /// Attempts to write an image manifest to a stream as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image manifest cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_manifest.to_writer(&mut writer);\n    /// ```\n    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, false)\n    }\n\n    /// Attempts to write an image manifest to a stream as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image manifest cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// let mut writer = Vec::new();\n    /// image_manifest.to_writer_pretty(&mut writer);\n    /// ```\n    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, true)\n    }\n\n    /// Attempts to write an image manifest to a string as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// let json_str = image_manifest.to_string().unwrap();\n    /// ```\n    pub fn to_string(&self) -> Result<String> {\n        to_string(&self, false)\n    }\n\n    /// Attempts to write an image manifest to a string as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the image configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::ImageManifest;\n    ///\n    /// let image_manifest = ImageManifest::from_file(\"manifest.json\").unwrap();\n    /// let json_str = image_manifest.to_string_pretty().unwrap();\n    /// ```\n    pub fn to_string_pretty(&self) -> Result<String> {\n        to_string(&self, true)\n    }\n}\n\n/// This ToString trait is automatically implemented for any type which implements the Display trait.\n/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,\n/// and you get the ToString implementation for free.\nimpl Display for ImageManifest {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Serde serialization never fails since this is\n        // a combination of String and enums.\n        write!(\n            f,\n            \"{}\",\n            self.to_string_pretty()\n                .expect(\"ImageManifest to JSON conversion failed\")\n        )\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{fs, path::PathBuf, str::FromStr};\n\n    use super::*;\n    use crate::image::{DescriptorBuilder, Sha256Digest};\n\n    fn create_manifest() -> ImageManifest {\n        use crate::image::SCHEMA_VERSION;\n\n        let config = DescriptorBuilder::default()\n            .media_type(MediaType::ImageConfig)\n            .size(7023u64)\n            .digest(\n                \"b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\"\n                    .parse::<Sha256Digest>()\n                    .unwrap(),\n            )\n            .build()\n            .expect(\"build config descriptor\");\n\n        let layers: Vec<Descriptor> = [\n            (\n                32654u64,\n                \"9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0\",\n            ),\n            (\n                16724,\n                \"3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b\",\n            ),\n            (\n                73109,\n                \"ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736\",\n            ),\n        ]\n        .iter()\n        .map(|l| {\n            DescriptorBuilder::default()\n                .media_type(MediaType::ImageLayerGzip)\n                .size(l.0)\n                .digest(Sha256Digest::from_str(l.1).unwrap())\n                .build()\n                .expect(\"build layer\")\n        })\n        .collect();\n\n        ImageManifestBuilder::default()\n            .schema_version(SCHEMA_VERSION)\n            .config(config)\n            .layers(layers)\n            .build()\n            .expect(\"build image manifest\")\n    }\n\n    fn get_manifest_path() -> PathBuf {\n        PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"test/data/manifest.json\")\n    }\n\n    #[test]\n    fn load_manifest_from_file() {\n        // arrange\n        let manifest_path = get_manifest_path();\n        let expected = create_manifest();\n\n        // act\n        let actual = ImageManifest::from_file(manifest_path).expect(\"from file\");\n\n        // assert\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn getset() {\n        let mut manifest = create_manifest();\n        assert_eq!(manifest.layers().len(), 3);\n        let layer_copy = manifest.layers()[0].clone();\n        manifest.layers_mut().push(layer_copy);\n        assert_eq!(manifest.layers().len(), 4);\n    }\n\n    #[test]\n    fn load_manifest_from_reader() {\n        // arrange\n        let reader = fs::read(get_manifest_path()).expect(\"read manifest\");\n\n        // act\n        let actual = ImageManifest::from_reader(&*reader).expect(\"from reader\");\n\n        // assert\n        let expected = create_manifest();\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_manifest_to_file() {\n        // arrange\n        let tmp = std::env::temp_dir().join(\"save_manifest_to_file\");\n        fs::create_dir_all(&tmp).expect(\"create test directory\");\n        let manifest = create_manifest();\n        let manifest_path = tmp.join(\"manifest.json\");\n\n        // act\n        manifest\n            .to_file_pretty(&manifest_path)\n            .expect(\"write manifest to file\");\n\n        // assert\n        let actual = fs::read_to_string(manifest_path).expect(\"read actual\");\n        let expected = fs::read_to_string(get_manifest_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_manifest_to_writer() {\n        // arrange\n        let manifest = create_manifest();\n        let mut actual = Vec::new();\n\n        // act\n        manifest.to_writer_pretty(&mut actual).expect(\"to writer\");\n\n        // assert\n        let expected = fs::read(get_manifest_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_manifest_to_string() {\n        // arrange\n        let manifest = create_manifest();\n\n        // act\n        let actual = manifest.to_string_pretty().expect(\"to string\");\n\n        // assert\n        let expected = fs::read_to_string(get_manifest_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "src/image/mod.rs",
    "content": "//! [OCI image spec](https://github.com/opencontainers/image-spec) types and definitions.\n\nmod annotations;\nmod artifact;\nmod config;\nmod descriptor;\nmod digest;\nmod index;\nmod manifest;\nmod oci_layout;\nmod version;\n\nuse std::fmt::Display;\n\nuse serde::{Deserialize, Serialize};\n\npub use annotations::*;\npub use artifact::*;\npub use config::*;\npub use descriptor::*;\npub use digest::*;\npub use index::*;\npub use manifest::*;\npub use oci_layout::*;\npub use version::*;\n\n/// Media types used by OCI image format spec. Values MUST comply with RFC 6838,\n/// including the naming requirements in its section 4.2.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum MediaType {\n    /// MediaType Descriptor specifies the media type for a content descriptor.\n    Descriptor,\n    /// MediaType LayoutHeader specifies the media type for the oci-layout.\n    LayoutHeader,\n    /// MediaType ImageManifest specifies the media type for an image manifest.\n    ImageManifest,\n    /// MediaType ImageIndex specifies the media type for an image index.\n    ImageIndex,\n    /// MediaType ImageLayer is the media type used for layers referenced by the\n    /// manifest.\n    ImageLayer,\n    /// MediaType ImageLayerGzip is the media type used for gzipped layers\n    /// referenced by the manifest.\n    ImageLayerGzip,\n    /// MediaType ImageLayerZstd is the media type used for zstd compressed\n    /// layers referenced by the manifest.\n    ImageLayerZstd,\n    /// MediaType ImageLayerNonDistributable is the media type for layers\n    /// referenced by the manifest but with distribution restrictions.\n    ImageLayerNonDistributable,\n    /// MediaType ImageLayerNonDistributableGzip is the media type for\n    /// gzipped layers referenced by the manifest but with distribution\n    /// restrictions.\n    ImageLayerNonDistributableGzip,\n    /// MediaType ImageLayerNonDistributableZstd is the media type for zstd\n    /// compressed layers referenced by the manifest but with distribution\n    /// restrictions.\n    ImageLayerNonDistributableZstd,\n    /// MediaType ImageConfig specifies the media type for the image\n    /// configuration.\n    ImageConfig,\n    /// MediaType ArtifactManifest specifies the media type used for content addressable\n    /// artifacts to store them along side container images in a registry.\n    ArtifactManifest,\n    /// MediaType EmptyJSON specifies a descriptor that has no content for the implementation. The\n    /// blob payload is the most minimal content that is still a valid JSON object: {} (size of 2).\n    /// The blob digest of {} is\n    /// sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a.\n    EmptyJSON,\n    /// MediaType not specified by OCI image format.\n    Other(String),\n}\n\nimpl Display for MediaType {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{}\", self.as_ref())\n    }\n}\n\nimpl From<&str> for MediaType {\n    fn from(media_type: &str) -> Self {\n        match media_type {\n            \"application/vnd.oci.descriptor\" => MediaType::Descriptor,\n            \"application/vnd.oci.layout.header.v1+json\" => MediaType::LayoutHeader,\n            \"application/vnd.oci.image.manifest.v1+json\" => MediaType::ImageManifest,\n            \"application/vnd.oci.image.index.v1+json\" => MediaType::ImageIndex,\n            \"application/vnd.oci.image.layer.v1.tar\" => MediaType::ImageLayer,\n            \"application/vnd.oci.image.layer.v1.tar+gzip\" => MediaType::ImageLayerGzip,\n            \"application/vnd.oci.image.layer.v1.tar+zstd\" => MediaType::ImageLayerZstd,\n            \"application/vnd.oci.image.layer.nondistributable.v1.tar\" => {\n                MediaType::ImageLayerNonDistributable\n            }\n            \"application/vnd.oci.image.layer.nondistributable.v1.tar+gzip\" => {\n                MediaType::ImageLayerNonDistributableGzip\n            }\n            \"application/vnd.oci.image.layer.nondistributable.v1.tar+zstd\" => {\n                MediaType::ImageLayerNonDistributableZstd\n            }\n            \"application/vnd.oci.image.config.v1+json\" => MediaType::ImageConfig,\n            \"application/vnd.oci.artifact.manifest.v1+json\" => MediaType::ArtifactManifest,\n            \"application/vnd.oci.empty.v1+json\" => MediaType::EmptyJSON,\n            media => MediaType::Other(media.to_owned()),\n        }\n    }\n}\n\nimpl From<MediaType> for String {\n    fn from(media_type: MediaType) -> Self {\n        media_type.as_ref().to_owned()\n    }\n}\n\nimpl AsRef<str> for MediaType {\n    fn as_ref(&self) -> &str {\n        match self {\n            Self::Descriptor => \"application/vnd.oci.descriptor\",\n            Self::LayoutHeader => \"application/vnd.oci.layout.header.v1+json\",\n            Self::ImageManifest => \"application/vnd.oci.image.manifest.v1+json\",\n            Self::ImageIndex => \"application/vnd.oci.image.index.v1+json\",\n            Self::ImageLayer => \"application/vnd.oci.image.layer.v1.tar\",\n            Self::ImageLayerGzip => \"application/vnd.oci.image.layer.v1.tar+gzip\",\n            Self::ImageLayerZstd => \"application/vnd.oci.image.layer.v1.tar+zstd\",\n            Self::ImageLayerNonDistributable => {\n                \"application/vnd.oci.image.layer.nondistributable.v1.tar\"\n            }\n            Self::ImageLayerNonDistributableGzip => {\n                \"application/vnd.oci.image.layer.nondistributable.v1.tar+gzip\"\n            }\n            Self::ImageLayerNonDistributableZstd => {\n                \"application/vnd.oci.image.layer.nondistributable.v1.tar+zstd\"\n            }\n            Self::ImageConfig => \"application/vnd.oci.image.config.v1+json\",\n            Self::ArtifactManifest => \"application/vnd.oci.artifact.manifest.v1+json\",\n            Self::EmptyJSON => \"application/vnd.oci.empty.v1+json\",\n            Self::Other(media_type) => media_type.as_str(),\n        }\n    }\n}\n\n/// Trait to get the Docker Image Manifest V2 Schema 2 media type for an OCI media type\n///\n/// This may be necessary for compatibility with tools that do not recognize the OCI media types.\n/// Where a [`MediaType`] is expected you can use `MediaType::ImageManifest.to_docker_v2s2()?` instead and\n/// `impl From<&str> for MediaType` will create a [`MediaType::Other`] for it.\n///\n/// Not all OCI Media Types have an equivalent Docker V2S2 Media Type. In those cases, `to_docker_v2s2` will error.\npub trait ToDockerV2S2 {\n    /// Get the [Docker Image Manifest V2 Schema 2](https://docs.docker.com/registry/spec/manifest-v2-2/)\n    /// media type equivalent for an OCI media type\n    fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error>;\n}\n\nimpl ToDockerV2S2 for MediaType {\n    fn to_docker_v2s2(&self) -> Result<&str, std::fmt::Error> {\n        Ok(match self {\n            Self::ImageIndex => \"application/vnd.docker.distribution.manifest.list.v2+json\",\n            Self::ImageManifest => \"application/vnd.docker.distribution.manifest.v2+json\",\n            Self::ImageConfig => \"application/vnd.docker.container.image.v1+json\",\n            Self::ImageLayerGzip => \"application/vnd.docker.image.rootfs.diff.tar.gzip\",\n            _ => return Err(std::fmt::Error),\n        })\n    }\n}\n\nimpl Serialize for MediaType {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        let media_type = format!(\"{self}\");\n        media_type.serialize(serializer)\n    }\n}\n\nimpl<'de> Deserialize<'de> for MediaType {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let media_type = String::deserialize(deserializer)?;\n        Ok(media_type.as_str().into())\n    }\n}\n\n/// Name of the target operating system.\n#[allow(missing_docs)]\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Os {\n    AIX,\n    Android,\n    Darwin,\n    DragonFlyBSD,\n    FreeBSD,\n    Hurd,\n    Illumos,\n    #[allow(non_camel_case_types)]\n    iOS,\n    Js,\n    Linux,\n    Nacl,\n    NetBSD,\n    OpenBSD,\n    Plan9,\n    Solaris,\n    Windows,\n    #[allow(non_camel_case_types)]\n    zOS,\n    Other(String),\n}\n\nimpl From<&str> for Os {\n    fn from(os: &str) -> Self {\n        match os {\n            \"aix\" => Os::AIX,\n            \"android\" => Os::Android,\n            \"darwin\" => Os::Darwin,\n            \"dragonfly\" => Os::DragonFlyBSD,\n            \"freebsd\" => Os::FreeBSD,\n            \"hurd\" => Os::Hurd,\n            \"illumos\" => Os::Illumos,\n            \"ios\" => Os::iOS,\n            \"js\" => Os::Js,\n            \"linux\" => Os::Linux,\n            \"nacl\" => Os::Nacl,\n            \"netbsd\" => Os::NetBSD,\n            \"openbsd\" => Os::OpenBSD,\n            \"plan9\" => Os::Plan9,\n            \"solaris\" => Os::Solaris,\n            \"windows\" => Os::Windows,\n            \"zos\" => Os::zOS,\n            name => Os::Other(name.to_owned()),\n        }\n    }\n}\n\nimpl Display for Os {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            Os::AIX => \"aix\",\n            Os::Android => \"android\",\n            Os::Darwin => \"darwin\",\n            Os::DragonFlyBSD => \"dragonfly\",\n            Os::FreeBSD => \"freebsd\",\n            Os::Hurd => \"hurd\",\n            Os::Illumos => \"illumos\",\n            Os::iOS => \"ios\",\n            Os::Js => \"js\",\n            Os::Linux => \"linux\",\n            Os::Nacl => \"nacl\",\n            Os::NetBSD => \"netbsd\",\n            Os::OpenBSD => \"openbsd\",\n            Os::Plan9 => \"plan9\",\n            Os::Solaris => \"solaris\",\n            Os::Windows => \"windows\",\n            Os::zOS => \"zos\",\n            Os::Other(name) => name,\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\nimpl Serialize for Os {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        let os = format!(\"{self}\");\n        os.serialize(serializer)\n    }\n}\n\nimpl<'de> Deserialize<'de> for Os {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let os = String::deserialize(deserializer)?;\n        Ok(os.as_str().into())\n    }\n}\n\nimpl Default for Os {\n    fn default() -> Self {\n        Os::from(std::env::consts::OS)\n    }\n}\n\n/// Name of the CPU target architecture.\n#[derive(Clone, Debug, PartialEq, Eq)]\npub enum Arch {\n    /// 32 bit x86, little-endian\n    #[allow(non_camel_case_types)]\n    i386,\n    /// 64 bit x86, little-endian\n    Amd64,\n    /// 64 bit x86 with 32 bit pointers, little-endian\n    Amd64p32,\n    /// 32 bit ARM, little-endian\n    ARM,\n    /// 32 bit ARM, big-endian\n    ARMbe,\n    /// 64 bit ARM, little-endian\n    ARM64,\n    /// 64 bit ARM, big-endian\n    ARM64be,\n    /// 64 bit Loongson RISC CPU, little-endian\n    LoongArch64,\n    /// 32 bit Mips, big-endian\n    Mips,\n    /// 32 bit Mips, little-endian\n    Mipsle,\n    /// 64 bit Mips, big-endian\n    Mips64,\n    /// 64 bit Mips, little-endian\n    Mips64le,\n    /// 64 bit Mips with 32 bit pointers, big-endian\n    Mips64p32,\n    /// 64 bit Mips with 32 bit pointers, little-endian\n    Mips64p32le,\n    /// 32 bit PowerPC, big endian\n    PowerPC,\n    /// 64 bit PowerPC, big-endian\n    PowerPC64,\n    /// 64 bit PowerPC, little-endian\n    PowerPC64le,\n    /// 32 bit RISC-V, little-endian\n    RISCV,\n    /// 64 bit RISC-V, little-endian\n    RISCV64,\n    /// 32 bit IBM System/390, big-endian\n    #[allow(non_camel_case_types)]\n    s390,\n    /// 64 bit IBM System/390, big-endian\n    #[allow(non_camel_case_types)]\n    s390x,\n    /// 32 bit SPARC, big-endian\n    SPARC,\n    /// 64 bit SPARC, bi-endian\n    SPARC64,\n    /// 32 bit Web Assembly\n    Wasm,\n    /// Architecture not specified by OCI image format\n    Other(String),\n}\n\nimpl Display for Arch {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let print = match self {\n            Arch::i386 => \"386\",\n            Arch::Amd64 => \"amd64\",\n            Arch::Amd64p32 => \"amd64p32\",\n            Arch::ARM => \"arm\",\n            Arch::ARMbe => \"armbe\",\n            Arch::ARM64 => \"arm64\",\n            Arch::ARM64be => \"arm64be\",\n            Arch::LoongArch64 => \"loong64\",\n            Arch::Mips => \"mips\",\n            Arch::Mipsle => \"mipsle\",\n            Arch::Mips64 => \"mips64\",\n            Arch::Mips64le => \"mips64le\",\n            Arch::Mips64p32 => \"mips64p32\",\n            Arch::Mips64p32le => \"mips64p32le\",\n            Arch::PowerPC => \"ppc\",\n            Arch::PowerPC64 => \"ppc64\",\n            Arch::PowerPC64le => \"ppc64le\",\n            Arch::RISCV => \"riscv\",\n            Arch::RISCV64 => \"riscv64\",\n            Arch::s390 => \"s390\",\n            Arch::s390x => \"s390x\",\n            Arch::SPARC => \"sparc\",\n            Arch::SPARC64 => \"sparc64\",\n            Arch::Wasm => \"wasm\",\n            Arch::Other(arch) => arch,\n        };\n\n        write!(f, \"{print}\")\n    }\n}\n\nimpl From<&str> for Arch {\n    fn from(arch: &str) -> Self {\n        match arch {\n            \"386\" => Arch::i386,\n            \"amd64\" => Arch::Amd64,\n            \"amd64p32\" => Arch::Amd64p32,\n            \"arm\" => Arch::ARM,\n            \"armbe\" => Arch::ARM64be,\n            \"arm64\" => Arch::ARM64,\n            \"arm64be\" => Arch::ARM64be,\n            \"loong64\" => Arch::LoongArch64,\n            \"mips\" => Arch::Mips,\n            \"mipsle\" => Arch::Mipsle,\n            \"mips64\" => Arch::Mips64,\n            \"mips64le\" => Arch::Mips64le,\n            \"mips64p32\" => Arch::Mips64p32,\n            \"mips64p32le\" => Arch::Mips64p32le,\n            \"ppc\" => Arch::PowerPC,\n            \"ppc64\" => Arch::PowerPC64,\n            \"ppc64le\" => Arch::PowerPC64le,\n            \"riscv\" => Arch::RISCV,\n            \"riscv64\" => Arch::RISCV64,\n            \"s390\" => Arch::s390,\n            \"s390x\" => Arch::s390x,\n            \"sparc\" => Arch::SPARC,\n            \"sparc64\" => Arch::SPARC64,\n            \"wasm\" => Arch::Wasm,\n            arch => Arch::Other(arch.to_owned()),\n        }\n    }\n}\n\nimpl Serialize for Arch {\n    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>\n    where\n        S: serde::Serializer,\n    {\n        let arch = format!(\"{self}\");\n        arch.serialize(serializer)\n    }\n}\n\nimpl<'de> Deserialize<'de> for Arch {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: serde::Deserializer<'de>,\n    {\n        let arch = String::deserialize(deserializer)?;\n        Ok(arch.as_str().into())\n    }\n}\n\nimpl Default for Arch {\n    fn default() -> Self {\n        // Translate from the Rust architecture names to the Go versions.\n        // It seems like the Rust ones are the same GNU/Linux...except for `powerpc64` and not `ppc64le`?\n        // This list just contains exceptions, everything else is passed through literally.\n        // See also https://github.com/containerd/containerd/blob/140ecc9247386d3be21616fe285021c081f4ea08/platforms/database.go\n        let goarch = match std::env::consts::ARCH {\n            \"x86_64\" => \"amd64\",\n            \"aarch64\" => \"arm64\",\n            \"powerpc64\" if cfg!(target_endian = \"big\") => \"ppc64\",\n            \"powerpc64\" if cfg!(target_endian = \"little\") => \"ppc64le\",\n            o => o,\n        };\n        Arch::from(goarch)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_arch_translation() {\n        let a = Arch::default();\n        // If you hit this, please update the mapping above.\n        if let Arch::Other(o) = a {\n            panic!(\"Architecture {o} not mapped between Rust and OCI\")\n        }\n    }\n\n    #[test]\n    fn test_asref() {\n        // This just spot checks a few conversions\n        assert_eq!(\n            MediaType::ImageConfig.as_ref(),\n            \"application/vnd.oci.image.config.v1+json\"\n        );\n        assert_eq!(\n            String::from(MediaType::ImageConfig).as_str(),\n            \"application/vnd.oci.image.config.v1+json\"\n        );\n    }\n}\n"
  },
  {
    "path": "src/image/oci_layout.rs",
    "content": "use crate::{\n    error::{OciSpecError, Result},\n    from_file, from_reader, to_file, to_string, to_writer,\n};\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    io::{Read, Write},\n    path::Path,\n};\n\n#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// The oci layout JSON object serves as a marker for the base of an Open Container Image Layout\n/// and to provide the version of the image-layout in use. The imageLayoutVersion value will align\n/// with the OCI Image Specification version at the time changes to the layout are made, and will\n/// pin a given version until changes to the image layout are required.\npub struct OciLayout {\n    /// This REQUIRED property specifies the image layout version.\n    #[getset(get = \"pub\", set = \"pub\")]\n    image_layout_version: String,\n}\n\nimpl OciLayout {\n    /// Attempts to load an oci layout from a file.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io](crate::OciSpecError::Io)\n    /// if the file does not exist or an\n    /// [OciSpecError::SerDe](crate::OciSpecError::SerDe) if the oci layout\n    /// cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// ```\n    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<OciLayout> {\n        from_file(path)\n    }\n\n    /// Attempts to load an oci layout from a stream.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe)\n    /// if the oci layout cannot be deserialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    /// use std::fs::File;\n    ///\n    /// let reader = File::open(\"oci-layout\").unwrap();\n    /// let oci_layout = OciLayout::from_reader(reader).unwrap();\n    /// ```\n    pub fn from_reader<R: Read>(reader: R) -> Result<OciLayout> {\n        from_reader(reader)\n    }\n\n    /// Attempts to write an oci layout to a file as JSON. If the file already exists, it\n    /// will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// oci_layout.to_file(\"oci-layout\").unwrap();\n    /// ```\n    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, false)\n    }\n\n    /// Attempts to write an oci layout to a file as pretty printed JSON. If the file\n    /// already exists, it will be overwritten.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// oci_layout.to_file_pretty(\"my-oci-layout\").unwrap();\n    /// ```\n    pub fn to_file_pretty<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        to_file(&self, path, true)\n    }\n\n    /// Attempts to write an oci layout to a stream as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// let mut writer = Vec::new();\n    /// oci_layout.to_writer(&mut writer);\n    /// ```\n    pub fn to_writer<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, false)\n    }\n\n    /// Attempts to write an oci layout to a stream as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// let mut writer = Vec::new();\n    /// oci_layout.to_writer_pretty(&mut writer);\n    /// ```\n    pub fn to_writer_pretty<W: Write>(&self, writer: &mut W) -> Result<()> {\n        to_writer(&self, writer, true)\n    }\n\n    /// Attempts to write an oci layout to a string as JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// let json_str = oci_layout.to_string().unwrap();\n    /// ```\n    pub fn to_string(&self) -> Result<String> {\n        to_string(&self, false)\n    }\n\n    /// Attempts to write an oci layout to a string as pretty printed JSON.\n    /// # Errors\n    /// This function will return an [OciSpecError::SerDe](crate::OciSpecError::SerDe) if\n    /// the oci layout configuration cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::image::OciLayout;\n    ///\n    /// let oci_layout = OciLayout::from_file(\"oci-layout\").unwrap();\n    /// let json_str = oci_layout.to_string_pretty().unwrap();\n    /// ```\n    pub fn to_string_pretty(&self) -> Result<String> {\n        to_string(&self, true)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{fs, path::PathBuf};\n\n    use super::*;\n\n    fn create_oci_layout() -> OciLayout {\n        OciLayoutBuilder::default()\n            .image_layout_version(\"lorem ipsum\")\n            .build()\n            .expect(\"build oci layout\")\n    }\n\n    fn get_oci_layout_path() -> PathBuf {\n        PathBuf::from(env!(\"CARGO_MANIFEST_DIR\")).join(\"test/data/oci-layout\")\n    }\n\n    #[test]\n    fn load_oci_layout_from_file() {\n        // arrange\n        let oci_layout_path = get_oci_layout_path();\n\n        // act\n        let actual = OciLayout::from_file(oci_layout_path).expect(\"from file\");\n\n        // assert\n        let expected = create_oci_layout();\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn load_oci_layout_from_reader() {\n        // arrange\n        let reader = fs::read(get_oci_layout_path()).expect(\"read oci-layout\");\n\n        // act\n        let actual = OciLayout::from_reader(&*reader).expect(\"from reader\");\n\n        // assert\n        let expected = create_oci_layout();\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_oci_layout_to_file() {\n        // arrange\n        let tmp = std::env::temp_dir().join(\"save_oci_layout_to_file\");\n        fs::create_dir_all(&tmp).expect(\"create test directory\");\n        let oci_layout = create_oci_layout();\n        let oci_layout_path = tmp.join(\"oci-layout\");\n\n        // act\n        oci_layout\n            .to_file_pretty(&oci_layout_path)\n            .expect(\"write oci-layout to file\");\n\n        // assert\n        let actual = fs::read_to_string(oci_layout_path).expect(\"read actual\");\n        let expected = fs::read_to_string(get_oci_layout_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_oci_layout_to_writer() {\n        // arrange\n        let mut actual = Vec::new();\n        let oci_layout = create_oci_layout();\n\n        // act\n        oci_layout.to_writer_pretty(&mut actual).expect(\"to writer\");\n\n        // assert\n        let expected = fs::read(get_oci_layout_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n\n    #[test]\n    fn save_oci_layout_to_string() {\n        // arrange\n        let oci_layout = create_oci_layout();\n\n        // act\n        let actual = oci_layout.to_string_pretty().expect(\"to string\");\n\n        // assert\n        let expected = fs::read_to_string(get_oci_layout_path()).expect(\"read expected\");\n        assert_eq!(actual, expected);\n    }\n}\n"
  },
  {
    "path": "src/image/version.rs",
    "content": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality in a backwards-compatible manner\npub const VERSION_MINOR: u32 = 0;\n\n/// Backwards-compatible bug fixes.\npub const VERSION_PATCH: u32 = 1;\n\n/// Indicates development branch. Releases will be empty string.\npub const VERSION_DEV: &str = \"-dev\";\n\n/// Retrieve the version as static str representation.\npub const VERSION: &str = formatcp!(\"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_DEV}\");\n\n/// Retrieve the version as string representation.\n///\n/// Use [`VERSION`] instead.\n#[deprecated]\npub fn version() -> String {\n    VERSION.to_owned()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    #[allow(deprecated)]\n    fn version_test() {\n        assert_eq!(version(), \"1.0.1-dev\".to_string())\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "#![deny(missing_docs, warnings)]\n#![doc = include_str!(\"../README.md\")]\n#![allow(clippy::too_long_first_doc_paragraph)]\n\n#[cfg(feature = \"distribution\")]\npub mod distribution;\nmod error;\n#[cfg(feature = \"image\")]\npub mod image;\n#[cfg(feature = \"runtime\")]\npub mod runtime;\n\nuse std::{\n    fs::{self, OpenOptions},\n    io::{Read, Write},\n    path::Path,\n};\n\nuse serde::{de::DeserializeOwned, Serialize};\n\npub use error::*;\n\nfn from_file<P: AsRef<Path>, T: DeserializeOwned>(path: P) -> Result<T> {\n    let path = path.as_ref();\n    let manifest_file = std::io::BufReader::new(fs::File::open(path)?);\n    let manifest = serde_json::from_reader(manifest_file)?;\n    Ok(manifest)\n}\n\nfn from_reader<R: Read, T: DeserializeOwned>(reader: R) -> Result<T> {\n    let manifest = serde_json::from_reader(reader)?;\n    Ok(manifest)\n}\n\nfn to_file<P: AsRef<Path>, T: Serialize>(item: &T, path: P, pretty: bool) -> Result<()> {\n    let path = path.as_ref();\n    let file = OpenOptions::new()\n        .write(true)\n        .create(true)\n        .truncate(true)\n        .open(path)?;\n    let file = std::io::BufWriter::new(file);\n\n    match pretty {\n        true => serde_json::to_writer_pretty(file, item)?,\n        false => serde_json::to_writer(file, item)?,\n    }\n\n    Ok(())\n}\n\nfn to_writer<W: Write, T: Serialize>(item: &T, writer: &mut W, pretty: bool) -> Result<()> {\n    match pretty {\n        true => serde_json::to_writer_pretty(writer, item)?,\n        false => serde_json::to_writer(writer, item)?,\n    }\n\n    Ok(())\n}\n\nfn to_string<T: Serialize>(item: &T, pretty: bool) -> Result<String> {\n    Ok(match pretty {\n        true => serde_json::to_string_pretty(item)?,\n        false => serde_json::to_string(item)?,\n    })\n}\n\n// A generic helper for any Option containing a collection whose reference implements `IntoIterator` (e.g., Vec, HashMap).\nfn is_none_or_empty<C>(opt: &Option<C>) -> bool\nwhere\n    for<'a> &'a C: IntoIterator,\n{\n    opt.as_ref().is_none_or(|c| c.into_iter().next().is_none())\n}\n"
  },
  {
    "path": "src/runtime/capability.rs",
    "content": "use serde::{\n    de::{Deserializer, Error},\n    Deserialize, Serialize,\n};\nuse std::collections::HashSet;\n\nuse strum_macros::{Display, EnumString};\n\n/// Capabilities is a unique set of Capability values.\npub type Capabilities = HashSet<Capability>;\n\n#[derive(Clone, Copy, Debug, EnumString, Eq, Display, Hash, PartialEq, Serialize)]\n/// All available capabilities.\n///\n/// For the purpose of performing permission checks, traditional UNIX\n/// implementations distinguish two categories of processes: privileged\n/// processes (whose effective user ID is 0, referred to as superuser or root),\n/// and unprivileged processes (whose effective UID is nonzero). Privileged\n/// processes bypass all kernel permission checks, while unprivileged processes\n/// are subject to full permission checking based on the process's credentials\n/// (usually: effective UID, effective GID, and supplementary group list).\n///\n/// Starting with kernel 2.2, Linux divides the privileges traditionally\n/// associated with superuser into distinct units, known as capabilities, which\n/// can be independently enabled and disabled. Capabilities are a per-thread attribute.\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\npub enum Capability {\n    #[serde(rename = \"CAP_AUDIT_CONTROL\")]\n    /// Enable and disable kernel auditing; change auditing filter rules;\n    /// retrieve auditing status and filtering rules.\n    ///\n    /// _since Linux 2.6.11_\n    AuditControl,\n\n    #[serde(rename = \"CAP_AUDIT_READ\")]\n    /// Allow reading the audit log via multicast netlink socket.\n    ///\n    /// _since Linux 3.16_\n    AuditRead,\n\n    #[serde(rename = \"CAP_AUDIT_WRITE\")]\n    /// Write records to kernel auditing log.\n    ///\n    /// _since Linux 2.6.11_\n    AuditWrite,\n\n    #[serde(rename = \"CAP_BLOCK_SUSPEND\")]\n    /// Employ features that can block system suspend\n    /// ([epoll(7)](https://man7.org/linux/man-pages/man7/epoll.7.html)\n    /// **EPOLLWAKEUP**, `/proc/sys/wake_lock`).\n    ///\n    /// _since Linux 3.5_\n    BlockSuspend,\n\n    #[serde(rename = \"CAP_BPF\")]\n    /// Employ privileged BPF operations; see\n    /// [bpf(2)](https://man7.org/linux/man-pages/man2/bpf.2.html) and\n    /// [bpf-helpers(7)](https://man7.org/linux/man-pages/man7/bpf-helpers.7.html).\n    ///\n    /// This capability was added to separate out BPF functionality from the\n    /// overloaded **CAP_SYS_ADMIN** capability.\n    ///\n    /// _since Linux 5.8_\n    Bpf,\n\n    #[serde(rename = \"CAP_CHECKPOINT_RESTORE\")]\n    /// - update `/proc/sys/kernel/ns_last_pid` (see\n    ///   [pid_namespaces(7)](https://man7.org/linux/man-pages/man7/pid_namespaces.7.html))\n    /// - employ the set_tid feature of\n    ///   [clone3(2)](https://man7.org/linux/man-pages/man2/clone3.2.html)\n    /// - read the contents of the symbolic links in `/proc/[pid]/map_files` for\n    ///   other processes.\n    ///\n    /// This capability was added to separate out BPF functionality from the\n    /// overloaded **CAP_SYS_ADMIN** capability.\n    ///\n    /// _since Linux 5.9_\n    CheckpointRestore,\n\n    #[serde(rename = \"CAP_CHOWN\")]\n    /// Make arbitrary changes to file UIDs and GIDs (see\n    /// [chown(2)](https://man7.org/linux/man-pages/man2/chown.2.html)).\n    Chown,\n\n    #[serde(rename = \"CAP_DAC_OVERRIDE\")]\n    /// Bypass file read, write, and execute permission checks.\n    ///\n    /// (DAC is an abbreviation of \"discretionary access control\".)\n    DacOverride,\n\n    #[serde(rename = \"CAP_DAC_READ_SEARCH\")]\n    /// - bypass file read permission checks and directory read and execute\n    ///   permission checks\n    /// - invoke\n    ///   [open_by_handle_at(2)](https://man7.org/linux/man-pages/man2/open_by_handle_at.2.html)\n    /// - use the\n    ///   [linkat(2)](https://man7.org/linux/man-pages/man2/linkat.2.html)\n    ///   **AT_EMPTY_PATH** flag to create a link to a file referred to by a\n    ///   file descriptor.\n    DacReadSearch,\n\n    #[serde(rename = \"CAP_FOWNER\")]\n    /// - Bypass permission checks on operations that normally require the\n    ///   filesystem UID of the process to match the UID of the file (e.g.,\n    ///   [chmod(2)](https://man7.org/linux/man-pages/man2/chmod.2.html),\n    ///   [utime(2)](https://man7.org/linux/man-pages/man2/utime.2.html)),\n    ///   excluding those operations covered by **CAP_DAC_OVERRIDE** and\n    ///   **CAP_DAC_READ_SEARCH**\n    /// - set inode flags (see\n    ///   [ioctl_iflags(2)](https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html))\n    ///   on arbitrary files\n    /// - set Access Control Lists (ACLs) on arbitrary files\n    /// - ignore directory sticky bit on file deletion\n    /// - modify user extended attributes on sticky directory owned by any user\n    /// - specify **O_NOATIME** for arbitrary files in\n    ///   [open(2)](https://man7.org/linux/man-pages/man2/open.2.html) and\n    ///   [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html)\n    ///\n    /// Overrides all restrictions about allowed operations on files, where\n    /// file owner ID must be equal to the user ID, except where CAP_FSETID\n    /// is applicable. It doesn't override MAC and DAC restrictions.\n    Fowner,\n\n    #[serde(rename = \"CAP_FSETID\")]\n    /// - don't clear set-user-ID and set-group-ID mode bits when a file is\n    ///   modified\n    /// - set the set-group-ID bit for a file whose GID does not match the\n    ///   filesystem or any of the supplementary GIDs of the calling process\n    Fsetid,\n\n    #[serde(rename = \"CAP_IPC_LOCK\")]\n    /// - lock memory\n    ///   ([mlock(2)](https://man7.org/linux/man-pages/man2/mlock.2.html),\n    ///   [mlockall(2)](https://man7.org/linux/man-pages/man2/mlockall.2.html),\n    ///   [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html),\n    ///   [shmctl(2)](https://man7.org/linux/man-pages/man2/shmctl.2.html))\n    /// - allocate memory using huge pages\n    ///   ([memfd_create(2)](https://man7.org/linux/man-pages/man2/memfd_create.2.html)\n    ///   [mmap(2)](https://man7.org/linux/man-pages/man2/mmap.2.html),\n    ///   [shmctl(2)](https://man7.org/linux/man-pages/man2/shmctl.2.html))\n    IpcLock,\n\n    #[serde(rename = \"CAP_IPC_OWNER\")]\n    /// Bypass permission checks for operations on System V IPC objects.\n    IpcOwner,\n\n    #[serde(rename = \"CAP_KILL\")]\n    /// Bypass permission checks for sending signals (see\n    /// [kill(2)](https://man7.org/linux/man-pages/man2/kill.2.html)). This\n    /// includes use of the\n    /// [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    /// **KDSIGACCEPT** operation.\n    Kill,\n\n    #[serde(rename = \"CAP_LEASE\")]\n    /// Establish leases on arbitrary files (see\n    /// [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html)).\n    ///\n    /// _since Linux 2.4_\n    Lease,\n\n    #[serde(rename = \"CAP_LINUX_IMMUTABLE\")]\n    /// Set the **FS_APPEND_FL** and **FS_IMMUTABLE_FL** inode flags (see\n    /// [ioctl_iflags(2)](https://man7.org/linux/man-pages/man2/ioctl_iflags.2.html)).\n    LinuxImmutable,\n\n    #[serde(rename = \"CAP_MAC_ADMIN\")]\n    /// Allow MAC configuration or state changes.\n    ///\n    /// Implemented for the Smack Linux Security Module (LSM).\n    ///\n    /// _since Linux 2.6.25_\n    MacAdmin,\n\n    #[serde(rename = \"CAP_MAC_OVERRIDE\")]\n    /// Override Mandatory Access Control (MAC).\n    ///\n    /// Implemented for the Smack Linux Security Module (LSM).\n    ///\n    /// _since Linux 2.6.25_\n    MacOverride,\n\n    #[serde(rename = \"CAP_MKNOD\")]\n    /// Create special files using\n    /// [mknod(2)](https://man7.org/linux/man-pages/man2/mknod.2.html).\n    ///\n    /// _since Linux 2.4_\n    Mknod,\n\n    #[serde(rename = \"CAP_NET_ADMIN\")]\n    /// Perform various network-related operations:\n    /// - interface configuration\n    /// - administration of IP firewall, masquerading, and accounting\n    /// - modify routing tables\n    /// - bind to any address for transparent proxying\n    /// - set type-of-service (TOS)\n    /// - clear driver statistics\n    /// - set promiscuous mode\n    /// - enabling multicasting\n    /// - use\n    ///   [setsockopt(2)](https://man7.org/linux/man-pages/man2/setsockopt.2.html)\n    ///   to set the following socket options: **SO_DEBUG**, **SO_MARK**,\n    ///   **SO_PRIORITY** (for a priority outside the range 0 to 6),\n    ///   **SO_RCVBUFFORCE** and **SO_SNDBUFFORCE**\n    NetAdmin,\n\n    #[serde(rename = \"CAP_NET_BIND_SERVICE\")]\n    /// Bind a socket to Internet domain privileged ports (port numbers less\n    /// than 1024).\n    NetBindService,\n\n    #[serde(rename = \"CAP_NET_BROADCAST\")]\n    /// (Unused) Make socket broadcasts, and listen to multicasts.\n    NetBroadcast,\n\n    #[serde(rename = \"CAP_NET_RAW\")]\n    /// - use RAW and PACKET sockets\n    /// - bind to any address for transparent proxying\n    NetRaw,\n\n    #[serde(rename = \"CAP_PERFMON\")]\n    /// Employ various performance-monitoring mechanisms, including:\n    /// - call\n    ///   [perf_event_open(2)](https://man7.org/linux/man-pages/man2/perf_event_open.2.html)\n    /// - employ various BPF operations that have performance implications\n    ///\n    /// This capability was added to separate out performance monitoring\n    /// functionality from the overloaded **CAP_SYS_ADMIN** capability. See also\n    /// the kernel source file `Documentation/admin-guide/perf-security.rst`.\n    ///\n    /// _since Linux 5.8_\n    Perfmon,\n\n    #[serde(rename = \"CAP_SETGID\")]\n    /// - make arbitrary manipulations of process GIDs and supplementary GID list\n    /// - forge GID when passing socket credentials via UNIX domain sockets\n    /// - write a group ID mapping in a user namespace (see\n    ///   [user_namespaces(7)](https://man7.org/linux/man-pages/man7/user_namespaces.7.html))\n    Setgid,\n\n    #[serde(rename = \"CAP_SETFCAP\")]\n    /// Set arbitrary capabilities on a file.\n    ///\n    /// _since Linux 2.6.24_\n    Setfcap,\n\n    #[serde(rename = \"CAP_SETPCAP\")]\n    /// If file capabilities are supported (i.e., since LinuxIDMapping 2.6.24):\n    /// add any capability from the calling thread's bounding set to its\n    /// inheritable set; drop capabilities from the bounding set (via\n    /// [prctl(2)](https://man7.org/linux/man-pages/man2/prctl.2.html)\n    /// **PR_CAPBSET_DROP**); make changes to the `securebits` flags.\n    ///\n    /// If file capabilities are not supported (i.e., kernels before Linux\n    /// 2.6.24): grant or remove any capability in the caller's permitted\n    /// capability set to or from any other process. (This property of\n    /// **CAP_SETPCAP** is not available when the kernel is configured to\n    /// support file capabilities, since **CAP_SETPCAP** has entirely different\n    /// semantics for such kernels.)\n    Setpcap,\n\n    #[serde(rename = \"CAP_SETUID\")]\n    /// - make arbitrary manipulations of process UIDs\n    ///   ([setuid(2)](https://man7.org/linux/man-pages/man2/setuid.2.html),\n    ///   [setreuid(2)](https://man7.org/linux/man-pages/man2/setreuid.2.html),\n    ///   [setresuid(2)](https://man7.org/linux/man-pages/man2/setresuid.2.html),\n    ///   [setfsuid(2)](https://man7.org/linux/man-pages/man2/setfsuid.2.html))\n    /// - forge UID when passing socket credentials via UNIX domain sockets\n    /// - write a user ID mapping in a user namespace (see\n    ///   [user_namespaces(7)](https://man7.org/linux/man-pages/man7/user_namespaces.7.html))\n    Setuid,\n\n    #[serde(rename = \"CAP_SYS_ADMIN\")]\n    /// - perform a range of system administration operations including:\n    ///   [quotactl(2)](https://man7.org/linux/man-pages/man2/quotactl.2.html),\n    ///   [mount(2)](https://man7.org/linux/man-pages/man2/mount.2.html),\n    ///   [umount(2)](https://man7.org/linux/man-pages/man2/umount.2.html),\n    ///   [pivot_root(2)](https://man7.org/linux/man-pages/man2/pivot_root.2.html),\n    ///   [swapon(2)](https://man7.org/linux/man-pages/man2/swapon.2.html),\n    ///   [swapoff(2)](https://man7.org/linux/man-pages/man2/swapoff.2.html),\n    ///   [sethostname(2)](https://man7.org/linux/man-pages/man2/sethostname.2.html),\n    ///   and [setdomainname(2)](https://man7.org/linux/man-pages/man2/setdomainname.2.html)\n    /// - perform privileged\n    ///   [syslog(2)](https://man7.org/linux/man-pages/man2/syslog.2.html)\n    ///   operations (since Linux 2.6.37, **CAP_SYSLOG** should be used to\n    ///   permit such operations)\n    /// - perform **VM86_REQUEST_IRQ vm86**(2) command\n    /// - access the same checkpoint/restore functionality that is governed by\n    ///   **CAP_CHECKPOINT_RESTORE** (but the latter, weaker capability is\n    ///   preferred for accessing that functionality)\n    /// - perform the same BPF operations as are governed by **CAP_BPF** (but\n    ///   the latter, weaker capability is preferred for accessing that\n    ///   functionality).\n    /// - employ the same performance monitoring mechanisms as are governed by\n    ///   **CAP_PERFMON** (but the latter, weaker capability is preferred for\n    ///   accessing that functionality).\n    /// - perform **IPC_SET** and **IPC_RMID** operations on arbitrary System V\n    ///   IPC objects\n    /// - override **RLIMIT_NPROC** resource limit\n    /// - perform operations on `trusted` and `security` extended attributes\n    ///   (see [xattr(7)](https://man7.org/linux/man-pages/man7/xattr.7.html))\n    /// - use\n    ///   [lookup_dcookie(2)](https://man7.org/linux/man-pages/man2/lookup_dcookie.2.html)\n    /// - use\n    ///   [ioprio_set(2)](https://man7.org/linux/man-pages/man2/ioprio_set.2.html)\n    ///   to assign **IOPRIO_CLASS_RT** and (before Linux 2.6.25)\n    ///   **IOPRIO_CLASS_IDLE** I/O scheduling classes\n    /// - forge PID when passing socket credentials via UNIX domain sockets\n    /// - exceed `/proc/sys/fs/file-max`, the system-wide limit on the number of\n    ///   open files, in system calls that open files (e.g.,\n    ///   [accept(2)](https://man7.org/linux/man-pages/man2/accept.2.html),\n    ///   [execve(2)](https://man7.org/linux/man-pages/man2/execve.2.html),\n    ///   [open(2)](https://man7.org/linux/man-pages/man2/open.2.html),\n    ///   [pipe(2)](https://man7.org/linux/man-pages/man2/pipe.2.html))\n    /// - employ **CLONE_*** flags that create new namespaces with\n    ///   [clone(2)](https://man7.org/linux/man-pages/man2/clone.2.html) and\n    ///   [unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html)\n    ///   (but, since Linux 3.8, creating user namespaces does not require any\n    ///   capability)\n    /// - access privileged `perf` event information\n    /// - call [setns(2)](https://man7.org/linux/man-pages/man2/setns.2.html)\n    ///   (requires **CAP_SYS_ADMIN** in the `target` namespace)\n    /// - call\n    ///   [fanotify_init(2)](https://man7.org/linux/man-pages/man2/fanotify_init.2.html)\n    /// - perform privileged **KEYCTL_CHOWN** and **KEYCTL_SETPERM**\n    ///   [keyctl(2)](https://man7.org/linux/man-pages/man2/keyctl.2.html)\n    ///   operations\n    /// - perform\n    ///   [madvise(2)](https://man7.org/linux/man-pages/man2/madvise.2.html)\n    ///   **MADV_HWPOISON** operation\n    /// - employ the **TIOCSTI ioctl**(2) to insert characters into the input\n    ///   queue of a terminal other than the caller's controlling terminal\n    /// - employ the obsolete\n    ///   [nfsservctl(2)](https://man7.org/linux/man-pages/man2/nfsservctl.2.html)\n    ///   system call\n    /// - employ the obsolete\n    ///   [bdflush(2)](https://man7.org/linux/man-pages/man2/bdflush.2.html)\n    ///   system call\n    /// - perform various privileged block-device\n    ///   [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    ///   operations\n    /// - perform various privileged filesystem\n    ///   [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    ///   operations\n    /// - perform privileged\n    ///   [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    ///   operations on the `/dev/random` device (see\n    ///   [random(4)](https://man7.org/linux/man-pages/man4/random.4.html))\n    /// - install a\n    ///   [seccomp(2)](https://man7.org/linux/man-pages/man2/seccomp.2.html)\n    ///   filter without first having to set the `no_new_privs` thread attribute\n    /// - modify allow/deny rules for device control groups\n    /// - employ the\n    ///   [ptrace(2)](https://man7.org/linux/man-pages/man2/ptrace.2.html)\n    ///   **PTRACE_SECCOMP_GET_FILTER** operation to dump tracee's seccomp\n    ///   filters\n    /// - employ the\n    ///   [ptrace(2)](https://man7.org/linux/man-pages/man2/ptrace.2.html)\n    ///   **PTRACE_SETOPTIONS** operation to suspend the tracee's seccomp\n    ///   protections (i.e., the **PTRACE_O_SUSPEND_SECCOMP** flag)\n    /// - perform administrative operations on many device drivers\n    /// - modify autogroup nice values by writing to `/proc/[pid]/autogroup`\n    ///   (see [sched(7)](https://man7.org/linux/man-pages/man7/sched.7.html))\n    SysAdmin,\n\n    #[serde(rename = \"CAP_SYS_BOOT\")]\n    /// Use [reboot(2)](https://man7.org/linux/man-pages/man2/reboot.2.html) and\n    /// [kexec_load(2)](https://man7.org/linux/man-pages/man2/kexec_load.2.html).\n    SysBoot,\n\n    #[serde(rename = \"CAP_SYS_CHROOT\")]\n    /// - use [chroot(2)](https://man7.org/linux/man-pages/man2/chroot.2.html)\n    /// - change mount namespaces using\n    ///   [setns(2)](https://man7.org/linux/man-pages/man2/setns.2.html)\n    SysChroot,\n\n    #[serde(rename = \"CAP_SYS_MODULE\")]\n    /// - load and unload kernel modules (see\n    ///   [init_module(2)](https://man7.org/linux/man-pages/man2/init_module.2.html)\n    ///   and\n    ///   [delete_module(2)](https://man7.org/linux/man-pages/man2/delete_module.2.html))\n    /// - in kernels before 2.6.25: drop capabilities from the system-wide\n    ///   capability bounding set\n    SysModule,\n\n    #[serde(rename = \"CAP_SYS_NICE\")]\n    /// - lower the process nice value\n    ///   ([nice(2)](https://man7.org/linux/man-pages/man2/nice.2.html),\n    ///   [setpriority(2)](https://man7.org/linux/man-pages/man2/setpriority.2.html))\n    ///   and change the nice value for arbitrary processes\n    /// - set real-time scheduling policies for calling process, and set\n    ///   scheduling policies and priorities for arbitrary processes\n    ///   ([sched_setscheduler(2)](https://man7.org/linux/man-pages/man2/sched_setscheduler.2.html),\n    ///   [sched_setparam(2)](https://man7.org/linux/man-pages/man2/sched_setparam.2.html),\n    ///   [sched_setattr(2)](https://man7.org/linux/man-pages/man2/sched_setattr.2.html))\n    /// - set CPU affinity for arbitrary processes\n    ///   ([sched_setaffinity(2)](https://man7.org/linux/man-pages/man2/sched_setaffinity.2.html))\n    /// - set I/O scheduling class and priority for arbitrary processes\n    ///   ([ioprio_set(2)](https://man7.org/linux/man-pages/man2/ioprio_set.2.html))\n    /// - apply\n    ///   [migrate_pages(2)](https://man7.org/linux/man-pages/man2/migrate_pages.2.html)\n    ///   to arbitrary processes and allow processes to be migrated to arbitrary\n    ///   nodes\n    /// - apply\n    ///   [move_pages(2)](https://man7.org/linux/man-pages/man2/move_pages.2.html)\n    ///   to arbitrary processes\n    /// - use the **MPOL_MF_MOVE_ALL** flag with\n    ///   [mbind(2)](https://man7.org/linux/man-pages/man2/mbind.2.html) and\n    ///   [move_pages(2)](https://man7.org/linux/man-pages/man2/move_pages.2.html)\n    SysNice,\n\n    #[serde(rename = \"CAP_SYS_PACCT\")]\n    /// Use [acct(2)](https://man7.org/linux/man-pages/man2/acct.2.html).\n    SysPacct,\n\n    #[serde(rename = \"CAP_SYS_PTRACE\")]\n    /// - trace arbitrary processes using\n    ///   [ptrace(2)](https://man7.org/linux/man-pages/man2/ptrace.2.html)\n    /// - apply\n    ///   [get_robust_list(2)](https://man7.org/linux/man-pages/man2/get_robust_list.2.html)\n    ///   to arbitrary processes\n    /// - transfer data to or from the memory of arbitrary processes using\n    ///   [process_vm_readv(2)](https://man7.org/linux/man-pages/man2/process_vm_readv.2.html)\n    ///   and\n    ///   [process_vm_writev(2)](https://man7.org/linux/man-pages/man2/process_vm_writev.2.html)\n    /// - inspect processes using\n    ///   [kcmp(2)](https://man7.org/linux/man-pages/man2/kcmp.2.html)\n    SysPtrace,\n\n    #[serde(rename = \"CAP_SYS_RAWIO\")]\n    /// - perform I/O port operations\n    ///   ([iopl(2)](https://man7.org/linux/man-pages/man2/iopl.2.html) and\n    ///   [ioperm(2)](https://man7.org/linux/man-pages/man2/ioperm.2.html));\n    /// - access `/proc/kcore`\n    /// - employ the **FIBMAP ioctl**(2) operation\n    /// - open devices for accessing x86 model-specific registers (MSRs, see\n    ///   [msr(4)](https://man7.org/linux/man-pages/man4/msr.4.html))\n    /// - update `/proc/sys/vm/mmap_min_addr`\n    /// - create memory mappings at addresses below the value specified by\n    ///   `/proc/sys/vm/mmap_min_addr`\n    /// - map files in `/proc/bus/pci`\n    /// - open `/dev/mem` and `/dev/kmem`\n    /// - perform various SCSI device commands\n    /// - perform certain operations on\n    ///   [hpsa(4)](https://man7.org/linux/man-pages/man4/hpsa.4.html) and\n    ///   [cciss(4)](https://man7.org/linux/man-pages/man4/cciss.4.html) devices\n    /// - perform a range of device-specific operations on other devices\n    SysRawio,\n\n    #[serde(rename = \"CAP_SYS_RESOURCE\")]\n    /// - use reserved space on ext2 filesystems\n    /// - make [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    ///   calls controlling ext3 journaling\n    /// - override disk quota limits\n    /// - increase resource limits (see\n    ///   [setrlimit(2)](https://man7.org/linux/man-pages/man2/setrlimit.2.html))\n    /// - override **RLIMIT_NPROC** resource limit\n    /// - override maximum number of consoles on console allocation\n    /// - override maximum number of keymaps\n    /// - allow more than 64hz interrupts from the real-time clock\n    /// - raise `msg_qbytes` limit for a System V message queue above the limit\n    ///   in `/proc/sys/kernel/msgmnb` (see\n    ///   [msgop(2)](https://man7.org/linux/man-pages/man2/msgop.2.html) and\n    ///   [msgctl(2)](https://man7.org/linux/man-pages/man2/msgctl.2.html))\n    /// - allow the **RLIMIT_NOFILE** resource limit on the number of\n    ///   \"in-flight\" file descriptors to be bypassed when passing file\n    ///   descriptors to another process via a UNIX domain socket (see\n    ///   [unix(7)](https://man7.org/linux/man-pages/man7/unix.7.html));\n    /// - override the `/proc/sys/fs/pipe-size-max` limit when setting the\n    ///   capacity of a pipe using the **F_SETPIPE_SZ**\n    ///   [fcntl(2)](https://man7.org/linux/man-pages/man2/fcntl.2.html) command\n    /// - use **F_SETPIPE_SZ** to increase the capacity of a pipe above the\n    ///   limit specified by `/proc/sys/fs/pipe-max-size`\n    /// - override `/proc/sys/fs/mqueue/queues_max`,\n    ///   `/proc/sys/fs/mqueue/msg_max` and `/proc/sys/fs/mqueue/msgsize_max`\n    ///   limits when creating POSIX message queues (see\n    ///   [mq_overview(7)](https://man7.org/linux/man-pages/man7/mq_overview.7.html))\n    /// - employ the\n    ///   [prctl(2)](https://man7.org/linux/man-pages/man2/prctl.2.html)\n    ///   **PR_SET_MM** operation\n    /// - set `/proc/[pid]/oom_score_adj` to a value lower than the value last\n    ///   set by a process with **CAP_SYS_RESOURCE**\n    SysResource,\n\n    #[serde(rename = \"CAP_SYS_TIME\")]\n    /// - set system clock\n    ///   ([settimeofday(2)](https://man7.org/linux/man-pages/man2/settimeofday.2.html),\n    ///   [stime(2)](https://man7.org/linux/man-pages/man2/stime.2.html),\n    ///   [adjtimex(2)](https://man7.org/linux/man-pages/man2/adjtimex.2.html))\n    /// - set real-time (hardware) clock\n    SysTime,\n\n    #[serde(rename = \"CAP_SYS_TTY_CONFIG\")]\n    /// - use\n    ///   [vhangup(2)](https://man7.org/linux/man-pages/man2/vhangup.2.html)\n    /// - employ various privileged\n    ///   [ioctl(2)](https://man7.org/linux/man-pages/man2/ioctl.2.html)\n    ///   operations on virtual terminals\n    SysTtyConfig,\n\n    #[serde(rename = \"CAP_SYSLOG\")]\n    /// - perform privileged\n    ///   [syslog(2)](https://man7.org/linux/man-pages/man2/syslog.2.html)\n    ///   operations. See\n    ///   [syslog(2)](https://man7.org/linux/man-pages/man2/syslog.2.html) for\n    ///   information on which operations require privilege.\n    /// - view kernel addresses exposed via `/proc` and other interfaces when\n    ///   `/proc/sys/kernel/kptr_restrict` has the value 1. (See the discussion\n    ///   of the `kptr_restrict` in\n    ///   [proc(5)](https://man7.org/linux/man-pages/man5/proc.5.html).)\n    ///\n    /// _since Linux 2.6.37_\n    Syslog,\n\n    #[serde(rename = \"CAP_WAKE_ALARM\")]\n    /// Trigger something that will wake up the system (set\n    /// **CLOCK_REALTIME_ALARM** and **CLOCK_BOOTTIME_ALARM** timers).\n    /// _since Linux 3.0_\n    WakeAlarm,\n}\n\nimpl<'de> Deserialize<'de> for Capability {\n    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>\n    where\n        D: Deserializer<'de>,\n    {\n        let input = String::deserialize(deserializer)?;\n        let upper = input.to_uppercase();\n\n        // Extract the capability name from various input formats.\n        //\n        // This function strips all \"CAP_\" prefixes to normalize capability names.\n        // This ensures correct adaptation regardless of whether users specify\n        // CAP_XXX or XXX directly at the upper layer(Specially k8s), making the\n        // API more flexible and user-friendly.\n        // Examples: \"CAP_SYS_ADMIN\", \"SYS_ADMIN\"\n        //\n        let mut clean_cap = upper.as_str();\n        while let Some(stripped) = clean_cap.strip_prefix(\"CAP_\") {\n            clean_cap = stripped;\n        }\n        match clean_cap {\n            \"AUDIT_CONTROL\" => Ok(Self::AuditControl),\n            \"AUDIT_READ\" => Ok(Self::AuditRead),\n            \"AUDIT_WRITE\" => Ok(Self::AuditWrite),\n            \"BLOCK_SUSPEND\" => Ok(Self::BlockSuspend),\n            \"BPF\" => Ok(Self::Bpf),\n            \"CHECKPOINT_RESTORE\" => Ok(Self::CheckpointRestore),\n            \"CHOWN\" => Ok(Self::Chown),\n            \"DAC_OVERRIDE\" => Ok(Self::DacOverride),\n            \"DAC_READ_SEARCH\" => Ok(Self::DacReadSearch),\n            \"FOWNER\" => Ok(Self::Fowner),\n            \"FSETID\" => Ok(Self::Fsetid),\n            \"IPC_LOCK\" => Ok(Self::IpcLock),\n            \"IPC_OWNER\" => Ok(Self::IpcOwner),\n            \"KILL\" => Ok(Self::Kill),\n            \"LEASE\" => Ok(Self::Lease),\n            \"LINUX_IMMUTABLE\" => Ok(Self::LinuxImmutable),\n            \"MAC_ADMIN\" => Ok(Self::MacAdmin),\n            \"MAC_OVERRIDE\" => Ok(Self::MacOverride),\n            \"MKNOD\" => Ok(Self::Mknod),\n            \"NET_ADMIN\" => Ok(Self::NetAdmin),\n            \"NET_BIND_SERVICE\" => Ok(Self::NetBindService),\n            \"NET_BROADCAST\" => Ok(Self::NetBroadcast),\n            \"NET_RAW\" => Ok(Self::NetRaw),\n            \"PERFMON\" => Ok(Self::Perfmon),\n            \"SETGID\" => Ok(Self::Setgid),\n            \"SETFCAP\" => Ok(Self::Setfcap),\n            \"SETPCAP\" => Ok(Self::Setpcap),\n            \"SETUID\" => Ok(Self::Setuid),\n            \"SYS_ADMIN\" => Ok(Self::SysAdmin),\n            \"SYS_BOOT\" => Ok(Self::SysBoot),\n            \"SYS_CHROOT\" => Ok(Self::SysChroot),\n            \"SYS_MODULE\" => Ok(Self::SysModule),\n            \"SYS_NICE\" => Ok(Self::SysNice),\n            \"SYS_PACCT\" => Ok(Self::SysPacct),\n            \"SYS_PTRACE\" => Ok(Self::SysPtrace),\n            \"SYS_RAWIO\" => Ok(Self::SysRawio),\n            \"SYS_RESOURCE\" => Ok(Self::SysResource),\n            \"SYS_TIME\" => Ok(Self::SysTime),\n            \"SYS_TTY_CONFIG\" => Ok(Self::SysTtyConfig),\n            \"SYSLOG\" => Ok(Self::Syslog),\n            \"WAKE_ALARM\" => Ok(Self::WakeAlarm),\n            other => Err(Error::custom(format!(\n                \"no variant for {input} (converted to {other})\",\n            ))),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::error::Result;\n\n    #[test]\n    fn serialize() {\n        let chown = Capability::Chown;\n        let res = serde_json::to_string(&chown).expect(\"unable to serialize\");\n        assert_eq!(\"\\\"CAP_CHOWN\\\"\", res);\n    }\n\n    #[test]\n    fn deserialize() -> Result<()> {\n        for case in &[\"SYSLOG\", \"CAP_SYSLOG\", \"cap_SYSLOG\", \"sySloG\"] {\n            let res: Capability = serde_json::from_str(&format!(\"\\\"{case}\\\"\"))?;\n            assert_eq!(Capability::Syslog, res);\n        }\n        Ok(())\n    }\n\n    #[test]\n    fn capabilities() -> Result<()> {\n        let res: Capabilities = serde_json::from_str(\n            r#\"[\n                \"syslog\",\n                \"SYSLOG\",\n                \"chown\",\n                \"cap_chown\"\n            ]\"#,\n        )?;\n        assert_eq!(res.len(), 2);\n        assert!(res.contains(&Capability::Syslog));\n        assert!(res.contains(&Capability::Chown));\n        Ok(())\n    }\n\n    #[test]\n    fn invalid_string2enum() {\n        let invalid_cap_str = \"INVALID_CAP\";\n        let unknown_cap = invalid_cap_str.parse::<Capability>();\n        assert!(unknown_cap.is_err());\n    }\n\n    #[test]\n    fn cap_enum_to_string() {\n        let cap = Capability::AuditControl;\n        assert_eq!(cap.to_string(), \"AUDIT_CONTROL\");\n\n        let cap = Capability::AuditRead;\n        assert_eq!(cap.to_string(), \"AUDIT_READ\");\n\n        let cap = Capability::SysAdmin;\n        assert_eq!(cap.to_string(), \"SYS_ADMIN\");\n    }\n\n    #[test]\n    fn cap_string_to_enum() {\n        let cap_str = \"AUDIT_CONTROL\";\n        let cap_enum: Capability = cap_str.parse().unwrap();\n        assert_eq!(cap_enum, Capability::AuditControl);\n\n        let cap_str = \"AUDIT_READ\";\n        let cap_enum: Capability = cap_str.parse().unwrap();\n        assert_eq!(cap_enum, Capability::AuditRead);\n\n        let cap_str = \"SYS_ADMIN\";\n        let cap_enum: Capability = cap_str.parse().unwrap();\n        assert_eq!(cap_enum, Capability::SysAdmin);\n    }\n\n    #[test]\n    fn test_serde_serialization() {\n        let cap = Capability::AuditControl;\n        let serialized = serde_json::to_string(&cap).unwrap();\n        assert_eq!(serialized, \"\\\"CAP_AUDIT_CONTROL\\\"\");\n\n        let cap = Capability::SysAdmin;\n        let serialized = serde_json::to_string(&cap).unwrap();\n        assert_eq!(serialized, \"\\\"CAP_SYS_ADMIN\\\"\");\n    }\n\n    #[test]\n    fn test_serde_deserialization() {\n        let serialized = \"\\\"CAP_AUDIT_CONTROL\\\"\";\n        let cap: Capability = serde_json::from_str(serialized).unwrap();\n        assert_eq!(cap, Capability::AuditControl);\n\n        let serialized = \"\\\"CAP_SYS_ADMIN\\\"\";\n        let cap: Capability = serde_json::from_str(serialized).unwrap();\n        assert_eq!(cap, Capability::SysAdmin);\n    }\n\n    #[test]\n    fn deserialize_one_more_cap_prefix() -> Result<()> {\n        for case in &[\"SYS_ADMIN\", \"CAP_CAP_SYS_ADMIN\", \"cap_CAP_cap_SYS_ADMIN\"] {\n            let res: Capability = serde_json::from_str(&format!(\"\\\"{case}\\\"\"))?;\n            assert_eq!(Capability::SysAdmin, res);\n        }\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "src/runtime/features.rs",
    "content": "use std::collections::HashMap;\n\nuse crate::{\n    error::OciSpecError,\n    runtime::{Arch, LinuxNamespaceType, LinuxSeccompAction},\n};\nuse derive_builder::Builder;\nuse getset::{Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\n\n/// Features represents supported features of the runtime.\n///\n/// This structure is used to report the supported features of the runtime to runtime callers.\n///\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Features {\n    /// The minimum OCI Runtime Spec version recognized by the runtime, e.g., \"1.0.0\".\n    oci_version_min: String,\n    /// The maximum OCI Runtime Spec version recognized by the runtime, e.g., \"1.0.2-dev\".\n    oci_version_max: String,\n    /// The list of the recognized hook names, e.g., \"createRuntime\".\n    /// \"None\" means \"unknown\", not \"no support for any hook\".\n    hooks: Option<Vec<String>>,\n    /// The list of the recognized mount options, e.g., \"ro\".\n    /// \"None\" means \"unknown\", not \"no support for any mount option\".\n    /// This list does not contain filesystem-specific options passed to mount(2) syscall as (const void *).\n    mount_options: Option<Vec<String>>,\n    /// Information specific to Linux\n    linux: Option<LinuxFeature>,\n    /// Implementation-specific annotation strings,\n    /// such as the implementation version, and third-party extensions.\n    annotations: Option<HashMap<String, String>>,\n    /// The list of the potential unsafe annotations\n    /// that may appear in `config.json`.\n    /// A value that ends with \".\" is interpreted as a prefix of annotations.\n    potentially_unsafe_config_annotations: Option<Vec<String>>,\n}\n\n/// Linux specific features.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct LinuxFeature {\n    /// The list of the recognized namespaces, e.g., \"mount\".\n    /// \"None\" means \"unknown\", not \"no support for any namespace\".\n    namespaces: Option<Vec<LinuxNamespaceType>>,\n    /// The list of the recognized capabilities , e.g., \"CAP_SYS_ADMIN\".\n    /// \"None\" means \"unknown\", not \"no support for any capability\".\n    capabilities: Option<Vec<String>>,\n    /// The available features related to cgroup.\n    cgroup: Option<Cgroup>,\n    /// The available features related to seccomp.\n    seccomp: Option<Seccomp>,\n    /// The available features related to apparmor.\n    apparmor: Option<Apparmor>,\n    /// The available features related to selinux.\n    selinux: Option<Selinux>,\n    /// The available features related to Intel RDT.\n    intel_rdt: Option<IntelRdt>,\n    /// The available features related to memory policy.\n    memory_policy: Option<MemoryPolicy>,\n    /// The available features related to mount extensions.\n    mount_extensions: Option<MountExtensions>,\n    /// The available features related to net devices.\n    net_devices: Option<NetDevices>,\n}\n\n/// Cgroup represents the \"cgroup\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Cgroup {\n    /// \"v1\" field represents whether Cgroup v1 support is compiled in.\n    /// Unrelated to whether the host uses cgroup v1 or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    v1: Option<bool>,\n    /// \"v2\" field represents whether Cgroup v2 support is compiled in.\n    /// Unrelated to whether the host uses cgroup v2 or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    v2: Option<bool>,\n    /// \"systemd\" field represents whether systemd-cgroup support is compiled in.\n    /// Unrelated to whether the host uses systemd or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    systemd: Option<bool>,\n    /// \"systemdUser\" field represents whether user-scoped systemd-cgroup support is compiled in.\n    /// Unrelated to whether the host uses systemd or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    systemd_user: Option<bool>,\n    /// \"rdma\" field represents whether RDMA cgroup support is compiled in.\n    /// Unrelated to whether the host supports it or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    rdma: Option<bool>,\n}\n\n/// Seccomp represents the \"seccomp\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Seccomp {\n    /// \"enabled\" field represents whether seccomp support is compiled in.\n    /// \"None\" means \"unknown\", not \"false\".\n    enabled: Option<bool>,\n    /// \"actions\" field represents the list of the recognized actions.\n    /// \"None\" means \"unknown\", not \"no support for any action\".\n    actions: Option<Vec<LinuxSeccompAction>>,\n    /// \"operators\" field represents the list of the recognized operators.\n    /// \"None\" means \"unknown\", not \"no support for any operator\".\n    operators: Option<Vec<String>>,\n    /// \"archs\" field represents the list of the recognized architectures.\n    /// \"None\" means \"unknown\", not \"no support for any architecture\".\n    archs: Option<Vec<Arch>>,\n    /// \"knownFlags\" field represents the list of the recognized filter flags.\n    /// \"None\" means \"unknown\", not \"no flags are recognized\".\n    known_flags: Option<Vec<String>>,\n    /// \"supportedFlags\" field represents the list of the supported filter flags.\n    /// This list may be a subset of the \"knownFlags\" due to some of unsupported flags\n    /// by the current kernel and/or libseccomp.\n    /// \"None\" means \"unknown\", not \"no flags are supported\".\n    supported_flags: Option<Vec<String>>,\n}\n\n/// Apparmor represents the \"apparmor\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Apparmor {\n    /// \"enabled\" field represents whether AppArmor support is compiled in.\n    /// Unrelated to whether the host supports AppArmor or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    enabled: Option<bool>,\n}\n\n/// Selinux represents the \"selinux\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Selinux {\n    /// \"enabled\" field represents whether SELinux support is compiled in.\n    /// Unrelated to whether the host supports SELinux or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    enabled: Option<bool>,\n}\n\n/// IntelRdt represents the \"intelRdt\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct IntelRdt {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// \"enabled\" field represents whether Intel RDT support is compiled in.\n    /// Unrelated to whether the host supports Intel RDT or not.\n    enabled: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Schemata is true if the \"linux.intelRdt.schemata\" field of the\n    /// spec is implemented.\n    schemata: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Monitoring is true if the \"linux.intelRdt.enableMonitoring\" field of the\n    /// spec is implemented.\n    /// None value means \"unknown\", not \"false\".\n    monitoring: Option<bool>,\n}\n\n/// MemoryPolicy represents the \"memoryPolicy\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct MemoryPolicy {\n    /// modes is the list of known memory policy modes, e.g., \"MPOL_INTERLEAVE\".\n    modes: Option<Vec<String>>,\n    /// flags is the list of known memory policy mode flags, e.g., \"MPOL_F_STATIC_NODES\".\n    flags: Option<Vec<String>>,\n}\n\n/// MountExtensions represents the \"mountExtensions\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct MountExtensions {\n    /// \"idMap\" field represents the ID mapping support.\n    idmap: Option<IDMap>,\n}\n\n/// NetDevices represents the \"netDevices\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct NetDevices {\n    /// \"enabled\" field represents whether Net Devices support is compiled in.\n    /// Unrelated to whether the host supports Net Devices or not.\n    enabled: Option<bool>,\n}\n\n/// IDMap represents the \"idmap\" field.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct IDMap {\n    /// \"enabled\" field represents whether idmap mounts supports is compiled in.\n    /// Unrelated to whether the host supports it or not.\n    /// \"None\" means \"unknown\", not \"false\".\n    enabled: Option<bool>,\n}\n\n#[cfg(test)]\nmod tests {\n    use std::ops::Deref;\n\n    use super::*;\n\n    #[test]\n    fn test_parse_features() {\n        let example_json = r#\"\n{\n    \"ociVersionMin\": \"1.0.0\",\n    \"ociVersionMax\": \"1.1.0-rc.2\",\n    \"hooks\": [\n        \"prestart\",\n        \"createRuntime\",\n        \"createContainer\",\n        \"startContainer\",\n        \"poststart\",\n        \"poststop\"\n    ],\n    \"mountOptions\": [\n        \"async\",\n        \"atime\",\n        \"bind\",\n        \"defaults\",\n        \"dev\",\n        \"diratime\",\n        \"dirsync\",\n        \"exec\",\n        \"iversion\",\n        \"lazytime\",\n        \"loud\",\n        \"mand\",\n        \"noatime\",\n        \"nodev\",\n        \"nodiratime\",\n        \"noexec\",\n        \"noiversion\",\n        \"nolazytime\",\n        \"nomand\",\n        \"norelatime\",\n        \"nostrictatime\",\n        \"nosuid\",\n        \"nosymfollow\",\n        \"private\",\n        \"ratime\",\n        \"rbind\",\n        \"rdev\",\n        \"rdiratime\",\n        \"relatime\",\n        \"remount\",\n        \"rexec\",\n        \"rnoatime\",\n        \"rnodev\",\n        \"rnodiratime\",\n        \"rnoexec\",\n        \"rnorelatime\",\n        \"rnostrictatime\",\n        \"rnosuid\",\n        \"rnosymfollow\",\n        \"ro\",\n        \"rprivate\",\n        \"rrelatime\",\n        \"rro\",\n        \"rrw\",\n        \"rshared\",\n        \"rslave\",\n        \"rstrictatime\",\n        \"rsuid\",\n        \"rsymfollow\",\n        \"runbindable\",\n        \"rw\",\n        \"shared\",\n        \"silent\",\n        \"slave\",\n        \"strictatime\",\n        \"suid\",\n        \"symfollow\",\n        \"sync\",\n        \"tmpcopyup\",\n        \"unbindable\"\n    ],\n    \"linux\": {\n        \"namespaces\": [\n            \"cgroup\",\n            \"ipc\",\n            \"mount\",\n            \"network\",\n            \"pid\",\n            \"user\",\n            \"uts\"\n        ],\n        \"capabilities\": [\n            \"CAP_CHOWN\",\n            \"CAP_DAC_OVERRIDE\",\n            \"CAP_DAC_READ_SEARCH\",\n            \"CAP_FOWNER\",\n            \"CAP_FSETID\",\n            \"CAP_KILL\",\n            \"CAP_SETGID\",\n            \"CAP_SETUID\",\n            \"CAP_SETPCAP\",\n            \"CAP_LINUX_IMMUTABLE\",\n            \"CAP_NET_BIND_SERVICE\",\n            \"CAP_NET_BROADCAST\",\n            \"CAP_NET_ADMIN\",\n            \"CAP_NET_RAW\",\n            \"CAP_IPC_LOCK\",\n            \"CAP_IPC_OWNER\",\n            \"CAP_SYS_MODULE\",\n            \"CAP_SYS_RAWIO\",\n            \"CAP_SYS_CHROOT\",\n            \"CAP_SYS_PTRACE\",\n            \"CAP_SYS_PACCT\",\n            \"CAP_SYS_ADMIN\",\n            \"CAP_SYS_BOOT\",\n            \"CAP_SYS_NICE\",\n            \"CAP_SYS_RESOURCE\",\n            \"CAP_SYS_TIME\",\n            \"CAP_SYS_TTY_CONFIG\",\n            \"CAP_MKNOD\",\n            \"CAP_LEASE\",\n            \"CAP_AUDIT_WRITE\",\n            \"CAP_AUDIT_CONTROL\",\n            \"CAP_SETFCAP\",\n            \"CAP_MAC_OVERRIDE\",\n            \"CAP_MAC_ADMIN\",\n            \"CAP_SYSLOG\",\n            \"CAP_WAKE_ALARM\",\n            \"CAP_BLOCK_SUSPEND\",\n            \"CAP_AUDIT_READ\",\n            \"CAP_PERFMON\",\n            \"CAP_BPF\",\n            \"CAP_CHECKPOINT_RESTORE\"\n        ],\n        \"cgroup\": {\n            \"v1\": true,\n            \"v2\": true,\n            \"systemd\": true,\n            \"systemdUser\": true,\n            \"rdma\": true\n        },\n        \"seccomp\": {\n            \"enabled\": true,\n            \"actions\": [\n                \"SCMP_ACT_ALLOW\",\n                \"SCMP_ACT_ERRNO\",\n                \"SCMP_ACT_KILL\",\n                \"SCMP_ACT_KILL_PROCESS\",\n                \"SCMP_ACT_KILL_THREAD\",\n                \"SCMP_ACT_LOG\",\n                \"SCMP_ACT_NOTIFY\",\n                \"SCMP_ACT_TRACE\",\n                \"SCMP_ACT_TRAP\"\n            ],\n            \"operators\": [\n                \"SCMP_CMP_EQ\",\n                \"SCMP_CMP_GE\",\n                \"SCMP_CMP_GT\",\n                \"SCMP_CMP_LE\",\n                \"SCMP_CMP_LT\",\n                \"SCMP_CMP_MASKED_EQ\",\n                \"SCMP_CMP_NE\"\n            ],\n            \"archs\": [\n                \"SCMP_ARCH_AARCH64\",\n                \"SCMP_ARCH_ARM\",\n                \"SCMP_ARCH_MIPS\",\n                \"SCMP_ARCH_MIPS64\",\n                \"SCMP_ARCH_MIPS64N32\",\n                \"SCMP_ARCH_MIPSEL\",\n                \"SCMP_ARCH_MIPSEL64\",\n                \"SCMP_ARCH_MIPSEL64N32\",\n                \"SCMP_ARCH_PPC\",\n                \"SCMP_ARCH_PPC64\",\n                \"SCMP_ARCH_PPC64LE\",\n                \"SCMP_ARCH_RISCV64\",\n                \"SCMP_ARCH_S390\",\n                \"SCMP_ARCH_S390X\",\n                \"SCMP_ARCH_X32\",\n                \"SCMP_ARCH_X86\",\n                \"SCMP_ARCH_X86_64\"\n            ],\n            \"knownFlags\": [\n                \"SECCOMP_FILTER_FLAG_TSYNC\",\n                \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\",\n                \"SECCOMP_FILTER_FLAG_LOG\"\n            ],\n            \"supportedFlags\": [\n                \"SECCOMP_FILTER_FLAG_TSYNC\",\n                \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\",\n                \"SECCOMP_FILTER_FLAG_LOG\"\n            ]\n        },\n        \"apparmor\": {\n            \"enabled\": true\n        },\n        \"selinux\": {\n            \"enabled\": true\n        },\n        \"intelRdt\": {\n            \"enabled\": true\n        },\n        \"memoryPolicy\": {\n            \"modes\": [\n                \"MPOL_DEFAULT\",\n                \"MPOL_BIND\",\n                \"MPOL_INTERLEAVE\",\n                \"MPOL_WEIGHTED_INTERLEAVE\",\n                \"MPOL_PREFERRED\",\n                \"MPOL_PREFERRED_MANY\",\n                \"MPOL_LOCAL\"\n            ],\n            \"flags\": [\n                \"MPOL_F_NUMA_BALANCING\",\n                \"MPOL_F_RELATIVE_NODES\",\n                \"MPOL_F_STATIC_NODES\"\n            ]\n        },\n        \"netDevices\": {\n            \"enabled\": true\n        }\n    },\n    \"annotations\": {\n        \"io.github.seccomp.libseccomp.version\": \"2.5.4\",\n        \"org.opencontainers.runc.checkpoint.enabled\": \"true\",\n        \"org.opencontainers.runc.commit\": \"v1.1.0-534-g26851168\",\n        \"org.opencontainers.runc.version\": \"1.1.0+dev\"\n    }\n}\"#;\n\n        // Parse and check each field\n        let features: Features = serde_json::from_str(example_json).unwrap();\n        assert_eq!(features.oci_version_min().deref(), \"1.0.0\".to_string());\n        assert_eq!(features.oci_version_max().deref(), \"1.1.0-rc.2\".to_string());\n\n        assert_eq!(\n            features.hooks.as_ref().unwrap(),\n            &[\n                \"prestart\",\n                \"createRuntime\",\n                \"createContainer\",\n                \"startContainer\",\n                \"poststart\",\n                \"poststop\"\n            ]\n        );\n\n        assert_eq!(\n            features.mount_options.as_ref().unwrap(),\n            &[\n                \"async\",\n                \"atime\",\n                \"bind\",\n                \"defaults\",\n                \"dev\",\n                \"diratime\",\n                \"dirsync\",\n                \"exec\",\n                \"iversion\",\n                \"lazytime\",\n                \"loud\",\n                \"mand\",\n                \"noatime\",\n                \"nodev\",\n                \"nodiratime\",\n                \"noexec\",\n                \"noiversion\",\n                \"nolazytime\",\n                \"nomand\",\n                \"norelatime\",\n                \"nostrictatime\",\n                \"nosuid\",\n                \"nosymfollow\",\n                \"private\",\n                \"ratime\",\n                \"rbind\",\n                \"rdev\",\n                \"rdiratime\",\n                \"relatime\",\n                \"remount\",\n                \"rexec\",\n                \"rnoatime\",\n                \"rnodev\",\n                \"rnodiratime\",\n                \"rnoexec\",\n                \"rnorelatime\",\n                \"rnostrictatime\",\n                \"rnosuid\",\n                \"rnosymfollow\",\n                \"ro\",\n                \"rprivate\",\n                \"rrelatime\",\n                \"rro\",\n                \"rrw\",\n                \"rshared\",\n                \"rslave\",\n                \"rstrictatime\",\n                \"rsuid\",\n                \"rsymfollow\",\n                \"runbindable\",\n                \"rw\",\n                \"shared\",\n                \"silent\",\n                \"slave\",\n                \"strictatime\",\n                \"suid\",\n                \"symfollow\",\n                \"sync\",\n                \"tmpcopyup\",\n                \"unbindable\"\n            ]\n        );\n\n        let linux = features.linux().as_ref().unwrap();\n\n        assert_eq!(\n            linux.namespaces.as_ref().unwrap(),\n            &[\n                LinuxNamespaceType::Cgroup,\n                LinuxNamespaceType::Ipc,\n                LinuxNamespaceType::Mount,\n                LinuxNamespaceType::Network,\n                LinuxNamespaceType::Pid,\n                LinuxNamespaceType::User,\n                LinuxNamespaceType::Uts,\n            ]\n        );\n\n        assert_eq!(\n            linux.capabilities.as_ref().unwrap(),\n            &[\n                \"CAP_CHOWN\",\n                \"CAP_DAC_OVERRIDE\",\n                \"CAP_DAC_READ_SEARCH\",\n                \"CAP_FOWNER\",\n                \"CAP_FSETID\",\n                \"CAP_KILL\",\n                \"CAP_SETGID\",\n                \"CAP_SETUID\",\n                \"CAP_SETPCAP\",\n                \"CAP_LINUX_IMMUTABLE\",\n                \"CAP_NET_BIND_SERVICE\",\n                \"CAP_NET_BROADCAST\",\n                \"CAP_NET_ADMIN\",\n                \"CAP_NET_RAW\",\n                \"CAP_IPC_LOCK\",\n                \"CAP_IPC_OWNER\",\n                \"CAP_SYS_MODULE\",\n                \"CAP_SYS_RAWIO\",\n                \"CAP_SYS_CHROOT\",\n                \"CAP_SYS_PTRACE\",\n                \"CAP_SYS_PACCT\",\n                \"CAP_SYS_ADMIN\",\n                \"CAP_SYS_BOOT\",\n                \"CAP_SYS_NICE\",\n                \"CAP_SYS_RESOURCE\",\n                \"CAP_SYS_TIME\",\n                \"CAP_SYS_TTY_CONFIG\",\n                \"CAP_MKNOD\",\n                \"CAP_LEASE\",\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_AUDIT_CONTROL\",\n                \"CAP_SETFCAP\",\n                \"CAP_MAC_OVERRIDE\",\n                \"CAP_MAC_ADMIN\",\n                \"CAP_SYSLOG\",\n                \"CAP_WAKE_ALARM\",\n                \"CAP_BLOCK_SUSPEND\",\n                \"CAP_AUDIT_READ\",\n                \"CAP_PERFMON\",\n                \"CAP_BPF\",\n                \"CAP_CHECKPOINT_RESTORE\"\n            ],\n        );\n\n        assert_eq!(\n            linux.cgroup.as_ref().unwrap(),\n            &Cgroup {\n                v1: Some(true),\n                v2: Some(true),\n                systemd: Some(true),\n                systemd_user: Some(true),\n                rdma: Some(true),\n            }\n        );\n\n        assert_eq!(\n            linux.seccomp.as_ref().unwrap(),\n            &Seccomp {\n                enabled: Some(true),\n                actions: Some(vec![\n                    LinuxSeccompAction::ScmpActAllow,\n                    LinuxSeccompAction::ScmpActErrno,\n                    LinuxSeccompAction::ScmpActKill,\n                    LinuxSeccompAction::ScmpActKillProcess,\n                    LinuxSeccompAction::ScmpActKillThread,\n                    LinuxSeccompAction::ScmpActLog,\n                    LinuxSeccompAction::ScmpActNotify,\n                    LinuxSeccompAction::ScmpActTrace,\n                    LinuxSeccompAction::ScmpActTrap\n                ]),\n                operators: Some(vec![\n                    \"SCMP_CMP_EQ\".to_string(),\n                    \"SCMP_CMP_GE\".to_string(),\n                    \"SCMP_CMP_GT\".to_string(),\n                    \"SCMP_CMP_LE\".to_string(),\n                    \"SCMP_CMP_LT\".to_string(),\n                    \"SCMP_CMP_MASKED_EQ\".to_string(),\n                    \"SCMP_CMP_NE\".to_string()\n                ]),\n                archs: Some(vec![\n                    Arch::ScmpArchAarch64,\n                    Arch::ScmpArchArm,\n                    Arch::ScmpArchMips,\n                    Arch::ScmpArchMips64,\n                    Arch::ScmpArchMips64n32,\n                    Arch::ScmpArchMipsel,\n                    Arch::ScmpArchMipsel64,\n                    Arch::ScmpArchMipsel64n32,\n                    Arch::ScmpArchPpc,\n                    Arch::ScmpArchPpc64,\n                    Arch::ScmpArchPpc64le,\n                    Arch::ScmpArchRiscv64,\n                    Arch::ScmpArchS390,\n                    Arch::ScmpArchS390x,\n                    Arch::ScmpArchX32,\n                    Arch::ScmpArchX86,\n                    Arch::ScmpArchX86_64,\n                ]),\n                known_flags: Some(vec![\n                    \"SECCOMP_FILTER_FLAG_TSYNC\".to_string(),\n                    \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\".to_string(),\n                    \"SECCOMP_FILTER_FLAG_LOG\".to_string()\n                ]),\n                supported_flags: Some(vec![\n                    \"SECCOMP_FILTER_FLAG_TSYNC\".to_string(),\n                    \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\".to_string(),\n                    \"SECCOMP_FILTER_FLAG_LOG\".to_string()\n                ])\n            },\n        );\n\n        assert_eq!(\n            linux.apparmor.as_ref().unwrap(),\n            &Apparmor {\n                enabled: Some(true)\n            }\n        );\n\n        assert_eq!(\n            linux.selinux.as_ref().unwrap(),\n            &Selinux {\n                enabled: Some(true)\n            }\n        );\n\n        assert_eq!(\n            linux.intel_rdt.as_ref().unwrap(),\n            &IntelRdt {\n                enabled: Some(true),\n                schemata: None,\n                monitoring: None,\n            }\n        );\n\n        assert_eq!(\n            linux.memory_policy.as_ref().unwrap(),\n            &MemoryPolicy {\n                modes: Some(vec![\n                    \"MPOL_DEFAULT\".to_string(),\n                    \"MPOL_BIND\".to_string(),\n                    \"MPOL_INTERLEAVE\".to_string(),\n                    \"MPOL_WEIGHTED_INTERLEAVE\".to_string(),\n                    \"MPOL_PREFERRED\".to_string(),\n                    \"MPOL_PREFERRED_MANY\".to_string(),\n                    \"MPOL_LOCAL\".to_string(),\n                ]),\n                flags: Some(vec![\n                    \"MPOL_F_NUMA_BALANCING\".to_string(),\n                    \"MPOL_F_RELATIVE_NODES\".to_string(),\n                    \"MPOL_F_STATIC_NODES\".to_string(),\n                ]),\n            }\n        );\n\n        assert_eq!(\n            linux.net_devices.as_ref().unwrap(),\n            &NetDevices {\n                enabled: Some(true)\n            }\n        );\n\n        assert_eq!(\n            features.annotations().as_ref().unwrap(),\n            &[\n                (\n                    \"io.github.seccomp.libseccomp.version\".to_string(),\n                    \"2.5.4\".to_string()\n                ),\n                (\n                    \"org.opencontainers.runc.checkpoint.enabled\".to_string(),\n                    \"true\".to_string()\n                ),\n                (\n                    \"org.opencontainers.runc.commit\".to_string(),\n                    \"v1.1.0-534-g26851168\".to_string()\n                ),\n                (\n                    \"org.opencontainers.runc.version\".to_string(),\n                    \"1.1.0+dev\".to_string()\n                )\n            ]\n            .iter()\n            .cloned()\n            .collect()\n        );\n\n        assert_eq!(\n            features.potentially_unsafe_config_annotations().as_ref(),\n            None,\n        );\n    }\n}\n"
  },
  {
    "path": "src/runtime/hooks.rs",
    "content": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n/// Hooks specifies a command that is run in the container at a particular\n/// event in the lifecycle (setup and teardown) of a container.\npub struct Hooks {\n    #[deprecated(\n        note = \"Prestart hooks were deprecated in favor of `createRuntime`, `createContainer` and `startContainer` hooks\"\n    )]\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The `prestart` hooks MUST be called after the `start` operation is\n    /// called but before the user-specified program command is\n    /// executed.\n    ///\n    /// On Linux, for example, they are called after the container\n    /// namespaces are created, so they provide an opportunity to\n    /// customize the container (e.g. the network namespace could be\n    /// specified in this hook).\n    ///\n    /// The `prestart` hooks' path MUST resolve in the runtime namespace.\n    /// The `prestart` hooks MUST be executed in the runtime namespace.\n    prestart: Option<Vec<Hook>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// CreateRuntime is a list of hooks to be run after the container has\n    /// been created but before `pivot_root` or any equivalent\n    /// operation has been called. It is called in the Runtime\n    /// Namespace.\n    create_runtime: Option<Vec<Hook>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// CreateContainer is a list of hooks to be run after the container has\n    /// been created but before `pivot_root` or any equivalent\n    /// operation has been called. It is called in the\n    /// Container Namespace.\n    create_container: Option<Vec<Hook>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// StartContainer is a list of hooks to be run after the start\n    /// operation is called but before the container process is\n    /// started. It is called in the Container Namespace.\n    start_container: Option<Vec<Hook>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Poststart is a list of hooks to be run after the container process\n    /// is started. It is called in the Runtime Namespace.\n    poststart: Option<Vec<Hook>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Poststop is a list of hooks to be run after the container process\n    /// exits. It is called in the Runtime Namespace.\n    poststop: Option<Vec<Hook>>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Hook specifies a command that is run at a particular event in the\n/// lifecycle of a container.\npub struct Hook {\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Path to the binary to be executed. Following similar semantics to\n    /// [IEEE Std 1003.1-2008 `execv`'s path](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html). This\n    /// specification extends the IEEE standard in that path MUST be\n    /// absolute.\n    path: PathBuf,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Arguments used for the binary, including the binary name itself.\n    /// Following the same semantics as [IEEE Std 1003.1-2008\n    /// `execv`'s argv](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html).\n    args: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Additional `key=value` environment variables. Following the same\n    /// semantics as [IEEE Std 1003.1-2008's `environ`](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_01).\n    env: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Timeout is the number of seconds before aborting the hook. If set,\n    /// timeout MUST be greater than zero.\n    timeout: Option<i64>,\n}\n"
  },
  {
    "path": "src/runtime/linux.rs",
    "content": "use crate::error::{oci_error, OciSpecError};\nuse crate::is_none_or_empty;\n\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, fmt::Display, path::PathBuf, vec};\nuse strum_macros::{Display as StrumDisplay, EnumString};\n\n#[derive(\n    Builder, Clone, Debug, Deserialize, Eq, Getters, MutGetters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n/// Linux contains platform-specific configuration for Linux based\n/// containers.\npub struct Linux {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// NetDevices are key-value pairs, keyed by network device name on the host, moved to the container's network namespace.\n    net_devices: Option<HashMap<String, LinuxNetDevice>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// UIDMappings specifies user mappings for supporting user namespaces.\n    uid_mappings: Option<Vec<LinuxIdMapping>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// GIDMappings specifies group mappings for supporting user namespaces.\n    gid_mappings: Option<Vec<LinuxIdMapping>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Sysctl are a set of key value pairs that are set for the container\n    /// on start.\n    sysctl: Option<HashMap<String, String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Resources contain cgroup information for handling resource\n    /// constraints for the container.\n    resources: Option<LinuxResources>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// CgroupsPath specifies the path to cgroups that are created and/or\n    /// joined by the container. The path is expected to be relative\n    /// to the cgroups mountpoint. If resources are specified,\n    /// the cgroups at CgroupsPath will be updated based on resources.\n    cgroups_path: Option<PathBuf>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Namespaces contains the namespaces that are created and/or joined by\n    /// the container.\n    namespaces: Option<Vec<LinuxNamespace>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Devices are a list of device nodes that are created for the\n    /// container.\n    devices: Option<Vec<LinuxDevice>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Seccomp specifies the seccomp security settings for the container.\n    seccomp: Option<LinuxSeccomp>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// RootfsPropagation is the rootfs mount propagation mode for the\n    /// container.\n    rootfs_propagation: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// MaskedPaths masks over the provided paths inside the container.\n    masked_paths: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// ReadonlyPaths sets the provided paths as RO inside the container.\n    readonly_paths: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// MountLabel specifies the selinux context for the mounts in the\n    /// container.\n    mount_label: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// IntelRdt contains Intel Resource Director Technology (RDT)\n    /// information for handling resource constraints and monitoring metrics\n    /// (e.g., L3 cache, memory bandwidth) for the container.\n    intel_rdt: Option<LinuxIntelRdt>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// MemoryPolicy contains NUMA memory policy for the container.\n    memory_policy: Option<LinuxMemoryPolicy>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Personality contains configuration for the Linux personality\n    /// syscall.\n    personality: Option<LinuxPersonality>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// TimeOffsets specifies the offset for supporting time namespaces.\n    time_offsets: Option<HashMap<String, LinuxTimeOffset>>,\n}\n\n// Default impl for Linux (see functions for more info)\nimpl Default for Linux {\n    fn default() -> Self {\n        Linux {\n            // Creates empty Hashmap\n            net_devices: None,\n            // Creates empty Vec\n            uid_mappings: Default::default(),\n            // Creates empty Vec\n            gid_mappings: Default::default(),\n            // Empty sysctl Hashmap\n            sysctl: Default::default(),\n            resources: Some(LinuxResources {\n                devices: vec![].into(),\n                memory: Default::default(),\n                cpu: Default::default(),\n                pids: Default::default(),\n                block_io: Default::default(),\n                hugepage_limits: Default::default(),\n                network: Default::default(),\n                rdma: Default::default(),\n                unified: Default::default(),\n            }),\n            // Defaults to None\n            cgroups_path: Default::default(),\n            namespaces: get_default_namespaces().into(),\n            // Empty Vec\n            devices: Default::default(),\n            // Empty String\n            rootfs_propagation: Default::default(),\n            masked_paths: get_default_maskedpaths().into(),\n            readonly_paths: get_default_readonly_paths().into(),\n            // Empty String\n            mount_label: Default::default(),\n            seccomp: None,\n            intel_rdt: None,\n            memory_policy: None,\n            personality: None,\n            time_offsets: None,\n        }\n    }\n}\n\nimpl Linux {\n    /// Return rootless Linux configuration.\n    pub fn rootless(uid: u32, gid: u32) -> Self {\n        let mut namespaces = get_default_namespaces();\n        namespaces.retain(|ns| ns.typ != LinuxNamespaceType::Network);\n        namespaces.push(LinuxNamespace {\n            typ: LinuxNamespaceType::User,\n            ..Default::default()\n        });\n        Self {\n            resources: None,\n            uid_mappings: Some(vec![LinuxIdMapping {\n                container_id: 0,\n                host_id: uid,\n                size: 1,\n            }]),\n            gid_mappings: Some(vec![LinuxIdMapping {\n                container_id: 0,\n                host_id: gid,\n                size: 1,\n            }]),\n            namespaces: Some(namespaces),\n            ..Default::default()\n        }\n    }\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxIDMapping specifies UID/GID mappings.\npub struct LinuxIdMapping {\n    #[serde(default, rename = \"hostID\")]\n    /// HostID is the starting UID/GID on the host to be mapped to\n    /// `container_id`.\n    host_id: u32,\n\n    #[serde(default, rename = \"containerID\")]\n    /// ContainerID is the starting UID/GID in the container.\n    container_id: u32,\n\n    #[serde(default)]\n    /// Size is the number of IDs to be mapped.\n    size: u32,\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, EnumString)]\n#[strum(serialize_all = \"lowercase\")]\n#[serde(rename_all = \"lowercase\")]\n/// Device types\npub enum LinuxDeviceType {\n    /// All\n    A,\n\n    /// block (buffered)\n    B,\n\n    /// character (unbuffered)\n    C,\n\n    /// character (unbufferd)\n    U,\n\n    /// FIFO\n    P,\n}\n\n#[allow(clippy::derivable_impls)] // because making it clear that All is the default\nimpl Default for LinuxDeviceType {\n    fn default() -> LinuxDeviceType {\n        LinuxDeviceType::A\n    }\n}\n\nimpl LinuxDeviceType {\n    /// Retrieve a string reference for the device type.\n    pub fn as_str(&self) -> &str {\n        match self {\n            Self::A => \"a\",\n            Self::B => \"b\",\n            Self::C => \"c\",\n            Self::U => \"u\",\n            Self::P => \"p\",\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxNetDevice represents a single network device to be added to the container's network namespace\npub struct LinuxNetDevice {\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Name of the device in the container namespace\n    name: Option<String>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Represents a device rule for the devices specified to the device\n/// controller\npub struct LinuxDeviceCgroup {\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Allow or deny\n    allow: bool,\n\n    #[serde(default, rename = \"type\", skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Device type, block, char, etc.\n    typ: Option<LinuxDeviceType>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Device's major number\n    major: Option<i64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Device's minor number\n    minor: Option<i64>,\n\n    /// Cgroup access permissions format, rwm.\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    access: Option<String>,\n}\n\n/// This ToString trait is automatically implemented for any type which implements the Display trait.\n/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,\n/// and you get the ToString implementation for free.\nimpl Display for LinuxDeviceCgroup {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let major = self\n            .major\n            .map(|mj| mj.to_string())\n            .unwrap_or_else(|| \"*\".to_string());\n        let minor = self\n            .minor\n            .map(|mi| mi.to_string())\n            .unwrap_or_else(|| \"*\".to_string());\n        let access = self.access.as_deref().unwrap_or(\"\");\n        write!(\n            f,\n            \"{} {}:{} {}\",\n            &self.typ.unwrap_or_default().as_str(),\n            &major,\n            &minor,\n            &access\n        )\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Copy,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    PartialEq,\n    Serialize,\n    Setters,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxMemory for Linux cgroup 'memory' resource management.\npub struct LinuxMemory {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Memory limit (in bytes).\n    limit: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Memory reservation or soft_limit (in bytes).\n    reservation: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Total memory limit (memory + swap).\n    swap: Option<i64>,\n\n    #[deprecated(\n        note = \"kernel-memory limits are not supported in cgroups v2, and were obsoleted in kernel v5.4\"\n    )]\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Kernel memory limit (in bytes).\n    ///\n    /// # Deprecated\n    ///\n    /// kernel-memory limits are not supported in cgroups v2,\n    /// and were obsoleted in kernel v5.4.\n    kernel: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\", rename = \"kernelTCP\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Kernel memory limit for tcp (in bytes).\n    kernel_tcp: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// How aggressive the kernel will swap memory pages.\n    swappiness: Option<u64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\", rename = \"disableOOMKiller\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// DisableOOMKiller disables the OOM killer for out of memory\n    /// conditions.\n    disable_oom_killer: Option<bool>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Enables hierarchical memory accounting\n    use_hierarchy: Option<bool>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Enables checking if a new memory limit is lower\n    check_before_update: Option<bool>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxCPU for Linux cgroup 'cpu' resource management.\npub struct LinuxCpu {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// CPU shares (relative weight (ratio) vs. other cgroups with cpu\n    /// shares).\n    shares: Option<u64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// CPU hardcap limit (in usecs). Allowed cpu time in a given period.\n    quota: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Cgroups are configured with minimum weight, 0: default behavior, 1: SCHED_IDLE.\n    idle: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Maximum amount of accumulated time in microseconds for which tasks\n    /// in a cgroup can run additionally for burst during one period\n    burst: Option<u64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// CPU period to be used for hardcapping (in usecs).\n    period: Option<u64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// How much time realtime scheduling may use (in usecs).\n    realtime_runtime: Option<i64>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// CPU period to be used for realtime scheduling (in usecs).\n    realtime_period: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// CPUs to use within the cpuset. Default is to use any CPU available.\n    cpus: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// List of memory nodes in the cpuset. Default is to use any available\n    /// memory node.\n    mems: Option<String>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Copy,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    CopyGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxPids for Linux cgroup 'pids' resource management (Linux 4.3).\npub struct LinuxPids {\n    #[serde(default)]\n    /// Maximum number of PIDs. Default is \"no limit\".\n    limit: i64,\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxWeightDevice struct holds a `major:minor weight` pair for\n/// weightDevice.\npub struct LinuxWeightDevice {\n    #[serde(default)]\n    /// Major is the device's major number.\n    major: i64,\n\n    #[serde(default)]\n    /// Minor is the device's minor number.\n    minor: i64,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    /// Weight is the bandwidth rate for the device.\n    weight: Option<u16>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    /// LeafWeight is the bandwidth rate for the device while competing with\n    /// the cgroup's child cgroups, CFQ scheduler only.\n    leaf_weight: Option<u16>,\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxThrottleDevice struct holds a `major:minor rate_per_second` pair.\npub struct LinuxThrottleDevice {\n    #[serde(default)]\n    /// Major is the device's major number.\n    major: i64,\n\n    #[serde(default)]\n    /// Minor is the device's minor number.\n    minor: i64,\n\n    #[serde(default)]\n    /// Rate is the IO rate limit per cgroup per device.\n    rate: u64,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxBlockIO for Linux cgroup 'blkio' resource management.\npub struct LinuxBlockIo {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Specifies per cgroup weight.\n    weight: Option<u16>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Specifies tasks' weight in the given cgroup while competing with the\n    /// cgroup's child cgroups, CFQ scheduler only.\n    leaf_weight: Option<u16>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Weight per cgroup per device, can override BlkioWeight.\n    weight_device: Option<Vec<LinuxWeightDevice>>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// IO read rate limit per cgroup per device, bytes per second.\n    throttle_read_bps_device: Option<Vec<LinuxThrottleDevice>>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// IO write rate limit per cgroup per device, bytes per second.\n    throttle_write_bps_device: Option<Vec<LinuxThrottleDevice>>,\n\n    #[serde(\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"throttleReadIOPSDevice\"\n    )]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// IO read rate limit per cgroup per device, IO per second.\n    throttle_read_iops_device: Option<Vec<LinuxThrottleDevice>>,\n\n    #[serde(\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"throttleWriteIOPSDevice\"\n    )]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// IO write rate limit per cgroup per device, IO per second.\n    throttle_write_iops_device: Option<Vec<LinuxThrottleDevice>>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxHugepageLimit structure corresponds to limiting kernel hugepages.\n/// Default to reservation limits if supported. Otherwise fallback to page fault limits.\npub struct LinuxHugepageLimit {\n    #[serde(default)]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Pagesize is the hugepage size.\n    /// Format: \"&lt;size&gt;&lt;unit-prefix&gt;B' (e.g. 64KB, 2MB, 1GB, etc.)\n    page_size: String,\n\n    #[serde(default)]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Limit is the limit of \"hugepagesize\" hugetlb reservations (if supported) or usage.\n    limit: i64,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxInterfacePriority for network interfaces.\npub struct LinuxInterfacePriority {\n    #[serde(default)]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Name is the name of the network interface.\n    name: String,\n\n    #[serde(default)]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Priority for the interface.\n    priority: u32,\n}\n\n/// This ToString trait is automatically implemented for any type which implements the Display trait.\n/// As such, ToString shouldn’t be implemented directly: Display should be implemented instead,\n/// and you get the ToString implementation for free.\nimpl Display for LinuxInterfacePriority {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        // Serde serialization never fails since this is\n        // a combination of String and enums.\n        writeln!(f, \"{} {}\", self.name, self.priority)\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxNetwork identification and priority configuration.\npub struct LinuxNetwork {\n    #[serde(skip_serializing_if = \"Option::is_none\", rename = \"classID\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Set class identifier for container's network packets\n    class_id: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Set priority of network traffic for container.\n    priorities: Option<Vec<LinuxInterfacePriority>>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Resource constraints for container\npub struct LinuxResources {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Devices configures the device allowlist.\n    devices: Option<Vec<LinuxDeviceCgroup>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Memory restriction configuration.\n    memory: Option<LinuxMemory>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// CPU resource restriction configuration.\n    cpu: Option<LinuxCpu>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Task resource restrictions\n    pids: Option<LinuxPids>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\", rename = \"blockIO\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// BlockIO restriction configuration.\n    block_io: Option<LinuxBlockIo>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Hugetlb limit (in bytes).\n    hugepage_limits: Option<Vec<LinuxHugepageLimit>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Network restriction configuration.\n    network: Option<LinuxNetwork>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Rdma resource restriction configuration. Limits are a set of key\n    /// value pairs that define RDMA resource limits, where the key\n    /// is device name and value is resource limits.\n    rdma: Option<HashMap<String, LinuxRdma>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Unified resources.\n    unified: Option<HashMap<String, String>>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Copy,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    MutGetters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n/// LinuxRdma for Linux cgroup 'rdma' resource management (Linux 4.11).\npub struct LinuxRdma {\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    /// Maximum number of HCA handles that can be opened. Default is \"no\n    /// limit\".\n    hca_handles: Option<u32>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    /// Maximum number of HCA objects that can be created. Default is \"no\n    /// limit\".\n    hca_objects: Option<u32>,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Hash, StrumDisplay,\n)]\n#[strum(serialize_all = \"lowercase\")]\n#[serde(rename_all = \"snake_case\")]\n/// Available Linux namespaces.\npub enum LinuxNamespaceType {\n    #[strum(to_string = \"mnt\")]\n    /// Mount Namespace for isolating mount points\n    Mount = 0x00020000,\n\n    /// Cgroup Namespace for isolating cgroup hierarchies\n    Cgroup = 0x02000000,\n\n    /// Uts Namespace for isolating hostname and NIS domain name\n    Uts = 0x04000000,\n\n    /// Ipc Namespace for isolating System V, IPC, POSIX message queues\n    Ipc = 0x08000000,\n\n    /// User Namespace for isolating user and group  ids\n    User = 0x10000000,\n\n    /// PID Namespace for isolating process ids\n    #[default]\n    Pid = 0x20000000,\n\n    #[strum(to_string = \"net\")]\n    /// Network Namespace for isolating network devices, ports, stacks etc.\n    Network = 0x40000000,\n\n    /// Time Namespace for isolating the clocks\n    Time = 0x00000080,\n}\n\nimpl TryFrom<&str> for LinuxNamespaceType {\n    type Error = OciSpecError;\n\n    fn try_from(namespace: &str) -> Result<Self, Self::Error> {\n        match namespace {\n            \"mnt\" | \"mount\" => Ok(LinuxNamespaceType::Mount),\n            \"cgroup\" => Ok(LinuxNamespaceType::Cgroup),\n            \"uts\" => Ok(LinuxNamespaceType::Uts),\n            \"ipc\" => Ok(LinuxNamespaceType::Ipc),\n            \"user\" => Ok(LinuxNamespaceType::User),\n            \"pid\" => Ok(LinuxNamespaceType::Pid),\n            \"net\" | \"network\" => Ok(LinuxNamespaceType::Network),\n            \"time\" => Ok(LinuxNamespaceType::Time),\n            _ => Err(oci_error(format!(\n                \"unknown namespace {namespace}, could not convert\"\n            ))),\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxNamespace is the configuration for a Linux namespace.\npub struct LinuxNamespace {\n    #[serde(rename = \"type\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Type is the type of namespace.\n    typ: LinuxNamespaceType,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Path is a path to an existing namespace persisted on disk that can\n    /// be joined and is of the same type\n    path: Option<PathBuf>,\n}\n\n/// Utility function to get default namespaces.\npub fn get_default_namespaces() -> Vec<LinuxNamespace> {\n    vec![\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Pid,\n            path: Default::default(),\n        },\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Network,\n            path: Default::default(),\n        },\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Ipc,\n            path: Default::default(),\n        },\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Uts,\n            path: Default::default(),\n        },\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Mount,\n            path: Default::default(),\n        },\n        LinuxNamespace {\n            typ: LinuxNamespaceType::Cgroup,\n            path: Default::default(),\n        },\n    ]\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxDevice represents the mknod information for a Linux special device\n/// file.\npub struct LinuxDevice {\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Path to the device.\n    path: PathBuf,\n\n    #[serde(rename = \"type\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Device type, block, char, etc..\n    typ: LinuxDeviceType,\n\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Major is the device's major number.\n    major: i64,\n\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Minor is the device's minor number.\n    minor: i64,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// FileMode permission bits for the device.\n    file_mode: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// UID of the device.\n    uid: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Gid of the device.\n    gid: Option<u32>,\n}\n\nimpl From<&LinuxDevice> for LinuxDeviceCgroup {\n    fn from(linux_device: &LinuxDevice) -> LinuxDeviceCgroup {\n        LinuxDeviceCgroup {\n            allow: true,\n            typ: linux_device.typ.into(),\n            major: Some(linux_device.major),\n            minor: Some(linux_device.minor),\n            access: \"rwm\".to_string().into(),\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxSeccomp represents syscall restrictions.\npub struct LinuxSeccomp {\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// The default action to be done.\n    default_action: LinuxSeccompAction,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// The default error return code to use when the default action is SCMP_ACT_ERRNO.\n    default_errno_ret: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Available architectures for the restriction.\n    architectures: Option<Vec<Arch>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Flags added to the seccomp restriction.\n    flags: Option<Vec<LinuxSeccompFilterFlag>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// The unix domain socket path over which runtime will use for `SCMP_ACT_NOTIFY`.\n    listener_path: Option<PathBuf>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// An opaque data to pass to the seccomp agent.\n    listener_metadata: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// The syscalls for the restriction.\n    syscalls: Option<Vec<LinuxSyscall>>,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n/// Available seccomp actions.\npub enum LinuxSeccompAction {\n    /// Kill the thread, defined for backward compatibility.\n    ScmpActKill,\n\n    /// Kill the thread\n    ScmpActKillThread,\n\n    /// Kill the process.\n    ScmpActKillProcess,\n\n    /// Throw a SIGSYS signal.\n    ScmpActTrap,\n\n    /// Return the specified error code.\n    ScmpActErrno,\n\n    /// Notifies userspace.\n    ScmpActNotify,\n\n    /// Notify a tracing process with the specified value.\n    ScmpActTrace,\n\n    /// Allow the syscall to be executed after the action has been logged.\n    ScmpActLog,\n\n    /// Allow the syscall to be executed.\n    #[default]\n    ScmpActAllow,\n}\n\n/// Converts [`LinuxSeccompAction`] to its `u32` representation.\nimpl From<LinuxSeccompAction> for u32 {\n    fn from(action: LinuxSeccompAction) -> Self {\n        action.as_u32(None)\n    }\n}\n\nimpl LinuxSeccompAction {\n    /// Converts the seccomp action to its u32 representation.\n    ///\n    /// The `errno_ret` parameter is used for actions that return an errno value.\n    /// If the action is `ScmpActErrno` or `ScmpActTrace`, and `errno_ret` is provided,\n    /// it will be included in the returned value.\n    pub fn as_u32(&self, errno_ret: Option<u32>) -> u32 {\n        match self {\n            LinuxSeccompAction::ScmpActKill => 0x00000000,\n            LinuxSeccompAction::ScmpActKillThread => 0x00000000,\n            LinuxSeccompAction::ScmpActKillProcess => 0x80000000,\n            LinuxSeccompAction::ScmpActTrap => 0x00030000,\n            LinuxSeccompAction::ScmpActErrno => {\n                if let Some(errno_ret) = errno_ret {\n                    0x00050000 | errno_ret\n                } else {\n                    0x00050001\n                }\n            }\n            LinuxSeccompAction::ScmpActNotify => 0x7fc00000,\n            LinuxSeccompAction::ScmpActTrace => {\n                if let Some(errno_ret) = errno_ret {\n                    0x7ff00000 | errno_ret\n                } else {\n                    0x7ff00001\n                }\n            }\n            LinuxSeccompAction::ScmpActLog => 0x7ffc0000,\n            LinuxSeccompAction::ScmpActAllow => 0x7fff0000,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n#[repr(u32)]\n/// Available seccomp architectures.\npub enum Arch {\n    /// The native architecture.\n    ScmpArchNative = 0x00000000,\n\n    /// The x86 (32-bit) architecture.\n    ScmpArchX86 = 0x40000003,\n\n    /// The x86-64 (64-bit) architecture.\n    ScmpArchX86_64 = 0xc000003e,\n\n    /// The x32 (32-bit x86_64) architecture.\n    ///\n    /// This is different from the value used by the kernel because we need to\n    /// be able to distinguish between x32 and x86_64.\n    ScmpArchX32 = 0x4000003e,\n\n    /// The ARM architecture.\n    ScmpArchArm = 0x40000028,\n\n    /// The AArch64 architecture.\n    ScmpArchAarch64 = 0xc00000b7,\n\n    /// The MIPS architecture.\n    ScmpArchMips = 0x00000008,\n\n    /// The MIPS64 architecture.\n    ScmpArchMips64 = 0x80000008,\n\n    /// The MIPS64n32 architecture.\n    ScmpArchMips64n32 = 0xa0000008,\n\n    /// The MIPSel architecture.\n    ScmpArchMipsel = 0x40000008,\n\n    /// The MIPSel64 architecture.\n    ScmpArchMipsel64 = 0xc0000008,\n\n    /// The MIPSel64n32 architecture.\n    ScmpArchMipsel64n32 = 0xe0000008,\n\n    /// The PowerPC architecture.\n    ScmpArchPpc = 0x00000014,\n\n    /// The PowerPC64 architecture.\n    ScmpArchPpc64 = 0x80000015,\n\n    /// The PowerPC64le architecture.\n    ScmpArchPpc64le = 0xc0000015,\n\n    /// The S390 architecture.\n    ScmpArchS390 = 0x00000016,\n\n    /// The S390x architecture.\n    ScmpArchS390x = 0x80000016,\n\n    /// The PA-RISC architecture.\n    ScmpArchParisc = 0x0000000f,\n\n    /// The PA-RISC64 architecture.\n    ScmpArchParisc64 = 0x8000000f,\n\n    /// The RISCV64 architecture.\n    ScmpArchRiscv64 = 0xc00000f3,\n\n    /// The LoongArch64 architecture.\n    ScmpArchLoongarch64 = 0xc0000102,\n\n    /// The Motorola 68000 (32-bit) architecture.\n    ScmpArchM68k = 0x00000004,\n\n    /// The SuperH (32-bit) architecture.\n    ScmpArchSh = 0x4000002a,\n\n    /// The SuperH (32-bit, big-endian) architecture.\n    ScmpArchSheb = 0x0000002a,\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n/// Available seccomp filter flags.\npub enum LinuxSeccompFilterFlag {\n    /// All filter return actions except SECCOMP_RET_ALLOW should be logged. An administrator may\n    /// override this filter flag by preventing specific actions from being logged via the\n    /// /proc/sys/kernel/seccomp/actions_logged file. (since Linux 4.14)\n    SeccompFilterFlagLog,\n\n    /// When adding a new filter, synchronize all other threads of the calling process to the same\n    /// seccomp filter tree. A \"filter tree\" is the ordered list of filters attached to a thread.\n    /// (Attaching identical filters in separate seccomp() calls results in different filters from this\n    /// perspective.)\n    ///\n    /// If any thread cannot synchronize to the same filter tree, the call will not attach the new\n    /// seccomp filter, and will fail, returning the first thread ID found that cannot synchronize.\n    /// Synchronization will fail if another thread in the same process is in SECCOMP_MODE_STRICT or if\n    /// it has attached new seccomp filters to itself, diverging from the calling thread's filter tree.\n    SeccompFilterFlagTsync,\n\n    /// Disable Speculative Store Bypass mitigation. (since Linux 4.17)\n    SeccompFilterFlagSpecAllow,\n\n    /// Ensures the process is killable while waiting for notifications. (since linux 5.19)\n    SeccompFilterFlagWaitKillableRecv,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n#[repr(u32)]\n/// The seccomp operator to be used for args.\npub enum LinuxSeccompOperator {\n    /// Refers to the SCMP_CMP_NE operator (not equal).\n    ScmpCmpNe = 1,\n\n    /// Refers to the SCMP_CMP_LT operator (less than).\n    ScmpCmpLt = 2,\n\n    /// Refers to the SCMP_CMP_LE operator (less equal).\n    ScmpCmpLe = 3,\n\n    /// Refers to the SCMP_CMP_EQ operator (equal to).\n    #[default]\n    ScmpCmpEq = 4,\n\n    /// Refers to the SCMP_CMP_GE operator (greater equal).\n    ScmpCmpGe = 5,\n\n    /// Refers to the SCMP_CMP_GT operator (greater than).\n    ScmpCmpGt = 6,\n\n    /// Refers to the SCMP_CMP_MASKED_EQ operator (masked equal).\n    ScmpCmpMaskedEq = 7,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxSyscall is used to match a syscall in seccomp.\npub struct LinuxSyscall {\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// The names of the syscalls.\n    names: Vec<String>,\n\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// The action to be done for the syscalls.\n    action: LinuxSeccompAction,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// The error return value.\n    errno_ret: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// The arguments for the syscalls.\n    args: Option<Vec<LinuxSeccompArg>>,\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// LinuxSeccompArg used for matching specific syscall arguments in seccomp.\npub struct LinuxSeccompArg {\n    /// The index of the argument.\n    index: usize,\n\n    /// The value of the argument.\n    value: u64,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The second value of the argument.\n    value_two: Option<u64>,\n\n    /// The operator for the argument.\n    op: LinuxSeccompOperator,\n}\n\n/// Default masks paths, cannot read these host files.\npub fn get_default_maskedpaths() -> Vec<String> {\n    vec![\n        // For example now host interfaces such as\n        // bluetooth cannot be accessed due to /proc/acpi\n        \"/proc/acpi\".to_string(),\n        \"/proc/asound\".to_string(),\n        \"/proc/kcore\".to_string(),\n        \"/proc/keys\".to_string(),\n        \"/proc/latency_stats\".to_string(),\n        \"/proc/timer_list\".to_string(),\n        \"/proc/timer_stats\".to_string(),\n        \"/proc/sched_debug\".to_string(),\n        \"/sys/firmware\".to_string(),\n        \"/proc/scsi\".to_string(),\n    ]\n}\n\n/// Default readonly paths, for example most containers shouldn't have permission to write to\n/// `/proc/sys`.\npub fn get_default_readonly_paths() -> Vec<String> {\n    vec![\n        \"/proc/bus\".to_string(),\n        \"/proc/fs\".to_string(),\n        \"/proc/irq\".to_string(),\n        \"/proc/sys\".to_string(),\n        \"/proc/sysrq-trigger\".to_string(),\n    ]\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n/// LinuxIntelRdt has container runtime resource constraints for Intel RDT CAT and MBA\n/// features and flags enabling Intel RDT CMT and MBM features.\n/// Intel RDT features are available in Linux 4.14 and newer kernel versions.\npub struct LinuxIntelRdt {\n    #[serde(default, skip_serializing_if = \"Option::is_none\", rename = \"closID\")]\n    /// The identity for RDT Class of Service.\n    clos_id: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"is_none_or_empty\")]\n    /// Schemata specifies the complete schemata to be written as is to the\n    /// schemata file in resctrl fs. Each element represents a single line in the schemata file.\n    /// NOTE: This will overwrite schemas specified in the L3CacheSchema and/or\n    /// MemBwSchema fields.\n    schemata: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The schema for L3 cache id and capacity bitmask (CBM).\n    /// Format: \"L3:&lt;cache_id0&gt;=&lt;cbm0&gt;;&lt;cache_id1&gt;=&lt;cbm1&gt;;...\"\n    /// NOTE: Should not be specified if Schemata is non-empty.\n    l3_cache_schema: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The schema of memory bandwidth per L3 cache id.\n    /// Format: \"MB:&lt;cache_id0&gt;=bandwidth0;&lt;cache_id1&gt;=bandwidth1;...\"\n    /// The unit of memory bandwidth is specified in \"percentages\" by\n    /// default, and in \"MBps\" if MBA Software Controller is\n    /// enabled.\n    /// NOTE: Should not be specified if Schemata is non-empty.\n    mem_bw_schema: Option<String>,\n\n    #[deprecated(\n        since = \"0.10.0\",\n        note = \"enable_cmt is deprecated in runtime-spec v1.3.0. Use enable_monitoring instead.\"\n    )]\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// EnableCMT is the flag to indicate if the Intel RDT CMT is enabled. CMT (Cache Monitoring Technology) supports monitoring of\n    /// the last-level cache (LLC) occupancy for the container.\n    ///\n    /// # Deprecated\n    /// This field is deprecated in runtime-spec v1.3.0.\n    /// Use `enable_monitoring` instead.\n    enable_cmt: Option<bool>,\n\n    #[deprecated(\n        since = \"0.10.0\",\n        note = \"enable_mbm is deprecated in runtime-spec v1.3.0. Use enable_monitoring instead.\"\n    )]\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// EnableMBM is the flag to indicate if the Intel RDT MBM is enabled. MBM (Memory Bandwidth Monitoring) supports monitoring of\n    /// total and local memory bandwidth for the container.\n    ///\n    /// # Deprecated\n    /// This field is deprecated in runtime-spec v1.3.0.\n    /// Use `enable_monitoring` instead.\n    enable_mbm: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// EnableMonitoring enables resctrl monitoring for the container. This will\n    /// create a dedicated resctrl monitoring group for the container.\n    enable_monitoring: Option<bool>,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxPersonality represents the Linux personality syscall input.\npub struct LinuxPersonality {\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Domain for the personality.\n    domain: LinuxPersonalityDomain,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Additional flags\n    flags: Option<Vec<String>>,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n/// Define domain and flags for LinuxPersonality.\npub enum LinuxPersonalityDomain {\n    #[serde(rename = \"LINUX\")]\n    #[strum(serialize = \"LINUX\")]\n    /// PerLinux is the standard Linux personality.\n    #[default]\n    PerLinux,\n\n    #[serde(rename = \"LINUX32\")]\n    #[strum(serialize = \"LINUX32\")]\n    /// PerLinux32 sets personality to 32 bit.\n    PerLinux32,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxMemoryPolicy represents input for the set_mempolicy syscall.\npub struct LinuxMemoryPolicy {\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Mode for the set_mempolicy syscall.\n    mode: MemoryPolicyModeType,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Nodes representing the nodemask for the set_mempolicy syscall in comma separated ranges format.\n    /// Format: \"&lt;node0&gt;-&lt;node1&gt;,&lt;node2&gt;,&lt;node3&gt;-&lt;node4&gt;,...\"\n    nodes: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", get_mut = \"pub\", set = \"pub\")]\n    /// Flags for the set_mempolicy syscall.\n    flags: Option<Vec<MemoryPolicyFlagType>>,\n}\n\nimpl Default for LinuxMemoryPolicy {\n    fn default() -> Self {\n        Self {\n            mode: MemoryPolicyModeType::MpolDefault,\n            nodes: None,\n            flags: None,\n        }\n    }\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n/// MemoryPolicyModeType defines the memory policy mode.\npub enum MemoryPolicyModeType {\n    #[serde(rename = \"MPOL_DEFAULT\")]\n    #[strum(serialize = \"MPOL_DEFAULT\")]\n    /// MpolDefault - Default NUMA policy.\n    #[default]\n    MpolDefault,\n\n    #[serde(rename = \"MPOL_BIND\")]\n    #[strum(serialize = \"MPOL_BIND\")]\n    /// MpolBind - Bind memory allocation to specific nodes.\n    MpolBind,\n\n    #[serde(rename = \"MPOL_INTERLEAVE\")]\n    #[strum(serialize = \"MPOL_INTERLEAVE\")]\n    /// MpolInterleave - Interleave memory allocation across nodes.\n    MpolInterleave,\n\n    #[serde(rename = \"MPOL_WEIGHTED_INTERLEAVE\")]\n    #[strum(serialize = \"MPOL_WEIGHTED_INTERLEAVE\")]\n    /// MpolWeightedInterleave - Weighted interleave memory allocation across nodes.\n    MpolWeightedInterleave,\n\n    #[serde(rename = \"MPOL_PREFERRED\")]\n    #[strum(serialize = \"MPOL_PREFERRED\")]\n    /// MpolPreferred - Prefer memory allocation from specific nodes.\n    MpolPreferred,\n\n    #[serde(rename = \"MPOL_PREFERRED_MANY\")]\n    #[strum(serialize = \"MPOL_PREFERRED_MANY\")]\n    /// MpolPreferredMany - Prefer memory allocation from multiple nodes.\n    MpolPreferredMany,\n\n    #[serde(rename = \"MPOL_LOCAL\")]\n    #[strum(serialize = \"MPOL_LOCAL\")]\n    /// MpolLocal - Local node memory allocation.\n    MpolLocal,\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n/// MemoryPolicyFlagType defines the memory policy flags.\npub enum MemoryPolicyFlagType {\n    #[serde(rename = \"MPOL_F_NUMA_BALANCING\")]\n    #[strum(serialize = \"MPOL_F_NUMA_BALANCING\")]\n    /// MpolFNumaBalancing - Enable NUMA balancing.\n    MpolFNumaBalancing,\n\n    #[serde(rename = \"MPOL_F_RELATIVE_NODES\")]\n    #[strum(serialize = \"MPOL_F_RELATIVE_NODES\")]\n    /// MpolFRelativeNodes - Use relative node numbers.\n    MpolFRelativeNodes,\n\n    #[serde(rename = \"MPOL_F_STATIC_NODES\")]\n    #[strum(serialize = \"MPOL_F_STATIC_NODES\")]\n    /// MpolFStaticNodes - Use static node numbers.\n    MpolFStaticNodes,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// LinuxTimeOffset specifies the offset for Time Namespace\npub struct LinuxTimeOffset {\n    /// Secs is the offset of clock (in secs) in the container\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    secs: Option<i64>,\n    /// Nanosecs is the additional offset for Secs (in nanosecs)\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    nanosecs: Option<u32>,\n}\n\n#[cfg(feature = \"proptests\")]\nuse quickcheck::{Arbitrary, Gen};\n\n#[cfg(feature = \"proptests\")]\nfn some_none_generator_util<T: Arbitrary>(g: &mut Gen) -> Option<T> {\n    let choice = g.choose(&[true, false]).unwrap();\n    match choice {\n        false => None,\n        true => Some(T::arbitrary(g)),\n    }\n}\n\n#[cfg(feature = \"proptests\")]\nimpl Arbitrary for LinuxDeviceCgroup {\n    fn arbitrary(g: &mut Gen) -> LinuxDeviceCgroup {\n        let typ_choices = [\"a\", \"b\", \"c\", \"u\", \"p\"];\n\n        let typ_chosen = g.choose(&typ_choices).unwrap();\n\n        let typ = match typ_chosen.to_string().as_str() {\n            \"a\" => LinuxDeviceType::A,\n            \"b\" => LinuxDeviceType::B,\n            \"c\" => LinuxDeviceType::C,\n            \"u\" => LinuxDeviceType::U,\n            \"p\" => LinuxDeviceType::P,\n            _ => LinuxDeviceType::A,\n        };\n\n        let access_choices = [\"rwm\", \"m\"];\n        LinuxDeviceCgroup {\n            allow: bool::arbitrary(g),\n            typ: typ.into(),\n            major: some_none_generator_util::<i64>(g),\n            minor: some_none_generator_util::<i64>(g),\n            access: g.choose(&access_choices).unwrap().to_string().into(),\n        }\n    }\n}\n\n#[cfg(feature = \"proptests\")]\nimpl Arbitrary for LinuxMemory {\n    #[allow(deprecated)]\n    fn arbitrary(g: &mut Gen) -> LinuxMemory {\n        LinuxMemory {\n            kernel: some_none_generator_util::<i64>(g),\n            kernel_tcp: some_none_generator_util::<i64>(g),\n            limit: some_none_generator_util::<i64>(g),\n            reservation: some_none_generator_util::<i64>(g),\n            swap: some_none_generator_util::<i64>(g),\n            swappiness: some_none_generator_util::<u64>(g),\n            disable_oom_killer: some_none_generator_util::<bool>(g),\n            use_hierarchy: some_none_generator_util::<bool>(g),\n            check_before_update: some_none_generator_util::<bool>(g),\n        }\n    }\n}\n\n#[cfg(feature = \"proptests\")]\nimpl Arbitrary for LinuxHugepageLimit {\n    fn arbitrary(g: &mut Gen) -> LinuxHugepageLimit {\n        let unit_choice = [\"KB\", \"MB\", \"GB\"];\n        let unit = g.choose(&unit_choice).unwrap();\n        let page_size = u64::arbitrary(g).to_string() + unit;\n\n        LinuxHugepageLimit {\n            page_size,\n            limit: i64::arbitrary(g),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    // LinuxDeviceType test cases\n    #[test]\n    fn device_type_enum_to_str() {\n        let type_a = LinuxDeviceType::A;\n        assert_eq!(type_a.as_str(), \"a\");\n\n        let type_b = LinuxDeviceType::B;\n        assert_eq!(type_b.as_str(), \"b\");\n\n        let type_c = LinuxDeviceType::C;\n        assert_eq!(type_c.as_str(), \"c\");\n    }\n\n    #[test]\n    fn device_type_string_to_enum() {\n        let devtype_str = \"a\";\n        let devtype_enum: LinuxDeviceType = devtype_str.parse().unwrap();\n        assert_eq!(devtype_enum, LinuxDeviceType::A);\n\n        let devtype_str = \"b\";\n        let devtype_enum: LinuxDeviceType = devtype_str.parse().unwrap();\n        assert_eq!(devtype_enum, LinuxDeviceType::B);\n\n        let devtype_str = \"c\";\n        let devtype_enum: LinuxDeviceType = devtype_str.parse().unwrap();\n        assert_eq!(devtype_enum, LinuxDeviceType::C);\n\n        let invalid_devtype_str = \"x\";\n        let unknown_devtype = invalid_devtype_str.parse::<LinuxDeviceType>();\n        assert!(unknown_devtype.is_err());\n    }\n\n    // LinuxNamespaceType test cases\n    #[test]\n    fn ns_type_enum_to_string() {\n        let type_a = LinuxNamespaceType::Network;\n        assert_eq!(type_a.to_string(), \"net\");\n\n        let type_b = LinuxNamespaceType::Mount;\n        assert_eq!(type_b.to_string(), \"mnt\");\n\n        let type_c = LinuxNamespaceType::Ipc;\n        assert_eq!(type_c.to_string(), \"ipc\");\n    }\n\n    #[test]\n    fn ns_type_string_to_enum() {\n        let nstype_str = \"net\";\n        let nstype_enum = LinuxNamespaceType::try_from(nstype_str).unwrap();\n        assert_eq!(nstype_enum, LinuxNamespaceType::Network);\n\n        let nstype_str = \"network\";\n        let nstype_enum = LinuxNamespaceType::try_from(nstype_str).unwrap();\n        assert_eq!(nstype_enum, LinuxNamespaceType::Network);\n\n        let nstype_str = \"ipc\";\n        let nstype_enum = LinuxNamespaceType::try_from(nstype_str).unwrap();\n        assert_eq!(nstype_enum, LinuxNamespaceType::Ipc);\n\n        let nstype_str = \"cgroup\";\n        let nstype_enum = LinuxNamespaceType::try_from(nstype_str).unwrap();\n        assert_eq!(nstype_enum, LinuxNamespaceType::Cgroup);\n\n        let nstype_str = \"mount\";\n        let nstype_enum = LinuxNamespaceType::try_from(nstype_str).unwrap();\n        assert_eq!(nstype_enum, LinuxNamespaceType::Mount);\n\n        let invalid_nstype_str = \"xxx\";\n        let unknown_nstype = LinuxNamespaceType::try_from(invalid_nstype_str);\n        assert!(unknown_nstype.is_err());\n    }\n\n    // LinuxSeccompAction test cases\n    #[test]\n    fn seccomp_action_enum_to_string() {\n        let type_a = LinuxSeccompAction::ScmpActKill;\n        assert_eq!(type_a.to_string(), \"SCMP_ACT_KILL\");\n\n        let type_b = LinuxSeccompAction::ScmpActAllow;\n        assert_eq!(type_b.to_string(), \"SCMP_ACT_ALLOW\");\n\n        let type_c = LinuxSeccompAction::ScmpActNotify;\n        assert_eq!(type_c.to_string(), \"SCMP_ACT_NOTIFY\");\n    }\n\n    #[test]\n    fn seccomp_action_string_to_enum() {\n        let action_str = \"SCMP_ACT_KILL\";\n        let action_enum: LinuxSeccompAction = action_str.parse().unwrap();\n        assert_eq!(action_enum, LinuxSeccompAction::ScmpActKill);\n\n        let action_str = \"SCMP_ACT_ALLOW\";\n        let action_enum: LinuxSeccompAction = action_str.parse().unwrap();\n        assert_eq!(action_enum, LinuxSeccompAction::ScmpActAllow);\n\n        let action_str = \"SCMP_ACT_NOTIFY\";\n        let action_enum: LinuxSeccompAction = action_str.parse().unwrap();\n        assert_eq!(action_enum, LinuxSeccompAction::ScmpActNotify);\n\n        let invalid_action_str = \"x\";\n        let unknown_action = invalid_action_str.parse::<LinuxSeccompAction>();\n        assert!(unknown_action.is_err());\n    }\n\n    // LinuxSeccomp Arch test cases\n    #[test]\n    fn seccomp_arch_enum_to_string() {\n        let type_a = Arch::ScmpArchX86_64;\n        assert_eq!(type_a.to_string(), \"SCMP_ARCH_X86_64\");\n\n        let type_b = Arch::ScmpArchAarch64;\n        assert_eq!(type_b.to_string(), \"SCMP_ARCH_AARCH64\");\n\n        let type_c = Arch::ScmpArchPpc64le;\n        assert_eq!(type_c.to_string(), \"SCMP_ARCH_PPC64LE\");\n\n        let type_d = Arch::ScmpArchParisc;\n        assert_eq!(type_d.to_string(), \"SCMP_ARCH_PARISC\");\n\n        let type_e = Arch::ScmpArchParisc64;\n        assert_eq!(type_e.to_string(), \"SCMP_ARCH_PARISC64\");\n\n        let type_f = Arch::ScmpArchLoongarch64;\n        assert_eq!(type_f.to_string(), \"SCMP_ARCH_LOONGARCH64\");\n\n        let type_g = Arch::ScmpArchM68k;\n        assert_eq!(type_g.to_string(), \"SCMP_ARCH_M68K\");\n\n        let type_h = Arch::ScmpArchSh;\n        assert_eq!(type_h.to_string(), \"SCMP_ARCH_SH\");\n\n        let type_i = Arch::ScmpArchSheb;\n        assert_eq!(type_i.to_string(), \"SCMP_ARCH_SHEB\");\n    }\n\n    #[test]\n    fn seccomp_arch_string_to_enum() {\n        let arch_type_str = \"SCMP_ARCH_X86_64\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchX86_64);\n\n        let arch_type_str = \"SCMP_ARCH_AARCH64\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchAarch64);\n\n        let arch_type_str = \"SCMP_ARCH_PPC64LE\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchPpc64le);\n\n        let arch_type_str = \"SCMP_ARCH_PARISC\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchParisc);\n\n        let arch_type_str = \"SCMP_ARCH_PARISC64\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchParisc64);\n\n        let arch_type_str = \"SCMP_ARCH_LOONGARCH64\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchLoongarch64);\n\n        let arch_type_str = \"SCMP_ARCH_M68K\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchM68k);\n\n        let arch_type_str = \"SCMP_ARCH_SH\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchSh);\n\n        let arch_type_str = \"SCMP_ARCH_SHEB\";\n        let arch_type_enum: Arch = arch_type_str.parse().unwrap();\n        assert_eq!(arch_type_enum, Arch::ScmpArchSheb);\n\n        let invalid_arch_str = \"x\";\n        let unknown_arch = invalid_arch_str.parse::<Arch>();\n        assert!(unknown_arch.is_err());\n    }\n\n    // LinuxSeccompFilterFlag test cases\n    #[test]\n    fn seccomp_filter_flag_enum_to_string() {\n        let type_a = LinuxSeccompFilterFlag::SeccompFilterFlagLog;\n        assert_eq!(type_a.to_string(), \"SECCOMP_FILTER_FLAG_LOG\");\n\n        let type_b = LinuxSeccompFilterFlag::SeccompFilterFlagTsync;\n        assert_eq!(type_b.to_string(), \"SECCOMP_FILTER_FLAG_TSYNC\");\n\n        let type_c = LinuxSeccompFilterFlag::SeccompFilterFlagSpecAllow;\n        assert_eq!(type_c.to_string(), \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\");\n\n        let type_d = LinuxSeccompFilterFlag::SeccompFilterFlagWaitKillableRecv;\n        assert_eq!(type_d.to_string(), \"SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV\");\n    }\n\n    #[test]\n    fn seccomp_filter_flag_string_to_enum() {\n        let filter_flag_type_str = \"SECCOMP_FILTER_FLAG_LOG\";\n        let filter_flag_type_enum: LinuxSeccompFilterFlag = filter_flag_type_str.parse().unwrap();\n        assert_eq!(\n            filter_flag_type_enum,\n            LinuxSeccompFilterFlag::SeccompFilterFlagLog\n        );\n\n        let filter_flag_type_str = \"SECCOMP_FILTER_FLAG_TSYNC\";\n        let filter_flag_type_enum: LinuxSeccompFilterFlag = filter_flag_type_str.parse().unwrap();\n        assert_eq!(\n            filter_flag_type_enum,\n            LinuxSeccompFilterFlag::SeccompFilterFlagTsync\n        );\n\n        let filter_flag_type_str = \"SECCOMP_FILTER_FLAG_SPEC_ALLOW\";\n        let filter_flag_type_enum: LinuxSeccompFilterFlag = filter_flag_type_str.parse().unwrap();\n        assert_eq!(\n            filter_flag_type_enum,\n            LinuxSeccompFilterFlag::SeccompFilterFlagSpecAllow\n        );\n\n        let filter_flag_type_str = \"SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV\";\n        let filter_flag_type_enum: LinuxSeccompFilterFlag = filter_flag_type_str.parse().unwrap();\n        assert_eq!(\n            filter_flag_type_enum,\n            LinuxSeccompFilterFlag::SeccompFilterFlagWaitKillableRecv\n        );\n\n        let invalid_filter_flag_str = \"x\";\n        let unknown_arch = invalid_filter_flag_str.parse::<LinuxSeccompFilterFlag>();\n        assert!(unknown_arch.is_err());\n    }\n\n    // LinuxSeccompOperator test cases\n    #[test]\n    fn seccomp_operator_enum_to_string() {\n        let type_a = LinuxSeccompOperator::ScmpCmpNe;\n        assert_eq!(type_a.to_string(), \"SCMP_CMP_NE\");\n\n        let type_b = LinuxSeccompOperator::ScmpCmpMaskedEq;\n        assert_eq!(type_b.to_string(), \"SCMP_CMP_MASKED_EQ\");\n\n        let type_c = LinuxSeccompOperator::ScmpCmpGt;\n        assert_eq!(type_c.to_string(), \"SCMP_CMP_GT\");\n    }\n\n    // LinuxSeccompAction test cases\n    #[test]\n    fn seccomp_action_as_u32() {\n        let action = LinuxSeccompAction::ScmpActErrno;\n        assert_eq!(action.as_u32(Option::None), 0x00050001);\n\n        let action = LinuxSeccompAction::ScmpActErrno;\n        assert_eq!(action.as_u32(Option::Some(10)), 0x00050000 | 10);\n\n        let action = LinuxSeccompAction::ScmpActTrace;\n        assert_eq!(action.as_u32(Option::None), 0x7ff00001);\n\n        let action = LinuxSeccompAction::ScmpActTrace;\n        assert_eq!(action.as_u32(Option::Some(10)), 0x7ff00000 | 10);\n    }\n\n    #[test]\n    fn seccomp_operator_string_to_enum() {\n        let seccomp_operator_str = \"SCMP_CMP_GT\";\n        let seccomp_operator_enum: LinuxSeccompOperator = seccomp_operator_str.parse().unwrap();\n        assert_eq!(seccomp_operator_enum, LinuxSeccompOperator::ScmpCmpGt);\n\n        let seccomp_operator_str = \"SCMP_CMP_NE\";\n        let seccomp_operator_enum: LinuxSeccompOperator = seccomp_operator_str.parse().unwrap();\n        assert_eq!(seccomp_operator_enum, LinuxSeccompOperator::ScmpCmpNe);\n\n        let seccomp_operator_str = \"SCMP_CMP_MASKED_EQ\";\n        let seccomp_operator_enum: LinuxSeccompOperator = seccomp_operator_str.parse().unwrap();\n        assert_eq!(seccomp_operator_enum, LinuxSeccompOperator::ScmpCmpMaskedEq);\n\n        let invalid_seccomp_operator_str = \"x\";\n        let unknown_operator = invalid_seccomp_operator_str.parse::<LinuxSeccompOperator>();\n        assert!(unknown_operator.is_err());\n    }\n\n    // MemoryPolicyModeType test cases\n    #[test]\n    fn memory_policy_mode_enum_to_string() {\n        let mode_a = MemoryPolicyModeType::MpolDefault;\n        assert_eq!(mode_a.to_string(), \"MPOL_DEFAULT\");\n\n        let mode_b = MemoryPolicyModeType::MpolBind;\n        assert_eq!(mode_b.to_string(), \"MPOL_BIND\");\n\n        let mode_c = MemoryPolicyModeType::MpolInterleave;\n        assert_eq!(mode_c.to_string(), \"MPOL_INTERLEAVE\");\n\n        let mode_d = MemoryPolicyModeType::MpolWeightedInterleave;\n        assert_eq!(mode_d.to_string(), \"MPOL_WEIGHTED_INTERLEAVE\");\n\n        let mode_e = MemoryPolicyModeType::MpolPreferred;\n        assert_eq!(mode_e.to_string(), \"MPOL_PREFERRED\");\n\n        let mode_f = MemoryPolicyModeType::MpolPreferredMany;\n        assert_eq!(mode_f.to_string(), \"MPOL_PREFERRED_MANY\");\n\n        let mode_g = MemoryPolicyModeType::MpolLocal;\n        assert_eq!(mode_g.to_string(), \"MPOL_LOCAL\");\n    }\n\n    #[test]\n    fn memory_policy_mode_string_to_enum() {\n        let mode_str = \"MPOL_INTERLEAVE\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolInterleave);\n\n        let mode_str = \"MPOL_BIND\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolBind);\n\n        let mode_str = \"MPOL_DEFAULT\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolDefault);\n\n        let mode_str = \"MPOL_WEIGHTED_INTERLEAVE\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolWeightedInterleave);\n\n        let mode_str = \"MPOL_PREFERRED\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolPreferred);\n\n        let mode_str = \"MPOL_PREFERRED_MANY\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolPreferredMany);\n\n        let mode_str = \"MPOL_LOCAL\";\n        let mode_enum: MemoryPolicyModeType = mode_str.parse().unwrap();\n        assert_eq!(mode_enum, MemoryPolicyModeType::MpolLocal);\n\n        let invalid_mode_str = \"INVALID_MODE\";\n        let unknown_mode = invalid_mode_str.parse::<MemoryPolicyModeType>();\n        assert!(unknown_mode.is_err());\n    }\n\n    // MemoryPolicyFlagType test cases\n    #[test]\n    fn memory_policy_flag_enum_to_string() {\n        let flag_a = MemoryPolicyFlagType::MpolFNumaBalancing;\n        assert_eq!(flag_a.to_string(), \"MPOL_F_NUMA_BALANCING\");\n\n        let flag_b = MemoryPolicyFlagType::MpolFRelativeNodes;\n        assert_eq!(flag_b.to_string(), \"MPOL_F_RELATIVE_NODES\");\n\n        let flag_c = MemoryPolicyFlagType::MpolFStaticNodes;\n        assert_eq!(flag_c.to_string(), \"MPOL_F_STATIC_NODES\");\n    }\n\n    #[test]\n    fn memory_policy_flag_string_to_enum() {\n        let flag_str = \"MPOL_F_NUMA_BALANCING\";\n        let flag_enum: MemoryPolicyFlagType = flag_str.parse().unwrap();\n        assert_eq!(flag_enum, MemoryPolicyFlagType::MpolFNumaBalancing);\n\n        let flag_str = \"MPOL_F_RELATIVE_NODES\";\n        let flag_enum: MemoryPolicyFlagType = flag_str.parse().unwrap();\n        assert_eq!(flag_enum, MemoryPolicyFlagType::MpolFRelativeNodes);\n\n        let flag_str = \"MPOL_F_STATIC_NODES\";\n        let flag_enum: MemoryPolicyFlagType = flag_str.parse().unwrap();\n        assert_eq!(flag_enum, MemoryPolicyFlagType::MpolFStaticNodes);\n\n        let invalid_flag_str = \"INVALID_FLAG\";\n        let unknown_flag = invalid_flag_str.parse::<MemoryPolicyFlagType>();\n        assert!(unknown_flag.is_err());\n    }\n\n    #[test]\n    fn test_linux_memory_policy_serialization() {\n        let memory_policy = LinuxMemoryPolicy {\n            mode: MemoryPolicyModeType::MpolInterleave,\n            nodes: Some(\"0-3,7\".to_string()),\n            flags: Some(vec![\n                MemoryPolicyFlagType::MpolFStaticNodes,\n                MemoryPolicyFlagType::MpolFRelativeNodes,\n            ]),\n        };\n\n        let json = serde_json::to_string(&memory_policy).unwrap();\n        let deserialized: LinuxMemoryPolicy = serde_json::from_str(&json).unwrap();\n\n        assert_eq!(deserialized.mode, MemoryPolicyModeType::MpolInterleave);\n        assert_eq!(deserialized.nodes, Some(\"0-3,7\".to_string()));\n        assert_eq!(\n            deserialized.flags,\n            Some(vec![\n                MemoryPolicyFlagType::MpolFStaticNodes,\n                MemoryPolicyFlagType::MpolFRelativeNodes,\n            ])\n        );\n    }\n\n    #[test]\n    fn test_linux_memory_policy_default() {\n        let memory_policy = LinuxMemoryPolicy::default();\n        assert_eq!(memory_policy.mode, MemoryPolicyModeType::MpolDefault);\n        assert_eq!(memory_policy.nodes, None);\n        assert_eq!(memory_policy.flags, None);\n    }\n}\n"
  },
  {
    "path": "src/runtime/miscellaneous.rs",
    "content": "use crate::error::OciSpecError;\nuse crate::runtime::LinuxIdMapping;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n#[derive(\n    Builder, Clone, CopyGetters, Debug, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Root contains information about the container's root filesystem on the\n/// host.\npub struct Root {\n    /// Path is the absolute path to the container's root filesystem.\n    #[serde(default)]\n    #[getset(get = \"pub\", set = \"pub\")]\n    path: PathBuf,\n\n    /// Readonly makes the root filesystem for the container readonly before\n    /// the process is executed.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    readonly: Option<bool>,\n}\n\n/// Default path for container root is \"./rootfs\" from config.json, with\n/// readonly true\nimpl Default for Root {\n    fn default() -> Self {\n        Root {\n            path: PathBuf::from(\"rootfs\"),\n            readonly: true.into(),\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\", validate = \"Self::validate\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n/// Mount specifies a mount for a container.\npub struct Mount {\n    /// Destination is the absolute path where the mount will be placed in\n    /// the container.\n    destination: PathBuf,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\", rename = \"type\")]\n    /// Type specifies the mount kind.\n    typ: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Source specifies the source path of the mount.\n    source: Option<PathBuf>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Options are fstab style mount options.\n    options: Option<Vec<String>>,\n\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"uidMappings\"\n    )]\n    /// UID mappings for ID-mapped mounts (Linux 5.12+).  \n    ///  \n    /// Specifies how to map UIDs from the source filesystem to the destination mount point.  \n    /// This allows changing file ownership without calling chown.  \n    ///  \n    /// **Important**: If specified, gid_mappings MUST also be specified.  \n    /// The mount options SHOULD include \"idmap\" or \"ridmap\".  \n    ///  \n    /// See: <https://github.com/opencontainers/runtime-spec/blob/main/config.md#posix-platform-mounts>\n    uid_mappings: Option<Vec<LinuxIdMapping>>,\n\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"gidMappings\"\n    )]\n    /// GID mappings for ID-mapped mounts (Linux 5.12+).\n    ///\n    /// Specifies how to map GIDs from the source filesystem to the destination mount point.\n    /// This allows changing file group ownership without calling chown.\n    ///\n    /// **Important**: If specified, `uid_mappings` MUST also be specified.\n    /// The mount options SHOULD include `\"idmap\"` or `\"ridmap\"`.\n    ///\n    /// See: <https://github.com/opencontainers/runtime-spec/blob/main/config.md#posix-platform-mounts>\n    gid_mappings: Option<Vec<LinuxIdMapping>>,\n}\n\n/// utility function to generate default config for mounts.\npub fn get_default_mounts() -> Vec<Mount> {\n    vec![\n        Mount {\n            destination: PathBuf::from(\"/proc\"),\n            typ: \"proc\".to_string().into(),\n            source: PathBuf::from(\"proc\").into(),\n            options: None,\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/dev\"),\n            typ: \"tmpfs\".to_string().into(),\n            source: PathBuf::from(\"tmpfs\").into(),\n            options: vec![\n                \"nosuid\".into(),\n                \"strictatime\".into(),\n                \"mode=755\".into(),\n                \"size=65536k\".into(),\n            ]\n            .into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/dev/pts\"),\n            typ: \"devpts\".to_string().into(),\n            source: PathBuf::from(\"devpts\").into(),\n            options: vec![\n                \"nosuid\".into(),\n                \"noexec\".into(),\n                \"newinstance\".into(),\n                \"ptmxmode=0666\".into(),\n                \"mode=0620\".into(),\n                \"gid=5\".into(),\n            ]\n            .into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/dev/shm\"),\n            typ: \"tmpfs\".to_string().into(),\n            source: PathBuf::from(\"shm\").into(),\n            options: vec![\n                \"nosuid\".into(),\n                \"noexec\".into(),\n                \"nodev\".into(),\n                \"mode=1777\".into(),\n                \"size=65536k\".into(),\n            ]\n            .into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/dev/mqueue\"),\n            typ: \"mqueue\".to_string().into(),\n            source: PathBuf::from(\"mqueue\").into(),\n            options: vec![\"nosuid\".into(), \"noexec\".into(), \"nodev\".into()].into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/sys\"),\n            typ: \"sysfs\".to_string().into(),\n            source: PathBuf::from(\"sysfs\").into(),\n            options: vec![\n                \"nosuid\".into(),\n                \"noexec\".into(),\n                \"nodev\".into(),\n                \"ro\".into(),\n            ]\n            .into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n        Mount {\n            destination: PathBuf::from(\"/sys/fs/cgroup\"),\n            typ: \"cgroup\".to_string().into(),\n            source: PathBuf::from(\"cgroup\").into(),\n            options: vec![\n                \"nosuid\".into(),\n                \"noexec\".into(),\n                \"nodev\".into(),\n                \"relatime\".into(),\n                \"ro\".into(),\n            ]\n            .into(),\n            uid_mappings: None,\n            gid_mappings: None,\n        },\n    ]\n}\n\nimpl MountBuilder {\n    fn validate(&self) -> Result<(), OciSpecError> {\n        let uid_specified = self\n            .uid_mappings\n            .as_ref()\n            .and_then(|v| v.as_ref())\n            .map(|v| !v.is_empty())\n            .unwrap_or(false);\n\n        let gid_specified = self\n            .gid_mappings\n            .as_ref()\n            .and_then(|v| v.as_ref())\n            .map(|v| !v.is_empty())\n            .unwrap_or(false);\n\n        if uid_specified ^ gid_specified {\n            return Err(OciSpecError::Other(\n                \"Mount.uidMappings and Mount.gidMappings must be specified together\".to_string(),\n            ));\n        }\n\n        Ok(())\n    }\n}\n\n/// utility function to generate default rootless config for mounts.\n// TODO(saschagrunert): remove once clippy does not report this false positive any more. We cannot\n// use `inspect` instead of `map` because we need to mutate the mounts.\n// Ref: https://github.com/rust-lang/rust-clippy/issues/13185\n#[allow(clippy::manual_inspect)]\npub fn get_rootless_mounts() -> Vec<Mount> {\n    let mut mounts = get_default_mounts();\n    mounts\n        .iter_mut()\n        .find(|m| m.destination.to_string_lossy() == \"/dev/pts\")\n        .map(|m| {\n            if let Some(opts) = &mut m.options {\n                opts.retain(|o| o != \"gid=5\")\n            }\n            m\n        });\n    mounts\n        .iter_mut()\n        .find(|m| m.destination.to_string_lossy() == \"/sys\")\n        .map(|m| {\n            m.typ = Some(\"none\".to_string());\n            m.source = Some(\"/sys\".into());\n            if let Some(o) = m.options.as_mut() {\n                o.push(\"rbind\".to_string())\n            }\n            m\n        });\n    mounts\n}\n"
  },
  {
    "path": "src/runtime/mod.rs",
    "content": "//! [OCI runtime spec](https://github.com/opencontainers/runtime-spec) types and definitions.\n//!\n//! [`Spec`] represents the root object from the specification.\n\nuse derive_builder::Builder;\nuse getset::{Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{\n    collections::HashMap,\n    fs,\n    io::{BufReader, BufWriter, Write},\n    path::{Path, PathBuf},\n};\n\nuse crate::error::{oci_error, OciSpecError, Result};\n\nmod capability;\nmod features;\nmod hooks;\nmod linux;\nmod miscellaneous;\nmod process;\nmod solaris;\nmod state;\nmod test;\nmod version;\nmod vm;\nmod windows;\nmod zos;\n\n// re-export for ease of use\npub use capability::*;\npub use features::*;\npub use hooks::*;\npub use linux::*;\npub use miscellaneous::*;\npub use process::*;\npub use solaris::*;\npub use state::*;\npub use version::*;\npub use vm::*;\npub use windows::*;\npub use zos::*;\n\n/// `config.json` file root object.\n#[derive(\n    Builder, Clone, Debug, Deserialize, Getters, MutGetters, Setters, PartialEq, Eq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct Spec {\n    #[serde(default, rename = \"ociVersion\")]\n    ///  MUST be in SemVer v2.0.0 format and specifies the version of the\n    /// Open Container Initiative  Runtime Specification with which\n    /// the bundle complies. The Open Container Initiative\n    ///  Runtime Specification follows semantic versioning and retains\n    /// forward and backward  compatibility within major versions.\n    /// For example, if a configuration is compliant with\n    ///  version 1.1 of this specification, it is compatible with all\n    /// runtimes that support any 1.1  or later release of this\n    /// specification, but is not compatible with a runtime that supports\n    ///  1.0 and not 1.1.\n    version: String,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies the container's root filesystem. On Windows, for Windows\n    /// Server Containers, this field is REQUIRED. For Hyper-V\n    /// Containers, this field MUST NOT be set.\n    ///\n    /// On all other platforms, this field is REQUIRED.\n    root: Option<Root>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies additional mounts beyond `root`. The runtime MUST mount\n    /// entries in the listed order.\n    ///\n    /// For Linux, the parameters are as documented in\n    /// [`mount(2)`](http://man7.org/linux/man-pages/man2/mount.2.html) system call man page. For\n    /// Solaris, the mount entry corresponds to the 'fs' resource in the\n    /// [`zonecfg(1M)`](http://docs.oracle.com/cd/E86824_01/html/E54764/zonecfg-1m.html) man page.\n    mounts: Option<Vec<Mount>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies the container process. This property is REQUIRED when\n    /// [`start`](https://github.com/opencontainers/runtime-spec/blob/master/runtime.md#start) is\n    /// called.\n    process: Option<Process>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies the container's hostname as seen by processes running\n    /// inside the container. On Linux, for example, this will\n    /// change the hostname in the container [UTS namespace](http://man7.org/linux/man-pages/man7/namespaces.7.html). Depending on your\n    /// [namespace\n    /// configuration](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#namespaces),\n    /// the container UTS namespace may be the runtime UTS namespace.\n    hostname: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies the container's domainame as seen by processes running\n    /// inside the container. On Linux, for example, this will\n    /// change the domainame in the container [UTS namespace](http://man7.org/linux/man-pages/man7/namespaces.7.html). Depending on your\n    /// [namespace\n    /// configuration](https://github.com/opencontainers/runtime-spec/blob/master/config-linux.md#namespaces),\n    /// the container UTS namespace may be the runtime UTS namespace.\n    domainname: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Hooks allow users to specify programs to run before or after various\n    /// lifecycle events. Hooks MUST be called in the listed order.\n    /// The state of the container MUST be passed to hooks over\n    /// stdin so that they may do work appropriate to the current state of\n    /// the container.\n    hooks: Option<Hooks>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Annotations contains arbitrary metadata for the container. This\n    /// information MAY be structured or unstructured. Annotations\n    /// MUST be a key-value map. If there are no annotations then\n    /// this property MAY either be absent or an empty map.\n    ///\n    /// Keys MUST be strings. Keys MUST NOT be an empty string. Keys SHOULD\n    /// be named using a reverse domain notation - e.g.\n    /// com.example.myKey. Keys using the org.opencontainers\n    /// namespace are reserved and MUST NOT be used by subsequent\n    /// specifications. Runtimes MUST handle unknown annotation keys\n    /// like any other unknown property.\n    ///\n    /// Values MUST be strings. Values MAY be an empty string.\n    annotations: Option<HashMap<String, String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Linux is platform-specific configuration for Linux based containers.\n    linux: Option<Linux>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Solaris is platform-specific configuration for Solaris based\n    /// containers.\n    solaris: Option<Solaris>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Windows is platform-specific configuration for Windows based\n    /// containers.\n    windows: Option<Windows>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// VM specifies configuration for Virtual Machine based containers.\n    vm: Option<VM>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// z/OS is platform-specific configuration for z/OS based containers.\n    zos: Option<ZOS>,\n\n    #[deprecated(\n        since = \"0.10.0\",\n        note = \"uid_mappings on the top-level Spec struct has never existed in the OCI runtime spec. Use Linux::uid_mappings or Mount::uid_mappings instead.\"\n    )]\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// UID mappings used for changing file owners w/o calling chown, fs should support it.\n    /// Every mount point could have its own mapping.\n    ///\n    /// # Deprecated\n    /// This field has never existed on the top-level `Spec` struct in the OCI runtime spec.\n    /// Use [`Linux::uid_mappings`] or [`Mount::uid_mappings`] instead.\n    uid_mappings: Option<Vec<LinuxIdMapping>>,\n\n    #[deprecated(\n        since = \"0.10.0\",\n        note = \"gid_mappings on the top-level Spec struct has never existed in the OCI runtime spec. Use Linux::gid_mappings or Mount::gid_mappings instead.\"\n    )]\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// GID mappings used for changing file owners w/o calling chown, fs should support it.\n    /// Every mount point could have its own mapping.\n    ///\n    /// # Deprecated\n    /// This field has never existed on the top-level `Spec` struct in the OCI runtime spec.\n    /// Use [`Linux::gid_mappings`] or [`Mount::gid_mappings`] instead.\n    gid_mappings: Option<Vec<LinuxIdMapping>>,\n}\n\n// This gives a basic boilerplate for Spec that can be used calling\n// Default::default(). The values given are similar to the defaults seen in\n// docker and runc, it creates a containerized shell! (see respective types\n// default impl for more info)\n#[allow(deprecated)]\nimpl Default for Spec {\n    fn default() -> Self {\n        Spec {\n            // Defaults to most current oci version\n            version: String::from(\"1.0.2-dev\"),\n            process: Some(Default::default()),\n            root: Some(Default::default()),\n            hostname: \"youki\".to_string().into(),\n            domainname: None,\n            mounts: get_default_mounts().into(),\n            // Defaults to empty metadata\n            annotations: Some(Default::default()),\n            linux: Some(Default::default()),\n            hooks: None,\n            solaris: None,\n            windows: None,\n            vm: None,\n            zos: None,\n            uid_mappings: None,\n            gid_mappings: None,\n        }\n    }\n}\n\nimpl Spec {\n    /// Load a new `Spec` from the provided JSON file `path`.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io] if the spec does not exist or an\n    /// [OciSpecError::SerDe] if it is invalid.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::runtime::Spec;\n    ///\n    /// let spec = Spec::load(\"config.json\").unwrap();\n    /// ```\n    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {\n        let path = path.as_ref();\n        let file = fs::File::open(path)?;\n        let reader = BufReader::new(file);\n        let s = serde_json::from_reader(reader)?;\n        Ok(s)\n    }\n\n    /// Save a `Spec` to the provided JSON file `path`.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io] if a file cannot be created at the provided\n    /// path or an [OciSpecError::SerDe] if the spec cannot be serialized.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::runtime::Spec;\n    ///\n    /// let mut spec = Spec::load(\"config.json\").unwrap();\n    /// spec.save(\"my_config.json\").unwrap();\n    /// ```\n    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {\n        let path = path.as_ref();\n        let file = fs::File::create(path)?;\n        let mut writer = BufWriter::new(file);\n        serde_json::to_writer(&mut writer, self)?;\n        writer.flush()?;\n        Ok(())\n    }\n\n    /// Canonicalize the `root.path` of the `Spec` for the provided `bundle`.\n    pub fn canonicalize_rootfs<P: AsRef<Path>>(&mut self, bundle: P) -> Result<()> {\n        let root = self\n            .root\n            .as_ref()\n            .ok_or_else(|| oci_error(\"no root path provided for canonicalization\"))?;\n        let path = Self::canonicalize_path(bundle, root.path())?;\n        self.root = Some(\n            RootBuilder::default()\n                .path(path)\n                .readonly(root.readonly().unwrap_or(false))\n                .build()\n                .map_err(|_| oci_error(\"failed to set canonicalized root\"))?,\n        );\n        Ok(())\n    }\n\n    /// Return default rootless spec.\n    /// # Example\n    /// ``` no_run\n    /// use oci_spec::runtime::Spec;\n    ///\n    /// let spec = Spec::rootless(1000, 1000);\n    /// ```\n    pub fn rootless(uid: u32, gid: u32) -> Self {\n        Self {\n            mounts: get_rootless_mounts().into(),\n            linux: Some(Linux::rootless(uid, gid)),\n            ..Default::default()\n        }\n    }\n\n    fn canonicalize_path<B, P>(bundle: B, path: P) -> Result<PathBuf>\n    where\n        B: AsRef<Path>,\n        P: AsRef<Path>,\n    {\n        Ok(if path.as_ref().is_absolute() {\n            fs::canonicalize(path.as_ref())?\n        } else {\n            let canonical_bundle_path = fs::canonicalize(&bundle)?;\n            fs::canonicalize(canonical_bundle_path.join(path.as_ref()))?\n        })\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_canonicalize_rootfs() {\n        let rootfs_name = \"rootfs\";\n        let bundle = tempfile::tempdir().expect(\"failed to create tmp test bundle dir\");\n\n        // On macOS, `$TMPDIR` may not point to canonicalized path.\n        // ```\n        // $ echo $TMPDIR; realpath $TMPDIR\n        // /var/folders/_h/j_17023n23s3_50cq_gwhrrc0000gq/T/\n        // /private/var/folders/_h/j_17023n23s3_50cq_gwhrrc0000gq/T\n        // ```\n        let bundle = fs::canonicalize(bundle.path()).expect(\"failed to canonicalize bundle\");\n\n        let rootfs_absolute_path = bundle.join(rootfs_name);\n        assert!(\n            rootfs_absolute_path.is_absolute(),\n            \"rootfs path is not absolute path\"\n        );\n        fs::create_dir_all(&rootfs_absolute_path).expect(\"failed to create the testing rootfs\");\n        {\n            // Test the case with absolute path\n            let mut spec = SpecBuilder::default()\n                .root(\n                    RootBuilder::default()\n                        .path(rootfs_absolute_path.clone())\n                        .build()\n                        .unwrap(),\n                )\n                .build()\n                .unwrap();\n\n            spec.canonicalize_rootfs(&bundle)\n                .expect(\"failed to canonicalize rootfs\");\n\n            assert_eq!(\n                &rootfs_absolute_path,\n                spec.root.expect(\"no root in spec\").path()\n            );\n        }\n        {\n            // Test the case with relative path\n            let mut spec = SpecBuilder::default()\n                .root(RootBuilder::default().path(rootfs_name).build().unwrap())\n                .build()\n                .unwrap();\n\n            spec.canonicalize_rootfs(&bundle)\n                .expect(\"failed to canonicalize rootfs\");\n\n            assert_eq!(\n                &rootfs_absolute_path,\n                spec.root.expect(\"no root in spec\").path()\n            );\n        }\n    }\n\n    #[test]\n    fn test_load_save() {\n        let spec = Spec {\n            ..Default::default()\n        };\n        let test_dir = tempfile::tempdir().expect(\"failed to create tmp test dir\");\n        let spec_path = test_dir.keep().join(\"config.json\");\n\n        // Test first save the default config, and then load the saved config.\n        // The before and after should be the same.\n        spec.save(&spec_path).expect(\"failed to save spec\");\n        let loaded_spec = Spec::load(&spec_path).expect(\"failed to load the saved spec.\");\n        assert_eq!(\n            spec, loaded_spec,\n            \"The saved spec is not the same as the loaded spec\"\n        );\n    }\n\n    #[test]\n    fn test_rootless() {\n        const UID: u32 = 1000;\n        const GID: u32 = 1000;\n\n        let spec = Spec::default();\n        let spec_rootless = Spec::rootless(UID, GID);\n        assert!(\n            spec != spec_rootless,\n            \"default spec and rootless spec should be different\"\n        );\n\n        // Check rootless linux object.\n        let linux = spec_rootless\n            .linux\n            .expect(\"linux object should not be empty\");\n        let uid_mappings = linux\n            .uid_mappings()\n            .clone()\n            .expect(\"uid mappings should not be empty\");\n        let gid_mappings = linux\n            .gid_mappings()\n            .clone()\n            .expect(\"gid mappings should not be empty\");\n        let namespaces = linux\n            .namespaces()\n            .clone()\n            .expect(\"namespaces should not be empty\");\n        assert_eq!(uid_mappings.len(), 1, \"uid mappings length should be 1\");\n        assert_eq!(\n            uid_mappings[0].host_id(),\n            UID,\n            \"uid mapping host id should be as defined\"\n        );\n        assert_eq!(gid_mappings.len(), 1, \"gid mappings length should be 1\");\n        assert_eq!(\n            gid_mappings[0].host_id(),\n            GID,\n            \"gid mapping host id should be as defined\"\n        );\n        assert!(\n            !namespaces\n                .iter()\n                .any(|ns| ns.typ() == LinuxNamespaceType::Network),\n            \"rootless spec should not contain network namespace type\"\n        );\n        assert!(\n            namespaces\n                .iter()\n                .any(|ns| ns.typ() == LinuxNamespaceType::User),\n            \"rootless spec should contain user namespace type\"\n        );\n        assert!(\n            linux.resources().is_none(),\n            \"resources in rootless spec should be empty\"\n        );\n\n        // Check rootless mounts.\n        let mounts = spec_rootless.mounts.expect(\"mounts should not be empty\");\n        assert!(\n            !mounts.iter().any(|m| {\n                if m.destination().to_string_lossy() == \"/dev/pts\" {\n                    m.options()\n                        .clone()\n                        .expect(\"options should not be empty\")\n                        .iter()\n                        .any(|o| o == \"gid=5\")\n                } else {\n                    false\n                }\n            }),\n            \"gid=5 in rootless should not be present\"\n        );\n        let sys_mount = mounts\n            .iter()\n            .find(|m| m.destination().to_string_lossy() == \"/sys\")\n            .expect(\"sys mount should be present\");\n        assert_eq!(\n            sys_mount.typ(),\n            &Some(\"none\".to_string()),\n            \"type should be changed in sys mount\"\n        );\n        assert_eq!(\n            sys_mount\n                .source()\n                .clone()\n                .expect(\"source should not be empty in sys mount\")\n                .to_string_lossy(),\n            \"/sys\",\n            \"source should be changed in sys mount\"\n        );\n        assert!(\n            sys_mount\n                .options()\n                .clone()\n                .expect(\"options should not be empty in sys mount\")\n                .iter()\n                .any(|o| o == \"rbind\"),\n            \"rbind option should be present in sys mount\"\n        );\n\n        // Check that some other objects have same values.\n        assert!(spec.process == spec_rootless.process);\n        assert!(spec.root == spec_rootless.root);\n        assert!(spec.hooks == spec_rootless.hooks);\n    }\n}\n"
  },
  {
    "path": "src/runtime/process.rs",
    "content": "use crate::{\n    error::OciSpecError,\n    runtime::{Capabilities, Capability},\n};\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, MutGetters, Setters};\nuse regex::Regex;\nuse serde::{de, Deserialize, Deserializer, Serialize};\nuse std::path::PathBuf;\nuse std::sync::OnceLock;\nuse strum_macros::{Display as StrumDisplay, EnumString};\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Deserialize,\n    Getters,\n    MutGetters,\n    Setters,\n    Eq,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Process contains information to start a specific application inside the\n/// container.\npub struct Process {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Terminal creates an interactive terminal for the container.\n    terminal: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// ConsoleSize specifies the size of the console.\n    console_size: Option<Box>,\n\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// User specifies user information for the process.\n    user: User,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Args specifies the binary and arguments for the application to\n    /// execute.\n    args: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// CommandLine specifies the full command line for the application to\n    /// execute on Windows.\n    command_line: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Env populates the process environment for the process.\n    env: Option<Vec<String>>,\n\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Cwd is the current working directory for the process and must be\n    /// relative to the container's root.\n    cwd: PathBuf,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Capabilities are Linux capabilities that are kept for the process.\n    capabilities: Option<LinuxCapabilities>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Rlimits specifies rlimit options to apply to the process.\n    rlimits: Option<Vec<PosixRlimit>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// NoNewPrivileges controls whether additional privileges could be\n    /// gained by processes in the container.\n    no_new_privileges: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// ApparmorProfile specifies the apparmor profile for the container.\n    apparmor_profile: Option<String>,\n\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Specify an oom_score_adj for the container.\n    oom_score_adj: Option<i32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// SelinuxLabel specifies the selinux context that the container\n    /// process is run as.\n    selinux_label: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// IOPriority contains the I/O priority settings for the cgroup.\n    io_priority: Option<LinuxIOPriority>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Scheduler specifies the scheduling attributes for a process\n    scheduler: Option<Scheduler>,\n\n    #[serde(\n        rename = \"execCPUAffinity\",\n        default,\n        skip_serializing_if = \"Option::is_none\"\n    )]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// ExecCPUAffinity specifies the cpu affinity for a process\n    exec_cpu_affinity: Option<ExecCPUAffinity>,\n}\n\n// Default impl for processes in the container\nimpl Default for Process {\n    fn default() -> Self {\n        Process {\n            // Don't create an interactive terminal for container by default\n            terminal: false.into(),\n            // Gives default console size of 0, 0\n            console_size: Default::default(),\n            // Gives process a uid and gid of 0 (root)\n            user: Default::default(),\n            // By default executes sh command, giving user shell\n            args: vec![\"sh\".to_string()].into(),\n            // Sets linux default environment for binaries and default xterm emulator\n            env: vec![\n                \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\".into(),\n                \"TERM=xterm\".into(),\n            ]\n            .into(),\n            // Sets cwd of process to the container root by default\n            cwd: \"/\".into(),\n            // By default does not allow process to gain additional privileges\n            no_new_privileges: true.into(),\n            // Empty String, no default apparmor\n            apparmor_profile: Default::default(),\n            // Empty String, no default selinux\n            selinux_label: Default::default(),\n            // Empty String, no default scheduler\n            scheduler: Default::default(),\n            // See impl Default for LinuxCapabilities\n            capabilities: Some(Default::default()),\n            // Sets the default maximum of 1024 files the process can open\n            // This is the same as the linux kernel default\n            rlimits: vec![PosixRlimit {\n                typ: PosixRlimitType::RlimitNofile,\n                hard: 1024,\n                soft: 1024,\n            }]\n            .into(),\n            oom_score_adj: None,\n            command_line: None,\n            // Empty IOPriority, no default iopriority\n            io_priority: Default::default(),\n            exec_cpu_affinity: Default::default(),\n        }\n    }\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// Box specifies dimensions of a rectangle. Used for specifying the size of\n/// a console.\npub struct Box {\n    #[serde(default)]\n    /// Height is the vertical dimension of a box.\n    height: u64,\n\n    #[serde(default)]\n    /// Width is the horizontal dimension of a box.\n    width: u64,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n/// Available rlimit types (see <https://man7.org/linux/man-pages/man2/getrlimit.2.html>)\npub enum PosixRlimitType {\n    /// Limit in seconds of the amount of CPU time that the process can consume.\n    #[default]\n    RlimitCpu,\n\n    /// Maximum size in bytes of the files that the process creates.\n    RlimitFsize,\n\n    /// Maximum size of the process's data segment (init data, uninit data and\n    /// heap) in bytes.\n    RlimitData,\n\n    /// Maximum size of the process stack in bytes.\n    RlimitStack,\n\n    /// Maximum size of a core dump file in bytes.\n    RlimitCore,\n\n    /// Limit on the process's resident set (the number of virtual pages\n    /// resident in RAM).\n    RlimitRss,\n\n    /// Limit on number of threads for the real uid calling processes.\n    RlimitNproc,\n\n    /// One greator than the maximum number of file descriptors that one process\n    /// may open.\n    RlimitNofile,\n\n    /// Maximum number of bytes of memory that may be locked into RAM.\n    RlimitMemlock,\n\n    /// Maximum size of the process's virtual memory(address space) in bytes.\n    RlimitAs,\n\n    /// Limit on the number of locks and leases for the process.\n    RlimitLocks,\n\n    /// Limit on number of signals that may be queued for the process.\n    RlimitSigpending,\n\n    /// Limit on the number of bytes that can be allocated for POSIX message\n    /// queue.\n    RlimitMsgqueue,\n\n    /// Specifies a ceiling to which the process's nice value can be raised.\n    RlimitNice,\n\n    /// Specifies a ceiling on the real-time priority.\n    RlimitRtprio,\n\n    /// This is a limit (in microseconds) on the amount of CPU time that a\n    /// process scheduled under a real-time scheduling policy may consume\n    /// without making a blocking system call.\n    RlimitRttime,\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// RLimit types and restrictions.\npub struct PosixRlimit {\n    #[serde(rename = \"type\")]\n    /// Type of Rlimit to set\n    typ: PosixRlimitType,\n\n    #[serde(default)]\n    /// Hard limit for specified type\n    hard: u64,\n\n    #[serde(default)]\n    /// Soft limit for specified type\n    soft: u64,\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Getters,\n    MutGetters,\n    Setters,\n    Eq,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// User id (uid) and group id (gid) tracks file permissions.\npub struct User {\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// UID is the user id.\n    uid: u32,\n\n    #[serde(default)]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// GID is the group id.\n    gid: u32,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get_copy = \"pub\", set = \"pub\")]\n    /// Specifies the umask of the user.\n    umask: Option<u32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// AdditionalGids are additional group ids set for the container's\n    /// process.\n    additional_gids: Option<Vec<u32>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\n    /// Username is the user name.\n    username: Option<String>,\n}\n\n#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// LinuxCapabilities specifies the list of allowed capabilities that are\n/// kept for a process. <http://man7.org/linux/man-pages/man7/capabilities.7.html>\npub struct LinuxCapabilities {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Bounding is the set of capabilities checked by the kernel.\n    bounding: Option<Capabilities>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Effective is the set of capabilities checked by the kernel.\n    effective: Option<Capabilities>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Inheritable is the capabilities preserved across execve.\n    inheritable: Option<Capabilities>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Permitted is the limiting superset for effective capabilities.\n    permitted: Option<Capabilities>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Ambient is the ambient set of capabilities that are kept.\n    ambient: Option<Capabilities>,\n}\n\n// Default container's linux capabilities:\n// CAP_AUDIT_WRITE gives container ability to write to linux audit logs,\n// CAP_KILL gives container ability to kill non root processes\n// CAP_NET_BIND_SERVICE allows container to bind to ports below 1024\nimpl Default for LinuxCapabilities {\n    fn default() -> Self {\n        let audit_write = Capability::AuditWrite;\n        let cap_kill = Capability::Kill;\n        let net_bind = Capability::NetBindService;\n        let default_vec = vec![audit_write, cap_kill, net_bind]\n            .into_iter()\n            .collect::<Capabilities>();\n        LinuxCapabilities {\n            bounding: default_vec.clone().into(),\n            effective: default_vec.clone().into(),\n            inheritable: default_vec.clone().into(),\n            permitted: default_vec.clone().into(),\n            ambient: default_vec.into(),\n        }\n    }\n}\n\n#[derive(\n    Builder, Clone, Copy, CopyGetters, Debug, Default, Deserialize, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// RLimit types and restrictions.\npub struct LinuxIOPriority {\n    #[serde(default)]\n    /// Class represents an I/O scheduling class.\n    class: IOPriorityClass,\n\n    #[serde(default)]\n    /// Priority for the io operation\n    priority: i64,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString,\n)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n/// IOPriorityClass represents an I/O scheduling class.\npub enum IOPriorityClass {\n    /// This is the realtime io class. This scheduling class is given\n    /// higher priority than any other in the system, processes from this class are\n    /// given first access to the disk every time. Thus it needs to be used with some\n    /// care, one io RT process can starve the entire system. Within the RT class,\n    /// there are 8 levels of class data that determine exactly how much time this\n    /// process needs the disk for on each service. In the future this might change\n    /// to be more directly mappable to performance, by passing in a wanted data\n    /// rate instead\n    IoprioClassRt,\n    /// This is the best-effort scheduling class, which is the default\n    /// for any process that hasn't set a specific io priority. The class data\n    /// determines how much io bandwidth the process will get, it's directly mappable\n    /// to the cpu nice levels just more coarsely implemented. 0 is the highest\n    /// BE prio level, 7 is the lowest. The mapping between cpu nice level and io\n    /// nice level is determined as: io_nice = (cpu_nice + 20) / 5.\n    #[default]\n    IoprioClassBe,\n    /// This is the idle scheduling class, processes running at this\n    /// level only get io time when no one else needs the disk. The idle class has no\n    /// class data, since it doesn't really apply here.\n    IoprioClassIdle,\n}\n\n#[derive(Builder, Clone, Debug, Deserialize, Getters, Setters, Eq, PartialEq, Serialize)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// Scheduler represents the scheduling attributes for a process. It is based on\n/// the Linux sched_setattr(2) syscall.\npub struct Scheduler {\n    /// Policy represents the scheduling policy (e.g., SCHED_FIFO, SCHED_RR, SCHED_OTHER).\n    policy: LinuxSchedulerPolicy,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Nice is the nice value for the process, which affects its priority.\n    nice: Option<i32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Priority represents the static priority of the process.\n    priority: Option<i32>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Flags is an array of scheduling flags.\n    flags: Option<Vec<LinuxSchedulerFlag>>,\n\n    // The following ones are used by the DEADLINE scheduler.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Runtime is the amount of time in nanoseconds during which the process\n    /// is allowed to run in a given period.\n    runtime: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Deadline is the absolute deadline for the process to complete its execution.\n    deadline: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Period is the length of the period in nanoseconds used for determining the process runtime.\n    period: Option<u64>,\n}\n\n/// Default scheduler is SCHED_OTHER with no priority.\nimpl Default for Scheduler {\n    fn default() -> Self {\n        Self {\n            policy: LinuxSchedulerPolicy::default(),\n            nice: None,\n            priority: None,\n            flags: None,\n            runtime: None,\n            deadline: None,\n            period: None,\n        }\n    }\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n///  LinuxSchedulerPolicy represents different scheduling policies used with the Linux Scheduler\npub enum LinuxSchedulerPolicy {\n    /// SchedOther is the default scheduling policy\n    SchedOther,\n    /// SchedFIFO is the First-In-First-Out scheduling policy\n    SchedFifo,\n    /// SchedRR is the Round-Robin scheduling policy\n    SchedRr,\n    /// SchedBatch is the Batch scheduling policy\n    SchedBatch,\n    /// SchedISO is the Isolation scheduling policy\n    SchedIso,\n    /// SchedIdle is the Idle scheduling policy\n    SchedIdle,\n    /// SchedDeadline is the Deadline scheduling policy\n    SchedDeadline,\n}\n\n/// Default LinuxSchedulerPolicy is SchedOther\nimpl Default for LinuxSchedulerPolicy {\n    fn default() -> Self {\n        LinuxSchedulerPolicy::SchedOther\n    }\n}\n\n#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]\n#[strum(serialize_all = \"SCREAMING_SNAKE_CASE\")]\n#[serde(rename_all = \"SCREAMING_SNAKE_CASE\")]\n///  LinuxSchedulerFlag represents the flags used by the Linux Scheduler.\npub enum LinuxSchedulerFlag {\n    /// SchedFlagResetOnFork represents the reset on fork scheduling flag\n    SchedResetOnFork,\n    /// SchedFlagReclaim represents the reclaim scheduling flag\n    SchedFlagReclaim,\n    /// SchedFlagDLOverrun represents the deadline overrun scheduling flag\n    SchedFlagDLOverrun,\n    /// SchedFlagKeepPolicy represents the keep policy scheduling flag\n    SchedFlagKeepPolicy,\n    /// SchedFlagKeepParams represents the keep parameters scheduling flag\n    SchedFlagKeepParams,\n    /// SchedFlagUtilClampMin represents the utilization clamp minimum scheduling flag\n    SchedFlagUtilClampMin,\n    /// SchedFlagUtilClampMin represents the utilization clamp maximum scheduling flag\n    SchedFlagUtilClampMax,\n}\n\n/// Default LinuxSchedulerFlag is SchedResetOnFork\nimpl Default for LinuxSchedulerFlag {\n    fn default() -> Self {\n        LinuxSchedulerFlag::SchedResetOnFork\n    }\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(validate = \"Self::validate\", error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// ExecCPUAffinity specifies CPU affinity used to execute the process.\n/// This setting is not applicable to the container's init process.\npub struct ExecCPUAffinity {\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        deserialize_with = \"deserialize\"\n    )]\n    /// initial is a list of CPUs a runtime parent process to be run on\n    /// initially, before the transition to container's cgroup.\n    /// This is a a comma-separated list, with dashes to represent ranges.\n    /// For example, `0-3,7` represents CPUs 0,1,2,3, and 7.\n    initial: Option<String>,\n\n    #[serde(\n        default,\n        rename = \"final\",\n        skip_serializing_if = \"Option::is_none\",\n        deserialize_with = \"deserialize\"\n    )]\n    /// cpu_affinity_final is a list of CPUs the process will be run on after the transition\n    /// to container's cgroup. The format is the same as for `initial`. If omitted or empty,\n    /// runtime SHOULD NOT change process' CPU affinity after the process is moved to\n    /// container's cgroup, and the final affinity is determined by the Linux kernel.\n    cpu_affinity_final: Option<String>,\n}\n\nimpl ExecCPUAffinityBuilder {\n    fn validate(&self) -> Result<(), OciSpecError> {\n        if let Some(Some(ref s)) = self.initial {\n            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;\n        }\n\n        if let Some(Some(ref s)) = self.cpu_affinity_final {\n            validate_cpu_affinity(s).map_err(|e| OciSpecError::Other(e.to_string()))?;\n        }\n\n        Ok(())\n    }\n}\n\nfn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>\nwhere\n    D: Deserializer<'de>,\n{\n    let value: Option<String> = Option::deserialize(deserializer)?;\n\n    if let Some(ref s) = value {\n        validate_cpu_affinity(s).map_err(de::Error::custom)?;\n    }\n\n    Ok(value)\n}\n\nfn exec_cpu_affinity_regex() -> &'static Regex {\n    static EXEC_CPU_AFFINITY_REGEX: OnceLock<Regex> = OnceLock::new();\n    EXEC_CPU_AFFINITY_REGEX.get_or_init(|| {\n        Regex::new(r\"^(\\d+(-\\d+)?)(,\\d+(-\\d+)?)*$\")\n            .expect(\"Failed to create regex for execCPUAffinity\")\n    })\n}\n\nfn validate_cpu_affinity(s: &str) -> Result<(), String> {\n    if !exec_cpu_affinity_regex().is_match(s) {\n        return Err(format!(\"Invalid execCPUAffinity format: {s}\"));\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use serde_json::json;\n\n    // PosixRlimitType test cases\n    #[test]\n    fn posix_rlimit_type_enum_to_string() {\n        let type_a = PosixRlimitType::RlimitCpu;\n        assert_eq!(type_a.to_string(), \"RLIMIT_CPU\");\n\n        let type_b = PosixRlimitType::RlimitData;\n        assert_eq!(type_b.to_string(), \"RLIMIT_DATA\");\n\n        let type_c = PosixRlimitType::RlimitNofile;\n        assert_eq!(type_c.to_string(), \"RLIMIT_NOFILE\");\n    }\n\n    #[test]\n    fn posix_rlimit_type_string_to_enum() {\n        let posix_rlimit_type_str = \"RLIMIT_CPU\";\n        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();\n        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitCpu);\n\n        let posix_rlimit_type_str = \"RLIMIT_DATA\";\n        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();\n        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitData);\n\n        let posix_rlimit_type_str = \"RLIMIT_NOFILE\";\n        let posix_rlimit_type_enum: PosixRlimitType = posix_rlimit_type_str.parse().unwrap();\n        assert_eq!(posix_rlimit_type_enum, PosixRlimitType::RlimitNofile);\n\n        let invalid_posix_rlimit_type_str = \"x\";\n        let unknown_rlimit = invalid_posix_rlimit_type_str.parse::<PosixRlimitType>();\n        assert!(unknown_rlimit.is_err());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_valid_initial_final() {\n        let json = json!({\"initial\": \"0-3,7\", \"final\": \"4-6,8\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n\n        let json = json!({\"initial\": \"0-3\", \"final\": \"4-6\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n\n        let json = json!({\"initial\": \"0\", \"final\": \"4\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_invalid_initial() {\n        let json = json!({\"initial\": \"0-3,,7\", \"final\": \"4-6,8\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_invalid_final() {\n        let json = json!({\"initial\": \"0-3,7\", \"final\": \"4-6.,8\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_valid_final() {\n        let json = json!({\"final\": \"0,1,2,3\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n        assert!(result.unwrap().initial.is_none());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_valid_initial() {\n        let json = json!({\"initial\": \"0-1,2-5\"});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n        assert!(result.unwrap().cpu_affinity_final.is_none());\n    }\n\n    #[test]\n    fn exec_cpu_affinity_empty() {\n        let json = json!({});\n        let result: Result<ExecCPUAffinity, _> = serde_json::from_value(json);\n        assert!(result.is_ok());\n        let affinity = result.unwrap();\n        assert!(affinity.initial.is_none());\n        assert!(affinity.cpu_affinity_final.is_none());\n    }\n\n    #[test]\n    fn test_build_valid_input() {\n        let affinity = ExecCPUAffinityBuilder::default()\n            .initial(\"0-3,7,8,9,10\".to_string())\n            .cpu_affinity_final(\"4-6,8\".to_string())\n            .build();\n        assert!(affinity.is_ok());\n        let affinity = affinity.unwrap();\n        assert_eq!(affinity.initial, Some(\"0-3,7,8,9,10\".to_string()));\n        assert_eq!(affinity.cpu_affinity_final, Some(\"4-6,8\".to_string()));\n    }\n\n    #[test]\n    fn test_build_invalid_initial() {\n        let affinity = ExecCPUAffinityBuilder::default()\n            .initial(\"0-3,i\".to_string())\n            .cpu_affinity_final(\"4-6,8\".to_string())\n            .build();\n        let err = affinity.unwrap_err();\n        assert_eq!(err.to_string(), \"Invalid execCPUAffinity format: 0-3,i\");\n\n        let affinity = ExecCPUAffinityBuilder::default()\n            .initial(\"-\".to_string())\n            .cpu_affinity_final(\"4-6,8\".to_string())\n            .build();\n        let err = affinity.unwrap_err();\n        assert_eq!(err.to_string(), \"Invalid execCPUAffinity format: -\");\n    }\n\n    #[test]\n    fn test_build_invalid_final() {\n        let affinity = ExecCPUAffinityBuilder::default()\n            .initial(\"0-3,7\".to_string())\n            .cpu_affinity_final(\"0-l1\".to_string())\n            .build();\n        let err = affinity.unwrap_err();\n        assert_eq!(err.to_string(), \"Invalid execCPUAffinity format: 0-l1\");\n\n        let affinity = ExecCPUAffinityBuilder::default()\n            .initial(\"0-3,7\".to_string())\n            .cpu_affinity_final(\",1,2\".to_string())\n            .build();\n        let err = affinity.unwrap_err();\n        assert_eq!(err.to_string(), \"Invalid execCPUAffinity format: ,1,2\");\n    }\n\n    #[test]\n    fn test_build_empty() {\n        let affinity = ExecCPUAffinityBuilder::default().build();\n        let affinity = affinity.unwrap();\n        assert!(affinity.initial.is_none());\n        assert!(affinity.cpu_affinity_final.is_none());\n    }\n}\n"
  },
  {
    "path": "src/runtime/solaris.rs",
    "content": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Serialize};\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// Solaris contains platform-specific configuration for Solaris application\n/// containers.\npub struct Solaris {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// SMF FMRI which should go \"online\" before we start the container\n    /// process.\n    milestone: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Maximum set of privileges any process in this container can obtain.\n    limitpriv: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The maximum amount of shared memory allowed for this container.\n    max_shm_memory: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specification for automatic creation of network resources for this\n    /// container.\n    anet: Option<Vec<SolarisAnet>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\", rename = \"cappedCPU\")]\n    /// Set limit on the amount of CPU time that can be used by container.\n    capped_cpu: Option<SolarisCappedCPU>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The physical and swap caps on the memory that can be used by this\n    /// container.\n    capped_memory: Option<SolarisCappedMemory>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// SolarisAnet provides the specification for automatic creation of network\n/// resources for this container.\npub struct SolarisAnet {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specify a name for the automatically created VNIC datalink.\n    linkname: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specify the link over which the VNIC will be created.\n    lower_link: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The set of IP addresses that the container can use.\n    allowed_address: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies whether allowedAddress limitation is to be applied to the\n    /// VNIC.\n    configure_allowed_address: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The value of the optional default router.\n    defrouter: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Enable one or more types of link protection.\n    link_protection: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Set the VNIC's macAddress.\n    mac_address: Option<String>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// SolarisCappedCPU allows users to set limit on the amount of CPU time\n/// that can be used by container.\npub struct SolarisCappedCPU {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The amount of CPUs.\n    ncpus: Option<String>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// SolarisCappedMemory allows users to set the physical and swap caps on\n/// the memory that can be used by this container.\npub struct SolarisCappedMemory {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The physical caps on the memory.\n    physical: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// The swap caps on the memory.\n    swap: Option<String>,\n}\n"
  },
  {
    "path": "src/runtime/state.rs",
    "content": "use crate::error::OciSpecError;\n\nuse std::{\n    fs,\n    io::{BufReader, BufWriter, Write},\n    path::{Path, PathBuf},\n};\n\nuse derive_builder::Builder;\nuse getset::{Getters, MutGetters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::{collections::HashMap, fmt::Display};\n\n/// ContainerState represents the state of a container.\n#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Deserialize, Serialize)]\n#[serde(rename_all = \"lowercase\")]\npub enum ContainerState {\n    /// Creating indicates that the container is being created,\n    Creating,\n\n    /// Created indicates that the runtime has finished the create operation,\n    /// but the container exists but has not been run yet.\n    Created,\n\n    /// Running indicates that the container process has executed the\n    /// user-specified program but has not exited\n    Running,\n\n    /// Stopped indicates that the container process has exited,\n    /// and does not have a created or running process.\n    #[default]\n    Stopped,\n}\n\nimpl Display for ContainerState {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ContainerState::Creating => write!(f, \"creating\"),\n            ContainerState::Created => write!(f, \"created\"),\n            ContainerState::Running => write!(f, \"running\"),\n            ContainerState::Stopped => write!(f, \"stopped\"),\n        }\n    }\n}\n\n/// State holds information about the runtime state of the container.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct State {\n    /// version is the version of the specification that is supported.\n    #[serde(default, rename = \"ociVersion\")]\n    version: String,\n\n    /// id is the container ID\n    #[serde(default)]\n    id: String,\n\n    /// status is the runtime status of the container.\n    #[serde(default)]\n    status: ContainerState,\n\n    /// pid is the process ID for the container process.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pid: Option<i32>,\n\n    /// bundle is the path to the container's bundle directory.\n    #[serde(default)]\n    bundle: PathBuf,\n\n    /// annotations are key values associated with the container.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    annotations: Option<HashMap<String, String>>,\n}\n\nimpl State {\n    /// Load a State from the provided JSON file path.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io] if the file does not exist or an\n    /// [OciSpecError::SerDe] if the JSON is invalid.\n    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, OciSpecError> {\n        let path = path.as_ref();\n        let file = fs::File::open(path)?;\n        let reader = BufReader::new(file);\n        let state = serde_json::from_reader(reader)?;\n        Ok(state)\n    }\n\n    /// Save a State to the provided JSON file path.\n    /// # Errors\n    /// This function will return an [OciSpecError::Io] if a file cannot be created at the provided\n    /// path or an [OciSpecError::SerDe] if the state cannot be serialized.\n    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<(), OciSpecError> {\n        let path = path.as_ref();\n        let file = fs::File::create(path)?;\n        let mut writer = BufWriter::new(file);\n        serde_json::to_writer(&mut writer, self)?;\n        writer.flush()?;\n        Ok(())\n    }\n}\n\n/// SeccompFdName is the name of the seccomp notify file descriptor.\n/// Used in ContainerProcessState.fds to identify seccomp listener file descriptors.\n/// See: <https://github.com/opencontainers/runtime-spec/blob/main/specs-go/state.go>\npub const SECCOMP_FD_NAME: &str = \"seccompFd\";\n\n/// ContainerProcessState holds information about the state of a container process.\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    MutGetters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_mut = \"pub\", get = \"pub\", set = \"pub\")]\npub struct ContainerProcessState {\n    /// version is the version of the specification that is supported.\n    #[serde(default, rename = \"ociVersion\")]\n    version: String,\n\n    /// fds is a string array containing the names of the file descriptors passed.\n    /// The index of the name in this array corresponds to index of the file\n    /// descriptor in the `SCM_RIGHTS` array.\n    #[serde(default)]\n    fds: Vec<String>,\n\n    /// pid is the process ID as seen by the runtime.\n    #[serde(default)]\n    pid: i32,\n\n    /// opaque metadata.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    metadata: Option<String>,\n\n    /// state of the container.\n    #[serde(default)]\n    state: State,\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_load_save() {\n        let state = State {\n            ..Default::default()\n        };\n        let test_dir = tempfile::tempdir().expect(\"failed to create tmp test dir\");\n        let state_path = test_dir.keep().join(\"state.json\");\n\n        // Test first save the default config, and then load the saved config.\n        // The before and after should be the same.\n        state.save(&state_path).expect(\"failed to save state\");\n        let loaded_state = State::load(&state_path).expect(\"failed to load state\");\n        assert_eq!(\n            state, loaded_state,\n            \"The saved state is not the same as the loaded state\"\n        );\n    }\n}\n"
  },
  {
    "path": "src/runtime/test/fixture/sample.json",
    "content": "{\n    \"ociVersion\": \"0.5.0-dev\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n            \"gid\": 1,\n            \"additionalGids\": [\n                5,\n                6\n            ]\n        },\n        \"args\": [\n            \"sh\"\n        ],\n        \"env\": [\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n            \"TERM=xterm\"\n        ],\n        \"cwd\": \"/\",\n        \"capabilities\": {\n            \"bounding\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"permitted\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"inheritable\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\",\n                \"CAP_NET_BIND_SERVICE\"\n            ],\n            \"effective\": [\n                \"CAP_AUDIT_WRITE\",\n                \"CAP_KILL\"\n            ],\n            \"ambient\": [\n                \"CAP_NET_BIND_SERVICE\"\n            ]\n        },\n        \"rlimits\": [\n            {\n                \"type\": \"RLIMIT_CORE\",\n                \"hard\": 1024,\n                \"soft\": 1024\n            },\n            {\n                \"type\": \"RLIMIT_NOFILE\",\n                \"hard\": 1024,\n                \"soft\": 1024\n            }\n        ],\n        \"apparmorProfile\": \"acme_secure_profile\",\n        \"selinuxLabel\": \"system_u:system_r:svirt_lxc_net_t:s0:c124,c675\",\n        \"noNewPrivileges\": true,\n        \"execCPUAffinity\": {\n            \"initial\": \"7\",\n            \"final\": \"0-3,7\"\n        }\n    },\n    \"root\": {\n        \"path\": \"rootfs\",\n        \"readonly\": true\n    },\n    \"hostname\": \"slartibartfast\",\n    \"mounts\": [\n        {\n            \"destination\": \"/proc\",\n            \"type\": \"proc\",\n            \"source\": \"proc\"\n        },\n        {\n            \"destination\": \"/dev\",\n            \"type\": \"tmpfs\",\n            \"source\": \"tmpfs\",\n            \"options\": [\n                \"nosuid\",\n                \"strictatime\",\n                \"mode=755\",\n                \"size=65536k\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/pts\",\n            \"type\": \"devpts\",\n            \"source\": \"devpts\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"newinstance\",\n                \"ptmxmode=0666\",\n                \"mode=0620\",\n                \"gid=5\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/shm\",\n            \"type\": \"tmpfs\",\n            \"source\": \"shm\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\",\n                \"mode=1777\",\n                \"size=65536k\"\n            ]\n        },\n        {\n            \"destination\": \"/dev/mqueue\",\n            \"type\": \"mqueue\",\n            \"source\": \"mqueue\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\"\n            ]\n        },\n        {\n            \"destination\": \"/sys\",\n            \"type\": \"sysfs\",\n            \"source\": \"sysfs\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\"\n            ]\n        },\n        {\n            \"destination\": \"/sys/fs/cgroup\",\n            \"type\": \"cgroup\",\n            \"source\": \"cgroup\",\n            \"options\": [\n                \"nosuid\",\n                \"noexec\",\n                \"nodev\",\n                \"relatime\",\n                \"ro\"\n            ]\n        }\n    ],\n    \"hooks\": {\n        \"prestart\": [\n            {\n                \"path\": \"/usr/bin/fix-mounts\",\n                \"args\": [\n                    \"fix-mounts\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            },\n            {\n                \"path\": \"/usr/bin/setup-network\"\n            }\n        ],\n        \"createRuntime\": [\n            {\n                \"path\": \"/usr/bin/fix-mounts\",\n                \"args\": [\n                    \"fix-mounts\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            },\n            {\n                \"path\": \"/usr/bin/setup-network\"\n            }\n        ],\n        \"createContainer\": [\n            {\n                \"path\": \"/usr/bin/mount-hook\",\n                \"args\": [\n                    \"-mount\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            }\n        ],\n        \"startContainer\": [\n            {\n                \"path\": \"/usr/bin/refresh-ldcache\"\n            }\n        ],\n        \"poststart\": [\n            {\n                \"path\": \"/usr/bin/notify-start\",\n                \"timeout\": 5\n            }\n        ],\n        \"poststop\": [\n            {\n                \"path\": \"/usr/sbin/cleanup.sh\",\n                \"args\": [\n                    \"cleanup.sh\",\n                    \"-f\"\n                ]\n            }\n        ]\n    },\n    \"linux\": {\n        \"devices\": [\n            {\n                \"path\": \"/dev/fuse\",\n                \"type\": \"c\",\n                \"major\": 10,\n                \"minor\": 229,\n                \"fileMode\": 438,\n                \"uid\": 0,\n                \"gid\": 0\n            },\n            {\n                \"path\": \"/dev/sda\",\n                \"type\": \"b\",\n                \"major\": 8,\n                \"minor\": 0,\n                \"fileMode\": 432,\n                \"uid\": 0,\n                \"gid\": 0\n            }\n        ],\n        \"netDevices\": {\n            \"eth0\": {\n                \"name\": \"container_eth0\"\n            },\n            \"ens4\": {},\n            \"ens5\": {}\n        },\n        \"uidMappings\": [\n            {\n                \"containerID\": 0,\n                \"hostID\": 1000,\n                \"size\": 32000\n            }\n        ],\n        \"gidMappings\": [\n            {\n                \"containerID\": 0,\n                \"hostID\": 1000,\n                \"size\": 32000\n            }\n        ],\n        \"sysctl\": {\n            \"net.ipv4.ip_forward\": \"1\",\n            \"net.core.somaxconn\": \"256\"\n        },\n        \"cgroupsPath\": \"/myRuntime/myContainer\",\n        \"resources\": {\n            \"network\": {\n                \"classID\": 1048577,\n                \"priorities\": [\n                    {\n                        \"name\": \"eth0\",\n                        \"priority\": 500\n                    },\n                    {\n                        \"name\": \"eth1\",\n                        \"priority\": 1000\n                    }\n                ]\n            },\n            \"pids\": {\n                \"limit\": 32771\n            },\n            \"hugepageLimits\": [\n                {\n                    \"pageSize\": \"2MB\",\n                    \"limit\": 9223372036854772000\n                },\n                {\n                    \"pageSize\": \"64KB\",\n                    \"limit\": 1000000\n                }\n            ],\n            \"oomScoreAdj\": 100,\n            \"memory\": {\n                \"limit\": 536870912,\n                \"reservation\": 536870912,\n                \"swap\": 536870912,\n                \"kernel\": -1,\n                \"kernelTCP\": -1,\n                \"swappiness\": 0,\n                \"disableOOMKiller\": false,\n                \"useHierarchy\": false\n            },\n            \"cpu\": {\n                \"shares\": 1024,\n                \"quota\": 1000000,\n                \"period\": 500000,\n                \"realtimeRuntime\": 950000,\n                \"realtimePeriod\": 1000000,\n                \"cpus\": \"2-3\",\n                \"mems\": \"0-7\"\n            },\n            \"devices\": [\n                {\n                    \"allow\": false,\n                    \"access\": \"rwm\"\n                },\n                {\n                    \"allow\": true,\n                    \"type\": \"c\",\n                    \"major\": 10,\n                    \"minor\": 229,\n                    \"access\": \"rw\"\n                },\n                {\n                    \"allow\": true,\n                    \"type\": \"b\",\n                    \"major\": 8,\n                    \"minor\": 0,\n                    \"access\": \"r\"\n                }\n            ],\n            \"blockIO\": {\n                \"weight\": 10,\n                \"leafWeight\": 10,\n                \"weightDevice\": [\n                    {\n                        \"major\": 8,\n                        \"minor\": 0,\n                        \"weight\": 500,\n                        \"leafWeight\": 300\n                    },\n                    {\n                        \"major\": 8,\n                        \"minor\": 16,\n                        \"weight\": 500\n                    }\n                ],\n                \"throttleReadBpsDevice\": [\n                    {\n                        \"major\": 8,\n                        \"minor\": 0,\n                        \"rate\": 600\n                    }\n                ],\n                \"throttleWriteIOPSDevice\": [\n                    {\n                        \"major\": 8,\n                        \"minor\": 16,\n                        \"rate\": 300\n                    }\n                ]\n            }\n        },\n        \"rootfsPropagation\": \"slave\",\n        \"seccomp\": {\n            \"defaultAction\": \"SCMP_ACT_ALLOW\",\n            \"architectures\": [\n                \"SCMP_ARCH_X86\",\n                \"SCMP_ARCH_X32\"\n            ],\n            \"syscalls\": [\n                {\n                    \"names\": [\n                        \"getcwd\",\n                        \"chmod\"\n                    ],\n                    \"action\": \"SCMP_ACT_ERRNO\"\n                }\n            ]\n        },\n        \"namespaces\": [\n            {\n                \"type\": \"pid\"\n            },\n            {\n                \"type\": \"network\"\n            },\n            {\n                \"type\": \"ipc\"\n            },\n            {\n                \"type\": \"uts\"\n            },\n            {\n                \"type\": \"mount\"\n            },\n            {\n                \"type\": \"user\"\n            },\n            {\n                \"type\": \"cgroup\"\n            }\n        ],\n        \"maskedPaths\": [\n            \"/proc/kcore\",\n            \"/proc/latency_stats\",\n            \"/proc/timer_stats\",\n            \"/proc/sched_debug\"\n        ],\n        \"readonlyPaths\": [\n            \"/proc/asound\",\n            \"/proc/bus\",\n            \"/proc/fs\",\n            \"/proc/irq\",\n            \"/proc/sys\",\n            \"/proc/sysrq-trigger\"\n        ],\n        \"mountLabel\": \"system_u:object_r:svirt_sandbox_file_t:s0:c715,c811\"\n    },\n    \"annotations\": {\n        \"com.example.key1\": \"value1\",\n        \"com.example.key2\": \"value2\"\n    }\n}"
  },
  {
    "path": "src/runtime/test/fixture/sample_state.json",
    "content": "{\n    \"ociVersion\": \"1.0.2\",\n    \"id\": \"oci-container1\",\n    \"status\": \"running\",\n    \"pid\": 4422,\n    \"bundle\": \"/containers/redis\",\n    \"annotations\": {\n        \"myKey\": \"myValue\"\n    }\n}\n"
  },
  {
    "path": "src/runtime/test/fixture/sample_windows.json",
    "content": "{\n    \"ociVersion\": \"0.5.0-dev\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n            \"gid\": 1,\n            \"username\": \"ContainerUser\"\n        },\n        \"args\": [\n            \"cmd\"\n        ],\n        \"env\": [\n            \"PATH=C:\\\\Windows\\\\system32;C:\\\\Windows;\"\n        ],\n        \"cwd\": \"C:\\\\\"\n    },\n    \"root\": {\n        \"path\": \"\"\n    },\n    \"hostname\": \"slartibartfast\",\n    \"mounts\": [\n    ],\n    \"hooks\": {\n    },\n    \"linux\": {\n    },\n    \"windows\": {\n        \"layerFolders\": null,\n        \"resources\": {\n            \"cpu\": {\n                \"shares\": 2\n            }\n        },\n        \"network\": {\n            \"networkNamespace\": \"1124faf5-e1d3-43fe-b758-8e99e5b7fa02\"\n        }\n    },\n    \"annotations\": {\n        \"com.example.key1\": \"value1\",\n        \"com.example.key2\": \"value2\"\n    }\n}\n"
  },
  {
    "path": "src/runtime/test/fixture/sample_zos.json",
    "content": "{\n    \"ociVersion\": \"1.2.1\",\n    \"process\": {\n        \"terminal\": true,\n        \"user\": {\n            \"uid\": 1,\n            \"gid\": 1,\n            \"additionalGids\": [\n                5,\n                6\n            ]\n        },\n        \"args\": [\n            \"sh\"\n        ],\n        \"env\": [\n            \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin\",\n            \"TERM=xterm\"\n        ],\n        \"cwd\": \"/\",\n        \"rlimits\": [\n            {\n                \"type\": \"RLIMIT_NOFILE\",\n                \"hard\": 1024,\n                \"soft\": 1024\n            }\n        ],\n        \"noNewPrivileges\": true\n    },\n    \"root\": {\n        \"path\": \"rootfs\"\n    },\n    \"hostname\": \"slartibartfast\",\n    \"mounts\": [\n        {\n            \"destination\": \"/proc\",\n            \"type\": \"proc\",\n            \"source\": \"proc\"\n        },\n        {\n            \"destination\": \"/dev\",\n            \"type\": \"tfs\",\n            \"source\": \"tmpfs\",\n            \"options\": [\n                \"nosuid\",\n                \"-p 1755\",\n                \"-s 64\"\n            ]\n        }\n    ],\n    \"hooks\": {\n        \"prestart\": [\n            {\n                \"path\": \"/usr/bin/fix-mounts\",\n                \"args\": [\n                    \"fix-mounts\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            },\n            {\n                \"path\": \"/usr/bin/setup-network\"\n            }\n        ],\n        \"createRuntime\": [\n            {\n                \"path\": \"/usr/bin/fix-mounts\",\n                \"args\": [\n                    \"fix-mounts\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            },\n            {\n                \"path\": \"/usr/bin/setup-network\"\n            }\n        ],\n        \"createContainer\": [\n            {\n                \"path\": \"/usr/bin/mount-hook\",\n                \"args\": [\n                    \"-mount\",\n                    \"arg1\",\n                    \"arg2\"\n                ],\n                \"env\": [\n                    \"key1=value1\"\n                ]\n            }\n        ],\n        \"startContainer\": [\n            {\n                \"path\": \"/usr/bin/refresh-ldcache\"\n            }\n        ],\n        \"poststart\": [\n            {\n                \"path\": \"/usr/bin/notify-start\",\n                \"timeout\": 5\n            }\n        ],\n        \"poststop\": [\n            {\n                \"path\": \"/usr/sbin/cleanup.sh\",\n                \"args\": [\n                    \"cleanup.sh\",\n                    \"-f\"\n                ]\n            }\n        ]\n    },\n    \"zos\": {\n        \"namespaces\": [\n            {\n                \"type\": \"pid\"\n            },\n            {\n                \"type\": \"ipc\"\n            },\n            {\n                \"type\": \"uts\"\n            },\n            {\n                \"type\": \"mount\"\n            }\n        ]\n    },\n    \"annotations\": {\n        \"com.example.key1\": \"value1\",\n        \"com.example.key2\": \"value2\"\n    }\n}\n"
  },
  {
    "path": "src/runtime/test.rs",
    "content": "#[cfg(test)]\nuse super::*;\n\n#[test]\nfn serialize_and_deserialize_spec() {\n    let spec: Spec = Default::default();\n    let json_string = serde_json::to_string(&spec).unwrap();\n    let new_spec = serde_json::from_str(&json_string).unwrap();\n    assert_eq!(spec, new_spec);\n}\n\n#[test]\nfn test_linux_device_cgroup_to_string() {\n    let ldc = LinuxDeviceCgroupBuilder::default()\n        .allow(true)\n        .typ(LinuxDeviceType::B)\n        .access(\"rwm\".to_string())\n        .build()\n        .expect(\"build device cgroup\");\n    assert_eq!(ldc.to_string(), \"b *:* rwm\");\n\n    let ldc = LinuxDeviceCgroupBuilder::default()\n        .allow(true)\n        .typ(LinuxDeviceType::A)\n        .major(1)\n        .minor(9)\n        .access(\"rwm\".to_string())\n        .build()\n        .expect(\"build device cgroup\");\n    assert_eq!(ldc.to_string(), \"a 1:9 rwm\");\n}\n\n#[test]\nfn test_load_sample_spec() {\n    let fixture_path = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n        .join(\"src/runtime/test/fixture/sample.json\");\n    let err = Spec::load(fixture_path);\n    assert!(err.is_ok(), \"failed to load spec: {err:?}\");\n}\n\n#[test]\nfn test_load_sample_state() {\n    let fixture_path = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n        .join(\"src/runtime/test/fixture/sample_state.json\");\n    let err = State::load(fixture_path);\n    assert!(err.is_ok(), \"failed to load state: {err:?}\");\n}\n\n#[test]\nfn test_load_sample_windows_spec() {\n    let fixture_path = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n        .join(\"src/runtime/test/fixture/sample_windows.json\");\n    let err = Spec::load(fixture_path);\n    assert!(err.is_ok(), \"failed to load spec: {err:?}\");\n}\n\n#[test]\nfn test_load_sample_zos_spec() {\n    let fixture_path = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n        .join(\"src/runtime/test/fixture/sample_zos.json\");\n    let err = Spec::load(fixture_path);\n    assert!(err.is_ok(), \"failed to load spec: {err:?}\");\n}\n\n#[test]\nfn test_linux_netdevice_lifecycle() {\n    let fixture_path = std::path::PathBuf::from(env!(\"CARGO_MANIFEST_DIR\"))\n        .join(\"src/runtime/test/fixture/sample.json\");\n\n    let spec =\n        Spec::load(fixture_path).unwrap_or_else(|err| panic!(\"Failed to load spec: {}\", err));\n\n    let net_devices = spec.linux.as_ref().unwrap().net_devices().as_ref().unwrap();\n    assert_eq!(net_devices.len(), 3);\n\n    // Get eth0\n    let eth0 = net_devices.get(\"eth0\").unwrap();\n    assert_eq!(eth0.name().as_ref(), Some(&\"container_eth0\".to_string()));\n\n    // Get ens4\n    let ens4 = net_devices.get(\"ens4\").unwrap();\n    assert_eq!(ens4.name().as_ref(), None);\n\n    // Get ens5\n    let ens5 = net_devices.get(\"ens5\").unwrap();\n    assert_eq!(ens5.name().as_ref(), None);\n\n    // Set ens6\n    let mut net_devices = net_devices.clone();\n    let ens6 = LinuxNetDeviceBuilder::default()\n        .name(\"ens6\".to_string())\n        .build()\n        .unwrap();\n    net_devices.insert(\"ens6\".to_string(), ens6);\n    assert_eq!(net_devices.len(), 4);\n\n    // Get ens6\n    let ens6 = net_devices.get(\"ens6\").unwrap();\n    assert_eq!(ens6.name().as_ref(), Some(&\"ens6\".to_string()));\n\n    // Change name of ens6\n    let ens6 = net_devices.get_mut(\"ens6\").unwrap();\n    ens6.set_name(Some(\"ens6_container\".to_string()));\n    assert_eq!(ens6.name().as_ref(), Some(&\"ens6_container\".to_string()));\n}\n"
  },
  {
    "path": "src/runtime/version.rs",
    "content": "use const_format::formatcp;\n\n/// API incompatible changes.\npub const VERSION_MAJOR: u32 = 1;\n\n/// Changing functionality in a backwards-compatible manner\npub const VERSION_MINOR: u32 = 1;\n\n/// Backwards-compatible bug fixes.\npub const VERSION_PATCH: u32 = 0;\n\n/// Indicates development branch. Releases will be empty string.\npub const VERSION_DEV: &str = \"-dev\";\n\n/// Retrieve the version as static str representation.\npub const VERSION: &str = formatcp!(\"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_DEV}\");\n\n/// Retrieve the version as string representation.\n///\n/// Use [`VERSION`] instead.\n#[deprecated]\npub fn version() -> String {\n    VERSION.to_owned()\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    #[allow(deprecated)]\n    fn version_test() {\n        assert_eq!(version(), \"1.1.0-dev\".to_string())\n    }\n}\n"
  },
  {
    "path": "src/runtime/vm.rs",
    "content": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// VM contains information for virtual-machine-based containers.\npub struct VM {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Hypervisor specifies hypervisor-related configuration for\n    /// virtual-machine-based containers.\n    hypervisor: Option<VMHypervisor>,\n\n    /// Kernel specifies kernel-related configuration for\n    /// virtual-machine-based containers.\n    kernel: VMKernel,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Image specifies guest image related configuration for\n    /// virtual-machine-based containers.\n    image: Option<VMImage>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// VMHypervisor contains information about the hypervisor to use for a\n/// virtual machine.\npub struct VMHypervisor {\n    /// Path is the host path to the hypervisor used to manage the virtual\n    /// machine.\n    path: PathBuf,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Parameters specifies parameters to pass to the hypervisor.\n    parameters: Option<Vec<String>>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// VMKernel contains information about the kernel to use for a virtual\n/// machine.\npub struct VMKernel {\n    /// Path is the host path to the kernel used to boot the virtual\n    /// machine.\n    path: PathBuf,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Parameters specifies parameters to pass to the kernel.\n    parameters: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// InitRD is the host path to an initial ramdisk to be used by the\n    /// kernel.\n    initrd: Option<String>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Getters, Setters, Eq, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// VMImage contains information about the virtual machine root image.\npub struct VMImage {\n    /// Path is the host path to the root image that the VM kernel would\n    /// boot into.\n    path: PathBuf,\n\n    /// Format is the root image format type (e.g. \"qcow2\", \"raw\", \"vhd\",\n    /// etc).\n    format: String,\n}\n"
  },
  {
    "path": "src/runtime/windows.rs",
    "content": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::collections::HashMap;\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    CopyGetters,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// Windows defines the runtime configuration for Windows based containers,\n/// including Hyper-V containers.\npub struct Windows {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// LayerFolders contains a list of absolute paths to directories\n    /// containing image layers.\n    layer_folders: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Devices are the list of devices to be mapped into the container.\n    devices: Option<Vec<WindowsDevice>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Resources contains information for handling resource constraints for\n    /// the container.\n    resources: Option<WindowsResources>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// CredentialSpec contains a JSON object describing a group Managed\n    /// Service Account (gMSA) specification.\n    credential_spec: Option<HashMap<String, Option<serde_json::Value>>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Servicing indicates if the container is being started in a mode to\n    /// apply a Windows Update servicing operation.\n    servicing: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// IgnoreFlushesDuringBoot indicates if the container is being started\n    /// in a mode where disk writes are not flushed during its boot\n    /// process.\n    ignore_flushes_during_boot: Option<bool>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// HyperV contains information for running a container with Hyper-V\n    /// isolation.\n    hyperv: Option<WindowsHyperV>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Network restriction configuration.\n    network: Option<WindowsNetwork>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// WindowsDevice represents information about a host device to be mapped\n/// into the container.\npub struct WindowsDevice {\n    /// Device identifier: interface class GUID, etc..\n    id: String,\n\n    /// Device identifier type: \"class\", etc..\n    id_type: String,\n}\n\n#[derive(\n    Builder, Clone, Copy, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// Available windows resources.\npub struct WindowsResources {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Memory restriction configuration.\n    memory: Option<WindowsMemoryResources>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// CPU resource restriction configuration.\n    cpu: Option<WindowsCPUResources>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Storage restriction configuration.\n    storage: Option<WindowsStorageResources>,\n}\n\n#[derive(\n    Builder, Clone, Copy, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// WindowsMemoryResources contains memory resource management settings.\npub struct WindowsMemoryResources {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Memory limit in bytes.\n    limit: Option<u64>,\n}\n\n#[derive(\n    Builder, Clone, Copy, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// WindowsCPUResources contains CPU resource management settings.\npub struct WindowsCPUResources {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Number of CPUs available to the container.\n    count: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// CPU shares (relative weight to other containers with cpu shares).\n    shares: Option<u16>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies the portion of processor cycles that this container can\n    /// use as a percentage times 100.\n    maximum: Option<u16>,\n}\n\n#[derive(\n    Builder, Clone, Copy, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get_copy = \"pub\", set = \"pub\")]\n/// WindowsStorageResources contains storage resource management settings.\npub struct WindowsStorageResources {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies maximum Iops for the system drive.\n    iops: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Specifies maximum bytes per second for the system drive.\n    bps: Option<u64>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Sandbox size specifies the minimum size of the system drive in\n    /// bytes.\n    sandbox_size: Option<u64>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// WindowsHyperV contains information for configuring a container to run\n/// with Hyper-V isolation.\npub struct WindowsHyperV {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// UtilityVMPath is an optional path to the image used for the Utility\n    /// VM.\n    utility_vm_path: Option<String>,\n}\n\n#[derive(\n    Builder, Clone, Debug, Default, Deserialize, Eq, Getters, Setters, PartialEq, Serialize,\n)]\n#[serde(rename_all = \"camelCase\")]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// WindowsNetwork contains network settings for Windows containers.\npub struct WindowsNetwork {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// List of HNS endpoints that the container should connect to.\n    endpoint_list: Option<Vec<String>>,\n\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"allowUnqualifiedDNSQuery\"\n    )]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Specifies if unqualified DNS name resolution is allowed.\n    allow_unqualified_dns_query: Option<bool>,\n\n    #[serde(\n        default,\n        skip_serializing_if = \"Option::is_none\",\n        rename = \"DNSSearchList\"\n    )]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Comma separated list of DNS suffixes to use for name resolution.\n    dns_search_list: Option<Vec<String>>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Name (ID) of the container that we will share with the network\n    /// stack.\n    network_shared_container_name: Option<String>,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// name (ID) of the network namespace that will be used for the\n    /// container.\n    network_namespace: Option<String>,\n}\n"
  },
  {
    "path": "src/runtime/zos.rs",
    "content": "use crate::error::OciSpecError;\nuse derive_builder::Builder;\nuse getset::{CopyGetters, Getters, Setters};\nuse serde::{Deserialize, Serialize};\nuse std::path::PathBuf;\nuse strum_macros::Display as StrumDisplay;\n\n#[derive(\n    Builder,\n    Clone,\n    Debug,\n    Default,\n    Deserialize,\n    CopyGetters,\n    Getters,\n    Setters,\n    Eq,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n#[getset(get = \"pub\", set = \"pub\")]\n/// ZOS contains information for z/OS based containers.\npub struct ZOS {\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    /// Namespaces contains the namespaces that are created and/or joined by the container\n    namespaces: Option<Vec<ZOSNamespace>>,\n}\n\n#[derive(\n    Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize, Hash, StrumDisplay,\n)]\n#[strum(serialize_all = \"lowercase\")]\n#[serde(rename_all = \"lowercase\")]\n/// Available z/OS namespace types.\npub enum ZOSNamespaceType {\n    #[default]\n    /// PID Namespace for isolating process IDs\n    Pid,\n    /// Mount Namespace for isolating mount points\n    Mount,\n    /// IPC Namespace for isolating System V IPC, POSIX message queues\n    Ipc,\n    /// UTS Namespace for isolating hostname and NIS domain name\n    Uts,\n}\n\nimpl TryFrom<&str> for ZOSNamespaceType {\n    type Error = OciSpecError;\n\n    fn try_from(namespace: &str) -> Result<Self, Self::Error> {\n        match namespace {\n            \"pid\" => Ok(ZOSNamespaceType::Pid),\n            \"mount\" => Ok(ZOSNamespaceType::Mount),\n            \"ipc\" => Ok(ZOSNamespaceType::Ipc),\n            \"uts\" => Ok(ZOSNamespaceType::Uts),\n            _ => Err(OciSpecError::Other(format!(\n                \"unknown z/OS namespace {namespace}, could not convert\"\n            ))),\n        }\n    }\n}\n\n#[derive(\n    Builder,\n    Clone,\n    CopyGetters,\n    Debug,\n    Default,\n    Deserialize,\n    Eq,\n    Getters,\n    Setters,\n    PartialEq,\n    Serialize,\n)]\n#[builder(\n    default,\n    pattern = \"owned\",\n    setter(into, strip_option),\n    build_fn(error = \"OciSpecError\")\n)]\n/// ZOSNamespace is the configuration for a z/OS namespace.\npub struct ZOSNamespace {\n    #[serde(rename = \"type\")]\n    #[getset(get_copy = \"pub\", set = \"pub\")]\n    /// Type is the type of namespace.\n    typ: ZOSNamespaceType,\n\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    #[getset(get = \"pub\", set = \"pub\")]\n    /// Path is a path to an existing namespace persisted on disk that can\n    /// be joined and is of the same type\n    path: Option<PathBuf>,\n}\n"
  },
  {
    "path": "test/data/artifact_manifest.json",
    "content": "{\n  \"mediaType\": \"application/vnd.oci.artifact.manifest.v1+json\",\n  \"artifactType\": \"application/vnd.example.sbom.v1\",\n  \"blobs\": [\n    {\n      \"mediaType\": \"application/gzip\",\n      \"size\": 123,\n      \"digest\": \"sha256:87923725d74f4bfb94c9e86d64170f7521aad8221a5de834851470ca142da630\"\n    }\n  ],\n  \"subject\": {\n    \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n    \"size\": 1234,\n    \"digest\": \"sha256:cc06a2839488b8bd2a2b99dcdc03d5cfd818eed72ad08ef3cc197aac64c0d0a0\"\n  },\n  \"annotations\": {\n    \"org.opencontainers.artifact.created\": \"2022-01-01T14:42:55Z\",\n    \"org.example.sbom.format\": \"json\"\n  }\n}\n"
  },
  {
    "path": "test/data/config.json",
    "content": "{\n  \"created\": \"2015-10-31T22:22:56.015925234Z\",\n  \"author\": \"Alyssa P. Hacker <alyspdev@example.com>\",\n  \"architecture\": \"amd64\",\n  \"os\": \"linux\",\n  \"config\": {\n    \"User\": \"alice\",\n    \"ExposedPorts\": {\n      \"8080/tcp\": {}\n    },\n    \"Env\": [\n      \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n      \"FOO=oci_is_a\",\n      \"BAR=well_written_spec\"\n    ],\n    \"Entrypoint\": [\n      \"/bin/my-app-binary\"\n    ],\n    \"Cmd\": [\n      \"--foreground\",\n      \"--config\",\n      \"/etc/my-app.d/default.cfg\"\n    ],\n    \"Volumes\": {\n      \"/var/job-result-data\": {},\n      \"/var/log/my-app-logs\": {}\n    },\n    \"WorkingDir\": \"/home/alice\"\n  },\n  \"rootfs\": {\n    \"type\": \"layers\",\n    \"diff_ids\": [\n      \"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1\",\n      \"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef\"\n    ]\n  },\n  \"history\": [\n    {\n      \"created\": \"2015-10-31T22:22:54.690851953Z\",\n      \"created_by\": \"/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /\"\n    },\n    {\n      \"created\": \"2015-10-31T22:22:55.613815829Z\",\n      \"created_by\": \"/bin/sh -c #(nop) CMD [\\\"sh\\\"]\",\n      \"empty_layer\": true\n    }\n  ]\n}"
  },
  {
    "path": "test/data/index.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"manifests\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"digest\": \"sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f\",\n      \"size\": 7143,\n      \"platform\": {\n        \"architecture\": \"ppc64le\",\n        \"os\": \"linux\"\n      }\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.manifest.v1+json\",\n      \"digest\": \"sha256:5b0bcabd1ed22e9fb1310cf6c2dec7cdef19f0ad69efa1f392e94a4333501270\",\n      \"size\": 7682,\n      \"platform\": {\n        \"architecture\": \"amd64\",\n        \"os\": \"linux\"\n      }\n    }\n  ]\n}"
  },
  {
    "path": "test/data/manifest.json",
    "content": "{\n  \"schemaVersion\": 2,\n  \"config\": {\n    \"mediaType\": \"application/vnd.oci.image.config.v1+json\",\n    \"digest\": \"sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7\",\n    \"size\": 7023\n  },\n  \"layers\": [\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:9834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f0\",\n      \"size\": 32654\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b\",\n      \"size\": 16724\n    },\n    {\n      \"mediaType\": \"application/vnd.oci.image.layer.v1.tar+gzip\",\n      \"digest\": \"sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736\",\n      \"size\": 73109\n    }\n  ]\n}"
  },
  {
    "path": "test/data/oci-layout",
    "content": "{\n  \"imageLayoutVersion\": \"lorem ipsum\"\n}"
  }
]