Skip to content

Commit e5a341c

Browse files
committed
Fix crashes in ZBCommand
Replace manual pipe allocation and tedious/unsafe opening/closing of pipes with safe objc wrappers that handle the sanity checks at each step. Safer and less boilerplate
1 parent c615ab4 commit e5a341c

File tree

1 file changed

+113
-44
lines changed

1 file changed

+113
-44
lines changed

Zebra/Console/ZBCommand.m

Lines changed: 113 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,92 @@
1010
#import "ZBDevice.h"
1111
#import "spawn.h"
1212

13-
typedef struct ZBCommandFds {
14-
int stdOut[2];
15-
int stdErr[2];
16-
int finish[2];
17-
} ZBCommandFds;
13+
@interface ZBPipe : NSObject {
14+
@public
15+
int fds[2];
16+
}
17+
- (void)close;
18+
- (void)closeRead;
19+
- (void)closeWrite;
20+
@end
21+
22+
@implementation ZBPipe
23+
- (instancetype)init {
24+
self = [super init];
25+
if (![self open]) {
26+
return nil;
27+
}
28+
return self;
29+
}
30+
- (BOOL)open {
31+
if (pipe(fds) == -1) return NO;
32+
return YES;
33+
}
34+
- (void)close {
35+
[self closeRead];
36+
[self closeWrite];
37+
}
38+
- (void)closeRead {
39+
[self close:0];
40+
}
41+
- (void)closeWrite {
42+
[self close:1];
43+
}
44+
- (void)close:(int)idx {
45+
NSParameterAssert(idx == 0 || idx == 1);
46+
if (fds[idx] > 0) {
47+
close(fds[idx]);
48+
fds[idx] = -1;
49+
}
50+
}
51+
@end
52+
53+
@interface ZBCommandFds : NSObject {
54+
@public
55+
ZBPipe *stdOut;
56+
ZBPipe *stdErr;
57+
ZBPipe *finish;
58+
}
59+
+ (instancetype)useFinish:(BOOL)useFinishFd;
60+
- (void)close;
61+
- (void)closeRead:(BOOL)useFinishFd;
62+
- (void)closeWrite:(BOOL)useFinishFd;
63+
@end
64+
65+
@implementation ZBCommandFds
66+
67+
+ (instancetype)useFinish:(BOOL)useFinishFd {
68+
ZBCommandFds *fds = [ZBCommandFds new];
69+
fds->stdOut = [ZBPipe new];
70+
fds->stdErr = [ZBPipe new];
71+
72+
if (!fds->stdOut || !fds->stdErr) {
73+
return nil;
74+
}
75+
76+
if (useFinishFd) {
77+
fds->finish = [ZBPipe new];
78+
if (!fds->finish) return nil;
79+
}
80+
81+
return fds;
82+
}
83+
84+
- (void)close {
85+
[self closeRead];
86+
[self closeWrite];
87+
}
88+
- (void)closeRead {
89+
[self->stdOut closeRead];
90+
[self->stdErr closeRead];
91+
[self->finish closeRead];
92+
}
93+
- (void)closeWrite {
94+
[self->stdOut closeWrite];
95+
[self->stdErr closeWrite];
96+
[self->finish closeWrite];
97+
}
98+
@end
1899

19100
static const int ZBCommandFinishFileno = 3;
20101

@@ -105,19 +186,11 @@ - (void)setUseFinishFd:(BOOL)useFinishFd {
105186

106187
- (int)execute {
107188
// Create output and error pipes
108-
fds = malloc(sizeof(ZBCommandFds));
109-
if (pipe(fds->stdOut) == -1 || pipe(fds->stdErr) == -1) {
110-
free(fds);
189+
fds = [ZBCommandFds useFinish:_useFinishFd];
190+
if (!fds) {
111191
return errno; // pipe() sets errno on failure
112192
}
113193

114-
if (_useFinishFd) {
115-
if (pipe(fds->finish) == -1) {
116-
free(fds);
117-
return errno; // pipe() sets errno on failure
118-
}
119-
}
120-
121194
// Convert our arguments array from NSStrings to char pointers
122195
char **argv = (char **)malloc((_arguments.count + 1) * sizeof(char *));
123196
for (int i = 0; i < _arguments.count; i++) {
@@ -145,17 +218,17 @@ - (int)execute {
145218
// Create our file actions to read data back from posix_spawn
146219
posix_spawn_file_actions_t child_fd_actions;
147220
posix_spawn_file_actions_init(&child_fd_actions);
148-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdOut[0]);
149-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdErr[0]);
150-
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->stdOut[1], STDOUT_FILENO);
151-
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->stdErr[1], STDERR_FILENO);
152-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdOut[1]);
153-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdErr[1]);
221+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdOut->fds[0]);
222+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdErr->fds[0]);
223+
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->stdOut->fds[1], STDOUT_FILENO);
224+
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->stdErr->fds[1], STDERR_FILENO);
225+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdOut->fds[1]);
226+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->stdErr->fds[1]);
154227

