holy shit, full duplex-ish net.conn to exec.cmd is working

This commit is contained in:
Sundog Jones 2021-04-14 17:42:07 -07:00
parent 2a295b43b9
commit cf2bf6f872
3 changed files with 145 additions and 31 deletions

6
go.mod
View File

@ -2,4 +2,8 @@ module versestudios.com/go-telnet-asyncio-test
go 1.16 go 1.16
require github.com/xtaci/gaio v1.2.9 require (
github.com/creack/pty v1.1.11 // indirect
github.com/xtaci/gaio v1.2.9
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0 // indirect
)

4
go.sum
View File

@ -1,2 +1,6 @@
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/xtaci/gaio v1.2.9 h1:EuVc7Q2JDzIY2mk5mjtq4K5BgTuO+kj5LXzCwjOK+mo= github.com/xtaci/gaio v1.2.9 h1:EuVc7Q2JDzIY2mk5mjtq4K5BgTuO+kj5LXzCwjOK+mo=
github.com/xtaci/gaio v1.2.9/go.mod h1:rJMerwiLCLnKa14YTM/sRggTPrnBZrlCg9U3DnV5VBE= github.com/xtaci/gaio v1.2.9/go.mod h1:rJMerwiLCLnKa14YTM/sRggTPrnBZrlCg9U3DnV5VBE=
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0 h1:g9s1Ppvvun/fI+BptTMj909BBIcGrzQ32k9FNlcevOE=
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

166
main.go
View File

