mirror of
https://github.com/lightninglabs/loop.git
synced 2026-08-13 12:33:03 +02:00
143 lines
2.9 KiB
Go
143 lines
2.9 KiB
Go
package main
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
// hookStdout redirects stdout and returns a function to restore it.
|
|
func hookStdout(orig *os.File, forward io.Writer,
|
|
onChunk func([]byte)) (func() error, error) {
|
|
|
|
return hookOutput(
|
|
func(f *os.File) { os.Stdout = f },
|
|
orig,
|
|
forward,
|
|
onChunk,
|
|
)
|
|
}
|
|
|
|
// hookStderr redirects stderr and returns a function to restore it.
|
|
func hookStderr(orig *os.File, forward io.Writer,
|
|
onChunk func([]byte)) (func() error, error) {
|
|
|
|
return hookOutput(
|
|
func(f *os.File) { os.Stderr = f },
|
|
orig,
|
|
forward,
|
|
onChunk,
|
|
)
|
|
}
|
|
|
|
// hookOutput redirects an output stream and returns a restore function.
|
|
func hookOutput(setDest func(*os.File), orig *os.File, forward io.Writer,
|
|
onChunk func([]byte)) (func() error, error) {
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
setDest(w)
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Go(func() {
|
|
defer r.Close()
|
|
|
|
writer := composeWriter(forward, onChunk)
|
|
// Restoring the original descriptor closes the pipe from the
|
|
// other side, so io.Copy can fail as part of normal teardown.
|
|
_, _ = io.Copy(writer, r)
|
|
})
|
|
|
|
return func() error {
|
|
setDest(orig)
|
|
_ = w.Close()
|
|
wg.Wait()
|
|
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
// hookStdin redirects stdin and returns a function to restore it.
|
|
func hookStdin(orig *os.File, source io.Reader,
|
|
onChunk func([]byte)) (func() error, error) {
|
|
|
|
r, w, err := os.Pipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
useOrig := false
|
|
if source == nil {
|
|
source = orig
|
|
useOrig = true
|
|
} else if source == orig {
|
|
useOrig = true
|
|
}
|
|
|
|
if onChunk != nil {
|
|
source = io.TeeReader(source, chunkWriter(onChunk))
|
|
}
|
|
|
|
os.Stdin = r
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Go(func() {
|
|
defer w.Close()
|
|
|
|
// Closing the pipe during hook restoration can terminate the
|
|
// copy loop with an expected error.
|
|
_, _ = io.Copy(w, source)
|
|
})
|
|
|
|
return func() error {
|
|
os.Stdin = orig
|
|
_ = r.Close()
|
|
_ = w.Close()
|
|
// When stdin is still backed by the original terminal, the copy
|
|
// goroutine can block indefinitely on user input. In that case
|
|
// we rely on closing the pipe or process exit to end the copy
|
|
// loop instead of waiting here and hanging the CLI shutdown
|
|
// path.
|
|
if !useOrig {
|
|
wg.Wait()
|
|
}
|
|
|
|
return nil
|
|
}, nil
|
|
}
|
|
|
|
// composeWriter builds a writer that forwards to the provided sinks.
|
|
func composeWriter(forward io.Writer, onChunk func([]byte)) io.Writer {
|
|
switch {
|
|
case forward != nil && onChunk != nil:
|
|
return io.MultiWriter(forward, chunkWriter(onChunk))
|
|
|
|
case forward != nil:
|
|
return forward
|
|
|
|
case onChunk != nil:
|
|
return chunkWriter(onChunk)
|
|
|
|
default:
|
|
return io.Discard
|
|
}
|
|
}
|
|
|
|
// chunkWriter writes copy-safe chunks to a callback.
|
|
type chunkWriter func([]byte)
|
|
|
|
// Write copies p before forwarding to the callback.
|
|
func (w chunkWriter) Write(p []byte) (int, error) {
|
|
if len(p) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
copyBuf := make([]byte, len(p))
|
|
copy(copyBuf, p)
|
|
w(copyBuf)
|
|
|
|
return len(p), nil
|
|
}
|