@@ -1393,8 +1393,14 @@ impl FileSystemPath {
1393
1393
self . fs ( ) . metadata ( self . clone ( ) )
1394
1394
}
1395
1395
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
+ }
1398
1404
}
1399
1405
1400
1406
pub fn rebase (
@@ -1453,15 +1459,37 @@ impl ValueToString for FileSystemPath {
1453
1459
#[ derive( Clone , Debug ) ]
1454
1460
#[ turbo_tasks:: value( shared) ]
1455
1461
pub struct RealPathResult {
1456
- pub path : FileSystemPath ,
1462
+ pub path_or_error : Result < FileSystemPath , RealPathResultError > ,
1457
1463
pub symlinks : Vec < FileSystemPath > ,
1458
1464
}
1459
1465
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
+ }
1465
1493
}
1466
1494
}
1467
1495
@@ -1628,7 +1656,9 @@ pub enum LinkContent {
1628
1656
// link because there is only **dist** path in `fn write_link`, and we need the raw path if
1629
1657
// we want to restore the link value in `fn write_link`
1630
1658
Link { target : RcStr , link_type : LinkType } ,
1659
+ // Invalid means the link is invalid it points out of the filesystem root
1631
1660
Invalid ,
1661
+ // The target was not found
1632
1662
NotFound ,
1633
1663
}
1634
1664
@@ -2081,8 +2111,8 @@ pub enum RawDirectoryEntry {
2081
2111
File ,
2082
2112
Directory ,
2083
2113
Symlink ,
2114
+ // Other just means 'not a file, directory, or symlink'
2084
2115
Other ,
2085
- Error ,
2086
2116
}
2087
2117
2088
2118
#[ derive( Hash , Clone , Debug , PartialEq , Eq , TraceRawVcs , Serialize , Deserialize , NonLocalValue ) ]
@@ -2091,7 +2121,7 @@ pub enum DirectoryEntry {
2091
2121
Directory ( FileSystemPath ) ,
2092
2122
Symlink ( FileSystemPath ) ,
2093
2123
Other ( FileSystemPath ) ,
2094
- Error ,
2124
+ Error ( RcStr ) ,
2095
2125
}
2096
2126
2097
2127
impl DirectoryEntry {
@@ -2100,12 +2130,28 @@ impl DirectoryEntry {
2100
2130
/// `DirectoryEntry::Directory`.
2101
2131
pub async fn resolve_symlink ( self ) -> Result < Self > {
2102
2132
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
+ } )
2109
2155
} else {
2110
2156
Ok ( self )
2111
2157
}
@@ -2117,7 +2163,7 @@ impl DirectoryEntry {
2117
2163
| DirectoryEntry :: Directory ( path)
2118
2164
| DirectoryEntry :: Symlink ( path)
2119
2165
| DirectoryEntry :: Other ( path) => Some ( path) ,
2120
- DirectoryEntry :: Error => None ,
2166
+ DirectoryEntry :: Error ( _ ) => None ,
2121
2167
}
2122
2168
}
2123
2169
}
@@ -2129,6 +2175,7 @@ pub enum FileSystemEntryType {
2129
2175
File ,
2130
2176
Directory ,
2131
2177
Symlink ,
2178
+ /// These would be things like named pipes, sockets, etc.
2132
2179
Other ,
2133
2180
Error ,
2134
2181
}
@@ -2157,7 +2204,7 @@ impl From<&DirectoryEntry> for FileSystemEntryType {
2157
2204
DirectoryEntry :: Directory ( _) => FileSystemEntryType :: Directory ,
2158
2205
DirectoryEntry :: Symlink ( _) => FileSystemEntryType :: Symlink ,
2159
2206
DirectoryEntry :: Other ( _) => FileSystemEntryType :: Other ,
2160
- DirectoryEntry :: Error => FileSystemEntryType :: Error ,
2207
+ DirectoryEntry :: Error ( _ ) => FileSystemEntryType :: Error ,
2161
2208
}
2162
2209
}
2163
2210
}
@@ -2175,7 +2222,6 @@ impl From<&RawDirectoryEntry> for FileSystemEntryType {
2175
2222
RawDirectoryEntry :: Directory => FileSystemEntryType :: Directory ,
2176
2223
RawDirectoryEntry :: Symlink => FileSystemEntryType :: Symlink ,
2177
2224
RawDirectoryEntry :: Other => FileSystemEntryType :: Other ,
2178
- RawDirectoryEntry :: Error => FileSystemEntryType :: Error ,
2179
2225
}
2180
2226
}
2181
2227
}
@@ -2300,7 +2346,6 @@ async fn read_dir(path: FileSystemPath) -> Result<Vc<DirectoryContent>> {
2300
2346
RawDirectoryEntry :: Directory => DirectoryEntry :: Directory ( entry_path) ,
2301
2347
RawDirectoryEntry :: Symlink => DirectoryEntry :: Symlink ( entry_path) ,
2302
2348
RawDirectoryEntry :: Other => DirectoryEntry :: Other ( entry_path) ,
2303
- RawDirectoryEntry :: Error => DirectoryEntry :: Error ,
2304
2349
} ;
2305
2350
normalized_entries. insert ( name. clone ( ) , entry) ;
2306
2351
}
@@ -2331,64 +2376,79 @@ async fn get_type(path: FileSystemPath) -> Result<Vc<FileSystemEntryType>> {
2331
2376
2332
2377
#[ turbo_tasks:: function]
2333
2378
async fn realpath_with_links ( path : FileSystemPath ) -> Result < Vc < RealPathResult > > {
2334
- let mut current_vc = path. clone ( ) ;
2379
+ let mut current_path = path;
2335
2380
let mut symlinks: IndexSet < FileSystemPath > = IndexSet :: new ( ) ;
2336
2381
let mut visited: AutoSet < RcStr > = AutoSet :: new ( ) ;
2382
+ let mut error = RealPathResultError :: TooManySymlinks ;
2337
2383
// Pick some arbitrary symlink depth limit... similar to the ELOOP logic for realpath(3).
2338
2384
// SYMLOOP_MAX is 40 for Linux: https://unix.stackexchange.com/q/721724
2339
2385
for _i in 0 ..40 {
2340
- let current = current_vc. clone ( ) ;
2341
- if current. is_root ( ) {
2386
+ if current_path. is_root ( ) {
2342
2387
// fast path
2343
2388
return Ok ( RealPathResult {
2344
- path : current_vc ,
2389
+ path_or_error : Ok ( current_path ) ,
2345
2390
symlinks : symlinks. into_iter ( ) . collect ( ) ,
2346
2391
}
2347
2392
. cell ( ) ) ;
2348
2393
}
2349
2394
2350
- if !visited. insert ( current. path . clone ( ) ) {
2395
+ if !visited. insert ( current_path. path . clone ( ) ) {
2396
+ error = RealPathResultError :: CycleDetected ;
2351
2397
break ; // we detected a cycle
2352
2398
}
2353
2399
2354
2400
// 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 ( ) ;
2356
2402
let parent_result = parent. realpath_with_links ( ) . owned ( ) . await ?;
2357
- let basename = current
2403
+ let basename = current_path
2358
2404
. path
2359
2405
. 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) ;
2364
2407
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
+ } ;
2365
2420
2366
2421
// use `get_type` before trying `read_link`, as there's a good chance of a cache hit on
2367
2422
// `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
+ ) {
2369
2427
return Ok ( RealPathResult {
2370
- path : current_vc ,
2428
+ path_or_error : Ok ( current_path ) ,
2371
2429
symlinks : symlinks. into_iter ( ) . collect ( ) , // convert set to vec
2372
2430
}
2373
2431
. cell ( ) ) ;
2374
2432
}
2375
2433
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) ?;
2382
2443
}
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 ;
2390
2451
}
2391
- . cell ( ) ) ;
2392
2452
}
2393
2453
}
2394
2454
@@ -2400,7 +2460,7 @@ async fn realpath_with_links(path: FileSystemPath) -> Result<Vc<RealPathResult>>
2400
2460
// Returning the followed symlinks is still important, even if there is an error! Otherwise
2401
2461
// we may never notice if the symlink loop is fixed.
2402
2462
Ok ( RealPathResult {
2403
- path ,
2463
+ path_or_error : Err ( error ) ,
2404
2464
symlinks : symlinks. into_iter ( ) . collect ( ) ,
2405
2465
}
2406
2466
. cell ( ) )
0 commit comments