How to remove Cursor Position ANSI escape code from `HijackedResponse` in Go? - go

I am trying to exec (interact with) a docker container, with Go.
This is the code I am using:
func (docker *Docker) redirectResponseToOutputStream(outputStream, errorStream io.Writer, resp io.Reader) error {
_, err := stdcopy.StdCopy(outputStream, errorStream, resp)
return err
}
func (docker *Docker) holdHijackedConnection(inputStream io.Reader, outputStream, errorStream io.Writer, resp types.HijackedResponse) error {
receiveStdout := make(chan error)
if outputStream != nil || errorStream != nil {
go func() {
receiveStdout <- docker.redirectResponseToOutputStream(outputStream, errorStream, resp.Reader)
}()
}
stdinDone := make(chan struct{})
go func() {
if inputStream != nil {
io.Copy(resp.Conn, inputStream)
}
resp.CloseWrite()
close(stdinDone)
}()
select {
case err := <-receiveStdout:
return err
case <-stdinDone:
if outputStream != nil || errorStream != nil {
return <-receiveStdout
}
}
return nil
}
...and call the the holdHijackedConnection here:
func (docker *Docker) ContainerExec(ctx context.Context, container *injection.Container) error {
createResponse, err := docker.client.ContainerExecCreate(ctx, container.ID, types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
AttachStdin: true,
Detach: true,
Tty: true,
Cmd: []string{"sh"},
})
if err != nil {
return err
}
stream, err := docker.client.ContainerExecAttach(ctx, createResponse.ID, types.ExecStartCheck{})
if err != nil {
return err
}
defer stream.Close()
docker.holdHijackedConnection(os.Stdin, os.Stdout, os.Stderr, stream)
return nil
}
Some notes:
sh is necessary, it's an alpine image
injection.Container just holds information about the container, it's a custom struct
Docker is a struct, that holds the docker client (an instance from Client at github.com/docker/docker/client)
What I get as a result to the cli, if I execute my application, is something like this:
/usr/app $ ^[[43;12R
I far as I know, the ^[[43;12R is the ANSI escape code for the position of the cursor.
I can execute commands, like ls or npm i whatever, but I alwasy get back these ANSI escape codes.
My question is, is there some way to remove these from the stdout?

Related

How to kill command Exec in difference Function in Golang

i'm making screen record web based using command exec to run FFMPEG. here I created a startRecording function but I am still confused about stopping the command process in the stopRecording function, because the command is executed in the startRecording function. How to stop a process that is already running in the srartRecording function in the stopRecording function?
here my code
//Handler to create room/start record
func RoomCreate(c *fiber.Ctx) error {
fileName := "out.mp4"
fmt.Println(fileName)
if len(os.Args) > 1 {
fileName = os.Args[1]
}
errCh := make(chan error, 2)
ctx, cancelFn := context.WithCancel(context.Background())
// Call to function startRecording
go func() { errCh <- startRecording(ctx, fileName) }()
go func() {
errCh <- nil
}()
err := <-errCh
cancelFn()
if err != nil && err != context.Canceled {
log.Fatalf("Execution failed: %v", err)
}
return c.Redirect(fmt.Sprintf("/room/%s", guuid.New().String()))
}
//Function to run command FFMPEG
func startRecording(ctx context.Context, fileName string) error {
ctx, cancelFn := context.WithCancel(ctx)
defer cancelFn()
// Build ffmpeg
ffmpeg := exec.Command("ffmpeg",
"-f", "gdigrab",
"-framerate", "30",
"-i", "desktop",
"-f", "mp4",
fileName,
)
// Stdin for sending data
stdin, err := ffmpeg.StdinPipe()
if err != nil {
return err
}
//var buf bytes.Buffer
defer stdin.Close()
// Run it in the background
errCh := make(chan error, 1)
go func() {
fmt.Printf("Executing: %v\n", strings.Join(ffmpeg.Args, " "))
if err := ffmpeg.Run(); err != nil {
return
}
//fmt.Printf("FFMPEG output:\n%v\n", string(out))
errCh <- err
}()
// Just start sending a bunch of frames
for {
// Check if we're done, otherwise go again
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errCh:
return err
default:
}
}
}
//Here function to stop Recording
func stopRecording(ctx context.Context) error {
//Code stop recording in here
}
Thanks for advance
As requested from comments.
The basic idea is to use global storage to store your active commands. It doesn't necessarily be global but you need to have bigger scope so that your functions can access it.
var commands = map[string]*exec.Cmd{}
func startRecording(fileName string) error {
ffmpeg := exec.Command("ffmpeg",
"-f", "gdigrab",
"-framerate", "30",
"-i", "desktop",
"-f", "mp4",
fileName,
)
commands[fileName] = ffmpeg
...
}
func stopRecording(fileName string) error {
cmd, ok := commands[fileName]
if !ok {
return errors.New("command not found")
}
defer func() {
delete(commands, fileName)
}()
return cmd.Process.Kill()
}
You probably want to use sync.Mutex or sync.RWMutex to avoid concurrent map writes.
So your commands cloud look like:
type Commands struct {
sync.RWMutex
items map[string]*exec.Cmd
}
// use Commands.Lock() for writing, Commands.RLock() for reading

Channels in Golang with TCP/IP socket not working

I just started writting a Golang client for a server that I've made in C with TCP/IP sockets, then I figured out that my channel wasn't working.
Any ideas why ?
func reader(r io.Reader, channel chan<- []byte) {
buf := make([]byte, 2048)
for {
n, err := r.Read(buf[:])
if err != nil {
return
}
channel <- buf[0:n]
}
}
func client(e *gowd.Element) {
f, err := os.Create("/tmp/dat2")
if err != nil {
log.Fatal()
}
read := make(chan []byte)
c, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
go reader(c, read)
for {
buf := <-read
n := strings.Index(string(buf), "\n")
if n == -1 {
continue
}
msg := string(buf[0:n])
if msg == "WELCOME" {
fmt.Fprint(c, "GRAPHIC\n")
}
f.WriteString(msg + "\n")
}
Testing my server with netcat results in the following output :
http://pasted.co/a37b2954
But i only have : http://pasted.co/f13d56b4
I'm new to chan in Golang so maybe I'm wrong (I probably am)
Channel usage looks alright, however retrieving value from channel would overwrite previously read value at buf := <-read since your waiting for newline.
Also you can use bufio.Reader to read string upto newline.
Your code snippet is partial so its not feasible to execute, try and let me know:
func reader(r io.Reader, channel chan<- string) {
bufReader := bufio.NewReader(conn)
for {
msg, err := bufReader.ReadString('\n')
if err != nil { // connection error or connection reset error, etc
break
}
channel <- msg
}
}
func client(e *gowd.Element) {
f, err := os.Create("/tmp/dat2")
if err != nil {
log.Fatal()
}
read := make(chan string)
c, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
go reader(c, read)
for {
msg := <-read
if msg == "WELCOME" {
fmt.Fprint(c, "GRAPHIC\n")
}
f.WriteString(msg + "\n")
}
//...
}
EDIT:
Please find example of generic TCP client to read data. Also I have removed scanner from above code snippet and added buffer reader.
func main() {
conn, err := net.Dial("tcp", "127.0.0.1:4242")
if err != nil {
log.Fatal(err)
}
reader := bufio.NewReader(conn)
for {
msg, err := reader.ReadString('\n')
if err != nil {
break
}
fmt.Println(msg)
}
}

Is there a good way to cancel a blocking read?

I've got a command I have to run via the OS, let's call it 'login', that is interactive and therefore requires me to read from the stdin and pass it to the command's stdin pipe in order for it to work correctly. The only problem is the goroutine blocks on a read from stdin and I haven't been able to find a way to cancel a Reader in Go in order to get it to not hang on the blocking call. For example, from the perspective of the user, after the command looks as if it completed, you still have the opportunity to write to stdin once more (then the goroutine will move past the blocking read and exit)
Ideally I would like to avoid having to parse output from the command's StdoutPipe as that makes my code frail and prone to error if the strings of the login command were to change.
loginCmd := exec.Command("login")
stdin , err := loginCmd.StdinPipe()
if err != nil {
return err
}
out, err := loginCmd.StdoutPipe()
if err != nil {
return err
}
if err := loginCmd.Start(); err != nil {
return err
}
ctx, cancel := context.WithCancel(context.TODO())
var done sync.WaitGroup
done.Add(1)
ready := make(chan bool, 1)
defer cancel()
go func(ctx context.Context) {
reader := bufio.NewReader(os.Stdin)
for {
select {
case <- ctx.Done():
done.Done()
return
default:
//blocks on this line, if a close can unblock the read, then it should exit normally via the ctx.Done() case
line, err :=reader.ReadString('\n')
if err != nil {
fmt.Println("Error: ", err.Error())
}
stdin.Write([]byte(line))
}
}
}(ctx)
var bytesRead = 4096
output := make([]byte, bytesRead)
reader := bufio.NewReader(out)
for err == nil {
bytesRead, err = reader.Read(output)
if err != nil && err != io.EOF {
return err
}
fmt.Printf("%s", output[:bytesRead])
}
if err := loginCmd.Wait(); err != nil {
return err
}
cancel()
done.Wait()

writing twice to the same sub process golang

I have a simple scp function that is just a wrapper over the scp cli tool.
type credential struct {
username string
password string
host string
port string
}
func scpFile(filepath, destpath string, c *credential) error {
cmd := exec.Command("scp", filepath, c.username+"#"+c.host+":"+destpath)
if err := cmd.Run(); err != nil {
return err
}
fmt.Println("done")
return nil
}
This works just fine now I want to add the capability of putting in a password the SSH if scp needs it. This is what I came up with
func scpFile(filepath, destpath string, c *credential) error {
cmd := exec.Command("scp", filepath, c.username+"#"+c.host+":"+destpath)
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
defer stdin.Close()
if err := cmd.Start(); err != nil {
return err
}
io.WriteString(stdin, c.password+"\n")
cmd.Wait()
fmt.Println("done")
return nil
}
This does not work as the password prompt just hangs there. I tried adding a 1 second sleep before I re write to stdin thinking maybe I was writing the password to fast but did not make a difference.
So I was able to find a work around by instead of trying to send the password to stdin I create a ssh session and scp a file through the ssh session. Here is the new scpFile function:
func scpFile(filePath, destinationPath string, session *ssh.Session) error {
defer session.Close()
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
s, err := f.Stat()
if err != nil {
return err
}
go func() {
w, _ := session.StdinPipe()
defer w.Close()
fmt.Fprintf(w, "C%#o %d %s\n", s.Mode().Perm(), s.Size(), path.Base(filePath))
io.Copy(w, f)
fmt.Fprint(w, "\x00")
}()
cmd := fmt.Sprintf("scp -t %s", destinationPath)
if err := session.Run(cmd); err != nil {
return err
}
return nil
}
This could probably be made better but the main idea is there

Golang - Copy exec output to a buffer

I'm writing a function that exec's a program and returns stdout and stderr. It also has the option to display the output to the console. I'm clearly not waiting on something, as if I run the function twice in a row, the outputs are different. Here's a sample program, replace the dir var with a dir with a lot of files to fill up the buffers:
func main() {
dir := "SOMEDIRECTORYWITHALOTOFFILES"
out, err := run("ls -l "+dir, true)
if err != nil {
log.Fatalf("run returned %s", err)
}
log.Printf("Out: %s", out)
out2, err := run("ls -l "+dir, false)
if err != nil {
log.Fatalf("run returned %s", err)
}
log.Printf("Out2: %s", out2)
if out != out2 {
log.Fatalf("Out mismatch")
}
}
func run(cmd string, displayOutput bool) (string, error) {
var command *exec.Cmd
command = exec.Command("/bin/sh", "-c", cmd)
var output bytes.Buffer
stdout, err := command.StdoutPipe()
if err != nil {
return "", fmt.Errorf("Unable to setup stdout for command: %v", err)
}
go func() {
if displayOutput == true {
w := io.MultiWriter(os.Stdout, &output)
io.Copy(w, stdout)
} else {
output.ReadFrom(stdout)
}
}()
stderr, err := command.StderrPipe()
if err != nil {
return "", fmt.Errorf("Unable to setup stderr for command: %v", err)
}
go func() {
if displayOutput == true {
w := io.MultiWriter(os.Stderr, &output)
io.Copy(w, stderr)
} else {
output.ReadFrom(stderr)
}
}()
err = command.Run()
if err != nil {
return "", err
}
return output.String(), nil
}
Here is a simplified and working revision of your example. Note that the test command was swapped out so that I could test within Windows and that your error checks have been omitted only for brevity.
The key change is that a sync.WaitGroup is preventing the run function from printing the output and returning until the goroutine has indicated that it's finished.
func main() {
dir := "c:\\windows\\system32"
command1 := exec.Command("cmd", "/C", "dir", "/s", dir)
command2 := exec.Command("cmd", "/C", "dir", "/s", dir)
out1, _ := run(command1)
out2, _ := run(command2)
log.Printf("Length [%d] vs [%d]\n", len(out1), len(out2))
}
func run(cmd *exec.Cmd) (string, error) {
var output bytes.Buffer
var waitGroup sync.WaitGroup
stdout, _ := cmd.StdoutPipe()
writer := io.MultiWriter(os.Stdout, &output)
waitGroup.Add(1)
go func() {
defer waitGroup.Done()
io.Copy(writer, stdout)
}()
cmd.Run()
waitGroup.Wait()
return output.String(), nil
}
I see some problems:
You should be waiting for the goroutines to finish (e.g., using
sync.WaitGroup).
You're accessing output concurrently in two
goroutines, which is not safe.
You could collect stdout and stderr in two separate buffers and return them separately, if that works for what you're trying to do.

Resources