virtiofs: Add support for CHROMEOS_TMPFILE
This is a chromeos extension to fuse to support the O_TMPFILE flag.
BUG=b:160932094
TEST=Open a file with O_TMPFILE on a virtio-fs mount
Change-Id: I21a6390e919d5949fbd12bb304b20374b9b9172a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2520561
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Chirantan Ekbote <chirantan@chromium.org>
Reviewed-by: Stephen Barber <smbarber@chromium.org>
diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs
index fe2e261..5f89dc2 100644
--- a/devices/src/virtio/fs/passthrough.rs
+++ b/devices/src/virtio/fs/passthrough.rs
@@ -630,32 +630,8 @@
Err(io::Error::from_raw_os_error(libc::ENOENT))
}
- fn do_lookup(&self, parent: &InodeData, name: &CStr) -> io::Result<Entry> {
- let raw_descriptor = {
- // Safe because this doesn't modify any memory and we check the return value.
- let raw_descriptor = unsafe {
- libc::openat(
- parent.file.as_raw_descriptor(),
- name.as_ptr(),
- libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
- )
- };
-
- if raw_descriptor < 0 && self.cfg.ascii_casefold {
- // Ignore any errors during casefold lookup.
- self.ascii_casefold_lookup(parent, name.to_bytes())
- .unwrap_or(raw_descriptor)
- } else {
- raw_descriptor
- }
- };
- if raw_descriptor < 0 {
- return Err(io::Error::last_os_error());
- }
-
- // Safe because we just opened this descriptor.
- let f = unsafe { File::from_raw_descriptor(raw_descriptor) };
-
+ // Creates a new entry for `f` or increases the refcount of the existing entry for `f`.
+ fn add_entry(&self, f: File) -> io::Result<Entry> {
let st = stat(&f)?;
let altkey = InodeAltKey {
@@ -699,6 +675,35 @@
})
}
+ fn do_lookup(&self, parent: &InodeData, name: &CStr) -> io::Result<Entry> {
+ let fd = {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = unsafe {
+ libc::openat(
+ parent.file.as_raw_descriptor(),
+ name.as_ptr(),
+ libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+ )
+ };
+
+ if fd < 0 && self.cfg.ascii_casefold {
+ // Ignore any errors during casefold lookup.
+ self.ascii_casefold_lookup(parent, name.to_bytes())
+ .unwrap_or(fd)
+ } else {
+ fd
+ }
+ };
+ if fd < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Safe because we just opened this fd.
+ let f = unsafe { File::from_raw_descriptor(fd) };
+
+ self.add_entry(f)
+ }
+
fn do_open(&self, inode: Inode, flags: u32) -> io::Result<(Option<Handle>, OpenOptions)> {
let inode_data = self.find_inode(inode)?;
@@ -729,6 +734,68 @@
Ok((Some(handle), opts))
}
+ fn do_tmpfile(
+ &self,
+ ctx: &Context,
+ dir: &InodeData,
+ flags: u32,
+ mut mode: u32,
+ umask: u32,
+ ) -> io::Result<File> {
+ // We don't want to use `O_EXCL` with `O_TMPFILE` as it has a different meaning when used in
+ // that combination.
+ let mut tmpflags = (flags as i32 | libc::O_TMPFILE | libc::O_CLOEXEC | libc::O_NOFOLLOW)
+ & !(libc::O_EXCL | libc::O_CREAT);
+
+ // O_TMPFILE requires that we use O_RDWR or O_WRONLY.
+ if flags as i32 & libc::O_ACCMODE == libc::O_RDONLY {
+ tmpflags &= !libc::O_ACCMODE;
+ tmpflags |= libc::O_RDWR;
+ }
+
+ // The presence of a default posix acl xattr in the parent directory completely changes the
+ // meaning of the mode parameter so only apply the umask if it doesn't have one.
+ if !self.has_default_posix_acl(&dir)? {
+ mode &= !umask;
+ }
+
+ // Safe because this is a valid c string.
+ let current_dir = unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = unsafe {
+ libc::openat(
+ dir.file.as_raw_descriptor(),
+ current_dir.as_ptr(),
+ tmpflags,
+ mode,
+ )
+ };
+ if fd < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Safe because we just opened this fd.
+ let tmpfile = unsafe { File::from_raw_descriptor(fd) };
+
+ // We need to respect the setgid bit in the parent directory if it is set.
+ let st = stat(&dir.file)?;
+ let gid = if st.st_mode & libc::S_ISGID != 0 {
+ st.st_gid
+ } else {
+ ctx.gid
+ };
+
+ // Now set the uid and gid for the file. Safe because this doesn't modify any memory and we
+ // check the return value.
+ let ret = unsafe { libc::fchown(tmpfile.as_raw_descriptor(), ctx.uid, gid) };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(tmpfile)
+ }
+
fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
let mut handles = self.handles.lock();
@@ -1403,12 +1470,32 @@
self.do_release(inode, handle)
}
+ fn chromeos_tmpfile(
+ &self,
+ ctx: Context,
+ parent: Self::Inode,
+ mode: u32,
+ umask: u32,
+ security_ctx: Option<&CStr>,
+ ) -> io::Result<Entry> {
+ let data = self.find_inode(parent)?;
+
+ let _ctx = security_ctx
+ .filter(|ctx| ctx.to_bytes() != UNLABELED)
+ .map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
+ .transpose()?;
+
+ let tmpfile = self.do_tmpfile(&ctx, &data, 0, mode, umask)?;
+
+ self.add_entry(tmpfile)
+ }
+
fn create(
&self,
ctx: Context,
parent: Inode,
name: &CStr,
- mut mode: u32,
+ mode: u32,
flags: u32,
umask: u32,
security_ctx: Option<&CStr>,
@@ -1430,56 +1517,7 @@
.map(|ctx| ScopedSecurityContext::new(&self.proc, ctx))
.transpose()?;
- // We don't want to use `O_EXCL` with `O_TMPFILE` as it has a different meaning when used in
- // that combination.
- let mut tmpflags = (flags as i32 | libc::O_TMPFILE | libc::O_CLOEXEC | libc::O_NOFOLLOW)
- & !(libc::O_EXCL | libc::O_CREAT);
-
- // O_TMPFILE requires that we use O_RDWR or O_WRONLY.
- if flags as i32 & libc::O_ACCMODE == libc::O_RDONLY {
- tmpflags &= !libc::O_ACCMODE;
- tmpflags |= libc::O_RDWR;
- }
-
- // The presence of a default posix acl xattr in the parent directory completely changes the
- // meaning of the mode parameter so only apply the umask if it doesn't have one.
- if !self.has_default_posix_acl(&data)? {
- mode &= !umask;
- }
-
- // Safe because this is a valid c string.
- let current_dir = unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") };
-
- // Safe because this doesn't modify any memory and we check the return value.
- let raw_descriptor = unsafe {
- libc::openat(
- data.file.as_raw_descriptor(),
- current_dir.as_ptr(),
- tmpflags,
- mode,
- )
- };
- if raw_descriptor < 0 {
- return Err(io::Error::last_os_error());
- }
-
- // Safe because we just opened this descriptor.
- let tmpfile = unsafe { File::from_raw_descriptor(raw_descriptor) };
-
- // We need to respect the setgid bit in the parent directory if it is set.
- let st = stat(&data.file)?;
- let gid = if st.st_mode & libc::S_ISGID != 0 {
- st.st_gid
- } else {
- ctx.gid
- };
-
- // Now set the uid and gid for the file. Safe because this doesn't modify any memory and we
- // check the return value.
- let ret = unsafe { libc::fchown(tmpfile.as_raw_descriptor(), ctx.uid, gid) };
- if ret < 0 {
- return Err(io::Error::last_os_error());
- }
+ let tmpfile = self.do_tmpfile(&ctx, &data, flags, mode, umask)?;
let proc_path = CString::new(format!("self/fd/{}", tmpfile.as_raw_descriptor()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
diff --git a/fuse/src/filesystem.rs b/fuse/src/filesystem.rs
index 0cb0d60..400abb8 100644
--- a/fuse/src/filesystem.rs
+++ b/fuse/src/filesystem.rs
@@ -544,6 +544,18 @@
Err(io::Error::from_raw_os_error(libc::ENOSYS))
}
+ /// Create an unnamed temporary file.
+ fn chromeos_tmpfile(
+ &self,
+ ctx: Context,
+ parent: Self::Inode,
+ mode: u32,
+ umask: u32,
+ security_ctx: Option<&CStr>,
+ ) -> io::Result<Entry> {
+ Err(io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
/// Remove a file.
///
/// If the file's inode lookup count is non-zero, then the file system is expected to delay
diff --git a/fuse/src/server.rs b/fuse/src/server.rs
index 77894bf..e316215 100644
--- a/fuse/src/server.rs
+++ b/fuse/src/server.rs
@@ -114,6 +114,7 @@
Some(Opcode::Rename2) => self.rename2(in_header, r, w),
Some(Opcode::Lseek) => self.lseek(in_header, r, w),
Some(Opcode::CopyFileRange) => self.copy_file_range(in_header, r, w),
+ Some(Opcode::ChromeOsTmpfile) => self.chromeos_tmpfile(in_header, r, w),
Some(Opcode::SetUpMapping) | Some(Opcode::RemoveMapping) | None => reply_error(
io::Error::from_raw_os_error(libc::ENOSYS),
in_header.unique,
@@ -346,6 +347,44 @@
}
}
+ fn chromeos_tmpfile<R: Reader, W: Writer>(
+ &self,
+ in_header: InHeader,
+ mut r: R,
+ w: W,
+ ) -> Result<usize> {
+ let ChromeOsTmpfileIn { mode, umask } =
+ ChromeOsTmpfileIn::from_reader(&mut r).map_err(Error::DecodeMessage)?;
+
+ let buflen = (in_header.len as usize)
+ .checked_sub(size_of::<InHeader>())
+ .and_then(|l| l.checked_sub(size_of::<MkdirIn>()))
+ .ok_or(Error::InvalidHeaderLength)?;
+ let mut buf = vec![0u8; buflen];
+
+ let security_ctx = if buflen > 0 {
+ r.read_exact(&mut buf).map_err(Error::DecodeMessage)?;
+ Some(bytes_to_cstr(&buf)?)
+ } else {
+ None
+ };
+
+ match self.fs.chromeos_tmpfile(
+ Context::from(in_header),
+ in_header.nodeid.into(),
+ mode,
+ umask,
+ security_ctx,
+ ) {
+ Ok(entry) => {
+ let out = EntryOut::from(entry);
+
+ reply_ok(Some(out), None, in_header.unique, w)
+ }
+ Err(e) => reply_error(e, in_header.unique, w),
+ }
+ }
+
fn unlink<R: Reader, W: Writer>(&self, in_header: InHeader, mut r: R, w: W) -> Result<usize> {
let namelen = (in_header.len as usize)
.checked_sub(size_of::<InHeader>())
diff --git a/fuse/src/sys.rs b/fuse/src/sys.rs
index 29a2729..1f4ba56 100644
--- a/fuse/src/sys.rs
+++ b/fuse/src/sys.rs
@@ -588,6 +588,8 @@
CopyFileRange = 47,
SetUpMapping = 48,
RemoveMapping = 49,
+
+ ChromeOsTmpfile = u32::MAX,
}
#[repr(u32)]
@@ -677,6 +679,14 @@
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
+pub struct ChromeOsTmpfileIn {
+ pub mode: u32,
+ pub umask: u32,
+}
+unsafe impl DataInit for ChromeOsTmpfileIn {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct RenameIn {
pub newdir: u64,
}