summary refs log tree commit diff
path: root/devices/src/virtio/fs
diff options
context:
space:
mode:
Diffstat (limited to 'devices/src/virtio/fs')
-rw-r--r--devices/src/virtio/fs/filesystem.rs4
-rw-r--r--devices/src/virtio/fs/fuse.rs1
-rw-r--r--devices/src/virtio/fs/passthrough.rs110
-rw-r--r--devices/src/virtio/fs/server.rs13
4 files changed, 91 insertions, 37 deletions
diff --git a/devices/src/virtio/fs/filesystem.rs b/devices/src/virtio/fs/filesystem.rs
index eb9726c..474a278 100644
--- a/devices/src/virtio/fs/filesystem.rs
+++ b/devices/src/virtio/fs/filesystem.rs
@@ -9,8 +9,6 @@ use std::io;
 use std::mem;
 use std::time::Duration;
 
-use libc;
-
 use crate::virtio::fs::fuse;
 
 pub use fuse::{FsOptions, IoctlFlags, IoctlIovec, OpenOptions, SetattrValid, ROOT_ID};
@@ -77,7 +75,7 @@ pub struct DirEntry<'a> {
 
     /// The name of this directory entry. There are no requirements for the contents of this field
     /// and any sequence of bytes is considered valid.
-    pub name: &'a [u8],
+    pub name: &'a CStr,
 }
 
 /// A reply to a `getxattr` method call.
diff --git a/devices/src/virtio/fs/fuse.rs b/devices/src/virtio/fs/fuse.rs
index 5c531cf..981596b 100644
--- a/devices/src/virtio/fs/fuse.rs
+++ b/devices/src/virtio/fs/fuse.rs
@@ -7,7 +7,6 @@ use std::mem;
 use bitflags::bitflags;
 use data_model::DataInit;
 use enumn::N;
-use libc;
 
 /// Version number of this interface.
 pub const KERNEL_VERSION: u32 = 7;
diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs
index d2034ba..bcc7c5d 100644
--- a/devices/src/virtio/fs/passthrough.rs
+++ b/devices/src/virtio/fs/passthrough.rs
@@ -15,7 +15,6 @@ use std::sync::Arc;
 use std::time::Duration;
 
 use data_model::DataInit;
-use libc;
 use sync::Mutex;
 use sys_util::{error, ioctl_ior_nr, ioctl_iow_nr, ioctl_with_mut_ptr, ioctl_with_ptr};
 
@@ -26,8 +25,6 @@ use crate::virtio::fs::filesystem::{
 use crate::virtio::fs::fuse;
 use crate::virtio::fs::multikey::MultikeyBTreeMap;
 
-const CURRENT_DIR_CSTR: &[u8] = b".\0";
-const PARENT_DIR_CSTR: &[u8] = b"..\0";
 const EMPTY_CSTR: &[u8] = b"\0";
 const ROOT_CSTR: &[u8] = b"/\0";
 const PROC_CSTR: &[u8] = b"/proc\0";
@@ -199,6 +196,20 @@ fn stat(f: &File) -> io::Result<libc::stat64> {
     }
 }
 
+// Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
+// doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &CStr {
+    // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+    let pos = b
+        .iter()
+        .position(|&c| c == 0)
+        .expect("`b` doesn't contain any nul bytes");
+
+    // Safe because we are creating this string with the first nul-byte we found so we can
+    // guarantee that it is nul-terminated and doesn't contain any interior nuls.
+    unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
+}
+
 /// The caching policy that the file system should report to the FUSE client. By default the FUSE
 /// protocol uses close-to-open consistency. This means that any cached contents of the file are
 /// invalidated the next time that file is opened.
@@ -576,19 +587,15 @@ impl PassthroughFs {
             let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
             debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
 
-            let name = &back[..namelen];
-            let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) {
-                // We don't want to report the "." and ".." entries. However, returning `Ok(0)` will
-                // break the loop so return `Ok` with a non-zero value instead.
-                Ok(1)
-            } else {
-                add_entry(DirEntry {
-                    ino: dirent64.d_ino,
-                    offset: dirent64.d_off as u64,
-                    type_: dirent64.d_ty as u32,
-                    name,
-                })
-            };
+            // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
+            // we need to strip those off here.
+            let name = strip_padding(&back[..namelen]);
+            let res = add_entry(DirEntry {
+                ino: dirent64.d_ino,
+                offset: dirent64.d_off as u64,
+                type_: dirent64.d_ty as u32,
+                name,
+            });
 
             debug_assert!(
                 rem.len() >= dirent64.d_reclen as usize,
@@ -806,7 +813,8 @@ impl FileSystem for PassthroughFs {
             }),
         );
 
