in src/nodes/dir.rs [81:175]
fn readdirall(&self, ids: &IdGenerator, cache: &dyn Cache) -> NodeResult<Vec<ReplyEntry>> {
let mut reply = vec!();
let mut state = self.state.lock().unwrap();
reply.push(ReplyEntry {
inode: self.inode,
fs_type: fuse::FileType::Directory,
name: OsString::from(".")
});
reply.push(ReplyEntry {
inode: state.parent,
fs_type: fuse::FileType::Directory,
name: OsString::from("..")
});
// First, return the entries that correspond to explicit mappings performed by the user at
// either mount time or during a reconfiguration. Those should clobber any on-disk
// contents that we discover later when we issue the readdir on the underlying directory,
// if any.
for (name, dirent) in &state.children {
if dirent.explicit_mapping {
reply.push(ReplyEntry {
inode: dirent.node.inode(),
fs_type: dirent.node.file_type_cached(),
name: name.clone()
});
}
}
let mut handle = self.handle.lock().unwrap();
if handle.is_none() {
debug_assert!(state.underlying_path.is_none());
return Ok(reply);
}
debug_assert!(state.underlying_path.is_some());
let handle = handle.as_mut().unwrap();
for entry in handle.iter() {
let entry = entry?;
let name = entry.file_name();
let name = OsStr::from_bytes(name.to_bytes()).to_os_string();
if name == "." || name == ".." {
continue;
}
if let Some(dirent) = state.children.get(&name) {
// Found a previously-known on-disk entry. Must return it "as is" (even if its
// type might have changed) because, if we called into `cache.get_or_create` below,
// we might recreate the node unintentionally. Note that mappings were handled
// earlier, so only handle the non-mapping case here.
if !dirent.explicit_mapping {
reply.push(ReplyEntry {
inode: dirent.node.inode(),
fs_type: dirent.node.file_type_cached(),
name: name.clone(),
});
}
continue;
}
let path = state.underlying_path.as_ref().unwrap().join(&name);
// TODO(jmmv): In theory we shouldn't need to issue a stat for every entry during a
// readdir. However, it's much easier to handle things this way because we currently
// require a file's metadata in order to instantiate a node. Note that the Go variant
// of this code does the same and an attempt to "fix" this resulted in more complex
// code and no visible performance gains. That said, it'd be worth to investigate this
// again.
let fs_attr = fs::symlink_metadata(&path)?;
let fs_type = conv::filetype_fs_to_fuse(&path, fs_attr.file_type());
let child = cache.get_or_create(ids, &path, &fs_attr, self.writable);
reply.push(ReplyEntry { inode: child.inode(), fs_type: fs_type, name: name.clone() });
// Do the insertion into state.children after calling reply.add() to be able to move
// the name into the key without having to copy it again.
let dirent = Dirent {
node: child.clone(),
explicit_mapping: false,
};
// TODO(jmmv): We should remove stale entries at some point (possibly here), but the Go
// variant does not do this so any implications of this are not tested. The reason this
// hasn't caused trouble yet is because: on readdir, we don't use any contents from
// state.children that correspond to unmapped entries, and any stale entries visited
// during lookup will result in an ENOENT.
state.children.insert(name, dirent);
}
// No need to worry about rewinding handle.iter() for future reads on the same OpenDir:
// the rawdir::Dir implementation does this for us.
Ok(reply)
}