155228
if (_useFinishFd) {
156-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->finish[0]);
157-
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->finish[1], ZBCommandFinishFileno);
158-
posix_spawn_file_actions_addclose(&child_fd_actions, fds->finish[1]);
229+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->finish->fds[0]);
230+
posix_spawn_file_actions_adddup2(&child_fd_actions, fds->finish->fds[1], ZBCommandFinishFileno);
231+
posix_spawn_file_actions_addclose(&child_fd_actions, fds->finish->fds[1]);
159232
}
160233

161234
// Create persona config if needed
@@ -175,12 +248,12 @@ - (int)execute {
175248
dispatch_queue_t readQueue = dispatch_queue_create("xyz.willy.Zebra.david", DISPATCH_QUEUE_CONCURRENT);
176249

177250
// Setup the dispatch handler for the output pipes
178-
dispatch_source_t stdOutSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->stdOut[0], 0, readQueue);
179-
dispatch_source_t stdErrSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->stdErr[0], 0, readQueue);
251+
dispatch_source_t stdOutSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->stdOut->fds[0], 0, readQueue);
252+
dispatch_source_t stdErrSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->stdErr->fds[0], 0, readQueue);
180253
dispatch_source_t finishSource = nil;
181254

182255
if (_useFinishFd) {
183-
finishSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->finish[0], 0, readQueue);
256+
finishSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fds->finish->fds[0], 0, readQueue);
184257
}
185258

186259
void (^handleSourceEvent)(dispatch_source_t, int, void (^)(NSString *)) = ^(dispatch_source_t source, int fd, void (^action)(NSString *)) {
@@ -202,37 +275,39 @@ - (int)execute {
202275
free(buffer);
203276
};
204277

278+
ZBCommandFds *fds = self->fds;
279+
205280
dispatch_source_set_event_handler(stdOutSource, ^{
206-
handleSourceEvent(stdOutSource, self->fds->stdOut[0], ^(NSString *string) {
281+
handleSourceEvent(stdOutSource, fds->stdOut->fds[0], ^(NSString *string) {
207282
if (self->delegate) [self->delegate receivedData:string];
208283
if (self.output) [self.output appendString:string];
209284
});
210285
});
211286
dispatch_source_set_event_handler(stdErrSource, ^{
212-
handleSourceEvent(stdErrSource, self->fds->stdErr[0], ^(NSString *string) {
287+
handleSourceEvent(stdErrSource, fds->stdErr->fds[0], ^(NSString *string) {
213288
if (self->delegate) [self->delegate receivedErrorData:string];
214289
if (self.output) [self.output appendString:string];
215290
});
216291
});
217292

218293
dispatch_source_set_cancel_handler(stdOutSource, ^{
219-
close(self->fds->stdOut[0]);
294+
[fds->stdOut closeRead];
220295
dispatch_semaphore_signal(lock);
221296
});
222297
dispatch_source_set_cancel_handler(stdErrSource, ^{
223-
close(self->fds->stdErr[0]);
298+
[fds->stdErr closeRead];
224299
dispatch_semaphore_signal(lock);
225300
});
226301

227302
if (_useFinishFd) {
228303
dispatch_source_set_event_handler(finishSource, ^{
229-
handleSourceEvent(finishSource, self->fds->finish[0], ^(NSString *string) {
304+
handleSourceEvent(finishSource, fds->finish->fds[0], ^(NSString *string) {
230305
if (self->delegate) [self->delegate receivedFinishData:string];
231306
});
232307
});
233308
dispatch_source_set_cancel_handler(finishSource, ^{
234309
// Finish fd isn’t expected to be closed, so no semaphore involved here.
235-
close(self->fds->finish[0]);
310+
[fds->finish closeRead];
236311
});
237312
}
238313

@@ -251,20 +326,14 @@ - (int)execute {
251326
free(argv);
252327
free(envp);
253328
if (ret < 0) {
254-
close(fds->stdOut[0]);
255-
close(fds->stdOut[1]);
256-
close(fds->stdErr[0]);
257-
close(fds->stdErr[1]);
258-
if (_useFinishFd) {
259-
close(fds->finish[0]);
260-
close(fds->finish[1]);
261-
}
329+
[fds close];
330+
fds = nil;
262331
return ret;
263332
}
264333

265334
// Close the write ends of the pipes so no odd data comes through them
266-
close(fds->stdOut[1]);
267-
close(fds->stdErr[1]);
335+
[fds->stdOut closeWrite];
336+
[fds->stdErr closeWrite];
268337

269338
// We need to wait twice, once for the output handler and once for the error handler
270339
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
@@ -280,7 +349,7 @@ - (int)execute {
280349
}
281350

282351
// Free our pipes
283-
free(fds);
352+
fds = nil;
284353

285354
// Get the true status code, if the process exited normally. If it died for some other reason,
286355
// we return the actual value we got back from waitpid(3), which should still be useful for

0 commit comments

Comments
 (0)