diff --git a/Cargo.lock b/Cargo.lock index 03aef4b344..60aec42d66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cipher" version = "0.4.4" @@ -233,15 +239,15 @@ dependencies = [ [[package]] name = "fuser" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8" +checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367" dependencies = [ "libc", "log", "memchr", + "nix", "page_size", - "pkg-config", "smallvec", "zerocopy", ] @@ -368,6 +374,18 @@ version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "numtoa" version = "0.2.4" @@ -396,12 +414,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "portable-atomic" version = "1.11.1" @@ -816,19 +828,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 1329216543..1e10a04b69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,7 +82,7 @@ std = [ ] [target.'cfg(not(target_os = "redox"))'.dependencies] -fuser = { version = "0.14", optional = true } +fuser = { version = "0.16", optional = true } [target.'cfg(target_os = "redox")'.dependencies] libredox = { version = "0.1.11", features = ["call"], optional = true } diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 24f8dced3f..3a1d92c8f4 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -191,15 +191,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "deranged" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" -dependencies = [ - "powerfmt", -] - [[package]] name = "derive_arbitrary" version = "1.3.2" @@ -269,15 +260,15 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fuser" -version = "0.14.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e697f6f62c20b6fad1ba0f84ae909f25971cf16e735273524e3977c94604cf8" +checksum = "0bb29a3ae32279fe3e79a958fe01899f5fb23eadccee919cf88e145b54ed9367" dependencies = [ "libc", "log", "memchr", + "nix", "page_size", - "pkg-config", "smallvec", "zerocopy", ] @@ -303,6 +294,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "inout" version = "0.1.4" @@ -369,14 +369,20 @@ dependencies = [ ] [[package]] -name = "libredox" -version = "0.1.9" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", - "redox_syscall", + "redox_syscall 0.7.0", ] [[package]] @@ -415,12 +421,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "numtoa" version = "0.2.4" @@ -444,10 +444,10 @@ dependencies = [ ] [[package]] -name = "pkg-config" -version = "0.3.30" +name = "parse-size" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "487f2ccd1e17ce8c1bfab3a65c89525af41cfad4c8659021a1e9a2aacd73b89b" [[package]] name = "portable-atomic" @@ -464,12 +464,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "proc-macro2" version = "1.0.95" @@ -502,19 +496,28 @@ checksum = "436d45c2b6a5b159d43da708e62b25be3a4a3d5550d654b72216ade4c4bfd717" [[package]] name = "redox-scheme" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da6a0251965958189cdfd5ebb66f99754db4aa165394300aa2b958525d94b64" +checksum = "2cea2668a5932f878a4298a1d7f8950249bbbb77120fb263da252c589152f5ea" dependencies = [ "libredox", - "redox_syscall", + "redox_syscall 0.6.0", ] [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags", ] @@ -527,26 +530,28 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "redoxfs" -version = "0.7.1" +version = "0.8.4" dependencies = [ "aes", "argon2", "base64ct", + "bitflags", "endian-num", "env_logger", "fuser", "getrandom", + "humansize", "libc", "libredox", "log", "lz4_flex", + "parse-size", "range-tree", "redox-path", "redox-scheme", - "redox_syscall", + "redox_syscall 0.6.0", "seahash", "termion", - "time", "uuid", "xts-mode", ] @@ -679,25 +684,6 @@ dependencies = [ "redox_termios", ] -[[package]] -name = "time" -version = "0.3.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" - [[package]] name = "typenum" version = "1.17.0" @@ -853,19 +839,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 7bb6c9ec30..b93a05b8f6 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -14,7 +14,7 @@ cargo-fuzz = true [dependencies] anyhow = "1.0.86" arbitrary = { version = "1.3.2", features = ["derive"] } -fuser = { version = "0.14" } +fuser = { version = "0.16" } libfuzzer-sys = "0.4" nix = { version = "0.29.0", features = ["fs"] } tempfile = "3.10.1" diff --git a/fuzz/fuzz_targets/fuse_fuzz_target.rs b/fuzz/fuzz_targets/fuse_fuzz_target.rs index d983a6754a..b46cccbfe8 100644 --- a/fuzz/fuzz_targets/fuse_fuzz_target.rs +++ b/fuzz/fuzz_targets/fuse_fuzz_target.rs @@ -99,7 +99,7 @@ enum Operation { /// Parameters for mounting the file system and operations to be performed afterwards. #[derive(Arbitrary, Clone, Debug)] struct MountSequence { - squash: bool, + cleanup: bool, operations: Vec, } @@ -133,13 +133,13 @@ fn create_redoxfs(disk: DiskSparse, reserved_size: u64) -> bool { } /// Mounts an existing Redoxfs, runs the callback and performs the unmount. -fn with_redoxfs_mount(temp_path: &Path, disk: DiskSparse, squash: bool, callback: F) +fn with_redoxfs_mount(temp_path: &Path, disk: DiskSparse, cleanup: bool, callback: F) where F: FnOnce(&Path) + Send + 'static, { let password = None; let block = None; - let mut fs = FileSystem::open(disk, password, block, squash).unwrap(); + let mut fs = FileSystem::open(disk, password, block, cleanup).unwrap(); let mount_path = temp_path.join("mount"); fs::create_dir_all(&mount_path).unwrap(); @@ -319,7 +319,7 @@ fuzz_target!(|test_case: TestCase| -> Corpus { let disk = create_disk(temp_dir.path(), test_case.disk_size); let operations = mount_seq.operations.clone(); - with_redoxfs_mount(temp_dir.path(), disk, mount_seq.squash, move |fs_path| { + with_redoxfs_mount(temp_dir.path(), disk, mount_seq.cleanup, move |fs_path| { for operation in operations.iter() { #[cfg(feature = "log")] eprintln!("do operation {operation:?}"); diff --git a/src/filesystem.rs b/src/filesystem.rs index 0f6f531970..5baf6ea5be 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,5 +1,9 @@ use aes::Aes128; -use alloc::{boxed::Box, collections::VecDeque, vec}; +use alloc::{ + boxed::Box, + collections::{BTreeMap, VecDeque}, + vec, +}; use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY}; use xts_mode::{get_tweak_default, Xts128}; @@ -25,6 +29,7 @@ pub struct FileSystem { pub(crate) allocator: Allocator, pub(crate) cipher_opt: Option>, pub(crate) compress_cache: Box<[u8]>, + pub node_usages: BTreeMap, } impl FileSystem { @@ -33,7 +38,7 @@ impl FileSystem { mut disk: D, password_opt: Option<&[u8]>, block_opt: Option, - squash: bool, + cleanup: bool, ) -> Result { for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) { let mut header = Header::default(); @@ -90,12 +95,14 @@ impl FileSystem { allocator: Allocator::default(), cipher_opt, compress_cache: compress_cache(), + node_usages: BTreeMap::new(), }; unsafe { fs.reset_allocator()? }; - // Squash allocations and sync - Transaction::new(&mut fs).commit(squash)?; + if cleanup { + fs.cleanup()? + } return Ok(fs); } @@ -171,6 +178,7 @@ impl FileSystem { allocator: Allocator::default(), cipher_opt, compress_cache: compress_cache(), + node_usages: BTreeMap::new(), }; // Write header generation zero @@ -219,12 +227,18 @@ impl FileSystem { Ok(()) })?; - // Make sure everything is synced and squash allocations - Transaction::new(&mut fs).commit(true)?; + fs.cleanup()?; Ok(fs) } + /// Release unused nodes and squash allocation log, happens on mount (with cleanup) and unmount + pub fn cleanup(&mut self) -> Result<()> { + let mut tx = Transaction::new(self); + tx.release_unused_nodes()?; + tx.commit(true) + } + /// start a filesystem transaction, required for making any changes pub fn tx) -> Result, T>(&mut self, f: F) -> Result { let mut tx = Transaction::new(self); diff --git a/src/mount/fuse.rs b/src/mount/fuse.rs index 5d07de3768..415cc42dbe 100644 --- a/src/mount/fuse.rs +++ b/src/mount/fuse.rs @@ -12,11 +12,11 @@ use self::fuser::TimeOrNow; use crate::mount::fuse::TimeOrNow::Now; use crate::mount::fuse::TimeOrNow::SpecificTime; -use crate::{filesystem, Disk, Node, Transaction, TreeData, TreePtr, BLOCK_SIZE}; +use crate::{filesystem, Disk, Node, TreeData, TreePtr, BLOCK_SIZE}; use self::fuser::{ FileAttr, FileType, Filesystem, ReplyAttr, ReplyCreate, ReplyData, ReplyDirectory, ReplyEmpty, - ReplyEntry, ReplyStatfs, ReplyWrite, Request, Session, + ReplyEntry, ReplyOpen, ReplyStatfs, ReplyWrite, Request, Session, }; use std::time::Duration; @@ -62,8 +62,8 @@ where res }; - // Squash allocations and sync on unmount - let _ = Transaction::new(&mut filesystem).commit(true); + // Cleanup on unmount + filesystem.cleanup()?; Ok(res) } @@ -115,7 +115,7 @@ impl Filesystem for Fuse<'_, D> { } } - fn getattr(&mut self, _req: &Request, node_id: u64, reply: ReplyAttr) { + fn getattr(&mut self, _req: &Request, node_id: u64, _fh: Option, reply: ReplyAttr) { let node_ptr = TreePtr::::new(node_id as u32); match self.fs.tx(|tx| tx.read_tree(node_ptr)) { Ok(node) => { @@ -225,6 +225,14 @@ impl Filesystem for Fuse<'_, D> { reply.attr(&TTL, &attr); } + fn open(&mut self, _req: &Request<'_>, node_id: u64, _flags: i32, reply: ReplyOpen) { + let node_ptr = TreePtr::::new(node_id as u32); + match self.fs.tx(|tx| tx.on_open_node(node_ptr)) { + Ok(()) => reply.opened(0, 0), + Err(err) => reply.error(err.errno), + } + } + fn read( &mut self, _req: &Request, @@ -295,6 +303,23 @@ impl Filesystem for Fuse<'_, D> { reply.ok(); } + fn release( + &mut self, + _req: &Request, + node_id: u64, + _fh: u64, + _flags: i32, + _lock_owner: Option, + _flush: bool, + reply: ReplyEmpty, + ) { + let node_ptr = TreePtr::new(node_id as u32); + match self.fs.tx(|tx| tx.on_close_node(node_ptr)) { + Ok(()) => reply.ok(), + Err(err) => reply.error(err.errno), + } + } + fn fsync(&mut self, _req: &Request, _ino: u64, _fh: u64, _datasync: bool, reply: ReplyEmpty) { reply.ok(); } @@ -380,13 +405,15 @@ impl Filesystem for Fuse<'_, D> { let parent_ptr = TreePtr::::new(parent_id as u32); let ctime = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); match self.fs.tx(|tx| { - tx.create_node( + let node = tx.create_node( parent_ptr, name.to_str().unwrap(), Node::MODE_FILE | (mode as u16 & Node::MODE_PERM), ctime.as_secs(), ctime.subsec_nanos(), - ) + )?; + tx.on_open_node(node.ptr())?; + Ok(node) }) { Ok(node) => { // println!("Create {:?}:{:o}:{:o}", node.1.name(), node.1.mode, mode); diff --git a/src/mount/redox/mod.rs b/src/mount/redox/mod.rs index a559957435..a397ff2f60 100644 --- a/src/mount/redox/mod.rs +++ b/src/mount/redox/mod.rs @@ -3,7 +3,7 @@ use std::io; use std::path::Path; use std::sync::atomic::Ordering; -use crate::{Disk, FileSystem, Transaction, IS_UMT}; +use crate::{Disk, FileSystem, IS_UMT}; use self::scheme::FileScheme; @@ -42,6 +42,10 @@ where } continue; } + RequestKind::OnClose { id } => { + scheme.on_close(id); + continue; + } _ => { // TODO: Redoxfs does not yet support asynchronous file IO. It might still make // sense to implement cancellation for huge buffers, e.g. dd bs=1G @@ -57,8 +61,8 @@ where } } - // Squash allocations and sync on unmount - let _ = Transaction::new(&mut scheme.fs).commit(true); + // Cleanup on unmount + fs.cleanup()?; Ok(res) } diff --git a/src/mount/redox/resource.rs b/src/mount/redox/resource.rs index 69dc5194ca..295421feed 100644 --- a/src/mount/redox/resource.rs +++ b/src/mount/redox/resource.rs @@ -359,6 +359,7 @@ pub struct FileResource { flags: usize, uid: u32, } + #[derive(Debug)] pub struct FileMmapInfo { base: *mut u8, @@ -366,13 +367,9 @@ pub struct FileMmapInfo { pub ranges: RangeTree, pub open_fds: usize, } + impl FileMmapInfo { - pub fn in_use(&self) -> bool { - self.open_fds > 0 || !self.ranges.is_empty() - } -} -impl Default for FileMmapInfo { - fn default() -> Self { + pub fn new() -> Self { Self { base: core::ptr::null_mut(), size: 0, @@ -380,6 +377,18 @@ impl Default for FileMmapInfo { open_fds: 0, } } + + pub fn in_use(&self) -> bool { + self.open_fds > 0 || !self.ranges.is_empty() + } +} + +impl Drop for FileMmapInfo { + fn drop(&mut self) { + if self.in_use() { + log::error!("FileMmapInfo dropped while in use"); + } + } } impl FileResource { @@ -588,7 +597,14 @@ impl Resource for FileResource { } //dbg!(&self.fmaps); - //TODO: allow release of node if not in use anymore + // Allow release of node if not in use anymore + if !fmap_info.in_use() { + // Notify filesystem of close + tx.on_close_node(self.node_ptr)?; + + // Remove from fmaps list + fmaps.remove(&self.node_ptr.id()); + } Ok(()) } diff --git a/src/mount/redox/scheme.rs b/src/mount/redox/scheme.rs index 58f73f8745..b258b2c3e4 100644 --- a/src/mount/redox/scheme.rs +++ b/src/mount/redox/scheme.rs @@ -26,7 +26,7 @@ use redox_path::{ use crate::{Disk, FileSystem, Node, Transaction, TreeData, TreePtr, BLOCK_SIZE}; -use super::resource::{DirResource, Entry, FileResource, Resource}; +use super::resource::{DirResource, Entry, FileMmapInfo, FileResource, Resource}; pub struct FileScheme<'sock, D: Disk> { scheme_name: String, @@ -333,9 +333,15 @@ impl<'sock, D: Disk> FileScheme<'sock, D> { } } }; + + let node_ptr = resource.node_ptr(); self.fmap - .entry(resource.node_ptr().id()) - .or_insert_with(Default::default) + .entry(node_ptr.id()) + .or_insert_with(|| { + // Notify filesystem of open + self.fs.tx(|tx| tx.on_open_node(node_ptr)).unwrap(); + FileMmapInfo::new() + }) .open_fds += 1; let id = self.next_id.fetch_add(1, Ordering::Relaxed); @@ -487,13 +493,7 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { Node::MODE_FILE }; - let in_use = if let Some(file_info) = self.fmap.get(&child.id()) { - file_info.in_use() - } else { - false - }; - - tx.remove_node_in_use(parent.ptr(), &child_name, mode, in_use) + tx.remove_node(parent.ptr(), &child_name, mode) } else { Err(Error::new(EISDIR)) } @@ -912,7 +912,8 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { let Some(file) = self.files.remove(&id) else { return; }; - let Some(file_info) = self.fmap.get_mut(&file.node_ptr().id()) else { + let node_ptr = file.node_ptr(); + let Some(file_info) = self.fmap.get_mut(&node_ptr.id()) else { return; }; @@ -921,8 +922,16 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { .checked_sub(1) .expect("open_fds not tracked correctly"); - // TODO: If open_fds reaches zero and there are no hardlinks (directory entries) to any - // particular inode, remove that inode here. + // Check if node no longer in use + if !file_info.in_use() { + // Notify filesystem of close + if let Err(err) = self.fs.tx(|tx| tx.on_close_node(node_ptr)) { + log::error!("failed to close node {}: {}", node_ptr.id(), err); + } + + // Remove from fmap list + self.fmap.remove(&node_ptr.id()); + } } fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result { @@ -1016,9 +1025,14 @@ impl<'sock, D: Disk> SchemeSync for FileScheme<'sock, D> { return Err(Error::new(EINVAL)); }; + let node_ptr = resource.node_ptr(); self.fmap - .entry(resource.node_ptr().id()) - .or_insert_with(Default::default) + .entry(node_ptr.id()) + .or_insert_with(|| { + // Notify filesystem of open + self.fs.tx(|tx| tx.on_open_node(node_ptr)).unwrap(); + FileMmapInfo::new() + }) .open_fds += 1; let id = self.next_id.fetch_add(1, Ordering::Relaxed); diff --git a/src/transaction.rs b/src/transaction.rs index fbfffdfb66..0a1cdb6f9f 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -16,8 +16,8 @@ use crate::{ htree::{self, HTreeHash, HTreeNode, HTreePtr}, AllocEntry, AllocList, Allocator, BlockAddr, BlockData, BlockLevel, BlockMeta, BlockPtr, BlockTrait, DirEntry, DirList, Disk, FileSystem, Header, Node, NodeFlags, NodeLevel, - NodeLevelData, RecordRaw, TreeData, TreePtr, ALLOC_GC_THRESHOLD, ALLOC_LIST_ENTRIES, - DIR_ENTRY_MAX_LENGTH, HEADER_RING, + NodeLevelData, RecordRaw, ReleaseList, TreeData, TreePtr, ALLOC_GC_THRESHOLD, + ALLOC_LIST_ENTRIES, DIR_ENTRY_MAX_LENGTH, HEADER_RING, }; pub(crate) fn level_data(node: &TreeData) -> Result<&NodeLevelData> { @@ -1086,16 +1086,6 @@ impl<'a, D: Disk> Transaction<'a, D> { parent_ptr: TreePtr, name: &str, mode: u16, - ) -> Result> { - self.remove_node_in_use(parent_ptr, name, mode, false) - } - - pub fn remove_node_in_use( - &mut self, - parent_ptr: TreePtr, - name: &str, - mode: u16, - in_use: bool, ) -> Result> { #[cfg(feature = "log")] log::debug!( @@ -1201,7 +1191,7 @@ impl<'a, D: Disk> Transaction<'a, D> { if remove_node { self.sync_tree(parent)?; - self.release_node(node.ptr(), in_use)?; + self.release_node(node.ptr())?; Ok(Some(node_id)) } else { // Sync both parent and node at the same time @@ -1210,12 +1200,143 @@ impl<'a, D: Disk> Transaction<'a, D> { } } - pub fn release_node(&mut self, node_ptr: TreePtr, in_use: bool) -> Result<()> { - if in_use { - eprintln!( - "LEAKING NODE {}, TODO PUT IT IN RELEASE LIST", - node_ptr.id() - ); + /// Notify of node open, for tracking node usage + pub fn on_open_node(&mut self, node_ptr: TreePtr) -> Result<()> { + let entry = self.fs.node_usages.entry(node_ptr.id()).or_insert(0); + *entry = entry.checked_add(1).ok_or_else(|| { + log::error!("node {} usage overflow", node_ptr.id()); + Error::new(EINVAL) + })?; + Ok(()) + } + + /// Notify of node close, for tracking node usage. Delete node if it is in + /// the release list. + /// + /// Returns `Ok(true)` if the node was deleted + pub fn on_close_node(&mut self, node_ptr: TreePtr) -> Result<()> { + // Subtract from usages and return if not zero + match self.fs.node_usages.get_mut(&node_ptr.id()) { + Some(entry) => { + *entry = entry.checked_sub(1).ok_or_else(|| { + log::error!("node {} usage underflow", node_ptr.id()); + Error::new(EINVAL) + })?; + if *entry > 0 { + // Node still in use + return Ok(()); + } + } + None => { + log::error!( + "tried to close node {} that is not already open", + node_ptr.id() + ); + return Ok(()); + } + } + + // Remove node usages entry + self.fs.node_usages.remove(&node_ptr.id()); + + // Check for node in release list and delete it + self.release_unused_nodes() + } + + /// Check for unused nodes in release list and delete them + pub fn release_unused_nodes(&mut self) -> Result<()> { + // Read current release lists (going forward through list) + let mut releases = VecDeque::>::new(); + { + let mut release_ptr = self.header.release; + while !release_ptr.is_null() { + let release = self.read_block(release_ptr)?; + release_ptr = release.data().prev; + releases.push_front(release); + } + } + + // Find unused nodes and remove them (going backwards through list) + let mut update_prev = None; + let mut release_nodes = Vec::new(); + while let Some(mut release) = releases.pop_back() { + if let Some(prev_ptr) = update_prev.take() { + release.data_mut().prev = prev_ptr; + } + + let mut changed = false; + let mut empty = true; + for entry in release.data_mut().entries.iter_mut() { + if !entry.is_null() { + let usages = self.fs.node_usages.get(&entry.id()).copied().unwrap_or(0); + if usages == 0 { + release_nodes.push(*entry); + *entry = TreePtr::default(); + changed = true; + } else { + empty = false; + } + } + } + + if empty { + // Deallocate this release list block + unsafe { + self.deallocate(&mut FsCtx, release.addr()); + } + // Skip this block in the list + update_prev = Some(release.data().prev); + } else if changed { + // Update this block + update_prev = Some(self.sync_block(&mut FsCtx, release)?); + } else { + update_prev = None; + } + } + if let Some(prev_ptr) = update_prev.take() { + self.header.release = prev_ptr; + self.header_changed = true; + } + + for node_ptr in release_nodes { + self.release_node(node_ptr)?; + } + + Ok(()) + } + + /// Removes node if usages are zero, adds it to release list if usages are + /// greater than zero. + pub fn release_node(&mut self, node_ptr: TreePtr) -> Result<()> { + let usages = self + .fs + .node_usages + .get(&node_ptr.id()) + .copied() + .unwrap_or(0); + if usages > 0 { + let mut release = unsafe { self.read_block_or_empty(self.header.release)? }; + + // Try to insert into current release block + let mut inserted = false; + for entry in release.data_mut().entries.iter_mut() { + if entry.is_null() { + *entry = node_ptr; + inserted = true; + break; + } + } + + // If not inserted, try to add another release block + if !inserted { + release = BlockData::empty(BlockAddr::null(BlockMeta::default())).unwrap(); + release.data_mut().prev = self.header.release; + release.data_mut().entries[0] = node_ptr; + } + + // Update header + self.header.release = self.sync_block(&mut FsCtx, release)?; + self.header_changed = true; } else { let (mut node, node_addr) = self.read_tree_and_addr(node_ptr)?; self.truncate_node_inner(&mut node, 0)?; diff --git a/tests/tests.rs b/tests/tests.rs index 39d1b09f22..58ba58ab00 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,6 +1,7 @@ use core::panic::AssertUnwindSafe; use redoxfs::{unmount_path, DirEntry, DiskMemory, DiskSparse, FileSystem, Node, TreePtr}; +use std::io::{Read, Seek, SeekFrom, Write}; use std::panic::catch_unwind; use std::path::Path; use std::process::Command; @@ -614,3 +615,25 @@ fn rename_works() { fs.tx(|tx| tx.rename_node(root, "dir", root, "newdir")) .expect("Renaming 'dir' to 'newdir' should succeed"); } + +#[test] +fn temporary_file() { + with_mounted(|path| { + let file_path = path.join("temp"); + let mut file = fs::File::create_new(&file_path).expect("failed to create temp file"); + + fs::remove_file(&file_path).expect("failed to unlink temp file"); + + let write_data = "Test\n"; + file.write_all(write_data.as_bytes()) + .expect("failed to write temp file"); + + let mut read_data = String::new(); + file.seek(SeekFrom::Start(0)) + .expect("failed to seek temp file"); + file.read_to_string(&mut read_data) + .expect("failed to read temp file"); + + assert_eq!(read_data, write_data); + }); +}