示例#1
0
        /// <inheritdoc/>
        public override Task <IPtyConnection> StartTerminalAsync(PtyOptions options, TraceSource trace, CancellationToken cancellationToken)
        {
            var winSize = new WinSize((ushort)options.Rows, (ushort)options.Cols);

            string?[] terminalArgs = GetExecvpArgs(options);

            var controlCharacters = new Dictionary <TermSpecialControlCharacter, sbyte>
            {
                { TermSpecialControlCharacter.VEOF, 4 },
                { TermSpecialControlCharacter.VEOL, -1 },
                { TermSpecialControlCharacter.VEOL2, -1 },
                { TermSpecialControlCharacter.VERASE, 0x7f },
                { TermSpecialControlCharacter.VWERASE, 23 },
                { TermSpecialControlCharacter.VKILL, 21 },
                { TermSpecialControlCharacter.VREPRINT, 18 },
                { TermSpecialControlCharacter.VINTR, 3 },
                { TermSpecialControlCharacter.VQUIT, 0x1c },
                { TermSpecialControlCharacter.VSUSP, 26 },
                { TermSpecialControlCharacter.VSTART, 17 },
                { TermSpecialControlCharacter.VSTOP, 19 },
                { TermSpecialControlCharacter.VLNEXT, 22 },
                { TermSpecialControlCharacter.VDISCARD, 15 },
                { TermSpecialControlCharacter.VMIN, 1 },
                { TermSpecialControlCharacter.VTIME, 0 },
                { TermSpecialControlCharacter.VDSUSP, 25 },
                { TermSpecialControlCharacter.VSTATUS, 20 },
            };

            var term = new Termios(
                inputFlag: TermInputFlag.ICRNL | TermInputFlag.IXON | TermInputFlag.IXANY | TermInputFlag.IMAXBEL | TermInputFlag.BRKINT | TermInputFlag.IUTF8,
                outputFlag: TermOuptutFlag.OPOST | TermOuptutFlag.ONLCR,
                controlFlag: TermConrolFlag.CREAD | TermConrolFlag.CS8 | TermConrolFlag.HUPCL,
                localFlag: TermLocalFlag.ICANON | TermLocalFlag.ISIG | TermLocalFlag.IEXTEN | TermLocalFlag.ECHO | TermLocalFlag.ECHOE | TermLocalFlag.ECHOK | TermLocalFlag.ECHOKE | TermLocalFlag.ECHOCTL,
                speed: TermSpeed.B38400,
                controlCharacters: controlCharacters);

            int controller = 0;
            int pid        = forkpty(ref controller, null, ref term, ref winSize);

            if (pid == -1)
            {
                throw new InvalidOperationException($"forkpty(4) failed with error {Marshal.GetLastWin32Error()}");
            }

            if (pid == 0)
            {
                // We are in a forked process! See http://man7.org/linux/man-pages/man2/fork.2.html for details.
                // Only our thread is running. We inherited open file descriptors and get a copy of the parent process memory.
                Environment.CurrentDirectory = options.Cwd;
                execvpe(options.App, terminalArgs, options.Environment);

                // Unreachable code after execvpe()
            }

            // We have forked the terminal
            return(Task.FromResult <IPtyConnection>(new PtyConnection(controller, pid)));
        }
示例#2
0
 /// <inheritdoc/>
 public Task <IPtyConnection> StartTerminalAsync(
     PtyOptions options,
     TraceSource trace,
     CancellationToken cancellationToken)
 {
     if (NativeMethods.IsPseudoConsoleSupported && !options.ForceWinPty)
     {
         return(this.StartPseudoConsoleAsync(options, trace, cancellationToken));
     }
     else
     {
         return(this.StartWinPtyTerminalAsync(options, trace, cancellationToken));
     }
 }
示例#3
0
        /// <summary>
        /// Gets the arguments to pass to execvp.
        /// </summary>
        /// <param name="options">The options for spawning the pty.</param>
        /// <returns>An array of arguments to pass to execvp.</returns>
        protected static string?[] GetExecvpArgs(PtyOptions options)
        {
            // execvp(2) args array must end with null. The first arg is the app itself.
            if (options.CommandLine.Length == 0)
            {
                return(new[] { options.App, null });
            }

            var result = new string?[options.CommandLine.Length + 2];

            Array.Copy(options.CommandLine, 0, result, 1, options.CommandLine.Length);
            result[0] = options.App;
            return(result);
        }
