Skip to content

Commit 3e2e401

Browse files
committed
Merge branch 'safehandle_filestatus' of https://github.com/tmds/runtime into tmds-safehandle_filestatus
2 parents d5bb271 + b2eec67 commit 3e2e401

File tree

10 files changed

+178
-259
lines changed

10 files changed

+178
-259
lines changed

src/libraries/System.Private.CoreLib/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,6 +1053,9 @@
10531053
<data name="Argument_HasToBeArrayClass" xml:space="preserve">
10541054
<value>Must be an array type.</value>
10551055
</data>
1056+
<data name="Arg_HandleHasNoPath" xml:space="preserve">
1057+
<value>The handle has no associated path.</value>
1058+
</data>
10561059
<data name="Argument_IdnBadBidi" xml:space="preserve">
10571060
<value>Left to right characters may not be mixed with right to left characters in IDN labels.</value>
10581061
</data>

src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OSX.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Runtime.InteropServices;
5+
using Microsoft.Win32.SafeHandles;
56

67
namespace System.IO
78
{
89
internal partial struct FileStatus
910
{
1011
internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory)
12+
=> SetCreationTime(handle: null, path, time, asDirectory);
13+
14+
internal void SetCreationTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory)
15+
=> SetCreationTime(handle, handle.Path, time, asDirectory);
16+
17+
private void SetCreationTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool asDirectory)
1118
{
1219
// Try to set the attribute on the file system entry using setattrlist,
1320
// if we get ENOTSUP then it means that "The volume does not support
@@ -19,23 +26,23 @@ internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory
1926
// great care.
2027
long seconds = time.ToUnixTimeSeconds();
2128
long nanoseconds = UnixTimeSecondsToNanoseconds(time, seconds);
22-
Interop.Error error = SetCreationTimeCore(path, seconds, nanoseconds);
29+
Interop.Error error = SetCreationTimeCore(handle, path, seconds, nanoseconds);
2330

2431
if (error == Interop.Error.SUCCESS)
2532
{
2633
InvalidateCaches();
2734
}
2835
else if (error == Interop.Error.ENOTSUP)
2936
{
30-
SetAccessOrWriteTimeCore(path, time, isAccessTime: false, checkCreationTime: false, asDirectory);
37+
SetAccessOrWriteTimeCore(handle, path, time, isAccessTime: false, checkCreationTime: false, asDirectory);
3138
}
3239
else
3340
{
3441
Interop.CheckIo(error, path, asDirectory);
3542
}
3643
}
3744

38-
private unsafe Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds)
45+
private unsafe Interop.Error SetCreationTimeCore(SafeFileHandle? handle, string path, long seconds, long nanoseconds)
3946
{
4047
Interop.Sys.TimeSpec timeSpec = default;
4148

@@ -46,15 +53,18 @@ private unsafe Interop.Error SetCreationTimeCore(string path, long seconds, long
4653
attrList.bitmapCount = Interop.libc.AttrList.ATTR_BIT_MAP_COUNT;
4754
attrList.commonAttr = Interop.libc.AttrList.ATTR_CMN_CRTIME;
4855

56+
// Follow links when using SafeFileHandle API.
57+
int flags = handle is null ? new CULong(Interop.libc.FSOPT_NOFOLLOW) : 0;
58+
4959
Interop.Error error =
50-
Interop.libc.setattrlist(path, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), new CULong(Interop.libc.FSOPT_NOFOLLOW)) == 0 ?
60+
Interop.libc.setattrlist(path, &attrList, &timeSpec, sizeof(Interop.Sys.TimeSpec), flags) == 0 ?
5161
Interop.Error.SUCCESS :
5262
Interop.Sys.GetLastErrorInfo().Error;
5363

5464
return error;
5565
}
5666

57-
private void SetAccessOrWriteTime(string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
58-
SetAccessOrWriteTimeCore(path, time, isAccessTime, checkCreationTime: true, asDirectory);
67+
private void SetAccessOrWriteTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
68+
SetAccessOrWriteTimeCore(handle, path, time, isAccessTime, checkCreationTime: true, asDirectory);
5969
}
6070
}

src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.SetTimes.OtherUnix.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Runtime.InteropServices;
5+
using Microsoft.Win32.SafeHandles;
56

67
namespace System.IO
78
{
@@ -10,11 +11,14 @@ internal partial struct FileStatus
1011
internal void SetCreationTime(string path, DateTimeOffset time, bool asDirectory) =>
1112
SetLastWriteTime(path, time, asDirectory);
1213

13-
private void SetAccessOrWriteTime(string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
14-
SetAccessOrWriteTimeCore(path, time, isAccessTime, checkCreationTime: false, asDirectory);
14+
internal void SetCreationTime(SafeFileHandle handle, DateTimeOffset time, bool asDirectory) =>
15+
SetLastWriteTime(handle, time, asDirectory);
16+
17+
private void SetAccessOrWriteTime(SafeFileHandle? handle, string path, DateTimeOffset time, bool isAccessTime, bool asDirectory) =>
18+
SetAccessOrWriteTimeCore(handle, path, time, isAccessTime, checkCreationTime: false, asDirectory);
1519

1620
// This is not used on these platforms, but is needed for source compat
17-
private Interop.Error SetCreationTimeCore(string path, long seconds, long nanoseconds) =>
21+
private Interop.Error SetCreationTimeCore(SafeFileHandle? handle, string path, long seconds, long nanoseconds) =>
1822
throw new InvalidOperationException();
1923
}
2024
}

src/libraries/System.Private.CoreLib/src/System/IO/FileStatus.Unix.cs

Lines changed: 107 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.Diagnostics;
55
using System.Runtime.InteropServices;
6+
using Microsoft.Win32.SafeHandles;
67

78
namespace 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

Comments
 (0)