diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
index 85154cffae..dfa5ff300f 100644
--- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
+++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
@@ -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.
+
+ /// Unix specific implementations of required functionality.
internal static class Unix
{
+ private static Dictionary usernameCache = new Dictionary();
+ private static Dictionary groupnameCache = new Dictionary();
+
+ /// The type of a Unix file system item.
+ public enum ItemType
+ {
+ /// The item is a Directory.
+ Directory,
+
+ /// The item is a File.
+ File,
+
+ /// The item is a Symbolic Link.
+ SymbolicLink,
+
+ /// The item is a Block Device.
+ BlockDevice,
+
+ /// The item is a Character Device.
+ CharacterDevice,
+
+ /// The item is a Named Pipe.
+ NamedPipe,
+
+ /// The item is a Socket.
+ Socket,
+ }
+
+ /// The mask to use to retrieve specific mode bits from the mode value in the stat class.
+ public enum StatMask
+ {
+ /// The mask to collect the owner mode.
+ OwnerModeMask = 0x1C0,
+
+ /// The mask to get the owners read bit.
+ OwnerRead = 0x100,
+
+ /// The mask to get the owners write bit.
+ OwnerWrite = 0x080,
+
+ /// The mask to get the owners execute bit.
+ OwnerExecute = 0x040,
+
+ /// The mask to get the group mode.
+ GroupModeMask = 0x038,
+
+ /// The mask to get the group mode.
+ GroupRead = 0x20,
+
+ /// The mask to get the group mode.
+ GroupWrite = 0x10,
+
+ /// The mask to get the group mode.
+ GroupExecute = 0x8,
+
+ /// The mask to get the "other" mode.
+ OtherModeMask = 0x007,
+
+ /// The mask to get the "other" read bit.
+ OtherRead = 0x004,
+
+ /// The mask to get the "other" write bit.
+ OtherWrite = 0x002,
+
+ /// The mask to get the "other" execute bit.
+ OtherExecute = 0x001,
+
+ /// The mask to retrieve the sticky bit.
+ SetStickyMask = 0x200,
+
+ /// The mask to retrieve the setgid bit.
+ SetGidMask = 0x400,
+
+ /// The mask to retrieve the setuid bit.
+ SetUidMask = 0x800,
+ }
+
+ /// The Common Stat class.
+ public class CommonStat
+ {
+ /// The inode of the filesystem item.
+ public long Inode;
+
+ /// The Mode of the filesystem item.
+ public int Mode;
+
+ /// The user id of the filesystem item.
+ public int UserId;
+
+ /// The group id of the filesystem item.
+ public int GroupId;
+
+ /// The number of hard links for the filesystem item.
+ public int HardlinkCount;
+
+ /// The size in bytes of the filesystem item.
+ public long Size;
+
+ /// The last access time of the filesystem item.
+ public DateTime AccessTime;
+
+ /// The last modified time for the filesystem item.
+ public DateTime ModifiedTime;
+
+ /// The last time the status changes for the filesystem item.
+ public DateTime StatusChangeTime;
+
+ /// The block size of the filesystem.
+ public long BlockSize;
+
+ /// The device id of the filesystem item.
+ public int DeviceId;
+
+ /// The number of blocks used by the filesystem item.
+ public int NumberOfBlocks;
+
+ /// The type of the filesystem item.
+ public ItemType ItemType;
+
+ /// Whether the filesystem item has the setuid bit enabled.
+ public bool IsSetUid;
+
+ /// Whether the filesystem item has the setgid bit enabled.
+ public bool IsSetGid;
+
+ /// Whether the filesystem item has the sticky bit enabled. This is only available for directories.
+ 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 modeMap = new Dictionary()
+ {
+ { 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 itemTypeTable = new Dictionary()
+ {
+ { ItemType.BlockDevice, 'b' },
+ { ItemType.CharacterDevice, 'c' },
+ { ItemType.Directory, 'd' },
+ { ItemType.File, '-' },
+ { ItemType.NamedPipe, 'p' },
+ { ItemType.Socket, 's' },
+ { ItemType.SymbolicLink, 'l' },
+ };
+
+ /// Convert the mode to a string which is usable in our formatting.
+ /// The mode converted into a Unix style string similar to the output of ls.
+ 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);
+ }
+
+ ///
+ /// Get the user name. This is used in formatting, but we shouldn't
+ /// do the pinvoke this unless we're going to use it.
+ ///
+ /// The user name.
+ 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;
+ }
+
+ ///
+ /// Get the group name. This is used in formatting, but we shouldn't
+ /// do the pinvoke this unless we're going to use it.
+ ///
+ /// The name of the group.
+ 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);
}
+ /// Is this a hardlink.
+ /// The handle to a file.
+ /// A boolean that represents whether the item is a hardlink.
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;
}
+ /// Determine if the item is a hardlink.
+ /// A FileSystemInfo to check to determine if it is a hardlink.
+ /// A boolean that represents whether the item is a hardlink.
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());
}
+ ///
+ /// Create a managed replica of the native stat structure.
+ ///
+ /// The common stat structure from which we copy.
+ /// A managed common stat class instance.
+ 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;
+ }
+
+ /// Get the lstat info from a path.
+ /// The path to the lstat information.
+ /// An instance of the CommonStat for the path.
+ public static CommonStat GetLStat(string path)
+ {
+ NativeMethods.CommonStatStruct css;
+ if (NativeMethods.GetCommonLStat(path, out css) == 0)
+ {
+ return CopyStatStruct(css);
+ }
+
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ /// Get the stat info from a path.
+ /// The path to the stat information.
+ /// An instance of the CommonStat for the path.
+ public static CommonStat GetStat(string path)
+ {
+ NativeMethods.CommonStatStruct css;
+ if (NativeMethods.GetCommonStat(path, out css) == 0)
+ {
+ return CopyStatStruct(css);
+ }
+
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ /// Read the /proc file system for information about the parent.
+ /// The process id used to get the parent process.
+ /// The process id.
public static int GetProcFSParentPid(int pid)
{
const int invalidPid = -1;
+
// read /proc//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
}
}
+ /// The native methods class.
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
+ // This is a struct tm from .
[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 */
+ /// Seconds (0-60).
+ internal int tm_sec;
+
+ /// Minutes (0-59).
+ internal int tm_min;
+
+ /// Hours (0-23).
+ internal int tm_hour;
+
+ /// Day of the month (1-31).
+ internal int tm_mday;
+
+ /// Month (0-11).
+ internal int tm_mon;
+
+ /// The year - 1900.
+ internal int tm_year;
+
+ /// Day of the week (0-6, Sunday = 0).
+ internal int tm_wday;
+
+ /// Day in the year (0-365, 1 Jan = 0).
+ internal int tm_yday;
+
+ /// Daylight saving time.
+ 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);
+
+ ///
+ /// 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.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct CommonStatStruct
+ {
+ /// The inode of the filesystem item.
+ internal long Inode;
+
+ /// The mode of the filesystem item.
+ internal int Mode;
+
+ /// The user id of the filesystem item.
+ internal int UserId;
+
+ /// The group id of the filesystem item.
+ internal int GroupId;
+
+ /// The number of hard links to the filesystem item.
+ internal int HardlinkCount;
+
+ /// The size in bytes of the filesystem item.
+ internal long Size;
+
+ /// The time of the last access for the filesystem item.
+ internal long AccessTime;
+
+ /// The time of the last modification for the filesystem item.
+ internal long ModifiedTime;
+
+ /// The time of the last status change for the filesystem item.
+ internal long StatusChangeTime;
+
+ /// The size in bytes of the file system.
+ internal long BlockSize;
+
+ /// The device id for the filesystem item.
+ internal int DeviceId;
+
+ /// The number of filesystem blocks that the filesystem item uses.
+ internal int NumberOfBlocks;
+
+ /// This filesystem item is a directory.
+ internal int IsDirectory;
+
+ /// This filesystem item is a file.
+ internal int IsFile;
+
+ /// This filesystem item is a symbolic link.
+ internal int IsSymbolicLink;
+
+ /// This filesystem item is a block device.
+ internal int IsBlockDevice;
+
+ /// This filesystem item is a character device.
+ internal int IsCharacterDevice;
+
+ /// This filesystem item is a named pipe.
+ internal int IsNamedPipe;
+
+ /// This filesystem item is a socket.
+ internal int IsSocket;
+
+ /// This filesystem item will run as the the owner if executed.
+ internal int IsSetUid;
+
+ /// This filesystem item will run as the the group if executed.
+ internal int IsSetGid;
+
+ /// Whether the sticky bit is set on the filesystem item.
+ 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);
}
}
}
diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs
index b65fde9eb6..0346a09418 100644
--- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs
+++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs
@@ -41,6 +41,30 @@ namespace System.Management.Automation.Runspaces
private static IEnumerable 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])
diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj
index 75454c62d0..605eb6e700 100644
--- a/src/System.Management.Automation/System.Management.Automation.csproj
+++ b/src/System.Management.Automation/System.Management.Automation.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
index 95824a9992..9313e72e7e 100644
--- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
+++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs
@@ -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")
diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
index 257a9b79d3..be3bf66889 100644
--- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
+++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs
@@ -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)
{
diff --git a/src/System.Management.Automation/namespaces/ProviderBase.cs b/src/System.Management.Automation/namespaces/ProviderBase.cs
index 5d706b10d3..4b9e46ab59 100644
--- a/src/System.Management.Automation/namespaces/ProviderBase.cs
+++ b/src/System.Management.Automation/namespaces/ProviderBase.cs
@@ -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
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1
new file mode 100644
index 0000000000..c6df456615
--- /dev/null
+++ b/test/powershell/Modules/Microsoft.PowerShell.Management/UnixStat.Tests.ps1
@@ -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 '' string correctly as ''" -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 ''" -TestCases $testCases {
+ param ( $Title, $expected, $observed )
+
+ $observed | Should -Be $expected
+
+ }
+ }
+
+}
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1
index 5c50e448f1..e4f71441bc 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FormatData.Tests.ps1
@@ -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" {
diff --git a/test/powershell/engine/ETS/TypeTable.Tests.ps1 b/test/powershell/engine/ETS/TypeTable.Tests.ps1
index deccc8c4a3..4b8fd759c4 100644
--- a/test/powershell/engine/ETS/TypeTable.Tests.ps1
+++ b/test/powershell/engine/ETS/TypeTable.Tests.ps1
@@ -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'" {