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>
This commit is contained in:
Chirantan Ekbote 2020-11-05 19:18:10 +09:00 committed by Commit Bot
parent 3b18157805
commit 67afb23016
4 changed files with 176 additions and 77 deletions

View file

@ -630,32 +630,8 @@ impl PassthroughFs {
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 @@ impl PassthroughFs {
})
}
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 @@ impl PassthroughFs {
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 @@ impl FileSystem for PassthroughFs {
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 @@ impl FileSystem for PassthroughFs {
.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))?;

View file

@ -544,6 +544,18 @@ pub trait FileSystem {
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

View file

@ -114,6 +114,7 @@ impl<F: FileSystem + Sync> Server<F> {
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 @@ impl<F: FileSystem + Sync> Server<F> {
}
}
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>())

View file

@ -588,6 +588,8 @@ pub enum Opcode {
CopyFileRange = 47,
SetUpMapping = 48,
RemoveMapping = 49,
ChromeOsTmpfile = u32::MAX,
}
#[repr(u32)]
@ -675,6 +677,14 @@ pub struct MkdirIn {
}
unsafe impl DataInit for MkdirIn {}
#[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 {