Welcome to mirror list, hosted at ThFree Co, Russian Federation.

SwitchProcessCommand.cs « Modules « engine « System.Management.Automation « src - github.com/PowerShell/PowerShell.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 57345182310465b69b74ae56eec75cc1ed581409 (plain)
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