Keith Rarick 11 years ago
commit
12319125d6
6 changed files with 243 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 9 0
      Makefile
  3. 40 0
      README.md
  4. 89 0
      pty_darwin.go
  5. 68 0
      pty_linux.go
  6. 33 0
      run.go

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+[568].out
+_go*
+_test*
+_obj

+ 9 - 0
Makefile

@@ -0,0 +1,9 @@
+GOROOT ?= $(shell printf 't:;@echo $$(GOROOT)\n' | gomake -f -)
+include $(GOROOT)/src/Make.inc
+
+TARG=github.com/kr/pty
+GOFILES=\
+	pty_$(GOOS).go\
+	run.go\
+
+include $(GOROOT)/src/Make.pkg

+ 40 - 0
README.md

@@ -0,0 +1,40 @@
+# pty
+
+Pty is a Go package for using unix pseudo-terminals.
+
+## Install
+
+    goinstall github.com/kr/pty
+
+## Example
+
+    package main
+
+    import (
+        "fmt"
+        "github.com/kr/pty"
+        "io"
+        "os"
+    )
+
+
+    func main() {
+        c, err := pty.Run(
+            "/bin/grep",
+            []string{"grep", "--color=auto", "bar"},
+            nil,
+            "",
+        )
+        if err != nil {
+            panic(err)
+        }
+
+        go func() {
+            fmt.Fprintln(c.Stdin, "foo")
+            fmt.Fprintln(c.Stdin, "bar")
+            fmt.Fprintln(c.Stdin, "baz")
+            c.Stdin.Close()
+        }()
+        io.Copy(os.Stdout, c.Stdout)
+        c.Wait(0)
+    }

+ 89 - 0
pty_darwin.go

@@ -0,0 +1,89 @@
+package pty
+
+import (
+	"os"
+	"syscall"
+	"unsafe"
+)
+
+const (
+	sys_TIOCGPTN   = 0x80045430
+	sys_TIOCSPTLCK = 0x40045431
+)
+
+
+// Opens a pty and its corresponding tty.
+func Open() (pty, tty *os.File, err os.Error) {
+	p, err := os.Open("/dev/ptmx", os.O_RDWR, 0)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	sname, err := ptsname(p)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	err = grantpt(p)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t, err := os.Open(sname, os.O_RDWR, 0)
+	if err != nil {
+		return nil, nil, err
+	}
+	return p, t, nil
+}
+
+const (
+	ptdev1 = "pqrsPQRS"
+	ptdev2 = "0123456789abcdefghijklmnopqrstuv"
+)
+
+func ptsname(f *os.File) (string, os.Error) {
+	fi, err := f.Stat()
+	if err != nil {
+		return "", err
+	}
+	return "/dev/tty" + string([]byte{
+		ptdev1[minor(fi.Rdev)/32],
+		ptdev2[minor(fi.Rdev)%32],
+	}), nil
+}
+
+
+func grantpt(f *os.File) os.Error {
+	p, err := os.StartProcess("/bin/ptchown", []string{"/bin/ptchown"},
+nil, "", []*os.File{f})
+	if err != nil {
+		return err
+	}
+	w, err := p.Wait(0)
+	if err != nil {
+		return err
+	}
+	if w.Exited() && w.ExitStatus() == 0 {
+		return nil
+	}
+	return os.EACCES
+}
+
+
+func ioctl(fd int, cmd uint, data *int) os.Error {
+	_, _, e := syscall.Syscall(
+		syscall.SYS_IOCTL,
+		uintptr(fd),
+		uintptr(cmd),
+		uintptr(unsafe.Pointer(data)),
+	)
+	if e != 0 {
+		return os.ENOTTY
+	}
+	return nil
+}
+
+
+func minor(d uint64) int {
+	return int(d & 0xffffffff)
+}

+ 68 - 0
pty_linux.go

@@ -0,0 +1,68 @@
+package pty
+
+import (
+	"os"
+	"strconv"
+	"syscall"
+	"unsafe"
+)
+
+const (
+	sys_TIOCGPTN   = 0x80045430
+	sys_TIOCSPTLCK = 0x40045431
+)
+
+
+// Opens a pty and its corresponding tty.
+func Open() (pty, tty *os.File, err os.Error) {
+	p, err := os.Open("/dev/ptmx", os.O_RDWR, 0)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	sname, err := ptsname(p)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	err = unlockpt(p)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	t, err := os.Open(sname, os.O_RDWR, 0)
+	if err != nil {
+		return nil, nil, err
+	}
+	return p, t, nil
+}
+
+
+func ptsname(f *os.File) (string, os.Error) {
+	var n int
+	err := ioctl(f.Fd(), sys_TIOCGPTN, &n)
+	if err != nil {
+		return "", err
+	}
+	return "/dev/pts/" + strconv.Itoa(n), nil
+}
+
+
+func unlockpt(f *os.File) os.Error {
+	var u int
+	return ioctl(f.Fd(), sys_TIOCSPTLCK, &u)
+}
+
+
+func ioctl(fd int, cmd uint, data *int) os.Error {
+	_, _, e := syscall.Syscall(
+		syscall.SYS_IOCTL,
+		uintptr(fd),
+		uintptr(cmd),
+		uintptr(unsafe.Pointer(data)),
+	)
+	if e != 0 {
+		return os.ENOTTY
+	}
+	return nil
+}

+ 33 - 0
run.go

@@ -0,0 +1,33 @@
+package pty
+
+import (
+	"exec"
+	"os"
+)
+
+
+// Run starts a process with its stdin, stdout, and stderr
+// connected to a pseudo-terminal tty;
+// Stdin and Stdout of the returned exec.Cmd
+// are the corresponding pty (Stderr is always nil).
+// Arguments name, argv, envv, and dir are passed
+// to os.StartProcess unchanged.
+func Run(name string, argv, envv []string, dir string) (c *exec.Cmd, err os.Error) {
+	c = new(exec.Cmd)
+	var fd [3]*os.File
+
+	c.Stdin, fd[0], err = Open()
+	if err != nil {
+		return nil, err
+	}
+	fd[1] = fd[0]
+	fd[2] = fd[0]
+	c.Stdout = c.Stdin
+	c.Process, err = os.StartProcess(name, argv, envv, dir, fd[:])
+	fd[0].Close()
+	if err != nil {
+		c.Stdin.Close()
+		return nil, err
+	}
+	return c, nil
+}