Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
CorePsPlatform.cs1127 linesDownload Raw Back to CoreCLR
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections.Generic;5using System.ComponentModel;6using System.IO;7using System.Runtime.InteropServices;8using System.Management.Automation.Internal;9using Microsoft.Win32;10 11namespace System.Management.Automation12{13    /// <summary>14    /// These are platform abstractions and platform specific implementations.15    /// </summary>16    public static partial class Platform17    {18        /// <summary>19        /// True if the current platform is Linux.20        /// </summary>21        public static bool IsLinux22        {23            get24            {25                return OperatingSystem.IsLinux();26            }27        }28 29        /// <summary>30        /// True if the current platform is macOS.31        /// </summary>32        public static bool IsMacOS33        {34            get35            {36                return OperatingSystem.IsMacOS();37            }38        }39 40        /// <summary>41        /// True if the current platform is Windows.42        /// </summary>43        public static bool IsWindows44        {45            get46            {47                return OperatingSystem.IsWindows();48            }49        }50 51        /// <summary>52        /// True if PowerShell was built targeting .NET Core.53        /// </summary>54        public static bool IsCoreCLR55        {56            get57            {58                return true;59            }60        }61 62        /// <summary>63        /// True if the underlying system is NanoServer.64        /// </summary>65        public static bool IsNanoServer66        {67            get68            {69#if UNIX70                return false;71#else72                if (_isNanoServer.HasValue)73                {74                    return _isNanoServer.Value;75                }76 77                _isNanoServer = false;78                using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels"))79                {80                    if (regKey != null)81                    {82                        object value = regKey.GetValue("NanoServer");83                        if (value != null && regKey.GetValueKind("NanoServer") == RegistryValueKind.DWord)84                        {85                            _isNanoServer = (int)value == 1;86                        }87                    }88                }89 90                return _isNanoServer.Value;91#endif92            }93        }94 95        /// <summary>96        /// True if the underlying system is IoT.97        /// </summary>98        public static bool IsIoT99        {100            get101            {102#if UNIX103                return false;104#else105                if (_isIoT.HasValue)106                {107                    return _isIoT.Value;108                }109 110                _isIoT = false;111                using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"))112                {113                    if (regKey != null)114                    {115                        object value = regKey.GetValue("ProductName");116                        if (value != null && regKey.GetValueKind("ProductName") == RegistryValueKind.String)117                        {118                            _isIoT = string.Equals("IoTUAP", (string)value, StringComparison.OrdinalIgnoreCase);119                        }120                    }121                }122 123                return _isIoT.Value;124#endif125            }126        }127 128        /// <summary>129        /// True if underlying system is Windows Desktop.130        /// </summary>131        public static bool IsWindowsDesktop132        {133            get134            {135#if UNIX136                return false;137#else138                if (_isWindowsDesktop.HasValue)139                {140                    return _isWindowsDesktop.Value;141                }142 143                _isWindowsDesktop = !IsNanoServer && !IsIoT;144                return _isWindowsDesktop.Value;145#endif146            }147        }148 149        /// <summary>150        /// Gets a value indicating whether the underlying system supports single-threaded apartment.151        /// </summary>152        public static bool IsStaSupported153        {154            get155            {156#if UNIX157                return false;158#else159                return _isStaSupported.Value;160#endif161            }162        }163 164#if UNIX165        // Gets the location for cache and config folders.166        internal static readonly string CacheDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CACHE);167        internal static readonly string ConfigDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG);168#else169        // Gets the location for cache and config folders.170        internal static readonly string CacheDirectory = SafeDeriveFromSpecialFolder(171            Environment.SpecialFolder.LocalApplicationData,172            @"Microsoft\PowerShell");173 174        internal static readonly string ConfigDirectory = SafeDeriveFromSpecialFolder(175            Environment.SpecialFolder.Personal,176            @"PowerShell");177 178        private static readonly Lazy<bool> _isStaSupported = new Lazy<bool>(() =>179        {180            int result = Interop.Windows.CoInitializeEx(IntPtr.Zero, Interop.Windows.COINIT_APARTMENTTHREADED);181 182            // If 0 is returned the thread has been initialized for the first time183            // as an STA and thus supported and needs to be uninitialized.184            if (result > 0)185            {186                Interop.Windows.CoUninitialize();187            }188 189            return result != Interop.Windows.E_NOTIMPL;190        });191 192        private static bool? _isNanoServer = null;193        private static bool? _isIoT = null;194        private static bool? _isWindowsDesktop = null;195#endif196 197        internal static bool TryDeriveFromCache(string path1, out string result)198        {199            if (CacheDirectory is null or [])200            {201                result = null;202                return false;203            }204 205            result = Path.Combine(CacheDirectory, path1);206            return true;207        }208 209        internal static bool TryDeriveFromCache(string path1, string path2, out string result)210        {211            if (CacheDirectory is null or [])212            {213                result = null;214                return false;215            }216 217            result = Path.Combine(CacheDirectory, path1, path2);218            return true;219        }220 221        // format files222        internal static readonly string[] FormatFileNames = new string[]223        {224            "Certificate.format.ps1xml",225            "Diagnostics.format.ps1xml",226            "DotNetTypes.format.ps1xml",227            "Event.format.ps1xml",228            "FileSystem.format.ps1xml",229            "Help.format.ps1xml",230            "HelpV3.format.ps1xml",231            "PowerShellCore.format.ps1xml",232            "PowerShellTrace.format.ps1xml",233            "Registry.format.ps1xml",234            "WSMan.format.ps1xml"235        };236 237        /// <summary>238        /// Some common environment variables used in PS have different239        /// names in different OS platforms.240        /// </summary>241        internal static class CommonEnvVariableNames242        {243#if UNIX244            internal const string Home = "HOME";245#else246            internal const string Home = "USERPROFILE";247#endif248        }249 250        private static string SafeDeriveFromSpecialFolder(Environment.SpecialFolder specialFolder, string subPath)251        {252            string basePath = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.DoNotVerify);253            if (string.IsNullOrWhiteSpace(basePath))254            {255                return string.Empty;256            }257 258            return Path.Join(basePath, subPath);259        }260 261#if UNIX262        private static string s_tempHome = null;263 264        /// <summary>265        /// Get the 'HOME' environment variable or create a temporary home directory if the environment variable is not set.266        /// </summary>267        private static string GetHomeOrCreateTempHome()268        {269            const string tempHomeFolderName = "pwsh-{0}-98288ff9-5712-4a14-9a11-23693b9cd91a";270 271            string envHome = Environment.GetEnvironmentVariable("HOME") ?? s_tempHome;272            if (envHome is not null)273            {274                return envHome;275            }276 277            try278            {279                s_tempHome = Path.Combine(Path.GetTempPath(), StringUtil.Format(tempHomeFolderName, Environment.UserName));280                Directory.CreateDirectory(s_tempHome);281            }282            catch (UnauthorizedAccessException)283            {284                // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts.285                // Return an empty string in this case so the process working directory will be used.286                s_tempHome = string.Empty;287            }288 289            return s_tempHome;290        }291 292        /// <summary>293        /// X Desktop Group configuration type enum.294        /// </summary>295        public enum XDG_Type296        {297            /// <summary> XDG_CONFIG_HOME/powershell </summary>298            CONFIG,299            /// <summary> XDG_CACHE_HOME/powershell </summary>300            CACHE,301            /// <summary> XDG_DATA_HOME/powershell </summary>302            DATA,303            /// <summary> XDG_DATA_HOME/powershell/Modules </summary>304            USER_MODULES,305            /// <summary> /usr/local/share/powershell/Modules </summary>306            SHARED_MODULES,307            /// <summary> XDG_CONFIG_HOME/powershell </summary>308            DEFAULT309        }310 311        /// <summary>312        /// Function for choosing directory location of PowerShell for profile loading.313        /// </summary>314        public static string SelectProductNameForDirectory(XDG_Type dirpath)315        {316            // TODO: XDG_DATA_DIRS implementation as per GitHub issue #1060317 318            string xdgconfighome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");319            string xdgdatahome = Environment.GetEnvironmentVariable("XDG_DATA_HOME");320            string xdgcachehome = Environment.GetEnvironmentVariable("XDG_CACHE_HOME");321            string envHome = GetHomeOrCreateTempHome();322 323            string xdgConfigHomeDefault = Path.Combine(envHome, ".config", "powershell");324            string xdgDataHomeDefault = Path.Combine(envHome, ".local", "share", "powershell");325            string xdgModuleDefault = Path.Combine(xdgDataHomeDefault, "Modules");326            string xdgCacheDefault = Path.Combine(envHome, ".cache", "powershell");327 328            try329            {330                switch (dirpath)331                {332                    case XDG_Type.CONFIG:333                        // Use 'XDG_CONFIG_HOME' if it's set, otherwise use the default path.334                        return string.IsNullOrEmpty(xdgconfighome)335                            ? xdgConfigHomeDefault336                            : Path.Combine(xdgconfighome, "powershell");337 338                    case XDG_Type.DATA:339                        // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path.340                        if (string.IsNullOrEmpty(xdgdatahome))341                        {342                            // Create the default data directory if it doesn't exist.343                            Directory.CreateDirectory(xdgDataHomeDefault);344                            return xdgDataHomeDefault;345                        }346                        return Path.Combine(xdgdatahome, "powershell");347 348                    case XDG_Type.USER_MODULES:349                        // Use 'XDG_DATA_HOME' if it's set, otherwise use the default path.350                        if (string.IsNullOrEmpty(xdgdatahome))351                        {352                            Directory.CreateDirectory(xdgModuleDefault);353                            return xdgModuleDefault;354                        }355                        return Path.Combine(xdgdatahome, "powershell", "Modules");356 357                    case XDG_Type.SHARED_MODULES:358                        return "/usr/local/share/powershell/Modules";359 360                    case XDG_Type.CACHE:361                        // Use 'XDG_CACHE_HOME' if it's set, otherwise use the default path.362                        if (string.IsNullOrEmpty(xdgcachehome))363                        {364                            Directory.CreateDirectory(xdgCacheDefault);365                            return xdgCacheDefault;366                        }367 368                        string cachePath = Path.Combine(xdgcachehome, "powershell");369                        Directory.CreateDirectory(cachePath);370                        return cachePath;371 372                    case XDG_Type.DEFAULT:373                        // Use 'xdgConfigHomeDefault' for 'XDG_Type.DEFAULT' and create the directory if it doesn't exist.374                        Directory.CreateDirectory(xdgConfigHomeDefault);375                        return xdgConfigHomeDefault;376 377                    default:378                        throw new InvalidOperationException("Unreachable code.");379                }380            }381            catch (UnauthorizedAccessException)382            {383                // Directory creation may fail if the account doesn't have filesystem permission such as some service accounts.384                // Return an empty string in this case so the process working directory will be used.385                return string.Empty;386            }387        }388#endif389 390        /// <summary>391        /// Mimic 'Environment.GetFolderPath(folder)' on Unix.392        /// </summary>393        internal static string GetFolderPath(Environment.SpecialFolder folder)394        {395#if UNIX396            return folder switch397            {398                Environment.SpecialFolder.ProgramFiles => Directory.Exists("/bin") ? "/bin" : string.Empty,399                Environment.SpecialFolder.MyDocuments => GetHomeOrCreateTempHome(),400                _ => throw new NotSupportedException()401            };402#else403            return Environment.GetFolderPath(folder, Environment.SpecialFolderOption.DoNotVerify);404#endif405        }406 407        // Platform methods prefixed NonWindows are:408        // - non-windows by the definition of the IsWindows method above409        // - here, because porting to Linux and other operating systems410        //   should not move the original Windows code out of the module411        //   it belongs to, so this way the windows code can remain in it's412        //   original source file and only the non-windows code has been moved413        //   out here414        // - only to be used with the IsWindows feature query, and only if415        //   no other more specific feature query makes sense416 417        internal static bool NonWindowsIsHardLink(FileSystemInfo fileInfo)418        {419            return Unix.IsHardLink(fileInfo);420        }421 422        internal static string NonWindowsGetUserFromPid(int path)423        {424            return Unix.NativeMethods.GetUserFromPid(path);425        }426 427        internal static string NonWindowsInternalGetLinkType(FileSystemInfo fileInfo)428        {429            if (fileInfo.Attributes.HasFlag(System.IO.FileAttributes.ReparsePoint))430            {431                return "SymbolicLink";432            }433 434            if (NonWindowsIsHardLink(fileInfo))435            {436                return "HardLink";437            }438 439            return null;440        }441 442        internal static bool NonWindowsCreateSymbolicLink(string path, string target)443        {444            // Linux doesn't care if target is a directory or not445            return Unix.NativeMethods.CreateSymLink(path, target) == 0;446        }447 448        internal static bool NonWindowsCreateHardLink(string path, string strTargetPath)449        {450            return Unix.NativeMethods.CreateHardLink(path, strTargetPath) == 0;451        }452 453        internal static unsafe bool NonWindowsSetDate(DateTime dateToUse)454        {455            Unix.NativeMethods.UnixTm tm = Unix.NativeMethods.DateTimeToUnixTm(dateToUse);456            return Unix.NativeMethods.SetDate(&tm) == 0;457        }458 459        internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathTwo)460        {461            return Unix.NativeMethods.IsSameFileSystemItem(pathOne, pathTwo);462        }463 464        internal static bool NonWindowsGetInodeData(string path, out ValueTuple<ulong, ulong> inodeData)465        {466            var result = Unix.NativeMethods.GetInodeData(path, out ulong device, out ulong inode);467 468            inodeData = (device, inode);469            return result == 0;470        }471 472        internal static bool NonWindowsIsExecutable(string path)473        {474            return Unix.NativeMethods.IsExecutable(path);475        }476 477        internal static uint NonWindowsGetThreadId()478        {479            return Unix.NativeMethods.GetCurrentThreadId();480        }481 482        internal static int NonWindowsGetProcessParentPid(int pid)483        {484            return IsMacOS ? Unix.NativeMethods.GetPPid(pid) : Unix.GetProcFSParentPid(pid);485        }486 487        internal static bool NonWindowsKillProcess(int pid)488        {489            return Unix.NativeMethods.KillProcess(pid);490        }491 492        internal static int NonWindowsWaitPid(int pid, bool nohang)493        {494            return Unix.NativeMethods.WaitPid(pid, nohang);495        }496 497        // Please note that `Win32Exception(Marshal.GetLastWin32Error())`498        // works *correctly* on Linux in that it creates an exception with499        // the string perror would give you for the last set value of errno.500        // No manual mapping is required. .NET Core maps the Linux errno501        // to a PAL value and calls strerror_r underneath to generate the message.502 503        /// <summary>Unix specific implementations of required functionality.</summary>504        internal static partial class Unix505        {506            private static readonly Dictionary<int, string> usernameCache = new();507            private static readonly Dictionary<int, string> groupnameCache = new();508 509            /// <summary>The type of a Unix file system item.</summary>510            public enum ItemType511            {512                /// <summary>The item is a Directory.</summary>513                Directory,514 515                /// <summary>The item is a File.</summary>516                File,517 518                /// <summary>The item is a Symbolic Link.</summary>519                SymbolicLink,520 521                /// <summary>The item is a Block Device.</summary>522                BlockDevice,523 524                /// <summary>The item is a Character Device.</summary>525                CharacterDevice,526 527                /// <summary>The item is a Named Pipe.</summary>528                NamedPipe,529 530                /// <summary>The item is a Socket.</summary>531                Socket,532            }533 534            /// <summary>The mask to use to retrieve specific mode bits from the mode value in the stat class.</summary>535            public enum StatMask536            {537                /// <summary>The mask to collect the owner mode.</summary>538                OwnerModeMask = 0x1C0,539 540                /// <summary>The mask to get the owners read bit.</summary>541                OwnerRead = 0x100,542 543                /// <summary>The mask to get the owners write bit.</summary>544                OwnerWrite = 0x080,545 546                /// <summary>The mask to get the owners execute bit.</summary>547                OwnerExecute = 0x040,548 549                /// <summary>The mask to get the group mode.</summary>550                GroupModeMask = 0x038,551 552                /// <summary>The mask to get the group mode.</summary>553                GroupRead = 0x20,554 555                /// <summary>The mask to get the group mode.</summary>556                GroupWrite = 0x10,557 558                /// <summary>The mask to get the group mode.</summary>559                GroupExecute = 0x8,560 561                /// <summary>The mask to get the "other" mode.</summary>562                OtherModeMask = 0x007,563 564                /// <summary>The mask to get the "other" read bit.</summary>565                OtherRead = 0x004,566 567                /// <summary>The mask to get the "other" write bit.</summary>568                OtherWrite = 0x002,569 570                /// <summary>The mask to get the "other" execute bit.</summary>571                OtherExecute = 0x001,572 573                /// <summary>The mask to retrieve the sticky bit.</summary>574                SetStickyMask = 0x200,575 576                /// <summary>The mask to retrieve the setgid bit.</summary>577                SetGidMask = 0x400,578 579                /// <summary>The mask to retrieve the setuid bit.</summary>580                SetUidMask = 0x800,581            }582 583            /// <summary>The Common Stat class.</summary>584            public class CommonStat585            {586                /// <summary>The inode of the filesystem item.</summary>587                public long Inode;588 589                /// <summary>The Mode of the filesystem item.</summary>590                public int Mode;591 592                /// <summary>The user id of the filesystem item.</summary>593                public int UserId;594 595                /// <summary>The group id of the filesystem item.</summary>596                public int GroupId;597 598                /// <summary>The number of hard links for the filesystem item.</summary>599                public int HardlinkCount;600 601                /// <summary>The size in bytes of the filesystem item.</summary>602                public long Size;603 604                /// <summary>The last access time of the filesystem item.</summary>605                public DateTime AccessTime;606 607                /// <summary>The last modified time for the filesystem item.</summary>608                public DateTime ModifiedTime;609 610                /// <summary>The last time the status changes for the filesystem item.</summary>611                public DateTime StatusChangeTime;612 613                /// <summary>The block size of the filesystem.</summary>614                public long BlockSize;615 616                /// <summary>The device id of the filesystem item.</summary>617                public int DeviceId;618 619                /// <summary>The number of blocks used by the filesystem item.</summary>620                public int NumberOfBlocks;621 622                /// <summary>The type of the filesystem item.</summary>623                public ItemType ItemType;624 625                /// <summary>Whether the filesystem item has the setuid bit enabled.</summary>626                public bool IsSetUid;627 628                /// <summary>Whether the filesystem item has the setgid bit enabled.</summary>629                public bool IsSetGid;630 631                /// <summary>Whether the filesystem item has the sticky bit enabled. This is only available for directories.</summary>632                public bool IsSticky;633 634                private const char CanRead = 'r';635                private const char CanWrite = 'w';636                private const char CanExecute = 'x';637                private const char NoPerm = '-';638                private const char SetAndExec = 's';639                private const char SetAndNotExec = 'S';640                private const char StickyAndExec = 't';641                private const char StickyAndNotExec = 'T';642 643                // The item type and the character representation for the first element in the stat string644                private static readonly Dictionary<ItemType, char> itemTypeTable = new()645                {646                    { ItemType.BlockDevice,     'b' },647                    { ItemType.CharacterDevice, 'c' },648                    { ItemType.Directory,       'd' },649                    { ItemType.File,            '-' },650                    { ItemType.NamedPipe,       'p' },651                    { ItemType.Socket,          's' },652                    { ItemType.SymbolicLink,    'l' },653                };654 655                // We'll create a few common mode strings here to reduce allocations and improve performance a bit.656                private const string OwnerReadGroupReadOtherRead = "-r--r--r--";657                private const string OwnerReadWriteGroupReadOtherRead = "-rw-r--r--";658                private const string DirectoryOwnerFullGroupReadExecOtherReadExec = "drwxr-xr-x";659 660                /// <summary>Convert the mode to a string which is usable in our formatting.</summary>661                /// <returns>The mode converted into a Unix style string similar to the output of ls.</returns>662                public string GetModeString()663                {664                    // On an Ubuntu system (docker), these 3 are roughly 70% of all the permissions665                    if ((Mode & 0xFFF) == 292)666                    {667                        return OwnerReadGroupReadOtherRead;668                    }669 670                    if ((Mode & 0xFFF) == 420)671                    {672                       return OwnerReadWriteGroupReadOtherRead;673                    }674 675                    if (ItemType == ItemType.Directory & (Mode & 0xFFF) == 493)676                    {677                        return DirectoryOwnerFullGroupReadExecOtherReadExec;678                    }679 680                    UnixFileMode modeInfo = (UnixFileMode)Mode;681 682                    Span<char> modeCharacters = [683                        itemTypeTable[ItemType],684 685                        modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm,686                        modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm,687                        modeInfo.HasFlag(UnixFileMode.SetUser) ?688                            (modeInfo.HasFlag(UnixFileMode.UserExecute) ? SetAndExec : SetAndNotExec) :689                            (modeInfo.HasFlag(UnixFileMode.UserExecute) ? CanExecute : NoPerm),690 691                        modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm,692                        modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm,693                        modeInfo.HasFlag(UnixFileMode.SetGroup) ?694                            (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? SetAndExec : SetAndNotExec) :695                            (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? CanExecute : NoPerm),696 697                        modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm,698                        modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm,699                        modeInfo.HasFlag(UnixFileMode.StickyBit) ?700                            (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? StickyAndExec : StickyAndNotExec) :701                            (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? CanExecute : NoPerm),702                    ];703 704                    return new string(modeCharacters);705                }706 707                /// <summary>708                /// Get the user name. This is used in formatting, but we shouldn't709                /// do the pinvoke this unless we're going to use it.710                /// </summary>711                /// <returns>The user name.</returns>712                public string GetUserName()713                {714                    if (usernameCache.TryGetValue(UserId, out string username))715                    {716                        return username;717                    }718 719                    // Get and add the user name to the cache so we don't need to720                    // have a pinvoke for each file.721                    username = NativeMethods.GetPwUid(UserId);722                    usernameCache.Add(UserId, username);723 724                    return username;725                }726 727                /// <summary>728                /// Get the group name. This is used in formatting, but we shouldn't729                /// do the pinvoke this unless we're going to use it.730                /// </summary>731                /// <returns>The name of the group.</returns>732                public string GetGroupName()733                {734                    if (groupnameCache.TryGetValue(GroupId, out string groupname))735                    {736                        return groupname;737                    }738 739                    // Get and add the group name to the cache so we don't need to740                    // have a pinvoke for each file.741                    groupname = NativeMethods.GetGrGid(GroupId);742                    groupnameCache.Add(GroupId, groupname);743 744                    return groupname;745                }746            }747 748            // This is a helper that attempts to map errno into a PowerShell ErrorCategory749            internal static ErrorCategory GetErrorCategory(int errno)750            {751                return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno);752            }753 754            /// <summary>Determine if the item is a hardlink.</summary>755            /// <param name="fs">A FileSystemInfo to check to determine if it is a hardlink.</param>756            /// <returns>A boolean that represents whether the item is a hardlink.</returns>757            public static bool IsHardLink(FileSystemInfo fs)758            {759                if (!fs.Exists || (fs.Attributes & FileAttributes.Directory) == FileAttributes.Directory)760                {761                    return false;762                }763 764                int count;765                string filePath = fs.FullName;766                int ret = NativeMethods.GetLinkCount(filePath, out count);767                if (ret == 0)768                {769                    return count > 1;770                }771 772                throw new Win32Exception(Marshal.GetLastWin32Error());773            }774 775            /// <summary>776            /// Create a managed replica of the native stat structure.777            /// </summary>778            /// <param name="css">The common stat structure from which we copy.</param>779            /// <returns>A managed common stat class instance.</returns>780            private static CommonStat CopyStatStruct(NativeMethods.CommonStatStruct css)781            {782                CommonStat cs = new();783                cs.Inode = css.Inode;784                cs.Mode = css.Mode;785                cs.UserId = css.UserId;786                cs.GroupId = css.GroupId;787                cs.HardlinkCount = css.HardlinkCount;788                cs.Size = css.Size;789 790                // These can sometime throw if we get too large a number back (seen on Raspbian).791                // As a fallback, set the time to UnixEpoch.792                try793                {794                    cs.AccessTime = DateTime.UnixEpoch.AddSeconds(css.AccessTime).ToLocalTime();795                }796                catch797                {798                    cs.AccessTime = DateTime.UnixEpoch.ToLocalTime();799                }800 801                try802                {803                    cs.ModifiedTime = DateTime.UnixEpoch.AddSeconds(css.ModifiedTime).ToLocalTime();804                }805                catch806                {807                    cs.ModifiedTime = DateTime.UnixEpoch.ToLocalTime();808                }809 810                try811                {812                    cs.StatusChangeTime = DateTime.UnixEpoch.AddSeconds(css.StatusChangeTime).ToLocalTime();813                }814                catch815                {816                    cs.StatusChangeTime = DateTime.UnixEpoch.ToLocalTime();817                }818 819                cs.BlockSize = css.BlockSize;820                cs.DeviceId = css.DeviceId;821                cs.NumberOfBlocks = css.NumberOfBlocks;822 823                if (css.IsDirectory == 1)824                {825                    cs.ItemType = ItemType.Directory;826                }827                else if (css.IsFile == 1)828                {829                    cs.ItemType = ItemType.File;830                }831                else if (css.IsSymbolicLink == 1)832                {833                    cs.ItemType = ItemType.SymbolicLink;834                }835                else if (css.IsBlockDevice == 1)836                {837                    cs.ItemType = ItemType.BlockDevice;838                }839                else if (css.IsCharacterDevice == 1)840                {841                    cs.ItemType = ItemType.CharacterDevice;842                }843                else if (css.IsNamedPipe == 1)844                {845                    cs.ItemType = ItemType.NamedPipe;846                }847                else848                {849                    cs.ItemType = ItemType.Socket;850                }851 852                cs.IsSetUid = css.IsSetUid == 1;853                cs.IsSetGid = css.IsSetGid == 1;854                cs.IsSticky = css.IsSticky == 1;855 856                return cs;857            }858 859            /// <summary>Get the lstat info from a path.</summary>860            /// <param name="path">The path to the lstat information.</param>861            /// <returns>An instance of the CommonStat for the path.</returns>862            public static CommonStat GetLStat(string path)863            {864                NativeMethods.CommonStatStruct css;865                if (NativeMethods.GetCommonLStat(path, out css) == 0)866                {867                    return CopyStatStruct(css);868                }869 870                throw new Win32Exception(Marshal.GetLastWin32Error());871            }872 873            /// <summary>Get the stat info from a path.</summary>874            /// <param name="path">The path to the stat information.</param>875            /// <returns>An instance of the CommonStat for the path.</returns>876            public static CommonStat GetStat(string path)877            {878                NativeMethods.CommonStatStruct css;879                if (NativeMethods.GetCommonStat(path, out css) == 0)880                {881                    return CopyStatStruct(css);882                }883 884                throw new Win32Exception(Marshal.GetLastWin32Error());885            }886 887            /// <summary>Read the /proc file system for information about the parent.</summary>888            /// <param name="pid">The process id used to get the parent process.</param>889            /// <returns>The process id.</returns>890            public static int GetProcFSParentPid(int pid)891            {892                const int invalidPid = -1;893 894                // read /proc/<pid>/status895                // Row beginning with PPid: \d is the parent process id.896                // This used to check /proc/<pid>/stat but that file was meant897                // to be a space delimited line but it contains a value which898                // could contain spaces itself. Using the status file is a lot899                // simpler because each line contains a record with a simple900                // label.901                // https://github.com/PowerShell/PowerShell/issues/17541#issuecomment-1159911577902                var path = $"/proc/{pid}/status";903                try904                {905                    using FileStream fs = File.OpenRead(path);906                    using StreamReader sr = new(fs);907                    string line;908                    while ((line = sr.ReadLine()) != null)909                    {910                        if (!line.StartsWith("PPid:\t", StringComparison.OrdinalIgnoreCase))911                        {912                            continue;913                        }914 915                        string[] lineSplit = line.Split('\t', 2, StringSplitOptions.RemoveEmptyEntries);916                        if (lineSplit.Length != 2)917                        {918                            continue;919                        }920 921                        if (int.TryParse(lineSplit[1].Trim(), out var ppid))922                        {923                            return ppid;924                        }925                    }926 927                    return invalidPid;928                }929                catch (Exception)930                {931                    return invalidPid;932                }933            }934 935            /// <summary>The native methods class.</summary>936            internal static partial class NativeMethods937            {938                private const string psLib = "libpsl-native";939 940                // Ansi is a misnomer, it is hardcoded to UTF-8 on Linux and macOS941                // C bools are 1 byte and so must be marshalled as I1942 943                [LibraryImport(psLib)]944                internal static partial int GetErrorCategory(int errno);945 946                [LibraryImport(psLib)]947                internal static partial int GetPPid(int pid);948 949                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]950                internal static partial int GetLinkCount(string filePath, out int linkCount);951 952                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]953                [return: MarshalAs(UnmanagedType.I1)]954                internal static partial bool IsExecutable(string filePath);955 956                [LibraryImport(psLib)]957                internal static partial uint GetCurrentThreadId();958 959                [LibraryImport(psLib)]960                [return: MarshalAs(UnmanagedType.Bool)]961                internal static partial bool KillProcess(int pid);962 963                [LibraryImport(psLib)]964                internal static partial int WaitPid(int pid, [MarshalAs(UnmanagedType.Bool)] bool nohang);965 966                // This is the struct `private_tm` from setdate.h in libpsl-native.967                // Packing is set to 4 to match the unmanaged declaration.968                // https://github.com/PowerShell/PowerShell-Native/blob/c5575ceb064e60355b9fee33eabae6c6d2708d14/src/libpsl-native/src/setdate.h#L23969                [StructLayout(LayoutKind.Sequential, Pack = 4)]970                internal unsafe struct UnixTm971                {972                    /// <summary>Seconds (0-60).</summary>973                    internal int tm_sec;974 975                    /// <summary>Minutes (0-59).</summary>976                    internal int tm_min;977 978                    /// <summary>Hours (0-23).</summary>979                    internal int tm_hour;980 981                    /// <summary>Day of the month (1-31).</summary>982                    internal int tm_mday;983 984                    /// <summary>Month (0-11).</summary>985                    internal int tm_mon;986 987                    /// <summary>The year - 1900.</summary>988                    internal int tm_year;989 990                    /// <summary>Day of the week (0-6, Sunday = 0).</summary>991                    internal int tm_wday;992 993                    /// <summary>Day in the year (0-365, 1 Jan = 0).</summary>994                    internal int tm_yday;995 996                    /// <summary>Daylight saving time.</summary>997                    internal int tm_isdst;998                }999 1000                // We need a way to convert a DateTime to a unix date.1001                internal static UnixTm DateTimeToUnixTm(DateTime date)1002                {1003                    UnixTm tm;1004                    tm.tm_sec = date.Second;1005                    tm.tm_min = date.Minute;1006                    tm.tm_hour = date.Hour;1007                    tm.tm_mday = date.Day;1008                    tm.tm_mon = date.Month - 1; // needs to be 0 indexed1009                    tm.tm_year = date.Year - 1900; // years since 19001010                    tm.tm_wday = 0; // this is ignored by mktime1011                    tm.tm_yday = 0; // this is also ignored1012                    tm.tm_isdst = date.IsDaylightSavingTime() ? 1 : 0;1013                    return tm;1014                }1015 1016                [LibraryImport(psLib, SetLastError = true)]1017                internal static unsafe partial int SetDate(UnixTm* tm);1018 1019                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1020                internal static partial int CreateSymLink(string filePath, string target);1021 1022                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1023                internal static partial int CreateHardLink(string filePath, string target);1024 1025                [LibraryImport(psLib)]1026                [return: MarshalAs(UnmanagedType.LPStr)]1027                internal static partial string GetUserFromPid(int pid);1028 1029                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1030                [return: MarshalAs(UnmanagedType.I1)]1031                internal static partial bool IsSameFileSystemItem(string filePathOne, string filePathTwo);1032 1033                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1034                internal static partial int GetInodeData(string path, out ulong device, out ulong inode);1035 1036                /// <summary>1037                /// This is a struct from getcommonstat.h in the native library.1038                /// It presents each member of the stat structure as the largest type of that member across1039                /// all stat structures on the platforms we support. This allows us to present a common1040                /// stat structure for all our platforms.1041                /// </summary>1042                [StructLayout(LayoutKind.Sequential)]1043                internal struct CommonStatStruct1044                {1045                    /// <summary>The inode of the filesystem item.</summary>1046                    internal long Inode;1047 1048                    /// <summary>The mode of the filesystem item.</summary>1049                    internal int Mode;1050 1051                    /// <summary>The user id of the filesystem item.</summary>1052                    internal int UserId;1053 1054                    /// <summary>The group id of the filesystem item.</summary>1055                    internal int GroupId;1056 1057                    /// <summary>The number of hard links to the filesystem item.</summary>1058                    internal int HardlinkCount;1059 1060                    /// <summary>The size in bytes of the filesystem item.</summary>1061                    internal long Size;1062 1063                    /// <summary>The time of the last access for the filesystem item.</summary>1064                    internal long AccessTime;1065 1066                    /// <summary>The time of the last modification for the filesystem item.</summary>1067                    internal long ModifiedTime;1068 1069                    /// <summary>The time of the last status change for the filesystem item.</summary>1070                    internal long StatusChangeTime;1071 1072                    /// <summary>The size in bytes of the file system.</summary>1073                    internal long BlockSize;1074 1075                    /// <summary>The device id for the filesystem item.</summary>1076                    internal int DeviceId;1077 1078                    /// <summary>The number of filesystem blocks that the filesystem item uses.</summary>1079                    internal int NumberOfBlocks;1080 1081                    /// <summary>This filesystem item is a directory.</summary>1082                    internal int IsDirectory;1083 1084                    /// <summary>This filesystem item is a file.</summary>1085                    internal int IsFile;1086 1087                    /// <summary>This filesystem item is a symbolic link.</summary>1088                    internal int IsSymbolicLink;1089 1090                    /// <summary>This filesystem item is a block device.</summary>1091                    internal int IsBlockDevice;1092 1093                    /// <summary>This filesystem item is a character device.</summary>1094                    internal int IsCharacterDevice;1095 1096                    /// <summary>This filesystem item is a named pipe.</summary>1097                    internal int IsNamedPipe;1098 1099                    /// <summary>This filesystem item is a socket.</summary>1100                    internal int IsSocket;1101 1102                    /// <summary>This filesystem item will run as the owner if executed.</summary>1103                    internal int IsSetUid;1104 1105                    /// <summary>This filesystem item will run as the group if executed.</summary>1106                    internal int IsSetGid;1107 1108                    /// <summary>Whether the sticky bit is set on the filesystem item.</summary>1109                    internal int IsSticky;1110                }1111 1112                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]1113                internal static unsafe partial int GetCommonLStat(string filePath, out CommonStatStruct cs);1114 1115                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]1116                internal static unsafe partial int GetCommonStat(string filePath, out CommonStatStruct cs);1117 1118                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1119                internal static partial string GetPwUid(int id);1120 1121                [LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]1122                internal static partial string GetGrGid(int id);1123            }1124        }1125    }1126}1127