From 94d4929a042637b804a587cc822a042683be72d7 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Sun, 8 Feb 2026 15:28:02 +0100 Subject: [PATCH] cli/streams: Out, In: preserve original os.File when available Preserve the original *os.File, if available, and add a File() method to return it. Signed-off-by: Sebastiaan van Stijn --- cli/streams/in.go | 11 ++++++++++- cli/streams/out.go | 11 ++++++++++- cli/streams/stream.go | 10 ++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/cli/streams/in.go b/cli/streams/in.go index f060d81ec12f..45c10ab7997a 100644 --- a/cli/streams/in.go +++ b/cli/streams/in.go @@ -3,6 +3,7 @@ package streams import ( "errors" "io" + "os" "github.com/moby/term" ) @@ -14,7 +15,8 @@ type In struct { cs commonStream } -// NewIn returns a new [In] from an [io.ReadCloser]. +// NewIn returns a new [In] from an [io.ReadCloser]. If in is an [*os.File], +// a reference is kept to the file, and accessible through [In.File]. func NewIn(in io.ReadCloser) *In { return &In{ in: in, @@ -27,6 +29,13 @@ func (i *In) FD() uintptr { return i.cs.fd } +// File returns the underlying *os.File if the stream was constructed from one. +// If the stream was created from a non-file (e.g., a pipe, buffer, or wrapper), +// the returned boolean will be false. +func (i *In) File() (*os.File, bool) { + return i.cs.file() +} + // Read implements the [io.Reader] interface. func (i *In) Read(p []byte) (int, error) { return i.in.Read(p) diff --git a/cli/streams/out.go b/cli/streams/out.go index 8565771fad11..85d69d3a81ff 100644 --- a/cli/streams/out.go +++ b/cli/streams/out.go @@ -2,6 +2,7 @@ package streams import ( "io" + "os" "github.com/moby/term" ) @@ -14,7 +15,8 @@ type Out struct { cs commonStream } -// NewOut returns a new [Out] from an [io.Writer]. +// NewOut returns a new [Out] from an [io.Writer]. If out is an [*os.File], +// a reference is kept to the file, and accessible through [Out.File]. func NewOut(out io.Writer) *Out { return &Out{ out: out, @@ -27,6 +29,13 @@ func (o *Out) FD() uintptr { return o.cs.FD() } +// File returns the underlying *os.File if the stream was constructed from one. +// If the stream was created from a non-file (e.g., a pipe, buffer, or wrapper), +// the returned boolean will be false. +func (o *Out) File() (*os.File, bool) { + return o.cs.file() +} + // Write writes to the output stream. func (o *Out) Write(p []byte) (int, error) { return o.out.Write(p) diff --git a/cli/streams/stream.go b/cli/streams/stream.go index 35758fbe975f..f6356ccf347d 100644 --- a/cli/streams/stream.go +++ b/cli/streams/stream.go @@ -11,14 +11,21 @@ import ( ) func newCommonStream(stream any) commonStream { + var f *os.File + if v, ok := stream.(*os.File); ok { + f = v + } + fd, tty := term.GetFdInfo(stream) return commonStream{ + f: f, fd: fd, tty: tty, } } type commonStream struct { + f *os.File fd uintptr tty bool state *term.State @@ -27,6 +34,9 @@ type commonStream struct { // FD returns the file descriptor number for this stream. func (s *commonStream) FD() uintptr { return s.fd } +// file returns the underlying *os.File if the stream was constructed from one. +func (s *commonStream) file() (*os.File, bool) { return s.f, s.f != nil } + // isTerminal returns whether this stream is connected to a terminal. func (s *commonStream) isTerminal() bool { return s.tty }