示例#4
0
        public async Task ConnectToTerminal()
        {
            const uint CtrlCExitCode = 0xC000013A;

            var          encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
            const string Data     = "abc✓ЖЖЖ①Ⅻㄨㄩ 啊阿鼾齄丂丄狚狛狜狝﨨﨩ˊˋ˙– ⿻〇㐀㐁䶴䶵";

            string app     = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(Environment.SystemDirectory, "cmd.exe") : "sh";
            var    options = new PtyOptions
            {
                Name        = "Custom terminal",
                Cols        = Data.Length + Environment.CurrentDirectory.Length + 50,
                Rows        = 25,
                Cwd         = Environment.CurrentDirectory,
                App         = app,
                Environment = new Dictionary <string, string>()
                {
                    { "FOO", "bar" },
                    { "Bazz", string.Empty },
                },
            };

            IPtyConnection terminal = await PtyProvider.SpawnAsync(options, this.TimeoutToken);

            var processExitedTcs = new TaskCompletionSource <uint>();

            terminal.ProcessExited += (sender, e) => processExitedTcs.TrySetResult((uint)terminal.ExitCode);

            string GetTerminalExitCode() =>
            processExitedTcs.Task.IsCompleted ? $". Terminal process has exited with exit code {processExitedTcs.Task.GetAwaiter().GetResult()}." : string.Empty;

            var firstOutput              = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
            var firstDataFound           = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously);
            var output                   = string.Empty;
            var checkTerminalOutputAsync = Task.Run(async() =>
            {
                var buffer    = new byte[4096];
                var ansiRegex = new Regex(
                    @"[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PRZcf-ntqry=><~]))");

                while (!this.TimeoutToken.IsCancellationRequested && !processExitedTcs.Task.IsCompleted)
                {
                    int count = await terminal.ReaderStream.ReadAsync(buffer, 0, buffer.Length, this.TimeoutToken);
                    if (count == 0)
                    {
                        break;
                    }

                    firstOutput.TrySetResult(null);

                    output += encoding.GetString(buffer, 0, count);
                    output  = output.Replace("\r", string.Empty).Replace("\n", string.Empty);
                    output  = ansiRegex.Replace(output, string.Empty);

                    var index = output.IndexOf(Data);
                    if (index >= 0)
                    {
                        firstDataFound.TrySetResult(null);
                        if (index <= output.Length - (2 * Data.Length) &&
                            output.IndexOf(Data, index + Data.Length) >= 0)
                        {
                            return(true);
                        }
                    }
                }

                firstOutput.TrySetCanceled();
                firstDataFound.TrySetCanceled();
                return(false);
            });

            try
            {
                await firstOutput.Task;
            }
            catch (OperationCanceledException exception)
            {
                throw new InvalidOperationException(
                          $"Could not get any output from terminal{GetTerminalExitCode()}",
                          exception);
            }

            try
            {
                byte[] commandBuffer = encoding.GetBytes("echo " + Data);
                await terminal.WriterStream.WriteAsync(commandBuffer, 0, commandBuffer.Length, this.TimeoutToken);

                await terminal.WriterStream.FlushAsync();

                await firstDataFound.Task;

                await terminal.WriterStream.WriteAsync(new byte[] { 0x0D }, 0, 1, this.TimeoutToken); // Enter

                await terminal.WriterStream.FlushAsync();

                Assert.True(await checkTerminalOutputAsync);
            }
            catch (Exception exception)
            {
                throw new InvalidOperationException(
                          $"Could not get expected data from terminal.{GetTerminalExitCode()} Actual terminal output:\n{output}",
                          exception);
            }

            terminal.Resize(40, 10);

            terminal.Dispose();

            using (this.TimeoutToken.Register(() => processExitedTcs.TrySetCanceled(this.TimeoutToken)))
            {
                uint exitCode = await processExitedTcs.Task;
                Assert.True(
                    exitCode == CtrlCExitCode || // WinPty terminal exit code.
                    exitCode == 1 ||             // Pseudo Console exit code on Win 10.
                    exitCode == 0);              // pty exit code on *nix.
            }

            Assert.True(terminal.WaitForExit(TestTimeoutMs));
        }
