1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#nullable enable
using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Runtime.InteropServices;
using Dbg = System.Management.Automation.Diagnostics;
#if UNIX
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Implements a cmdlet that allows use of execv API.
/// </summary>
[Cmdlet(VerbsCommon.Switch, "Process", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2181448")]
public sealed class SwitchProcessCommand : PSCmdlet
{
/// <summary>
/// Get or set the command and arguments to replace the current pwsh process.
/// </summary>
[Parameter(Position = 0, Mandatory = false, ValueFromRemainingArguments = true)]
public string[] WithCommand { get; set; } = Array.Empty<string>();
/// <summary>
/// Execute the command and arguments
/// </summary>
protected override void EndProcessing()
{
if (WithCommand.Length == 0)
{
return;
}
// execv requires command to be full path so resolve command to first match
var command = this.SessionState.InvokeCommand.GetCommand(WithCommand[0], CommandTypes.Application);
if (command is null)
{
ThrowTerminatingError(
new ErrorRecord(
new CommandNotFoundException(
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
CommandBaseStrings.NativeCommandNotFound,
WithCommand[0]
)
),
"CommandNotFound",
ErrorCategory.InvalidArgument,
WithCommand[0]
)
);
}
var execArgs = new string?[WithCommand.Length + 1];
// execv convention is the first arg is the program name
execArgs[0] = command.Name;
for (int i = 1; i < WithCommand.Length; i++)
{
execArgs[i] = WithCommand[i];
}
// need null terminator at end
execArgs[execArgs.Length - 1] = null;
// setup termios for a child process as .NET modifies termios dynamically for use with ReadKey()
ConfigureTerminalForChildProcess(true);
int exitCode = Exec(command.Source, execArgs);
if (exitCode < 0)
{
ConfigureTerminalForChildProcess(false);
ThrowTerminatingError(
new ErrorRecord(
new Exception(
string.Format(
System.Globalization.CultureInfo.InvariantCulture,
CommandBaseStrings.ExecFailed,
Marshal.GetLastPInvokeError(),
string.Join(' ', WithCommand)
)
),
"ExecutionFailed",
ErrorCategory.InvalidOperation,
WithCommand
)
);
}
}
/// <summary>
/// The `execv` POSIX syscall we use to exec /bin/sh.
/// </summary>
/// <param name="path">The path to the executable to exec.</param>
/// <param name="args">
/// The arguments to send through to the executable.
/// Array must have its final element be null.
/// </param>
/// <returns>
/// An exit code if exec failed, but if successful the calling process will be overwritten.
/// </returns>
[DllImport("libc",
EntryPoint = "execv",
CallingConvention = CallingConvention.Cdecl,
CharSet = CharSet.Ansi,
SetLastError = true)]
private static extern int Exec(string path, string?[] args);
// leverage .NET runtime's native library which abstracts the need to handle different OS and architectures for termios api
[DllImport("libSystem.Native", EntryPoint = "SystemNative_ConfigureTerminalForChildProcess")]
private static extern void ConfigureTerminalForChildProcess([MarshalAs(UnmanagedType.Bool)] bool childUsesTerminal);
}
}
#endif
|