mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-25 05:03:05 +00:00
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:
parent
3b18157805
commit
67afb23016
4 changed files with 176 additions and 77 deletions
|
@ -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))?;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>())
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue