Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 94 additions & 63 deletions src/System.Management.Automation/CoreCLR/CorePsPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,9 @@ internal static bool NonWindowsCreateHardLink(string path, string strTargetPath)
return Unix.NativeMethods.CreateHardLink(path, strTargetPath) == 0;
}

internal static unsafe bool NonWindowsSetDate(DateTime dateToUse)
internal static bool NonWindowsSetDate(DateTime dateToUse)
{
Unix.NativeMethods.UnixTm tm = Unix.NativeMethods.DateTimeToUnixTm(dateToUse);
return Unix.NativeMethods.SetDate(&tm) == 0;
return Unix.NativeMethods.SetDate(dateToUse) == 0;
}

internal static bool NonWindowsIsSameFileSystemItem(string pathOne, string pathTwo)
Expand Down Expand Up @@ -943,87 +942,119 @@ internal static partial class NativeMethods
// Ansi is a misnomer, it is hardcoded to UTF-8 on Linux and macOS
// C bools are 1 byte and so must be marshalled as I1

[LibraryImport(psLib)]
internal static partial int GetErrorCategory(int errno);
// errno values below are the standard POSIX numbers, identical on Linux and macOS.
private const int EPERM = 1;
private const int ENOENT = 2;
private const int ESRCH = 3;
private const int EINTR = 4;
private const int EACCES = 13;
private const int EINVAL = 22;

// Maps a Unix errno to the integer value of a PowerShell ErrorCategory.
// Mirrors the mapping previously provided by libpsl-native's GetErrorCategory.
internal static int GetErrorCategory(int errno)
{
switch (errno)
{
case EINVAL:
return (int)ErrorCategory.InvalidArgument;
case ENOENT:
case ESRCH:
return (int)ErrorCategory.ObjectNotFound;
case EINTR:
return (int)ErrorCategory.OperationStopped;
case EACCES:
case EPERM:
return (int)ErrorCategory.PermissionDenied;
default:
return (int)ErrorCategory.NotSpecified;
}
}

[LibraryImport(psLib)]
internal static partial int GetPPid(int pid);

[LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
internal static partial int GetLinkCount(string filePath, out int linkCount);

[LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.I1)]
internal static partial bool IsExecutable(string filePath);

[LibraryImport(psLib)]
internal static partial uint GetCurrentThreadId();

