Implement release list and add open node tracking to filesystem

This commit is contained in:
Jeremy Soller
2026-01-02 13:23:29 -07:00
parent 57e05acef2
commit 0e01427cd8
12 changed files with 355 additions and 140 deletions
Generated
+25 -14
View File
@@ -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",
+1 -1
View File
@@ -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 }
+48 -63
View File
@@ -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",
+1 -1
View File
@@ -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"
+4 -4
View File
@@ -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<Operation>,
}
@@ -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<F>(temp_path: &Path, disk: DiskSparse, squash: bool, callback: F)
fn with_redoxfs_mount<F>(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:?}");
+20 -6
View File
@@ -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<D: Disk> {
pub(crate) allocator: Allocator,
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
pub(crate) compress_cache: Box<[u8]>,
pub node_usages: BTreeMap<u32, u64>,
}
impl<D: Disk> FileSystem<D> {
@@ -33,7 +38,7 @@ impl<D: Disk> FileSystem<D> {
mut disk: D,
password_opt: Option<&[u8]>,
block_opt: Option<u64>,
squash: bool,
cleanup: bool,
) -> Result<Self> {
for ring_block in block_opt.map_or(0..65536, |x| x..x + 1) {
let mut header = Header::default();
@@ -90,12 +95,14 @@ impl<D: Disk> FileSystem<D> {
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<D: Disk> FileSystem<D> {
allocator: Allocator::default(),
cipher_opt,
compress_cache: compress_cache(),
node_usages: BTreeMap::new(),
};
// Write header generation zero
@@ -219,12 +227,18 @@ impl<D: Disk> FileSystem<D> {
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<F: FnOnce(&mut Transaction<D>) -> Result<T>, T>(&mut self, f: F) -> Result<T> {
let mut tx = Transaction::new(self);
+34 -7
View File
@@ -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<D: Disk> 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<u64>, reply: ReplyAttr) {
let node_ptr = TreePtr::<Node>::new(node_id as u32);
match self.fs.tx(|tx| tx.read_tree(node_ptr)) {
Ok(node) => {
@@ -225,6 +225,14 @@ impl<D: Disk> 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::<Node>::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<D: Disk> Filesystem for Fuse<'_, D> {
reply.ok();
}
fn release(
&mut self,
_req: &Request,
node_id: u64,
_fh: u64,
_flags: i32,
_lock_owner: Option<u64>,
_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<D: Disk> Filesystem for Fuse<'_, D> {
let parent_ptr = TreePtr::<Node>::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);
+7 -3
View File
@@ -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)
}
+23 -7
View File
@@ -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<Fmap>,
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<D: Disk> Resource<D> 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(())
}
+29 -15
View File
@@ -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<usize> {
@@ -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);
+140 -19
View File
@@ -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<Node>) -> Result<&NodeLevelData> {
@@ -1086,16 +1086,6 @@ impl<'a, D: Disk> Transaction<'a, D> {
parent_ptr: TreePtr<Node>,
name: &str,
mode: u16,
) -> Result<Option<u32>> {
self.remove_node_in_use(parent_ptr, name, mode, false)
}
pub fn remove_node_in_use(
&mut self,
parent_ptr: TreePtr<Node>,
name: &str,
mode: u16,
in_use: bool,
) -> Result<Option<u32>> {
#[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<Node>, 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<Node>) -> 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<Node>) -> 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::<BlockData<ReleaseList>>::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<Node>) -> 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)?;
+23
View File
@@ -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);
});
}