Skip to content

Commit 95b55c7

Browse files
committed
renamed directories should propagate to their children in the inodetable
1 parent af7622e commit 95b55c7

2 files changed

Lines changed: 175 additions & 91 deletions

File tree

RELEASENOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
v0.6.4: (unreleased)
2+
* Fixed a bug in inode table where renamed directories did not propagate to their children in the
3+
table. See #51.
4+
15
v0.6.3: 2025-11-30
26
* Fixed a missing inodes.unlink in rmdir, which could cause a panic in rare circumstances
37

src/inode_table.rs

Lines changed: 171 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
// InodeTable :: a bi-directional map of paths to inodes.
22
//
3-
// Copyright (c) 2016-2022 by William R. Fraser
3+
// Copyright (c) 2016-2026 by William R. Fraser
44
//
55

66
use std::borrow::Borrow;
7-
use std::cmp::{Eq, PartialEq};
8-
use std::collections::{HashMap, VecDeque};
9-
use std::collections::hash_map::Entry::*;
7+
use std::cmp::{Eq, Ordering, PartialEq};
8+
use std::collections::{BTreeMap, VecDeque};
9+
use std::collections::btree_map::Entry::*;
1010
use std::hash::{Hash, Hasher};
11+
use std::ops::Bound::{Excluded, Included, Unbounded};
1112
use std::path::{Path, PathBuf};
1213
use std::sync::Arc;
1314

