Skip to content

Commit 1210ae6

Browse files
eryugeyliubogithub
authored andcommitted
passthrough: open file for reading if writeback cache is enabled
When writeback caching is enabled, the kernel may send read requests even if the userspace program opened the file write-only. So we need to ensure that we have opened the file for reading as well as writing. But we only did this when handling OPEN request, CREATE request with O_WRONLY still misses read permission. This results in EBADF error when writing such file from client side. Fix it by introducing get_writeback_open_flags() function, which ensures read permission is granted, and call this helper function in both open and create code path. Signed-off-by: Eryu Guan <[email protected]>
1 parent 240dbd9 commit 1210ae6

File tree

3 files changed

+128
-25
lines changed

3 files changed

+128
-25
lines changed

src/passthrough/async_io.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,29 +148,22 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
148148
inode: Inode,
149149
mut flags: i32,
150150
) -> io::Result<File> {
151-
// When writeback caching is enabled, the kernel may send read requests even if the
152-
// userspace program opened the file write-only. So we need to ensure that we have opened
153-
// the file for reading as well as writing.
154-
let writeback = self.writeback.load(Ordering::Relaxed);
155-
if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
156-
flags &= !libc::O_ACCMODE;
157-
flags |= libc::O_RDWR;
158-
}
151+
let mut new_flags = self.get_writeback_open_flags(flags);
159152
160153
// When writeback caching is enabled the kernel is responsible for handling `O_APPEND`.
161154
// However, this breaks atomicity as the file may have changed on disk, invalidating the
162155
// cached copy of the data in the kernel and the offset that the kernel thinks is the end of
163156
// the file. Just allow this for now as it is the user's responsibility to enable writeback
164157
// caching only for directories that are not shared. It also means that we need to clear the
165158
// `O_APPEND` flag.
166-
if writeback && flags & libc::O_APPEND != 0 {
167-
flags &= !libc::O_APPEND;
159+
if self.writeback.load(Ordering::Relaxed) && flags & libc::O_APPEND != 0 {
160+
new_flags &= !libc::O_APPEND;
168161
}
169162
170163
let data = self.inode_map.get(inode)?;
171164
let file = data.async_get_file(&self.mount_fds).await?;
172165
173-
self.async_open_proc_file(ctx, file.as_raw_fd(), flags, data.mode)
166+
self.async_open_proc_file(ctx, file.as_raw_fd(), new_flags, data.mode)
174167
.await
175168
}
176169
@@ -632,10 +625,11 @@ impl<S: BitmapSlice + Send + Sync> AsyncFileSystem for PassthroughFs<S> {
632625
let new_file = {
633626
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
634627
628+
let flags = self.get_writeback_open_flags(args.flags as i32);
635629
Self::create_file_excl(
636630
dir_file.as_raw_fd(),
637631
name,
638-
args.flags as i32,
632+
flags,
639633
args.mode & !(args.umask & 0o777),
640634
)?
641635
};

src/passthrough/mod.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,19 @@ impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
10591059
_ => Ok(()),
10601060
}
10611061
}
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+
}
10621075
}
10631076

10641077
#[cfg(not(feature = "async-io"))]
@@ -1167,10 +1180,13 @@ fn ebadf() -> io::Error {
11671180
#[cfg(test)]
11681181
mod tests {
11691182
use super::*;
1183+
use crate::abi::fuse_abi::CreateIn;
11701184
use crate::api::filesystem::*;
11711185
use crate::api::{Vfs, VfsOptions};
11721186
use caps::{CapSet, Capability};
11731187
use log;
1188+
use std::fs::File;
1189+
use std::io::Read;
11741190
use std::ops::Deref;
11751191
use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
11761192

@@ -1336,4 +1352,103 @@ mod tests {
13361352
let mode = libc::S_IFSOCK;
13371353
assert!(!is_safe_inode(mode));
13381354
}
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+
}
13391454
}

src/passthrough/sync_io.rs

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,23 @@ use crate::bytes_to_cstr;
2727
use crate::transport::FsCacheReqHandler;
2828

2929
impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
30-
fn open_inode(&self, inode: Inode, mut flags: i32) -> io::Result<File> {
31-
// When writeback caching is enabled, the kernel may send read requests even if the
32-
// userspace program opened the file write-only. So we need to ensure that we have opened
33-
// the file for reading as well as writing.
34-
let writeback = self.writeback.load(Ordering::Relaxed);
35-
if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
36-
flags &= !libc::O_ACCMODE;
37-
flags |= libc::O_RDWR;
38-
}
30+
fn open_inode(&self, inode: Inode, flags: i32) -> io::Result<File> {
31+
let mut new_flags = self.get_writeback_open_flags(flags);
3932

4033
// When writeback caching is enabled the kernel is responsible for handling `O_APPEND`.
4134
// However, this breaks atomicity as the file may have changed on disk, invalidating the
4235
// cached copy of the data in the kernel and the offset that the kernel thinks is the end of
4336
// the file. Just allow this for now as it is the user's responsibility to enable writeback
4437
// caching only for directories that are not shared. It also means that we need to clear the
4538
// `O_APPEND` flag.
46-
if writeback && flags & libc::O_APPEND != 0 {
47-
flags &= !libc::O_APPEND;
39+
if self.writeback.load(Ordering::Relaxed) && flags & libc::O_APPEND != 0 {
40+
new_flags &= !libc::O_APPEND;
4841
}
4942

5043
let data = self.inode_map.get(inode)?;
5144
let file = data.get_file(&self.mount_fds)?;
5245

53-
Self::open_proc_file(&self.proc_self_fd, file.as_raw_fd(), flags, data.mode)
46+
Self::open_proc_file(&self.proc_self_fd, file.as_raw_fd(), new_flags, data.mode)
5447
}
5548

5649
fn do_readdir(
@@ -556,10 +549,11 @@ impl<S: BitmapSlice + Send + Sync> FileSystem for PassthroughFs<S> {
556549
let new_file = {
557550
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
558551

552+
let flags = self.get_writeback_open_flags(args.flags as i32);
559553
Self::create_file_excl(
560554
dir_file.as_raw_fd(),
561555
name,
562-
args.flags as i32,
556+
flags,
563557
args.mode & !(args.umask & 0o777),
564558
)?
565559
};

0 commit comments

Comments
 (0)