@@ -100,28 +100,61 @@ struct Exec: AsyncParsableCommand {
100100 try await withThrowingTaskGroup { group in
101101 // Stream host's standard input if interactive mode is enabled
102102 if interactive {
103- let stdinStream = AsyncStream < Data> { continuation in
103+ let stdinStream = AsyncThrowingStream < Data , Error > { continuation in
104104 let handle = FileHandle . standardInput
105105
106- handle. readabilityHandler = { handle in
107- let data = handle. availableData
108-
109- continuation. yield ( data)
110-
111- if data. isEmpty {
112- continuation. finish ( )
106+ if isRegularFile ( handle. fileDescriptor) {
107+ // Standard input can be a regular file when input redirection (<) is used,
108+ // in which case the handle won't receive any new readability events, so we
109+ // just read the file normally here in chunks and consider done with it
110+ //
111+ // Ideally this is best handled by using non-blocking I/O, but Swift's
112+ // standard library only offers inefficient bytes[1] property and SwiftNIO's
113+ // NIOFileSystem doesn't seem to support opening raw file descriptors.
114+ //
115+ // [1]: https://developer.apple.com/documentation/foundation/filehandle/bytes
116+ while true {
117+ do {
118+ let data = try handle. read ( upToCount: 64 * 1024 )
119+ if let data = data {
120+ continuation. yield ( data)
121+ } else {
122+ continuation. finish ( )
123+ break
124+ }
125+ } catch ( let error) {
126+ continuation. finish ( throwing: error)
127+ break
128+ }
129+ }
130+ } else {
131+ handle. readabilityHandler = { handle in
132+ let data = handle. availableData
133+
134+ if data. isEmpty {
135+ continuation. finish ( )
136+ } else {
137+ continuation. yield ( data)
138+ }
113139 }
114140 }
115141 }
116142
117143 group. addTask {
118- for await stdinData in stdinStream {
144+ for try await stdinData in stdinStream {
119145 try await execCall. requestStream. send ( . with {
120146 $0. type = . standardInput( . with {
121147 $0. data = stdinData
122148 } )
123149 } )
124150 }
151+
152+ // Signal EOF as we're done reading standard input
153+ try await execCall. requestStream. send ( . with {
154+ $0. type = . standardInput( . with {
155+ $0. data = Data ( )
156+ } )
157+ } )
125158 }
126159 }
127160
@@ -178,3 +211,13 @@ struct Exec: AsyncParsableCommand {
178211 }
179212 }
180213}
214+
215+ private func isRegularFile( _ fileDescriptor: Int32 ) -> Bool {
216+ var stat = stat ( )
217+
218+ if fstat ( fileDescriptor, & stat) != 0 {
219+ return false
220+ }
221+
222+ return ( stat. st_mode & S_IFMT) == S_IFREG
223+ }
0 commit comments