mirror of
https://github.com/tailscale/tailscale.git
synced 2025-06-21 07:38:38 +00:00
tempfork/netshell: delete, use github.com/go4org/plan9netshell instead
Change-Id: I2aee6b55f2084a7c3df0db830b28edd1fba0f528
This commit is contained in:
parent
391edc4712
commit
261776e2e4
1
go.mod
1
go.mod
@ -37,6 +37,7 @@ require (
|
|||||||
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874
|
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874
|
||||||
github.com/go-logr/zapr v1.3.0
|
github.com/go-logr/zapr v1.3.0
|
||||||
github.com/go-ole/go-ole v1.3.0
|
github.com/go-ole/go-ole v1.3.0
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||||
github.com/golang/snappy v0.0.4
|
github.com/golang/snappy v0.0.4
|
||||||
|
4
go.sum
4
go.sum
@ -236,8 +236,6 @@ github.com/creachadair/mds v0.17.1/go.mod h1:4b//mUiL8YldH6TImXjmW45myzTLNS1LLjO
|
|||||||
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc=
|
||||||
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
|
||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||||
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||||
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
|
github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo=
|
||||||
@ -393,6 +391,8 @@ github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsM
|
|||||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U=
|
github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U=
|
||||||
github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
|
||||||
|
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737/go.mod h1:MIS0jDzbU/vuM9MC4YnBITCv+RYuTRq8dJzmCrFsK9g=
|
||||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
@ -23,10 +23,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/go4org/plan9netshell"
|
||||||
"github.com/pkg/sftp"
|
"github.com/pkg/sftp"
|
||||||
"tailscale.com/cmd/tailscaled/childproc"
|
"tailscale.com/cmd/tailscaled/childproc"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tempfork/netshell"
|
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -219,7 +219,7 @@ func (ia incubatorArgs) forwardedEnviron() ([]string, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func beNetshell(args []string) error {
|
func beNetshell(args []string) error {
|
||||||
netshell.Main()
|
plan9netshell.Main()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ func beIncubator(args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ia.isShell {
|
if ia.isShell {
|
||||||
netshell.Main()
|
plan9netshell.Main()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,493 +0,0 @@
|
|||||||
// Copyright (c) Plan 9 Foundation, Russ Cox, Google LLC, Tailscale Inc, etc.
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
// This file is a fork of github.com/9fans/go (Go module 9fans.net/go)'s
|
|
||||||
// file ./plan9/srv9p/example/netshell/main.go turned from a package main
|
|
||||||
// to a non-main package.
|
|
||||||
|
|
||||||
//go:build plan9
|
|
||||||
|
|
||||||
// Package netshell implements /dev/cons and /dev/consctl using
|
|
||||||
// stdin and stdout as a source of bytes and runs a login shell
|
|
||||||
// in a new namespace with /dev/cons opened to it.
|
|
||||||
//
|
|
||||||
// For example, the one-line “insecure shell daemon”:
|
|
||||||
//
|
|
||||||
// aux/listen1 'tcp!*!22222' netshell
|
|
||||||
//
|
|
||||||
// This differs from running rc directly in that it provides
|
|
||||||
// terminal echo, a /dev/cons file, and mostly standard Plan 9 line editing
|
|
||||||
// (^C, ^D, ^U, ^W, backspace, and delete).
|
|
||||||
// Unlike standard Plan 9, ^C sends an interrupt and delete is backspace.
|
|
||||||
package netshell
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"9fans.net/go/plan9"
|
|
||||||
"9fans.net/go/plan9/srv9p"
|
|
||||||
)
|
|
||||||
|
|
||||||
const Env = "_NETSHELL_CHILD_"
|
|
||||||
|
|
||||||
var Verbose = os.Getenv("NETSHELL_VERBOSE") == "1"
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if os.Getenv(Env) != "" {
|
|
||||||
runtime.LockOSThread() // to keep runChild on main thread
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
log.SetPrefix("netshell: ")
|
|
||||||
|
|
||||||
if os.Getenv(Env) != "" {
|
|
||||||
runChild()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c := newConsole(os.Stdin, os.Stdout)
|
|
||||||
|
|
||||||
tree := srv9p.NewTree("netshell", "netshell", plan9.DMDIR|0555, nil)
|
|
||||||
cons, _ := tree.Root.Create("cons", "netshell", 0666, nil)
|
|
||||||
consctl, _ := tree.Root.Create("consctl", "netshell", 0222, nil)
|
|
||||||
|
|
||||||
srv := &srv9p.Server{
|
|
||||||
Tree: tree,
|
|
||||||
Read: func(ctx context.Context, fid *srv9p.Fid, data []byte, offset int64) (int, error) {
|
|
||||||
switch fid.File() {
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown file")
|
|
||||||
case cons:
|
|
||||||
return c.consRead(ctx, data)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Write: func(ctx context.Context, fid *srv9p.Fid, data []byte, offset int64) (int, error) {
|
|
||||||
switch fid.File() {
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("unknown file")
|
|
||||||
case cons:
|
|
||||||
return c.consWrite(ctx, data)
|
|
||||||
case consctl:
|
|
||||||
return c.consctlWrite(data)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Clunk: func(fid *srv9p.Fid) {
|
|
||||||
if fid.File() == consctl {
|
|
||||||
c.consctlClose()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if Verbose {
|
|
||||||
srv.Trace = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
p1, p2, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
go srv.Serve(p1, p1)
|
|
||||||
|
|
||||||
// Child process runs with the 9P service on stdin (pipes are bidirectional),
|
|
||||||
// leaving stdout and stderr directly connected to the parent's stdout/stderr
|
|
||||||
// for printing any last gasp errors.
|
|
||||||
// The child mounts the 9P session and then runs a shell or other command.
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd := exec.Command("/bin/auth/newns", exe)
|
|
||||||
cmd.Env = append(os.Environ(), Env+"=1")
|
|
||||||
cmd.Stdin = p2
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Rfork: syscall.RFNOTEG | syscall.RFNAMEG}
|
|
||||||
err = cmd.Start()
|
|
||||||
p2.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
cmd.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func runChild() {
|
|
||||||
// Note: locked on main thread, so RFNOTEG and RFNAMEG will not migrate elsewhere.
|
|
||||||
syscall.RawSyscall(syscall.SYS_RFORK, syscall.RFNOTEG|syscall.RFNAMEG, 0, 0)
|
|
||||||
|
|
||||||
// Mount console device from parent process onto /dev and open.
|
|
||||||
if err := syscall.Mount(0, -1, "/dev", syscall.MBEFORE, ""); err != nil {
|
|
||||||
log.Fatalf("mount /dev: %v", err)
|
|
||||||
}
|
|
||||||
cons, err := syscall.Open("/dev/cons", syscall.O_RDWR)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tell parent to open our note group file for sending interrupts.
|
|
||||||
consctl, err := syscall.Open("/dev/consctl", syscall.O_WRONLY)
|
|
||||||
if err == nil {
|
|
||||||
syscall.Write(consctl, []byte(fmt.Sprintf("notepg %d", os.Getpid())))
|
|
||||||
syscall.Close(consctl)
|
|
||||||
} else if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put /dev/cons on stdin, stdout, stderr and exec command.
|
|
||||||
syscall.Dup(cons, 0)
|
|
||||||
syscall.Dup(cons, 1)
|
|
||||||
syscall.Dup(cons, 2)
|
|
||||||
if cons > 2 {
|
|
||||||
syscall.Close(cons)
|
|
||||||
}
|
|
||||||
args := flag.Args()
|
|
||||||
if len(args) == 0 {
|
|
||||||
args = []string{"/bin/rc", "-l"}
|
|
||||||
}
|
|
||||||
err = syscall.Exec(args[0], args, append(os.Environ(), "service=netshell"))
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A rendez is a simple sleep/wakeup primitive (like a condition variable)
|
|
||||||
// based on a channel. Using a channel lets us sleep on the rendez until
|
|
||||||
// either it becomes ready or a context is canceled.
|
|
||||||
type rendez chan bool
|
|
||||||
|
|
||||||
func newRendez() rendez {
|
|
||||||
return make(rendez, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep sleeps until either something calls r.Wakeup or the context is canceled.
|
|
||||||
func (r rendez) Sleep(ctx context.Context) {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-r:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wakeup wakes up one call to r.Sleep or returns immediately if
|
|
||||||
// there are no calls sleeping.
|
|
||||||
func (r rendez) Wakeup() {
|
|
||||||
select {
|
|
||||||
default:
|
|
||||||
case r <- true:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxLine = 4096 // max line buffer
|
|
||||||
maxBuf = 8192 // max read/write buffer
|
|
||||||
)
|
|
||||||
|
|
||||||
// A console implements a simple terminal-like device reading from
|
|
||||||
// r and writing to w.
|
|
||||||
type console struct {
|
|
||||||
r io.Reader // reader connected to network
|
|
||||||
w io.Writer // writer connected to network
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
notefd int
|
|
||||||
raw bool
|
|
||||||
line []byte // line buffer
|
|
||||||
read []byte // from network
|
|
||||||
readErr error
|
|
||||||
readEmpty rendez
|
|
||||||
readFull rendez
|
|
||||||
write []byte // to network
|
|
||||||
writeErr error
|
|
||||||
writeEmpty rendez
|
|
||||||
writeFull rendez
|
|
||||||
}
|
|
||||||
|
|
||||||
// newConsole creates a new console reading from r and writing to w.
|
|
||||||
func newConsole(r io.Reader, w io.Writer) *console {
|
|
||||||
c := &console{
|
|
||||||
r: r,
|
|
||||||
w: w,
|
|
||||||
notefd: -1,
|
|
||||||
readEmpty: newRendez(),
|
|
||||||
readFull: newRendez(),
|
|
||||||
writeEmpty: newRendez(),
|
|
||||||
writeFull: newRendez(),
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
go c.readNet(ctx)
|
|
||||||
go c.writeNet(ctx)
|
|
||||||
_ = cancel
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// consctlClose handles a close of /dev/consctl; it turns raw mode off.
|
|
||||||
func (c *console) consctlClose() {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
c.raw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// consctlWrite handles writes to /dev/consctl.
|
|
||||||
func (c *console) consctlWrite(data []byte) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
s := strings.TrimSpace(string(data))
|
|
||||||
switch s {
|
|
||||||
case "rawon": // turn raw mode on
|
|
||||||
c.raw = true
|
|
||||||
c.read = append(c.read, c.line...)
|
|
||||||
c.line = nil
|
|
||||||
return len(data), nil
|
|
||||||
|
|
||||||
case "rawoff": // turn raw mode off
|
|
||||||
c.raw = false
|
|
||||||
return len(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if s, ok := strings.CutPrefix(s, "notepg"); ok { // notepg n means send interrupt notes to /proc/n/notepg
|
|
||||||
if _, err := strconv.ParseUint(s, 10, 64); err != nil {
|
|
||||||
return 0, fmt.Errorf("bad notepg")
|
|
||||||
}
|
|
||||||
fd, err := syscall.Open("/proc/"+s+"/notepg", syscall.O_WRONLY)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
c.notefd = fd
|
|
||||||
return len(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("unknown control message")
|
|
||||||
}
|
|
||||||
|
|
||||||
// readNet reads bytes from the network connection
|
|
||||||
// and adds them to the read buffer for use by /dev/cons.
|
|
||||||
func (c *console) readNet(ctx context.Context) {
|
|
||||||
buf := make([]byte, 4096)
|
|
||||||
for context.Cause(ctx) == nil {
|
|
||||||
n, err := c.r.Read(buf)
|
|
||||||
if n > 0 {
|
|
||||||
c.consType(ctx, buf[:n])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
Backspace = 'H' - '@'
|
|
||||||
Delete = 0x7F
|
|
||||||
CtrlC = 'C' - '@'
|
|
||||||
CtrlD = 'D' - '@'
|
|
||||||
CtrlU = 'U' - '@'
|
|
||||||
CtrlW = 'W' - '@'
|
|
||||||
)
|
|
||||||
|
|
||||||
// consType processes typed characters, adding them to the console.
|
|
||||||
func (c *console) consType(ctx context.Context, data []byte) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.raw {
|
|
||||||
c.addRead(ctx, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, ch := range data {
|
|
||||||
if len(c.line) >= maxLine { // too much, just send it
|
|
||||||
c.addRead(ctx, c.line)
|
|
||||||
c.line = c.line[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ch {
|
|
||||||
default:
|
|
||||||
c.consWriteLocked(ctx, []byte{ch})
|
|
||||||
c.line = append(c.line, ch)
|
|
||||||
|
|
||||||
case '\r', '\n':
|
|
||||||
ch = '\n'
|
|
||||||
c.consWriteLocked(ctx, []byte{ch})
|
|
||||||
c.line = append(c.line, ch)
|
|
||||||
c.addRead(ctx, c.line)
|
|
||||||
c.line = c.line[:0]
|
|
||||||
|
|
||||||
case CtrlC:
|
|
||||||
if c.notefd >= 0 {
|
|
||||||
syscall.Write(c.notefd, []byte("interrupt"))
|
|
||||||
}
|
|
||||||
c.consWriteLocked(ctx, []byte(fmt.Sprintf("^C")))
|
|
||||||
|
|
||||||
case CtrlD:
|
|
||||||
c.consWriteLocked(ctx, []byte("^D"))
|
|
||||||
c.line = append(c.line, CtrlD)
|
|
||||||
c.addRead(ctx, c.line)
|
|
||||||
c.line = c.line[:0]
|
|
||||||
|
|
||||||
case Backspace, Delete:
|
|
||||||
if len(c.line) > 0 {
|
|
||||||
_, size := utf8.DecodeLastRune(c.line)
|
|
||||||
c.line = c.line[:len(c.line)-size]
|
|
||||||
c.consWriteLocked(ctx, []byte{ch})
|
|
||||||
}
|
|
||||||
|
|
||||||
case CtrlW:
|
|
||||||
deleted := false
|
|
||||||
for len(c.line) > 0 {
|
|
||||||
r, size := utf8.DecodeLastRune(c.line)
|
|
||||||
alnum := unicode.IsLetter(r) || unicode.IsDigit(r)
|
|
||||||
if alnum {
|
|
||||||
deleted = true
|
|
||||||
} else if deleted {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
c.line = c.line[:len(c.line)-size]
|
|
||||||
c.consWriteLocked(ctx, []byte{Backspace})
|
|
||||||
}
|
|
||||||
|
|
||||||
case CtrlU:
|
|
||||||
if len(c.line) > 0 {
|
|
||||||
c.consWriteLocked(ctx, []byte("^U\n"))
|
|
||||||
c.line = c.line[:0]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRead adds data to the read buffer.
|
|
||||||
func (c *console) addRead(ctx context.Context, data []byte) (int, error) {
|
|
||||||
b := data
|
|
||||||
for len(b) > 0 {
|
|
||||||
if err := context.Cause(ctx); err != nil {
|
|
||||||
return len(data) - len(b), err
|
|
||||||
}
|
|
||||||
n := min(maxBuf-len(c.read), len(b))
|
|
||||||
if n <= 0 {
|
|
||||||
c.mu.Unlock()
|
|
||||||
c.readFull.Sleep(ctx)
|
|
||||||
c.mu.Lock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.read = append(c.read, b[:n]...)
|
|
||||||
b = b[n:]
|
|
||||||
c.readEmpty.Wakeup()
|
|
||||||
}
|
|
||||||
return len(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// consRead uses the read buffer to satisfy a 9P read of /dev/cons.
|
|
||||||
func (c *console) consRead(ctx context.Context, data []byte) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for {
|
|
||||||
if err := context.Cause(ctx); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if len(c.read) == 0 {
|
|
||||||
c.mu.Unlock()
|
|
||||||
c.readEmpty.Sleep(ctx)
|
|
||||||
c.mu.Lock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.raw {
|
|
||||||
n := copy(data, c.read)
|
|
||||||
c.read = c.read[:copy(c.read, c.read[n:])]
|
|
||||||
c.readFull.Wakeup()
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify next line.
|
|
||||||
n := min(len(data), len(c.read))
|
|
||||||
if i := bytes.IndexByte(c.read[:n], '\n'); i >= 0 {
|
|
||||||
n = i + 1
|
|
||||||
}
|
|
||||||
if i := bytes.IndexByte(c.read[:n], CtrlD); i >= 0 {
|
|
||||||
n = i + 1
|
|
||||||
}
|
|
||||||
copy(data, c.read[:n])
|
|
||||||
c.read = c.read[:copy(c.read, c.read[n:])]
|
|
||||||
c.readFull.Wakeup()
|
|
||||||
if n > 0 && data[n-1] == CtrlD {
|
|
||||||
n--
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// consWrite handles a write to /dev/cons by adding data to the write buffer.
|
|
||||||
func (c *console) consWrite(ctx context.Context, data []byte) (int, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
return c.consWriteLocked(ctx, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *console) consWriteLocked(ctx context.Context, data []byte) (int, error) {
|
|
||||||
b := data
|
|
||||||
for len(b) > 0 {
|
|
||||||
if err := context.Cause(ctx); err != nil {
|
|
||||||
return len(data) - len(b), err
|
|
||||||
}
|
|
||||||
n := min(maxBuf-len(c.write), len(b))
|
|
||||||
if n <= 0 {
|
|
||||||
c.mu.Unlock()
|
|
||||||
c.writeFull.Sleep(ctx)
|
|
||||||
c.mu.Lock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.write = append(c.write, b[:n]...)
|
|
||||||
b = b[n:]
|
|
||||||
c.writeEmpty.Wakeup()
|
|
||||||
}
|
|
||||||
return len(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeNet writes bytes from the write buffer to the network connection.
|
|
||||||
func (c *console) writeNet(ctx context.Context) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
var withCR []byte
|
|
||||||
|
|
||||||
for context.Cause(ctx) == nil {
|
|
||||||
if len(c.write) == 0 {
|
|
||||||
c.mu.Unlock()
|
|
||||||
c.writeEmpty.Sleep(ctx)
|
|
||||||
c.mu.Lock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
b := c.write
|
|
||||||
c.mu.Unlock()
|
|
||||||
|
|
||||||
withCR = withCR[:0]
|
|
||||||
for _, b := range b {
|
|
||||||
if b == '\n' {
|
|
||||||
withCR = append(withCR, '\r', '\n')
|
|
||||||
} else {
|
|
||||||
withCR = append(withCR, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := c.w.Write(withCR)
|
|
||||||
c.mu.Lock()
|
|
||||||
if err != nil {
|
|
||||||
c.writeErr = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.write = c.write[:copy(c.write, c.write[len(b):])]
|
|
||||||
c.writeFull.Wakeup()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user