-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Ensure FileStatus and FileSystemEntry Hidden and ReadOnly attributes are retrieved the same way #40641
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure FileStatus and FileSystemEntry Hidden and ReadOnly attributes are retrieved the same way #40641
Changes from all commits
aff0182
cfb8175
ba00f54
a39bf8a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,19 +39,23 @@ internal static void Initialize( | |
| internal bool IsReadOnly(ReadOnlySpan<char> path, bool continueOnError = false) | ||
| { | ||
| EnsureStatInitialized(path, continueOnError); | ||
| return IsReadOnly(_fileStatus); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we re-read if we have Refresh() for this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the question. The functionality of this code has not been changed. I merely moved the contents of the old I don't think we should do a |
||
| } | ||
|
|
||
| internal static bool IsReadOnly(Interop.Sys.FileStatus fileStatus) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Private?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it needs to be accessed by |
||
| { | ||
| #if TARGET_BROWSER | ||
| const Interop.Sys.Permissions readBit = Interop.Sys.Permissions.S_IRUSR; | ||
| const Interop.Sys.Permissions writeBit = Interop.Sys.Permissions.S_IWUSR; | ||
| #else | ||
| Interop.Sys.Permissions readBit, writeBit; | ||
|
|
||
| if (_fileStatus.Uid == Interop.Sys.GetEUid()) | ||
| if (fileStatus.Uid == Interop.Sys.GetEUid()) | ||
| { | ||
| // User effectively owns the file | ||
| readBit = Interop.Sys.Permissions.S_IRUSR; | ||
| writeBit = Interop.Sys.Permissions.S_IWUSR; | ||
| } | ||
| else if (_fileStatus.Gid == Interop.Sys.GetEGid()) | ||
| else if (fileStatus.Gid == Interop.Sys.GetEGid()) | ||
| { | ||
| // User belongs to a group that effectively owns the file | ||
| readBit = Interop.Sys.Permissions.S_IRGRP; | ||
|
|
@@ -65,37 +69,57 @@ internal bool IsReadOnly(ReadOnlySpan<char> path, bool continueOnError = false) | |
| } | ||
| #endif | ||
|
|
||
| return ((_fileStatus.Mode & (int)readBit) != 0 && // has read permission | ||
| (_fileStatus.Mode & (int)writeBit) == 0); // but not write permission | ||
| return (fileStatus.Mode & (int)readBit) != 0 && // has read permission | ||
| (fileStatus.Mode & (int)writeBit) == 0; // but not write permission | ||
| } | ||
|
|
||
| public FileAttributes GetAttributes(ReadOnlySpan<char> path, ReadOnlySpan<char> fileName) | ||
| internal static bool IsDirectory(Interop.Sys.FileStatus fileStatus) | ||
| { | ||
| // IMPORTANT: Attribute logic must match the logic in FileSystemEntry | ||
| return (fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is better move p/invokes from FileSystemEntry.Initialize() in the place and exclude re-reading too.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I talked to @jozkee about this, we can get them moved to a separate helper class, but I would like to do that in a future PR. |
||
| } | ||
|
|
||
| EnsureStatInitialized(path); | ||
| internal static bool IsHidden(Interop.Sys.FileStatus fileStatus) | ||
| { | ||
| return (fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN; | ||
| } | ||
|
|
||
| if (!_exists) | ||
| return (FileAttributes)(-1); | ||
| internal static bool IsSymLink(Interop.Sys.FileStatus fileStatus) | ||
| { | ||
| return (fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK; | ||
| } | ||
|
|
||
| internal static FileAttributes GetAttributes(bool isReadOnly, bool isSymlink, bool isDirectory, bool isHidden) | ||
| { | ||
| FileAttributes attributes = default; | ||
|
|
||
| if (IsReadOnly(path)) | ||
| if (isReadOnly) | ||
| attributes |= FileAttributes.ReadOnly; | ||
|
|
||
| if ((_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK) | ||
| if (isSymlink) | ||
| attributes |= FileAttributes.ReparsePoint; | ||
|
|
||
| if (_isDirectory) | ||
| if (isDirectory) | ||
| attributes |= FileAttributes.Directory; | ||
|
|
||
| // If the filename starts with a period or has UF_HIDDEN flag set, it's hidden. | ||
| if (fileName.Length > 0 && (fileName[0] == '.' || (_fileStatus.UserFlags & (uint)Interop.Sys.UserFlags.UF_HIDDEN) == (uint)Interop.Sys.UserFlags.UF_HIDDEN)) | ||
| if (isHidden) | ||
| attributes |= FileAttributes.Hidden; | ||
|
|
||
| return attributes != default ? attributes : FileAttributes.Normal; | ||
| } | ||
|
|
||
| public FileAttributes GetAttributes(ReadOnlySpan<char> path, ReadOnlySpan<char> fileName) | ||
| { | ||
| // IMPORTANT: Attribute logic must match the logic in FileSystemEntry | ||
|
|
||
| EnsureStatInitialized(path); | ||
|
|
||
| if (!_exists) | ||
| return (FileAttributes)(-1); | ||
|
|
||
| return GetAttributes( | ||
| IsReadOnly(path), | ||
| IsSymLink(_fileStatus), | ||
| _isDirectory, | ||
| (fileName.Length > 0 && fileName[0] == '.') || IsHidden(_fileStatus)); | ||
| } | ||
|
|
||
| public void SetAttributes(string path, FileAttributes attributes) | ||
carlossanlop marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| // Validate that only flags from the attribute are being provided. This is an | ||
|
|
@@ -300,13 +324,13 @@ public void Refresh(ReadOnlySpan<char> path) | |
| _exists = true; | ||
|
|
||
| // IMPORTANT: Is directory logic must match the logic in FileSystemEntry | ||
| _isDirectory = (_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; | ||
| _isDirectory = IsDirectory(_fileStatus); | ||
|
|
||
| // If we're a symlink, attempt to check the target to see if it is a directory | ||
| if ((_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK && | ||
| Interop.Sys.Stat(path, out Interop.Sys.FileStatus targetStatus) >= 0) | ||
| { | ||
| _isDirectory = (targetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR; | ||
| _isDirectory = IsDirectory(targetStatus); | ||
| } | ||
|
|
||
| _fileStatusInitialized = 0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -113,18 +113,42 @@ public void DirectoryAttributesAreExpected() | |
| } | ||
|
|
||
| [Fact] | ||
| public void IsHiddenAttribute() | ||
| [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] | ||
| public void IsHiddenAttribute_Windows_OSX() | ||
| { | ||
| // Put a period in front to make it hidden on Unix | ||
| IsHiddenAttributeInternal(useDotPrefix: false, useHiddenFlag: true); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you also add an OSX test that uses the dot prefix?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test |
||
|
|
||
| } | ||
|
|
||
|
|
||
| [Fact] | ||
| [PlatformSpecific(TestPlatforms.AnyUnix)] | ||
| public void IsHiddenAttribute_Unix() | ||
| { | ||
| // Windows and MacOS hide a file by setting the hidden attribute | ||
| IsHiddenAttributeInternal(useDotPrefix: true, useHiddenFlag: false); | ||
| } | ||
|
|
||
| private void IsHiddenAttributeInternal(bool useDotPrefix, bool useHiddenFlag) | ||
| { | ||
| string prefix = useDotPrefix ? "." : ""; | ||
|
|
||
| DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); | ||
| FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
|
|
||
| // Put a period in front to make it hidden on Unix | ||
| FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName())); | ||
| FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
| FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, prefix + GetTestFileName())); | ||
|
|
||
| fileOne.Create().Dispose(); | ||
| fileTwo.Create().Dispose(); | ||
| if (PlatformDetection.IsWindows) | ||
| fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden; | ||
|
|
||
| if (useHiddenFlag) | ||
| { | ||
| fileTwo.Attributes |= FileAttributes.Hidden; | ||
| } | ||
|
|
||
| FileInfo fileCheck = new FileInfo(fileTwo.FullName); | ||
| Assert.Equal(fileTwo.Attributes, fileCheck.Attributes); | ||
|
|
||
| IEnumerable<string> enumerable = new FileSystemEnumerable<string>( | ||
| testDirectory.FullName, | ||
|
|
@@ -136,5 +160,29 @@ public void IsHiddenAttribute() | |
|
|
||
| Assert.Equal(new string[] { fileTwo.FullName }, enumerable); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void IsReadOnlyAttribute() | ||
| { | ||
| DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); | ||
|
|
||
| FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
| FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
|
|
||
| fileOne.Create().Dispose(); | ||
| fileTwo.Create().Dispose(); | ||
|
|
||
| fileTwo.Attributes |= FileAttributes.ReadOnly; | ||
|
|
||
| IEnumerable<string> enumerable = new FileSystemEnumerable<string>( | ||
| testDirectory.FullName, | ||
| (ref FileSystemEntry entry) => entry.ToFullPath(), | ||
| new EnumerationOptions() { AttributesToSkip = 0 }) | ||
| { | ||
| ShouldIncludePredicate = (ref FileSystemEntry entry) => (entry.Attributes & FileAttributes.ReadOnly) != 0 | ||
| }; | ||
|
|
||
| Assert.Equal(new string[] { fileTwo.FullName }, enumerable); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,25 +21,42 @@ protected virtual string[] GetPaths(string directory, EnumerationOptions options | |
| } | ||
|
|
||
| [Fact] | ||
| public void SkippingHiddenFiles() | ||
| [PlatformSpecific(TestPlatforms.Windows | TestPlatforms.OSX)] | ||
| public void SkippingHiddenFiles_Windows_OSX() | ||
| { | ||
| SkippingHiddenFilesInternal(useDotPrefix: false, useHiddenFlag: true); | ||
| } | ||
|
|
||
| [Fact] | ||
| [PlatformSpecific(TestPlatforms.AnyUnix)] | ||
| public void SkippingHiddenFiles_Unix() | ||
| { | ||
| SkippingHiddenFilesInternal(useDotPrefix: true, useHiddenFlag: false); | ||
| } | ||
|
|
||
| private void SkippingHiddenFilesInternal(bool useDotPrefix, bool useHiddenFlag) | ||
| { | ||
| DirectoryInfo testDirectory = Directory.CreateDirectory(GetTestFilePath()); | ||
| DirectoryInfo testSubdirectory = Directory.CreateDirectory(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
| FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
|
|
||
| // Put a period in front to make it hidden on Unix | ||
| FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, "." + GetTestFileName())); | ||
| FileInfo fileOne = new FileInfo(Path.Combine(testDirectory.FullName, GetTestFileName())); | ||
|
||
| FileInfo fileThree = new FileInfo(Path.Combine(testSubdirectory.FullName, GetTestFileName())); | ||
| FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, "." + GetTestFileName())); | ||
|
|
||
| // Put a period in front of files two and four to make them hidden on Unix | ||
| string prefix = useDotPrefix ? "." : ""; | ||
| FileInfo fileTwo = new FileInfo(Path.Combine(testDirectory.FullName, prefix + GetTestFileName())); | ||
| FileInfo fileFour = new FileInfo(Path.Combine(testSubdirectory.FullName, prefix + GetTestFileName())); | ||
|
|
||
| fileOne.Create().Dispose(); | ||
| fileTwo.Create().Dispose(); | ||
| if (PlatformDetection.IsWindows) | ||
| fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden; | ||
| fileThree.Create().Dispose(); | ||
| fileFour.Create().Dispose(); | ||
| if (PlatformDetection.IsWindows) | ||
| fileFour.Attributes = fileTwo.Attributes | FileAttributes.Hidden; | ||
|
|
||
| if (useHiddenFlag) | ||
| { | ||
| fileTwo.Attributes |= FileAttributes.Hidden; | ||
| fileFour.Attributes |= FileAttributes.Hidden; | ||
| } | ||
|
|
||
| // Default EnumerationOptions is to skip hidden | ||
| string[] paths = GetPaths(testDirectory.FullName, new EnumerationOptions()); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.