Skip to content
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

Merged
merged 4 commits into from
Aug 14, 2020

Conversation

carlossanlop
Copy link
Member

@carlossanlop carlossanlop commented Aug 11, 2020

Fixes #37301

These two files do very similar tasks, but when it comes to collect the Hidden attribute, the first one is simpler than the second one:

if (directoryEntry.InodeType == Interop.Sys.NodeType.DT_LNK)
{
isSymlink = true;
}
else if ((directoryEntry.InodeType == Interop.Sys.NodeType.DT_UNKNOWN)
&& (Interop.Sys.LStat(entry.FullPath, out Interop.Sys.FileStatus linkTargetStatus) >= 0))
{
isSymlink = (linkTargetStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK;
}

if ((_fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFLNK)
attributes |= FileAttributes.ReparsePoint;

This change ensures both collect the Hidden attribute the same way, and makes it more obvious that both methods are closely related.

Also, the ReadOnly attribute was not being collected in the first file, so I added code to do it, as well as a unit test.

I ran all the System.IO.FileSystem unit tests in Ubuntu and in MacOS and they passed. Pending yet to see the CI results.

This fix needs to go into 5.0, or later it could become a breaking change, as explained here.

@carlossanlop carlossanlop added this to the 5.0.0 milestone Aug 11, 2020
@carlossanlop carlossanlop added this to In progress in System.IO - File links via automation Aug 11, 2020
@carlossanlop carlossanlop self-assigned this Aug 11, 2020

// 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()));
Copy link
Member Author

@carlossanlop carlossanlop Aug 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No changes in this test, just organized some lines for easier understanding.
It also helps to show that this test is affected by my PR.

EDIT: I actually ended up making changes in this file, to split the tests into each platform, since the IsHidden property is not supported in Linux.

@carlossanlop
Copy link
Member Author

carlossanlop commented Aug 11, 2020

@iSazonov can you take a look to this PR? I think I still need to add code to throw but I'm not entirely sure exactly where.

Comment on lines 127 to 128
if (PlatformDetection.IsWindows)
fileTwo.Attributes = fileTwo.Attributes | FileAttributes.Hidden;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this also be performed on other platforms?

Copy link
Member Author

@carlossanlop carlossanlop Aug 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You pointed out the documentation says it is supported in Linux, Windows and MacOS. Let me debug this unit test in WIndows, MacOS and Unix to see if it's truly supported there, and if so, then the if should be removed, and maybe a separate test should be added for Linux and MacOS (because of the "." prefix in the name, which already hides the file).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in Linux, if I remove the code that prepends a "." to the filename, and attempt to set FileAttributes.Hidden to fileTwo, the test fails, because
Interop.Sys.CanSetHiddenFlag returns false.
The documentation needs to be updated for Linux.
I'll test MacOS and Windows now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works on OSX. I actually found a test in FileSystemEntry which would prevent the user from collecting an enumeration of hidden files. I fixed it now.
Next: Windows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works on Windows too.

@carlossanlop
Copy link
Member Author

@Jozkee @iSazonov can you take another look? The new commits include:

  • Added unit tests for each platform, and split an existing one into platforms: The FileAttributes.Hidden flag is only supported by Windows and MacOS. Linux does not support it, the file needs to be hidden using a dot prefix in the filename.
  • I found a bug in the FileSystemEntry.IsHidden property, where we were only checking for the dot prefix in the filename, but we also had to check for the IsHidden attribute.
  • Cleaned the code as suggested in the PR.

@@ -143,7 +140,7 @@ public FileAttributes Attributes
public DateTimeOffset LastAccessTimeUtc => _status.GetLastAccessTime(FullPath, continueOnError: true);
public DateTimeOffset LastWriteTimeUtc => _status.GetLastWriteTime(FullPath, continueOnError: true);
public bool IsDirectory => _status.InitiallyDirectory;
public bool IsHidden => _directoryEntry.Name[0] == '.';
public bool IsHidden => _directoryEntry.Name[0] == '.' || (Attributes & FileAttributes.Hidden) != 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I think this should be:

        public bool IsHidden => _directoryEntry.Name[0] == '.' || (Interop.Sys.CanSetHiddenFlag && (Attributes & FileAttributes.Hidden) != 0);

Otherwise, if the user sets the Hidden flag in Linux, it won't really hide the file, but this will return true.
I'm going to test this on Linux.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this check too, just in case. I noticed that the Hidden flag cannot be set to the file Attributes in Linux, because the property performs some checks to see if it's possible. But just in case, attempting to read the value should be protected.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The public property was introduced here: c603407

But the change that added the case for UF_HIDDEN should've added this check:
dotnet/corefx#34560

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a conversation with @Jozkee about this and the Interop.Sys.CanSetHiddenFlag check is not necessary, because the Linux P/Invoke will never allow setting it. So I'm reverting the last commit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attributes adds FileAttributes.ReadOnly which is irrelevant in this operation.

Suggested change
public bool IsHidden => _directoryEntry.Name[0] == '.' || (Attributes & FileAttributes.Hidden) != 0;
public bool IsHidden => _directoryEntry.Name[0] == '.' || (_initialAttributes & FileAttributes.Hidden) != 0;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please also add a comment on top explaining where the attribute is supported?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion applied, and comment added.

{
// Put a period in front to make it hidden on Unix
IsHiddenAttributeInternal(useDotPrefix: false, useHiddenFlag: true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add an OSX test that uses the dot prefix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test IsHiddenAttribute_Unix in line 127 runs in OSX too (TestPlatforms.AnyUnix is true in OSX).

return IsReadOnly(_fileStatus);
}

internal static bool IsReadOnly(Interop.Sys.FileStatus fileStatus)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Private?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it needs to be accessed by FileSystemEntry too, which is in the same assembly.

@@ -39,19 +39,23 @@ internal struct FileStatus
internal bool IsReadOnly(ReadOnlySpan<char> path, bool continueOnError = false)
{
EnsureStatInitialized(path, continueOnError);
return IsReadOnly(_fileStatus);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we re-read if we have Refresh() for this.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 IsReadOnly method into its own internal helper static method that can be accessed by both FileSystemEntry and FileStatus, to avoid repeating code.

I don't think we should do a Refresh call here, that's what EnsureStatInitialized is for - it will only perform a Refresh if we haven't called it yet.

{
// IMPORTANT: Attribute logic must match the logic in FileSystemEntry
return (fileStatus.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR;
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
The same for all attributes.

Copy link
Member Author

Choose a reason for hiding this comment

The 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.

System.IO - File links automation moved this from In progress to Reviewer approved Aug 14, 2020
@carlossanlop carlossanlop merged commit 6ed1e41 into dotnet:master Aug 14, 2020
System.IO - File links automation moved this from Reviewer approved to Done Aug 14, 2020
@carlossanlop carlossanlop deleted the Hidden branch August 14, 2020 20:04
@iSazonov
Copy link
Contributor

Thanks for fixing this!

What is Preview we get the fix in?

@Jozkee
Copy link
Member

Jozkee commented Aug 15, 2020

@iSazonov .NET RC1

carlossanlop added a commit to carlossanlop/runtime that referenced this pull request Sep 3, 2020
Reverts "Ensure FileStatus and FileSystemEntry Hidden and ReadOnly attributes are retrieved the same way"
(commit 6ed1e41) which introduced a perf regression.
@carlossanlop carlossanlop mentioned this pull request Sep 3, 2020
github-actions bot pushed a commit that referenced this pull request Sep 3, 2020
Reverts "Ensure FileStatus and FileSystemEntry Hidden and ReadOnly attributes are retrieved the same way"
(commit 6ed1e41) which introduced a perf regression.
carlossanlop added a commit that referenced this pull request Sep 3, 2020
carlossanlop added a commit that referenced this pull request Sep 4, 2020
Reverts "Ensure FileStatus and FileSystemEntry Hidden and ReadOnly attributes are retrieved the same way"
(commit 6ed1e41) which introduced a perf regression.

Co-authored-by: carlossanlop <carlossanlop@users.noreply.github.com>
@ghost ghost locked as resolved and limited conversation to collaborators Dec 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
Development

Successfully merging this pull request may close these issues.

FileSystemEntry.Attributes property is not correct on Unix
3 participants