Skip to content

Commit da6803b

Browse files
committed
Support multiple hub devices
1 parent fa7a877 commit da6803b

File tree

4 files changed

+31
-24
lines changed

4 files changed

+31
-24
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ However this would still give the container access to all the devices handled by
1515
This program tries to solve that problem by listening to udev events to detect when a device is (un)plugged.
1616
It then interfaces directly with the container's cgroup to grant it access to that specific device.
1717

18-
To limit the devices the container can access, a _root device_ is specified.
19-
The container will receive access to any device descending from the root device.
18+
To limit the devices the container can access, _root devices_ are specified.
19+
The container will receive access to any device descending from one of the root devices.
2020
This is particularly useful if the root device is set to a USB hub.
2121
The hub can be specified directly, or it can be specified as "the parent of device X",
2222
e.g., we can giving a container access to all devices connected to the same hub as an Arduino board.
@@ -31,7 +31,7 @@ This tool wraps `runc` with the additional hotplug feature, therefore it can be
3131
many container managers/orchestrators such as Docker, Podman, and Kubernetes. You need to ensure `runc` is available in your `PATH`
3232
so `container-hotplug` can find it.
3333

34-
It supports two annotations, `org.lowrisc.hotplug.device` and `org.lowrisc.hotplug.symlinks`.
34+
It supports two annotations, `org.lowrisc.hotplug.devices` and `org.lowrisc.hotplug.symlinks`.
3535

