@@ -3,7 +3,7 @@ mod docker;
3
3
mod hotplug;
4
4
mod util;
5
5
6
- use cli:: { Device , LogFormat , Symlink , Timeout } ;
6
+ use cli:: { Action , Device , Symlink } ;
7
7
use docker:: { Container , Docker } ;
8
8
use hotplug:: { Event as HotPlugEvent , HotPlug , PluggedDevice } ;
9
9
@@ -12,66 +12,12 @@ use std::{fmt::Display, path::Path};
12
12
use tokio_stream:: StreamExt ;
13
13
14
14
use anyhow:: { bail, Context , Result } ;
15
- use clap:: { Parser , Subcommand } ;
15
+ use clap:: Parser ;
16
16
use clap_verbosity_flag:: { InfoLevel , LogLevel , Verbosity } ;
17
17
use log:: info;
18
18
19
19
use crate :: hotplug:: PluggableDevice ;
20
20
21
- #[ derive( Parser ) ]
22
- #[ command( max_term_width = 180 ) ]
23
- struct Args {
24
- #[ command( subcommand) ]
25
- action : Action ,
26
- }
27
-
28
- #[ derive( Subcommand ) ]
29
- enum Action {
30
- /// Wraps a call to `docker run` to allow hot-plugging devices into a
31
- /// container as they are plugged
32
- Run {
33
- #[ arg( short = 'd' , long, id = "DEVICE" ) ]
34
- /// Root hotplug device: [[parent-of:]*]<PREFIX>:<DEVICE> {n}
35
- /// PREFIX can be: {n}
36
- /// - usb: A USB device identified as <VID>[:<PID>[:<SERIAL>]] {n}
37
- /// - syspath: A directory path in /sys/** {n}
38
- /// - devnode: A device path in /dev/** {n}
39
- /// e.g., parent-of:usb:2b3e:c310
40
- root_device : Device ,
41
-
42
- #[ arg( short = 'l' , long, id = "SYMLINK" ) ]
43
- /// Create a symlink for a device: <PREFIX>:<DEVICE>=<PATH> {n}
44
- /// PREFIX can be: {n}
45
- /// - usb: A USB device identified as <VID>:<PID>:<INTERFACE> {n}
46
- /// e.g., usb:2b3e:c310:1=/dev/ttyACM_CW310_0
47
- symlink : Vec < Symlink > ,
48
-
49
- #[ arg( short = 'u' , long, default_value = "5" , id = "CODE" ) ]
50
- /// Exit code to return when the root device is unplugged
51
- root_unplugged_exit_code : u8 ,
52
-
53
- #[ arg( short = 't' , long, default_value = "20s" , id = "TIMEOUT" ) ]
54
- /// Timeout when waiting for the container to be removed
55
- remove_timeout : Timeout ,
56
-
57
- #[ command( flatten) ]
58
- verbosity : Verbosity < InfoLevel > ,
59
-
60
- #[ arg( short = 'L' , long, default_value = "+l-pmt" , id = "FORMAT" ) ]
61
- /// Log mesage format: [[+-][ltmp]*]* {n}
62
- /// +/-: enable/disable {n}
63
- /// l: level {n}
64
- /// t: timestamp {n}
65
- /// m/p: module name/path {n}
66
- ///
67
- log_format : LogFormat ,
68
-
69
- #[ arg( trailing_var_arg = true , id = "ARGS" ) ]
70
- /// Arguments to pass to `docker run`
71
- docker_args : Vec < String > ,
72
- } ,
73
- }
74
-
75
21
#[ derive( Clone ) ]
76
22
enum Event {
77
23
Add ( PluggedDevice ) ,
@@ -149,101 +95,97 @@ fn run_hotplug(
149
95
}
150
96
}
151
97
152
- async fn hotplug_main ( ) -> Result < u8 > {
153
- let args = Args :: parse ( ) ;
98
+ async fn run ( param : cli:: Run , verbosity : Verbosity < InfoLevel > ) -> Result < u8 > {
154
99
let mut status = 0 ;
155
100
156
- match args. action {
157
- Action :: Run {
158
- verbosity,
159
- log_format,
160
- remove_timeout,
161
- root_unplugged_exit_code,
162
- root_device,
163
- symlink,
164
- docker_args,
165
- } => {
166
- let log_env = env_logger:: Env :: default ( )
167
- . filter_or ( "LOG" , "off" )
168
- . write_style_or ( "LOG_STYLE" , "auto" ) ;
169
-
170
- if !Path :: new ( "/sys/fs/cgroup/devices/" ) . is_dir ( ) {
171
- bail ! ( "Could not find cgroup v1" ) ;
172
- }
173
-
174
- env_logger:: Builder :: from_env ( log_env)
175
- . filter_module ( "container_hotplug" , verbosity. log_level_filter ( ) )
176
- . format_timestamp ( if log_format. timestamp {
177
- Some ( Default :: default ( ) )
178
- } else {
179
- None
180
- } )
181
- . format_module_path ( log_format. path )
182
- . format_target ( log_format. module )
183
- . format_level ( log_format. level )
184
- . init ( ) ;
101
+ if !Path :: new ( "/sys/fs/cgroup/devices/" ) . is_dir ( ) {
102
+ bail ! ( "Could not find cgroup v1" ) ;
103
+ }
185
104
186
- let docker = Docker :: connect_with_defaults ( ) ?;
187
- let container = docker. run ( docker_args) . await ?;
188
- let _ = container. pipe_signals ( ) ;
105
+ let docker = Docker :: connect_with_defaults ( ) ?;
106
+ let container = docker. run ( param. docker_args ) . await ?;
107
+ let _ = container. pipe_signals ( ) ;
108
+
109
+ let hub_path = param. root_device . hub ( ) ?. syspath ( ) . to_owned ( ) ;
110
+ let hotplug_stream = run_hotplug (
111
+ param. root_device ,
112
+ param. symlink ,
113
+ container. clone ( ) ,
114
+ verbosity,
115
+ ) ;
116
+ let container_stream = {
117
+ let container = container. clone ( ) ;
118
+ async_stream:: try_stream! {
119
+ let status = container. wait( ) . await ?;
120
+ yield Event :: Stopped ( container. clone( ) , status)
121
+ }
122
+ } ;
189
123
190
- let hub_path = root_device. hub ( ) ?. syspath ( ) . to_owned ( ) ;
191
- let hotplug_stream = run_hotplug ( root_device, symlink, container. clone ( ) , verbosity) ;
192
- let container_stream = {
193
- let container = container. clone ( ) ;
194
- async_stream:: try_stream! {
195
- let status = container. wait( ) . await ?;
196
- yield Event :: Stopped ( container. clone( ) , status)
124
+ let stream = pin ! ( tokio_stream:: empty( )
125
+ . merge( hotplug_stream)
126
+ . merge( container_stream) ) ;
127
+
128
+ let result: Result < ( ) > = async {
129
+ tokio:: pin!( stream) ;
130
+ while let Some ( event) = stream. next ( ) . await {
131
+ let event = event?;
132
+ info ! ( "{}" , event) ;
133
+ match event {
134
+ Event :: Remove ( dev) if dev. syspath ( ) == hub_path => {
135
+ info ! ( "Hub device detached. Stopping container." ) ;
136
+ status = param. root_unplugged_exit_code ;
137
+ container. kill ( 15 ) . await ?;
138
+ break ;
197
139
}
198
- } ;
199
-
200
- let stream = pin ! ( tokio_stream:: empty( )
201
- . merge( hotplug_stream)
202
- . merge( container_stream) ) ;
203
-
204
- let result: Result < ( ) > = async {
205
- tokio:: pin!( stream) ;
206
- while let Some ( event) = stream. next ( ) . await {
207
- let event = event?;
208
- info ! ( "{}" , event) ;
209
- match event {
210
- Event :: Remove ( dev) if dev. syspath ( ) == hub_path => {
211
- info ! ( "Hub device detached. Stopping container." ) ;
212
- status = root_unplugged_exit_code;
213
- container. kill ( 15 ) . await ?;
214
- break ;
140
+ Event :: Stopped ( _, code) => {
141
+ status = 1 ;
142
+ if let Ok ( code) = u8:: try_from ( code) {
143
+ // Use the container exit code, but only if it won't be confused
144
+ // with the pre-defined root_unplugged_exit_code.
145
+ if code != param. root_unplugged_exit_code {
146
+ status = code;
215
147
}
216
- Event :: Stopped ( _, code) => {
217
- status = 1 ;
218
- if let Ok ( code) = u8:: try_from ( code) {
219
- // Use the container exit code, but only if it won't be confused
220
- // with the pre-defined root_unplugged_exit_code.
221
- if code != root_unplugged_exit_code {
222
- status = code;
223
- }
224
- } else {
225
- status = 1 ;
226
- }
227
- break ;
228
- }
229
- _ => { }
148
+ } else {
149
+ status = 1 ;
230
150
}
151
+ break ;
231
152
}
232
- Ok ( ( ) )
153
+ _ => { }
233
154
}
234
- . await ;
235
-
236
- let _ = container. remove ( remove_timeout) . await ;
237
- result?
238
155
}
239
- } ;
156
+ Ok ( ( ) )
157
+ }
158
+ . await ;
240
159
160
+ let _ = container. remove ( param. remove_timeout ) . await ;
161
+ result?;
241
162
Ok ( status)
242
163
}
243
164
244
165
#[ tokio:: main]
245
166
async fn main ( ) {
246
- let code = match hotplug_main ( ) . await {
167
+ let args = cli:: Args :: parse ( ) ;
168
+
169
+ let log_env = env_logger:: Env :: default ( )
170
+ . filter_or ( "LOG" , "off" )
171
+ . write_style_or ( "LOG_STYLE" , "auto" ) ;
172
+
173
+ env_logger:: Builder :: from_env ( log_env)
174
+ . filter_module ( "container_hotplug" , args. verbosity . log_level_filter ( ) )
175
+ . format_timestamp ( if args. log_format . timestamp {
176
+ Some ( Default :: default ( ) )
177
+ } else {
178
+ None
179
+ } )
180
+ . format_module_path ( args. log_format . path )
181
+ . format_target ( args. log_format . module )
182
+ . format_level ( args. log_format . level )
183
+ . init ( ) ;
184
+
185
+ let result = match args. action {
186
+ Action :: Run ( param) => run ( param, args. verbosity ) . await ,
187
+ } ;
188
+ let code = match result {
247
189
Ok ( code) => code,
248
190
Err ( err) => {
249
191
eprintln ! ( "Error: {err:?}" ) ;
0 commit comments