33
44using System . Diagnostics ;
55using System . Runtime . InteropServices ;
6+ using Microsoft . Win32 . SafeHandles ;
67
78namespace System . IO
89{
@@ -180,8 +181,18 @@ internal bool IsSymbolicLink(ReadOnlySpan<char> path, bool continueOnError = fal
180181 }
181182
182183 internal FileAttributes GetAttributes ( ReadOnlySpan < char > path , ReadOnlySpan < char > fileName , bool continueOnError = false )
184+ => GetAttributes ( handle : null , path , fileName , continueOnError ) ;
185+
186+ internal FileAttributes GetAttributes ( SafeFileHandle handle , bool continueOnError = false )
183187 {
184- EnsureCachesInitialized ( path , continueOnError ) ;
188+ ReadOnlySpan < char > path = handle . Path ;
189+ ReadOnlySpan < char > fileName = Path . GetFileName ( path ) ;
190+ return GetAttributes ( handle , path , fileName , continueOnError ) ;
191+ }
192+
193+ private FileAttributes GetAttributes ( SafeFileHandle ? handle , ReadOnlySpan < char > path , ReadOnlySpan < char > fileName , bool continueOnError = false )
194+ {
195+ EnsureCachesInitialized ( handle , path , continueOnError ) ;
185196
186197 if ( ! EntryExists )
187198 return ( FileAttributes ) ( - 1 ) ;
@@ -204,6 +215,12 @@ internal FileAttributes GetAttributes(ReadOnlySpan<char> path, ReadOnlySpan<char
204215 }
205216
206217 internal void SetAttributes ( string path , FileAttributes attributes , bool asDirectory )
218+ => SetAttributes ( handle : null , path , attributes , asDirectory ) ;
219+
220+ internal void SetAttributes ( SafeFileHandle handle , FileAttributes attributes , bool asDirectory )
221+ => SetAttributes ( handle , GetHandlePath ( handle ) , attributes , asDirectory ) ;
222+
223+ private void SetAttributes ( SafeFileHandle ? handle , string path , FileAttributes attributes , bool asDirectory )
207224 {
208225 // Validate that only flags from the attribute are being provided. This is an
209226 // approximation for the validation done by the Win32 function.
@@ -220,22 +237,21 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec
220237 throw new ArgumentException ( SR . Arg_InvalidFileAttrs , "Attributes" ) ;
221238 }
222239
223- EnsureCachesInitialized ( path ) ;
240+ EnsureCachesInitialized ( handle , path ) ;
224241
225242 if ( ! EntryExists )
226243 FileSystemInfo . ThrowNotFound ( path ) ;
227244
228245 if ( Interop . Sys . CanSetHiddenFlag )
229246 {
230- if ( ( attributes & FileAttributes . Hidden ) != 0 && ( _fileCache . UserFlags & ( uint ) Interop . Sys . UserFlags . UF_HIDDEN ) == 0 )
231- {
232- // If Hidden flag is set and cached file status does not have the flag set then set it
233- Interop . CheckIo ( Interop . Sys . LChflags ( path , ( _fileCache . UserFlags | ( uint ) Interop . Sys . UserFlags . UF_HIDDEN ) ) , path , asDirectory ) ;
234- }
235- else if ( HasHiddenFlag )
247+ bool hidden = ( attributes & FileAttributes . Hidden ) != 0 ;
248+ if ( hidden ^ HasHiddenFlag )
236249 {
237- // If Hidden flag is not set and cached file status does have the flag set then remove it
238- Interop . CheckIo ( Interop . Sys . LChflags ( path , ( _fileCache . UserFlags & ~ ( uint ) Interop . Sys . UserFlags . UF_HIDDEN ) ) , path , asDirectory ) ;
250+ uint flags = hidden ? _fileCache . UserFlags | ( uint ) Interop . Sys . UserFlags . UF_HIDDEN :
251+ _fileCache . UserFlags & ~ ( uint ) Interop . Sys . UserFlags . UF_HIDDEN ;
252+ int rv = handle is not null ? Interop . Sys . FChflags ( handle , flags ) :
253+ Interop . Sys . LChflags ( path ! , flags ) ;
254+ Interop . CheckIo ( rv , path , asDirectory ) ;
239255 }
240256 }
241257
@@ -256,7 +272,9 @@ internal void SetAttributes(string path, FileAttributes attributes, bool asDirec
256272 // Change the permissions on the file
257273 if ( newMode != _fileCache . Mode )
258274 {
259- Interop . CheckIo ( Interop . Sys . ChMod ( path , newMode ) , path , asDirectory ) ;
275+ int rv = handle is not null ? Interop . Sys . FChMod ( handle , newMode ) :
276+ Interop . Sys . ChMod ( path ! , newMode ) ;
277+ Interop . CheckIo ( rv , path , asDirectory ) ;
260278 }
261279
262280 InvalidateCaches ( ) ;
@@ -269,8 +287,14 @@ internal bool GetExists(ReadOnlySpan<char> path, bool asDirectory)
269287 }
270288
271289 internal DateTimeOffset GetCreationTime ( ReadOnlySpan < char > path , bool continueOnError = false )
290+ => GetCreationTime ( handle : null , path , continueOnError ) ;
291+
292+ internal DateTimeOffset GetCreationTime ( SafeFileHandle handle , bool continueOnError = false )
293+ => GetCreationTime ( handle , GetHandlePath ( handle ) , continueOnError ) ;
294+
295+ private DateTimeOffset GetCreationTime ( SafeFileHandle ? handle , ReadOnlySpan < char > path , bool continueOnError = false )
272296 {
273- EnsureCachesInitialized ( path , continueOnError ) ;
297+ EnsureCachesInitialized ( handle , path , continueOnError ) ;
274298
275299 if ( ! EntryExists )
276300 return new DateTimeOffset ( DateTime . FromFileTimeUtc ( 0 ) ) ;
@@ -287,8 +311,14 @@ internal DateTimeOffset GetCreationTime(ReadOnlySpan<char> path, bool continueOn
287311 }
288312
289313 internal DateTimeOffset GetLastAccessTime ( ReadOnlySpan < char > path , bool continueOnError = false )
314+ => GetLastAccessTime ( handle : null , path , continueOnError ) ;
315+
316+ internal DateTimeOffset GetLastAccessTime ( SafeFileHandle handle , bool continueOnError = false )
317+ => GetLastAccessTime ( handle , handle . Path , continueOnError ) ;
318+
319+ private DateTimeOffset GetLastAccessTime ( SafeFileHandle ? handle , ReadOnlySpan < char > path , bool continueOnError = false )
290320 {
291- EnsureCachesInitialized ( path , continueOnError ) ;
321+ EnsureCachesInitialized ( handle , path , continueOnError ) ;
292322
293323 if ( ! EntryExists )
294324 return new DateTimeOffset ( DateTime . FromFileTimeUtc ( 0 ) ) ;
@@ -297,11 +327,23 @@ internal DateTimeOffset GetLastAccessTime(ReadOnlySpan<char> path, bool continue
297327 }
298328
299329 internal void SetLastAccessTime ( string path , DateTimeOffset time , bool asDirectory )
300- => SetAccessOrWriteTime ( path , time , isAccessTime : true , asDirectory ) ;
330+ => SetLastAccessTime ( handle : null , path , time , asDirectory ) ;
331+
332+ internal void SetLastAccessTime ( SafeFileHandle handle , DateTimeOffset time , bool asDirectory )
333+ => SetLastAccessTime ( handle , GetHandlePath ( handle ) , time , asDirectory ) ;
334+
335+ private void SetLastAccessTime ( SafeFileHandle ? handle , string path , DateTimeOffset time , bool asDirectory )
336+ => SetAccessOrWriteTime ( handle , path , time , isAccessTime : true , asDirectory ) ;
301337
302338 internal DateTimeOffset GetLastWriteTime ( ReadOnlySpan < char > path , bool continueOnError = false )
339+ => GetLastWriteTime ( handle : null , path , continueOnError ) ;
340+
341+ internal DateTimeOffset GetLastWriteTime ( SafeFileHandle handle , bool continueOnError = false )
342+ => GetLastWriteTime ( handle , handle . Path , continueOnError ) ;
343+
344+ private DateTimeOffset GetLastWriteTime ( SafeFileHandle ? handle , ReadOnlySpan < char > path , bool continueOnError = false )
303345 {
304- EnsureCachesInitialized ( path , continueOnError ) ;
346+ EnsureCachesInitialized ( handle , path , continueOnError ) ;
305347
306348 if ( ! EntryExists )
307349 return new DateTimeOffset ( DateTime . FromFileTimeUtc ( 0 ) ) ;
@@ -310,14 +352,20 @@ internal DateTimeOffset GetLastWriteTime(ReadOnlySpan<char> path, bool continueO
310352 }
311353
312354 internal void SetLastWriteTime ( string path , DateTimeOffset time , bool asDirectory )
313- => SetAccessOrWriteTime ( path , time , isAccessTime : false , asDirectory ) ;
355+ => SetLastWriteTime ( handle : null , path , time , asDirectory ) ;
356+
357+ internal void SetLastWriteTime ( SafeFileHandle handle , DateTimeOffset time , bool asDirectory )
358+ => SetLastWriteTime ( handle , GetHandlePath ( handle ) , time , asDirectory ) ;
359+
360+ internal void SetLastWriteTime ( SafeFileHandle ? handle , string path , DateTimeOffset time , bool asDirectory )
361+ => SetAccessOrWriteTime ( handle , path , time , isAccessTime : false , asDirectory ) ;
314362
315363 private DateTimeOffset UnixTimeToDateTimeOffset ( long seconds , long nanoseconds )
316364 {
317365 return DateTimeOffset . FromUnixTimeSeconds ( seconds ) . AddTicks ( nanoseconds / NanosecondsPerTick ) ;
318366 }
319367
320- private unsafe void SetAccessOrWriteTimeCore ( string path , DateTimeOffset time , bool isAccessTime , bool checkCreationTime , bool asDirectory )
368+ private unsafe void SetAccessOrWriteTimeCore ( SafeFileHandle ? handle , string path , DateTimeOffset time , bool isAccessTime , bool checkCreationTime , bool asDirectory )
321369 {
322370 // This api is used to set creation time on non OSX platforms, and as a fallback for OSX platforms.
323371 // The reason why we use it to set 'creation time' is the below comment:
@@ -333,7 +381,7 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
333381
334382 // force a refresh so that we have an up-to-date times for values not being overwritten
335383 InvalidateCaches ( ) ;
336- EnsureCachesInitialized ( path ) ;
384+ EnsureCachesInitialized ( handle , path ) ;
337385
338386 if ( ! EntryExists )
339387 FileSystemInfo . ThrowNotFound ( path ) ;
@@ -365,7 +413,9 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
365413 buf [ 1 ] . TvNsec = nanoseconds ;
366414 }
367415#endif
368- Interop . CheckIo ( Interop . Sys . UTimensat ( path , buf ) , path , asDirectory ) ;
416+ int rv = handle is not null ? Interop . Sys . FUTimens ( handle , buf ) :
417+ Interop . Sys . UTimensat ( path ! , buf ) ;
418+ Interop . CheckIo ( rv , path , asDirectory ) ;
369419
370420 // On OSX-like platforms, when the modification time is less than the creation time (including
371421 // when the modification time is already less than but access time is being set), the creation
@@ -382,7 +432,7 @@ private unsafe void SetAccessOrWriteTimeCore(string path, DateTimeOffset time, b
382432
383433 if ( updateCreationTime )
384434 {
385- Interop . Error error = SetCreationTimeCore ( path , _fileCache . BirthTime , _fileCache . BirthTimeNsec ) ;
435+ Interop . Error error = SetCreationTimeCore ( handle , path , _fileCache . BirthTime , _fileCache . BirthTimeNsec ) ;
386436 if ( error != Interop . Error . SUCCESS && error != Interop . Error . ENOTSUP )
387437 {
388438 Interop . CheckIo ( error , path , asDirectory ) ;
@@ -399,17 +449,27 @@ internal long GetLength(ReadOnlySpan<char> path, bool continueOnError = false)
399449 return EntryExists ? _fileCache . Size : 0 ;
400450 }
401451
452+ internal void RefreshCaches ( ReadOnlySpan < char > path )
453+ => RefreshCaches ( handle : null , path ) ;
454+
402455 // Tries to refresh the lstat cache (_fileCache).
403456 // This method should not throw. Instead, we store the results, and we will throw when the user attempts to access any of the properties when there was a failure
404- internal void RefreshCaches ( ReadOnlySpan < char > path )
457+ internal void RefreshCaches ( SafeFileHandle ? handle , ReadOnlySpan < char > path )
405458 {
406- path = Path . TrimEndingDirectorySeparator ( path ) ;
407-
408459#if ! TARGET_BROWSER
409460 _isReadOnlyCache = - 1 ;
410461#endif
462+ int rv ;
463+ if ( handle is not null )
464+ {
465+ rv = Interop . Sys . FStat ( handle , out _fileCache ) ;
466+ }
467+ else
468+ {
469+ path = Path . TrimEndingDirectorySeparator ( path ) ;
470+ rv = Interop . Sys . LStat ( path , out _fileCache ) ;
471+ }
411472
412- int rv = Interop . Sys . LStat ( path , out _fileCache ) ;
413473 if ( rv < 0 )
414474 {
415475 Interop . ErrorInfo errorInfo = Interop . Sys . GetLastErrorInfo ( ) ;
@@ -431,20 +491,24 @@ internal void RefreshCaches(ReadOnlySpan<char> path)
431491 // Check if the main path is a directory, or a link to a directory.
432492 int fileType = _fileCache . Mode & Interop . Sys . FileTypes . S_IFMT ;
433493 bool isDirectory = fileType == Interop . Sys . FileTypes . S_IFDIR ||
434- ( fileType == Interop . Sys . FileTypes . S_IFLNK
435- && Interop . Sys . Stat ( path , out Interop . Sys . FileStatus target ) == 0
436- && ( target . Mode & Interop . Sys . FileTypes . S_IFMT ) == Interop . Sys . FileTypes . S_IFDIR ) ;
494+ ( handle is null && // Don't follow links for SafeHandle APIs.
495+ fileType == Interop . Sys . FileTypes . S_IFLNK &&
496+ Interop . Sys . Stat ( path , out Interop . Sys . FileStatus target ) == 0 &&
497+ ( target . Mode & Interop . Sys . FileTypes . S_IFMT ) == Interop . Sys . FileTypes . S_IFDIR ) ;
437498
438499 _state = isDirectory ? InitializedExistsDir : InitializedExistsFile ;
439500 }
440501
502+ internal void EnsureCachesInitialized ( ReadOnlySpan < char > path , bool continueOnError = false )
503+ => EnsureCachesInitialized ( handle : null , path , continueOnError ) ;
504+
441505 // Checks if the file cache is uninitialized and refreshes it's value.
442506 // If it failed, and continueOnError is set to true, this method will throw.
443- internal void EnsureCachesInitialized ( ReadOnlySpan < char > path , bool continueOnError = false )
507+ internal void EnsureCachesInitialized ( SafeFileHandle ? handle , ReadOnlySpan < char > path , bool continueOnError = false )
444508 {
445509 if ( _state == Uninitialized )
446510 {
447- RefreshCaches ( path ) ;
511+ RefreshCaches ( handle , path ) ;
448512 }
449513
450514 if ( ! continueOnError )
@@ -471,5 +535,19 @@ private static long UnixTimeSecondsToNanoseconds(DateTimeOffset time, long secon
471535 const long TicksPerSecond = TicksPerMillisecond * 1000 ;
472536 return ( time . UtcDateTime . Ticks - DateTimeOffset . UnixEpoch . Ticks - seconds * TicksPerSecond ) * NanosecondsPerTick ;
473537 }
538+
539+ private static string GetHandlePath ( SafeFileHandle handle )
540+ {
541+ if ( handle . Path is null )
542+ {
543+ ThrowHandleHasNoPath ( ) ;
544+ }
545+ return handle . Path ! ;
546+
547+ static void ThrowHandleHasNoPath ( )
548+ {
549+ throw new ArgumentException ( SR . Arg_HandleHasNoPath ) ;
550+ }
551+ }
474552 }
475553}
0 commit comments