9
9
extern crate log;
10
10
11
11
use std:: fmt:: { Display , Formatter } ;
12
+ use std:: path:: Path ;
12
13
use std:: sync:: { Arc , Mutex } ;
13
14
use std:: thread;
14
15
@@ -45,6 +46,8 @@ pub enum Error {
45
46
CreateBackendListener ( VhostUserError ) ,
46
47
/// Failed creating vhost-user backend handler.
47
48
CreateBackendReqHandler ( VhostUserError ) ,
49
+ /// Failed creating listener socket
50
+ CreateVhostUserListener ( VhostUserError ) ,
48
51
/// Failed starting daemon thread.
49
52
StartDaemon ( std:: io:: Error ) ,
50
53
/// Failed waiting for daemon thread.
@@ -61,6 +64,9 @@ impl Display for Error {
61
64
Error :: CreateBackendReqHandler ( e) => {
62
65
write ! ( f, "cannot create backend req handler: {}" , e)
63
66
}
67
+ Error :: CreateVhostUserListener ( e) => {
68
+ write ! ( f, "cannot create vhost-user listener: {}" , e)
69
+ }
64
70
Error :: StartDaemon ( e) => write ! ( f, "failed to start daemon: {}" , e) ,
65
71
Error :: WaitDaemon ( _e) => write ! ( f, "failed to wait for daemon exit" ) ,
66
72
Error :: HandleRequest ( e) => write ! ( f, "failed to handle request: {}" , e) ,
@@ -145,6 +151,9 @@ where
145
151
///
146
152
/// This runs in an infinite loop that should be terminating once the other end of the socket
147
153
/// (the VMM) disconnects.
154
+ ///
155
+ /// *Note:* A convenience function [VhostUserDaemon::serve] exists that
156
+ /// may be a better option than this for simple use-cases.
148
157
// TODO: the current implementation has limitations that only one incoming connection will be
149
158
// handled from the listener. Should it be enhanced to support reconnection?
150
159
pub fn start ( & mut self , listener : Listener ) -> Result < ( ) > {
@@ -168,6 +177,9 @@ where
168
177
}
169
178
170
179
/// Wait for the thread handling the vhost-user socket connection to terminate.
180
+ ///
181
+ /// *Note:* A convenience function [VhostUserDaemon::serve] exists that
182
+ /// may be a better option than this for simple use-cases.
171
183
pub fn wait ( & mut self ) -> Result < ( ) > {
172
184
if let Some ( handle) = self . main_thread . take ( ) {
173
185
match handle. join ( ) . map_err ( Error :: WaitDaemon ) ? {
@@ -180,6 +192,42 @@ where
180
192
}
181
193
}
182
194
195
+ /// Bind to socket, handle a single connection and shutdown
196
+ ///
197
+ /// This is a convenience function that provides an easy way to handle the
198
+ /// following actions without needing to call the low-level functions:
199
+ /// - Create a listener
200
+ /// - Start listening
201
+ /// - Handle a single event
202
+ /// - Send the exit event to all handler threads
203
+ ///
204
+ /// Internal `Err` results that indicate a device disconnect will be treated
205
+ /// as success and `Ok(())` will be returned in those cases.
206
+ ///
207
+ /// *Note:* See [VhostUserDaemon::start] and [VhostUserDaemon::wait] if you
208
+ /// need more flexibility.
209
+ pub fn serve < P : AsRef < Path > > ( & mut self , socket : P ) -> Result < ( ) > {
210
+ let listener = Listener :: new ( socket, true ) . map_err ( Error :: CreateVhostUserListener ) ?;
211
+
212
+ self . start ( listener) ?;
213
+ let result = self . wait ( ) ;
214
+
215
+ // Regardless of the result, we want to signal worker threads to exit
216
+ self . handler . lock ( ) . unwrap ( ) . send_exit_event ( ) ;
217
+
218
+ // For this convenience function we are not treating certain "expected"
219
+ // outcomes as error. Disconnects and partial messages can be usual
220
+ // behaviour seen from quitting guests.
221
+ match & result {
222
+ Err ( e) => match e {
223
+ Error :: HandleRequest ( VhostUserError :: Disconnected ) => Ok ( ( ) ) ,
224
+ Error :: HandleRequest ( VhostUserError :: PartialMessage ) => Ok ( ( ) ) ,
225
+ _ => result,
226
+ } ,
227
+ _ => result,
228
+ }
229
+ }
230
+
183
231
/// Retrieve the vring epoll handler.
184
232
///
185
233
/// This is necessary to perform further actions like registering and unregistering some extra
@@ -194,8 +242,10 @@ where
194
242
mod tests {
195
243
use super :: backend:: tests:: MockVhostBackend ;
196
244
use super :: * ;
245
+ use libc:: EAGAIN ;
197
246
use std:: os:: unix:: net:: { UnixListener , UnixStream } ;
198
247
use std:: sync:: Barrier ;
248
+ use std:: time:: Duration ;
199
249
use vm_memory:: { GuestAddress , GuestMemoryAtomic , GuestMemoryMmap } ;
200
250
201
251
#[ test]
@@ -264,6 +314,50 @@ mod tests {
264
314
daemon. wait ( ) . unwrap_err ( ) ;
265
315
daemon. wait ( ) . unwrap ( ) ;
266
316
} ) ;
317
+ }
318
+
319
+ #[ test]
320
+ fn test_daemon_serve ( ) {
321
+ let mem = GuestMemoryAtomic :: new (
322
+ GuestMemoryMmap :: < ( ) > :: from_ranges ( & [ ( GuestAddress ( 0x100000 ) , 0x10000 ) ] ) . unwrap ( ) ,
323
+ ) ;
324
+ let backend = Arc :: new ( Mutex :: new ( MockVhostBackend :: new ( ) ) ) ;
325
+ let mut daemon = VhostUserDaemon :: new ( "test" . to_owned ( ) , backend. clone ( ) , mem) . unwrap ( ) ;
326
+ let tmpdir = tempfile:: tempdir ( ) . unwrap ( ) ;
327
+ let socket_path = tmpdir. path ( ) . join ( "socket" ) ;
328
+
329
+ thread:: scope ( |s| {
330
+ s. spawn ( || {
331
+ let _ = daemon. serve ( & socket_path) ;
332
+ } ) ;
333
+
334
+ // We have no way to wait for when the server becomes available...
335
+ // So we will have to spin!
336
+ while !socket_path. exists ( ) {
337
+ thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
338
+ }
339
+
340
+ // Check that no exit events got triggered yet
341
+ for thread_id in 0 ..backend. queues_per_thread ( ) . len ( ) {
342
+ let fd = backend. exit_event ( thread_id) . unwrap ( ) ;
343
+ // Reading from exit fd should fail since nothing was written yet
344
+ assert_eq ! (
345
+ fd. read( ) . unwrap_err( ) . raw_os_error( ) . unwrap( ) ,
346
+ EAGAIN ,
347
+ "exit event should not have been raised yet!"
348
+ ) ;
349
+ }
350
+
351
+ let socket = UnixStream :: connect ( & socket_path) . unwrap ( ) ;
352
+ // disconnect immediately again
353
+ drop ( socket) ;
354
+ } ) ;
267
355
356
+ // Check that exit events got triggered
357
+ let backend = backend. lock ( ) . unwrap ( ) ;
358
+ for thread_id in 0 ..backend. queues_per_thread ( ) . len ( ) {
359
+ let fd = backend. exit_event ( thread_id) . unwrap ( ) ;
360
+ assert ! ( fd. read( ) . is_ok( ) , "No exit event was raised!" ) ;
361
+ }
268
362
}
269
363
}
0 commit comments