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

This commit is contained in:
James Truher [MSFT]
2019-11-15 17:26:11 -08:00
committed by Aditya Patwardhan
parent f32ae64b8b
commit fe712f8de2
9 changed files with 693 additions and 21 deletions
@@ -554,27 +554,284 @@ namespace System.Management.Automation
return IsMacOS ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid);
}
// Unix specific implementations of required functionality
//
// Please note that `Win32Exception(Marshal.GetLastWin32Error())`
// works *correctly* on Linux in that it creates an exception with
// the string perror would give you for the last set value of errno.
// No manual mapping is required. .NET Core maps the Linux errno
// to a PAL value and calls strerror_r underneath to generate the message.
/// <summary>Unix specific implementations of required functionality.</summary>
internal static class Unix
{
private static Dictionary<int, string> usernameCache = new Dictionary<int, string>();
private static Dictionary<int, string> groupnameCache = new Dictionary<int, string>();
/// <summary>The type of a Unix file system item.</summary>
public enum ItemType
{
/// <summary>The item is a Directory.</summary>
Directory,
/// <summary>The item is a File.</summary>
File,
/// <summary>The item is a Symbolic Link.</summary>
SymbolicLink,
/// <summary>The item is a Block Device.</summary>
BlockDevice,
/// <summary>The item is a Character Device.</summary>
CharacterDevice,
/// <summary>The item is a Named Pipe.</summary>
NamedPipe,
/// <summary>The item is a Socket.</summary>
Socket,
}
/// <summary>The mask to use to retrieve specific mode bits from the mode value in the stat class.</summary>
public enum StatMask
{
/// <summary>The mask to collect the owner mode.</summary>
OwnerModeMask = 0x1C0,
/// <summary>The mask to get the owners read bit.</summary>
OwnerRead = 0x100,
/// <summary>The mask to get the owners write bit.</summary>
OwnerWrite = 0x080,
/// <summary>The mask to get the owners execute bit.</summary>
OwnerExecute = 0x040,
/// <summary>The mask to get the group mode.</summary>
GroupModeMask = 0x038,
/// <summary>The mask to get the group mode.</summary>
GroupRead = 0x20,
/// <summary>The mask to get the group mode.</summary>
GroupWrite = 0x10,
/// <summary>The mask to get the group mode.</summary>
GroupExecute = 0x8,
/// <summary>The mask to get the "other" mode.</summary>
OtherModeMask = 0x007,
/// <summary>The mask to get the "other" read bit.</summary>
OtherRead = 0x004,
/// <summary>The mask to get the "other" write bit.</summary>
OtherWrite = 0x002,
/// <summary>The mask to get the "other" execute bit.</summary>
OtherExecute = 0x001,
/// <summary>The mask to retrieve the sticky bit.</summary>
SetStickyMask = 0x200,
/// <summary>The mask to retrieve the setgid bit.</summary>
SetGidMask = 0x400,
/// <summary>The mask to retrieve the setuid bit.</summary>
SetUidMask = 0x800,
}
/// <summary>The Common Stat class.</summary>
public class CommonStat
{
/// <summary>The inode of the filesystem item.</summary>
public long Inode;
/// <summary>The Mode of the filesystem item.</summary>
public int Mode;
/// <summary>The user id of the filesystem item.</summary>
public int UserId;
/// <summary>The group id of the filesystem item.</summary>
public int GroupId;
/// <summary>The number of hard links for the filesystem item.</summary>
public int HardlinkCount;
/// <summary>The size in bytes of the filesystem item.</summary>
public long Size;
/// <summary>The last access time of the filesystem item.</summary>
public DateTime AccessTime;
/// <summary>The last modified time for the filesystem item.</summary>
public DateTime ModifiedTime;
/// <summary>The last time the status changes for the filesystem item.</summary>
public DateTime StatusChangeTime;
/// <summary>The block size of the filesystem.</summary>
public long BlockSize;
/// <summary>The device id of the filesystem item.</summary>
public int DeviceId;
/// <summary>The number of blocks used by the filesystem item.</summary>
public int NumberOfBlocks;
/// <summary>The type of the filesystem item.</summary>
public ItemType ItemType;
/// <summary>Whether the filesystem item has the setuid bit enabled.</summary>
public bool IsSetUid;
/// <summary>Whether the filesystem item has the setgid bit enabled.</summary>
public bool IsSetGid;
/// <summary>Whether the filesystem item has the sticky bit enabled. This is only available for directories.</summary>
public bool IsSticky;
private const char CanRead = 'r';
private const char CanWrite = 'w';
private const char CanExecute = 'x';
// helper for getting unix mode
private Dictionary<StatMask, char> modeMap = new Dictionary<StatMask, char>()
{
{ StatMask.OwnerRead, CanRead },
{ StatMask.OwnerWrite, CanWrite },
{ StatMask.OwnerExecute, CanExecute },
{ StatMask.GroupRead, CanRead },
{ StatMask.GroupWrite, CanWrite },
{ StatMask.GroupExecute, CanExecute },
{ StatMask.OtherRead, CanRead },
{ StatMask.OtherWrite, CanWrite },
{ StatMask.OtherExecute, CanExecute },
};
private StatMask[] permissions = new StatMask[]
{
StatMask.OwnerRead,
StatMask.OwnerWrite,
StatMask.OwnerExecute,
StatMask.GroupRead,
StatMask.GroupWrite,
StatMask.GroupExecute,
StatMask.OtherRead,
StatMask.OtherWrite,
StatMask.OtherExecute
};
// The item type and the character representation for the first element in the stat string
private Dictionary<ItemType, char> itemTypeTable = new Dictionary<ItemType, char>()
{
{ ItemType.BlockDevice, 'b' },
{ ItemType.CharacterDevice, 'c' },
{ ItemType.Directory, 'd' },
{ ItemType.File, '-' },
{ ItemType.NamedPipe, 'p' },
{ ItemType.Socket, 's' },
{ ItemType.SymbolicLink, 'l' },
};
/// <summary>Convert the mode to a string which is usable in our formatting.</summary>
/// <returns>The mode converted into a Unix style string similar to the output of ls.</returns>
public string GetModeString()
{
int offset = 0;
char[] modeCharacters = new char[10];
modeCharacters[offset++] = itemTypeTable[ItemType];
foreach (StatMask permission in permissions)
{
// determine whether we are setuid, sticky, or the usual rwx.
if ((Mode & (int)permission) == (int)permission)
{
if ((permission == StatMask.OwnerExecute && IsSetUid) || (permission == StatMask.GroupExecute && IsSetGid))
{
// Check for setuid and add 's'
modeCharacters[offset] = 's';
}
else if (permission == StatMask.OtherExecute && IsSticky && (ItemType == ItemType.Directory))
{
// Directories are sticky, rather than setuid
modeCharacters[offset] = 't';
}
else
{
modeCharacters[offset] = modeMap[permission];
}
}
else
{
modeCharacters[offset] = '-';
}
offset++;
}
return new string(modeCharacters);
}
/// <summary>
/// Get the user name. This is used in formatting, but we shouldn't
/// do the pinvoke this unless we're going to use it.
/// </summary>
/// <returns>The user name.</returns>
public string GetUserName()
{
if (usernameCache.TryGetValue(UserId, out string username))
{
return username;
}
// Get and add the user name to the cache so we don't need to
// have a pinvoke for each file.
username = NativeMethods.GetPwUid(UserId);
usernameCache.Add(UserId, username);
return username;
}
/// <summary>
/// Get the group name. This is used in formatting, but we shouldn't
/// do the pinvoke this unless we're going to use it.
/// </summary>
/// <returns>The name of the group.</returns>
public string GetGroupName()
{
if (groupnameCache.TryGetValue(GroupId, out string groupname))
{
return groupname;
}
// Get and add the group name to the cache so we don't need to
// have a pinvoke for each file.
groupname = NativeMethods.GetGrGid(GroupId);
groupnameCache.Add(GroupId, groupname);
return groupname;
}
}
// This is a helper that attempts to map errno into a PowerShell ErrorCategory
internal static ErrorCategory GetErrorCategory(int errno)
{
return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno);
}
/// <summary>Is this a hardlink.</summary>
/// <param name="handle">The handle to a file.</param>
/// <returns>A boolean that represents whether the item is a hardlink.</returns>
public static bool IsHardLink(ref IntPtr handle)
{
// TODO:PSL implement using fstat to query inode refcount to see if it is a hard link
return false;
}
/// <summary>Determine if the item is a hardlink.</summary>
/// <param name="fs">A FileSystemInfo to check to determine if it is a hardlink.</param>
/// <returns>A boolean that represents whether the item is a hardlink.</returns>
public static bool IsHardLink(FileSystemInfo fs)
{
if (!fs.Exists || (fs.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
@@ -589,19 +846,105 @@ namespace System.Management.Automation
{
return count > 1;
}
else
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
/// <summary>
/// Create a managed replica of the native stat structure.
/// </summary>
/// <param name="css">The common stat structure from which we copy.</param>
/// <returns>A managed common stat class instance.</returns>
private static CommonStat CopyStatStruct(NativeMethods.CommonStatStruct css)
{
CommonStat cs = new CommonStat();
cs.Inode = css.Inode;
cs.Mode = css.Mode;
cs.UserId = css.UserId;
cs.GroupId = css.GroupId;
cs.HardlinkCount = css.HardlinkCount;
cs.Size = css.Size;
cs.AccessTime = DateTime.UnixEpoch.AddSeconds(css.AccessTime).ToLocalTime();
cs.ModifiedTime = DateTime.UnixEpoch.AddSeconds(css.ModifiedTime).ToLocalTime();
cs.StatusChangeTime = DateTime.UnixEpoch.AddSeconds(css.StatusChangeTime).ToLocalTime();
cs.BlockSize = css.BlockSize;
cs.DeviceId = css.DeviceId;
cs.NumberOfBlocks = css.NumberOfBlocks;
if (css.IsDirectory == 1)
{
cs.ItemType = ItemType.Directory;
}
else if (css.IsFile == 1)
{
cs.ItemType = ItemType.File;
}
else if (css.IsSymbolicLink == 1)
{
cs.ItemType = ItemType.SymbolicLink;
}
else if (css.IsBlockDevice == 1)
{
cs.ItemType = ItemType.BlockDevice;
}
else if (css.IsCharacterDevice == 1)
{
cs.ItemType = ItemType.CharacterDevice;
}
else if (css.IsNamedPipe == 1)
{
cs.ItemType = ItemType.NamedPipe;
}
else
{
cs.ItemType = ItemType.Socket;
}
cs.IsSetUid = css.IsSetUid == 1;
cs.IsSetGid = css.IsSetGid == 1;
cs.IsSticky = css.IsSticky == 1;
return cs;
}
/// <summary>Get the lstat info from a path.</summary>
/// <param name="path">The path to the lstat information.</param>
/// <returns>An instance of the CommonStat for the path.</returns>
public static CommonStat GetLStat(string path)
{
NativeMethods.CommonStatStruct css;
if (NativeMethods.GetCommonLStat(path, out css) == 0)
{
return CopyStatStruct(css);
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
/// <summary>Get the stat info from a path.</summary>
/// <param name="path">The path to the stat information.</param>
/// <returns>An instance of the CommonStat for the path.</returns>
public static CommonStat GetStat(string path)
{
NativeMethods.CommonStatStruct css;
if (NativeMethods.GetCommonStat(path, out css) == 0)
{
return CopyStatStruct(css);
}
throw new Win32Exception(Marshal.GetLastWin32Error());
}
/// <summary>Read the /proc file system for information about the parent.</summary>
/// <param name="pid">The process id used to get the parent process.</param>
/// <returns>The process id.</returns>
public static int GetProcFSParentPid(int pid)
{
const int invalidPid = -1;
// read /proc/<pid>/stat
// 4th column will contain the ppid, 92 in the example below
// ex: 93 (bash) S 92 93 2 4294967295 ...
var path = $"/proc/{pid}/stat";
try
{
@@ -620,12 +963,12 @@ namespace System.Management.Automation
}
}
/// <summary>The native methods class.</summary>
internal static class NativeMethods
{
private const string psLib = "libpsl-native";
// Ansi is a misnomer, it is hardcoded to UTF-8 on Linux and macOS
// C bools are 1 byte and so must be marshaled as I1
[DllImport(psLib, CharSet = CharSet.Ansi)]
@@ -644,21 +987,39 @@ namespace System.Management.Automation
[DllImport(psLib, CharSet = CharSet.Ansi)]
internal static extern uint GetCurrentThreadId();
// This is a struct tm from <time.h>
// This is a struct tm from <time.h>.
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct UnixTm
{
public int tm_sec; /* Seconds (0-60) */
public int tm_min; /* Minutes (0-59) */
public int tm_hour; /* Hours (0-23) */
public int tm_mday; /* Day of the month (1-31) */
public int tm_mon; /* Month (0-11) */
public int tm_year; /* Year - 1900 */
public int tm_wday; /* Day of the week (0-6, Sunday = 0) */
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;
/// <summary>Minutes (0-59).</summary>
internal int tm_min;
/// <summary>Hours (0-23).</summary>
internal int tm_hour;
/// <summary>Day of the month (1-31).</summary>
internal int tm_mday;
/// <summary>Month (0-11).</summary>
internal int tm_mon;
/// <summary>The year - 1900.</summary>
internal int tm_year;
/// <summary>Day of the week (0-6, Sunday = 0).</summary>
internal int tm_wday;
/// <summary>Day in the year (0-365, 1 Jan = 0).</summary>
internal int tm_yday;
/// <summary>Daylight saving time.</summary>
internal int tm_isdst;
}
// We need a way to convert a DateTime to a unix date.
internal static UnixTm DateTimeToUnixTm(DateTime date)
{
UnixTm tm;
@@ -701,6 +1062,94 @@ namespace System.Management.Automation
[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 presents each member of the stat structure as the largest type of that member across
/// all stat structures on the platforms we support. This allows us to present a common
/// stat structure for all our platforms.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct CommonStatStruct
{
/// <summary>The inode of the filesystem item.</summary>
internal long Inode;
/// <summary>The mode of the filesystem item.</summary>
internal int Mode;
/// <summary>The user id of the filesystem item.</summary>
internal int UserId;
/// <summary>The group id of the filesystem item.</summary>
internal int GroupId;
/// <summary>The number of hard links to the filesystem item.</summary>
internal int HardlinkCount;
/// <summary>The size in bytes of the filesystem item.</summary>
internal long Size;
/// <summary>The time of the last access for the filesystem item.</summary>
internal long AccessTime;
/// <summary>The time of the last modification for the filesystem item.</summary>
internal long ModifiedTime;
/// <summary>The time of the last status change for the filesystem item.</summary>
internal long StatusChangeTime;
/// <summary>The size in bytes of the file system.</summary>
internal long BlockSize;
/// <summary>The device id for the filesystem item.</summary>
internal int DeviceId;
/// <summary>The number of filesystem blocks that the filesystem item uses.</summary>
internal int NumberOfBlocks;
/// <summary>This filesystem item is a directory.</summary>
internal int IsDirectory;
/// <summary>This filesystem item is a file.</summary>
internal int IsFile;
/// <summary>This filesystem item is a symbolic link.</summary>
internal int IsSymbolicLink;
/// <summary>This filesystem item is a block device.</summary>
internal int IsBlockDevice;
/// <summary>This filesystem item is a character device.</summary>
internal int IsCharacterDevice;
/// <summary>This filesystem item is a named pipe.</summary>
internal int IsNamedPipe;
/// <summary>This filesystem item is a socket.</summary>
internal int IsSocket;
/// <summary>This filesystem item will run as the the owner if executed.</summary>
internal int IsSetUid;
/// <summary>This filesystem item will run as the the group if executed.</summary>
internal int IsSetGid;
/// <summary>Whether the sticky bit is set on the filesystem item.</summary>
internal int IsSticky;
}
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern unsafe int GetCommonLStat(string filePath, [Out] out CommonStatStruct cs);
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern unsafe int GetCommonStat(string filePath, [Out] out CommonStatStruct cs);
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern string GetPwUid(int id);
[DllImport(psLib, CharSet = CharSet.Ansi, SetLastError = true)]
internal static extern string GetGrGid(int id);
}
}
}
@@ -41,6 +41,30 @@ namespace System.Management.Automation.Runspaces
private static IEnumerable<FormatViewDefinition> ViewsOf_FileSystemTypes(CustomControl[] sharedControls)
{
#if UNIX
if (ExperimentalFeature.IsEnabled("PSUnixFileStat"))
{
yield return new FormatViewDefinition("childrenWithUnixStat",
TableControl.Create()
.GroupByProperty("PSParentPath", customControl: sharedControls[0])
.AddHeader(Alignment.Left, label: "UnixMode", width: 10)
.AddHeader(Alignment.Left, label: "User", width: 16)
.AddHeader(Alignment.Left, label: "Group", width: 16)
.AddHeader(Alignment.Right, label: "LastWriteTime", width: 18)
.AddHeader(Alignment.Right, label: "Size", width: 14)
.AddHeader(Alignment.Left, label: "Name")
.StartRowDefinition(wrap: true)
.AddPropertyColumn("UnixMode")
.AddPropertyColumn("User")
.AddPropertyColumn("Group")
.AddScriptBlockColumn(scriptBlock: @"'{0:d} {0:HH}:{0:mm}' -f $_.LastWriteTime")
.AddPropertyColumn("Size")
.AddPropertyColumn("NameString")
.EndRowDefinition()
.EndTable());
}
#endif
yield return new FormatViewDefinition("children",
TableControl.Create()
.GroupByProperty("PSParentPath", customControl: sharedControls[0])
@@ -28,7 +28,7 @@
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.7.0-preview3.19551.4" />
<!-- the following package(s) are from the powershell org -->
<PackageReference Include="Microsoft.Management.Infrastructure" Version="2.0.0-preview.2" />
<PackageReference Include="Microsoft.PowerShell.Native" Version="7.0.0-preview.3" />
<PackageReference Include="Microsoft.PowerShell.Native" Version="7.0.0-preview.4" />
</ItemGroup>
<PropertyGroup>
@@ -127,6 +127,11 @@ namespace System.Management.Automation
new ExperimentalFeature(
name: "PSPipelineChainOperators",
description: "Allow use of && and || as operators between pipeline invocations"),
#if UNIX
new ExperimentalFeature(
name: "PSUnixFileStat",
description: "Provide unix permission information for files and directories"),
#endif
new ExperimentalFeature(
name: "PSNullConditionalOperators",
description: "Support the null conditional member access operators in PowerShell language")
@@ -9198,6 +9198,53 @@ namespace System.Management.Automation.Runspaces
#endregion System.Version#IncludeLabel
#if UNIX
#region UnixStat
if (ExperimentalFeature.IsEnabled("PSUnixFileStat"))
{
typeName = @"System.IO.FileSystemInfo";
typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(capacity: 1));
// Where we have a method to invoke below, first check to be sure that the object is present
// to avoid null reference issues
newMembers.Add(@"UnixMode");
AddMember(
errors,
typeName,
new PSScriptProperty(@"UnixMode", GetScriptBlock(@"if ($this.UnixStat) { $this.UnixStat.GetModeString() }")),
typeMembers,
isOverride: false);
newMembers.Add(@"User");
AddMember(
errors,
typeName,
new PSScriptProperty(@"User", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetUserName() } ")),
typeMembers,
isOverride: false);
newMembers.Add(@"Group");
AddMember(
errors,
typeName,
new PSScriptProperty(@"Group", GetScriptBlock(@" if ($this.UnixStat) { $this.UnixStat.GetGroupName() } ")),
typeMembers,
isOverride: false);
newMembers.Add(@"Size");
AddMember(
errors,
typeName,
new PSScriptProperty(@"Size", GetScriptBlock(@"$this.UnixStat.Size")),
typeMembers,
isOverride: false);
}
#endregion
#endif
// Update binder version for newly added members.
foreach (string memberName in newMembers)
{
@@ -1862,6 +1862,27 @@ namespace System.Management.Automation.Provider
result.AddOrSetProperty("PSChildName", childName);
providerBaseTracer.WriteLine("Attaching {0} = {1}", "PSChildName", childName);
#if UNIX
// Add a commonstat structure to file system objects
if (ExperimentalFeature.IsEnabled("PSUnixFileStat") && ProviderInfo.ImplementingType == typeof(Microsoft.PowerShell.Commands.FileSystemProvider))
{
try
{
// Use LStat because if you get a link, you want the information about the
// link, not the file.
var commonStat = Platform.Unix.GetLStat(path);
result.AddOrSetProperty("UnixStat", commonStat);
}
catch
{
// If there is *any* problem in retrieving the stat information
// set the property to null. There is no specific exception which
// would result in different behavior.
result.AddOrSetProperty("UnixStat", value: null);
}
}
#endif
}
// PSDriveInfo
@@ -0,0 +1,126 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
Describe "UnixFileSystem additions" -Tag "CI" {
# if PSUnixFileStat is converted from an experimental feature, these tests will need to be changed
BeforeAll {
$experimentalFeatureName = "PSUnixFileStat"
$skipTest = -not $EnabledExperimentalFeatures.Contains($experimentalFeatureName)
$PSDefaultParameterValues.Add('It:Skip', $skipTest)
}
AfterAll {
$PSDefaultParameterValues.Remove('It:Skip')
}
Context "Basic Validation" {
It "Should be an experimental feature on non-Windows systems" {
$feature = Get-ExperimentalFeature -Name $experimentalFeatureName
if ( $IsWindows ) {
$feature | Should -BeNullOrEmpty
}
else {
$feature.Name | Should -Be $experimentalFeatureName
}
}
It "Should include a UnixStat property" {
$i = Get-Item ${TestDrive}
$i.UnixStat | Should -Not -BeNullOrEmpty
}
It "The UnixStat property should be the correct type" {
$expected = "System.Management.Automation.Platform+Unix+CommonStat"
$i = (get-item /).psobject.properties['UnixStat'].TypeNameOfValue
$i | Should -Be $expected
}
}
Context "Validation of additional properties on file system objects" {
BeforeAll {
if ( $IsWindows ) {
return
}
$testDir = "${TestDrive}/TestDir"
$testFile = "${testDir}/TestFile"
$testCase = @{ Mode = '000'; Perm = '----------'; Item = "${testFile}" },
@{ Mode = '111'; Perm = '---x--x--x'; Item = "${testFile}" },
@{ Mode = '222'; Perm = '--w--w--w-'; Item = "${testFile}" },
@{ Mode = '333'; Perm = '--wx-wx-wx'; Item = "${testFile}" },
@{ Mode = '444'; Perm = '-r--r--r--'; Item = "${testFile}" },
@{ Mode = '555'; Perm = '-r-xr-xr-x'; Item = "${testFile}" },
@{ Mode = '666'; Perm = '-rw-rw-rw-'; Item = "${testFile}" },
@{ Mode = '777'; Perm = '-rwxrwxrwx'; Item = "${testFile}" },
@{ Mode = '4777'; Perm = '-rwsrwxrwx'; Item = "${testFile}" },
@{ Mode = '1777'; Perm = 'drwxrwxrwt'; Item = "${testDir}" }
}
BeforeEach {
$null = New-Item -ItemType Directory -Path "${testDir}"
$null = New-Item -ItemType File -Path "${testFile}"
}
AfterEach {
Remove-Item -Path "${testFile}" -Force
Remove-Item -Path "${testDir}" -Recurse -Force
}
It "Should present filemode '<Mode>' string correctly as '<Perm>'" -testCase $testCase {
param ($Mode, $Perm, $Item )
chmod "$Mode" "${Item}"
$i = Get-Item $Item
$i.UnixMode | Should -Be $Perm
}
It "Should retrieve the user name for the file" {
$i = Get-Item ${testFile}
$user = (/bin/ls -ld $testFile).split(" ",[System.StringSplitOptions]"RemoveEmptyEntries")[2]
$i.User | Should -Be $user
}
It "Should retrieve the group name for the file" {
$i = Get-Item ${testFile}
$group = (/bin/ls -ld $testFile).split(" ",[System.StringSplitOptions]"RemoveEmptyEntries")[3]
$i.Group | Should -Be $Group
}
}
Context "Other properties of UnixStat object" {
BeforeAll {
if ( $IsWindows ) {
return
}
$testDir = "${TestDrive}/TestDir"
$testFile = "${testDir}/TestFile"
$null = New-Item -Type Directory -Path $testDir
$null = New-Item -Type File -Path $testFile
Set-Content -Path ${testFile} -Value "abc"
$expectedFileInode,$permission,$expectedFileLinkCount,$user,$group,$expectedFileSize,$unused =
(/bin/ls -ldi ${testFile}).Split(" ", 7, [System.StringSplitOptions]"RemoveEmptyEntries")
$file = Get-Item ${testFile}
$expectedDirInode,$permission,$expectedDirLinkCount,$user,$group,$expectedDirSize,$unused =
(/bin/ls -ldi ${testDir}).Split(" ", 7, [System.StringSplitOptions]"RemoveEmptyEntries")
$Dir = Get-Item ${testDir}
$testCases =
@{ Expected = $expectedFileInode; Observed = $File.UnixStat.Inode; Title = "FileInode" },
@{ Expected = $expectedFileLinkCount; Observed = $File.UnixStat.HardlinkCount; Title = "FileHardlinkCount" },
@{ Expected = $expectedFileSize; Observed = $File.UnixStat.Size; Title = "FileSize" },
@{ Expected = $expectedDirInode; Observed = $Dir.UnixStat.Inode; Title = "DirInode" },
@{ Expected = $expectedDirLinkCount; Observed = $Dir.UnixStat.HardlinkCount; Title = "DirHardlinkCount" },
@{ Expected = $expectedDirSize; Observed = $Dir.UnixStat.Size; Title = "DirSize" }
}
It "Should have correct values in UnixStat property for '<Title>'" -TestCases $testCases {
param ( $Title, $expected, $observed )
$observed | Should -Be $expected
}
}
}
@@ -15,7 +15,7 @@ Describe "Get-FormatData" -Tags "CI" {
$format.TypeNames | Should -HaveCount 2
$format.TypeNames[0] | Should -BeExactly "System.IO.DirectoryInfo"
$format.TypeNames[1] | Should -BeExactly "System.IO.FileInfo"
$format.FormatViewDefinition | Should -HaveCount 4
$format.FormatViewDefinition | Should -HaveCount ($IsWindows ? 4 : 5)
}
It "Should return nothing for format data requiring '-PowerShellVersion 5.1' and not provided" {
@@ -18,7 +18,7 @@ Describe "Built-in type information tests" -Tag "CI" {
}
It "Should have correct number of built-in type items in type table" {
$types.Count | Should -BeExactly ($IsWindows ? 273 : 271)
$types.Count | Should -BeExactly ($IsWindows ? 273 : 272)
}
It "Should have expected member info for 'System.Diagnostics.ProcessModule'" {