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:: { BTreeMap , VecDeque } ;
9+ use std:: collections:: btree_map :: Entry :: * ;
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,39 @@ 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+ // None means it's a single path being renamed
184+ let mut range = None ;
185+ for ( candidate, _) in self . by_path . range :: < Pathish , _ > ( ( Excluded ( Pathish :: new ( oldpath) ) , Unbounded ) ) {
186+ // look for children of the path being renamed
187+ if candidate. starts_with ( oldpath) {
188+ if let Some ( ( start, _end) ) = range {
189+ range = Some ( ( start, Included ( candidate. clone ( ) ) ) ) ;
190+ } else {
191+ range = Some ( ( Included ( Arc :: new ( oldpath. to_owned ( ) ) ) , Included ( Arc :: clone ( candidate) ) ) ) ;
192+ }
193+ } else {
194+ break ;
195+ }
196+ }
197+
198+ if let Some ( range) = range {
199+ let mut new_entries = vec ! [ ] ;
200+ for ( path, idx) in self . by_path . extract_if ( range, |_, _| true ) {
201+ let suffix = path. strip_prefix ( oldpath) . unwrap ( ) ;
202+ let new_entry_path = if suffix. as_os_str ( ) . is_empty ( ) {
203+ Arc :: clone ( & newpath)
204+ } else {
205+ Arc :: new ( newpath. as_path ( ) . join ( suffix) )
206+ } ;
207+ self . table [ idx] . path = Some ( Arc :: clone ( & new_entry_path) ) ;
208+ new_entries. push ( ( new_entry_path, idx) ) ;
209+ }
210+ self . by_path . extend ( new_entries) ;
211+ } else {
212+ let idx = self . by_path . remove ( Pathish :: new ( oldpath) ) . unwrap ( ) ;
213+ self . table [ idx] . path = Some ( newpath. clone ( ) ) ;
214+ self . by_path . insert ( newpath, idx) ; // this can replace a path with a new inode
215+ }
186216 }
187217
188218 /// Remove the path->inode mapping for a given path, but keep the inode around.
@@ -236,13 +266,19 @@ impl Borrow<Pathish> for Arc<PathBuf> {
236266 }
237267}
238268
239- impl Hash for Pathish {
240- fn hash < H : Hasher > ( & self , state : & mut H ) {
241- self . inner . hash ( state) ;
269+ impl Eq for Pathish {
270+ }
271+
272+ impl PartialOrd < Self > for Pathish {
273+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
274+ Some ( self . cmp ( other) )
242275 }
243276}
244277
245- impl Eq for Pathish {
278+ impl Ord for Pathish {
279+ fn cmp ( & self , other : & Self ) -> Ordering {
280+ self . inner . cmp ( & other. inner )
281+ }
246282}
247283
248284impl PartialEq for Pathish {
@@ -251,93 +287,130 @@ impl PartialEq for Pathish {
251287 }
252288}
253289
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- }
290+ #[ cfg( test) ]
291+ mod tests {
292+ use super :: * ;
293+
294+ #[ test]
295+ fn test_inode_reuse ( ) {
296+ let mut table = InodeTable :: new ( ) ;
297+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
298+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
299+
300+ // Add a path.
301+ let inode1 = table. add ( path1. clone ( ) ) . 0 ;
302+ assert ! ( inode1 != 1 ) ;
303+ assert_eq ! ( * path1, * table. get_path( inode1) . unwrap( ) ) ;
304+
305+ // Add a second path; verify that the inode number is different.
306+ let inode2 = table. add ( path2. clone ( ) ) . 0 ;
307+ assert ! ( inode2 != inode1) ;
308+ assert ! ( inode2 != 1 ) ;
309+ assert_eq ! ( * path2, * table. get_path( inode2) . unwrap( ) ) ;
310+
311+ // Forget the first inode; verify that lookups on it fail.
312+ assert_eq ! ( 0 , table. forget( inode1, 1 ) ) ;
313+ assert ! ( table. get_path( inode1) . is_none( ) ) ;
314+
315+ // Add a third path; verify that the inode is reused.
316+ let ( inode3, generation3) = table. add ( Arc :: new ( PathBuf :: from ( "/foo/c" ) ) ) ;
317+ assert_eq ! ( inode1, inode3) ;
318+ assert_eq ! ( 1 , generation3) ;
319+
320+ // Check that lookups on the third path succeed.
321+ assert_eq ! ( Path :: new( "/foo/c" ) , * table. get_path( inode3) . unwrap( ) ) ;
322+ }
283323
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- }
324+ #[ test]
325+ fn test_add_or_get ( ) {
326+ let mut table = InodeTable :: new ( ) ;
327+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
328+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
329+
330+ // add_or_get() a path and verify that get by inode works before lookup() is done.
331+ let inode1 = table. add_or_get ( path1. clone ( ) ) . 0 ;
332+ assert_eq ! ( * path1, * table. get_path( inode1) . unwrap( ) ) ;
333+ table. lookup ( inode1) ;
334+
335+ // add() a second path and verify that get by path and inode work.
336+ let inode2 = table. add ( path2. clone ( ) ) . 0 ;
337+ assert_eq ! ( * path2, * table. get_path( inode2) . unwrap( ) ) ;
338+ assert_eq ! ( inode2, table. add_or_get( path2) . 0 ) ;
339+ table. lookup ( inode2) ;
340+
341+ // Check the ref counts by doing a single forget.
342+ assert_eq ! ( 0 , table. forget( inode1, 1 ) ) ;
343+ assert_eq ! ( 1 , table. forget( inode2, 1 ) ) ;
344+ }
305345
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- }
346+ #[ test]
347+ fn test_inode_rename ( ) {
348+ let mut table = InodeTable :: new ( ) ;
349+ let path1 = Arc :: new ( PathBuf :: from ( "/foo/a" ) ) ;
350+ let path2 = Arc :: new ( PathBuf :: from ( "/foo/b" ) ) ;
351+
352+ // Add a path; verify that get by path and inode work.
353+ let inode = table. add ( path1. clone ( ) ) . 0 ;
354+ assert_eq ! ( * path1, * table. get_path( inode) . unwrap( ) ) ;
355+ assert_eq ! ( inode, table. get_inode( & path1) . unwrap( ) ) ;
356+
357+ // Rename the inode; verify that get by the new path works and old path doesn't, and get by
358+ // inode still works.
359+ table. rename ( & path1, path2. clone ( ) ) ;
360+ assert ! ( table. get_inode( & path1) . is_none( ) ) ;
361+ assert_eq ! ( inode, table. get_inode( & path2) . unwrap( ) ) ;
362+ assert_eq ! ( * path2, * table. get_path( inode) . unwrap( ) ) ;
363+ }
364+
365+ #[ test]
366+ fn test_unlink ( ) {
367+ let mut table = InodeTable :: new ( ) ;
368+ let path = Arc :: new ( PathBuf :: from ( "/foo/bar" ) ) ;
324369
325- #[ test]
326- fn test_unlink ( ) {
327- let mut table = InodeTable :: new ( ) ;
328- let path = Arc :: new ( PathBuf :: from ( "/foo/bar" ) ) ;
370+ // Add a path.
371+ let inode = table. add ( path. clone ( ) ) . 0 ;
329372
330- // Add a path.
331- let inode = table. add ( path. clone ( ) ) . 0 ;
373+ // Unlink it and verify that get by path fails.
374+ table. unlink ( & path) ;
375+ assert ! ( table. get_inode( & path) . is_none( ) ) ;
332376
333- // Unlink it and verify that get by path fails.
334- table. unlink ( & path) ;
335- assert ! ( table. get_inode( & path) . is_none( ) ) ;
377+ // Getting the path for the inode should still return the path.
378+ assert_eq ! ( * path, * table. get_path( inode) . unwrap( ) ) ;
336379
337- // Getting the path for the inode should still return the path.
338- assert_eq ! ( * path, * table. get_path( inode) . unwrap( ) ) ;
380+ // Verify that forgetting it once drops the refcount to zero and then lookups by inode fail.
381+ assert_eq ! ( 0 , table. forget( inode, 1 ) ) ;
382+ assert ! ( table. get_path( inode) . is_none( ) ) ;
383+ }
339384
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( ) ) ;
385+ #[ test]
386+ fn test_rename_directory ( ) {
387+ let mut table = InodeTable :: new ( ) ;
388+ let a = table. add ( Arc :: new ( PathBuf :: from ( "/a_file" ) ) ) ;
389+ let d = table. add ( Arc :: new ( PathBuf :: from ( "/directory" ) ) ) ;
390+ let x = table. add ( Arc :: new ( PathBuf :: from ( "/directory.x" ) ) ) ; // '.' sorts before '/' naïvely!
391+ let d_f1 = table. add ( Arc :: new ( PathBuf :: from ( "/directory/file1" ) ) ) ;
392+ let d_f2 = table. add ( Arc :: new ( PathBuf :: from ( "/directory/file2" ) ) ) ;
393+ let z = table. add ( Arc :: new ( PathBuf :: from ( "/z_file" ) ) ) ;
394+
395+ table. rename ( Path :: new ( "/a_file" ) , Arc :: new ( PathBuf :: from ( "/a_file_renamed" ) ) ) ;
396+ assert_eq ! ( table. get_inode( Path :: new( "/a_file" ) ) , None ) ;
397+ assert_eq ! ( table. get_inode( Path :: new( "/a_file_renamed" ) ) , Some ( a. 0 ) ) ;
398+
399+ table. rename ( Path :: new ( "/directory" ) , Arc :: new ( PathBuf :: from ( "/new_directory" ) ) ) ;
400+
401+ // paths which should be unaffected
402+ assert_eq ! ( table. get_inode( Path :: new( "/a_file_renamed" ) ) , Some ( a. 0 ) ) ;
403+ assert_eq ! ( table. get_inode( Path :: new( "/z_file" ) ) , Some ( z. 0 ) ) ;
404+ assert_eq ! ( table. get_inode( Path :: new( "/directory.x" ) ) , Some ( x. 0 ) ) ;
405+
406+ // paths which should have been renamed and return the same inode as original
407+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory" ) ) , Some ( d. 0 ) ) ;
408+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory/file1" ) ) , Some ( d_f1. 0 ) ) ;
409+ assert_eq ! ( table. get_inode( Path :: new( "/new_directory/file2" ) ) , Some ( d_f2. 0 ) ) ;
410+
411+ // paths which should no longer exist
412+ assert_eq ! ( table. get_inode( Path :: new( "/directory" ) ) , None ) ;
413+ assert_eq ! ( table. get_inode( Path :: new( "/directory/file1" ) ) , None ) ;
414+ assert_eq ! ( table. get_inode( Path :: new( "/directory/file2" ) ) , None ) ;
415+ }
343416}
0 commit comments