Skip to content

Experimental Feature: Provide Unix stat information in filesystem output #11042

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

Merged
merged 18 commits into from
Nov 16, 2019

Conversation

JamesWTruher
Copy link
Collaborator

PR Summary

This PR provides new behavior, as experimental feature PSUnixFileStat, to include data from the Unix stat API in the output of the file system provider to facilitate a more Unix-like file listing. It adds a new note property in the filesystem provider named UnixStat which includes a common expression of the stat(2) API from the underlying Unix type system. The native calls in the powershell-native assembly have created

The output from Get-ChildItem should look something like this:

PS> dir | select -first 4 -skip 5


    Directory: /Users/jimtru/src/github/forks/JamesWTruher/PowerShell-1

UnixMode   User             Group                 LastWriteTime           Size Name
--------   ----             -----                 -------------           ---- ----
drwxr-xr-x jimtru           staff              10/23/2019 13:16            416 test
drwxr-xr-x jimtru           staff               11/8/2019 10:37            896 tools
-rw-r--r-- jimtru           staff               11/8/2019 10:37         112858 build.psm1
-rw-r--r-- jimtru           staff               11/8/2019 10:37         201297 CHANGELOG.md

The username and groupname are retrieved at the time of formatting to reduce the number of PInvokes required.

PR Context

I've been somewhat frustrated with viewing the output of Get-ChildItem since it's different from /bin/ls -l output. This is an attempt to improve the experience with regard to the file system provider when running on a non-Windows platforms.

The old format is still available by selecting the children view with format-table:

PS> dir | select -skip 5 -first 4 | ft -view children

    Directory: /Users/jimtru/src/github/forks/JamesWTruher/PowerShell-1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/23/2019  1:16 PM                test
d----           11/8/2019 10:37 AM                tools
-----           11/8/2019 10:37 AM         112858 build.psm1
-----           11/8/2019 10:37 AM         201297 CHANGELOG.md

PR Checklist

/// <returns>The mode converted into a Unix style string similar to the output of ls.</returns>
public string GetModeString()
{
StatMask[] permissions = new StatMask[]
Copy link
Member

Choose a reason for hiding this comment

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

Can this be defined statically? would reduce allocations i think.

};

// start the mode string with the ItemType
System.Text.StringBuilder sb = new System.Text.StringBuilder();
Copy link
Member

Choose a reason for hiding this comment

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

The length of the output string is fixed to 10 i guess, so we should initialize the string builder with 10 as the capacity.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

i rewrote this method using a char array.

}

// We need a way to convert a DateTime to a unix date.j
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// We need a way to convert a DateTime to a unix date.j
// We need a way to convert a DateTime to a unix date.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed!

Comment on lines 6 to 10
$experimentalFeatureName = "PSUnixFileStat"
$skipTest = $true
if ( Get-ExperimentalFeature -Name $experimentalFeatureName ) {
$skipTest = $false
}
Copy link
Member

Choose a reason for hiding this comment

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

This can be:

$skipTest = -not $EnabledExperimentalFeatures.Contains('PSUnixFileStat')

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed!

@ghost ghost added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Nov 12, 2019
@ghost ghost removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Nov 12, 2019
…array of chars

A number of smaller fixes suggested by review.
internal static class Unix
{
// Please note that `Win32Exception(Marshal.GetLastWin32Error())`
Copy link
Collaborator

Choose a reason for hiding this comment

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

It might be worth either moving this comment or putting a newline between it and the dictionary fields to imply its relevance throughout the class.

I know the linter doesn't like the second suggestion, but I feel strongly that the linter is wrong about that. This is a really helpful comment, but applies generally within the class, while still being an implementation detail (so shouldn't be in something like <remarks/>)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yah - I moved this comment all around to get it through code factor. I can try a different location :)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we can add it in a <remarks> field in an XML comment for the class itself?

modeCharacters[offset] = '-';
}

offset++;
Copy link
Collaborator

Choose a reason for hiding this comment

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

If there's an offset++ here, would it make sense to use a double indexed for () loop instead? That way rather than nesting ifs and elseifs you could just continue.

