Skip to content

Commit 664fd88

Browse files
committed
feat(core): add CDI policy resolver
Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 88cf35e commit 664fd88

9 files changed

Lines changed: 1275 additions & 2 deletions

File tree

‎Cargo.lock‎

Lines changed: 267 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ rustix = { version = "1.1", features = ["process"] }
7878
socket2 = "0.6"
7979

8080
# Serialization
81+
container-device-interface = { git = "https://github.com/cncf-tags/container-device-interface-rs", rev = "06644c1b167c8dcc247816590901e0a819636b01" }
8182
serde = { version = "1", features = ["derive"] }
8283
serde_json = "1"
8384
serde_yml = "0.0.12"

‎crates/openshell-core/Cargo.toml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ tempfile = { version = "3", optional = true }
3636
[target.'cfg(unix)'.dependencies]
3737
nix = { workspace = true }
3838

39+
[target.'cfg(target_os = "linux")'.dependencies]
40+
container-device-interface = { workspace = true }
41+
3942
[features]
4043
default = ["telemetry"]
4144
## Compile in anonymous telemetry emission support. On by default; disable with

‎crates/openshell-core/src/cdi.rs‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Shared CDI context schema and resolver helpers.
5+
6+
use std::fmt;
7+
use std::path::{Path, PathBuf};
8+
9+
use serde::{Deserialize, Serialize};
10+
11+
pub const CDI_CONTEXT_VERSION: u32 = 1;
12+
13+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14+
pub struct CdiContext {
15+
pub version: u32,
16+
pub selected_devices: Vec<String>,
17+
pub spec_dirs: Vec<CdiSpecDirectory>,
18+
}
19+
20+
impl CdiContext {
21+
#[must_use]
22+
pub fn new(selected_devices: Vec<String>, spec_dirs: Vec<CdiSpecDirectory>) -> Self {
23+
Self {
24+
version: CDI_CONTEXT_VERSION,
25+
selected_devices,
26+
spec_dirs,
27+
}
28+
}
29+
}
30+
31+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32+
pub struct CdiSpecDirectory {
33+
pub path: String,
34+
pub source: String,
35+
}
36+
37+
impl CdiSpecDirectory {
38+
#[must_use]
39+
pub fn new(path: impl Into<String>, source: impl Into<String>) -> Self {
40+
Self {
41+
path: path.into(),
42+
source: source.into(),
43+
}
44+
}
45+
}
46+
47+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
48+
pub struct CdiDerivedRequirements {
49+
pub device_node_paths: Vec<String>,
50+
pub read_only_mount_paths: Vec<String>,
51+
pub read_write_mount_paths: Vec<String>,
52+
pub additional_gids: Vec<u32>,
53+
}
54+
55+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56+
enum CdiPathKind {
57+
File,
58+
Directory,
59+
CharacterDevice,
60+
BlockDevice,
61+
Other,
62+
}
63+
64+
impl fmt::Display for CdiPathKind {
65+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66+
match self {
67+
Self::File => f.write_str("file"),
68+
Self::Directory => f.write_str("directory"),
69+
Self::CharacterDevice => f.write_str("character device"),
70+
Self::BlockDevice => f.write_str("block device"),
71+
Self::Other => f.write_str("other"),
72+
}
73+
}
74+
}
75+
76+
#[derive(Debug, thiserror::Error)]
77+
pub enum CdiError {
78+
#[error("CDI policy resolution is unavailable on this platform")]
79+
UnsupportedPlatform,
80+
#[error("failed to read CDI context '{}': {source}", path.display())]
81+
ContextRead {
82+
path: PathBuf,
83+
source: std::io::Error,
84+
},
85+
#[error("failed to parse CDI context '{}': {source}", path.display())]
86+
ContextParse {
87+
path: PathBuf,
88+
source: serde_json::Error,
89+
},
90+
#[error("unsupported CDI context version {0}")]
91+
UnsupportedContextVersion(u32),
92+
#[error("CDI spec dir '{path}' from source '{diagnostic_source}' is unsafe: {reason}")]
93+
UnsafeSpecDir {
94+
path: String,
95+
diagnostic_source: String,
96+
reason: &'static str,
97+
},
98+
#[error("selected CDI device '{0}' was not found in mounted CDI specs")]
99+
MissingDevice(String),
100+
#[error(
101+
"selected CDI device '{device}' was not found in mounted CDI specs after CDI spec refresh reported: {refresh_error}"
102+
)]
103+
MissingDeviceAfterRefresh {
104+
device: String,
105+
refresh_error: String,
106+
},
107+
#[error("failed to merge CDI edits for '{device}': {error}")]
108+
EditMerge { device: String, error: String },
109+
#[error("failed to encode resolved CDI edits: {source}")]
110+
EditEncode { source: serde_json::Error },
111+
#[error("failed to decode resolved CDI edits: {source}")]
112+
EditDecode { source: serde_json::Error },
113+
#[error("CDI-derived path '{path}' is unsafe: {reason}")]
114+
UnsafePolicyPath { path: String, reason: &'static str },
115+
#[error("CDI path '{path}' requested conflicting access modes")]
116+
ConflictingAccess { path: String },
117+
#[error(
118+
"CDI writable mount '{path}' is not explicitly listed in the sandbox policy read_write paths"
119+
)]
120+
WritableMountNotAllowed { path: String },
121+
#[error("CDI writable mount '{path}' must target a single file, found {kind}")]
122+
WritableMountNotFile { path: String, kind: String },
123+
#[error("CDI device node '{path}' must target a character or block device, found {kind}")]
124+
DeviceNodeNotDevice { path: String, kind: String },
125+
#[error("CDI mount '{path}' has conflicting ro/rw options")]
126+
ConflictingMountOptions { path: String },
127+
}
128+
129+
pub fn read_context(path: impl AsRef<Path>) -> Result<CdiContext, CdiError> {
130+
let path = path.as_ref();
131+
let json = std::fs::read_to_string(path).map_err(|source| CdiError::ContextRead {
132+
path: path.to_path_buf(),
133+
source,
134+
})?;
135+
serde_json::from_str(&json).map_err(|source| CdiError::ContextParse {
136+
path: path.to_path_buf(),
137+
source,
138+
})
139+
}
140+
141+
#[cfg(target_os = "linux")]
142+
#[path = "cdi_linux.rs"]
143+
mod cdi_linux;
144+
#[cfg(target_os = "linux")]
145+
pub use cdi_linux::resolve_cdi_context;
146+
147+
#[cfg(not(target_os = "linux"))]
148+
#[path = "cdi_stub.rs"]
149+
mod cdi_stub;
150+
#[cfg(not(target_os = "linux"))]
151+
pub use cdi_stub::resolve_cdi_context;

0 commit comments

Comments
 (0)