@@ -10,7 +10,6 @@ import (
1010 "os/exec"
1111 "os/signal"
1212 "path"
13- "runtime"
1413 "strings"
1514 "syscall"
1615
@@ -22,7 +21,6 @@ import (
2221 "github.com/tidwall/gjson"
2322 "github.com/tidwall/pretty"
2423 "github.com/urfave/cli/v3"
25- "golang.org/x/sys/unix"
2624 "golang.org/x/term"
2725)
2826
@@ -133,51 +131,14 @@ func streamOutput(label string, generateOutput func(w *os.File) error) error {
133131 return streamToStdout (generateOutput )
134132 }
135133
136- // Windows lacks UNIX socket APIs, so we fall back to pipes there or if
137- // socket creation fails. We prefer sockets when available because they
138- // allow for smaller buffer sizes, preventing unnecessary data streaming
139- // from the backend. Pipes typically have large buffers but serve as a
140- // decent alternative when sockets aren't available.
141- if runtime .GOOS == "windows" {
142- return streamToPagerWithPipe (label , generateOutput )
143- }
144-
145- // Try to use socket pair for better buffer control
146- pagerInput , pid , err := openSocketPairPager (label )
147- if err != nil || pagerInput == nil {
148- // Fall back to pipe if socket setup fails
149- return streamToPagerWithPipe (label , generateOutput )
150- }
151- defer pagerInput .Close ()
152-
153- // If we would be streaming to a terminal and aren't forcing color one way
154- // or the other, we should configure things to use color so the pager gets
155- // colorized input.
156- if isTerminal (os .Stdout ) && os .Getenv ("FORCE_COLOR" ) == "" {
157- os .Setenv ("FORCE_COLOR" , "1" )
158- }
159-
160- // If the pager exits before reading all input, then generateOutput() will
161- // produce a broken pipe error, which is fine and we don't want to propagate it.
162- if err := generateOutput (pagerInput ); err != nil &&
163- ! strings .Contains (err .Error (), "broken pipe" ) {
164- return err
165- }
166-
167- // Close the file NOW before we wait for the child process to terminate.
168- // This way, the child will receive the end-of-file signal and know that
169- // there is no more input. Otherwise the child process may block
170- // indefinitely waiting for another line (this can happen when streaming
171- // less than a screenful of data to a pager).
172- pagerInput .Close ()
173-
174- // Wait for child process to exit
175- var wstatus syscall.WaitStatus
176- _ , err = syscall .Wait4 (pid , & wstatus , 0 , nil )
177- if wstatus .ExitStatus () != 0 {
178- return fmt .Errorf ("Pager exited with non-zero exit status: %d" , wstatus .ExitStatus ())
179- }
180- return err
134+ // When streaming output on Unix-like systems, there's a special trick involving creating two socket pairs
135+ // that we prefer because it supports small buffer sizes which results in less pagination per buffer. The
136+ // constructs needed to run it don't exist on Windows builds, so we have this function broken up into
137+ // OS-specific files with conditional build comments. Under Windows (and in case our fancy constructs fail
138+ // on Unix), we fall back to using pipes (`streamToPagerWithPipe`), which are OS agnostic.
139+ //
140+ // Defined in either cmdutil_unix.go or cmdutil_windows.go.
141+ return streamOutputOSSpecific (label , generateOutput )
181142}
182143
183144func streamToPagerWithPipe (label string , generateOutput func (w * os.File ) error ) error {
@@ -238,70 +199,6 @@ func streamToStdout(generateOutput func(w *os.File) error) error {
238199 return err
239200}
240201
241- func openSocketPairPager (label string ) (* os.File , int , error ) {
242- fds , err := unix .Socketpair (unix .AF_UNIX , unix .SOCK_STREAM , 0 )
243- if err != nil {
244- return nil , 0 , err
245- }
246-
247- // The child file descriptor will be sent to the child process through
248- // ProcAttr and ForkExec(), while the parent process will always close the
249- // child file descriptor.
250- // The parent file descriptor will be wrapped in an os.File wrapper and
251- // returned from this function, or closed if something goes wrong.
252- parentFd , childFd := fds [0 ], fds [1 ]
253- defer unix .Close (childFd )
254-
255- // Use small buffer sizes so we don't ask the server for more paginated
256- // values than we actually need.
257- if err := unix .SetsockoptInt (parentFd , unix .SOL_SOCKET , unix .SO_SNDBUF , 128 ); err != nil {
258- unix .Close (parentFd )
259- return nil , 0 , err
260- }
261- if err := unix .SetsockoptInt (childFd , unix .SOL_SOCKET , unix .SO_RCVBUF , 128 ); err != nil {
262- unix .Close (parentFd )
263- return nil , 0 , err
264- }
265-
266- // Set CLOEXEC on the parent file descriptor so it doesn't leak to child
267- syscall .CloseOnExec (parentFd )
268-
269- parentConn := os .NewFile (uintptr (parentFd ), "parent-socket" )
270-
271- pagerProgram := os .Getenv ("PAGER" )
272- if pagerProgram == "" {
273- pagerProgram = "less"
274- }
275-
276- pagerPath , err := exec .LookPath (pagerProgram )
277- if err != nil {
278- unix .Close (parentFd )
279- return nil , 0 , err
280- }
281-
282- env := os .Environ ()
283- env = append (env , "LESS=-r -P " + label )
284- env = append (env , "MORE=-r -P " + label )
285-
286- procAttr := & syscall.ProcAttr {
287- Dir : "" ,
288- Env : env ,
289- Files : []uintptr {
290- uintptr (childFd ), // stdin (fd 0)
291- uintptr (syscall .Stdout ), // stdout (fd 1)
292- uintptr (syscall .Stderr ), // stderr (fd 2)
293- },
294- }
295-
296- pid , err := syscall .ForkExec (pagerPath , []string {pagerProgram }, procAttr )
297- if err != nil {
298- unix .Close (parentFd )
299- return nil , 0 , err
300- }
301-
302- return parentConn , pid , nil
303- }
304-
305202func shouldUseColors (w io.Writer ) bool {
306203 // Check if NO_COLOR environment variable is set
307204 if _ , noColor := os .LookupEnv ("NO_COLOR" ); noColor {
0 commit comments