Compare commits

...

24 Commits

Author SHA1 Message Date
33c9c625c9 chore: release v0.0.25 2025-07-03 20:38:49 +00:00
a58bb3b0fb added links in the tech overview (#475) 2025-07-03 20:34:27 +00:00
699c2e09ef added a technical overview and added a link in the readme (#474) 2025-07-03 14:46:02 +00:00
53052b24a0 [StepSecurity] Apply security best practices (#473)
Signed-off-by: StepSecurity Bot <bot@stepsecurity.io>
Co-authored-by: stepsecurity-app[bot] <188008098+stepsecurity-app[bot]@users.noreply.github.com>
2025-06-06 18:36:16 +00:00
ea3ac96926 build(deps): bump the actions-updates group across 1 directory with 3 updates (#467)
Bumps the actions-updates group with 3 updates in the / directory: [step-security/harden-runner](https://github.com/step-security/harden-runner), [actions/create-github-app-token](https://github.com/actions/create-github-app-token) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `step-security/harden-runner` from 2.10.2 to 2.11.0
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](0080882f6c...4d991eb9b9)

Updates `actions/create-github-app-token` from 1.11.0 to 1.11.6
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](5d869da34e...21cfef2b49)

Updates `MarcoIeni/release-plz-action` from 0.5.86 to 0.5.99
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](db75300cf2...476794ede1)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: actions/create-github-app-token
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-05 14:26:50 +00:00
cc1ff65869 build(deps): bump the dep-updates group across 1 directory with 4 updates (#441)
Bumps the dep-updates group with 4 updates in the / directory: [async-trait](https://github.com/dtolnay/async-trait), [env_logger](https://github.com/rust-cli/env_logger), [libc](https://github.com/rust-lang/libc) and [thiserror](https://github.com/dtolnay/thiserror).


Updates `async-trait` from 0.1.83 to 0.1.85
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85)

Updates `env_logger` from 0.11.5 to 0.11.6
- [Release notes](https://github.com/rust-cli/env_logger/releases)
- [Changelog](https://github.com/rust-cli/env_logger/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-cli/env_logger/compare/v0.11.5...v0.11.6)

Updates `libc` from 0.2.168 to 0.2.169
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.169/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.168...0.2.169)

Updates `thiserror` from 2.0.7 to 2.0.9
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/2.0.7...2.0.9)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: env_logger
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 06:38:16 +00:00
b7dd2b8d89 chore: release v0.0.24 (#388)
Co-authored-by: edera-cultivation[bot] <165992271+edera-cultivation[bot]@users.noreply.github.com>
2024-12-14 23:53:21 +00:00
bf8a8379db fix(xenplatform): use cfg attributes for returning supported platforms 2024-12-14 18:46:45 -05:00
e0bbeb5d64 fix(xenclient): examples should use supported platform 2024-12-14 18:33:53 -05:00
4b0f3782bd fix(xenclient): boot example should use unsupported platform on aarch64 2024-12-14 18:22:37 -05:00
3adf9b5e88 feat(xen): update xenclient and xenplatform to the latest structure (#433) 2024-12-14 18:16:10 -05:00
f9d4508149 build(deps): bump the actions-updates group across 1 directory with 3 updates (#427)
Bumps the actions-updates group with 3 updates in the / directory: [step-security/harden-runner](https://github.com/step-security/harden-runner), [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `step-security/harden-runner` from 2.10.1 to 2.10.2
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](91182cccc0...0080882f6c)

Updates `actions/checkout` from 4.2.1 to 4.2.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](eef61447b9...11bd71901b)

Updates `MarcoIeni/release-plz-action` from 0.5.76 to 0.5.86
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](ede2f28fa4...db75300cf2)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Alex Zenla <alex@edera.dev>
2024-12-14 18:10:32 -05:00
54dc9cbdaa chore(deps): upgrade dependencies and clean code (#432) 2024-12-14 18:09:56 -05:00
ea9624955c feat(evtchn): harden evtchn handling and improve api (#431) 2024-12-14 18:03:33 -05:00
960578efc4 feat(xencall): improve asynchronous support (#430) 2024-12-14 18:00:11 -05:00
d7affe6c8c feat(xenstore): multi-watch and maybe-commit support (#429) 2024-12-14 17:57:15 -05:00
a04a812f28 build(deps): bump hashbrown from 0.15.0 to 0.15.2 in the cargo group (#426)
Bumps the cargo group with 1 update: [hashbrown](https://github.com/rust-lang/hashbrown).


Updates `hashbrown` from 0.15.0 to 0.15.2
- [Changelog](https://github.com/rust-lang/hashbrown/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/hashbrown/commits)

---
updated-dependencies:
- dependency-name: hashbrown
  dependency-type: indirect
  dependency-group: cargo
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-13 01:07:56 +00:00
9f6383b4c5 build(deps): bump the dep-updates group across 1 directory with 4 updates (#415)
Bumps the dep-updates group with 4 updates in the / directory: [libc](https://github.com/rust-lang/libc), [regex](https://github.com/rust-lang/regex), [thiserror](https://github.com/dtolnay/thiserror) and [tokio](https://github.com/tokio-rs/tokio).


Updates `libc` from 0.2.159 to 0.2.162
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.162/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.159...0.2.162)

Updates `regex` from 1.11.0 to 1.11.1
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.11.0...1.11.1)

Updates `thiserror` from 1.0.64 to 1.0.65
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.64...1.0.65)

Updates `tokio` from 1.40.0 to 1.41.1
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.40.0...tokio-1.41.1)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 11:31:15 +00:00
7f5a8c7a6e fix(xenplatform): e820 sanitize should now produce valid mappings 2024-10-17 13:27:23 -04:00
b1e7a05440 build(deps): bump the actions-updates group across 1 directory with 2 updates (#404)
Bumps the actions-updates group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `actions/checkout` from 4.2.0 to 4.2.1
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d632683dd7...eef61447b9)

Updates `MarcoIeni/release-plz-action` from 0.5.72 to 0.5.76
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](dbf3db74cd...ede2f28fa4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 16:10:56 +00:00
998df55711 build(deps): bump uuid from 1.10.0 to 1.11.0 in the dep-updates group (#405)
Bumps the dep-updates group with 1 update: [uuid](https://github.com/uuid-rs/uuid).


Updates `uuid` from 1.10.0 to 1.11.0
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.10.0...1.11.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-16 16:10:18 +00:00
3138c3e3e5 build(deps): bump MarcoIeni/release-plz-action (#399)
Bumps the actions-updates group with 1 update: [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `MarcoIeni/release-plz-action` from 0.5.71 to 0.5.72
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](91356927c5...dbf3db74cd)

---
updated-dependencies:
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-03 21:00:36 +00:00
d12fee305c build(deps): bump the actions-updates group across 1 directory with 2 updates (#397)
Bumps the actions-updates group with 2 updates in the / directory: [actions/checkout](https://github.com/actions/checkout) and [MarcoIeni/release-plz-action](https://github.com/marcoieni/release-plz-action).


Updates `actions/checkout` from 4.1.7 to 4.2.0
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](692973e3d9...d632683dd7)

Updates `MarcoIeni/release-plz-action` from 0.5.65 to 0.5.71
- [Release notes](https://github.com/marcoieni/release-plz-action/releases)
- [Commits](e28810957e...91356927c5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-updates
- dependency-name: MarcoIeni/release-plz-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:53:34 +00:00
66aef77b05 build(deps): bump the dep-updates group across 1 directory with 6 updates (#398)
Bumps the dep-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.82` | `0.1.83` |
| [flate2](https://github.com/rust-lang/flate2-rs) | `1.0.33` | `1.0.34` |
| [indexmap](https://github.com/indexmap-rs/indexmap) | `2.5.0` | `2.6.0` |
| [libc](https://github.com/rust-lang/libc) | `0.2.158` | `0.2.159` |
| [regex](https://github.com/rust-lang/regex) | `1.10.6` | `1.11.0` |
| [thiserror](https://github.com/dtolnay/thiserror) | `1.0.63` | `1.0.64` |



Updates `async-trait` from 0.1.82 to 0.1.83
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.82...0.1.83)

Updates `flate2` from 1.0.33 to 1.0.34
- [Release notes](https://github.com/rust-lang/flate2-rs/releases)
- [Changelog](https://github.com/rust-lang/flate2-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/flate2-rs/compare/1.0.33...1.0.34)

Updates `indexmap` from 2.5.0 to 2.6.0
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.5.0...2.6.0)

Updates `libc` from 0.2.158 to 0.2.159
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Changelog](https://github.com/rust-lang/libc/blob/0.2.159/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.158...0.2.159)

Updates `regex` from 1.10.6 to 1.11.0
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.10.6...1.11.0)

Updates `thiserror` from 1.0.63 to 1.0.64
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.63...1.0.64)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: flate2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
- dependency-name: regex
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dep-updates
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dep-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 15:53:19 +00:00
46 changed files with 2830 additions and 1612 deletions

View File

@ -6,17 +6,20 @@ on:
merge_group:
branches:
- main
permissions:
contents: read
jobs:
rustfmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain with rustfmt
@ -33,11 +36,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: shellcheck
@ -55,11 +58,11 @@ jobs:
name: full build linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain
@ -83,11 +86,11 @@ jobs:
name: full test linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain
@ -110,11 +113,11 @@ jobs:
name: full clippy linux-${{ matrix.arch }}
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
- name: install stable rust toolchain with clippy

View File

@ -6,6 +6,9 @@ on:
concurrency:
group: "${{ github.workflow }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
release-plz:
name: release-plz
@ -15,17 +18,17 @@ jobs:
contents: write
steps:
- name: harden runner
uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1
uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0
with:
egress-policy: audit
- name: generate cultivator token
uses: actions/create-github-app-token@5d869da34e18e7287c1daad50e0b8ea0f506ce69 # v1.11.0
uses: actions/create-github-app-token@21cfef2b496dd8ef5b904c159339626a10ad380e # v1.11.6
id: generate-token
with:
app-id: "${{ secrets.EDERA_CULTIVATION_APP_ID }}"
private-key: "${{ secrets.EDERA_CULTIVATION_APP_PRIVATE_KEY }}"
- name: checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
submodules: recursive
fetch-depth: 0
@ -37,7 +40,7 @@ jobs:
- name: install linux dependencies
run: ./hack/ci/install-linux-deps.sh
- name: release-plz
uses: MarcoIeni/release-plz-action@e28810957ef1fedfa89b5e9692e750ce45f62a67 # v0.5.65
uses: MarcoIeni/release-plz-action@476794ede164c5137bfc3a1dc6ed3675275690f9 # v0.5.99
env:
GITHUB_TOKEN: "${{ steps.generate-token.outputs.token }}"
CARGO_REGISTRY_TOKEN: "${{ secrets.KRATA_RELEASE_CARGO_TOKEN }}"

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
/.idea
/.vscode

View File

@ -7,6 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.0.25](https://github.com/edera-dev/krata/compare/v0.0.24...v0.0.25) - 2025-07-03
### Other
- update Cargo.toml dependencies
## [0.0.24](https://github.com/edera-dev/krata/compare/v0.0.23...v0.0.24) - 2024-12-14
### Added
- *(xen)* update xenclient and xenplatform to the latest structure (#433)
- *(xencall)* improve asynchronous support (#430)
- *(evtchn)* harden evtchn handling and improve api (#431)
- *(xenstore)* multi-watch and maybe-commit support (#429)
### Fixed
- *(xenclient)* examples should use supported platform
- *(xenclient)* boot example should use unsupported platform on aarch64
- *(xenplatform)* e820 sanitize should now produce valid mappings
- *(xenplatform)* use cfg attributes for returning supported platforms
### Other
- *(deps)* upgrade dependencies and clean code (#432)
- update Cargo.toml dependencies
## [0.0.23](https://github.com/edera-dev/krata/compare/v0.0.22...v0.0.23) - 2024-09-17
### Other

259
Cargo.lock generated
View File

@ -1,22 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
version = "0.22.0"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler2"
version = "2.0.0"
@ -34,9 +28,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.15"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
@ -49,70 +43,76 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.8"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.5"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.1"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.4"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "async-trait"
version = "0.1.82"
version = "0.1.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.74",
"syn",
]
[[package]]
name = "autocfg"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.73"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide 0.7.4",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -127,35 +127,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "c2rust-bitfields"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b43c3f07ab0ef604fa6f595aa46ec2f8a22172c975e186f6f5bf9829a3b72c41"
dependencies = [
"c2rust-bitfields-derive",
]
[[package]]
name = "c2rust-bitfields-derive"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3cbc102e2597c9744c8bd8c15915d554300601c91a079430d309816b0912545"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cc"
version = "1.1.10"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e8aabfac534be767c909e0690571677d49f41bd8465ae876fe043d52ba5292"
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
@ -171,9 +154,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "colorchoice"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "crc32fast"
@ -202,9 +185,9 @@ dependencies = [
[[package]]
name = "env_logger"
version = "0.11.5"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
dependencies = [
"anstream",
"anstyle",
@ -221,12 +204,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "flate2"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide 0.8.0",
"miniz_oxide",
]
[[package]]
@ -242,21 +225,15 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.29.0"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "hashbrown"
version = "0.14.5"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "humantime"
@ -266,9 +243,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "indexmap"
version = "2.5.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown",
@ -282,7 +259,7 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "krata-xencall"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"env_logger",
"libc",
@ -290,20 +267,19 @@ dependencies = [
"nix",
"thiserror",
"tokio",
"uuid",
]
[[package]]
name = "krata-xenclient"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"async-trait",
"bit-vec",
"env_logger",
"indexmap",
"krata-xencall",
"krata-xenplatform",
"krata-xenstore",
"libc",
"log",
"regex",
"thiserror",
@ -313,7 +289,7 @@ dependencies = [
[[package]]
name = "krata-xenevtchn"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"byteorder",
"libc",
@ -325,7 +301,7 @@ dependencies = [
[[package]]
name = "krata-xengnt"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"libc",
"nix",
@ -334,14 +310,11 @@ dependencies = [
[[package]]
name = "krata-xenplatform"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"async-trait",
"c2rust-bitfields",
"elf",
"env_logger",
"flate2",
"indexmap",
"krata-xencall",
"libc",
"log",
@ -357,7 +330,7 @@ dependencies = [
[[package]]
name = "krata-xenstore"
version = "0.0.23"
version = "0.0.25"
dependencies = [
"byteorder",
"env_logger",
@ -369,9 +342,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.158"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "lock_api"
@ -406,15 +379,6 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@ -426,14 +390,13 @@ dependencies = [
[[package]]
name = "mio"
version = "1.0.2"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -450,9 +413,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.3"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
@ -482,48 +445,48 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "pkg-config"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "proc-macro2"
version = "1.0.86"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.10.6"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@ -533,9 +496,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.7"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
@ -544,9 +507,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.4"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-demangle"
@ -560,6 +523,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
@ -583,30 +552,19 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "syn"
version = "1.0.109"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
@ -615,29 +573,29 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.63"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.63"
version = "2.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.74",
"syn",
]
[[package]]
name = "tokio"
version = "1.40.0"
version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [
"backtrace",
"bytes",
@ -648,7 +606,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -659,14 +617,14 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.74",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "utf8parse"
@ -676,9 +634,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
dependencies = [
"getrandom",
]
@ -698,6 +656,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"

View File

@ -10,38 +10,34 @@ members = [
resolver = "2"
[workspace.package]
version = "0.0.23"
version = "0.0.25"
homepage = "https://krata.dev"
license = "GPL-2.0-or-later"
repository = "https://github.com/edera-dev/krata"
[workspace.dependencies]
async-trait = "0.1.82"
async-trait = "0.1.85"
bit-vec = "0.8.0"
byteorder = "1"
c2rust-bitfields = "0.18.0"
elf = "0.7.4"
env_logger = "0.11.5"
env_logger = "0.11.6"
flate2 = "1.0"
indexmap = "2.5.0"
indexmap = "2.6.0"
libc = "0.2"
log = "0.4.22"
memchr = "2"
nix = "0.29.0"
regex = "1.10.6"
regex = "1.11.1"
slice-copy = "0.3.0"
thiserror = "1.0"
thiserror = "2.0.9"
xz2 = "0.1"
[workspace.dependencies.serde]
version = "1.0.209"
features = ["derive"]
[workspace.dependencies.tokio]
version = "1.40.0"
version = "1.41.1"
features = ["full"]
[workspace.dependencies.uuid]
version = "1.10.0"
version = "1.11.0"
features = ["v4"]
[profile.release]

View File

@ -11,6 +11,7 @@ krata is an implementation of a Xen control-plane in Rust.
- [Frequently Asked Questions](FAQ.md)
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Security Policy](SECURITY.md)
- [Edera Technical Overview](technical-overview.md)
## Introduction
@ -23,3 +24,4 @@ It provides the base layer upon which Edera Protect zones are built on: a secure
|--------------|------------------|-------------------------|
| x86_64 | 100% Completed | None, Intel VT-x, AMD-V |
| aarch64 | 10% Completed | AArch64 virtualization |

View File

@ -14,7 +14,6 @@ log = { workspace = true }
nix = { workspace = true, features = ["ioctl"] }
thiserror = { workspace = true }
tokio = { workspace = true }
uuid = { workspace = true }
[lib]
name = "xencall"
@ -22,6 +21,10 @@ name = "xencall"
[dev-dependencies]
env_logger = { workspace = true }
[[example]]
name = "xencall-cpu-topology"
path = "examples/cpu_topology.rs"
[[example]]
name = "xencall-domain-info"
path = "examples/domain_info.rs"

View File

@ -1,5 +1,4 @@
use xencall::error::Result;
use xencall::sys::CpuId;
use xencall::XenCall;
#[tokio::main]
@ -11,9 +10,5 @@ async fn main() -> Result<()> {
println!("{:?}", physinfo);
let topology = call.cpu_topology().await?;
println!("{:?}", topology);
call.set_cpufreq_gov(CpuId::All, "performance").await?;
call.set_cpufreq_gov(CpuId::Single(0), "performance")
.await?;
call.set_turbo_mode(CpuId::All, true).await?;
Ok(())
}

View File

@ -7,8 +7,6 @@ async fn main() -> Result<()> {
env_logger::init();
let call = XenCall::open(0)?;
let physinfo = call.phys_info().await?;
println!("{:?}", physinfo);
let topology = call.cpu_topology().await?;
println!("{:?}", topology);
call.set_cpufreq_gov(CpuId::All, "performance").await?;

View File

@ -1,5 +1,7 @@
use std::io;
use tokio::task::JoinError;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("version of xen is not supported")]
@ -16,6 +18,8 @@ pub enum Error {
MmapBatchFailed(nix::errno::Errno),
#[error("specified value is too long")]
ValueTooLong,
#[error("failed to join async task: {0}")]
JoinError(JoinError),
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -10,10 +10,11 @@ use crate::sys::{
PciAssignDevice, XenCapabilitiesInfo, DOMCTL_DEV_PCI, HYPERVISOR_DOMCTL,
HYPERVISOR_EVENT_CHANNEL_OP, HYPERVISOR_HVM_OP, HYPERVISOR_MEMORY_OP, HYPERVISOR_MMUEXT_OP,
HYPERVISOR_MULTICALL, HYPERVISOR_XEN_VERSION, XENVER_CAPABILITIES, XEN_DOMCTL_ASSIGN_DEVICE,
XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_DESTROYDOMAIN, XEN_DOMCTL_GETDOMAININFO,
XEN_DOMCTL_GETHVMCONTEXT, XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_HYPERCALL_INIT,
XEN_DOMCTL_IOMEM_PERMISSION, XEN_DOMCTL_IOPORT_PERMISSION, XEN_DOMCTL_IRQ_PERMISSION,
XEN_DOMCTL_MAX_MEM, XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN, XEN_DOMCTL_SETHVMCONTEXT,
XEN_DOMCTL_CREATEDOMAIN, XEN_DOMCTL_CREATE_DOMAIN2_INTERFACE_THRESHOLD,
XEN_DOMCTL_DESTROYDOMAIN, XEN_DOMCTL_GETDOMAININFO, XEN_DOMCTL_GETHVMCONTEXT,
XEN_DOMCTL_GETPAGEFRAMEINFO3, XEN_DOMCTL_HYPERCALL_INIT, XEN_DOMCTL_IOMEM_PERMISSION,
XEN_DOMCTL_IOPORT_PERMISSION, XEN_DOMCTL_IRQ_PERMISSION, XEN_DOMCTL_MAX_MEM,
XEN_DOMCTL_MAX_VCPUS, XEN_DOMCTL_PAUSEDOMAIN, XEN_DOMCTL_SETHVMCONTEXT,
XEN_DOMCTL_SETVCPUCONTEXT, XEN_DOMCTL_SET_ADDRESS_SIZE, XEN_DOMCTL_SET_PAGING_MEMPOOL_SIZE,
XEN_DOMCTL_UNPAUSEDOMAIN, XEN_MEM_ADD_TO_PHYSMAP, XEN_MEM_CLAIM_PAGES, XEN_MEM_MEMORY_MAP,
XEN_MEM_POPULATE_PHYSMAP,
@ -25,15 +26,15 @@ use std::ffi::{c_long, c_uint, c_ulong, c_void};
use std::sync::Arc;
use std::time::Duration;
use sys::{
CpuId, E820Entry, ForeignMemoryMap, PhysdevMapPirq, Sysctl, SysctlCputopo, SysctlCputopoinfo,
SysctlPhysinfo, SysctlPmOp, SysctlPmOpValue, SysctlReadconsole, SysctlSetCpuFreqGov,
SysctlValue, VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP, HYPERVISOR_SYSCTL, PHYSDEVOP_MAP_PIRQ,
XEN_DOMCTL_MAX_INTERFACE_VERSION, XEN_DOMCTL_MIN_INTERFACE_VERSION, XEN_MEM_SET_MEMORY_MAP,
CpuId, E820Entry, ForeignMemoryMap, PhysdevMapPirq, SetDomainHandle, Sysctl, SysctlCputopo,
SysctlCputopoinfo, SysctlPhysinfo, SysctlPmOp, SysctlPmOpValue, SysctlReadconsole,
SysctlSetCpuFreqGov, SysctlValue, VcpuGuestContextAny, HYPERVISOR_PHYSDEV_OP,
HYPERVISOR_SYSCTL, PHYSDEVOP_MAP_PIRQ, XEN_DOMCTL_MAX_INTERFACE_VERSION,
XEN_DOMCTL_MIN_INTERFACE_VERSION, XEN_DOMCTL_SETDOMAINHANDLE, XEN_MEM_SET_MEMORY_MAP,
XEN_SYSCTL_CPUTOPOINFO, XEN_SYSCTL_MAX_INTERFACE_VERSION, XEN_SYSCTL_MIN_INTERFACE_VERSION,
XEN_SYSCTL_PHYSINFO, XEN_SYSCTL_PM_OP, XEN_SYSCTL_PM_OP_DISABLE_TURBO,
XEN_SYSCTL_PM_OP_ENABLE_TURBO, XEN_SYSCTL_PM_OP_SET_CPUFREQ_GOV, XEN_SYSCTL_READCONSOLE,
};
use tokio::sync::Semaphore;
use tokio::time::sleep;
use std::fs::{File, OpenOptions};
@ -44,7 +45,6 @@ use std::slice;
#[derive(Clone)]
pub struct XenCall {
pub handle: Arc<File>,
semaphore: Arc<Semaphore>,
domctl_interface_version: u32,
sysctl_interface_version: u32,
}
@ -60,7 +60,6 @@ impl XenCall {
let sysctl_interface_version = XenCall::detect_sysctl_interface_version(&handle)?;
Ok(XenCall {
handle: Arc::new(handle),
semaphore: Arc::new(Semaphore::new(1)),
domctl_interface_version,
sysctl_interface_version,
})
@ -117,7 +116,6 @@ impl XenCall {
}
pub async fn mmap(&self, addr: u64, len: u64) -> Option<u64> {
let _permit = self.semaphore.acquire().await.ok()?;
trace!(
"call fd={} mmap addr={:#x} len={}",
self.handle.as_raw_fd(),
@ -125,14 +123,20 @@ impl XenCall {
len
);
unsafe {
let ptr = mmap(
addr as *mut c_void,
len as usize,
PROT_READ | PROT_WRITE,
MAP_SHARED,
self.handle.as_raw_fd(),
0,
);
let handle = self.handle.clone();
let ptr = tokio::task::spawn_blocking(move || {
mmap(
addr as *mut c_void,
len as usize,
PROT_READ | PROT_WRITE,
MAP_SHARED,
handle.as_raw_fd(),
0,
) as u64
})
.await
.map_err(Error::JoinError)
.ok()? as *mut c_void;
if ptr == MAP_FAILED {
None
} else {
@ -149,18 +153,22 @@ impl XenCall {
}
pub async fn hypercall(&self, op: c_ulong, arg: [c_ulong; 5]) -> Result<c_long> {
let _permit = self.semaphore.acquire().await?;
trace!(
"call fd={} hypercall op={:#x} arg={:?}",
self.handle.as_raw_fd(),
op,
arg
);
unsafe {
let handle = self.handle.clone();
tokio::task::spawn_blocking(move || unsafe {
let mut call = Hypercall { op, arg };
let result = sys::hypercall(self.handle.as_raw_fd(), &mut call)?;
Ok(result as c_long)
}
sys::hypercall(handle.as_raw_fd(), &mut call)
.map(|x| x as c_long)
.map_err(|e| e.into())
})
.await
.map_err(Error::JoinError)?
}
pub async fn hypercall0(&self, op: c_ulong) -> Result<c_long> {
@ -232,18 +240,21 @@ impl XenCall {
num: u64,
addr: u64,
) -> Result<()> {
let _permit = self.semaphore.acquire().await?;
let mut resource = MmapResource {
dom: domid as u16,
typ,
id,
idx,
num,
addr,
};
unsafe {
sys::mmap_resource(self.handle.as_raw_fd(), &mut resource)?;
}
let handle = self.handle.clone();
tokio::task::spawn_blocking(move || {
let mut resource = MmapResource {
dom: domid as u16,
typ,
id,
idx,
num,
addr,
};
unsafe { sys::mmap_resource(handle.as_raw_fd(), &mut resource) }
})
.await
.map_err(Error::JoinError)??;
Ok(())
}
@ -254,7 +265,6 @@ impl XenCall {
addr: u64,
mfns: Vec<u64>,
) -> Result<c_long> {
let _permit = self.semaphore.acquire().await?;
trace!(
"call fd={} mmap_batch domid={} num={} addr={:#x} mfns={:?}",
self.handle.as_raw_fd(),
@ -320,7 +330,7 @@ impl XenCall {
break;
}
let count = result.unwrap();
let count = result?;
if count <= 0 {
break;
}
@ -328,7 +338,7 @@ impl XenCall {
return Ok(paged as c_long);
}
Ok(result.unwrap() as c_long)
Ok(result? as c_long)
}
}
@ -366,6 +376,28 @@ impl XenCall {
Ok(alloc_unbound.port)
}
pub async fn set_domain_handle(&self, domid: u32, domain_handle: [u8; 16]) -> Result<()> {
trace!(
"domctl fd={} set_domain_handle domid={} handle={:?}",
self.handle.as_raw_fd(),
domid,
domain_handle,
);
let mut domctl = DomCtl {
cmd: XEN_DOMCTL_SETDOMAINHANDLE,
interface_version: self.domctl_interface_version,
domid,
value: DomCtlValue {
set_domain_handle: SetDomainHandle {
handle: domain_handle,
},
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
Ok(())
}
pub async fn get_domain_info(&self, domid: u32) -> Result<GetDomainInfo> {
trace!(
"domctl fd={} get_domain_info domid={}",
@ -395,7 +427,14 @@ impl XenCall {
cmd: XEN_DOMCTL_CREATEDOMAIN,
interface_version: self.domctl_interface_version,
domid: 0,
value: DomCtlValue { create_domain },
value: if self.domctl_interface_version >= XEN_DOMCTL_CREATE_DOMAIN2_INTERFACE_THRESHOLD
{
DomCtlValue {
create_domain2: create_domain.to_cd_2(),
}
} else {
DomCtlValue { create_domain }
},
};
self.hypercall1(HYPERVISOR_DOMCTL, addr_of_mut!(domctl) as c_ulong)
.await?;
@ -587,6 +626,11 @@ impl XenCall {
}
pub async fn get_memory_map(&self, max_entries: u32) -> Result<Vec<E820Entry>> {
trace!(
"fd={} get_memory_map max_entries={}",
self.handle.as_raw_fd(),
max_entries,
);
let mut memory_map = MemoryMap {
count: max_entries,
buffer: 0,

View File

@ -205,6 +205,16 @@ pub const XEN_DOMCTL_GDBSX_PAUSEVCPU: u32 = 1001;
pub const XEN_DOMCTL_GDBSX_UNPAUSEVCPU: u32 = 1002;
pub const XEN_DOMCTL_GDBSX_DOMSTATUS: u32 = 1003;
pub const XEN_DOMINF_DYING: u32 = 1u32 << 0;
pub const XEN_DOMINF_HVM_GUEST: u32 = 1u32 << 1;
pub const XEN_DOMINF_SHUTDOWN: u32 = 1u32 << 2;
pub const XEN_DOMINF_PAUSED: u32 = 1u32 << 3;
pub const XEN_DOMINF_BLOCKED: u32 = 1u32 << 4;
pub const XEN_DOMINF_RUNNING: u32 = 1u32 << 5;
pub const XEN_DOMINF_DEBUGGED: u32 = 1u32 << 6;
pub const XEN_DOMINF_XS_DOMAIN: u32 = 1u32 << 7;
pub const XEN_DOMINF_HAP: u32 = 1u32 << 8;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct DomCtl {
@ -231,6 +241,7 @@ pub struct AddressSize {
#[derive(Copy, Clone)]
pub union DomCtlValue {
pub create_domain: CreateDomain,
pub create_domain2: CreateDomain2,
pub get_domain_info: GetDomainInfo,
pub max_mem: MaxMem,
pub max_cpus: MaxVcpus,
@ -244,6 +255,7 @@ pub union DomCtlValue {
pub assign_device: AssignDevice,
pub hvm_context: HvmContext,
pub paging_mempool: PagingMempool,
pub set_domain_handle: SetDomainHandle,
pub pad: [u8; 128],
}
@ -264,6 +276,24 @@ pub struct CreateDomain {
pub arch_domain_config: ArchDomainConfig,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct CreateDomain2 {
pub ssidref: u32,
pub handle: [u8; 16],
pub flags: u32,
pub iommu_opts: u32,
pub max_vcpus: u32,
pub max_evtchn_port: u32,
pub max_grant_frames: i32,
pub max_maptrack_frames: i32,
pub grant_opts: u32,
pub altp2m_opts: u32,
pub vmtrace_size: u32,
pub cpupool_id: u32,
pub arch_domain_config: ArchDomainConfig,
}
impl Default for CreateDomain {
fn default() -> Self {
CreateDomain {
@ -283,6 +313,32 @@ impl Default for CreateDomain {
}
}
impl CreateDomain {
pub fn to_cd_2(self) -> CreateDomain2 {
CreateDomain2 {
ssidref: self.ssidref,
handle: self.handle,
flags: self.flags,
iommu_opts: self.iommu_opts,
max_vcpus: self.max_vcpus,
max_evtchn_port: self.max_evtchn_port,
max_grant_frames: self.max_grant_frames,
max_maptrack_frames: self.max_maptrack_frames,
grant_opts: self.grant_opts,
altp2m_opts: 0,
vmtrace_size: self.vmtrace_size,
cpupool_id: self.cpupool_id,
arch_domain_config: self.arch_domain_config,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct SetDomainHandle {
pub handle: [u8; 16],
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default)]
pub struct GetDomainInfo {
@ -377,7 +433,8 @@ pub struct HypercallInit {
}
pub const XEN_DOMCTL_MIN_INTERFACE_VERSION: u32 = 0x00000015;
pub const XEN_DOMCTL_MAX_INTERFACE_VERSION: u32 = 0x00000016;
pub const XEN_DOMCTL_MAX_INTERFACE_VERSION: u32 = 0x00000017;
pub const XEN_DOMCTL_CREATE_DOMAIN2_INTERFACE_THRESHOLD: u32 = 0x00000017;
pub const SECINITSID_DOMU: u32 = 12;

View File

@ -10,12 +10,12 @@ resolver = "2"
[dependencies]
async-trait = { workspace = true }
bit-vec = { workspace = true }
indexmap = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
krata-xencall = { path = "../xencall", version = "^0.0.23" }
krata-xenplatform = { path = "../xenplatform", version = "^0.0.23" }
krata-xenstore = { path = "../xenstore", version = "^0.0.23" }
krata-xencall = { path = "../xencall", version = "^0.0.25" }
krata-xenplatform = { path = "../xenplatform", version = "^0.0.25" }
krata-xenstore = { path = "../xenstore", version = "^0.0.25" }
regex = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }

View File

@ -1,15 +1,15 @@
use std::sync::Arc;
use std::{env, process};
use tokio::fs;
use uuid::Uuid;
use xenclient::error::Result;
use xenclient::{DomainConfig, XenClient};
use xenplatform::domain::BaseDomainConfig;
#[cfg(target_arch = "x86_64")]
type RuntimePlatform = xenplatform::x86pv::X86PvPlatform;
#[cfg(not(target_arch = "x86_64"))]
type RuntimePlatform = xenplatform::unsupported::UnsupportedPlatform;
use xenclient::tx::channel::ChannelDeviceConfig;
use xenclient::{config::DomainConfig, XenClient};
use xenplatform::domain::{
KernelFormat, PlatformDomainConfig, PlatformKernelConfig, PlatformOptions,
PlatformResourcesConfig,
};
use xenplatform::RuntimePlatformType;
#[tokio::main]
async fn main() -> Result<()> {
@ -22,32 +22,31 @@ async fn main() -> Result<()> {
}
let kernel_image_path = args.get(1).expect("argument not specified");
let initrd_path = args.get(2).expect("argument not specified");
let client = XenClient::new(0, RuntimePlatform::new()).await?;
let config = DomainConfig {
base: BaseDomainConfig {
uuid: Uuid::new_v4(),
max_vcpus: 1,
target_vcpus: 1,
max_mem_mb: 512,
target_mem_mb: 512,
enable_iommu: true,
kernel: fs::read(&kernel_image_path).await?,
initrd: fs::read(&initrd_path).await?,
let client = XenClient::new().await?;
let mut config = DomainConfig::new();
config.platform(PlatformDomainConfig {
uuid: Uuid::new_v4(),
platform: RuntimePlatformType::supported(),
kernel: PlatformKernelConfig {
data: Arc::new(fs::read(&kernel_image_path).await?),
format: KernelFormat::ElfCompressed,
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
owner_domid: 0,
initrd: Some(Arc::new(fs::read(&initrd_path).await?)),
},
backend_domid: 0,
name: "xenclient-test".to_string(),
swap_console_backend: None,
disks: vec![],
channels: vec![],
vifs: vec![],
pcis: vec![],
filesystems: vec![],
extra_keys: vec![],
extra_rw_paths: vec![],
};
let created = client.create(&config).await?;
println!("created domain {}", created.domid);
resources: PlatformResourcesConfig {
max_vcpus: 1,
assigned_vcpus: 1,
max_memory_mb: 512,
assigned_memory_mb: 512,
},
options: PlatformOptions { iommu: true },
});
config.name("xenclient-test");
let mut channel = ChannelDeviceConfig::new();
channel.default_console().backend_initialized();
config.add_channel(channel);
let created = client.create(config).await?;
println!("created domain {}", created.platform.domid);
Ok(())
}

View File

@ -0,0 +1,66 @@
use std::sync::Arc;
use std::{env, process};
use tokio::fs;
use uuid::Uuid;
use xenclient::config::{DomainConfig, DomainResult};
use xenclient::error::Result;
use xenclient::tx::channel::ChannelDeviceConfig;
use xenclient::XenClient;
use xenplatform::domain::{
KernelFormat, PlatformDomainConfig, PlatformKernelConfig, PlatformOptions,
PlatformResourcesConfig,
};
use xenplatform::elfloader::ElfImageLoader;
use xenplatform::RuntimePlatformType;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
println!("usage: boot-speed <kernel-image>");
process::exit(1);
}
let kernel_path = args.get(1).expect("argument not specified");
let kernel = Arc::new(fs::read(kernel_path).await?);
let kernel = ElfImageLoader::load(kernel)?.into_elf_bytes();
let client = XenClient::new().await?;
for i in 0..5u32 {
let start = std::time::Instant::now();
let domain = create_domain(&client, kernel.clone(), i).await?;
let end = std::time::Instant::now();
let duration = end - start;
println!("boot setup time: {:?}", duration);
client.destroy(domain.platform.domid).await?;
}
Ok(())
}
async fn create_domain(client: &XenClient, kernel: Arc<Vec<u8>>, i: u32) -> Result<DomainResult> {
let mut config = DomainConfig::new();
config.platform(PlatformDomainConfig {
uuid: Uuid::new_v4(),
platform: RuntimePlatformType::supported(),
kernel: PlatformKernelConfig {
data: kernel,
format: KernelFormat::ElfUncompressed,
cmdline: "earlyprintk=xen earlycon=xen console=hvc0 init=/init".to_string(),
initrd: None,
},
resources: PlatformResourcesConfig {
max_vcpus: 1,
assigned_vcpus: 1,
max_memory_mb: 512,
assigned_memory_mb: 512,
},
options: PlatformOptions { iommu: true },
});
config.name(format!("xenboot-{}", i));
config.start(false);
let mut channel = ChannelDeviceConfig::new();
channel.default_console().backend_initialized();
config.add_channel(channel);
client.create(config).await
}

View File

@ -0,0 +1,185 @@
use std::collections::HashMap;
use xencall::XenCall;
pub use xenplatform::domain::PlatformDomainConfig;
use xenplatform::domain::PlatformDomainInfo;
use crate::{
error::Result,
tx::{
channel::ChannelDeviceConfig,
fs9p::Fs9pDeviceConfig,
pci::PciRootDeviceConfig,
vbd::VbdDeviceConfig,
vif::VifDeviceConfig,
{BlockDeviceResult, DeviceResult},
},
};
pub struct DomainConfig {
platform: Option<PlatformDomainConfig>,
name: Option<String>,
backend_domid: u32,
channels: Vec<ChannelDeviceConfig>,
vifs: Vec<VifDeviceConfig>,
vbds: Vec<VbdDeviceConfig>,
fs9ps: Vec<Fs9pDeviceConfig>,
pci: Option<PciRootDeviceConfig>,
extra_keys: HashMap<String, String>,
extra_rw_paths: Vec<String>,
start: bool,
}
impl Default for DomainConfig {
fn default() -> Self {
Self::new()
}
}
impl DomainConfig {
pub fn new() -> Self {
Self {
platform: None,
name: None,
backend_domid: 0,
channels: Vec::new(),
vifs: Vec::new(),
vbds: Vec::new(),
fs9ps: Vec::new(),
pci: None,
extra_keys: HashMap::new(),
extra_rw_paths: Vec::new(),
start: true,
}
}
pub fn platform(&mut self, platform: PlatformDomainConfig) -> &mut Self {
self.platform = Some(platform);
self
}
pub fn get_platform(&self) -> &Option<PlatformDomainConfig> {
&self.platform
}
pub fn name(&mut self, name: impl AsRef<str>) -> &mut Self {
self.name = Some(name.as_ref().to_string());
self
}
pub fn get_name(&self) -> &Option<String> {
&self.name
}
pub fn backend_domid(&mut self, backend_domid: u32) -> &mut Self {
self.backend_domid = backend_domid;
self
}
pub fn get_backend_domid(&self) -> u32 {
self.backend_domid
}
pub fn add_channel(&mut self, channel: ChannelDeviceConfig) -> &mut Self {
self.channels.push(channel);
self
}
pub fn get_channels(&self) -> &Vec<ChannelDeviceConfig> {
&self.channels
}
pub fn add_vif(&mut self, vif: VifDeviceConfig) -> &mut Self {
self.vifs.push(vif);
self
}
pub fn get_vifs(&self) -> &Vec<VifDeviceConfig> {
&self.vifs
}
pub fn add_vbd(&mut self, vbd: VbdDeviceConfig) -> &mut Self {
self.vbds.push(vbd);
self
}
pub fn get_vbds(&self) -> &Vec<VbdDeviceConfig> {
&self.vbds
}
pub fn add_fs9p(&mut self, fs9p: Fs9pDeviceConfig) -> &mut Self {
self.fs9ps.push(fs9p);
self
}
pub fn get_fs9ps(&self) -> &Vec<Fs9pDeviceConfig> {
&self.fs9ps
}
pub fn pci(&mut self, pci: PciRootDeviceConfig) -> &mut Self {
self.pci = Some(pci);
self
}
pub fn get_pci(&self) -> &Option<PciRootDeviceConfig> {
&self.pci
}
pub fn add_extra_key(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
self.extra_keys
.insert(key.as_ref().to_string(), value.to_string());
self
}
pub fn get_extra_keys(&self) -> &HashMap<String, String> {
&self.extra_keys
}
pub fn add_rw_path(&mut self, path: impl AsRef<str>) -> &mut Self {
self.extra_rw_paths.push(path.as_ref().to_string());
self
}
pub fn get_rw_paths(&self) -> &Vec<String> {
&self.extra_rw_paths
}
pub fn start(&mut self, start: bool) -> &mut Self {
self.start = start;
self
}
pub fn get_start(&self) -> bool {
self.start
}
pub fn done(self) -> Self {
self
}
pub(crate) async fn prepare(
&mut self,
domid: u32,
call: &XenCall,
platform: &PlatformDomainInfo,
) -> Result<()> {
if let Some(pci) = self.pci.as_mut() {
pci.prepare(domid, call).await?;
}
for channel in &mut self.channels {
channel.prepare(platform).await?;
}
Ok(())
}
}
pub struct DomainResult {
pub platform: PlatformDomainInfo,
pub channels: Vec<DeviceResult>,
pub vifs: Vec<DeviceResult>,
pub vbds: Vec<BlockDeviceResult>,
pub fs9ps: Vec<DeviceResult>,
pub pci: Option<DeviceResult>,
}

View File

@ -0,0 +1,79 @@
use bit_vec::BitVec;
const DEVICE_COUNT: usize = 4096;
const BYTE_COUNT: usize = DEVICE_COUNT / 8;
pub struct DeviceIdAllocator {
states: BitVec,
cursor: u32,
}
impl Default for DeviceIdAllocator {
fn default() -> Self {
Self::new()
}
}
impl DeviceIdAllocator {
pub fn new() -> Self {
Self {
states: BitVec::from_elem(DEVICE_COUNT, false),
cursor: 0,
}
}
pub fn deserialize(bytes: &[u8]) -> Option<Self> {
if bytes.len() != BYTE_COUNT + 4 {
return None;
}
let cursor = bytes[0] as u32
| ((bytes[1] as u32) << 8)
| ((bytes[2] as u32) << 16)
| ((bytes[3] as u32) << 24);
let slice = &bytes[4..BYTE_COUNT + 4];
if slice.len() != BYTE_COUNT {
return None;
}
let states = BitVec::from_bytes(slice);
Some(Self { states, cursor })
}
pub fn allocate(&mut self) -> Option<u32> {
let start = self.cursor;
loop {
let id = self.cursor;
let value = self.states.get(self.cursor as usize)?;
self.cursor = (self.cursor + 1) % DEVICE_COUNT as u32;
if !value {
self.states.set(id as usize, true);
return Some(id);
}
if self.cursor == start {
return None;
}
}
}
pub fn release(&mut self, id: u32) {
self.states.set(id as usize, false);
}
pub fn count_free(&mut self) -> u32 {
self.states.count_zeros() as u32
}
pub fn serialize(&mut self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(BYTE_COUNT + 4);
bytes.push((self.cursor & 0xff) as u8);
bytes.push(((self.cursor >> 8) & 0xff) as u8);
bytes.push(((self.cursor >> 16) & 0xff) as u8);
bytes.push(((self.cursor >> 24) & 0xff) as u8);
bytes.extend_from_slice(&self.states.to_bytes());
bytes
}
}

View File

@ -0,0 +1,131 @@
use std::time::Duration;
use tokio::{
select,
time::{sleep, timeout},
};
use xenstore::{XsdClient, XsdInterface};
use crate::error::{Error, Result};
pub struct DeviceLocator {
pub frontend_domid: u32,
pub backend_domid: u32,
pub frontend_type: String,
pub backend_type: String,
pub device_id: u64,
}
impl DeviceLocator {
pub fn new(
frontend_domid: u32,
backend_domid: u32,
frontend_type: String,
backend_type: String,
device_id: u64,
) -> Self {
DeviceLocator {
frontend_domid,
backend_domid,
frontend_type,
backend_type,
device_id,
}
}
pub fn frontend_state_path(&self) -> String {
format!(
"/local/domain/{}/device/{}/{}/state",
self.frontend_domid, self.frontend_type, self.device_id
)
}
pub fn backend_state_path(&self) -> String {
format!(
"/local/domain/{}/backend/{}/{}/{}/state",
self.backend_domid, self.backend_type, self.frontend_domid, self.device_id
)
}
}
pub struct DeviceStateWaiter {
devices: Vec<DeviceLocator>,
xsd: XsdClient,
}
impl DeviceStateWaiter {
pub fn new(xsd: XsdClient) -> Self {
DeviceStateWaiter {
devices: vec![],
xsd,
}
}
pub fn add_device(&mut self, device: DeviceLocator) -> &mut DeviceStateWaiter {
self.devices.push(device);
self
}
async fn check_states(xsd: &XsdClient, state_paths: &[String], desired: u32) -> Result<bool> {
let mut ready = 0;
for state_path in state_paths {
let Some(state_text) = xsd.read_string(state_path).await? else {
return Err(Error::DevStateWaitError(format!(
"state path '{}' did not exist",
state_path
)));
};
let Some(state_value) = state_text.parse::<u32>().ok() else {
return Err(Error::DevStateWaitError(format!(
"state path '{}' did not have a valid value",
state_path
)));
};
if state_value > desired {
return Err(Error::DevStateWaitError(format!(
"state path '{}' had a state of {} which is greater than {}",
state_path, state_value, desired
)));
}
if state_value == desired {
ready += 1;
}
}
Ok(ready == state_paths.len())
}
async fn do_wait(self, desired: u32) -> Result<()> {
let mut watch = self.xsd.create_multi_watch().await?;
let mut state_paths = Vec::new();
for device in self.devices {
let state_path = device.backend_state_path();
self.xsd.bind_watch_id(watch.id, &state_path).await?;
state_paths.push(state_path);
}
loop {
if DeviceStateWaiter::check_states(&self.xsd, &state_paths, desired).await? {
break;
}
select! {
_update = watch.receiver.recv() => {},
_timeout = sleep(Duration::from_millis(250)) => {},
}
}
Ok(())
}
pub async fn wait(self, desired: u32, deadline: Duration) -> Result<()> {
if let Some(err) = timeout(deadline, self.do_wait(desired)).await.err() {
return Err(Error::DevStateWaitError(format!(
"took too long for devices to be ready: {}",
err
)));
}
Ok(())
}
}

View File

@ -34,6 +34,8 @@ pub enum Error {
RegexError(#[from] regex::Error),
#[error("error: {0}")]
GenericError(String),
#[error("parameter missing: {0}")]
ParameterMissing(&'static str),
#[error("failed to parse int: {0}")]
ParseIntError(#[from] std::num::ParseIntError),
#[error("invalid pci bdf string")]
@ -42,6 +44,12 @@ pub enum Error {
PciDeviceNotAssignable(PciBdf),
#[error("xen platform error: {0}")]
XenPlatform(#[from] xenplatform::error::Error),
#[error("invalid block index")]
InvalidBlockIdx,
#[error("device state wait error: {0}")]
DevStateWaitError(String),
#[error("device ids exhausted")]
DevIdExhausted,
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -1,12 +1,11 @@
pub mod error;
use crate::error::{Error, Result};
use config::{DomainConfig, DomainResult};
use error::{Error, Result};
use log::{debug, trace};
use pci::PciBdf;
use tokio::time::timeout;
use tx::ClientTransaction;
use xenplatform::boot::BootSetupPlatform;
use xenplatform::domain::{BaseDomainConfig, BaseDomainManager, CreatedDomain};
use tx::{DeviceConfig, XenTransaction};
use xenplatform::domain::{PlatformDomainInfo, PlatformDomainManager};
use std::path::PathBuf;
use std::str::FromStr;
@ -15,109 +14,26 @@ use std::time::Duration;
use xencall::XenCall;
use xenstore::{XsdClient, XsdInterface};
pub mod config;
pub mod devalloc;
pub mod devstate;
pub mod pci;
pub mod tx;
pub mod util;
#[derive(Clone)]
pub struct XenClient<P: BootSetupPlatform> {
pub struct XenClient {
pub store: XsdClient,
pub call: XenCall,
domain_manager: Arc<BaseDomainManager<P>>,
}
#[derive(Clone, Debug)]
pub struct BlockDeviceRef {
pub path: String,
pub major: u32,
pub minor: u32,
}
#[derive(Clone, Debug)]
pub struct DomainDisk {
pub vdev: String,
pub block: BlockDeviceRef,
pub writable: bool,
}
#[derive(Clone, Debug)]
pub struct DomainFilesystem {
pub path: String,
pub tag: String,
}
#[derive(Clone, Debug)]
pub struct DomainNetworkInterface {
pub mac: String,
pub mtu: u32,
pub bridge: Option<String>,
pub script: Option<String>,
}
#[derive(Clone, Debug)]
pub struct DomainChannel {
pub typ: String,
pub initialized: bool,
}
#[derive(Clone, Debug)]
pub struct DomainEventChannel {
pub name: String,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum DomainPciRdmReservePolicy {
Invalid,
#[default]
Strict,
Relaxed,
}
impl DomainPciRdmReservePolicy {
pub fn to_option_str(&self) -> &str {
match self {
DomainPciRdmReservePolicy::Invalid => "-1",
DomainPciRdmReservePolicy::Strict => "0",
DomainPciRdmReservePolicy::Relaxed => "1",
}
}
}
#[derive(Clone, Debug)]
pub struct DomainPciDevice {
pub bdf: PciBdf,
pub permissive: bool,
pub msi_translate: bool,
pub power_management: bool,
pub rdm_reserve_policy: DomainPciRdmReservePolicy,
}
#[derive(Clone, Debug)]
pub struct DomainConfig {
pub base: BaseDomainConfig,
pub backend_domid: u32,
pub name: String,
pub disks: Vec<DomainDisk>,
pub swap_console_backend: Option<String>,
pub channels: Vec<DomainChannel>,
pub vifs: Vec<DomainNetworkInterface>,
pub filesystems: Vec<DomainFilesystem>,
pub pcis: Vec<DomainPciDevice>,
pub extra_keys: Vec<(String, String)>,
pub extra_rw_paths: Vec<String>,
}
#[derive(Debug)]
pub struct CreatedChannel {
pub ring_ref: u64,
pub evtchn: u32,
domain_manager: Arc<PlatformDomainManager>,
}
#[allow(clippy::too_many_arguments)]
impl<P: BootSetupPlatform> XenClient<P> {
pub async fn new(current_domid: u32, platform: P) -> Result<XenClient<P>> {
impl XenClient {
pub async fn new() -> Result<XenClient> {
let store = XsdClient::open().await?;
let call: XenCall = XenCall::open(current_domid)?;
let domain_manager = BaseDomainManager::new(call.clone(), platform).await?;
let call: XenCall = XenCall::open(0)?;
let domain_manager = PlatformDomainManager::new(call.clone()).await?;
Ok(XenClient {
store,
call,
@ -125,29 +41,47 @@ impl<P: BootSetupPlatform> XenClient<P> {
})
}
pub async fn create(&self, config: &DomainConfig) -> Result<CreatedDomain> {
let created = self.domain_manager.create(config.base.clone()).await?;
match self.init(created.domid, config, &created).await {
Ok(_) => Ok(created),
pub async fn create(&self, config: DomainConfig) -> Result<DomainResult> {
let platform = config
.get_platform()
.as_ref()
.ok_or_else(|| Error::ParameterMissing("platform"))?
.clone();
let platform = self.domain_manager.create(platform).await?;
match self.init(platform.domid, config, &platform).await {
Ok(result) => Ok(result),
Err(err) => {
// ignore since destroying a domain is best-effort when an error occurs
let _ = self.domain_manager.destroy(created.domid).await;
let _ = self.domain_manager.destroy(platform.domid).await;
Err(err)
}
}
}
pub async fn transaction(&self, domid: u32, backend_domid: u32) -> Result<ClientTransaction> {
ClientTransaction::new(&self.store, domid, backend_domid).await
pub async fn transaction(&self, domid: u32, backend_domid: u32) -> Result<XenTransaction> {
XenTransaction::new(&self.store, domid, backend_domid).await
}
async fn init(&self, domid: u32, config: &DomainConfig, created: &CreatedDomain) -> Result<()> {
async fn init(
&self,
domid: u32,
mut config: DomainConfig,
created: &PlatformDomainInfo,
) -> Result<DomainResult> {
trace!("xenclient init domid={} domain={:?}", domid, created);
let transaction = self.transaction(domid, config.backend_domid).await?;
transaction
.add_domain_declaration(&config.name, &config.base, created)
.await?;
transaction.commit().await?;
let platform_config = config
.get_platform()
.as_ref()
.ok_or_else(|| Error::ParameterMissing("platform"))?;
loop {
let transaction = self.transaction(domid, config.get_backend_domid()).await?;
transaction
.add_domain_declaration(config.get_name().clone(), platform_config, created)
.await?;
if transaction.maybe_commit().await? {
break;
}
}
if !self
.store
.introduce_domain(domid, created.store_mfn, created.store_evtchn)
@ -155,57 +89,69 @@ impl<P: BootSetupPlatform> XenClient<P> {
{
return Err(Error::IntroduceDomainFailed);
}
let transaction = self.transaction(domid, config.backend_domid).await?;
transaction
.add_channel_device(
created,
0,
&DomainChannel {
typ: config
.swap_console_backend
.clone()
.unwrap_or("xenconsoled".to_string())
.to_string(),
initialized: true,
},
)
.await?;
config.prepare(domid, &self.call, created).await?;
let mut channels;
let mut vifs;
let mut vbds;
let mut fs9ps;
let mut pci_result;
loop {
let transaction = self.transaction(domid, config.get_backend_domid()).await?;
for (index, channel) in config.channels.iter().enumerate() {
transaction
.add_channel_device(created, index + 1, channel)
.await?;
channels = Vec::new();
for channel in config.get_channels() {
let result = channel.add_to_transaction(&transaction).await?;
channels.push(result);
}
vifs = Vec::new();
for vif in config.get_vifs() {
let result = vif.add_to_transaction(&transaction).await?;
vifs.push(result);
}
vbds = Vec::new();
for vbd in config.get_vbds() {
let result = vbd.add_to_transaction(&transaction).await?;
vbds.push(result);
}
fs9ps = Vec::new();
for fs9p in config.get_fs9ps() {
let result = fs9p.add_to_transaction(&transaction).await?;
fs9ps.push(result);
}
pci_result = None;
if let Some(pci) = config.get_pci().as_ref() {
pci_result = Some(pci.add_to_transaction(&transaction).await?);
}
for (key, value) in config.get_extra_keys() {
transaction.write(key, value, None).await?;
}
for rw_path in config.get_rw_paths() {
transaction.add_rw_path(rw_path).await?;
}
if transaction.maybe_commit().await? {
break;
}
}
for (index, disk) in config.disks.iter().enumerate() {
transaction.add_vbd_device(index, disk).await?;
if config.get_start() {
self.call.unpause_domain(domid).await?;
}
for (index, filesystem) in config.filesystems.iter().enumerate() {
transaction.add_9pfs_device(index, filesystem).await?;
}
for (index, vif) in config.vifs.iter().enumerate() {
transaction.add_vif_device(index, vif).await?;
}
for (index, pci) in config.pcis.iter().enumerate() {
transaction
.add_pci_device(&self.call, index, config.pcis.len(), pci)
.await?;
}
for (key, value) in &config.extra_keys {
transaction.write_key(key, value).await?;
}
for key in &config.extra_rw_paths {
transaction.add_rw_path(key).await?;
}
transaction.commit().await?;
self.call.unpause_domain(domid).await?;
Ok(())
Ok(DomainResult {
platform: created.clone(),
channels,
vifs,
vbds,
fs9ps,
pci: pci_result,
})
}
pub async fn destroy(&self, domid: u32) -> Result<()> {
@ -251,39 +197,7 @@ impl<P: BootSetupPlatform> XenClient<P> {
}
for backend in &backend_paths {
let state_path = format!("{}/state", backend);
let mut watch = self.store.create_watch(&state_path).await?;
let online_path = format!("{}/online", backend);
let tx = self.store.transaction().await?;
let state = tx.read_string(&state_path).await?.unwrap_or(String::new());
if state.is_empty() {
break;
}
tx.write_string(&online_path, "0").await?;
if !state.is_empty() && u32::from_str(&state).unwrap_or(0) != 6 {
tx.write_string(&state_path, "5").await?;
}
self.store.bind_watch(&watch).await?;
tx.commit().await?;
let mut count: u32 = 0;
loop {
if count >= 3 {
debug!("unable to safely destroy backend: {}", backend);
break;
}
let _ = timeout(Duration::from_secs(1), watch.receiver.recv()).await;
let state = self
.store
.read_string(&state_path)
.await?
.unwrap_or_else(|| "6".to_string());
let state = i64::from_str(&state).unwrap_or(-1);
if state == 6 {
break;
}
count += 1;
}
self.destroy_backend(backend).await?;
}
let tx = self.store.transaction().await?;
@ -305,4 +219,72 @@ impl<P: BootSetupPlatform> XenClient<P> {
tx.commit().await?;
Ok(())
}
async fn destroy_backend(&self, backend: &str) -> Result<()> {
let state_path = format!("{}/state", backend);
let mut watch = self.store.create_watch(&state_path).await?;
let online_path = format!("{}/online", backend);
let tx = self.store.transaction().await?;
let state = tx.read_string(&state_path).await?.unwrap_or(String::new());
if state.is_empty() {
return Ok(());
}
tx.write_string(&online_path, "0").await?;
if !state.is_empty() && u32::from_str(&state).unwrap_or(0) != 6 {
tx.write_string(&state_path, "5").await?;
}
self.store.bind_watch(&watch).await?;
tx.commit().await?;
let mut count: u32 = 0;
loop {
if count >= 3 {
debug!("unable to safely destroy backend: {}", backend);
break;
}
let _ = timeout(Duration::from_secs(1), watch.receiver.recv()).await;
let state = self
.store
.read_string(&state_path)
.await?
.unwrap_or_else(|| "6".to_string());
let state = i64::from_str(&state).unwrap_or(-1);
if state == 6 {
break;
}
count += 1;
}
self.store.rm(backend).await?;
Ok(())
}
pub async fn destroy_device(
&self,
category: &str,
domid: u32,
devid: u64,
blkid: Option<u32>,
) -> Result<()> {
let dom_path = self.store.get_domain_path(domid).await?;
let device_path = format!("{}/device/{}/{}", dom_path, category, devid);
if let Some(backend_path) = self
.store
.read_string(format!("{}/backend", device_path).as_str())
.await?
{
self.destroy_backend(&backend_path).await?;
}
self.destroy_backend(&device_path).await?;
loop {
let tx = self.transaction(domid, 0).await?;
tx.release_devid(devid).await?;
if let Some(blkid) = blkid {
tx.release_blkid(blkid).await?;
}
if tx.maybe_commit().await? {
break;
}
}
Ok(())
}
}

View File

@ -1,582 +0,0 @@
use indexmap::IndexMap;
use xencall::{sys::DOMCTL_DEV_RDM_RELAXED, XenCall};
use xenplatform::{
domain::{BaseDomainConfig, CreatedDomain},
sys::XEN_PAGE_SHIFT,
};
use xenstore::{
XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
XS_PERM_READ_WRITE,
};
use crate::{
error::{Error, Result},
pci::XenPciBackend,
DomainChannel, DomainDisk, DomainFilesystem, DomainNetworkInterface, DomainPciDevice,
DomainPciRdmReservePolicy,
};
pub struct ClientTransaction {
tx: XsdTransaction,
abort: bool,
domid: u32,
dom_path: String,
backend_domid: u32,
backend_dom_path: String,
}
impl ClientTransaction {
pub async fn new(store: &XsdClient, domid: u32, backend_domid: u32) -> Result<Self> {
let backend_dom_path = store.get_domain_path(0).await?;
let dom_path = store.get_domain_path(domid).await?;
Ok(ClientTransaction {
tx: store.transaction().await?,
abort: true,
domid,
dom_path,
backend_domid,
backend_dom_path,
})
}
pub async fn add_domain_declaration(
&self,
name: impl AsRef<str>,
base: &BaseDomainConfig,
created: &CreatedDomain,
) -> Result<()> {
let vm_path = format!("/vm/{}", base.uuid);
let ro_perm = &[
XsPermission {
id: 0,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.domid,
perms: XS_PERM_READ,
},
];
let no_perm = &[XsPermission {
id: 0,
perms: XS_PERM_NONE,
}];
let rw_perm = &[XsPermission {
id: self.domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx.rm(&self.dom_path).await?;
self.tx.mknod(&self.dom_path, ro_perm).await?;
self.tx.rm(&vm_path).await?;
self.tx.mknod(&vm_path, ro_perm).await?;
self.tx.mknod(&vm_path, no_perm).await?;
self.tx
.mknod(format!("{}/device", vm_path).as_str(), no_perm)
.await?;
self.tx
.write_string(format!("{}/vm", self.dom_path).as_str(), &vm_path)
.await?;
self.tx
.mknod(format!("{}/cpu", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(format!("{}/memory", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(format!("{}/control", self.dom_path).as_str(), ro_perm)
.await?;
self.tx
.mknod(
format!("{}/control/shutdown", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-poweroff", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-reboot", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(
format!("{}/control/feature-suspend", self.dom_path).as_str(),
rw_perm,
)
.await?;
self.tx
.mknod(format!("{}/control/sysrq", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/data", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/drivers", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/feature", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/attr", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.mknod(format!("{}/error", self.dom_path).as_str(), rw_perm)
.await?;
self.tx
.write_string(format!("{}/uuid", vm_path).as_str(), &base.uuid.to_string())
.await?;
self.tx
.write_string(format!("{}/name", self.dom_path).as_str(), name.as_ref())
.await?;
self.tx
.write_string(format!("{}/name", vm_path).as_str(), name.as_ref())
.await?;
self.tx
.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
.await?;
self.tx
.write_string(format!("{}/image/cmdline", vm_path).as_str(), &base.cmdline)
.await?;
self.tx
.write_string(
format!("{}/memory/static-max", self.dom_path).as_str(),
&(base.max_mem_mb * 1024).to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/memory/target", self.dom_path).as_str(),
&(base.target_mem_mb * 1024).to_string(),
)
.await?;
self.tx
.write_string(format!("{}/memory/videoram", self.dom_path).as_str(), "0")
.await?;
self.tx
.write_string(
format!("{}/domid", self.dom_path).as_str(),
&created.domid.to_string(),
)
.await?;
self.tx
.write_string(format!("{}/type", self.dom_path).as_str(), "PV")
.await?;
self.tx
.write_string(
format!("{}/store/port", self.dom_path).as_str(),
&created.store_evtchn.to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/store/ring-ref", self.dom_path).as_str(),
&created.store_mfn.to_string(),
)
.await?;
for i in 0..base.max_vcpus {
let path = format!("{}/cpu/{}", self.dom_path, i);
self.tx.mkdir(&path).await?;
self.tx.set_perms(&path, ro_perm).await?;
let path = format!("{}/cpu/{}/availability", self.dom_path, i);
self.tx
.write_string(
&path,
if i < base.target_vcpus {
"online"
} else {
"offline"
},
)
.await?;
self.tx.set_perms(&path, ro_perm).await?;
}
Ok(())
}
pub async fn write_key(&self, key: impl AsRef<str>, value: impl AsRef<str>) -> Result<()> {
self.tx
.write_string(
&format!("{}/{}", self.dom_path, key.as_ref()),
value.as_ref(),
)
.await?;
Ok(())
}
pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
let rw_perm = &[XsPermission {
id: self.domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx
.mknod(&format!("{}/{}", self.dom_path, key.as_ref()), rw_perm)
.await?;
Ok(())
}
pub async fn add_device(
&self,
typ: impl AsRef<str>,
id: u64,
frontend_items: Vec<(&str, String)>,
backend_items: Vec<(&str, String)>,
) -> Result<()> {
let console_zero = typ.as_ref() == "console" && id == 0;
let frontend_path = if console_zero {
format!("{}/console", self.dom_path)
} else {
format!("{}/device/{}/{}", self.dom_path, typ.as_ref(), id)
};
let backend_path = format!(
"{}/backend/{}/{}/{}",
self.backend_dom_path,
typ.as_ref(),
self.domid,
id
);
let mut backend_items: Vec<(&str, String)> = backend_items.clone();
let mut frontend_items: Vec<(&str, String)> = frontend_items.clone();
backend_items.push(("frontend", frontend_path.clone()));
frontend_items.push(("backend", backend_path.clone()));
let frontend_perms = &[
XsPermission {
id: self.domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.backend_domid,
perms: XS_PERM_READ,
},
];
let backend_perms = &[
XsPermission {
id: self.backend_domid,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.domid,
perms: XS_PERM_READ,
},
];
self.tx.mknod(&frontend_path, frontend_perms).await?;
for (p, value) in &frontend_items {
let path = format!("{}/{}", frontend_path, *p);
self.tx.write_string(&path, value).await?;
if !console_zero {
self.tx.set_perms(&path, frontend_perms).await?;
}
}
self.tx.mknod(&backend_path, backend_perms).await?;
for (p, value) in &backend_items {
let path = format!("{}/{}", backend_path, *p);
self.tx.write_string(&path, value).await?;
}
Ok(())
}
pub async fn add_vbd_device(&self, index: usize, disk: &DomainDisk) -> Result<()> {
let id = (202 << 8) | (index << 4) as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("removable", "0".to_string()),
("bootable", "1".to_string()),
("state", "1".to_string()),
("dev", disk.vdev.to_string()),
("type", "phy".to_string()),
("mode", if disk.writable { "w" } else { "r" }.to_string()),
("device-type", "disk".to_string()),
("discard-enable", "0".to_string()),
("specification", "xen".to_string()),
("physical-device-path", disk.block.path.to_string()),
(
"physical-device",
format!("{:02x}:{:02x}", disk.block.major, disk.block.minor),
),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("virtual-device", id.to_string()),
("device-type", "disk".to_string()),
("trusted", "1".to_string()),
("protocol", "x86_64-abi".to_string()),
];
self.add_device("vbd", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_vif_device(&self, index: usize, vif: &DomainNetworkInterface) -> Result<()> {
let id = 20 + index as u64;
let mut backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("mtu", vif.mtu.to_string()),
("type", "vif".to_string()),
("handle", id.to_string()),
];
if vif.bridge.is_some() {
backend_items.extend_from_slice(&[("bridge", vif.bridge.clone().unwrap())]);
}
if vif.script.is_some() {
backend_items.extend_from_slice(&[
("script", vif.script.clone().unwrap()),
("hotplug-status", "".to_string()),
]);
} else {
backend_items.extend_from_slice(&[
("script", "".to_string()),
("hotplug-status", "connected".to_string()),
]);
}
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("mac", vif.mac.to_string()),
("trusted", "1".to_string()),
("mtu", vif.mtu.to_string()),
];
self.add_device("vif", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_9pfs_device(&self, index: usize, filesystem: &DomainFilesystem) -> Result<()> {
let id = 90 + index as u64;
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("path", filesystem.path.to_string()),
("security-model", "none".to_string()),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
("tag", filesystem.tag.to_string()),
];
self.add_device("9pfs", id, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_channel_device(
&self,
domain: &CreatedDomain,
index: usize,
channel: &DomainChannel,
) -> Result<()> {
let port = domain.console_evtchn;
let ring = domain.console_mfn;
let mut backend_items = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("protocol", "vt100".to_string()),
];
let mut frontend_items = vec![
("backend-id", self.backend_domid.to_string()),
("limit", "1048576".to_string()),
("output", "pty".to_string()),
("tty", "".to_string()),
];
frontend_items.push(("type", channel.typ.clone()));
backend_items.push(("type", channel.typ.clone()));
if index == 0 {
if channel.typ != "xenconsoled" {
frontend_items.push(("state", "1".to_string()));
}
frontend_items
.extend_from_slice(&[("port", port.to_string()), ("ring-ref", ring.to_string())]);
} else {
frontend_items.extend_from_slice(&[
("state", "1".to_string()),
("protocol", "vt100".to_string()),
]);
}
if channel.initialized {
backend_items.push(("state", "4".to_string()));
} else {
backend_items.push(("state", "1".to_string()));
}
self.add_device("console", index as u64, frontend_items, backend_items)
.await?;
Ok(())
}
pub async fn add_pci_device(
&self,
call: &XenCall,
index: usize,
device_count: usize,
device: &DomainPciDevice,
) -> Result<()> {
let backend = XenPciBackend::new();
if !backend.is_assigned(&device.bdf).await? {
return Err(Error::PciDeviceNotAssignable(device.bdf));
}
let resources = backend.read_resources(&device.bdf).await?;
for resource in resources {
if resource.is_bar_io() {
call.ioport_permission(
self.domid,
resource.start as u32,
resource.size() as u32,
true,
)
.await?;
} else {
call.iomem_permission(
self.domid,
resource.start >> XEN_PAGE_SHIFT,
(resource.size() + (XEN_PAGE_SHIFT - 1)) >> XEN_PAGE_SHIFT,
true,
)
.await?;
}
}
if let Some(irq) = backend.read_irq(&device.bdf).await? {
let irq = call.map_pirq(self.domid, irq as isize, None).await?;
call.irq_permission(self.domid, irq, true).await?;
}
backend.reset(&device.bdf).await?;
call.assign_device(
self.domid,
device.bdf.encode(),
if device.rdm_reserve_policy == DomainPciRdmReservePolicy::Relaxed {
DOMCTL_DEV_RDM_RELAXED
} else {
0
},
)
.await?;
if device.permissive {
backend.enable_permissive(&device.bdf).await?;
}
let id = 60;
if index == 0 {
let backend_items: Vec<(&str, String)> = vec![
("frontend-id", self.domid.to_string()),
("online", "1".to_string()),
("state", "1".to_string()),
("num_devs", device_count.to_string()),
];
let frontend_items: Vec<(&str, String)> = vec![
("backend-id", self.backend_domid.to_string()),
("state", "1".to_string()),
];
self.add_device("pci", id, frontend_items, backend_items)
.await?;
}
let backend_path = format!(
"{}/backend/{}/{}/{}",
self.backend_dom_path, "pci", self.domid, id
);
self.tx
.write_string(
format!("{}/key-{}", backend_path, index),
&device.bdf.to_string(),
)
.await?;
self.tx
.write_string(
format!("{}/dev-{}", backend_path, index),
&device.bdf.to_string(),
)
.await?;
if let Some(vdefn) = device.bdf.vdefn {
self.tx
.write_string(
format!("{}/vdefn-{}", backend_path, index),
&format!("{:#x}", vdefn),
)
.await?;
}
let mut options = IndexMap::new();
options.insert("permissive", if device.permissive { "1" } else { "0" });
options.insert("rdm_policy", device.rdm_reserve_policy.to_option_str());
options.insert("msitranslate", if device.msi_translate { "1" } else { "0" });
options.insert(
"power_mgmt",
if device.power_management { "1" } else { "0" },
);
let options = options
.into_iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<_>>()
.join(",");
self.tx
.write_string(format!("{}/opts-{}", backend_path, index), &options)
.await?;
Ok(())
}
pub async fn commit(mut self) -> Result<()> {
self.abort = false;
self.tx.commit().await?;
Ok(())
}
}
impl Drop for ClientTransaction {
fn drop(&mut self) {
if !self.abort {
return;
}
let tx = self.tx.clone();
tokio::task::spawn(async move {
let _ = tx.abort().await;
});
}
}

View File

@ -0,0 +1,89 @@
use xenplatform::domain::PlatformDomainInfo;
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
use crate::error::{Error, Result};
pub struct ChannelDeviceConfig {
backend_type: String,
default_console: bool,
default_console_options: Option<(u32, u64)>,
backend_initialized: bool,
}
impl Default for ChannelDeviceConfig {
fn default() -> Self {
Self::new()
}
}
impl ChannelDeviceConfig {
pub fn new() -> Self {
Self {
backend_type: "console".to_string(),
default_console: false,
default_console_options: None,
backend_initialized: false,
}
}
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
self.backend_type = backend_type.as_ref().to_string();
self
}
pub fn default_console(&mut self) -> &mut Self {
self.default_console = true;
self
}
pub fn backend_initialized(&mut self) -> &mut Self {
self.backend_initialized = true;
self
}
pub fn done(self) -> Self {
self
}
pub async fn prepare(&mut self, platform: &PlatformDomainInfo) -> Result<()> {
if self.default_console {
self.default_console_options = Some((platform.console_evtchn, platform.console_mfn));
}
Ok(())
}
}
#[async_trait::async_trait]
impl DeviceConfig for ChannelDeviceConfig {
type Result = DeviceResult;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
let id = tx.assign_next_devid().await?;
let mut device = DeviceDescription::new("console", &self.backend_type);
device
.add_backend_bool("online", true)
.add_backend_item("protocol", "vt100")
.add_backend_item("type", &self.backend_type)
.add_backend_item("state", if self.backend_initialized { 4 } else { 1 });
if self.default_console {
device.special_frontend_path("console");
let (port, ring_ref) = self
.default_console_options
.as_ref()
.ok_or_else(|| Error::ParameterMissing("default_console_options"))?;
device
.add_frontend_item("port", port)
.add_frontend_item("ring-ref", ring_ref);
}
device
.add_frontend_item("limit", 1048576)
.add_frontend_item("output", "pty")
.add_frontend_item("tty", "")
.add_frontend_item("type", &self.backend_type)
.add_frontend_item("state", 1);
tx.add_device(id, device).await?;
Ok(DeviceResult { id })
}
}

View File

@ -0,0 +1,78 @@
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
use crate::error::{Error, Result};
pub struct Fs9pDeviceConfig {
backend_type: String,
security_model: String,
path: Option<String>,
tag: Option<String>,
}
impl Default for Fs9pDeviceConfig {
fn default() -> Self {
Self::new()
}
}
impl Fs9pDeviceConfig {
pub fn new() -> Self {
Self {
backend_type: "9pfs".to_string(),
security_model: "none".to_string(),
path: None,
tag: None,
}
}
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
self.backend_type = backend_type.as_ref().to_string();
self
}
pub fn security_model(&mut self, security_model: impl AsRef<str>) -> &mut Self {
self.security_model = security_model.as_ref().to_string();
self
}
pub fn path(&mut self, path: impl AsRef<str>) -> &mut Self {
self.path = Some(path.as_ref().to_string());
self
}
pub fn tag(&mut self, tag: impl AsRef<str>) -> &mut Self {
self.tag = Some(tag.as_ref().to_string());
self
}
pub fn done(self) -> Self {
self
}
}
#[async_trait::async_trait]
impl DeviceConfig for Fs9pDeviceConfig {
type Result = DeviceResult;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
let id = tx.assign_next_devid().await?;
let path = self
.path
.as_ref()
.ok_or_else(|| Error::ParameterMissing("path"))?;
let tag = self
.tag
.as_ref()
.ok_or_else(|| Error::ParameterMissing("tag"))?;
let mut device = DeviceDescription::new("9pfs", &self.backend_type);
device
.add_backend_bool("online", true)
.add_backend_item("state", 1)
.add_backend_item("path", path)
.add_backend_item("security_model", &self.security_model);
device
.add_frontend_item("state", 1)
.add_frontend_item("tag", tag);
tx.add_device(id, device).await?;
Ok(DeviceResult { id })
}
}

View File

@ -0,0 +1,425 @@
pub mod channel;
pub mod fs9p;
pub mod pci;
pub mod vbd;
pub mod vif;
use crate::{
devalloc::DeviceIdAllocator,
error::{Error, Result},
};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::Mutex;
use xenplatform::domain::{PlatformDomainConfig, PlatformDomainInfo};
use xenstore::{
XsPermission, XsdClient, XsdInterface, XsdTransaction, XS_PERM_NONE, XS_PERM_READ,
XS_PERM_READ_WRITE,
};
pub struct XenTransaction {
frontend_domid: u32,
frontend_dom_path: String,
backend_domid: u32,
backend_dom_path: String,
blkalloc: Arc<Mutex<DeviceIdAllocator>>,
devalloc: Arc<Mutex<DeviceIdAllocator>>,
tx: XsdTransaction,
abort: bool,
}
impl XenTransaction {
pub async fn new(store: &XsdClient, frontend_domid: u32, backend_domid: u32) -> Result<Self> {
let frontend_dom_path = store.get_domain_path(frontend_domid).await?;
let backend_dom_path = store.get_domain_path(backend_domid).await?;
let tx = store.transaction().await?;
let devalloc = XenTransaction::load_id_allocator(&tx, "devid", &frontend_dom_path).await?;
let blkalloc = XenTransaction::load_id_allocator(&tx, "blkid", &frontend_dom_path).await?;
Ok(XenTransaction {
frontend_domid,
frontend_dom_path,
backend_domid,
backend_dom_path,
tx,
devalloc: Arc::new(Mutex::new(devalloc)),
blkalloc: Arc::new(Mutex::new(blkalloc)),
abort: true,
})
}
async fn load_id_allocator(
tx: &XsdTransaction,
allocator_type: &str,
frontend_dom_path: &str,
) -> Result<DeviceIdAllocator> {
let state = tx
.read(format!(
"{}/{}-alloc-state",
frontend_dom_path, allocator_type
))
.await?;
let allocator = state
.and_then(|state| DeviceIdAllocator::deserialize(&state))
.unwrap_or_else(DeviceIdAllocator::new);
Ok(allocator)
}
pub async fn assign_next_devid(&self) -> Result<u64> {
self.devalloc
.lock()
.await
.allocate()
.ok_or(Error::DevIdExhausted)
.map(|x| x as u64)
}
pub async fn assign_next_blkidx(&self) -> Result<u32> {
self.blkalloc
.lock()
.await
.allocate()
.ok_or(Error::DevIdExhausted)
}
pub async fn release_devid(&self, devid: u64) -> Result<()> {
self.devalloc.lock().await.release(devid as u32);
Ok(())
}
pub async fn release_blkid(&self, blkid: u32) -> Result<()> {
self.blkalloc.lock().await.release(blkid);
Ok(())
}
pub async fn write(
&self,
key: impl AsRef<str>,
value: impl AsRef<str>,
perms: Option<&[XsPermission]>,
) -> Result<()> {
let path = format!("{}/{}", self.frontend_dom_path, key.as_ref());
if let Some(perms) = perms {
self.tx.mknod(&path, perms).await?;
}
// empty string is written by mknod, if perms is set we can skip it.
if perms.is_none() || perms.is_some() && !value.as_ref().is_empty() {
self.tx.write_string(path, value.as_ref()).await?;
}
Ok(())
}
pub async fn add_domain_declaration(
&self,
name: Option<impl AsRef<str>>,
platform: &PlatformDomainConfig,
created: &PlatformDomainInfo,
) -> Result<()> {
let vm_path = format!("/vm/{}", platform.uuid);
let ro_perm = &[
XsPermission {
id: 0,
perms: XS_PERM_NONE,
},
XsPermission {
id: self.frontend_domid,
perms: XS_PERM_READ,
},
];
let no_perm = &[XsPermission {
id: 0,
perms: XS_PERM_NONE,
}];
let rw_perm = &[XsPermission {
id: self.frontend_domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx.rm(&self.frontend_dom_path).await?;
self.tx.mknod(&self.frontend_dom_path, ro_perm).await?;
self.tx.rm(&vm_path).await?;
self.tx.mknod(&vm_path, no_perm).await?;
self.tx
.write_string(format!("{}/uuid", vm_path), &platform.uuid.to_string())
.await?;
self.write("vm", &vm_path, None).await?;
self.write("cpu", "", Some(ro_perm)).await?;
self.write("memory", "", Some(ro_perm)).await?;
self.write("control", "", Some(ro_perm)).await?;
self.write("control/shutdown", "", Some(rw_perm)).await?;
self.write("control/feature-poweroff", "", Some(rw_perm))
.await?;
self.write("control/feature-reboot", "", Some(rw_perm))
.await?;
self.write("control/feature-suspend", "", Some(rw_perm))
.await?;
self.write("control/sysrq", "", Some(rw_perm)).await?;
self.write("data", "", Some(rw_perm)).await?;
self.write("drivers", "", Some(rw_perm)).await?;
self.write("feature", "", Some(rw_perm)).await?;
self.write("attr", "", Some(rw_perm)).await?;
self.write("error", "", Some(rw_perm)).await?;
self.write("uuid", platform.uuid.to_string(), Some(ro_perm))
.await?;
if let Some(name) = name {
self.write("name", name.as_ref(), Some(ro_perm)).await?;
}
self.write(
"memory/static-max",
(platform.resources.max_memory_mb * 1024).to_string(),
None,
)
.await?;
self.write(
"memory/target",
(platform.resources.assigned_memory_mb * 1024).to_string(),
None,
)
.await?;
self.write("memory/videoram", "0", None).await?;
self.write("domid", self.frontend_domid.to_string(), None)
.await?;
self.write("type", "PV", None).await?;
self.write("store/port", created.store_evtchn.to_string(), None)
.await?;
self.write("store/ring-ref", created.store_mfn.to_string(), None)
.await?;
for i in 0..platform.resources.max_vcpus {
let path = format!("{}/cpu/{}", self.frontend_dom_path, i);
self.tx.mkdir(&path).await?;
self.tx.set_perms(&path, ro_perm).await?;
let path = format!("{}/cpu/{}/availability", self.frontend_dom_path, i);
self.tx
.write_string(
&path,
if i < platform.resources.assigned_vcpus {
"online"
} else {
"offline"
},
)
.await?;
self.tx.set_perms(&path, ro_perm).await?;
}
Ok(())
}
pub async fn add_device(&self, id: u64, device: DeviceDescription) -> Result<()> {
let frontend_path = if let Some(ref special_frontend_path) = device.special_frontend_path {
format!("{}/{}", self.frontend_dom_path, special_frontend_path)
} else {
format!(
"{}/device/{}/{}",
self.frontend_dom_path, device.frontend_type, id
)
};
let backend_path = format!(
"{}/backend/{}/{}/{}",
self.backend_dom_path, device.backend_type, self.frontend_domid, id
);
let frontend_perms = &[
XsPermission {
id: self.frontend_domid,
perms: XS_PERM_READ_WRITE,
},
XsPermission {
id: self.backend_domid,
perms: XS_PERM_READ,
},
];
let backend_perms = &[
XsPermission {
id: self.backend_domid,
perms: XS_PERM_READ_WRITE,
},
XsPermission {
id: self.frontend_domid,
perms: XS_PERM_READ,
},
];
self.tx.mknod(&frontend_path, frontend_perms).await?;
self.tx.mknod(&backend_path, backend_perms).await?;
for (key, value) in &device.backend_items {
let path = format!("{}/{}", backend_path, key);
self.tx.write_string(&path, value).await?;
}
self.tx
.write_string(format!("{}/frontend", backend_path), &frontend_path)
.await?;
self.tx
.write_string(
format!("{}/frontend-id", backend_path),
&self.frontend_domid.to_string(),
)
.await?;
for (key, value) in &device.frontend_items {
let path = format!("{}/{}", frontend_path, key);
self.tx.write_string(&path, value).await?;
if device.special_frontend_path.is_none() {
self.tx.set_perms(&path, frontend_perms).await?;
}
}
self.tx
.write_string(format!("{}/backend", frontend_path), &backend_path)
.await?;
self.tx
.write_string(
format!("{}/backend-id", frontend_path),
&self.backend_domid.to_string(),
)
.await?;
Ok(())
}
pub async fn add_rw_path(&self, key: impl AsRef<str>) -> Result<()> {
let rw_perm = &[XsPermission {
id: self.frontend_domid,
perms: XS_PERM_READ_WRITE,
}];
self.tx
.mknod(
&format!("{}/{}", self.frontend_dom_path, key.as_ref()),
rw_perm,
)
.await?;
Ok(())
}
async fn before_commit(&self) -> Result<()> {
let devid_allocator_state = self.devalloc.lock().await.serialize();
let blkid_allocator_state = self.blkalloc.lock().await.serialize();
self.tx
.write(
format!("{}/devid-alloc-state", self.frontend_dom_path),
devid_allocator_state,
)
.await?;
self.tx
.write(
format!("{}/blkid-alloc-state", self.frontend_dom_path),
blkid_allocator_state,
)
.await?;
Ok(())
}
pub async fn maybe_commit(mut self) -> Result<bool> {
self.abort = false;
self.before_commit().await?;
Ok(self.tx.maybe_commit().await?)
}
pub async fn commit(mut self) -> Result<()> {
self.abort = false;
self.before_commit().await?;
self.tx.commit().await?;
Ok(())
}
}
impl Drop for XenTransaction {
fn drop(&mut self) {
if !self.abort {
return;
}
let tx = self.tx.clone();
tokio::task::spawn(async move {
let _ = tx.abort().await;
});
}
}
pub struct DeviceDescription {
frontend_type: String,
backend_type: String,
special_frontend_path: Option<String>,
frontend_items: HashMap<String, String>,
backend_items: HashMap<String, String>,
}
impl DeviceDescription {
pub fn new(frontend_type: impl AsRef<str>, backend_type: impl AsRef<str>) -> Self {
Self {
frontend_type: frontend_type.as_ref().to_string(),
backend_type: backend_type.as_ref().to_string(),
special_frontend_path: None,
frontend_items: HashMap::new(),
backend_items: HashMap::new(),
}
}
pub fn special_frontend_path(&mut self, path: impl AsRef<str>) -> &mut Self {
self.special_frontend_path = Some(path.as_ref().to_string());
self
}
pub fn add_frontend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
self.frontend_items
.insert(key.as_ref().to_string(), value.to_string());
self
}
pub fn add_backend_item(&mut self, key: impl AsRef<str>, value: impl ToString) -> &mut Self {
self.backend_items
.insert(key.as_ref().to_string(), value.to_string());
self
}
pub fn add_frontend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
self.add_frontend_item(key, if value { "1" } else { "0" })
}
pub fn add_backend_bool(&mut self, key: impl AsRef<str>, value: bool) -> &mut Self {
self.add_backend_item(key, if value { "1" } else { "0" })
}
pub fn done(self) -> Self {
self
}
}
#[derive(Clone, Debug)]
pub struct DeviceResult {
pub id: u64,
}
#[derive(Clone, Debug)]
pub struct BlockDeviceResult {
pub id: u64,
pub idx: u32,
}
#[async_trait::async_trait]
pub trait DeviceConfig {
type Result;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<Self::Result>;
}
#[derive(Clone, Debug)]
pub struct BlockDeviceRef {
pub path: String,
pub major: u32,
pub minor: u32,
}
impl BlockDeviceRef {
pub fn new(path: impl AsRef<str>, major: u32, minor: u32) -> Self {
Self {
path: path.as_ref().to_string(),
major,
minor,
}
}
}

View File

@ -0,0 +1,194 @@
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
use crate::{
error::{Error, Result},
pci::{PciBdf, XenPciBackend},
};
use indexmap::IndexMap;
use xencall::{sys::DOMCTL_DEV_RDM_RELAXED, XenCall};
use xenplatform::sys::XEN_PAGE_SHIFT;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum PciRdmReservePolicy {
Invalid,
#[default]
Strict,
Relaxed,
}
impl PciRdmReservePolicy {
pub fn to_option_str(&self) -> &str {
match self {
PciRdmReservePolicy::Invalid => "-1",
PciRdmReservePolicy::Strict => "0",
PciRdmReservePolicy::Relaxed => "1",
}
}
}
pub struct PciDeviceConfig {
bdf: PciBdf,
rdm_reserve_policy: PciRdmReservePolicy,
permissive: bool,
msi_translate: bool,
power_management: bool,
}
pub struct PciRootDeviceConfig {
backend_type: String,
devices: Vec<PciDeviceConfig>,
}
impl PciDeviceConfig {
pub fn new(bdf: PciBdf) -> Self {
Self {
bdf,
rdm_reserve_policy: PciRdmReservePolicy::Strict,
permissive: false,
msi_translate: false,
power_management: false,
}
}
pub fn rdm_reserve_policy(&mut self, rdm_reserve_policy: PciRdmReservePolicy) -> &mut Self {
self.rdm_reserve_policy = rdm_reserve_policy;
self
}
pub fn permissive(&mut self, permissive: bool) -> &mut Self {
self.permissive = permissive;
self
}
pub fn msi_translate(&mut self, msi_translate: bool) -> &mut Self {
self.msi_translate = msi_translate;
self
}
pub fn power_management(&mut self, power_management: bool) -> &mut Self {
self.power_management = power_management;
self
}
pub fn done(self) -> Self {
self
}
}
impl Default for PciRootDeviceConfig {
fn default() -> Self {
Self::new()
}
}
impl PciRootDeviceConfig {
pub fn new() -> Self {
Self {
backend_type: "pci".to_string(),
devices: Vec::new(),
}
}
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
self.backend_type = backend_type.as_ref().to_string();
self
}
pub fn add_device(&mut self, device: PciDeviceConfig) -> &mut Self {
self.devices.push(device);
self
}
pub async fn prepare(&self, domid: u32, call: &XenCall) -> Result<()> {
for device in &self.devices {
let backend = XenPciBackend::new();
if !backend.is_assigned(&device.bdf).await? {
return Err(Error::PciDeviceNotAssignable(device.bdf));
}
let resources = backend.read_resources(&device.bdf).await?;
for resource in resources {
if resource.is_bar_io() {
call.ioport_permission(
domid,
resource.start as u32,
resource.size() as u32,
true,
)
.await?;
} else {
call.iomem_permission(
domid,
resource.start >> XEN_PAGE_SHIFT,
(resource.size() + (XEN_PAGE_SHIFT - 1)) >> XEN_PAGE_SHIFT,
true,
)
.await?;
}
}
if let Some(irq) = backend.read_irq(&device.bdf).await? {
let irq = call.map_pirq(domid, irq as isize, None).await?;
call.irq_permission(domid, irq, true).await?;
}
backend.reset(&device.bdf).await?;
call.assign_device(
domid,
device.bdf.encode(),
if device.rdm_reserve_policy == PciRdmReservePolicy::Relaxed {
DOMCTL_DEV_RDM_RELAXED
} else {
0
},
)
.await?;
if device.permissive {
backend.enable_permissive(&device.bdf).await?;
}
}
Ok(())
}
pub fn done(self) -> Self {
self
}
}
#[async_trait::async_trait]
impl DeviceConfig for PciRootDeviceConfig {
type Result = DeviceResult;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
let id = tx.assign_next_devid().await?;
let mut device = DeviceDescription::new("pci", &self.backend_type);
device
.add_backend_bool("online", true)
.add_backend_item("state", 1)
.add_backend_item("num_devs", self.devices.len());
for (index, pci) in self.devices.iter().enumerate() {
let mut options = IndexMap::new();
options.insert("permissive", if pci.permissive { "1" } else { "0" });
options.insert("rdm_policy", pci.rdm_reserve_policy.to_option_str());
options.insert("msitranslate", if pci.msi_translate { "1" } else { "0" });
let options = options
.into_iter()
.map(|(key, value)| format!("{}={}", key, value))
.collect::<Vec<_>>()
.join(",");
device
.add_backend_item(format!("key-{}", index), pci.bdf.to_string())
.add_backend_item(format!("dev-{}", index), pci.bdf.to_string())
.add_backend_item(format!("opts-{}", index), options);
if let Some(vdefn) = pci.bdf.vdefn {
device.add_backend_item(format!("vdefn-{}", index), format!("{:#x}", vdefn));
}
}
device.add_frontend_item("state", 1);
tx.add_device(id, device).await?;
Ok(DeviceResult { id })
}
}

View File

@ -0,0 +1,131 @@
use super::{BlockDeviceRef, BlockDeviceResult, DeviceConfig, DeviceDescription, XenTransaction};
use crate::{
error::{Error, Result},
util::vbd_blkidx_to_disk_name,
};
pub struct VbdDeviceConfig {
backend_type: String,
removable: bool,
bootable: bool,
writable: bool,
discard: bool,
trusted: bool,
block_device: Option<BlockDeviceRef>,
}
impl Default for VbdDeviceConfig {
fn default() -> Self {
Self::new()
}
}
impl VbdDeviceConfig {
pub fn new() -> Self {
Self {
backend_type: "vbd".to_string(),
removable: false,
bootable: true,
writable: false,
discard: false,
trusted: true,
block_device: None,
}
}
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
self.backend_type = backend_type.as_ref().to_string();
self
}
pub fn removable(&mut self, removable: bool) -> &mut Self {
self.removable = removable;
self
}
pub fn bootable(&mut self, bootable: bool) -> &mut Self {
self.bootable = bootable;
self
}
pub fn writable(&mut self, writable: bool) -> &mut Self {
self.writable = writable;
self
}
pub fn discard(&mut self, discard: bool) -> &mut Self {
self.discard = discard;
self
}
pub fn trusted(&mut self, trusted: bool) -> &mut Self {
self.trusted = trusted;
self
}
pub fn block_device(&mut self, block_device: BlockDeviceRef) -> &mut Self {
self.block_device = Some(block_device);
self
}
pub fn done(self) -> Self {
self
}
}
#[async_trait::async_trait]
impl DeviceConfig for VbdDeviceConfig {
type Result = BlockDeviceResult;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<BlockDeviceResult> {
let id = tx.assign_next_devid().await?;
let idx = tx.assign_next_blkidx().await?;
let vdev = vbd_blkidx_to_disk_name(idx)?;
let block_device = self
.block_device
.as_ref()
.ok_or_else(|| Error::ParameterMissing("block device"))?;
let mut device = DeviceDescription::new("vbd", &self.backend_type);
device
.add_backend_item("online", 1)
.add_backend_bool("removable", self.removable)
.add_backend_bool("bootable", self.bootable)
.add_backend_item("type", "phy")
.add_backend_item("device-type", "disk")
.add_backend_item("discard-enable", self.discard)
.add_backend_item("specification", "xen")
.add_backend_item("physical-device-path", &block_device.path)
.add_backend_item("mode", if self.writable { "w" } else { "r" })
.add_backend_item(
"physical-device",
format!("{:02x}:{:02x}", block_device.major, block_device.minor),
)
.add_backend_item("dev", &vdev)
.add_backend_item("state", 1);
// we should use standard virtual-device support for first few block devices.
// the kernel warns when you use ext for indexes 5 or less, due to
// potential id overlapping.
let (vdev, vd_key) = if idx <= 5 {
// shift by 4 as partition count is 16
((202 << 8) | (idx as u64 * 16u64), "virtual-device")
} else {
// this is silly but 256 is the number of partitions
// multiply the index by that to get the actual id
((1u64 << 28u64) + (idx as u64) * 256, "virtual-device-ext")
};
device
.add_frontend_item(vd_key, vdev)
.add_frontend_item("state", 1)
.add_frontend_item("device-type", "disk")
.add_frontend_bool("trusted", self.trusted)
.add_frontend_item("protocol", "x86_64-abi")
.add_frontend_item("x-index", idx);
tx.add_device(id, device).await?;
Ok(BlockDeviceResult { id, idx })
}
}

View File

@ -0,0 +1,112 @@
use super::{DeviceConfig, DeviceDescription, DeviceResult, XenTransaction};
use crate::error::{Error, Result};
pub struct VifDeviceConfig {
backend_type: String,
mac: Option<String>,
mtu: Option<u32>,
script: Option<String>,
bridge: Option<String>,
trusted: bool,
}
impl Default for VifDeviceConfig {
fn default() -> Self {
Self::new()
}
}
impl VifDeviceConfig {
pub fn new() -> Self {
Self {
backend_type: "vif".to_string(),
mac: None,
mtu: None,
script: None,
bridge: None,
trusted: true,
}
}
pub fn backend_type(&mut self, backend_type: impl AsRef<str>) -> &mut Self {
self.backend_type = backend_type.as_ref().to_string();
self
}
pub fn mac(&mut self, mac: impl AsRef<str>) -> &mut Self {
self.mac = Some(mac.as_ref().to_string());
self
}
pub fn mtu(&mut self, mtu: u32) -> &mut Self {
self.mtu = Some(mtu);
self
}
pub fn script(&mut self, script: impl AsRef<str>) -> &mut Self {
self.script = Some(script.as_ref().to_string());
self
}
pub fn bridge(&mut self, bridge: impl AsRef<str>) -> &mut Self {
self.bridge = Some(bridge.as_ref().to_string());
self
}
pub fn trusted(&mut self, trusted: bool) -> &mut Self {
self.trusted = trusted;
self
}
pub fn done(self) -> Self {
self
}
}
#[async_trait::async_trait]
impl DeviceConfig for VifDeviceConfig {
type Result = DeviceResult;
async fn add_to_transaction(&self, tx: &XenTransaction) -> Result<DeviceResult> {
let id = tx.assign_next_devid().await?;
let mac = self
.mac
.as_ref()
.ok_or_else(|| Error::ParameterMissing("mac address"))?;
let mtu = self
.mtu
.ok_or_else(|| Error::ParameterMissing("mtu"))?
.to_string();
let mut device = DeviceDescription::new("vif", &self.backend_type);
device
.add_backend_item("online", 1)
.add_backend_item("state", 1)
.add_backend_item("mac", mac)
.add_backend_item("mtu", &mtu)
.add_backend_item("type", "vif")
.add_backend_item("handle", id);
if let Some(bridge) = self.bridge.as_ref() {
device.add_backend_item("bridge", bridge);
}
if let Some(script) = self.script.as_ref() {
device
.add_backend_item("script", script)
.add_backend_item("hotplug-status", "");
} else {
device
.add_backend_item("script", "")
.add_backend_item("hotplug-status", "connected");
}
device
.add_frontend_item("state", 1)
.add_frontend_item("mac", mac)
.add_frontend_item("mtu", &mtu)
.add_frontend_bool("trusted", self.trusted);
tx.add_device(id, device.done()).await?;
Ok(DeviceResult { id })
}
}

View File

@ -0,0 +1,21 @@
use crate::error::{Error, Result};
pub fn vbd_blkidx_to_disk_name(blkid: u32) -> Result<String> {
let mut name = "xvd".to_string();
let mut suffix = String::new();
let mut n = blkid;
loop {
let c = (n % 26) as u8;
let c = b'a' + c;
let c = char::from_u32(c as u32).ok_or(Error::InvalidBlockIdx)?;
suffix.push(c);
if n >= 26 {
n = (n / 26) - 1;
continue;
} else {
break;
}
}
name.push_str(&suffix.chars().rev().collect::<String>());
Ok(name)
}

View File

@ -1,6 +1,6 @@
[package]
name = "krata-xenevtchn"
description = "An implementation of Xen evtchn for krata"
description = "An implementation of Xen evtchn for krata."
license.workspace = true
version.workspace = true
homepage.workspace = true

View File

@ -12,6 +12,8 @@ pub enum Error {
LockAcquireFailed,
#[error("event port already in use")]
PortInUse,
#[error("failed to join blocking task")]
BlockingTaskJoin,
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -3,7 +3,10 @@ pub mod raw;
pub mod sys;
use crate::error::{Error, Result};
use crate::sys::{BindInterdomain, BindUnboundPort, BindVirq, Notify, UnbindPort};
use crate::sys::{
BindInterdomainRequest, BindUnboundPortRequest, BindVirqRequest, NotifyRequest,
UnbindPortRequest,
};
use crate::raw::EVENT_CHANNEL_DEVICE;
use byteorder::{LittleEndian, ReadBytesExt};
@ -16,12 +19,9 @@ use std::os::raw::c_void;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::fs::{File, OpenOptions};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::{Mutex, RwLock};
use tokio::sync::{Mutex, Notify};
const CHANNEL_QUEUE_LEN: usize = 30;
type WakeMap = Arc<RwLock<HashMap<u32, Sender<u32>>>>;
type WakeMap = Arc<Mutex<HashMap<u32, Arc<Notify>>>>;
#[derive(Clone)]
pub struct EventChannelService {
@ -32,7 +32,7 @@ pub struct EventChannelService {
pub struct BoundEventChannel {
pub local_port: u32,
pub receiver: Receiver<u32>,
pub receiver: Arc<Notify>,
pub service: EventChannelService,
}
@ -59,7 +59,7 @@ impl EventChannelService {
.write(true)
.open(EVENT_CHANNEL_DEVICE)
.await?;
let wakes = Arc::new(RwLock::new(HashMap::new()));
let wakes = Arc::new(Mutex::new(HashMap::new()));
let flag = Arc::new(AtomicBool::new(false));
let processor = EventChannelProcessor {
flag: flag.clone(),
@ -77,43 +77,52 @@ impl EventChannelService {
pub async fn bind_virq(&self, virq: u32) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe {
let mut request = BindVirq { virq };
Ok(sys::bind_virq(handle.as_raw_fd(), &mut request)? as u32)
}
let fd = handle.as_raw_fd();
let mut request = BindVirqRequest { virq };
let result =
tokio::task::spawn_blocking(move || unsafe { sys::bind_virq(fd, &mut request) })
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
Ok(result)
}
pub async fn bind_interdomain(&self, domid: u32, port: u32) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe {
let mut request = BindInterdomain {
remote_domain: domid,
remote_port: port,
};
Ok(sys::bind_interdomain(handle.as_raw_fd(), &mut request)? as u32)
}
let fd = handle.as_raw_fd();
let mut request = BindInterdomainRequest {
remote_domain: domid,
remote_port: port,
};
let result =
tokio::task::spawn_blocking(move || unsafe { sys::bind_interdomain(fd, &mut request) })
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
Ok(result)
}
pub async fn bind_unbound_port(&self, domid: u32) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe {
let mut request = BindUnboundPort {
remote_domain: domid,
};
Ok(sys::bind_unbound_port(handle.as_raw_fd(), &mut request)? as u32)
}
let fd = handle.as_raw_fd();
let mut request = BindUnboundPortRequest {
remote_domain: domid,
};
let result = tokio::task::spawn_blocking(move || unsafe {
sys::bind_unbound_port(fd, &mut request)
})
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
Ok(result)
}
pub async fn unmask(&self, port: u32) -> Result<()> {
let handle = self.handle.lock().await;
let mut port = port;
let result = unsafe {
libc::write(
handle.as_raw_fd(),
&mut port as *mut u32 as *mut c_void,
size_of::<u32>(),
)
};
let fd = handle.as_raw_fd();
let result = tokio::task::spawn_blocking(move || unsafe {
libc::write(fd, &mut port as *mut u32 as *mut c_void, size_of::<u32>())
})
.await
.map_err(|_| Error::BlockingTaskJoin)?;
if result != size_of::<u32>() as isize {
return Err(Error::Io(std::io::Error::from_raw_os_error(result as i32)));
}
@ -122,25 +131,32 @@ impl EventChannelService {
pub async fn unbind(&self, port: u32) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe {
let mut request = UnbindPort { port };
let result = sys::unbind(handle.as_raw_fd(), &mut request)? as u32;
self.wakes.write().await.remove(&port);
Ok(result)
}
let mut request = UnbindPortRequest { port };
let fd = handle.as_raw_fd();
let result = tokio::task::spawn_blocking(move || unsafe { sys::unbind(fd, &mut request) })
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
self.wakes.lock().await.remove(&port);
Ok(result)
}
pub async fn notify(&self, port: u32) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe {
let mut request = Notify { port };
Ok(sys::notify(handle.as_raw_fd(), &mut request)? as u32)
}
let mut request = NotifyRequest { port };
let fd = handle.as_raw_fd();
let result = tokio::task::spawn_blocking(move || unsafe { sys::notify(fd, &mut request) })
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
Ok(result)
}
pub async fn reset(&self) -> Result<u32> {
let handle = self.handle.lock().await;
unsafe { Ok(sys::reset(handle.as_raw_fd())? as u32) }
let fd = handle.as_raw_fd();
let result = tokio::task::spawn_blocking(move || unsafe { sys::reset(fd) })
.await
.map_err(|_| Error::BlockingTaskJoin)?? as u32;
Ok(result)
}
pub async fn bind(&self, domid: u32, port: u32) -> Result<BoundEventChannel> {
@ -154,17 +170,15 @@ impl EventChannelService {
Ok(bound)
}
pub async fn subscribe(&self, port: u32) -> Result<Receiver<u32>> {
let mut wakes = self.wakes.write().await;
pub async fn subscribe(&self, port: u32) -> Result<Arc<Notify>> {
let mut wakes = self.wakes.lock().await;
let receiver = match wakes.entry(port) {
Entry::Occupied(_) => {
return Err(Error::PortInUse);
}
Entry::Occupied(entry) => entry.get().clone(),
Entry::Vacant(entry) => {
let (sender, receiver) = channel::<u32>(CHANNEL_QUEUE_LEN);
entry.insert(sender);
receiver
let notify = Arc::new(Notify::new());
entry.insert(notify.clone());
notify
}
};
Ok(receiver)
@ -194,9 +208,16 @@ impl EventChannelProcessor {
pub fn process(&mut self) -> Result<()> {
loop {
let port = self.handle.read_u32::<LittleEndian>()?;
if let Some(wake) = self.wakes.blocking_read().get(&port) {
let _ = wake.try_send(port);
}
let receiver = match self.wakes.blocking_lock().entry(port) {
Entry::Occupied(entry) => entry.get().clone(),
Entry::Vacant(entry) => {
let notify = Arc::new(Notify::new());
entry.insert(notify.clone());
notify
}
};
receiver.notify_one();
}
}
}

View File

@ -32,13 +32,13 @@ impl RawEventChannelService {
pub fn bind_virq(&self, virq: u32) -> Result<u32> {
let handle = self.handle.lock().map_err(|_| Error::LockAcquireFailed)?;
let mut request = sys::BindVirq { virq };
let mut request = sys::BindVirqRequest { virq };
Ok(unsafe { sys::bind_virq(handle.as_raw_fd(), &mut request)? as u32 })
}
pub fn bind_interdomain(&self, domid: u32, port: u32) -> Result<u32> {
let handle = self.handle.lock().map_err(|_| Error::LockAcquireFailed)?;
let mut request = sys::BindInterdomain {
let mut request = sys::BindInterdomainRequest {
remote_domain: domid,
remote_port: port,
};
@ -47,7 +47,7 @@ impl RawEventChannelService {
pub fn bind_unbound_port(&self, domid: u32) -> Result<u32> {
let handle = self.handle.lock().map_err(|_| Error::LockAcquireFailed)?;
let mut request = sys::BindUnboundPort {
let mut request = sys::BindUnboundPortRequest {
remote_domain: domid,
};
Ok(unsafe { sys::bind_unbound_port(handle.as_raw_fd(), &mut request)? as u32 })
@ -55,13 +55,13 @@ impl RawEventChannelService {
pub fn unbind(&self, port: u32) -> Result<u32> {
let handle = self.handle.lock().map_err(|_| Error::LockAcquireFailed)?;
let mut request = sys::UnbindPort { port };
let mut request = sys::UnbindPortRequest { port };
Ok(unsafe { sys::unbind(handle.as_raw_fd(), &mut request)? as u32 })
}
pub fn notify(&self, port: u32) -> Result<u32> {
let handle = self.handle.lock().map_err(|_| Error::LockAcquireFailed)?;
let mut request = sys::Notify { port };
let mut request = sys::NotifyRequest { port };
Ok(unsafe { sys::notify(handle.as_raw_fd(), &mut request)? as u32 })
}

View File

@ -2,34 +2,34 @@ use nix::{ioctl_none, ioctl_readwrite_bad};
use std::ffi::c_uint;
#[repr(C)]
pub struct BindVirq {
pub struct BindVirqRequest {
pub virq: c_uint,
}
#[repr(C)]
pub struct BindInterdomain {
pub struct BindInterdomainRequest {
pub remote_domain: c_uint,
pub remote_port: c_uint,
}
#[repr(C)]
pub struct BindUnboundPort {
pub struct BindUnboundPortRequest {
pub remote_domain: c_uint,
}
#[repr(C)]
pub struct UnbindPort {
pub struct UnbindPortRequest {
pub port: c_uint,
}
#[repr(C)]
pub struct Notify {
pub struct NotifyRequest {
pub port: c_uint,
}
ioctl_readwrite_bad!(bind_virq, 0x44500, BindVirq);
ioctl_readwrite_bad!(bind_interdomain, 0x84501, BindInterdomain);
ioctl_readwrite_bad!(bind_unbound_port, 0x44503, BindUnboundPort);
ioctl_readwrite_bad!(unbind, 0x44502, UnbindPort);
ioctl_readwrite_bad!(notify, 0x44504, Notify);
ioctl_readwrite_bad!(bind_virq, 0x44500, BindVirqRequest);
ioctl_readwrite_bad!(bind_interdomain, 0x84501, BindInterdomainRequest);
ioctl_readwrite_bad!(bind_unbound_port, 0x44503, BindUnboundPortRequest);
ioctl_readwrite_bad!(unbind, 0x44502, UnbindPortRequest);
ioctl_readwrite_bad!(notify, 0x44504, NotifyRequest);
ioctl_none!(reset, 0x4505, 5);

View File

@ -10,13 +10,11 @@ resolver = "2"
[dependencies]
async-trait = { workspace = true }
c2rust-bitfields = { workspace = true }
elf = { workspace = true }
flate2 = { workspace = true }
indexmap = { workspace = true }
libc = { workspace = true }
log = { workspace = true }
krata-xencall = { path = "../xencall", version = "^0.0.23" }
krata-xencall = { path = "../xencall", version = "^0.0.25" }
memchr = { workspace = true }
nix = { workspace = true }
regex = { workspace = true }
@ -27,7 +25,6 @@ uuid = { workspace = true }
xz2 = { workspace = true }
[dev-dependencies]
env_logger = { workspace = true }
tokio = { workspace = true }
[lib]

View File

@ -8,16 +8,9 @@ use crate::{
error::{Error, Result},
mem::PhysicalPages,
sys::XEN_PAGE_SHIFT,
ImageLoader, PlatformKernelConfig, PlatformResourcesConfig,
};
pub struct BootSetup<I: BootImageLoader, P: BootSetupPlatform> {
pub call: XenCall,
pub domid: u32,
pub platform: P,
pub image_loader: I,
pub dtb: Option<Vec<u8>>,
}
#[derive(Debug, Default, Clone)]
pub struct DomainSegment {
pub vstart: u64,
@ -42,7 +35,7 @@ pub struct BootDomain {
pub phys: PhysicalPages,
pub store_evtchn: u32,
pub store_mfn: u64,
pub initrd_segment: DomainSegment,
pub initrd_segment: Option<DomainSegment>,
pub console_evtchn: u32,
pub console_mfn: u64,
pub cmdline: String,
@ -63,7 +56,7 @@ impl BootDomain {
}
let local_page_size: u32 = (1i64 << XEN_PAGE_SHIFT) as u32;
let pages = (size + local_page_size as u64 - 1) / local_page_size as u64;
let pages = size.div_ceil(local_page_size as u64);
let start = self.virt_alloc_end;
let mut segment = DomainSegment {
@ -142,129 +135,8 @@ impl BootDomain {
}
}
impl<I: BootImageLoader, P: BootSetupPlatform> BootSetup<I, P> {
pub fn new(
call: XenCall,
domid: u32,
platform: P,
image_loader: I,
dtb: Option<Vec<u8>>,
) -> BootSetup<I, P> {
BootSetup {
call,
domid,
platform,
image_loader,
dtb,
}
}
pub async fn initialize(
&mut self,
initrd: &[u8],
target_mem_mb: u64,
max_mem_mb: u64,
max_vcpus: u32,
cmdline: &str,
) -> Result<BootDomain> {
let target_pages = target_mem_mb << (20 - self.platform.page_shift());
let total_pages = max_mem_mb << (20 - self.platform.page_shift());
let image_info = self.image_loader.parse(self.platform.hvm()).await?;
let mut domain = BootDomain {
domid: self.domid,
call: self.call.clone(),
virt_alloc_end: 0,
virt_pgtab_end: 0,
pfn_alloc_end: 0,
total_pages,
target_pages,
page_size: self.platform.page_size(),
image_info,
console_evtchn: 0,
console_mfn: 0,
max_vcpus,
phys: PhysicalPages::new(self.call.clone(), self.domid, self.platform.page_shift()),
initrd_segment: DomainSegment::default(),
store_evtchn: 0,
store_mfn: 0,
cmdline: cmdline.to_string(),
};
self.platform.initialize_early(&mut domain).await?;
let mut initrd_segment = if !domain.image_info.unmapped_initrd {
Some(domain.alloc_module(initrd).await?)
} else {
None
};
let mut kernel_segment = if self.platform.needs_early_kernel() {
Some(self.load_kernel_segment(&mut domain).await?)
} else {
None
};
self.platform.initialize_memory(&mut domain).await?;
domain.virt_alloc_end = domain.image_info.virt_base;
if kernel_segment.is_none() {
kernel_segment = Some(self.load_kernel_segment(&mut domain).await?);
}
if domain.image_info.unmapped_initrd {
initrd_segment = Some(domain.alloc_module(initrd).await?);
}
domain.initrd_segment =
initrd_segment.ok_or(Error::MemorySetupFailed("initrd_segment missing"))?;
self.platform.alloc_magic_pages(&mut domain).await?;
domain.store_evtchn = self.call.evtchn_alloc_unbound(self.domid, 0).await?;
let _kernel_segment =
kernel_segment.ok_or(Error::MemorySetupFailed("kernel_segment missing"))?;
Ok(domain)
}
pub async fn boot(&mut self, domain: &mut BootDomain) -> Result<()> {
let domain_info = self.call.get_domain_info(self.domid).await?;
let shared_info_frame = domain_info.shared_info_frame;
self.platform.setup_page_tables(domain).await?;
self.platform
.setup_start_info(domain, shared_info_frame)
.await?;
self.platform.setup_hypercall_page(domain).await?;
self.platform.bootlate(domain).await?;
self.platform
.setup_shared_info(domain, shared_info_frame)
.await?;
self.platform.vcpu(domain).await?;
domain.phys.unmap_all()?;
self.platform.gnttab_seed(domain).await?;
Ok(())
}
async fn load_kernel_segment(&mut self, domain: &mut BootDomain) -> Result<DomainSegment> {
let kernel_segment = domain
.alloc_segment(
domain.image_info.virt_kstart,
domain.image_info.virt_kend - domain.image_info.virt_kstart,
)
.await?;
let kernel_segment_ptr = kernel_segment.addr as *mut u8;
let kernel_segment_slice =
unsafe { slice::from_raw_parts_mut(kernel_segment_ptr, kernel_segment.size as usize) };
self.image_loader
.load(&domain.image_info, kernel_segment_slice)
.await?;
Ok(kernel_segment)
}
}
#[async_trait::async_trait]
pub trait BootSetupPlatform: Clone {
pub trait BootSetupPlatform {
fn create_domain(&self, enable_iommu: bool) -> CreateDomain;
fn page_size(&self) -> u64;
fn page_shift(&self) -> u64;
@ -304,6 +176,135 @@ pub trait BootSetupPlatform: Clone {
async fn vcpu(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn setup_hypercall_page(&mut self, domain: &mut BootDomain) -> Result<()>;
async fn initialize_internal(
&mut self,
domid: u32,
call: XenCall,
image_loader: &ImageLoader,
domain: &mut BootDomain,
kernel: &PlatformKernelConfig,
) -> Result<()> {
self.initialize_early(domain).await?;
let mut initrd_segment = if !domain.image_info.unmapped_initrd && kernel.initrd.is_some() {
Some(domain.alloc_module(kernel.initrd.as_ref().unwrap()).await?)
} else {
None
};
let mut kernel_segment = if self.needs_early_kernel() {
Some(self.load_kernel_segment(image_loader, domain).await?)
} else {
None
};
self.initialize_memory(domain).await?;
domain.virt_alloc_end = domain.image_info.virt_base;
if kernel_segment.is_none() {
kernel_segment = Some(self.load_kernel_segment(image_loader, domain).await?);
}
if domain.image_info.unmapped_initrd && kernel.initrd.is_some() {
initrd_segment = Some(domain.alloc_module(kernel.initrd.as_ref().unwrap()).await?);
}
domain.initrd_segment = initrd_segment;
self.alloc_magic_pages(domain).await?;
domain.store_evtchn = call.evtchn_alloc_unbound(domid, 0).await?;
let _kernel_segment =
kernel_segment.ok_or(Error::MemorySetupFailed("kernel_segment missing"))?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn initialize(
&mut self,
domid: u32,
call: XenCall,
image_loader: &ImageLoader,
kernel: &PlatformKernelConfig,
resources: &PlatformResourcesConfig,
) -> Result<BootDomain> {
let target_pages = resources.assigned_memory_mb << (20 - self.page_shift());
let total_pages = resources.max_memory_mb << (20 - self.page_shift());
let image_info = image_loader.parse(self.hvm()).await?;
let mut domain = BootDomain {
domid,
call: call.clone(),
virt_alloc_end: 0,
virt_pgtab_end: 0,
pfn_alloc_end: 0,
total_pages,
target_pages,
page_size: self.page_size(),
image_info,
console_evtchn: 0,
console_mfn: 0,
max_vcpus: resources.max_vcpus,
phys: PhysicalPages::new(call.clone(), domid, self.page_shift()),
initrd_segment: None,
store_evtchn: 0,
store_mfn: 0,
cmdline: kernel.cmdline.clone(),
};
match self
.initialize_internal(domid, call, image_loader, &mut domain, kernel)
.await
{
Ok(_) => Ok(domain),
Err(error) => {
domain.phys.unmap_all()?;
Err(error)
}
}
}
async fn boot_internal(
&mut self,
call: XenCall,
domid: u32,
domain: &mut BootDomain,
) -> Result<()> {
let domain_info = call.get_domain_info(domid).await?;
let shared_info_frame = domain_info.shared_info_frame;
self.setup_page_tables(domain).await?;
self.setup_start_info(domain, shared_info_frame).await?;
self.setup_hypercall_page(domain).await?;
self.bootlate(domain).await?;
self.setup_shared_info(domain, shared_info_frame).await?;
self.vcpu(domain).await?;
self.gnttab_seed(domain).await?;
domain.phys.unmap_all()?;
Ok(())
}
async fn boot(&mut self, domid: u32, call: XenCall, domain: &mut BootDomain) -> Result<()> {
let result = self.boot_internal(call, domid, domain).await;
domain.phys.unmap_all()?;
result
}
async fn load_kernel_segment(
&mut self,
image_loader: &ImageLoader,
domain: &mut BootDomain,
) -> Result<DomainSegment> {
let kernel_segment = domain
.alloc_segment(
domain.image_info.virt_kstart,
domain.image_info.virt_kend - domain.image_info.virt_kstart,
)
.await?;
let kernel_segment_ptr = kernel_segment.addr as *mut u8;
let kernel_segment_slice =
unsafe { slice::from_raw_parts_mut(kernel_segment_ptr, kernel_segment.size as usize) };
image_loader
.load(&domain.image_info, kernel_segment_slice)
.await?;
Ok(kernel_segment)
}
}
#[async_trait::async_trait]

View File

@ -1,9 +1,10 @@
use std::sync::Arc;
use crate::{
boot::{BootSetup, BootSetupPlatform},
elfloader::ElfImageLoader,
boot::BootDomain, elfloader::ElfImageLoader, error::Error, ImageLoader, RuntimePlatform,
RuntimePlatformType,
};
use log::warn;
use uuid::Uuid;
use xencall::XenCall;
@ -11,42 +12,92 @@ use crate::error::Result;
pub const XEN_EXTRA_MEMORY_KB: u64 = 2048;
pub struct BaseDomainManager<P: BootSetupPlatform> {
pub struct PlatformDomainManager {
call: XenCall,
pub platform: Arc<P>,
}
impl<P: BootSetupPlatform> BaseDomainManager<P> {
pub async fn new(call: XenCall, platform: P) -> Result<BaseDomainManager<P>> {
Ok(BaseDomainManager {
call,
platform: Arc::new(platform),
})
impl PlatformDomainManager {
pub async fn new(call: XenCall) -> Result<PlatformDomainManager> {
Ok(PlatformDomainManager { call })
}
pub async fn create(&self, config: BaseDomainConfig) -> Result<CreatedDomain> {
let mut domain = self.platform.create_domain(config.enable_iommu);
fn max_memory_kb(resources: &PlatformResourcesConfig) -> u64 {
(resources.max_memory_mb * 1024) + XEN_EXTRA_MEMORY_KB
}
async fn create_base_domain(
&self,
config: &PlatformDomainConfig,
platform: &RuntimePlatform,
) -> Result<u32> {
let mut domain = platform.create_domain(config.options.iommu);
domain.handle = config.uuid.into_bytes();
domain.max_vcpus = config.max_vcpus;
domain.max_vcpus = config.resources.max_vcpus;
let domid = self.call.create_domain(domain).await?;
self.call.set_max_vcpus(domid, config.max_vcpus).await?;
Ok(domid)
}
async fn configure_domain_resources(
&self,
domid: u32,
config: &PlatformDomainConfig,
) -> Result<()> {
self.call
.set_max_mem(domid, (config.max_mem_mb * 1024) + XEN_EXTRA_MEMORY_KB)
.set_max_vcpus(domid, config.resources.max_vcpus)
.await?;
let loader = ElfImageLoader::load_file_kernel(&config.kernel)?;
let platform = (*self.platform).clone();
let mut boot = BootSetup::new(self.call.clone(), domid, platform, loader, None);
let mut domain = boot
.initialize(
&config.initrd,
config.target_mem_mb,
config.max_mem_mb,
config.max_vcpus,
&config.cmdline,
self.call
.set_max_mem(
domid,
PlatformDomainManager::max_memory_kb(&config.resources),
)
.await?;
boot.boot(&mut domain).await?;
Ok(CreatedDomain {
Ok(())
}
async fn create_internal(
&self,
domid: u32,
config: &PlatformDomainConfig,
mut platform: RuntimePlatform,
) -> Result<BootDomain> {
self.configure_domain_resources(domid, config).await?;
let kernel = config.kernel.clone();
let loader = tokio::task::spawn_blocking(move || match kernel.format {
KernelFormat::ElfCompressed => ElfImageLoader::load(kernel.data),
KernelFormat::ElfUncompressed => Ok(ElfImageLoader::new(kernel.data)),
})
.await
.map_err(Error::AsyncJoinError)??;
let loader = ImageLoader::Elf(loader);
let mut domain = platform
.initialize(
domid,
self.call.clone(),
&loader,
&config.kernel,
&config.resources,
)
.await?;
platform.boot(domid, self.call.clone(), &mut domain).await?;
Ok(domain)
}
pub async fn create(&self, config: PlatformDomainConfig) -> Result<PlatformDomainInfo> {
let platform = config.platform.create();
let domid = self.create_base_domain(&config, &platform).await?;
let domain = match self.create_internal(domid, &config, platform).await {
Ok(domain) => domain,
Err(error) => {
if let Err(destroy_fail) = self.call.destroy_domain(domid).await {
warn!(
"failed to destroy failed domain {}: {}",
domid, destroy_fail
);
}
return Err(error);
}
};
Ok(PlatformDomainInfo {
domid,
store_evtchn: domain.store_evtchn,
store_mfn: domain.store_mfn,
@ -62,21 +113,43 @@ impl<P: BootSetupPlatform> BaseDomainManager<P> {
}
#[derive(Clone, Debug)]
pub struct BaseDomainConfig {
pub struct PlatformDomainConfig {
pub uuid: Uuid,
pub owner_domid: u32,
pub max_vcpus: u32,
pub target_vcpus: u32,
pub max_mem_mb: u64,
pub target_mem_mb: u64,
pub kernel: Vec<u8>,
pub initrd: Vec<u8>,
pub cmdline: String,
pub enable_iommu: bool,
pub platform: RuntimePlatformType,
pub resources: PlatformResourcesConfig,
pub kernel: PlatformKernelConfig,
pub options: PlatformOptions,
}
#[derive(Clone, Debug)]
pub struct CreatedDomain {
pub struct PlatformKernelConfig {
pub data: Arc<Vec<u8>>,
pub format: KernelFormat,
pub initrd: Option<Arc<Vec<u8>>>,
pub cmdline: String,
}
#[derive(Clone, Debug)]
pub struct PlatformResourcesConfig {
pub max_vcpus: u32,
pub assigned_vcpus: u32,
pub max_memory_mb: u64,
pub assigned_memory_mb: u64,
}
#[derive(Clone, Debug)]
pub struct PlatformOptions {
pub iommu: bool,
}
#[derive(Clone, Debug)]
pub enum KernelFormat {
ElfUncompressed,
ElfCompressed,
}
#[derive(Clone, Debug)]
pub struct PlatformDomainInfo {
pub domid: u32,
pub store_evtchn: u32,
pub store_mfn: u64,

View File

@ -19,6 +19,12 @@ use std::mem::size_of;
use std::sync::Arc;
use xz2::bufread::XzDecoder;
const ELF_MAGIC: &[u8] = &[
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
const GZIP_MAGIC: &[u8] = &[0x1f, 0x8b];
const XZ_MAGIC: &[u8] = &[0xfd, 0x37, 0x7a, 0x58];
#[derive(Clone)]
pub struct ElfImageLoader {
data: Arc<Vec<u8>>,
@ -60,22 +66,40 @@ fn xen_note_value_as_u64(endian: AnyEndian, value: &[u8]) -> Option<u64> {
}
impl ElfImageLoader {
pub fn new(data: Vec<u8>) -> ElfImageLoader {
ElfImageLoader {
data: Arc::new(data),
}
pub fn new(data: Arc<Vec<u8>>) -> ElfImageLoader {
ElfImageLoader { data }
}
pub fn load_gz(data: &[u8]) -> Result<ElfImageLoader> {
let buff = BufReader::new(data);
let image = ElfImageLoader::read_one_stream(&mut GzDecoder::new(buff))?;
Ok(ElfImageLoader::new(image))
Ok(ElfImageLoader::new(Arc::new(image)))
}
pub fn load_xz(data: &[u8]) -> Result<ElfImageLoader> {
let buff = BufReader::new(data);
let image = ElfImageLoader::read_one_stream(&mut XzDecoder::new(buff))?;
Ok(ElfImageLoader::new(image))
Ok(ElfImageLoader::new(Arc::new(image)))
}
pub fn load(data: Arc<Vec<u8>>) -> Result<ElfImageLoader> {
if data.len() >= 16 && find_iter(&data[0..15], ELF_MAGIC).next().is_some() {
return Ok(ElfImageLoader::new(data));
}
for start in find_iter(&data, GZIP_MAGIC) {
if let Ok(elf) = ElfImageLoader::load_gz(&data[start..]) {
return Ok(elf);
}
}
for start in find_iter(&data, XZ_MAGIC) {
if let Ok(elf) = ElfImageLoader::load_xz(&data[start..]) {
return Ok(elf);
}
}
Err(Error::ElfCompressionUnknown)
}
fn read_one_stream(read: &mut dyn Read) -> Result<Vec<u8>> {
@ -101,36 +125,11 @@ impl ElfImageLoader {
Ok(result)
}
pub fn load_file_gz(path: &str) -> Result<ElfImageLoader> {
let file = std::fs::read(path)?;
ElfImageLoader::load_gz(file.as_slice())
}
pub fn load_file_xz(path: &str) -> Result<ElfImageLoader> {
let file = std::fs::read(path)?;
ElfImageLoader::load_xz(file.as_slice())
}
pub fn load_file_kernel(data: &[u8]) -> Result<ElfImageLoader> {
for start in find_iter(data, &[0x1f, 0x8b]) {
if let Ok(elf) = ElfImageLoader::load_gz(&data[start..]) {
return Ok(elf);
}
}
for start in find_iter(data, &[0xfd, 0x37, 0x7a, 0x58]) {
if let Ok(elf) = ElfImageLoader::load_xz(&data[start..]) {
return Ok(elf);
}
}
Err(Error::ElfCompressionUnknown)
}
fn parse_sync(&self, hvm: bool) -> Result<BootImageInfo> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let headers = elf.section_headers().ok_or(Error::ElfInvalidImage)?;
let mut linux_notes: HashMap<u64, Vec<u8>> = HashMap::new();
let headers = elf
.section_headers()
.ok_or(Error::ElfInvalidImage("section headers missing"))?;
let mut xen_notes: HashMap<u64, ElfNoteValue> = HashMap::new();
for header in headers {
@ -140,62 +139,55 @@ impl ElfImageLoader {
let notes = elf.section_data_as_notes(&header)?;
for note in notes {
if let Note::Unknown(note) = note {
if note.name == "Linux" {
linux_notes.insert(note.n_type, note.desc.to_vec());
}
let Note::Unknown(note) = note else {
continue;
};
if note.name == "Xen" {
for typ in XEN_ELFNOTE_TYPES {
if typ.id != note.n_type {
continue;
}
let value = if !typ.is_string {
xen_note_value_as_u64(elf.ehdr.endianness, note.desc).unwrap_or(0)
} else {
0
};
xen_notes.insert(typ.id, ElfNoteValue { value });
if note.name == "Xen" {
for typ in XEN_ELFNOTE_TYPES {
if typ.id != note.n_type {
continue;
}
continue;
let value = if !typ.is_string {
xen_note_value_as_u64(elf.ehdr.endianness, note.desc).unwrap_or(0)
} else {
0
};
xen_notes.insert(typ.id, ElfNoteValue { value });
}
}
}
}
if linux_notes.is_empty() {
return Err(Error::ElfInvalidImage);
}
if xen_notes.is_empty() {
return Err(Error::ElfXenSupportMissing);
}
let paddr_offset = xen_notes
.get(&XEN_ELFNOTE_PADDR_OFFSET)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("PADDR_OFFSET"))?
.value;
let virt_base = xen_notes
.get(&XEN_ELFNOTE_VIRT_BASE)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("VIRT_BASE"))?
.value;
let entry = xen_notes
.get(&XEN_ELFNOTE_ENTRY)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("ENTRY"))?
.value;
let virt_hypercall = xen_notes
.get(&XEN_ELFNOTE_HYPERCALL_PAGE)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("HYPERCALL_PAGE"))?
.value;
let init_p2m = xen_notes
.get(&XEN_ELFNOTE_INIT_P2M)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("INIT_P2M"))?
.value;
let mod_start_pfn = xen_notes
.get(&XEN_ELFNOTE_MOD_START_PFN)
.ok_or(Error::ElfInvalidImage)?
.ok_or(Error::ElfXenNoteMissing("MOD_START_PFN"))?
.value;
let phys32_entry = xen_notes.get(&XEN_ELFNOTE_PHYS32_ENTRY).map(|x| x.value);
@ -203,7 +195,9 @@ impl ElfImageLoader {
let mut start: u64 = u64::MAX;
let mut end: u64 = 0;
let segments = elf.segments().ok_or(Error::ElfInvalidImage)?;
let segments = elf
.segments()
.ok_or(Error::ElfInvalidImage("segments missing"))?;
for header in segments {
if (header.p_type != PT_LOAD) || (header.p_flags & (PF_R | PF_W | PF_X)) == 0 {
@ -221,7 +215,9 @@ impl ElfImageLoader {
}
if paddr_offset != u64::MAX && virt_base == u64::MAX {
return Err(Error::ElfInvalidImage);
return Err(Error::ElfInvalidImage(
"paddr_offset specified, but virt_base is not specified",
));
}
let virt_offset = virt_base - paddr_offset;
@ -247,8 +243,13 @@ impl ElfImageLoader {
};
Ok(image_info)
}
pub fn into_elf_bytes(self) -> Arc<Vec<u8>> {
self.data
}
}
#[derive(Debug)]
struct ElfNoteValue {
value: u64,
}
@ -262,7 +263,9 @@ impl BootImageLoader for ElfImageLoader {
async fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()> {
let elf = ElfBytes::<AnyEndian>::minimal_parse(self.data.as_slice())?;
let segments = elf.segments().ok_or(Error::ElfInvalidImage)?;
let segments = elf
.segments()
.ok_or(Error::ElfInvalidImage("segments missing"))?;
debug!(
"load dst={:#x} segments={}",

View File

@ -28,10 +28,14 @@ pub enum Error {
PopulatePhysmapFailed(usize, usize, usize),
#[error("unknown elf compression method")]
ElfCompressionUnknown,
#[error("expected elf image format not found")]
ElfInvalidImage,
#[error("elf image format invalid: {0}")]
ElfInvalidImage(&'static str),
#[error("elf linux image not found")]
ElfNotLinux,
#[error("provided elf image does not contain xen support")]
ElfXenSupportMissing,
#[error("provided elf image does not contain xen note {0}")]
ElfXenNoteMissing(&'static str),
#[error("regex error: {0}")]
RegexError(#[from] regex::Error),
#[error("error: {0}")]

View File

@ -4,9 +4,111 @@ pub mod error;
pub mod mem;
pub mod sys;
use boot::{BootDomain, BootImageInfo, BootImageLoader, BootSetupPlatform};
use domain::{PlatformKernelConfig, PlatformResourcesConfig};
use elfloader::ElfImageLoader;
use error::Result;
use unsupported::UnsupportedPlatform;
use xencall::{sys::CreateDomain, XenCall};
use crate::error::Error;
pub mod domain;
pub mod unsupported;
#[cfg(target_arch = "x86_64")]
pub mod x86pv;
#[derive(Clone)]
pub enum ImageLoader {
Elf(ElfImageLoader),
}
impl ImageLoader {
async fn parse(&self, hvm: bool) -> Result<BootImageInfo> {
match self {
ImageLoader::Elf(elf) => elf.parse(hvm).await,
}
}
async fn load(&self, image_info: &BootImageInfo, dst: &mut [u8]) -> Result<()> {
match self {
ImageLoader::Elf(elf) => elf.load(image_info, dst).await,
}
}
}
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub enum RuntimePlatformType {
Unsupported,
#[cfg(target_arch = "x86_64")]
Pv,
}
impl RuntimePlatformType {
pub fn create(&self) -> RuntimePlatform {
match self {
RuntimePlatformType::Unsupported => {
RuntimePlatform::Unsupported(UnsupportedPlatform::new())
}
#[cfg(target_arch = "x86_64")]
RuntimePlatformType::Pv => RuntimePlatform::Pv(x86pv::X86PvPlatform::new()),
}
}
pub fn supported() -> RuntimePlatformType {
#[cfg(target_arch = "x86_64")]
return RuntimePlatformType::Pv;
#[cfg(not(target_arch = "x86_64"))]
return RuntimePlatformType::Unsupported;
}
}
#[allow(clippy::large_enum_variant)]
pub enum RuntimePlatform {
Unsupported(UnsupportedPlatform),
#[cfg(target_arch = "x86_64")]
Pv(x86pv::X86PvPlatform),
}
impl RuntimePlatform {
#[allow(clippy::too_many_arguments)]
pub async fn initialize(
&mut self,
domid: u32,
call: XenCall,
image_loader: &ImageLoader,
kernel: &PlatformKernelConfig,
resources: &PlatformResourcesConfig,
) -> Result<BootDomain> {
match self {
RuntimePlatform::Unsupported(unsupported) => {
unsupported
.initialize(domid, call, image_loader, kernel, resources)
.await
}
#[cfg(target_arch = "x86_64")]
RuntimePlatform::Pv(pv) => {
pv.initialize(domid, call, image_loader, kernel, resources)
.await
}
}
}
pub async fn boot(&mut self, domid: u32, call: XenCall, domain: &mut BootDomain) -> Result<()> {
match self {
RuntimePlatform::Unsupported(unsupported) => {
unsupported.boot(domid, call, domain).await
}
#[cfg(target_arch = "x86_64")]
RuntimePlatform::Pv(pv) => pv.boot(domid, call, domain).await,
}
}
pub fn create_domain(&self, enable_iommu: bool) -> CreateDomain {
match self {
RuntimePlatform::Unsupported(unsupported) => unsupported.create_domain(enable_iommu),
#[cfg(target_arch = "x86_64")]
RuntimePlatform::Pv(pv) => pv.create_domain(enable_iommu),
}
}
}

View File

@ -44,19 +44,11 @@ impl BootSetupPlatform for UnsupportedPlatform {
panic!("unsupported platform")
}
async fn alloc_p2m_segment(&mut self, _: &mut BootDomain) -> Result<Option<DomainSegment>> {
panic!("unsupported platform")
}
async fn alloc_page_tables(&mut self, _: &mut BootDomain) -> Result<Option<DomainSegment>> {
panic!("unsupported platform")
}
async fn setup_page_tables(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn setup_hypercall_page(&mut self, _: &mut BootDomain) -> Result<()> {
async fn alloc_p2m_segment(&mut self, _: &mut BootDomain) -> Result<Option<DomainSegment>> {
panic!("unsupported platform")
}
@ -64,6 +56,10 @@ impl BootSetupPlatform for UnsupportedPlatform {
panic!("unsupported platform")
}
async fn setup_page_tables(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn setup_shared_info(&mut self, _: &mut BootDomain, _: u64) -> Result<()> {
panic!("unsupported platform")
}
@ -76,11 +72,15 @@ impl BootSetupPlatform for UnsupportedPlatform {
panic!("unsupported platform")
}
async fn gnttab_seed(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn vcpu(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
async fn gnttab_seed(&mut self, _: &mut BootDomain) -> Result<()> {
async fn setup_hypercall_page(&mut self, _: &mut BootDomain) -> Result<()> {
panic!("unsupported platform")
}
}

View File

@ -9,8 +9,8 @@ use log::{debug, trace};
use nix::errno::Errno;
use slice_copy::copy;
use xencall::sys::{
x8664VcpuGuestContext, CreateDomain, E820Entry, VcpuGuestContextAny, E820_MAX, E820_RAM,
E820_UNUSABLE, MMUEXT_PIN_L4_TABLE, XEN_DOMCTL_CDF_IOMMU,
x8664VcpuGuestContext, CreateDomain, VcpuGuestContextAny, MMUEXT_PIN_L4_TABLE,
XEN_DOMCTL_CDF_IOMMU,
};
use crate::{
@ -282,154 +282,6 @@ impl X86PvPlatform {
self.table.mappings[m] = map;
Ok(m)
}
fn e820_sanitize(
&self,
mut source: Vec<E820Entry>,
map_limit_kb: u64,
balloon_kb: u64,
) -> Result<Vec<E820Entry>> {
let mut e820 = vec![E820Entry::default(); E820_MAX as usize];
for entry in &mut source {
if entry.addr > 0x100000 {
continue;
}
// entries under 1MB should be removed.
entry.typ = 0;
entry.size = 0;
entry.addr = u64::MAX;
}
let mut lowest = u64::MAX;
let mut highest = 0;
for entry in &source {
if entry.typ == E820_RAM || entry.typ == E820_UNUSABLE || entry.typ == 0 {
continue;
}
lowest = if entry.addr < lowest {
entry.addr
} else {
lowest
};
highest = if entry.addr + entry.size > highest {
entry.addr + entry.size
} else {
highest
}
}
let start_kb = if lowest > 1024 { lowest >> 10 } else { 0 };
let mut idx: usize = 0;
e820[idx].addr = 0;
e820[idx].size = map_limit_kb << 10;
e820[idx].typ = E820_RAM;
let mut delta_kb = 0u64;
if start_kb > 0 && map_limit_kb > start_kb {
delta_kb = map_limit_kb - start_kb;
if delta_kb > 0 {
e820[idx].size -= delta_kb << 10;
}
}
let ram_end = source[0].addr + source[0].size;
idx += 1;
for src in &mut source {
let end = src.addr + src.size;
if src.typ == E820_UNUSABLE || end < ram_end {
src.typ = 0;
continue;
}
if src.typ != E820_RAM {
continue;
}
if src.addr >= (1 << 32) {
continue;
}
if src.addr < ram_end {
let delta = ram_end - src.addr;
src.typ = E820_UNUSABLE;
if src.size < delta {
src.typ = 0;
} else {
src.size -= delta;
src.addr = ram_end;
}
if src.addr + src.size != end {
src.typ = 0;
}
}
if end > ram_end {
src.typ = E820_UNUSABLE;
}
}
if lowest > ram_end {
let mut add_unusable = true;
for src in &mut source {
if !add_unusable {
break;
}
if src.typ != E820_UNUSABLE {
continue;
}
if ram_end != src.addr {
continue;
}
if lowest != src.addr + src.size {
src.size = lowest - src.addr;
}
add_unusable = false;
}
if add_unusable {
e820[1].typ = E820_UNUSABLE;
e820[1].addr = ram_end;
e820[1].size = lowest - ram_end;
}
}
for src in &source {
if src.typ == E820_RAM || src.typ == 0 {
continue;
}
e820[idx].typ = src.typ;
e820[idx].addr = src.addr;
e820[idx].size = src.size;
idx += 1;
}
if balloon_kb > 0 || delta_kb > 0 {
e820[idx].typ = E820_RAM;
e820[idx].addr = if (1u64 << 32u64) > highest {
1u64 << 32u64
} else {
highest
};
e820[idx].size = (delta_kb << 10) + (balloon_kb << 10);
}
Ok(e820)
}
}
#[async_trait::async_trait]
@ -576,31 +428,6 @@ impl BootSetupPlatform for X86PvPlatform {
Ok(())
}
async fn alloc_p2m_segment(
&mut self,
domain: &mut BootDomain,
) -> Result<Option<DomainSegment>> {
let mut p2m_alloc_size =
((domain.phys.p2m_size() * 8) + X86_PAGE_SIZE - 1) & !(X86_PAGE_SIZE - 1);
let from = domain.image_info.virt_p2m_base;
let to = from + p2m_alloc_size - 1;
let m = self.count_page_tables(domain, from, to, domain.pfn_alloc_end)?;
let pgtables: usize;
{
let map = &mut self.table.mappings[m];
map.area.pfn = domain.pfn_alloc_end;
for lvl_idx in 0..4 {
map.levels[lvl_idx].pfn += p2m_alloc_size >> X86_PAGE_SHIFT;
}
pgtables = map.area.pgtables;
}
self.table.mappings_count += 1;
p2m_alloc_size += (pgtables << X86_PAGE_SHIFT) as u64;
let p2m_segment = domain.alloc_segment(0, p2m_alloc_size).await?;
Ok(Some(p2m_segment))
}
async fn alloc_page_tables(
&mut self,
domain: &mut BootDomain,
@ -635,6 +462,61 @@ impl BootSetupPlatform for X86PvPlatform {
Ok(Some(segment))
}
async fn alloc_p2m_segment(
&mut self,
domain: &mut BootDomain,
) -> Result<Option<DomainSegment>> {
let mut p2m_alloc_size =
((domain.phys.p2m_size() * 8) + X86_PAGE_SIZE - 1) & !(X86_PAGE_SIZE - 1);
let from = domain.image_info.virt_p2m_base;
let to = from + p2m_alloc_size - 1;
let m = self.count_page_tables(domain, from, to, domain.pfn_alloc_end)?;
let pgtables: usize;
{
let map = &mut self.table.mappings[m];
map.area.pfn = domain.pfn_alloc_end;
for lvl_idx in 0..4 {
map.levels[lvl_idx].pfn += p2m_alloc_size >> X86_PAGE_SHIFT;
}
pgtables = map.area.pgtables;
}
self.table.mappings_count += 1;
p2m_alloc_size += (pgtables << X86_PAGE_SHIFT) as u64;
let p2m_segment = domain.alloc_segment(0, p2m_alloc_size).await?;
Ok(Some(p2m_segment))
}
async fn alloc_magic_pages(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_p2m_base >= domain.image_info.virt_base
|| (domain.image_info.virt_p2m_base & ((1 << self.page_shift()) - 1)) != 0
{
self.p2m_segment = self.alloc_p2m_segment(domain).await?;
}
self.start_info_segment = Some(domain.alloc_page()?);
self.xenstore_segment = Some(domain.alloc_page()?);
domain.store_mfn = domain.phys.p2m[self.xenstore_segment.as_ref().unwrap().pfn as usize];
let evtchn = domain.call.evtchn_alloc_unbound(domain.domid, 0).await?;
let page = domain.alloc_page()?;
domain.console_evtchn = evtchn;
domain.console_mfn = domain.phys.p2m[page.pfn as usize];
self.page_table_segment = self.alloc_page_tables(domain).await?;
self.boot_stack_segment = Some(domain.alloc_page()?);
if domain.virt_pgtab_end > 0 {
domain.alloc_padding_pages(domain.virt_pgtab_end)?;
}
if self.p2m_segment.is_none() {
if let Some(mut p2m_segment) = self.alloc_p2m_segment(domain).await? {
p2m_segment.vstart = domain.image_info.virt_p2m_base;
self.p2m_segment = Some(p2m_segment);
}
}
Ok(())
}
async fn setup_page_tables(&mut self, domain: &mut BootDomain) -> Result<()> {
let p2m_segment = self
.p2m_segment
@ -696,47 +578,6 @@ impl BootSetupPlatform for X86PvPlatform {
Ok(())
}
async fn setup_hypercall_page(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_hypercall == u64::MAX {
return Ok(());
}
let pfn =
(domain.image_info.virt_hypercall - domain.image_info.virt_base) >> self.page_shift();
let mfn = domain.phys.p2m[pfn as usize];
domain.call.hypercall_init(domain.domid, mfn).await?;
Ok(())
}
async fn alloc_magic_pages(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_p2m_base >= domain.image_info.virt_base
|| (domain.image_info.virt_p2m_base & ((1 << self.page_shift()) - 1)) != 0
{
self.p2m_segment = self.alloc_p2m_segment(domain).await?;
}
self.start_info_segment = Some(domain.alloc_page()?);
self.xenstore_segment = Some(domain.alloc_page()?);
domain.store_mfn = domain.phys.p2m[self.xenstore_segment.as_ref().unwrap().pfn as usize];
let evtchn = domain.call.evtchn_alloc_unbound(domain.domid, 0).await?;
let page = domain.alloc_page()?;
domain.console_evtchn = evtchn;
domain.console_mfn = domain.phys.p2m[page.pfn as usize];
self.page_table_segment = self.alloc_page_tables(domain).await?;
self.boot_stack_segment = Some(domain.alloc_page()?);
if domain.virt_pgtab_end > 0 {
domain.alloc_padding_pages(domain.virt_pgtab_end)?;
}
if self.p2m_segment.is_none() {
if let Some(mut p2m_segment) = self.alloc_p2m_segment(domain).await? {
p2m_segment.vstart = domain.image_info.virt_p2m_base;
self.p2m_segment = Some(p2m_segment);
}
}
Ok(())
}
async fn setup_shared_info(
&mut self,
domain: &mut BootDomain,
@ -803,8 +644,10 @@ impl BootSetupPlatform for X86PvPlatform {
(*info).store_mfn = domain.phys.p2m[xenstore_segment.pfn as usize];
(*info).console.mfn = domain.console_mfn;
(*info).console.evtchn = domain.console_evtchn;
(*info).mod_start = domain.initrd_segment.vstart;
(*info).mod_len = domain.initrd_segment.size;
if let Some(ref initrd_segment) = domain.initrd_segment {
(*info).mod_start = initrd_segment.vstart;
(*info).mod_len = initrd_segment.size;
}
for (i, c) in domain.cmdline.chars().enumerate() {
(*info).cmdline[i] = c as c_char;
}
@ -828,12 +671,6 @@ impl BootSetupPlatform for X86PvPlatform {
domain.phys.unmap(pg_pfn)?;
domain.phys.unmap(p2m_segment.pfn)?;
let map = domain.call.get_memory_map(E820_MAX).await?;
let mem_mb = domain.total_pages >> (20 - self.page_shift());
let mem_kb = mem_mb * 1024;
let e820 = self.e820_sanitize(map, mem_kb, 0)?;
domain.call.set_memory_map(domain.domid, e820).await?;
domain
.call
.mmuext(domain.domid, MMUEXT_PIN_L4_TABLE, pg_mfn, 0)
@ -841,6 +678,39 @@ impl BootSetupPlatform for X86PvPlatform {
Ok(())
}
async fn gnttab_seed(&mut self, domain: &mut BootDomain) -> Result<()> {
let xenstore_segment = self
.xenstore_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("xenstore_segment missing"))?;
let console_gfn = domain.console_mfn as usize;
let xenstore_gfn = domain.phys.p2m[xenstore_segment.pfn as usize];
let addr = domain
.call
.mmap(0, 1 << XEN_PAGE_SHIFT)
.await
.ok_or(Error::MmapFailed)?;
domain
.call
.map_resource(domain.domid, 1, 0, 0, 1, addr)
.await?;
let entries = unsafe { slice::from_raw_parts_mut(addr as *mut GrantEntry, 2) };
entries[0].flags = 1 << 0;
entries[0].domid = 0;
entries[0].frame = console_gfn as u32;
entries[1].flags = 1 << 0;
entries[1].domid = 0;
entries[1].frame = xenstore_gfn as u32;
unsafe {
let result = munmap(addr as *mut c_void, 1 << XEN_PAGE_SHIFT);
if result != 0 {
return Err(Error::UnmapFailed(Errno::from_raw(result)));
}
}
Ok(())
}
async fn vcpu(&mut self, domain: &mut BootDomain) -> Result<()> {
let page_table_segment = self
.page_table_segment
@ -885,36 +755,14 @@ impl BootSetupPlatform for X86PvPlatform {
Ok(())
}
async fn gnttab_seed(&mut self, domain: &mut BootDomain) -> Result<()> {
let xenstore_segment = self
.xenstore_segment
.as_ref()
.ok_or(Error::MemorySetupFailed("xenstore_segment missing"))?;
let console_gfn = domain.console_mfn as usize;
let xenstore_gfn = domain.phys.p2m[xenstore_segment.pfn as usize];
let addr = domain
.call
.mmap(0, 1 << XEN_PAGE_SHIFT)
.await
.ok_or(Error::MmapFailed)?;
domain
.call
.map_resource(domain.domid, 1, 0, 0, 1, addr)
.await?;
let entries = unsafe { slice::from_raw_parts_mut(addr as *mut GrantEntry, 2) };
entries[0].flags = 1 << 0;
entries[0].domid = 0;
entries[0].frame = console_gfn as u32;
entries[1].flags = 1 << 0;
entries[1].domid = 0;
entries[1].frame = xenstore_gfn as u32;
unsafe {
let result = munmap(addr as *mut c_void, 1 << XEN_PAGE_SHIFT);
if result != 0 {
return Err(Error::UnmapFailed(Errno::from_raw(result)));
}
async fn setup_hypercall_page(&mut self, domain: &mut BootDomain) -> Result<()> {
if domain.image_info.virt_hypercall == u64::MAX {
return Ok(());
}
let pfn =
(domain.image_info.virt_hypercall - domain.image_info.virt_base) >> self.page_shift();
let mfn = domain.phys.p2m[pfn as usize];
domain.call.hypercall_init(domain.domid, mfn).await?;
Ok(())
}
}

View File

@ -10,11 +10,12 @@ async fn list_recursive(client: &XsdClient, path: &str) -> Result<()> {
let children = client.list(path).await?;
for child in children {
let full = format!("{}/{}", if path == "/" { "" } else { path }, child);
let value = client
.read_string(full.as_str())
.await?
.expect("expected value");
println!("{} = {:?}", full, value,);
let value = client.read(full.as_str()).await?.expect("expected value");
let stringified = match String::from_utf8(value) {
Ok(string) => format!("\"{}\"", string),
Err(error) => format!("{:?}", error.into_bytes()),
};
println!("{} = {}", full, stringified);
pending.push(full);
}
}

View File

@ -48,6 +48,13 @@ impl Error {
_ => false,
}
}
pub fn is_again_response(&self) -> bool {
match self {
Error::ResponseError(message) => message == "EAGAIN",
_ => false,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -55,6 +55,27 @@ impl Drop for XsdWatchHandle {
}
}
pub struct XsdMultiWatchHandle {
pub paths: Vec<String>,
pub id: u32,
unwatch_sender: Sender<(u32, String)>,
pub receiver: Receiver<String>,
}
impl XsdMultiWatchHandle {
pub fn add_path(&mut self, path: impl AsRef<str>) {
self.paths.push(path.as_ref().to_string());
}
}
impl Drop for XsdMultiWatchHandle {
fn drop(&mut self) {
for path in &self.paths {
let _ = self.unwatch_sender.try_send((self.id, path.clone()));
}
}
}
#[allow(async_fn_in_trait)]
pub trait XsdInterface {
async fn list<P: AsRef<str>>(&self, path: P) -> Result<Vec<String>>;
@ -141,7 +162,7 @@ impl XsdClient {
}
return Err(error);
}
result.unwrap().parse_bool()
result?.parse_bool()
}
async fn set_perms<P: AsRef<str>>(
@ -197,6 +218,16 @@ impl XsdClient {
response.parse_bool()
}
pub async fn create_multi_watch(&self) -> Result<XsdMultiWatchHandle> {
let (id, receiver, unwatch_sender) = self.socket.add_watch().await?;
Ok(XsdMultiWatchHandle {
paths: vec![],
id,
receiver,
unwatch_sender,
})
}
pub async fn create_watch<P: AsRef<str>>(&self, path: P) -> Result<XsdWatchHandle> {
let (id, receiver, unwatch_sender) = self.socket.add_watch().await?;
Ok(XsdWatchHandle {
@ -319,6 +350,20 @@ impl XsdTransaction {
.parse_bool()
}
pub async fn maybe_commit(&self) -> Result<bool> {
match self.end(false).await {
Ok(result) => Ok(result),
Err(error) => {
if error.is_again_response() {
Ok(false)
} else {
Err(error)
}
}
}
}
pub async fn commit(&self) -> Result<bool> {
self.end(false).await
}

95
technical-overview.md Normal file
View File

@ -0,0 +1,95 @@
# Edera Technical Overview
## What is Edera?
Edera is a secure-by-default, cloud-native platform built on a reimagined, memory-safe type-1 hypervisor. It unlocks hard multitenancy and strong container isolation—without the performance hit.
Unlike traditional container runtimes that share a single Linux kernel across containers, Edera runs each container in a lightweight virtual machine (called a **zone**), with its own dedicated Linux kernel. This eliminates the kernel as a shared attack surface.
And because Edera doesnt rely on nested virtualization, it runs wherever containers do—across public clouds, on-prem, and edge environments.
## How Edera Works
At its core, Edera uses a [custom hypervisor](https://edera.dev/stories/rust-or-bust-our-rewrite-of-the-xen-control-plane) based on [Xen](https://edera.dev/stories/why-edera-built-on-xen-a-secure-container-foundation), with key components rewritten in Rust for safety, performance, and maintainability. Edera introduces the concept of **zones**—independent, fast-booting virtual machines that serve as security boundaries for container workloads.
Each zone runs its own Linux kernel and minimal init system. The kernel and other system components are delivered via OCI images, keeping things composable, cacheable, and consistent.
Zones are [paravirtualized](https://docs.edera.dev/concepts/paravirtualization/) using the Xen PV protocol. This keeps them lightweight and fast—no hardware virtualization required. But when hardware support is available (e.g., on x86 with VT-x), Edera uses it to get near bare-metal performance.
## How Edera Runs & Secures Containers
Edera allows you to compose your infrastructure the same way you compose workloads: using OCI images.
Each zone consumes a small number of OCI images:
- A **kernel image** that provides the zone kernel.
- One or more **system extension images** that provide init systems, utilities, and kernel modules.
- Optionally, **driver zones**—zones that provide shared services (like networking) to other zones.
Inside each zone, container workloads run via a minimal OCI runtime called [**Styrolite**]((https://github.com/edera-dev/styrolite/)), written in Rust. Unlike traditional setups (like Kata Containers, which layer containerd and runc as external processes), Styrolite is embedded inside the zone itself.
### Key Benefits of This Design
- No external container runtime processes
- Zone init system directly manages containers
- Minimal attack surface, optimized for secure execution
This tightly integrated design avoids the complexity, latency, and exposure introduced by conventional container runtimes. It keeps the execution path short, verifiable, and secure-by-design.
## Zones as Security Boundaries
In Kubernetes, Edera runs pods inside **zones**—isolated virtual machines that eliminate risks like container escape, privilege escalation, and lateral movement.
Each zone boots its own kernel, pulled via OCI, and runs a single pod by default. You can also configure zones to run a replica set, a namespace, or a set of trusted workloads together.
To use Edera, apply the `RuntimeClass`:
```yaml
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: edera
handler: edera
```
Then annotate your pod:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: edera-protect-pod
spec:
runtimeClassName: edera
```
This causes the pod to be scheduled to a node running Ederas hypervisor. The pod is transparently launched inside its own VM zone—no image changes, no config rewrites, and no extra work from developers.
## What Exactly Is an Edera Zone?
An Edera zone is a minimal VM built from OCI-delivered components. At launch time, the Edera daemon unpacks:
### Kernel Image
Located under `/kernel` in the OCI image:
- `image`: the Linux kernel (vmlinuz)
- `metadata`: key-value pairs for boot parameters
- `addons.squashfs`: includes kernel modules in `/modules`
- `config.gz`: the kernel configuration file
### Initramfs Contents
Packaged in a CPIO archive, typically mounted from:
`usr/lib/edera/protect/zone/initrd`
The initramfs includes:
- `/init`: static Rust binary that initializes the zone
- `/bin/styrolite`: embedded container runtime
- `/bin/zone`: control plane for managing containers and services via IDM (inter-domain messaging)
This structure lets Edera launch zones rapidly, with well-defined boundaries and no dependency on the host OS kernel. Everything the workload touches is defined, versioned, and validated.
---
If you want to know more check out our [docs site](https://docs.edera.dev)