-        let mut opts = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO;
+        let mut opts =
+            FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO | FsOptions::EXPORT_SUPPORT;
         if self.cfg.writeback && capable.contains(FsOptions::WRITEBACK_CACHE) {
             opts |= FsOptions::WRITEBACK_CACHE;
             self.writeback.store(true, Ordering::Relaxed);
@@ -933,16 +941,38 @@ impl FileSystem for PassthroughFs {
         F: FnMut(DirEntry, Entry) -> io::Result<usize>,
     {
         self.do_readdir(inode, handle, size, offset, |dir_entry| {
-            // Safe because the kernel guarantees that the buffer is nul-terminated. Additionally,
-            // the kernel will pad the name with '\0' bytes up to 8-byte alignment and there's no
-            // way for us to know exactly how many padding bytes there are. This would cause
-            // `CStr::from_bytes_with_nul` to return an error because it would think there are
-            // interior '\0' bytes. We trust the kernel to provide us with properly formatted data
-            // so we'll just skip the checks here.
-            let name = unsafe { CStr::from_bytes_with_nul_unchecked(dir_entry.name) };
-            let entry = self.do_lookup(inode, name)?;
-
-            add_entry(dir_entry, entry)
+            let name = dir_entry.name.to_bytes();
+            let entry = if name == b"." || name == b".." {
+                // Don't do lookups on the current directory or the parent directory. Safe because
+                // this only contains integer fields and any value is valid.
+                let mut attr = unsafe { MaybeUninit::<libc::stat64>::zeroed().assume_init() };
+                attr.st_ino = dir_entry.ino;
+                attr.st_mode = dir_entry.type_;
+
+                // We use 0 for the inode value to indicate a negative entry.
+                Entry {
+                    inode: 0,
+                    generation: 0,
+                    attr,
+                    attr_timeout: Duration::from_secs(0),
+                    entry_timeout: Duration::from_secs(0),
+                }
+            } else {
+                self.do_lookup(inode, dir_entry.name)?
+            };
+
+            let entry_inode = entry.inode;
+            add_entry(dir_entry, entry).map_err(|e| {
+                if entry_inode != 0 {
+                    // Undo the `do_lookup` for this inode since we aren't going to report it to
+                    // the kernel. If `entry_inode` was 0 then that means this was the "." or
+                    // ".." entry and there wasn't a lookup in the first place.
+                    let mut inodes = self.inodes.lock();
+                    forget_one(&mut inodes, entry_inode, 1);
+                }
+
+                e
+            })
         })
     }
 
@@ -1747,3 +1777,29 @@ impl FileSystem for PassthroughFs {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn padded_cstrings() {
+        assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
+        assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
+        assert_eq!(
+            strip_padding(b"normal cstring\0").to_bytes(),
+            b"normal cstring"
+        );
+        assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
+        assert_eq!(
+            strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
+            b"interior"
+        );
+    }
+
+    #[test]
+    #[should_panic(expected = "`b` doesn't contain any nul bytes")]
+    fn no_nul_byte() {
+        strip_padding(b"no nul bytes in string");
+    }
+}
diff --git a/devices/src/virtio/fs/server.rs b/devices/src/virtio/fs/server.rs
index c9025f2..33b7c98 100644
--- a/devices/src/virtio/fs/server.rs
+++ b/devices/src/virtio/fs/server.rs
@@ -8,7 +8,6 @@ use std::io::{self, Read, Write};
 use std::mem::size_of;
 
 use data_model::DataInit;
-use libc;
 use sys_util::error;
 
 use crate::virtio::fs::filesystem::{
@@ -19,7 +18,7 @@ use crate::virtio::fs::fuse::*;
 use crate::virtio::fs::{Error, Result};
 use crate::virtio::{Reader, Writer};
 
-const MAX_BUFFER_SIZE: u32 = (1 << 20);
+const MAX_BUFFER_SIZE: u32 = 1 << 20;
 const DIRENT_PADDING: [u8; 8] = [0; 8];
 
 struct ZCReader<'a>(Reader<'a>);
@@ -1369,12 +1368,14 @@ fn add_dirent(
     d: DirEntry,
     entry: Option<Entry>,
 ) -> io::Result<usize> {
-    if d.name.len() > ::std::u32::MAX as usize {
+    // Strip the trailing '\0'.
+    let name = d.name.to_bytes();
+    if name.len() > ::std::u32::MAX as usize {
         return Err(io::Error::from_raw_os_error(libc::EOVERFLOW));
     }
 
     let dirent_len = size_of::<Dirent>()
-        .checked_add(d.name.len())
+        .checked_add(name.len())
         .ok_or_else(|| io::Error::from_raw_os_error(libc::EOVERFLOW))?;
 
     // Directory entries must be padded to 8-byte alignment.  If adding 7 causes
@@ -1402,12 +1403,12 @@ fn add_dirent(
         let dirent = Dirent {
             ino: d.ino,
             off: d.offset,
-            namelen: d.name.len() as u32,
+            namelen: name.len() as u32,
             type_: d.type_,
         };
 
         cursor.write_all(dirent.as_slice())?;
-        cursor.write_all(d.name)?;
+        cursor.write_all(name)?;
 
         // We know that `dirent_len` <= `padded_dirent_len` due to the check above
         // so there's no need for checked arithmetic.