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
66use std:: borrow:: Borrow ;
7- use std:: cmp:: { Eq , PartialEq } ;
8- use std:: collections:: { HashMap , VecDeque } ;
9- use std:: collections:: hash_map :: Entry :: * ;
10- use std:: hash :: { Hash , Hasher } ;
7+ use std:: cmp:: { Eq , Ordering , PartialEq } ;
8+ use std:: collections:: btree_map :: Entry :: * ;
9+ use std:: collections:: { BTreeMap , VecDeque } ;
10+ use std:: ops :: Bound :: { Excluded , Included , Unbounded } ;
1111use std:: path:: { Path , PathBuf } ;
1212use std:: sync:: Arc ;
1313
@@ -27,7 +27,7 @@ struct InodeTableEntry {
2727pub struct InodeTable {
2828 table : Vec < InodeTableEntry > ,
2929 free_list : VecDeque < usize > ,
30- by_path : HashMap < Arc < PathBuf > , usize > ,
30+ by_path : BTreeMap < Arc < PathBuf > , usize > ,
3131}
3232
3333impl InodeTable {
@@ -42,7 +42,7 @@ impl InodeTable {
4242 let mut inode_table = InodeTable {
4343 table : Vec :: new ( ) ,
4444 free_list : VecDeque :: new ( ) ,
45- by_path : HashMap :: new ( )
45+ by_path : BTreeMap :: new ( ) ,
4646 } ;
4747 let root = Arc :: new ( PathBuf :: from ( "/" ) ) ;
4848 inode_table. table . push ( InodeTableEntry {
@@ -180,9 +180,42 @@ impl InodeTable {
180180 /// Change an inode's path to a different one, without changing the inode number.
181181 /// Lookup counts remain unchanged, even if this is replacing another file.
182182 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
183+ // Look for children of the path being renamed and fix them up too.
184+ // Note that we use range() to find the bounds of the map first, and only then use
185+ // extract_if to remove that range, because:
186+ // 1. extract_if does not provide a way to stop iterating early
187+ // 2. extract_if does not utilize Borrow in its range, so here we have to clone the
188+ // start and end paths because the range type must match the key type exactly.
189+ if let Some ( ( last_child, _) ) = self
190+ . by_path
191+ . range :: < Pathish , _ > ( ( Excluded ( Pathish :: new ( oldpath) ) , Unbounded ) )
192+ . take_while ( |( path, _) | path. starts_with ( oldpath) )
193+ . last ( )
194+ {
195+ let mut new_entries = vec ! [ ] ;
196+ for ( path, idx) in self . by_path . extract_if (
197+ (
198+ Included ( Arc :: new ( oldpath. to_owned ( ) ) ) ,
199+ Included ( Arc :: clone ( last_child) ) ,
200+ ) ,
201+ |_, _| true ,
202+ ) {
203+ let suffix = path. strip_prefix ( oldpath) . unwrap ( ) ;
204+ let new_entry_path = if suffix. as_os_str ( ) . is_empty ( ) {
205+ // this is the entry for parent path itself
206+ Arc :: clone ( & newpath)
207+ } else {
208+ Arc :: new ( newpath. as_path ( ) . join ( suffix) )
209+ } ;
210+ self . table [ idx] . path = Some ( Arc :: clone ( & new_entry_path) ) ;
211+ new_entries. push ( ( new_entry_path, idx) ) ;
212+ }
213+ self . by_path . extend ( new_entries) ;
214+ } else {
215+ let idx = self . by_path . remove ( Pathish :: new ( oldpath) ) . unwrap ( ) ;
216+ self . table [ idx] . path = Some ( Arc :: clone ( & newpath) ) ;
217+ self . by_path . insert ( newpath, idx) ; // this can replace a path with a new inode
218+ }
186219 }
187220
188221 /// Remove the path->inode mapping for a given path, but keep the inode around.
@@ -236,9 +269,15 @@ impl Borrow<Pathish> for Arc<PathBuf> {
236269 }
237270}
238271
239- impl Hash for Pathish {
240- fn hash < H : Hasher > ( & self , state : & mut H ) {
241- self . inner . hash ( state) ;
272+ impl PartialOrd < Self > for Pathish {
273+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
274+ Some ( self . cmp ( other) )
275+ }
276+ }
277+
278+ impl Ord for Pathish {
279+ fn cmp ( & self , other : & Self ) -> Ordering {
280+ self . inner . cmp ( & other. inner )
242281 }
243282}
244283
@@ -251,93 +290,130 @@ impl PartialEq for Pathish {
251290 }
252291}
253292
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- }
293+ #[ cfg( test) ]
294+ mod tests {
295+ use super :: * ;
296+
297+ #[ test]
298+ fn test_inode_reuse ( ) {
299+ let mut table = InodeTable :: new ( ) ;
300+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
301+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
302+
303+ // Add a path.
304+ let inode1 = table. add ( path1. clone ( ) ) . 0 ;
305+ assert ! ( inode1 != 1 ) ;
306+ assert_eq ! ( * path1, * table. get_path( inode1) . unwrap( ) ) ;
307+
308+ // Add a second path; verify that the inode number is different.
309+ let inode2 = table. add ( path2. clone ( ) ) . 0 ;
310+ assert ! ( inode2 != inode1) ;
311+ assert ! ( inode2 != 1 ) ;
312+ assert_eq ! ( * path2, * table. get_path( inode2) . unwrap( ) ) ;
313+
314+ // Forget the first inode; verify that lookups on it fail.
315+ assert_eq ! ( 0 , table. forget( inode1, 1 ) ) ;
316+ assert ! ( table. get_path( inode1) . is_none( ) ) ;
317+
318+ // Add a third path; verify that the inode is reused.
319+ let ( inode3, generation3) = table. add ( Arc :: new ( PathBuf :: from ( "/foo/c" ) ) ) ;
320+ assert_eq ! ( inode1, inode3) ;
321+ assert_eq ! ( 1 , generation3) ;
322+
323+ // Check that lookups on the third path succeed.
324+ assert_eq ! ( Path :: new( "/foo/c" ) , * table. get_path( inode3) . unwrap( ) ) ;
325+ }
283326
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- }
327+ #[ test]
328+ fn test_add_or_get ( ) {
329+ let mut table = InodeTable :: new ( ) ;
330+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
331+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
332+
333+ // add_or_get() a path and verify that get by inode works before lookup() is done.
334+ let inode1 = table. add_or_get ( path1. clone ( ) ) . 0 ;
335+ assert_eq ! ( * path1, * table. get_path( inode1) . unwrap( ) ) ;
336+ table. lookup ( inode1) ;
337+
338+ // add() a second path and verify that get by path and inode work.
339+ let inode2 = table. add ( path2. clone ( ) ) . 0 ;
340+ assert_eq ! ( * path2, * table. get_path( inode2) . unwrap( ) ) ;
341+ assert_eq ! ( inode2, table. add_or_get( path2) . 0 ) ;
342+ table. lookup ( inode2) ;
343+
344+ // Check the ref counts by doing a single forget.
345+ assert_eq ! ( 0 , table. forget( inode1, 1 ) ) ;
346+ assert_eq ! ( 1 , table. forget( inode2, 1 ) ) ;
347+ }
305348
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- }
349+ #[ test]
350+ fn test_inode_rename ( ) {
351+ let mut table = InodeTable :: new ( ) ;
352+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
353+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
354+
355+ // Add a path; verify that get by path and inode work.
356+ let inode = table. add ( path1. clone ( ) ) . 0 ;
357+ assert_eq ! ( * path1, * table. get_path( inode) . unwrap( ) ) ;
358+ assert_eq ! ( inode, table. get_inode( & path1) . unwrap( ) ) ;
359+
360+ // Rename the inode; verify that get by the new path works and old path doesn't, and get by
361+ // inode still works.
362+ table. rename ( & path1, path2. clone ( ) ) ;
363+ assert ! ( table. get_inode( & path1) . is_none( ) ) ;
364+ assert_eq ! ( inode, table. get_inode( & path2) . unwrap( ) ) ;
365+ assert_eq ! ( * path2, * table. get_path( inode) . unwrap( ) ) ;
366+ }
324367
325- #[ test]
326- fn test_unlink ( ) {
327- let mut table = InodeTable :: new ( ) ;
328- let path = Arc :: new ( PathBuf :: from ( "/foo/bar" ) ) ;
368+ #[ test]
369+ fn test_unlink ( ) {
370+ let mut table = InodeTable :: new ( ) ;
371+ let path = Arc :: new ( PathBuf :: from ( "/foo/bar" ) ) ;
329372
330- // Add a path.
331- let inode = table. add ( path. clone ( ) ) . 0 ;
373+ // Add a path.
374+ let inode = table. add ( path. clone ( ) ) . 0 ;
332375
333- // Unlink it and verify that get by path fails.
334- table. unlink ( & path) ;
335- assert ! ( table. get_inode( & path) . is_none( ) ) ;
376+ // Unlink it and verify that get by path fails.
377+ table. unlink ( & path) ;
378+ assert ! ( table. get_inode( & path) . is_none( ) ) ;
336379
337- // Getting the path for the inode should still return the path.
338- assert_eq ! ( * path, * table. get_path( inode) . unwrap( ) ) ;
380+ // Getting the path for the inode should still return the path.
381+ assert_eq ! ( * path, * table. get_path( inode) . unwrap( ) ) ;
339382
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( ) ) ;
383+ // Verify that forgetting it once drops the refcount to zero and then lookups by inode fail.
384+ assert_eq ! ( 0 , table. forget( inode, 1 ) ) ;
385+ assert ! ( table. get_path( inode) . is_none( ) ) ;
386+ }
387+
388+ #[ test]
389+ fn test_rename_directory ( ) {
390+ let mut table = InodeTable :: new ( ) ;
391+ let a = table. add ( Arc :: new ( PathBuf :: from ( "/a_file" ) ) ) ;
392+ let d = table. add ( Arc :: new ( PathBuf :: from ( "/directory" ) ) ) ;
393+ let x = table. add ( Arc :: new ( PathBuf :: from ( "/directory.x" ) ) ) ; // '.' sorts before '/' naïvely!
394+ let d_f1 = table. add ( Arc :: new ( PathBuf :: from ( "/directory/file1" ) ) ) ;
395+ let d_f2 = table. add ( Arc :: new ( PathBuf :: from ( "/directory/file2" ) ) ) ;
396+ let z = table. add ( Arc :: new ( PathBuf :: from ( "/z_file" ) ) ) ;
397+
398+ table. rename ( Path :: new ( "/a_file" ) , Arc :: new ( PathBuf :: from ( "/a_file_renamed" ) ) ) ;
399+ assert_eq ! ( table. get_inode( Path :: new( "/a_file" ) ) , None ) ;
400+ assert_eq ! ( table. get_inode( Path :: new( "/a_file_renamed" ) ) , Some ( a. 0 ) ) ;
401+
402+ table. rename ( Path :: new ( "/directory" ) , Arc :: new ( PathBuf :: from ( "/new_directory" ) ) ) ;
403+
404+ // paths which should be unaffected
405+ assert_eq ! ( table. get_inode( Path :: new( "/a_file_renamed" ) ) , Some ( a. 0 ) ) ;
406+ assert_eq ! ( table. get_inode( Path :: new( "/z_file" ) ) , Some ( z. 0 ) ) ;
407+ assert_eq ! ( table. get_inode( Path :: new( "/directory.x" ) ) , Some ( x. 0 ) ) ;
408+
409+ // paths which should have been renamed and return the same inode as original
410+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory" ) ) , Some ( d. 0 ) ) ;
411+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory/file1" ) ) , Some ( d_f1. 0 ) ) ;
412+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory/file2" ) ) , Some ( d_f2. 0 ) ) ;
413+
414+ // paths which should no longer exist
415+ assert_eq ! ( table. get_inode( Path :: new( "/directory" ) ) , None ) ;
416+ assert_eq ! ( table. get_inode( Path :: new( "/directory/file1" ) ) , None ) ;
417+ assert_eq ! ( table. get_inode( Path :: new( "/directory/file2" ) ) , None ) ;
418+ }
343419}
0 commit comments