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 :: * ;
7+ use std:: cmp:: { Eq , Ordering , PartialEq } ;
8+ use std:: collections:: { BTreeMap , VecDeque } ;
9+ use std:: collections:: btree_map :: Entry :: * ;
1010use std:: hash:: { Hash , Hasher } ;
11+ use std:: ops:: Bound :: { Excluded , Included , Unbounded } ;
1112use std:: path:: { Path , PathBuf } ;
1213use std:: sync:: Arc ;
1314
@@ -27,7 +28,7 @@ struct InodeTableEntry {
2728pub 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
3334impl 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 {
245276impl 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+
248291impl 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