foreach (StatMask permission in permissions)
{
// determine whether we are setuid, sticky, or the usual rwx.
if ((Mode & (int)permission) == (int)permission)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, is there a way to make this a switch? That seems like it would be the most natural approach/how C does it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

all the examples I could find (and I only looked for a couple) they do something similar to what I have using if/else (freebsd, gnu)

public string GetUserName()
{
string username;
if (usernameCache.TryGetValue(UserId, out username))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (usernameCache.TryGetValue(UserId, out username))
if (usernameCache.TryGetValue(UserId, out string username))

/// <returns>The user name.</returns>
public string GetUserName()
{
string username;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
string username;

cs.GroupId = css.GroupId;
cs.HardlinkCount = css.HardlinkCount;
cs.Size = css.Size;
cs.AccessTime = new DateTime(1970, 1, 1).AddSeconds(css.AccessTime).ToLocalTime();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
cs.AccessTime = new DateTime(1970, 1, 1).AddSeconds(css.AccessTime).ToLocalTime();
cs.AccessTime = DateTime.UnixEpoch.AddSeconds(css.AccessTime).ToLocalTime();

Copy link
Collaborator

Choose a reason for hiding this comment

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

And below

public int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
public int tm_isdst; /* Daylight saving time */
/// <summary>Seconds (0-60).</summary>
internal int tm_sec;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Any reason for changing these to internal? I know it's done elsewhere, but usually my reasoning goes like "If this type were public, should this field be visible?"

@@ -701,6 +1063,94 @@ internal static UnixTm DateTimeToUnixTm(DateTime date)
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern int GetInodeData([MarshalAs(UnmanagedType.LPStr)]string path,
out UInt64 device, out UInt64 inode);

/// <summary>
/// This is a struct from getcommonstat.h in the native library. It's a synthetic construct of the Unix stat
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you reword synthetic construct? It initially confused me until I realised we're saying this struct doesn't occur anywhere like this, but this type represents the maximal union of all structs in the wild.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done!

#region UnixStat


if (ExperimentalFeature.IsEnabled("PSUnixFileStat"))
Copy link
Collaborator

Choose a reason for hiding this comment

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

We could cache this, especially since it's an engine experimental feature, so it can't be toggled once PS has started

Copy link
Member

Choose a reason for hiding this comment

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

Caching not required for perf reasons. I believe changes to all engine experimental features need a restart of PS.

if (ExperimentalFeature.IsEnabled("PSUnixFileStat"))
{
// Add a commonstat structure to file system objects
if (ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could squash this into the previous condition with an &&

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@adityapatwardhan adityapatwardhan self-assigned this Nov 15, 2019
Move comment around to a hopefully acceptable location
Change language which describes the new common stat structure.
}
catch
{
result.AddOrSetProperty("UnixStat", null);
Copy link
Member

Choose a reason for hiding this comment

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

use named parameter in place of null

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

# if PSUnixFileStat is converted from an experimental feature, these tests will need to be changed
BeforeAll {
$experimentalFeatureName = "PSUnixFileStat"
$skipTest = -not $EnabledExperimentalFeatures.Contains($experimentalFeatureName)
Copy link
Member

Choose a reason for hiding this comment

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

Consider setting PSDefaultParameterValue for Skip

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

var commonStat = Platform.Unix.GetLStat(path);
result.AddOrSetProperty("UnixStat", commonStat);
}
catch
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to catch a specific exception here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no, any problem means that we can't add the property, so don't. I'll add a comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added a comment

$group = (/bin/ls -ld $testFile).split(" ",[System.StringSplitOptions]"RemoveEmptyEntries")[3]
$i.Group | Should -Be $Group
}

Copy link
Member

Choose a reason for hiding this comment

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

extra line

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

…t when adding UnixStat.

Fix a couple of small things in the tests, change to use PSDefaultParameterValue rather than using individual skip parameters
@adityapatwardhan adityapatwardhan added CL-Experimental Indicates that a PR should be marked as an Experimental Feature in the Change Log AutoMerge informs the bot to automerge the PR labels Nov 16, 2019
@ghost
Copy link

ghost commented Nov 16, 2019

Hello @adityapatwardhan!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@adityapatwardhan adityapatwardhan merged commit fe712f8 into PowerShell:master Nov 16, 2019
@SteveL-MSFT SteveL-MSFT added this to the 7.0.0-preview.6 milestone Nov 18, 2019
@ghost
Copy link

ghost commented Nov 21, 2019

🎉v7.0.0-preview.6 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
AutoMerge informs the bot to automerge the PR CL-Experimental Indicates that a PR should be marked as an Experimental Feature in the Change Log
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants