22from __future__ import annotations
33
44import io
5+ import os
6+ import errno
57import warnings
68
79from ctypes import LittleEndianStructure
1214
1315from typing import cast
1416from typing import final
17+ from typing import Any
1518from typing import TYPE_CHECKING
1619
20+ from cachetools import cachedmethod
21+ from cachetools import LRUCache
22+
1723from ._compat import override
1824from ._compat import ReadableStream
1925from ._compat import assert_cast
@@ -437,9 +443,11 @@ def readlink(self):
437443class Directory (Inode ):
438444 def __init__ (self , volume : "Volume" , offset : int , i_no : int ):
439445 super ().__init__ (volume , offset , i_no )
446+ self ._inode_at_cache : LRUCache [str | bytes , Inode ] = LRUCache (maxsize = 32 )
440447 self ._dirents : None | list [DirectoryEntry | DirectoryEntry2 ] = None
448+ self .htree : DXRoot | None = None
441449 if self .is_htree :
442- self .htree : DXRoot | None = DXRoot (self )
450+ self .htree = DXRoot (self )
443451
444452 @override
445453 def verify (self ):
@@ -471,7 +479,9 @@ def is_encrypted(self) -> bool:
471479 def hash_in_dirent (self ) -> bool :
472480 return self .is_casefolded and self .is_encrypted
473481
474- def _opendir (self ):
482+ def _opendir (
483+ self ,
484+ ) -> Generator [DirectoryEntry | DirectoryEntry2 , None , None ]:
475485 if self ._dirents is not None :
476486 for dirent in self ._dirents :
477487 yield dirent
@@ -554,7 +564,9 @@ def _get_file_type(self, dirent: DirectoryEntry | DirectoryEntry2) -> EXT4_FT:
554564 f"Unexpected file type { file_type } for inode { dirent_inode } "
555565 )
556566
557- def opendir (self ):
567+ def opendir (
568+ self ,
569+ ) -> Generator [tuple [DirectoryEntry | DirectoryEntry2 , EXT4_FT ], Any , None ]: # pyright: ignore[reportExplicitAny]
558570 for dirent in self ._opendir ():
559571 if isinstance (dirent , DirectoryEntry2 ):
560572 file_type = assert_cast (dirent .file_type , EXT4_FT ) # pyright: ignore[reportAny]
@@ -568,3 +580,37 @@ def opendir(self):
568580 file_type = self ._get_file_type (dirent )
569581
570582 yield dirent , file_type
583+
584+ @cachedmethod (lambda self : self ._inode_at_cache ) # pyright: ignore[reportAny]
585+ def inode_at (self , path : str | bytes ) -> Inode :
586+ if (isinstance (path , str ) and path .startswith ("/" )) or (
587+ isinstance (path , bytes ) and path .startswith (b"/" )
588+ ):
589+ return self .volume .inode_at (path )
590+
591+ if isinstance (path , bytes ):
592+ path = path .decode ("utf-8" )
593+
594+ paths = list (self .volume .path_tuple (f"/{ path } " ))
595+ cwd = self
596+ if not paths :
597+ return cwd
598+
599+ while paths :
600+ if not isinstance (cwd , Directory ):
601+ raise OSError (errno .ENOTDIR , os .strerror (errno .ENOTDIR ))
602+
603+ name = paths .pop (0 )
604+ inode = None
605+ for dirent , _ in cwd .opendir ():
606+ if dirent .name_bytes == name :
607+ dirent_inode = assert_cast (dirent .inode , int ) # pyright: ignore[reportAny]
608+ inode = self .volume .inodes [dirent_inode ]
609+ break
610+
611+ if inode is None :
612+ raise FileNotFoundError (path )
613+
614+ cwd = inode
615+
616+ return cwd
0 commit comments