55 "io"
66 "os/exec"
77 "strings"
8+ "sync"
89 "syscall"
910 "time"
1011
@@ -13,7 +14,7 @@ import (
1314)
1415
1516const (
16- StartCommandWait = 30 * time .Second
17+ StartCommandWait = 15 * time .Second
1718 RunCommandTimeout = 60 * time .Second
1819)
1920
@@ -32,39 +33,56 @@ func StartCommand(log *logrus.Entry, commandName string, arg ...string) (string,
3233 outPipe , _ := cmd .StdoutPipe ()
3334 errPipe , _ := cmd .StderrPipe ()
3435
35- var sb strings.Builder
36+ var sbOut strings.Builder
37+ var sbErr strings.Builder
38+
3639 go func (_ io.ReadCloser ) {
3740 reader := bufio .NewReader (errPipe )
38- line , err := reader .ReadString ('\n' )
39- for err == nil {
40- sb .WriteString (line )
41- line , err = reader .ReadString ('\n' )
41+ for {
42+ line , err := reader .ReadString ('\n' )
43+ // Write line even if there's an error, as long as we got data
44+ if len (line ) > 0 {
45+ sbErr .WriteString (line )
46+ }
47+ if err != nil {
48+ break
49+ }
4250 }
4351 }(errPipe )
4452
4553 go func (_ io.ReadCloser ) {
4654 reader := bufio .NewReader (outPipe )
47- line , err := reader .ReadString ('\n' )
48- for err == nil {
49- sb .WriteString (line )
50- line , err = reader .ReadString ('\n' )
55+ for {
56+ line , err := reader .ReadString ('\n' )
57+ // Write line even if there's an error, as long as we got data
58+ if len (line ) > 0 {
59+ sbOut .WriteString (line )
60+ }
61+ if err != nil {
62+ break
63+ }
5164 }
5265 }(outPipe )
5366
5467 // start async
5568 go func () {
5669 log .Debug ("Starting async ..." )
57- _ , err := pty .Start (cmd )
70+ ptmx , err := pty .Start (cmd )
5871 if err != nil {
5972 log .Errorf ("Start returned error: %v" , err )
73+ return
6074 }
75+ // Note: PTY is intentionally NOT closed here as command continues running
76+ // Keep the PTY file descriptor alive to prevent SIGHUP
77+ _ = ptmx // Keep reference to prevent premature PTY closure
6178 }()
6279
6380 log .Debugf ("Waiting %v ..." , StartCommandWait )
6481 time .Sleep (StartCommandWait )
6582
6683 log .Debug ("Returning result while command still running" )
67- return sb .String (), nil
84+ // Combine stderr first (errors more visible), then stdout
85+ return sbErr .String () + sbOut .String (), nil
6886}
6987
7088// run command with tty support and wait for stop
@@ -77,44 +95,65 @@ func RunCommand(log *logrus.Entry, commandName string, arg ...string) (string, e
7795 outPipe , _ := cmd .StdoutPipe ()
7896 errPipe , _ := cmd .StderrPipe ()
7997
80- var sb strings.Builder
98+ var sbOut strings.Builder
99+ var sbErr strings.Builder
100+ var wg sync.WaitGroup
101+
102+ wg .Add (2 )
81103 go func (_ io.ReadCloser ) {
104+ defer wg .Done ()
82105 reader := bufio .NewReader (errPipe )
83- line , err := reader .ReadString ('\n' )
84- for err == nil {
85- sb .WriteString (line )
86- line , err = reader .ReadString ('\n' )
106+ for {
107+ line , err := reader .ReadString ('\n' )
108+ // Write line even if there's an error, as long as we got data
109+ if len (line ) > 0 {
110+ sbErr .WriteString (line )
111+ }
112+ if err != nil {
113+ break
114+ }
87115 }
88116 }(errPipe )
89117
90118 go func (_ io.ReadCloser ) {
119+ defer wg .Done ()
91120 reader := bufio .NewReader (outPipe )
92- line , err := reader .ReadString ('\n' )
93- for err == nil {
94- sb .WriteString (line )
95- line , err = reader .ReadString ('\n' )
121+ for {
122+ line , err := reader .ReadString ('\n' )
123+ // Write line even if there's an error, as long as we got data
124+ if len (line ) > 0 {
125+ sbOut .WriteString (line )
126+ }
127+ if err != nil {
128+ break
129+ }
96130 }
97131 }(outPipe )
98132
99133 log .Debug ("Starting ..." )
100- _ , err := pty .Start (cmd )
134+ ptmx , err := pty .Start (cmd )
101135 if err != nil {
102136 log .Errorf ("Start returned error: %v" , err )
103137 return "" , err
104138 }
139+ defer ptmx .Close () // Ensure PTY is closed after command finishes
105140
106141 log .Debug ("Waiting ..." )
107142 err = cmd .Wait ()
108143 if err != nil {
109144 log .Errorf ("Wait returned error: %v" , err )
110145 }
111146
147+ log .Debug ("Waiting for output goroutines to finish..." )
148+ wg .Wait ()
149+
112150 // TODO: find why this returns -1. That may be related to pty implementation
113151 /*if cmd.ProcessState.ExitCode() != 0 {
114- return sb .String(), fmt.Errorf("Cmd returned code %d", cmd.ProcessState.ExitCode())
152+ return sbErr.String() + sbOut .String(), fmt.Errorf("Cmd returned code %d", cmd.ProcessState.ExitCode())
115153 }*/
116154
117- return sb .String (), nil
155+ // Combine stderr first (errors more visible), then stdout
156+ return sbErr .String () + sbOut .String (), nil
118157}
119158
120159// run command with tty support and terminate it after timeout
@@ -137,22 +176,38 @@ func RunCommandAndTerminate(log *logrus.Entry, commandName string, arg ...string
137176 })
138177 defer timer .Stop ()
139178
140- var sb strings.Builder
179+ var sbOut strings.Builder
180+ var sbErr strings.Builder
181+ var wg sync.WaitGroup
182+
183+ wg .Add (2 )
141184 go func (_ io.ReadCloser ) {
185+ defer wg .Done ()
142186 reader := bufio .NewReader (errPipe )
143- line , err := reader .ReadString ('\n' )
144- for err == nil {
145- sb .WriteString (line )
146- line , err = reader .ReadString ('\n' )
187+ for {
188+ line , err := reader .ReadString ('\n' )
189+ // Write line even if there's an error, as long as we got data
190+ if len (line ) > 0 {
191+ sbErr .WriteString (line )
192+ }
193+ if err != nil {
194+ break
195+ }
147196 }
148197 }(errPipe )
149198
150199 go func (_ io.ReadCloser ) {
200+ defer wg .Done ()
151201 reader := bufio .NewReader (outPipe )
152- line , err := reader .ReadString ('\n' )
153- for err == nil {
154- sb .WriteString (line )
155- line , err = reader .ReadString ('\n' )
202+ for {
203+ line , err := reader .ReadString ('\n' )
204+ // Write line even if there's an error, as long as we got data
205+ if len (line ) > 0 {
206+ sbOut .WriteString (line )
207+ }
208+ if err != nil {
209+ break
210+ }
156211 }
157212 }(outPipe )
158213
@@ -178,10 +233,14 @@ func RunCommandAndTerminate(log *logrus.Entry, commandName string, arg ...string
178233 log .Errorf ("Wait returned error: %v" , err )
179234 }
180235
236+ log .Debug ("Waiting for output goroutines to finish..." )
237+ wg .Wait ()
238+
181239 // TODO: find why this returns -1. That may be related to pty implementation
182240 /*if cmd.ProcessState.ExitCode() != 0 {
183- return sb .String(), fmt.Errorf("Cmd returned code %d", cmd.ProcessState.ExitCode())
241+ return sbErr.String() + sbOut .String(), fmt.Errorf("Cmd returned code %d", cmd.ProcessState.ExitCode())
184242 }*/
185243
186- return sb .String (), nil
244+ // Combine stderr first (errors more visible), then stdout
245+ return sbErr .String () + sbOut .String (), nil
187246}
0 commit comments