@@ -1393,8 +1393,14 @@ impl FileSystemPath {
13931393 self . fs ( ) . metadata ( self . clone ( ) )
13941394 }
13951395
1396- pub fn realpath ( & self ) -> Vc < FileSystemPath > {
1397- self . realpath_with_links ( ) . path ( )
1396+ // Returns the realpath to the file, resolving all symlinks and reporting an error if the path
1397+ // is invalid.
1398+ pub async fn realpath ( & self ) -> Result < FileSystemPath > {
1399+ let result = & ( * self . realpath_with_links ( ) . await ?) ;
1400+ match & result. path_or_error {
1401+ Ok ( path) => Ok ( path. clone ( ) ) ,
1402+ Err ( error) => Err ( anyhow:: anyhow!( error. as_error_message( self , result) ) ) ,
1403+ }
13981404 }
13991405
14001406 pub fn rebase (
@@ -1453,15 +1459,37 @@ impl ValueToString for FileSystemPath {
14531459#[ derive( Clone , Debug ) ]
14541460#[ turbo_tasks:: value( shared) ]
14551461pub struct RealPathResult {
1456- pub path : FileSystemPath ,
1462+ pub path_or_error : Result < FileSystemPath , RealPathResultError > ,
14571463 pub symlinks : Vec < FileSystemPath > ,
14581464}
14591465
1460- #[ turbo_tasks:: value_impl]
1461- impl RealPathResult {
1462- #[ turbo_tasks:: function]
1463- pub fn path ( & self ) -> Vc < FileSystemPath > {
1464- self . path . clone ( ) . cell ( )
1466+ /// Errors that can occur when resolving a path with symlinks.
1467+ /// Many of these can be transient conditions that might happen when package managers are running.
1468+ #[ derive( Debug , Clone , Hash , Eq , PartialEq , Serialize , Deserialize , NonLocalValue , TraceRawVcs ) ]
1469+ pub enum RealPathResultError {
1470+ TooManySymlinks ,
1471+ CycleDetected ,
1472+ Invalid ,
1473+ NotFound ,
1474+ }
1475+ impl RealPathResultError {
1476+ /// Formats the error message
1477+ pub fn as_error_message ( & self , orig : & FileSystemPath , result : & RealPathResult ) -> String {
1478+ match self {
1479+ RealPathResultError :: TooManySymlinks => format ! (
1480+ "Symlink {orig} leads to too many other symlinks ({len} links)" ,
1481+ len = result. symlinks. len( )
1482+ ) ,
1483+ RealPathResultError :: CycleDetected => {
1484+ format ! ( "Symlink {orig} is in a symlink loop: {:?}" , result. symlinks)
1485+ }
1486+ RealPathResultError :: Invalid => {
1487+ format ! ( "Symlink {orig} is invalid, it points out of the filesystem root" )
1488+ }
1489+ RealPathResultError :: NotFound => {
1490+ format ! ( "Symlink {orig} is invalid, it points at a file that doesn't exist" )
1491+ }
1492+ }
14651493 }
14661494}
14671495
@@ -1628,7 +1656,9 @@ pub enum LinkContent {
16281656 // link because there is only **dist** path in `fn write_link`, and we need the raw path if
16291657 // we want to restore the link value in `fn write_link`
16301658 Link { target : RcStr , link_type : LinkType } ,
1659+ // Invalid means the link is invalid it points out of the filesystem root
16311660 Invalid ,
1661+ // The target was not found
16321662 NotFound ,
16331663}
16341664
@@ -2081,8 +2111,8 @@ pub enum RawDirectoryEntry {
20812111 File ,
20822112 Directory ,
20832113 Symlink ,
2114+ // Other just means 'not a file, directory, or symlink'
20842115 Other ,
2085- Error ,
20862116}
20872117
20882118#[ derive( Hash , Clone , Debug , PartialEq , Eq , TraceRawVcs , Serialize , Deserialize , NonLocalValue ) ]
@@ -2091,7 +2121,7 @@ pub enum DirectoryEntry {
20912121 Directory ( FileSystemPath ) ,
20922122 Symlink ( FileSystemPath ) ,
20932123 Other ( FileSystemPath ) ,
2094- Error ,
2124+ Error ( RcStr ) ,
20952125}
20962126
20972127impl DirectoryEntry {
@@ -2100,12 +2130,28 @@ impl DirectoryEntry {
21002130 /// `DirectoryEntry::Directory`.
21012131 pub async fn resolve_symlink ( self ) -> Result < Self > {
21022132 if let DirectoryEntry :: Symlink ( symlink) = & self {
2103- let real_path = symlink. realpath ( ) . owned ( ) . await ?;
2104- match * real_path. get_type ( ) . await ? {
2105- FileSystemEntryType :: Directory => Ok ( DirectoryEntry :: Directory ( real_path) ) ,
2106- FileSystemEntryType :: File => Ok ( DirectoryEntry :: File ( real_path) ) ,
2107- _ => Ok ( self ) ,
2108- }
2133+ let result = & * symlink. realpath_with_links ( ) . await ?;
2134+ let real_path = match & result. path_or_error {
2135+ Ok ( path) => path,
2136+ Err ( error) => {
2137+ return Ok ( DirectoryEntry :: Error (
2138+ error. as_error_message ( symlink, result) . into ( ) ,
2139+ ) ) ;
2140+ }
2141+ } ;
2142+ Ok ( match * real_path. get_type ( ) . await ? {
2143+ FileSystemEntryType :: Directory => DirectoryEntry :: Directory ( real_path. clone ( ) ) ,
2144+ FileSystemEntryType :: File => DirectoryEntry :: File ( real_path. clone ( ) ) ,
2145+ // Happens if the link is to a non-existent file
2146+ FileSystemEntryType :: NotFound => DirectoryEntry :: Error (
2147+ format ! ( "Symlink {symlink} points at {real_path} which does not exist" ) . into ( ) ,
2148+ ) ,
2149+ FileSystemEntryType :: Symlink => bail ! (
2150+ "Symlink {symlink} points at a symlink but realpath_with_links returned a \
2151+ path, this is caused by eventual consistency."
2152+ ) ,
2153+ _ => self ,
2154+ } )
21092155 } else {
21102156 Ok ( self )
21112157 }
@@ -2117,7 +2163,7 @@ impl DirectoryEntry {
21172163 | DirectoryEntry :: Directory ( path)
21182164 | DirectoryEntry :: Symlink ( path)
21192165 | DirectoryEntry :: Other ( path) => Some ( path) ,
2120- DirectoryEntry :: Error => None ,
2166+ DirectoryEntry :: Error ( _ ) => None ,
21212167 }
21222168 }
21232169}
@@ -2129,6 +2175,7 @@ pub enum FileSystemEntryType {
21292175 File ,
21302176 Directory ,
21312177 Symlink ,
2178+ /// These would be things like named pipes, sockets, etc.
21322179 Other ,
21332180 Error ,
21342181}
@@ -2157,7 +2204,7 @@ impl From<&DirectoryEntry> for FileSystemEntryType {
21572204 DirectoryEntry :: Directory ( _) => FileSystemEntryType :: Directory ,
21582205 DirectoryEntry :: Symlink ( _) => FileSystemEntryType :: Symlink ,
21592206 DirectoryEntry :: Other ( _) => FileSystemEntryType :: Other ,
2160- DirectoryEntry :: Error => FileSystemEntryType :: Error ,
2207+ DirectoryEntry :: Error ( _ ) => FileSystemEntryType :: Error ,
21612208 }
21622209 }
21632210}
@@ -2175,7 +2222,6 @@ impl From<&RawDirectoryEntry> for FileSystemEntryType {
21752222 RawDirectoryEntry :: Directory => FileSystemEntryType :: Directory ,
21762223 RawDirectoryEntry :: Symlink => FileSystemEntryType :: Symlink ,
21772224 RawDirectoryEntry :: Other => FileSystemEntryType :: Other ,
2178- RawDirectoryEntry :: Error => FileSystemEntryType :: Error ,
21792225 }
21802226 }
21812227}
@@ -2300,7 +2346,6 @@ async fn read_dir(path: FileSystemPath) -> Result<Vc<DirectoryContent>> {
23002346 RawDirectoryEntry :: Directory => DirectoryEntry :: Directory ( entry_path) ,
23012347 RawDirectoryEntry :: Symlink => DirectoryEntry :: Symlink ( entry_path) ,
23022348 RawDirectoryEntry :: Other => DirectoryEntry :: Other ( entry_path) ,
2303- RawDirectoryEntry :: Error => DirectoryEntry :: Error ,
23042349 } ;
23052350 normalized_entries. insert ( name. clone ( ) , entry) ;
23062351 }
@@ -2331,64 +2376,79 @@ async fn get_type(path: FileSystemPath) -> Result<Vc<FileSystemEntryType>> {
23312376
23322377#[ turbo_tasks:: function]
23332378async fn realpath_with_links ( path : FileSystemPath ) -> Result < Vc < RealPathResult > > {
2334- let mut current_vc = path. clone ( ) ;
2379+ let mut current_path = path;
23352380 let mut symlinks: IndexSet < FileSystemPath > = IndexSet :: new ( ) ;
23362381 let mut visited: AutoSet < RcStr > = AutoSet :: new ( ) ;
2382+ let mut error = RealPathResultError :: TooManySymlinks ;
23372383 // Pick some arbitrary symlink depth limit... similar to the ELOOP logic for realpath(3).
23382384 // SYMLOOP_MAX is 40 for Linux: https://unix.stackexchange.com/q/721724
23392385 for _i in 0 ..40 {
2340- let current = current_vc. clone ( ) ;
2341- if current. is_root ( ) {
2386+ if current_path. is_root ( ) {
23422387 // fast path
23432388 return Ok ( RealPathResult {
2344- path : current_vc ,
2389+ path_or_error : Ok ( current_path ) ,
23452390 symlinks : symlinks. into_iter ( ) . collect ( ) ,
23462391 }
23472392 . cell ( ) ) ;
23482393 }
23492394
2350- if !visited. insert ( current. path . clone ( ) ) {
2395+ if !visited. insert ( current_path. path . clone ( ) ) {
2396+ error = RealPathResultError :: CycleDetected ;
23512397 break ; // we detected a cycle
23522398 }
23532399
23542400 // see if a parent segment of the path is a symlink and resolve that first
2355- let parent = current_vc . parent ( ) ;
2401+ let parent = current_path . parent ( ) ;
23562402 let parent_result = parent. realpath_with_links ( ) . owned ( ) . await ?;
2357- let basename = current
2403+ let basename = current_path
23582404 . path
23592405 . rsplit_once ( '/' )
2360- . map_or ( current. path . as_str ( ) , |( _, name) | name) ;
2361- if parent_result. path != parent {
2362- current_vc = parent_result. path . join ( basename) ?;
2363- }
2406+ . map_or ( current_path. path . as_str ( ) , |( _, name) | name) ;
23642407 symlinks. extend ( parent_result. symlinks ) ;
2408+ let parent_path = match parent_result. path_or_error {
2409+ Ok ( path) => {
2410+ if path != parent {
2411+ current_path = path. join ( basename) ?;
2412+ }
2413+ path
2414+ }
2415+ Err ( parent_error) => {
2416+ error = parent_error;
2417+ break ;
2418+ }
2419+ } ;
23652420
23662421 // use `get_type` before trying `read_link`, as there's a good chance of a cache hit on
23672422 // `get_type`, and `read_link` isn't the common codepath.
2368- if !matches ! ( * current_vc. get_type( ) . await ?, FileSystemEntryType :: Symlink ) {
2423+ if !matches ! (
2424+ * current_path. get_type( ) . await ?,
2425+ FileSystemEntryType :: Symlink
2426+ ) {
23692427 return Ok ( RealPathResult {
2370- path : current_vc ,
2428+ path_or_error : Ok ( current_path ) ,
23712429 symlinks : symlinks. into_iter ( ) . collect ( ) , // convert set to vec
23722430 }
23732431 . cell ( ) ) ;
23742432 }
23752433
2376- if let LinkContent :: Link { target, link_type } = & * current_vc. read_link ( ) . await ? {
2377- symlinks. insert ( current_vc. clone ( ) ) ;
2378- current_vc = if link_type. contains ( LinkType :: ABSOLUTE ) {
2379- current_vc. root ( ) . owned ( ) . await ?
2380- } else {
2381- parent_result. path
2434+ match & * current_path. read_link ( ) . await ? {
2435+ LinkContent :: Link { target, link_type } => {
2436+ symlinks. insert ( current_path. clone ( ) ) ;
2437+ current_path = if link_type. contains ( LinkType :: ABSOLUTE ) {
2438+ current_path. root ( ) . owned ( ) . await ?
2439+ } else {
2440+ parent_path
2441+ }
2442+ . join ( target) ?;
23822443 }
2383- . join ( target ) ? ;
2384- } else {
2385- // get_type() and read_link() might disagree temporarily due to turbo-tasks
2386- // eventual consistency or if the file gets invalidated before the directory does
2387- return Ok ( RealPathResult {
2388- path : current_vc ,
2389- symlinks : symlinks . into_iter ( ) . collect ( ) , // convert set to vec
2444+ LinkContent :: NotFound => {
2445+ error = RealPathResultError :: NotFound ;
2446+ break ;
2447+ }
2448+ LinkContent :: Invalid => {
2449+ error = RealPathResultError :: Invalid ;
2450+ break ;
23902451 }
2391- . cell ( ) ) ;
23922452 }
23932453 }
23942454
@@ -2400,7 +2460,7 @@ async fn realpath_with_links(path: FileSystemPath) -> Result<Vc<RealPathResult>>
24002460 // Returning the followed symlinks is still important, even if there is an error! Otherwise
24012461 // we may never notice if the symlink loop is fixed.
24022462 Ok ( RealPathResult {
2403- path ,
2463+ path_or_error : Err ( error ) ,
24042464 symlinks : symlinks. into_iter ( ) . collect ( ) ,
24052465 }
24062466 . cell ( ) )
0 commit comments