@@ -1059,6 +1059,19 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
1059
1059
_ => Ok ( ( ) ) ,
1060
1060
}
1061
1061
}
1062
+
1063
+ // When writeback caching is enabled, the kernel may send read requests even if the userspace
1064
+ // program opened the file write-only. So we need to ensure that we have opened the file for
1065
+ // reading as well as writing.
1066
+ fn get_writeback_open_flags ( & self , flags : i32 ) -> i32 {
1067
+ let mut new_flags = flags;
1068
+ let writeback = self . writeback . load ( Ordering :: Relaxed ) ;
1069
+ if writeback && flags & libc:: O_ACCMODE == libc:: O_WRONLY {
1070
+ new_flags &= !libc:: O_ACCMODE ;
1071
+ new_flags |= libc:: O_RDWR ;
1072
+ }
1073
+ new_flags
1074
+ }
1062
1075
}
1063
1076
1064
1077
#[ cfg( not( feature = "async-io" ) ) ]
@@ -1167,10 +1180,13 @@ fn ebadf() -> io::Error {
1167
1180
#[ cfg( test) ]
1168
1181
mod tests {
1169
1182
use super :: * ;
1183
+ use crate :: abi:: fuse_abi:: CreateIn ;
1170
1184
use crate :: api:: filesystem:: * ;
1171
1185
use crate :: api:: { Vfs , VfsOptions } ;
1172
1186
use caps:: { CapSet , Capability } ;
1173
1187
use log;
1188
+ use std:: fs:: File ;
1189
+ use std:: io:: Read ;
1174
1190
use std:: ops:: Deref ;
1175
1191
use vmm_sys_util:: { tempdir:: TempDir , tempfile:: TempFile } ;
1176
1192
@@ -1336,4 +1352,103 @@ mod tests {
1336
1352
let mode = libc:: S_IFSOCK ;
1337
1353
assert ! ( !is_safe_inode( mode) ) ;
1338
1354
}
1355
+
1356
+ #[ test]
1357
+ fn test_get_writeback_open_flags ( ) {
1358
+ // prepare a fs with writeback cache and open being true, so O_WRONLY should be promoted to
1359
+ // O_RDWR, as writeback may read files even if file being opened with write-only.
1360
+ let mut fs = prepare_passthroughfs ( ) ;
1361
+ fs. writeback = AtomicBool :: new ( true ) ;
1362
+ fs. no_open = AtomicBool :: new ( false ) ;
1363
+
1364
+ assert ! ( fs. writeback. load( Ordering :: Relaxed ) ) ;
1365
+ assert ! ( !fs. no_open. load( Ordering :: Relaxed ) ) ;
1366
+
1367
+ let flags = libc:: O_RDWR ;
1368
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_RDWR ) ;
1369
+
1370
+ let flags = libc:: O_RDONLY ;
1371
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_RDONLY ) ;
1372
+
1373
+ let flags = libc:: O_WRONLY ;
1374
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_RDWR ) ;
1375
+
1376
+ // prepare a fs with writeback cache disabled, open flags should not change
1377
+ let mut fs = prepare_passthroughfs ( ) ;
1378
+ fs. writeback = AtomicBool :: new ( false ) ;
1379
+ fs. no_open = AtomicBool :: new ( false ) ;
1380
+
1381
+ assert ! ( !fs. writeback. load( Ordering :: Relaxed ) ) ;
1382
+ assert ! ( !fs. no_open. load( Ordering :: Relaxed ) ) ;
1383
+
1384
+ let flags = libc:: O_RDWR ;
1385
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_RDWR ) ;
1386
+
1387
+ let flags = libc:: O_RDONLY ;
1388
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_RDONLY ) ;
1389
+
1390
+ let flags = libc:: O_WRONLY ;
1391
+ assert_eq ! ( fs. get_writeback_open_flags( flags) , libc:: O_WRONLY ) ;
1392
+ }
1393
+
1394
+ #[ test]
1395
+ fn test_writeback_open_and_create ( ) {
1396
+ // prepare a fs with writeback cache and open being true, so a write-only opened file
1397
+ // should have read permission as well.
1398
+ let source = TempDir :: new ( ) . expect ( "Cannot create temporary directory." ) ;
1399
+ let _ = std:: process:: Command :: new ( "sh" )
1400
+ . arg ( "-c" )
1401
+ . arg ( format ! ( "touch {}/existfile" , source. as_path( ) . to_str( ) . unwrap( ) ) . as_str ( ) )
1402
+ . output ( )
1403
+ . unwrap ( ) ;
1404
+ let fs_cfg = Config {
1405
+ writeback : true ,
1406
+ do_import : true ,
1407
+ no_open : false ,
1408
+ inode_file_handles : false ,
1409
+ root_dir : source
1410
+ . as_path ( )
1411
+ . to_str ( )
1412
+ . expect ( "source path to string" )
1413
+ . to_string ( ) ,
1414
+ ..Default :: default ( )
1415
+ } ;
1416
+ let mut fs = PassthroughFs :: < ( ) > :: new ( fs_cfg) . unwrap ( ) ;
1417
+ fs. writeback = AtomicBool :: new ( true ) ;
1418
+ fs. no_open = AtomicBool :: new ( false ) ;
1419
+ fs. import ( ) . unwrap ( ) ;
1420
+
1421
+ assert ! ( fs. writeback. load( Ordering :: Relaxed ) ) ;
1422
+ assert ! ( !fs. no_open. load( Ordering :: Relaxed ) ) ;
1423
+
1424
+ let ctx = Context :: default ( ) ;
1425
+
1426
+ // Create a new file with O_WRONLY, and make sure we can read it as well.
1427
+ let fname = CString :: new ( "testfile" ) . unwrap ( ) ;
1428
+ let args = CreateIn {
1429
+ flags : libc:: O_WRONLY as u32 ,
1430
+ mode : 0644 ,
1431
+ umask : 0 ,
1432
+ fuse_flags : 0 ,
1433
+ } ;
1434
+ let ( entry, handle, _) = fs. create ( & ctx, ROOT_ID , & fname, args) . unwrap ( ) ;
1435
+ let handle_data = fs. handle_map . get ( handle. unwrap ( ) , entry. inode ) . unwrap ( ) ;
1436
+ let mut f = unsafe { File :: from_raw_fd ( handle_data. get_handle_raw_fd ( ) ) } ;
1437
+ let mut buf = [ 0 ; 4 ] ;
1438
+ // Buggy code return EBADF on read
1439
+ let n = f. read ( & mut buf) . unwrap ( ) ;
1440
+ assert_eq ! ( n, 0 ) ;
1441
+
1442
+ // Then Open an existing file with O_WRONLY, we should be able to read it as well.
1443
+ let fname = CString :: new ( "existfile" ) . unwrap ( ) ;
1444
+ let entry = fs. lookup ( & ctx, ROOT_ID , & fname) . unwrap ( ) ;
1445
+ let ( handle, _) = fs
1446
+ . open ( & ctx, entry. inode , libc:: O_WRONLY as u32 , 0 )
1447
+ . unwrap ( ) ;
1448
+ let handle_data = fs. handle_map . get ( handle. unwrap ( ) , entry. inode ) . unwrap ( ) ;
1449
+ let mut f = unsafe { File :: from_raw_fd ( handle_data. get_handle_raw_fd ( ) ) } ;
1450
+ let mut buf = [ 0 ; 4 ] ;
1451
+ let n = f. read ( & mut buf) . unwrap ( ) ;
1452
+ assert_eq ! ( n, 0 ) ;
1453
+ }
1339
1454
}
0 commit comments