3636
For Docker, you can specify an alternative runtime by [changing /etc/docker/daemon.json](https://docs.docker.com/engine/alternative-runtimes/#youki):
3737
```json
@@ -45,12 +45,12 @@ For Docker, you can specify an alternative runtime by [changing /etc/docker/daem
4545
```
4646
and use it with the `--runtime hotplug` flag and appropriate annotation, e.g.
4747
```bash
48-
sudo docker run --runtime hotplug -it --annotation org.lowrisc.hotplug.device=parent-of:usb:2b2e:c310 ubuntu:latest
48+
sudo docker run --runtime hotplug -it --annotation org.lowrisc.hotplug.devices=parent-of:usb:2b2e:c310 ubuntu:latest
4949
```
5050

5151
For podman, you can specify the path directly, by:
5252
```bash
53-
sudo podman run --runtime /path/to/container-hotplug/binary -it --annotation org.lowrisc.hotplug.device=parent-of:usb:2b2e:c310 ubuntu:latest
53+
sudo podman run --runtime /path/to/container-hotplug/binary -it --annotation org.lowrisc.hotplug.devices=parent-of:usb:2b2e:c310 ubuntu:latest
5454
```
5555

5656
For containerd (e.g. when using kubernetes), you can edit `/etc/containerd/config.toml` to add:
@@ -78,7 +78,7 @@ kind: Pod
7878
metadata:
7979
name: ubuntu
8080
annotations:
81-
org.lowrisc.hotplug.device: parent-of:usb:0bda:5634
81+
org.lowrisc.hotplug.devices: parent-of:usb:0bda:5634
8282
spec:
8383
runtimeClassName: hotplug
8484
containers:

src/dev/monitor.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ pub enum DeviceEvent {
2121
}
2222

2323
pub struct DeviceMonitor {
24-
/// Root path for devices to monitor. This is usually a USB hub.
25-
root: PathBuf,
24+
/// Root paths for devices to monitor. This is usually a USB hub.
25+
roots: Vec<PathBuf>,
2626
/// Udev monitor socket.
2727
// Use `Rc` to avoid lifecycle issues in async stream impl.
2828
socket: Rc<AsyncFd<udev::MonitorSocket>>,
@@ -38,15 +38,15 @@ impl DeviceMonitor {
3838
/// Create a new device monitor.
3939
///
4040
/// Devices that are already plugged will each generate an `Add` event immediately.
41-
pub fn new(root: PathBuf) -> Result<Self> {
41+
pub fn new(roots: Vec<PathBuf>) -> Result<Self> {
4242
// Create a socket before enumerating devices to avoid missing events.
4343
let socket = Rc::new(AsyncFd::new(udev::MonitorBuilder::new()?.listen()?)?);
4444

4545
// Process all devices that are already plugged.
4646
let mut enumerator = Enumerator::new()?;
4747
let enumerated = enumerator
4848
.scan_devices()?
49-
.filter(|device| device.syspath().starts_with(&root))
49+
.filter(|device| roots.iter().any(|root| device.syspath().starts_with(root)))
5050
.map(Device::from_udev)
5151
.collect::<VecDeque<_>>();
5252

@@ -56,7 +56,7 @@ impl DeviceMonitor {
5656
}
5757

5858
Ok(Self {
59-
root,
59+
roots,
6060
socket,
6161
seen,
6262
enumerated,
@@ -74,7 +74,12 @@ impl DeviceMonitor {
7474
};
7575

7676
match event.event_type() {
77-
EventType::Add if event.syspath().starts_with(&self.root) => {
77+
EventType::Add
78+
if self
79+
.roots
80+
.iter()
81+
.any(|root| event.syspath().starts_with(root)) =>
82+
{
7883
match self.seen.entry(event.syspath().to_owned()) {
7984
Entry::Occupied(occupied) => {
8085
log::info!("Device already seen: {}", occupied.key().display());

src/hotplug/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ pub struct HotPlug {
2828
impl HotPlug {
2929
pub fn new(
3030
container: Arc<Container>,
31-
hub_path: PathBuf,
31+
hub_path: Vec<PathBuf>,
3232
symlinks: Vec<cli::Symlink>,
3333
) -> Result<Self> {
34-
let monitor = DeviceMonitor::new(hub_path.clone())?;
34+
let monitor = DeviceMonitor::new(hub_path)?;
3535
let devices = Default::default();
3636

3737
let udev_sender = UdevSender::new(crate::util::namespace::NetNamespace::of_pid(

src/main.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,18 @@ async fn create(global: GlobalOptions, create: CreateOptions, notifier: OwnedFd)
5757
let mut notifier = Some(notifier);
5858

5959
let config = runc::config::Config::from_bundle(&create.bundle)?;
60-
let device: DeviceRef = config
60+
let mut devices = Vec::new();
61+
let device_annotation = config
6162
.annotations
62-
.get("org.lowrisc.hotplug.device")
63+
.get("org.lowrisc.hotplug.devices")
64+
.or_else(|| config.annotations.get("org.lowrisc.hotplug.device"))
6365
.context(
64-
"Cannot find annotation `org.lowrisc.hotplug.device`. Please use normal runc instead.",
65-
)?
66-
.parse()?;
66+
"Cannot find annotation `org.lowrisc.hotplug.devices`. Please use normal runc instead.",
67+
)?;
68+
for device in device_annotation.split(',') {
69+
let devref: DeviceRef = device.parse()?;
70+
devices.push(devref.device()?.syspath().to_owned());
71+
}
6772

6873
let mut symlinks = Vec::<Symlink>::new();
6974
if let Some(symlink_annotation) = config.annotations.get("org.lowrisc.hotplug.symlinks") {
@@ -72,9 +77,6 @@ async fn create(global: GlobalOptions, create: CreateOptions, notifier: OwnedFd)
7277
}
7378
}
7479

75-
// Run this before calling into runc to create the container.
76-
let hub_path = device.device()?.syspath().to_owned();
77-
7880
// Switch the logger to syslog. The runc logs are barely forwarded to the user or syslog by
7981
// container managers and orchestrators, while we do want to preserve the hotplug events.
8082
util::log::global_replace(Box::new(util::log::SyslogLogger::new()?));
@@ -107,7 +109,7 @@ async fn create(global: GlobalOptions, create: CreateOptions, notifier: OwnedFd)
107109
rustix::stdio::dup2_stdout(&null)?;
108110
rustix::stdio::dup2_stderr(null)?;
109111

110-
let mut hotplug = HotPlug::new(Arc::clone(&container), hub_path.clone(), symlinks)?;
112+
let mut hotplug = HotPlug::new(Arc::clone(&container), devices.clone(), symlinks)?;
111113
let hotplug_stream = hotplug.run();
112114

113115
let container_stream = {
@@ -133,7 +135,7 @@ async fn create(global: GlobalOptions, create: CreateOptions, notifier: OwnedFd)
133135
let notifier = notifier.take().context("Initialized event seen twice")?;
134136
rustix::io::write(notifier, &[0])?;
135137
}
136-
Event::Detach(dev) if dev.syspath() == hub_path => {
138+
Event::Detach(dev) if devices.iter().any(|hub| dev.syspath() == hub) => {
137139
info!("Hub device detached. Stopping container.");
138140
let _ = container.kill(Signal::KILL).await;
139141
container.wait().await?;

0 commit comments

Comments
 (0)