@ -1,17 +1,19 @@
package main package main
import ( import (
"bufio"
"context" "context"
"flag" "flag"
"io"
"log" "log"
"net" "net"
"os"
"os/exec" "os/exec"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/creack/pty"
"github.com/xtaci/gaio" "github.com/xtaci/gaio"
"versestudios.com/go-telnet-asyncio-test/openconn" "versestudios.com/go-telnet-asyncio-test/openconn"
) )
@ -305,7 +307,9 @@ func exitHandler() []byte {
func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.WaitGroup) error { func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.WaitGroup) error {
defer menuwg.Done() defer menuwg.Done()
var wg sync.WaitGroup
const bannerText = "\r\nCOLOSSAL CAVE\r\n\r\n" const bannerText = "\r\nCOLOSSAL CAVE\r\n\r\n"
err := w.Write(ctx, c, []byte(bannerText)) err := w.Write(ctx, c, []byte(bannerText))
if err != nil { if err != nil {
log.Printf("error writing to connection: %v\n", err) log.Printf("error writing to connection: %v\n", err)
@ -320,52 +324,154 @@ func doorHandler(ctx context.Context, c net.Conn, w *gaio.Watcher, menuwg *sync.
cmd := exec.Command("/usr/games/adventure") cmd := exec.Command("/usr/games/adventure")
stdout, _ := cmd.StdoutPipe() ptmx, err := pty.Start(cmd)
stdin, _ := cmd.StdinPipe() if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = ptmx.Close() }() // Best effort.
cmd.Start() /*
stdout, _ := cmd.StdoutPipe()
var wg sync.WaitGroup stdin, _ := cmd.StdinPipe()
wg.Add(1) */
//CmdOutChan := make(chan []byte)
Terminator := make(chan bool) Terminator := make(chan bool)
go func(Terminator *chan bool, wg *sync.WaitGroup) {
log.Println("starting stdin reader goroutine") // cmd.Start()
wg.Add(1)
go func() {
defer wg.Done() defer wg.Done()
Check: waitingForInput := false
IOLoop:
for { for {
select { select {
case <-*Terminator:
break Check
case result, ok := <-inChan: case result, ok := <-inChan:
var l int
log.Println("i")
if ok { if ok {
if result.Operation == gaio.OpRead { if result.Operation == gaio.OpRead && result.Size > 0 {
// we asked for a read - send it to stdin // we asked for a read - send it to stdin
stdin.Write(result.Buffer[:result.Size]) l, err = ptmx.Write(result.Buffer[:result.Size])
if err != nil {
if err == io.EOF {
// terminal closed? wtfever
log.Println("EOF on cmd.write!!!")
break IOLoop
}
}
if l > 0 {
log.Printf("wrote %d bytes to cmd stdin: %v\n", l, string(result.Buffer[:result.Size]))
waitingForInput = false
}
} }
} else { } else {
log.Println("yikes, problem getting a result from inChan in doorHandler!") log.Println("yikes, problem getting a result from inChan in doorHandler!")
} }
case <-Terminator:
log.Println("Terminator'd!!!")
break IOLoop
default: default:
if !waitingForInput {
log.Println("o")
var l int
outBuf := make([]byte, 2048)
ptmx.SetReadDeadline(time.Now().Add(time.Millisecond))
if l, err = ptmx.Read(outBuf); err != nil {
if err == io.EOF {
log.Println("EOF on cmd stdout - outta here")
break IOLoop
} else if err == os.ErrDeadlineExceeded {
// do nothing but be quiet about it ffs
} else if strings.TrimSpace(err.Error()) == "input/output error" {
// program exited methinks - get outta here
break IOLoop
} else {
log.Printf("err reading from cmd term: %v\n", err)
break IOLoop
}
}
if l > 0 {
w.Write(ctx, c, outBuf[:l])
log.Printf("wrote %d bytes to watcher: %v\n", l, string(outBuf[:l]))
if outBuf[l-1] == byte('>') {
// looks like a prompt to me, I hope!
// let's add a space just for fun
outBuf[0] = byte(' ')
w.Write(ctx, c, outBuf[0:1])
// and now block until we get a read from the connection dammit
waitingForInput = true
}
} else {
// zero output? let's get some input?
// log.Println("0-read from cmd output - queueing watcher read")
w.Read(ctx, c, nil)
}
}
}
time.Sleep(time.Millisecond)
}
}()
/*
// goroutine to read cmd stdout in a loop and write it to CmdOutChan.
// on io.EOF in the scanner, will send Terminator to kill the IOLoop
// that follows the goroutine
go func(Terminator chan bool, wg *sync.WaitGroup, stdout *io.ReadCloser) {
defer wg.Done()
log.Println("starting stdout reader goroutine")
scanner := bufio.NewScanner(*stdout)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
m := scanner.Bytes()
m = append(m, '\n')
CmdOutChan <- m
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
} log.Println("exiting stdout reader goroutine")
log.Println("exiting stdin reader goroutine") Terminator <- true
}(&Terminator, &wg) }(Terminator, &wg, &stdout)
log.Println("starting stdout reader goroutine") // goroutine to handle actual IO calls
scanner := bufio.NewScanner(stdout) wg.Add(1)
scanner.Split(bufio.ScanLines) go func(Terminator chan bool, wg *sync.WaitGroup, stdout *io.ReadCloser) {
defer wg.Done()
log.Println("starting IOLoop")
IOLoop:
for {
select {
// if there's data coming out from cmd, write it to the watcher
case cmdOutput := <-CmdOutChan:
log.Printf("o")
err := w.Write(ctx, c, cmdOutput)
if err != nil {
log.Printf("err writing to watcher from cmd: %v\n", err)
} else {
log.Printf("send %d bytes from CmdOutChan from cmd stdout to watcher: %v\n", len(cmdOutput), string(cmdOutput))
}
for scanner.Scan() { // if there's data coming in from remote conn, send it to stdin for cmd
m := scanner.Bytes() case result, ok := <-inChan:
m = append(m, '\n') log.Printf("i")
w.Write(ctx, c, m) if ok {
log.Printf("wrote %d bytes to watcher from door stdout: %v\n", len(m)+1, string(m)) if result.Operation == gaio.OpRead && result.Size > 0 {
} // we asked for a read - send it to stdin
log.Println("exiting stdout reader goroutine") stdin.Write(result.Buffer[:result.Size])
Terminator <- true }
} else {
log.Println("waiting for wg") log.Println("yikes, problem getting a result from inChan in doorHandler!")
}
// if the Terminator shows up we're dead
case <-Terminator:
break IOLoop
}
time.Sleep(time.Millisecond)
}
log.Println("IOLoop exited")
}(Terminator, &wg, &stdout)
*/
wg.Wait() wg.Wait()
log.Println("waiting for cmd") log.Println("waiting for cmd")
exitErr := cmd.Wait() exitErr := cmd.Wait()