[LibraryImport(psLib)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static partial bool KillProcess(int pid);

[LibraryImport(psLib)]
internal static partial int WaitPid(int pid, [MarshalAs(UnmanagedType.Bool)] bool nohang);

// This is the struct `private_tm` from setdate.h in libpsl-native.
// Packing is set to 4 to match the unmanaged declaration.
// https://github.com/PowerShell/PowerShell-Native/blob/c5575ceb064e60355b9fee33eabae6c6d2708d14/src/libpsl-native/src/setdate.h#L23
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal unsafe struct UnixTm
// The methods below replace former libpsl-native P/Invokes with direct calls to
// libc (see engine/Interop/Unix/*). The Interop.Unix declarations are compiled only
// for non-Windows builds, so the bodies are guarded with #if UNIX. These methods are
// never invoked on Windows.
#if UNIX
// access(path, X_OK) returns 0 when the file is executable, -1 otherwise.
internal static bool IsExecutable(string filePath)
{
/// <summary>Seconds (0-60).</summary>
internal int tm_sec;

/// <summary>Minutes (0-59).</summary>
internal int tm_min;

/// <summary>Hours (0-23).</summary>
internal int tm_hour;
return Interop.Unix.Access(filePath, Interop.Unix.X_OK) != -1;
}

/// <summary>Day of the month (1-31).</summary>
internal int tm_mday;
internal static uint GetCurrentThreadId()
{
if (OperatingSystem.IsMacOS())
{
Interop.Unix.PthreadThreadIdNp(IntPtr.Zero, out ulong tid);
return (uint)tid;
}

/// <summary>Month (0-11).</summary>
internal int tm_mon;
return (uint)Interop.Unix.GetTid();
}

/// <summary>The year - 1900.</summary>
internal int tm_year;
internal static bool KillProcess(int pid)
{
return Interop.Unix.Kill(pid, Interop.Unix.SIGKILL) == 0;
}

/// <summary>Day of the week (0-6, Sunday = 0).</summary>
internal int tm_wday;
internal static int WaitPid(int pid, bool nohang)
{
return Interop.Unix.WaitPid(pid, IntPtr.Zero, nohang ? Interop.Unix.WNOHANG : 0);
}

/// <summary>Day in the year (0-365, 1 Jan = 0).</summary>
internal int tm_yday;
// Set the system clock. Mirrors libpsl-native's SetDate, which converted a
// broken-down local time via mktime() and called settimeofday(). DateTimeOffset
// performs the equivalent local-time-to-Unix-seconds conversion in managed code.
internal static int SetDate(DateTime date)
{
Interop.Unix.Timeval tv;
tv.Seconds = new DateTimeOffset(date).ToUnixTimeSeconds();
tv.Microseconds = 0;
return Interop.Unix.SetTimeOfDay(ref tv, IntPtr.Zero);
}

/// <summary>Daylight saving time.</summary>
internal int tm_isdst;
internal static int CreateSymLink(string filePath, string target)
{
// libpsl-native mapped CreateSymLink(link, target) to symlink(target, link).
return Interop.Unix.Symlink(target, filePath);
}

// We need a way to convert a DateTime to a unix date.
internal static UnixTm DateTimeToUnixTm(DateTime date)
internal static int CreateHardLink(string filePath, string target)
{
UnixTm tm;
tm.tm_sec = date.Second;
tm.tm_min = date.Minute;
tm.tm_hour = date.Hour;
tm.tm_mday = date.Day;
tm.tm_mon = date.Month - 1; // needs to be 0 indexed
tm.tm_year = date.Year - 1900; // years since 1900
tm.tm_wday = 0; // this is ignored by mktime
tm.tm_yday = 0; // this is also ignored
tm.tm_isdst = date.IsDaylightSavingTime() ? 1 : 0;
return tm;
// libpsl-native mapped CreateHardLink(newlink, target) to link(target, newlink).
return Interop.Unix.Link(target, filePath);
}
#else
// Windows builds exclude engine/Interop/Unix. These Unix-only helpers are never
// called on Windows but must be present so the platform-neutral wrappers compile.
internal static bool IsExecutable(string filePath)
=> throw new PlatformNotSupportedException();

[LibraryImport(psLib, SetLastError = true)]
internal static unsafe partial int SetDate(UnixTm* tm);
internal static uint GetCurrentThreadId()
=> throw new PlatformNotSupportedException();

[LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]
internal static partial int CreateSymLink(string filePath, string target);
internal static bool KillProcess(int pid)
=> throw new PlatformNotSupportedException();

[LibraryImport(psLib, StringMarshalling = StringMarshalling.Utf8)]
internal static partial int CreateHardLink(string filePath, string target);
internal static int WaitPid(int pid, bool nohang)
=> throw new PlatformNotSupportedException();

internal static int SetDate(DateTime date)
=> throw new PlatformNotSupportedException();

internal static int CreateSymLink(string filePath, string target)
=> throw new PlatformNotSupportedException();

internal static int CreateHardLink(string filePath, string target)
=> throw new PlatformNotSupportedException();
#endif

[LibraryImport(psLib)]
[return: MarshalAs(UnmanagedType.LPStr)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Unix
{
/// <summary>The <c>mode</c> value passed to <c>access</c> to test for execute permission.</summary>
internal const int X_OK = 1;

/// <summary>Create a symbolic link named <paramref name="linkPath"/> pointing to <paramref name="target"/>.</summary>
/// <param name="target">The path the symbolic link points to.</param>
/// <param name="linkPath">The path of the symbolic link to create.</param>
/// <returns>0 on success, -1 on failure (see <c>errno</c>).</returns>
[LibraryImport("libc", EntryPoint = "symlink", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
internal static partial int Symlink(string target, string linkPath);

/// <summary>Create a hard link named <paramref name="newPath"/> for the existing file <paramref name="oldPath"/>.</summary>
/// <param name="oldPath">The path of the existing file.</param>
/// <param name="newPath">The path of the hard link to create.</param>
/// <returns>0 on success, -1 on failure (see <c>errno</c>).</returns>
[LibraryImport("libc", EntryPoint = "link", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
internal static partial int Link(string oldPath, string newPath);

/// <summary>Check the calling process's permissions for the file <paramref name="pathName"/>.</summary>
/// <param name="pathName">The path to test.</param>
/// <param name="mode">The accessibility check(s) to perform (for example <see cref="X_OK"/>).</param>
/// <returns>0 if all requested access is granted, -1 otherwise (see <c>errno</c>).</returns>
[LibraryImport("libc", EntryPoint = "access", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
internal static partial int Access(string pathName, int mode);
}
}
46 changes: 46 additions & 0 deletions src/System.Management.Automation/engine/Interop/Unix/Process.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Unix
{
/// <summary>The <c>SIGKILL</c> signal number (same value on Linux and macOS).</summary>
internal const int SIGKILL = 9;

/// <summary>The <c>WNOHANG</c> option for <c>waitpid</c> (same value on Linux and macOS).</summary>
internal const int WNOHANG = 1;

/// <summary>Send signal <paramref name="sig"/> to process <paramref name="pid"/>.</summary>
/// <param name="pid">The target process id.</param>
/// <param name="sig">The signal to send (for example <see cref="SIGKILL"/>).</param>
/// <returns>0 on success, -1 on failure (see <c>errno</c>).</returns>
[LibraryImport("libc", EntryPoint = "kill", SetLastError = true)]
internal static partial int Kill(int pid, int sig);

/// <summary>Wait for state changes in a child process.</summary>
/// <param name="pid">The child process id to wait for.</param>
/// <param name="status">Pointer to receive status information, or <see cref="IntPtr.Zero"/> to ignore it.</param>
/// <param name="options">Bitwise options such as <see cref="WNOHANG"/>.</param>
/// <returns>The pid of the child whose state changed, 0 for <see cref="WNOHANG"/> with no change, or -1 on error.</returns>
[LibraryImport("libc", EntryPoint = "waitpid", SetLastError = true)]
internal static partial int WaitPid(int pid, IntPtr status, int options);

/// <summary>Linux: return the caller's kernel thread id via <c>gettid(2)</c>.</summary>
/// <returns>The current thread id.</returns>
[LibraryImport("libc", EntryPoint = "gettid")]
internal static partial int GetTid();

/// <summary>macOS: return the caller's integral thread id via <c>pthread_threadid_np</c>.</summary>
/// <param name="thread">The pthread to query, or <see cref="IntPtr.Zero"/> for the current thread.</param>
/// <param name="threadId">Receives the integral thread id.</param>
/// <returns>0 on success.</returns>
[LibraryImport("libc", EntryPoint = "pthread_threadid_np")]
internal static partial int PthreadThreadIdNp(IntPtr thread, out ulong threadId);
}
}
34 changes: 34 additions & 0 deletions src/System.Management.Automation/engine/Interop/Unix/Time.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#nullable enable

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Unix
{
/// <summary>
/// The <c>struct timeval</c> layout used by <c>settimeofday</c>.
/// On all 64-bit Linux and macOS platforms PowerShell supports, both members are 64-bit.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct Timeval
{
/// <summary>Whole seconds since the Unix epoch.</summary>
internal long Seconds;

/// <summary>Additional microseconds.</summary>
internal long Microseconds;
}

/// <summary>Set the system-wide clock.</summary>
/// <param name="tv">The new time expressed as seconds/microseconds since the Unix epoch.</param>
/// <param name="timezone">Should be <see cref="IntPtr.Zero"/>; the <c>timezone</c> argument is obsolete.</param>
/// <returns>0 on success, -1 on failure (see <c>errno</c>); requires appropriate privileges.</returns>
[LibraryImport("libc", EntryPoint = "settimeofday", SetLastError = true)]
internal static partial int SetTimeOfDay(ref Timeval tv, IntPtr timezone);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,22 @@ internal enum LogLevel : uint

internal static class NativeMethods
{
private const string libpslnative = "libpsl-native";
private const string libc = "libc";

// openlog options: log the pid with each message and open the connection immediately.
// These values are identical on Linux and macOS.
private const int LOG_PID = 0x01;
private const int LOG_NDELAY = 0x08;

[DllImport(libc, CharSet = CharSet.Ansi, EntryPoint = "openlog")]
private static extern void OpenLogNative(IntPtr ident, int option, int facility);

[DllImport(libc, CharSet = CharSet.Ansi, EntryPoint = "syslog")]
private static extern void SysLogNative(int priority, string format, string message);

[DllImport(libc, EntryPoint = "closelog")]
internal static extern void CloseLog();

/// <summary>
/// Write a message to the system logger, which in turn writes the message to the system console, log files, etc.
/// See man 3 syslog for more info.
Expand All @@ -378,14 +393,25 @@ internal static class NativeMethods
/// The OR of a priority and facility in the SysLogPriority enum indicating the priority and facility of the log entry.
/// </param>
/// <param name="message">The message to put in the log entry.</param>
[DllImport(libpslnative, CharSet = CharSet.Ansi, EntryPoint = "Native_SysLog")]
internal static extern void SysLog(SysLogPriority priority, string message);

[DllImport(libpslnative, CharSet = CharSet.Ansi, EntryPoint = "Native_OpenLog")]
internal static extern void OpenLog(IntPtr ident, SysLogPriority facility);
internal static void SysLog(SysLogPriority priority, string message)
{
// Pass the message as a "%s" argument so any '%' characters in the message
// are not interpreted as format specifiers.
SysLogNative((int)priority, "%s", message);
}

[DllImport(libpslnative, EntryPoint = "Native_CloseLog")]
internal static extern void CloseLog();
/// <summary>
/// Opens a connection to the system logger for the calling process.
/// </summary>
/// <param name="ident">
/// A pointer to a null-terminated string that is prepended to every message.
/// The pointer must remain valid for the lifetime of the log session; syslog does not copy the string.
/// </param>
/// <param name="facility">The default facility for subsequent calls to syslog.</param>
internal static void OpenLog(IntPtr ident, SysLogPriority facility)
{
OpenLogNative(ident, LOG_NDELAY | LOG_PID, (int)facility);
}

[Flags]
internal enum SysLogPriority : uint
Expand Down
Loading