示例#5
0
        private Task <IPtyConnection> StartPseudoConsoleAsync(
            PtyOptions options,
            TraceSource trace,
            CancellationToken cancellationToken)
        {
            // Create the in/out pipes
            if (!CreatePipe(out SafePipeHandle inPipePseudoConsoleSide, out SafePipeHandle inPipeOurSide, null, 0))
            {
                throw new InvalidOperationException("Could not create an anonymous pipe", new Win32Exception());
            }

            if (!CreatePipe(out SafePipeHandle outPipeOurSide, out SafePipeHandle outPipePseudoConsoleSide, null, 0))
            {
                throw new InvalidOperationException("Could not create an anonymous pipe", new Win32Exception());
            }

            var coord = new Coord(options.Cols, options.Rows);
            var pseudoConsoleHandle = new SafePseudoConsoleHandle();
            int hr;

            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                // Run CreatePseudoConsole* in a CER to make sure we don't leak handles.
                // MSDN suggest to put all CER code in a finally block
                // See http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions(v=vs.110).aspx
            }
            finally
            {
                // Create the Pseudo Console, using the pipes
                hr = CreatePseudoConsole(coord, inPipePseudoConsoleSide.Handle, outPipePseudoConsoleSide.Handle, 0, out IntPtr hPC);

                // Remember the handle inside the CER to prevent leakage
                if (hPC != IntPtr.Zero && hPC != INVALID_HANDLE_VALUE)
                {
                    pseudoConsoleHandle.InitialSetHandle(hPC);
                }
            }

            if (hr != S_OK)
            {
                Marshal.ThrowExceptionForHR(hr);
            }

            // Prepare the StartupInfoEx structure attached to the ConPTY.
            var startupInfo = default(STARTUPINFOEX);

            startupInfo.InitAttributeListAttachedToConPTY(pseudoConsoleHandle);
            IntPtr lpEnvironment = Marshal.StringToHGlobalUni(GetEnvironmentString(options.Environment));

            try
            {
                string app       = GetAppOnPath(options.App, options.Cwd, options.Environment);
                string arguments = options.VerbatimCommandLine ?
                                   WindowsArguments.FormatVerbatim(options.CommandLine) :
                                   WindowsArguments.Format(options.CommandLine);

                var  commandLine = new StringBuilder(app.Length + arguments.Length + 4);
                bool quoteApp    = app.Contains(" ") && !app.StartsWith("\"") && !app.EndsWith("\"");
                if (quoteApp)
                {
                    commandLine.Append('"').Append(app).Append('"');
                }
                else
                {
                    commandLine.Append(app);
                }

                if (!string.IsNullOrWhiteSpace(arguments))
                {
                    commandLine.Append(' ');
                    commandLine.Append(arguments);
                }

                bool success;
                int  errorCode        = 0;
                var  processInfo      = default(PROCESS_INFORMATION);
                var  processHandle    = new SafeProcessHandle();
                var  mainThreadHandle = new SafeThreadHandle();

                RuntimeHelpers.PrepareConstrainedRegions();
                try
                {
                    // Run CreateProcess* in a CER to make sure we don't leak handles.
                }
                finally
                {
                    success = CreateProcess(
                        null,                                                      // lpApplicationName
                        commandLine.ToString(),
                        null,                                                      // lpProcessAttributes
                        null,                                                      // lpThreadAttributes
                        false,                                                     // bInheritHandles VERY IMPORTANT that this is false
                        EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
                        lpEnvironment,
                        options.Cwd,
                        ref startupInfo,
                        out processInfo);

                    if (!success)
                    {
                        errorCode = Marshal.GetLastWin32Error();
                    }

                    // Remember the handles inside the CER to prevent leakage
                    if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != INVALID_HANDLE_VALUE)
                    {
                        processHandle.InitialSetHandle(processInfo.hProcess);
                    }

                    if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != INVALID_HANDLE_VALUE)
                    {
                        mainThreadHandle.InitialSetHandle(processInfo.hThread);
                    }
                }

                if (!success)
                {
                    var exception = new Win32Exception(errorCode);
                    throw new InvalidOperationException($"Could not start terminal process {commandLine.ToString()}: {exception.Message}", exception);
                }

                var connectionOptions = new PseudoConsoleConnection.PseudoConsoleConnectionHandles(
                    inPipePseudoConsoleSide,
                    outPipePseudoConsoleSide,
                    inPipeOurSide,
                    outPipeOurSide,
                    pseudoConsoleHandle,
                    processHandle,
                    processInfo.dwProcessId,
                    mainThreadHandle);

                var result = new PseudoConsoleConnection(connectionOptions);
                return(Task.FromResult <IPtyConnection>(result));
            }
            finally
            {
                startupInfo.FreeAttributeList();
                if (lpEnvironment != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(lpEnvironment);
                }
            }
        }