@@ -27,7 +28,7 @@ struct InodeTableEntry {
2728
pub struct InodeTable {
2829
table: Vec<InodeTableEntry>,
2930
free_list: VecDeque<usize>,
30-
by_path: HashMap<Arc<PathBuf>, usize>,
31+
by_path: BTreeMap<Arc<PathBuf>, usize>,
3132
}
3233

3334
impl InodeTable {
@@ -42,7 +43,7 @@ impl InodeTable {
4243
let mut inode_table = InodeTable {
4344
table: Vec::new(),
4445
free_list: VecDeque::new(),
45-
by_path: HashMap::new()
46+
by_path: BTreeMap::new()
4647
};
4748
let root = Arc::new(PathBuf::from("/"));
4849
inode_table.table.push(InodeTableEntry {
@@ -180,9 +181,39 @@ impl InodeTable {
180181
/// Change an inode's path to a different one, without changing the inode number.
181182
/// Lookup counts remain unchanged, even if this is replacing another file.
182183
pub fn rename(&mut self, oldpath: &Path, newpath: Arc<PathBuf>) {
183-
let idx = self.by_path.remove(Pathish::new(oldpath)).unwrap();
184-
self.table[idx].path = Some(newpath.clone());
185-
self.by_path.insert(newpath, idx); // this can replace a path with a new inode
184+
// None means it's a single path being renamed
185+
let mut range = None;
186+
for (candidate, _) in self.by_path.range::<Pathish, _>((Excluded(Pathish::new(oldpath)), Unbounded)) {
187+
// look for children of the path being renamed
188+
if candidate.starts_with(oldpath) {
189+
if let Some((start, _end)) = range {
190+
range = Some((start, Included(candidate.clone())));
191+
} else {
192+
range = Some((Included(Arc::new(oldpath.to_owned())), Included(Arc::clone(candidate))));
193+
}
194+
} else {
195+
break;
196+
}
197+
}
198+
199+
if let Some(range) = range {
200+
let mut new_entries = vec![];
201+
for (path, idx) in self.by_path.extract_if(range, |_, _| true) {
202+
let suffix = path.strip_prefix(oldpath).unwrap();
203+
let new_entry_path = if suffix.as_os_str().is_empty() {
204+
Arc::clone(&newpath)
205+
} else {
206+
Arc::new(newpath.as_path().join(suffix))
207+
};
208+
self.table[idx].path = Some(Arc::clone(&new_entry_path));
209+
new_entries.push((new_entry_path, idx));
210+
}
211+
self.by_path.extend(new_entries);
212+
} else {
213+
let idx = self.by_path.remove(Pathish::new(oldpath)).unwrap();
214+
self.table[idx].path = Some(newpath.clone());
215+
self.by_path.insert(newpath, idx); // this can replace a path with a new inode
216+
}
186217
}
187218

188219
/// Remove the path->inode mapping for a given path, but keep the inode around.
@@ -245,99 +276,148 @@ impl Hash for Pathish {
245276
impl Eq for Pathish {
246277
}
247278

279+
impl PartialOrd<Self> for Pathish {
280+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
281+
Some(self.cmp(other))
282+
}
283+
}
284+
285+
impl Ord for Pathish {
286+
fn cmp(&self, other: &Self) -> Ordering {
287+
self.inner.cmp(&other.inner)
288+
}
289+
}
290+
248291
impl PartialEq for Pathish {
249292
fn eq(&self, other: &Pathish) -> bool {
250293
self.inner.eq(&other.inner)
251294
}
252295
}
253296

254-
#[test]
255-
fn test_inode_reuse() {
256-
let mut table = InodeTable::new();
257-
let path1 = Arc::new(PathBuf::from("/foo/a"));
258-
let path2 = Arc::new(PathBuf::from("/foo/b"));
259-
260-
// Add a path.
261-
let inode1 = table.add(path1.clone()).0;
262-
assert!(inode1 != 1);
263-
assert_eq!(*path1, *table.get_path(inode1).unwrap());
264-
265-
// Add a second path; verify that the inode number is different.
266-
let inode2 = table.add(path2.clone()).0;
267-
assert!(inode2 != inode1);
268-
assert!(inode2 != 1);
269-
assert_eq!(*path2, *table.get_path(inode2).unwrap());
270-
271-
// Forget the first inode; verify that lookups on it fail.
272-
assert_eq!(0, table.forget(inode1, 1));
273-
assert!(table.get_path(inode1).is_none());
274-
275-
// Add a third path; verify that the inode is reused.
276-
let (inode3, generation3) = table.add(Arc::new(PathBuf::from("/foo/c")));
277-
assert_eq!(inode1, inode3);
278-
assert_eq!(1, generation3);
279-
280-
// Check that lookups on the third path succeed.
281-
assert_eq!(Path::new("/foo/c"), *table.get_path(inode3).unwrap());
282-
}
297+
#[cfg(test)]
298+
mod tests {
299+
use super::*;
300+
301+
#[test]
302+
fn test_inode_reuse() {
303+
let mut table = InodeTable::new();
304+
let path1 = Arc::new(PathBuf::from("/foo/a"));
305+
let path2 = Arc::new(PathBuf::from("/foo/b"));
306+
307+
// Add a path.
308+
let inode1 = table.add(path1.clone()).0;
309+
assert!(inode1 != 1);
310+
assert_eq!(*path1, *table.get_path(inode1).unwrap());
311+
312+
// Add a second path; verify that the inode number is different.
313+
let inode2 = table.add(path2.clone()).0;
314+
assert!(inode2 != inode1);
315+
assert!(inode2 != 1);
316+
assert_eq!(*path2, *table.get_path(inode2).unwrap());
317+
318+
// Forget the first inode; verify that lookups on it fail.
319+
assert_eq!(0, table.forget(inode1, 1));
320+
assert!(table.get_path(inode1).is_none());
321+
322+
// Add a third path; verify that the inode is reused.
323+
let (inode3, generation3) = table.add(Arc::new(PathBuf::from("/foo/c")));
324+
assert_eq!(inode1, inode3);
325+
assert_eq!(1, generation3);
326+
327+
// Check that lookups on the third path succeed.
328+
assert_eq!(Path::new("/foo/c"), *table.get_path(inode3).unwrap());
329+
}
283330

284-
#[test]
285-
fn test_add_or_get() {
286-
let mut table = InodeTable::new();
287-
let path1 = Arc::new(PathBuf::from("/foo/a"));
288-
let path2 = Arc::new(PathBuf::from("/foo/b"));
289-
290-
// add_or_get() a path and verify that get by inode works before lookup() is done.
291-
let inode1 = table.add_or_get(path1.clone()).0;
292-
assert_eq!(*path1, *table.get_path(inode1).unwrap());
293-
table.lookup(inode1);
294-
295-
// add() a second path and verify that get by path and inode work.
296-
let inode2 = table.add(path2.clone()).0;
297-
assert_eq!(*path2, *table.get_path(inode2).unwrap());
298-
assert_eq!(inode2, table.add_or_get(path2).0);
299-
table.lookup(inode2);
300-
301-
// Check the ref counts by doing a single forget.
302-
assert_eq!(0, table.forget(inode1, 1));
303-
assert_eq!(1, table.forget(inode2, 1));
304-
}
331+
#[test]
332+
fn test_add_or_get() {
333+
let mut table = InodeTable::new();
334+
let path1 = Arc::new(PathBuf::from("/foo/a"));
335+
let path2 = Arc::new(PathBuf::from("/foo/b"));
336+
337+
// add_or_get() a path and verify that get by inode works before lookup() is done.
338+
let inode1 = table.add_or_get(path1.clone()).0;
339+
assert_eq!(*path1, *table.get_path(inode1).unwrap());
340+
table.lookup(inode1);
341+
342+
// add() a second path and verify that get by path and inode work.
343+
let inode2 = table.add(path2.clone()).0;
344+
assert_eq!(*path2, *table.get_path(inode2).unwrap());
345+
assert_eq!(inode2, table.add_or_get(path2).0);
346+
table.lookup(inode2);
347+
348+
// Check the ref counts by doing a single forget.
349+
assert_eq!(0, table.forget(inode1, 1));
350+
assert_eq!(1, table.forget(inode2, 1));
351+
}
305352

306-
#[test]
307-
fn test_inode_rename() {
308-
let mut table = InodeTable::new();
309-
let path1 = Arc::new(PathBuf::from("/foo/a"));
310-
let path2 = Arc::new(PathBuf::from("/foo/b"));
311-
312-
// Add a path; verify that get by path and inode work.
313-
let inode = table.add(path1.clone()).0;
314-
assert_eq!(*path1, *table.get_path(inode).unwrap());
315-
assert_eq!(inode, table.get_inode(&path1).unwrap());
316-
317-
// Rename the inode; verify that get by the new path works and old path doesn't, and get by
318-
// inode still works.
319-
table.rename(&path1, path2.clone());
320-
assert!(table.get_inode(&path1).is_none());
321-
assert_eq!(inode, table.get_inode(&path2).unwrap());
322-
assert_eq!(*path2, *table.get_path(inode).unwrap());
323-
}
353+
#[test]
354+
fn test_inode_rename() {
355+
let mut table = InodeTable::new();
356+
let path1 = Arc::new(PathBuf::from("/foo/a"));
357+
let path2 = Arc::new(PathBuf::from("/foo/b"));
358+
359+
// Add a path; verify that get by path and inode work.
360+
let inode = table.add(path1.clone()).0;
361+
assert_eq!(*path1, *table.get_path(inode).unwrap());
362+
assert_eq!(inode, table.get_inode(&path1).unwrap());
363+
364+
// Rename the inode; verify that get by the new path works and old path doesn't, and get by
365+
// inode still works.
366+
table.rename(&path1, path2.clone());
367+
assert!(table.get_inode(&path1).is_none());
368+
assert_eq!(inode, table.get_inode(&path2).unwrap());
369+
assert_eq!(*path2, *table.get_path(inode).unwrap());
370+
}
324371

325-
#[test]
326-
fn test_unlink() {
327-
let mut table = InodeTable::new();
328-
let path = Arc::new(PathBuf::from("/foo/bar"));
372+
#[test]
373+
fn test_unlink() {
374+
let mut table = InodeTable::new();
375+
let path = Arc::new(PathBuf::from("/foo/bar"));
329376

330-
// Add a path.
331-
let inode = table.add(path.clone()).0;
377+
// Add a path.
378+
let inode = table.add(path.clone()).0;
332379

333-
// Unlink it and verify that get by path fails.
334-
table.unlink(&path);
335-
assert!(table.get_inode(&path).is_none());
380+
// Unlink it and verify that get by path fails.
381+
table.unlink(&path);
382+
assert!(table.get_inode(&path).is_none());
336383

337-
// Getting the path for the inode should still return the path.
338-
assert_eq!(*path, *table.get_path(inode).unwrap());
384+
// Getting the path for the inode should still return the path.
385+
assert_eq!(*path, *table.get_path(inode).unwrap());
339386

340-
// Verify that forgetting it once drops the refcount to zero and then lookups by inode fail.
341-
assert_eq!(0, table.forget(inode, 1));
342-
assert!(table.get_path(inode).is_none());
387+
// Verify that forgetting it once drops the refcount to zero and then lookups by inode fail.
388+
assert_eq!(0, table.forget(inode, 1));
389+
assert!(table.get_path(inode).is_none());
390+
}
391+
392+
#[test]
393+
fn test_rename_directory() {
394+
let mut table = InodeTable::new();
395+
let a = table.add(Arc::new(PathBuf::from("/a_file")));
396+
let d = table.add(Arc::new(PathBuf::from("/directory")));
397+
let x = table.add(Arc::new(PathBuf::from("/directory.x"))); // '.' sorts before '/' naïvely!
398+
let d_f1 = table.add(Arc::new(PathBuf::from("/directory/file1")));
399+
let d_f2 = table.add(Arc::new(PathBuf::from("/directory/file2")));
400+
let z = table.add(Arc::new(PathBuf::from("/z_file")));
401+
402+
table.rename(Path::new("/a_file"), Arc::new(PathBuf::from("/a_file_renamed")));
403+
assert_eq!(table.get_inode(Path::new("/a_file")), None);
404+
assert_eq!(table.get_inode(Path::new("/a_file_renamed")), Some(a.0));
405+
406+
table.rename(Path::new("/directory"), Arc::new(PathBuf::from("/new_directory")));
407+
408+
// paths which should be unaffected
409+
assert_eq!(table.get_inode(Path::new("/a_file_renamed")), Some(a.0));
410+
assert_eq!(table.get_inode(Path::new("/z_file")), Some(z.0));
411+
assert_eq!(table.get_inode(Path::new("/directory.x")), Some(x.0));
412+
413+
// paths which should have been renamed and return the same inode as original
414+
assert_eq!(table.get_inode(Path::new("/new_directory")), Some(d.0));
415+
assert_eq!(table.get_inode(Path::new("/new_directory/file1")), Some(d_f1.0));
416+
assert_eq!(table.get_inode(Path::new("/new_directory/file2")), Some(d_f2.0));
417+
418+
// paths which should no longer exist
419+
assert_eq!(table.get_inode(Path::new("/directory")), None);
420+
assert_eq!(table.get_inode(Path::new("/directory/file1")), None);
421+
assert_eq!(table.get_inode(Path::new("/directory/file2")), None);
422+
}
343423
}

0 commit comments

Comments
 (0)