@@ -82,28 +82,99 @@ func streamOutput(label string, generateOutput func(w *os.File) error) error {
8282 return streamToStdout (generateOutput )
8383 }
8484
85- pagerInput , outputFile , isSocketPair , err := createPagerFiles ()
86- if err != nil {
87- return err
85+ // Windows lacks UNIX socket APIs, so we fall back to pipes there or if
86+ // socket creation fails. We prefer sockets when available because they
87+ // allow for smaller buffer sizes, preventing unnecessary data streaming
88+ // from the backend. Pipes typically have large buffers but serve as a
89+ // decent alternative when sockets aren't available.
90+ if runtime .GOOS == "windows" {
91+ return streamToPagerWithPipe (label , generateOutput )
92+ }
93+
94+ // Try to use socket pair for better buffer control
95+ pagerInput , pid , err := openSocketPairPager (label )
96+ if err != nil || pagerInput == nil {
97+ // Fall back to pipe if socket setup fails
98+ return streamToPagerWithPipe (label , generateOutput )
8899 }
89100 defer pagerInput .Close ()
90- defer outputFile .Close ()
91101
92- cmd , err := startPagerCommand (pagerInput , label , isSocketPair )
102+ // If we would be streaming to a terminal and aren't forcing color one way
103+ // or the other, we should configure things to use color so the pager gets
104+ // colorized input.
105+ if isTerminal (os .Stdout ) && os .Getenv ("FORCE_COLOR" ) == "" {
106+ os .Setenv ("FORCE_COLOR" , "1" )
107+ }
108+
109+ // If the pager exits before reading all input, then generateOutput() will
110+ // produce a broken pipe error, which is fine and we don't want to propagate it.
111+ if err := generateOutput (pagerInput ); err != nil &&
112+ ! strings .Contains (err .Error (), "broken pipe" ) {
113+ return err
114+ }
115+
116+ // Close the file NOW before we wait for the child process to terminate.
117+ // This way, the child will receive the end-of-file signal and know that
118+ // there is no more input. Otherwise the child process may block
119+ // indefinitely waiting for another line (this can happen when streaming
120+ // less than a screenful of data to a pager).
121+ pagerInput .Close ()
122+
123+ // Wait for child process to exit
124+ var wstatus syscall.WaitStatus
125+ _ , err = syscall .Wait4 (pid , & wstatus , 0 , nil )
126+ if wstatus .ExitStatus () != 0 {
127+ return fmt .Errorf ("Pager exited with non-zero exit status: %d" , wstatus .ExitStatus ())
128+ }
129+ return err
130+ }
131+
132+ func streamToPagerWithPipe (label string , generateOutput func (w * os.File ) error ) error {
133+ r , w , err := os .Pipe ()
93134 if err != nil {
94135 return err
95136 }
137+ defer r .Close ()
138+ defer w .Close ()
139+
140+ pagerProgram := os .Getenv ("PAGER" )
141+ if pagerProgram == "" {
142+ pagerProgram = "less"
143+ }
96144
97- if err := pagerInput . Close ( ); err != nil {
145+ if _ , err := exec . LookPath ( pagerProgram ); err != nil {
98146 return err
99147 }
100148
101- // If the pager exits before reading all input, then generateOutput() will
102- // produce a broken pipe error, which is fine and we don't want to propagate it.
103- if err := generateOutput (outputFile ); err != nil && ! strings .Contains (err .Error (), "broken pipe" ) {
149+ cmd := exec .Command (pagerProgram )
150+ cmd .Stdin = r
151+ cmd .Stdout = os .Stdout
152+ cmd .Stderr = os .Stderr
153+ cmd .Env = append (os .Environ (),
154+ "LESS=-r -P " + label ,
155+ "MORE=-r -P " + label ,
156+ )
157+
158+ if err := cmd .Start (); err != nil {
104159 return err
105160 }
106161
162+ if err := r .Close (); err != nil {
163+ return err
164+ }
165+
166+ // If we would be streaming to a terminal and aren't forcing color one way
167+ // or the other, we should configure things to use color so the pager gets
168+ // colorized input.
169+ if isTerminal (os .Stdout ) && os .Getenv ("FORCE_COLOR" ) == "" {
170+ os .Setenv ("FORCE_COLOR" , "1" )
171+ }
172+
173+ if err := generateOutput (w ); err != nil && ! strings .Contains (err .Error (), "broken pipe" ) {
174+ return err
175+ }
176+
177+ w .Close ()
107178 return cmd .Wait ()
108179}
109180
@@ -137,31 +208,33 @@ func startPagerCommand(pagerInput *os.File, label string, useSocketpair bool) (*
137208 pagerProgram = "less"
138209 }
139210
140- if shouldUseColors (os .Stdout ) {
141- os .Setenv ("FORCE_COLOR" , "1" )
211+ pagerPath , err := exec .LookPath (pagerProgram )
212+ if err != nil {
213+ unix .Close (parentFd )
214+ return nil , 0 , err
142215 }
143216
144- var cmd * exec.Cmd
145- if useSocketpair {
146- cmd = exec .Command (pagerProgram , fmt .Sprintf ("/dev/fd/%d" , pagerInput .Fd ()))
147- cmd .ExtraFiles = []* os.File {pagerInput }
148- } else {
149- cmd = exec .Command (pagerProgram )
150- cmd .Stdin = pagerInput
151- }
217+ env := os .Environ ()
218+ env = append (env , "LESS=-r -P " + label )
219+ env = append (env , "MORE=-r -P " + label )
152220
153- cmd .Stdout = os .Stdout
154- cmd .Stderr = os .Stderr
155- cmd .Env = append (os .Environ (),
156- "LESS=-r -f -P " + label ,
157- "MORE=-r -f -P " + label ,
158- )
221+ procAttr := & syscall.ProcAttr {
222+ Dir : "" ,
223+ Env : env ,
224+ Files : []uintptr {
225+ uintptr (childFd ), // stdin (fd 0)
226+ uintptr (syscall .Stdout ), // stdout (fd 1)
227+ uintptr (syscall .Stderr ), // stderr (fd 2)
228+ },
229+ }
159230
160- if err := cmd .Start (); err != nil {
161- return nil , err
231+ pid , err := syscall .ForkExec (pagerPath , []string {pagerProgram }, procAttr )
232+ if err != nil {
233+ unix .Close (parentFd )
234+ return nil , 0 , err
162235 }
163236
164- return cmd , nil
237+ return parentConn , pid , nil
165238}
166239
167240func shouldUseColors (w io.Writer ) bool {
0 commit comments