MegaBites-AI/Windows-powershell
0372
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 