@@ -100,28 +100,61 @@ struct Exec: AsyncParsableCommand {
100
100
try await withThrowingTaskGroup { group in
101
101
// Stream host's standard input if interactive mode is enabled
102
102
if interactive {
103
- let stdinStream = AsyncStream < Data> { continuation in
103
+ let stdinStream = AsyncThrowingStream < Data , Error > { continuation in
104
104
let handle = FileHandle . standardInput
105
105
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
+ }
113
139
}
114
140
}
115
141
}
116
142
117
143
group. addTask {
118
- for await stdinData in stdinStream {
144
+ for try await stdinData in stdinStream {
119
145
try await execCall. requestStream. send ( . with {
120
146
$0. type = . standardInput( . with {
121
147
$0. data = stdinData
122
148
} )
123
149
} )
124
150
}
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
+ } )
125
158
}
126
159
}
127
160
@@ -178,3 +211,13 @@ struct Exec: AsyncParsableCommand {
178
211
}
179
212
}
180
213
}
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