示例#6
0
        private async Task <IPtyConnection> StartWinPtyTerminalAsync(
            PtyOptions options,
            TraceSource trace,
            CancellationToken cancellationToken)
        {
            IntPtr error;

            IntPtr config = winpty_config_new(WINPTY_FLAG_COLOR_ESCAPES, out error);

            ThrowIfErrorOrNull("Error creating WinPTY config", error, config);

            winpty_config_set_initial_size(config, options.Cols, options.Rows);

            IntPtr handle = winpty_open(config, out error);

            winpty_config_free(config);

            ThrowIfErrorOrNull("Error launching WinPTY agent", error, handle);

            string commandLine = options.VerbatimCommandLine ?
                                 WindowsArguments.FormatVerbatim(options.CommandLine) :
                                 WindowsArguments.Format(options.CommandLine);

            string env = GetEnvironmentString(options.Environment);
            string app = GetAppOnPath(options.App, options.Cwd, options.Environment);

            trace.TraceInformation($"Starting terminal process '{app}' with command line {commandLine}");

            IntPtr spawnConfig = winpty_spawn_config_new(
                WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
                app,
                commandLine,
                options.Cwd,
                env,
                out error);

            ThrowIfErrorOrNull("Error creating WinPTY spawn config", error, spawnConfig);

            bool spawnSuccess = winpty_spawn(handle, spawnConfig, out SafeProcessHandle hProcess, out IntPtr thread, out int procError, out error);

            winpty_spawn_config_free(spawnConfig);

            if (!spawnSuccess)
            {
                if (procError != 0)
                {
                    if (error != IntPtr.Zero)
                    {
                        winpty_error_free(error);
                    }

                    throw new InvalidOperationException($"Unable to start WinPTY terminal '{app}': {new Win32Exception(procError).Message} ({procError})");
                }

                ThrowIfError("Unable to start WinPTY terminal process", error, alwaysThrow: true);
            }

            Stream?writeToStream  = null;
            Stream?readFromStream = null;

            try
            {
                writeToStream = await CreatePipeAsync(winpty_conin_name(handle), PipeDirection.Out, cancellationToken);

                readFromStream = await CreatePipeAsync(winpty_conout_name(handle), PipeDirection.In, cancellationToken);
            }
            catch
            {
                writeToStream?.Dispose();
                hProcess.Close();
                winpty_free(handle);
                throw;
            }

            return(new WinPtyConnection(readFromStream, writeToStream, handle, hProcess));
        }
        public async Task <IPtyConnection> CreateSteamCMD(string strparams)
        {
            //if (activeSteamCMD != null && !activeSteamCMD.HasExited)
            if (activeSteamCMD != null && activeSteamCMD.ExitCode == default(int))
            {
                throw new Exception("Can't start a SteamCMD when one is already open!");
            }

            //string app = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Path.Combine(Environment.SystemDirectory, "cmd.exe") : "sh";
            string app     = SteamCMDExe.FullName;
            string cwd     = SteamCMDFolder.FullName;
            var    options = new PtyOptions()
            {
                Name                = "SteamCMD terminal",
                App                 = app,
                CommandLine         = strparams.Split(" "),
                VerbatimCommandLine = true,
                Cwd                 = cwd,
                Cols                = 100,
                Rows                = 80,
                Environment         = new Dictionary <string, string>()
            };

            IPtyConnection terminal = await PtyProvider.SpawnAsync(options, new System.Threading.CancellationToken());

            terminal.ProcessExited += (sender, e) =>
            {
                activeSteamCMD = null;
            };


            activeSteamCMD = terminal;

            /*
             *
             * var steamCMDProc = new Process();
             * steamCMDProc.StartInfo.FileName = SteamCMDExe.FullName;
             * steamCMDProc.StartInfo.WorkingDirectory = SteamCMDFolder.FullName;
             * steamCMDProc.StartInfo.Arguments = strparams;
             *
             * steamCMDProc.StartInfo.RedirectStandardInput = true;
             * steamCMDProc.StartInfo.RedirectStandardError = true;
             * steamCMDProc.StartInfo.RedirectStandardOutput = true;
             * //steamCMDProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
             * steamCMDProc.StartInfo.CreateNoWindow = true;
             * steamCMDProc.StartInfo.UseShellExecute = false;
             * steamCMDProc.EnableRaisingEvents = true; // Investigate?
             *                                                                               //activeSteamCMD.EnableRaisingEvents = false;
             * // activeSteamCMD.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
             * // activeSteamCMD.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
             *
             * steamCMDProc.Exited += (sender, e) =>
             * {
             *      //activeSteamCMD.Close();
             *      activeSteamCMD = null;
             * };
             *
             * activeSteamCMD = steamCMDProc;
             */

            return(activeSteamCMD);
        }
示例#8
0
 /// <inheritdoc/>
 public abstract Task <IPtyConnection> StartTerminalAsync(PtyOptions options, TraceSource trace, CancellationToken cancellationToken);