Skip to content

Commit

Permalink
feat: initial implementation
Browse files Browse the repository at this point in the history
Support Windows ConPty and *nix through creack/pty
  • Loading branch information
aymanbagabas committed Sep 22, 2023
1 parent 58cf4fa commit 2f46f51
Show file tree
Hide file tree
Showing 33 changed files with 1,357 additions and 2,065 deletions.
65 changes: 65 additions & 0 deletions cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package pty

import (
"context"
"os"
"syscall"
)

// Cmd is a command that can be started attached to a pseudo-terminal.
// This is similar to the API of exec.Cmd. The main difference is that
// the command is started attached to a pseudo-terminal.
// This is required as we cannot use exec.Cmd directly on Windows due to
// limitation of starting a process attached to a pseudo-terminal.
// See: https://github.com/golang/go/issues/62708
type Cmd struct {
ctx context.Context
pty Pty
sys interface{}

// Path is the path of the command to run.
Path string

// Args holds command line arguments, including the command as Args[0].
Args []string

// Env specifies the environment of the process.
// If Env is nil, the new process uses the current process's environment.
Env []string

// Dir specifies the working directory of the command.
// If Dir is the empty string, the current directory is used.
Dir string

// SysProcAttr holds optional, operating system-specific attributes.
SysProcAttr *syscall.SysProcAttr

// Process is the underlying process, once started.
Process *os.Process

// ProcessState contains information about an exited process.
// If the process was started successfully, Wait or Run will populate this
// field when the command completes.
ProcessState *os.ProcessState

// Cancel is called when the command is canceled.
Cancel func() error
}

// Start starts the specified command attached to the pseudo-terminal.
func (c *Cmd) Start() error {
return c.start()
}

// Wait waits for the command to exit.
func (c *Cmd) Wait() error {
return c.wait()
}

// Run runs the command and waits for it to complete.
func (c *Cmd) Run() error {
if err := c.Start(); err != nil {
return err
}
return c.Wait()
}
45 changes: 45 additions & 0 deletions cmd_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//go:build !windows
// +build !windows

package pty

import (
"os/exec"

"golang.org/x/sys/unix"
)

func (c *Cmd) start() error {
cmd, ok := c.sys.(*exec.Cmd)
if !ok {
return ErrInvalidCommand
}
pty, ok := c.pty.(*PosixPty)
if !ok {
return ErrInvalidCommand
}

cmd.Stdin = pty.slave
cmd.Stdout = pty.slave
cmd.Stderr = pty.slave
cmd.SysProcAttr = &unix.SysProcAttr{
Setsid: true,
Setctty: true,
}
if err := cmd.Start(); err != nil {
return err
}

c.Process = cmd.Process
return nil
}

func (c *Cmd) wait() error {
cmd, ok := c.sys.(*exec.Cmd)
if !ok {
return ErrInvalidCommand
}
err := cmd.Wait()
c.ProcessState = cmd.ProcessState
return err
}
Loading

0 comments on commit 2f46f51

Please sign in to comment.