Skip to content

Commit d4c3cd1

Browse files
Kranzesjiangliu
authored andcommitted
passthrough: forget a readdirplus entry the callback fails
readdirplus() looks each entry up before it calls the reply callback, and released that reference only when the reply buffer was full. When the callback failed, the kernel never saw the entry and never sent a FORGET for it, so the inode stayed in the inode map with its host fd open. Release the reference on an error too. Signed-off-by: Ilan Joselevich <personal@ilanjoselevich.com>
1 parent ca4d019 commit d4c3cd1

1 file changed

Lines changed: 48 additions & 8 deletions

File tree

‎crates/fuse-backend-passthrough/src/sync_io.rs‎

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -902,14 +902,14 @@ impl<S: BitmapSlice + Send + Sync> FileSystem for PassthroughFs<S> {
902902
let ino = entry.inode;
903903
dir_entry.ino = entry.attr.st_ino;
904904

905-
add_entry(dir_entry, entry).inspect(|&r| {
906-
// true when size is not large enough to hold entry.
907-
if r == 0 {
908-
// Release the refcount acquired by self.do_lookup().
909-
let mut inodes = self.inode_map.get_map_mut();
910-
self.forget_one(&mut inodes, ino, 1);
911-
}
912-
})
905+
let res = add_entry(dir_entry, entry);
906+
// Release the refcount acquired by self.do_lookup() if the entry didn't make it into
907+
// the reply, because the buffer was full or the callback failed.
908+
if matches!(res, Ok(0) | Err(_)) {
909+
let mut inodes = self.inode_map.get_map_mut();
910+
self.forget_one(&mut inodes, ino, 1);
911+
}
912+
res
913913
})
914914
}
915915

@@ -3406,6 +3406,46 @@ mod readdir_tests {
34063406
fs.releasedir(&ctx, ROOT_ID, 0, handle).unwrap();
34073407
}
34083408

3409+
// An entry the callback fails is forgotten again too, because the
3410+
// kernel never sees it and so never sends a FORGET for it.
3411+
#[test]
3412+
fn readdirplus_error_forgets_undelivered() {
3413+
let (fs, source) = prepare_fs();
3414+
let ctx = prepare_context();
3415+
create_backing_files(&source, 4);
3416+
3417+
let handle = open_root(&fs, &ctx);
3418+
let mut first_ino = 0;
3419+
fs.readdirplus(&ctx, ROOT_ID, handle, 8192, 0, &mut |_e, entry| {
3420+
first_ino = entry.inode;
3421+
Err(io::Error::from_raw_os_error(libc::EIO))
3422+
})
3423+
.unwrap_err();
3424+
3425+
assert_ne!(first_ino, 0, "an inode must have been observed");
3426+
assert_eq!(refcount(&fs, first_ino), None);
3427+
3428+
// A failure after an entry was delivered returns Ok(()), but the
3429+
// failed entry is still forgotten while the delivered one keeps its
3430+
// reference.
3431+
let mut inos = Vec::new();
3432+
fs.readdirplus(&ctx, ROOT_ID, handle, 8192, 0, &mut |_e, entry| {
3433+
inos.push(entry.inode);
3434+
if inos.len() == 1 {
3435+
Ok(1)
3436+
} else {
3437+
Err(io::Error::from_raw_os_error(libc::EIO))
3438+
}
3439+
})
3440+
.unwrap();
3441+
3442+
assert_eq!(inos.len(), 2);
3443+
assert_eq!(refcount(&fs, inos[0]), Some(1));
3444+
assert_eq!(refcount(&fs, inos[1]), None);
3445+
3446+
fs.releasedir(&ctx, ROOT_ID, 0, handle).unwrap();
3447+
}
3448+
34093449
// Error contract of the reply loop: an error before any entry was
34103450
// stored propagates to the caller; an error after at least one entry
34113451
// was stored returns Ok(()) with the partial delivery, because the

0 commit comments

Comments
 (0)