]>
Commit | Line | Data |
---|---|---|
a47d1dfd DH |
1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
2 | ||
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2014 David Herrmann <dh.herrmann@gmail.com> | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
9 | under the terms of the GNU Lesser General Public License as published by | |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
20 | ***/ | |
21 | ||
22 | /* | |
23 | * PTY | |
24 | * A PTY object represents a single PTY connection between a master and a | |
25 | * child. The child process is fork()ed so the caller controls what program | |
26 | * will be run. | |
27 | * | |
28 | * Programs like /bin/login tend to perform a vhangup() on their TTY | |
29 | * before running the login procedure. This also causes the pty master | |
30 | * to get a EPOLLHUP event as long as no client has the TTY opened. | |
31 | * This means, we cannot use the TTY connection as reliable way to track | |
32 | * the client. Instead, we _must_ rely on the PID of the client to track | |
33 | * them. | |
34 | * However, this has the side effect that if the client forks and the | |
35 | * parent exits, we loose them and restart the client. But this seems to | |
36 | * be the expected behavior so we implement it here. | |
37 | * | |
38 | * Unfortunately, epoll always polls for EPOLLHUP so as long as the | |
39 | * vhangup() is ongoing, we will _always_ get EPOLLHUP and cannot sleep. | |
40 | * This gets worse if the client closes the TTY but doesn't exit. | |
41 | * Therefore, the fd must be edge-triggered in the epoll-set so we | |
42 | * only get the events once they change. | |
43 | */ | |
44 | ||
45 | #include <errno.h> | |
46 | #include <fcntl.h> | |
a47d1dfd DH |
47 | #include <signal.h> |
48 | #include <stdbool.h> | |
49 | #include <stdint.h> | |
a47d1dfd | 50 | #include <stdlib.h> |
a47d1dfd | 51 | #include <sys/epoll.h> |
a47d1dfd | 52 | #include <sys/ioctl.h> |
a47d1dfd DH |
53 | #include <sys/uio.h> |
54 | #include <sys/wait.h> | |
55 | #include <termios.h> | |
56 | #include <unistd.h> | |
57 | ||
58 | #include "barrier.h" | |
59 | #include "macro.h" | |
60 | #include "pty.h" | |
61 | #include "ring.h" | |
62 | #include "util.h" | |
63 | ||
48fed5c5 | 64 | #define PTY_BUFSIZE 4096 |
a47d1dfd DH |
65 | |
66 | enum { | |
67 | PTY_ROLE_UNKNOWN, | |
68 | PTY_ROLE_PARENT, | |
69 | PTY_ROLE_CHILD, | |
70 | }; | |
71 | ||
72 | struct Pty { | |
73 | unsigned long ref; | |
74 | Barrier barrier; | |
75 | int fd; | |
76 | pid_t child; | |
77 | sd_event_source *fd_source; | |
78 | sd_event_source *child_source; | |
79 | ||
80 | char in_buf[PTY_BUFSIZE]; | |
81 | Ring out_buf; | |
82 | ||
83 | pty_event_t event_fn; | |
84 | void *event_fn_userdata; | |
85 | ||
86 | bool needs_requeue : 1; | |
87 | unsigned int role : 2; | |
88 | }; | |
89 | ||
90 | int pty_new(Pty **out) { | |
91 | _pty_unref_ Pty *pty = NULL; | |
92 | int r; | |
93 | ||
94 | assert_return(out, -EINVAL); | |
95 | ||
96 | pty = new0(Pty, 1); | |
97 | if (!pty) | |
98 | return -ENOMEM; | |
99 | ||
100 | pty->ref = 1; | |
101 | pty->fd = -1; | |
7566e267 | 102 | pty->barrier = (Barrier) BARRIER_NULL; |
a47d1dfd DH |
103 | |
104 | pty->fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK); | |
105 | if (pty->fd < 0) | |
106 | return -errno; | |
107 | ||
108 | /* | |
109 | * The slave-node is initialized to uid/gid of the caller of | |
110 | * posix_openpt(). Only if devpts is mounted with fixed uid/gid this is | |
111 | * skipped. In that case, grantpt() can overwrite these, but then you | |
112 | * have to be root to use chown() (or a pt_chown helper has to be | |
113 | * present). In those cases grantpt() really does something, | |
114 | * otherwise it's a no-op. We call grantpt() here to try supporting | |
115 | * those cases, even though no-one uses that, I guess. If you need other | |
116 | * access-rights, set them yourself after this call returns (no, this is | |
117 | * not racy, it looks racy, but races regarding your own UID are never | |
118 | * important as an attacker could ptrace you; and the slave-pty is also | |
119 | * still locked). | |
120 | */ | |
121 | r = grantpt(pty->fd); | |
122 | if (r < 0) | |
123 | return -errno; | |
124 | ||
7566e267 | 125 | r = barrier_create(&pty->barrier); |
a47d1dfd DH |
126 | if (r < 0) |
127 | return r; | |
128 | ||
129 | *out = pty; | |
130 | pty = NULL; | |
131 | return 0; | |
132 | } | |
133 | ||
134 | Pty *pty_ref(Pty *pty) { | |
135 | if (!pty || pty->ref < 1) | |
136 | return NULL; | |
137 | ||
138 | ++pty->ref; | |
139 | return pty; | |
140 | } | |
141 | ||
142 | Pty *pty_unref(Pty *pty) { | |
143 | if (!pty || pty->ref < 1 || --pty->ref > 0) | |
144 | return NULL; | |
145 | ||
146 | pty_close(pty); | |
147 | pty->child_source = sd_event_source_unref(pty->child_source); | |
148 | barrier_destroy(&pty->barrier); | |
149 | ring_clear(&pty->out_buf); | |
150 | free(pty); | |
151 | ||
152 | return NULL; | |
153 | } | |
154 | ||
155 | Barrier *pty_get_barrier(Pty *pty) { | |
156 | assert(pty); | |
157 | return &pty->barrier; | |
158 | } | |
159 | ||
160 | bool pty_is_unknown(Pty *pty) { | |
161 | return pty && pty->role == PTY_ROLE_UNKNOWN; | |
162 | } | |
163 | ||
164 | bool pty_is_parent(Pty *pty) { | |
165 | return pty && pty->role == PTY_ROLE_PARENT; | |
166 | } | |
167 | ||
168 | bool pty_is_child(Pty *pty) { | |
169 | return pty && pty->role == PTY_ROLE_CHILD; | |
170 | } | |
171 | ||
172 | bool pty_has_child(Pty *pty) { | |
173 | return pty_is_parent(pty) && pty->child > 0; | |
174 | } | |
175 | ||
176 | pid_t pty_get_child(Pty *pty) { | |
177 | return pty_has_child(pty) ? pty->child : -ECHILD; | |
178 | } | |
179 | ||
180 | bool pty_is_open(Pty *pty) { | |
181 | return pty && pty->fd >= 0; | |
182 | } | |
183 | ||
184 | int pty_get_fd(Pty *pty) { | |
185 | assert_return(pty, -EINVAL); | |
186 | ||
187 | return pty_is_open(pty) ? pty->fd : -EPIPE; | |
188 | } | |
189 | ||
190 | int pty_make_child(Pty *pty) { | |
611b312b | 191 | _cleanup_free_ char *slave_name = NULL; |
a47d1dfd DH |
192 | int r, fd; |
193 | ||
194 | assert_return(pty, -EINVAL); | |
195 | assert_return(pty_is_unknown(pty), -EALREADY); | |
196 | ||
611b312b | 197 | r = ptsname_malloc(pty->fd, &slave_name); |
a47d1dfd DH |
198 | if (r < 0) |
199 | return -errno; | |
200 | ||
201 | fd = open(slave_name, O_RDWR | O_CLOEXEC | O_NOCTTY); | |
202 | if (fd < 0) | |
203 | return -errno; | |
204 | ||
205 | safe_close(pty->fd); | |
206 | pty->fd = fd; | |
207 | pty->child = getpid(); | |
208 | pty->role = PTY_ROLE_CHILD; | |
209 | barrier_set_role(&pty->barrier, BARRIER_CHILD); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | int pty_make_parent(Pty *pty, pid_t child) { | |
215 | assert_return(pty, -EINVAL); | |
216 | assert_return(pty_is_unknown(pty), -EALREADY); | |
217 | ||
218 | pty->child = child; | |
219 | pty->role = PTY_ROLE_PARENT; | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
224 | int pty_unlock(Pty *pty) { | |
225 | assert_return(pty, -EINVAL); | |
226 | assert_return(pty_is_unknown(pty) || pty_is_parent(pty), -EINVAL); | |
227 | assert_return(pty_is_open(pty), -ENODEV); | |
228 | ||
229 | return unlockpt(pty->fd) < 0 ? -errno : 0; | |
230 | } | |
231 | ||
232 | int pty_setup_child(Pty *pty) { | |
233 | struct termios attr; | |
234 | pid_t pid; | |
235 | int r; | |
236 | ||
237 | assert_return(pty, -EINVAL); | |
238 | assert_return(pty_is_child(pty), -EINVAL); | |
239 | assert_return(pty_is_open(pty), -EALREADY); | |
240 | ||
241 | r = sigprocmask_many(SIG_SETMASK, -1); | |
242 | if (r < 0) | |
243 | return r; | |
244 | ||
245 | r = reset_all_signal_handlers(); | |
246 | if (r < 0) | |
247 | return r; | |
248 | ||
249 | pid = setsid(); | |
250 | if (pid < 0 && errno != EPERM) | |
251 | return -errno; | |
252 | ||
253 | r = ioctl(pty->fd, TIOCSCTTY, 0); | |
254 | if (r < 0) | |
255 | return -errno; | |
256 | ||
257 | r = tcgetattr(pty->fd, &attr); | |
258 | if (r < 0) | |
259 | return -errno; | |
260 | ||
261 | /* erase character should be normal backspace, PLEASEEE! */ | |
262 | attr.c_cc[VERASE] = 010; | |
263 | /* always set UTF8 flag */ | |
264 | attr.c_iflag |= IUTF8; | |
265 | ||
266 | r = tcsetattr(pty->fd, TCSANOW, &attr); | |
267 | if (r < 0) | |
268 | return -errno; | |
269 | ||
270 | if (dup2(pty->fd, STDIN_FILENO) != STDIN_FILENO || | |
271 | dup2(pty->fd, STDOUT_FILENO) != STDOUT_FILENO || | |
272 | dup2(pty->fd, STDERR_FILENO) != STDERR_FILENO) | |
273 | return -errno; | |
274 | ||
275 | /* only close FD if it's not a std-fd */ | |
276 | pty->fd = (pty->fd > 2) ? safe_close(pty->fd) : -1; | |
277 | ||
278 | return 0; | |
279 | } | |
280 | ||
281 | void pty_close(Pty *pty) { | |
282 | if (!pty_is_open(pty)) | |
283 | return; | |
284 | ||
285 | pty->fd_source = sd_event_source_unref(pty->fd_source); | |
286 | pty->fd = safe_close(pty->fd); | |
287 | } | |
288 | ||
289 | /* | |
290 | * Drain input-queue and dispatch data via the event-handler. Returns <0 on | |
291 | * error, 0 if queue is empty and 1 if we couldn't empty the input queue fast | |
292 | * enough and there's still data left. | |
293 | */ | |
294 | static int pty_dispatch_read(Pty *pty) { | |
295 | unsigned int i; | |
296 | ssize_t len; | |
297 | int r; | |
298 | ||
299 | /* | |
300 | * We're edge-triggered, means we need to read the whole queue. This, | |
301 | * however, might cause us to stall if the writer is faster than we | |
48fed5c5 DH |
302 | * are. Therefore, try reading as much as 8 times (32KiB) and only |
303 | * bail out then. | |
a47d1dfd DH |
304 | */ |
305 | ||
48fed5c5 | 306 | for (i = 0; i < 8; ++i) { |
a47d1dfd DH |
307 | len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf) - 1); |
308 | if (len < 0) { | |
309 | if (errno == EINTR) | |
310 | continue; | |
311 | ||
312 | return (errno == EAGAIN) ? 0 : -errno; | |
313 | } else if (len == 0) { | |
314 | continue; | |
315 | } | |
316 | ||
317 | /* set terminating zero for debugging safety */ | |
318 | pty->in_buf[len] = 0; | |
319 | r = pty->event_fn(pty, pty->event_fn_userdata, PTY_DATA, pty->in_buf, len); | |
320 | if (r < 0) | |
321 | return r; | |
322 | } | |
323 | ||
324 | /* still data left, make sure we're queued again */ | |
325 | pty->needs_requeue = true; | |
326 | ||
327 | return 1; | |
328 | } | |
329 | ||
330 | /* | |
331 | * Drain output-queue by writing data to the pty. Returns <0 on error, 0 if the | |
332 | * output queue is empty now and 1 if we couldn't empty the output queue fast | |
333 | * enough and there's still data left. | |
334 | */ | |
335 | static int pty_dispatch_write(Pty *pty) { | |
336 | struct iovec vec[2]; | |
337 | unsigned int i; | |
338 | ssize_t len; | |
339 | size_t num; | |
340 | ||
341 | /* | |
342 | * Same as pty_dispatch_read(), we're edge-triggered so we need to call | |
343 | * write() until either all data is written or it returns EAGAIN. We | |
344 | * call it twice and if it still writes successfully, we reschedule. | |
345 | */ | |
346 | ||
347 | for (i = 0; i < 2; ++i) { | |
348 | num = ring_peek(&pty->out_buf, vec); | |
349 | if (num < 1) | |
350 | return 0; | |
351 | ||
352 | len = writev(pty->fd, vec, (int)num); | |
353 | if (len < 0) { | |
354 | if (errno == EINTR) | |
355 | continue; | |
356 | ||
357 | return (errno == EAGAIN) ? 1 : -errno; | |
358 | } else if (len == 0) { | |
359 | continue; | |
360 | } | |
361 | ||
362 | ring_pull(&pty->out_buf, (size_t)len); | |
363 | } | |
364 | ||
365 | /* still data left, make sure we're queued again */ | |
366 | if (ring_get_size(&pty->out_buf) > 0) { | |
367 | pty->needs_requeue = true; | |
368 | return 1; | |
369 | } | |
370 | ||
371 | return 0; | |
372 | } | |
373 | ||
374 | static int pty_fd_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) { | |
375 | Pty *pty = userdata; | |
376 | int r_hup = 0, r_write = 0, r_read = 0, r; | |
377 | ||
378 | /* | |
379 | * Whenever we encounter I/O errors, we have to make sure to drain the | |
380 | * input queue first, before we handle any HUP. A child might send us | |
381 | * a message and immediately close the queue. We must not handle the | |
382 | * HUP first or we loose data. | |
383 | * Therefore, if we read a message successfully, we always return | |
384 | * success and wait for the next event-loop iteration. Furthermore, | |
385 | * whenever there is a write-error, we must try reading from the input | |
386 | * queue even if EPOLLIN is not set. The input might have arrived in | |
387 | * between epoll_wait() and write(). Therefore, write-errors are only | |
388 | * ever handled if the input-queue is empty. In all other cases they | |
389 | * are ignored until either reading fails or the input queue is empty. | |
390 | */ | |
391 | ||
392 | if (revents & (EPOLLHUP | EPOLLERR)) | |
393 | r_hup = -EPIPE; | |
394 | ||
395 | if (revents & EPOLLOUT) | |
396 | r_write = pty_dispatch_write(pty); | |
397 | ||
398 | /* Awesome! Kernel signals HUP without IN but queues are not empty.. */ | |
399 | if ((revents & EPOLLIN) || r_hup < 0 || r_write < 0) { | |
400 | r_read = pty_dispatch_read(pty); | |
401 | if (r_read > 0) | |
402 | return 0; /* still data left to fetch next round */ | |
403 | } | |
404 | ||
405 | if (r_hup < 0 || r_write < 0 || r_read < 0) { | |
406 | /* PTY closed and input-queue drained */ | |
407 | pty_close(pty); | |
408 | r = pty->event_fn(pty, pty->event_fn_userdata, PTY_HUP, NULL, 0); | |
409 | if (r < 0) | |
410 | return r; | |
411 | } | |
412 | ||
413 | return 0; | |
414 | } | |
415 | ||
416 | static int pty_fd_prepare_fn(sd_event_source *source, void *userdata) { | |
417 | Pty *pty = userdata; | |
418 | int r; | |
419 | ||
420 | if (pty->needs_requeue) { | |
421 | /* | |
422 | * We're edge-triggered. In case we couldn't handle all events | |
423 | * or in case new write-data is queued, we set needs_requeue. | |
424 | * Before going asleep, we set the io-events *again*. sd-event | |
425 | * notices that we're edge-triggered and forwards the call to | |
426 | * the kernel even if the events didn't change. The kernel will | |
427 | * check the events and re-queue us on the ready queue in case | |
428 | * an event is pending. | |
429 | */ | |
430 | r = sd_event_source_set_io_events(source, EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET); | |
431 | if (r >= 0) | |
432 | pty->needs_requeue = false; | |
433 | } | |
434 | ||
435 | return 0; | |
436 | } | |
437 | ||
438 | static int pty_child_fn(sd_event_source *source, const siginfo_t *si, void *userdata) { | |
439 | Pty *pty = userdata; | |
440 | int r; | |
441 | ||
442 | pty->child = 0; | |
443 | ||
444 | r = pty->event_fn(pty, pty->event_fn_userdata, PTY_CHILD, si, sizeof(*si)); | |
445 | if (r < 0) | |
446 | return r; | |
447 | ||
448 | return 0; | |
449 | } | |
450 | ||
451 | int pty_attach_event(Pty *pty, sd_event *event, pty_event_t event_fn, void *event_fn_userdata) { | |
452 | int r; | |
453 | ||
454 | assert_return(pty, -EINVAL); | |
455 | assert_return(event, -EINVAL); | |
456 | assert_return(event_fn, -EINVAL); | |
457 | assert_return(pty_is_parent(pty), -EINVAL); | |
458 | ||
459 | pty_detach_event(pty); | |
460 | ||
461 | if (pty_is_open(pty)) { | |
462 | r = sd_event_add_io(event, | |
463 | &pty->fd_source, | |
464 | pty->fd, | |
465 | EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET, | |
466 | pty_fd_fn, | |
467 | pty); | |
468 | if (r < 0) | |
469 | goto error; | |
470 | ||
471 | r = sd_event_source_set_prepare(pty->fd_source, pty_fd_prepare_fn); | |
472 | if (r < 0) | |
473 | goto error; | |
474 | } | |
475 | ||
476 | if (pty_has_child(pty)) { | |
477 | r = sd_event_add_child(event, | |
478 | &pty->child_source, | |
479 | pty->child, | |
480 | WEXITED, | |
481 | pty_child_fn, | |
482 | pty); | |
483 | if (r < 0) | |
484 | goto error; | |
485 | } | |
486 | ||
487 | pty->event_fn = event_fn; | |
488 | pty->event_fn_userdata = event_fn_userdata; | |
489 | ||
490 | return 0; | |
491 | ||
492 | error: | |
493 | pty_detach_event(pty); | |
494 | return r; | |
495 | } | |
496 | ||
497 | void pty_detach_event(Pty *pty) { | |
498 | if (!pty) | |
499 | return; | |
500 | ||
501 | pty->child_source = sd_event_source_unref(pty->child_source); | |
502 | pty->fd_source = sd_event_source_unref(pty->fd_source); | |
503 | pty->event_fn = NULL; | |
504 | pty->event_fn_userdata = NULL; | |
505 | } | |
506 | ||
507 | int pty_write(Pty *pty, const void *buf, size_t size) { | |
508 | bool was_empty; | |
509 | int r; | |
510 | ||
511 | assert_return(pty, -EINVAL); | |
512 | assert_return(pty_is_open(pty), -ENODEV); | |
513 | assert_return(pty_is_parent(pty), -ENODEV); | |
514 | ||
515 | if (size < 1) | |
516 | return 0; | |
517 | ||
518 | /* | |
519 | * Push @buf[0..@size] into the output ring-buffer. In case the | |
520 | * ring-buffer wasn't empty beforehand, we're already waiting for | |
521 | * EPOLLOUT and we're done. If it was empty, we have to re-queue the | |
522 | * FD for EPOLLOUT as we're edge-triggered and wouldn't get any new | |
523 | * EPOLLOUT event. | |
524 | */ | |
525 | ||
526 | was_empty = ring_get_size(&pty->out_buf) < 1; | |
527 | ||
528 | r = ring_push(&pty->out_buf, buf, size); | |
529 | if (r < 0) | |
530 | return r; | |
531 | ||
532 | if (was_empty) | |
533 | pty->needs_requeue = true; | |
534 | ||
535 | return 0; | |
536 | } | |
537 | ||
538 | int pty_signal(Pty *pty, int sig) { | |
539 | assert_return(pty, -EINVAL); | |
540 | assert_return(pty_is_open(pty), -ENODEV); | |
541 | assert_return(pty_is_parent(pty), -ENODEV); | |
542 | ||
543 | return ioctl(pty->fd, TIOCSIG, sig) < 0 ? -errno : 0; | |
544 | } | |
545 | ||
546 | int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height) { | |
aa0fff7f LP |
547 | struct winsize ws = { |
548 | .ws_col = term_width, | |
549 | .ws_row = term_height, | |
550 | }; | |
a47d1dfd DH |
551 | |
552 | assert_return(pty, -EINVAL); | |
553 | assert_return(pty_is_open(pty), -ENODEV); | |
554 | assert_return(pty_is_parent(pty), -ENODEV); | |
555 | ||
a47d1dfd DH |
556 | /* |
557 | * This will send SIGWINCH to the pty slave foreground process group. | |
558 | * We will also get one, but we don't need it. | |
559 | */ | |
560 | return ioctl(pty->fd, TIOCSWINSZ, &ws) < 0 ? -errno : 0; | |
561 | } | |
562 | ||
563 | pid_t pty_fork(Pty **out, sd_event *event, pty_event_t event_fn, void *event_fn_userdata, unsigned short initial_term_width, unsigned short initial_term_height) { | |
564 | _pty_unref_ Pty *pty = NULL; | |
565 | int r; | |
566 | pid_t pid; | |
567 | ||
568 | assert_return(out, -EINVAL); | |
569 | assert_return((event && event_fn) || (!event && !event_fn), -EINVAL); | |
570 | ||
571 | r = pty_new(&pty); | |
572 | if (r < 0) | |
573 | return r; | |
574 | ||
575 | r = pty_unlock(pty); | |
576 | if (r < 0) | |
577 | return r; | |
578 | ||
579 | pid = fork(); | |
580 | if (pid < 0) | |
581 | return -errno; | |
582 | ||
583 | if (pid == 0) { | |
584 | /* child */ | |
585 | ||
586 | r = pty_make_child(pty); | |
587 | if (r < 0) | |
588 | _exit(-r); | |
589 | ||
590 | r = pty_setup_child(pty); | |
591 | if (r < 0) | |
592 | _exit(-r); | |
593 | ||
594 | /* sync with parent */ | |
595 | if (!barrier_place_and_sync(&pty->barrier)) | |
596 | _exit(1); | |
597 | ||
598 | /* fallthrough and return the child's PTY object */ | |
599 | } else { | |
600 | /* parent */ | |
601 | ||
602 | r = pty_make_parent(pty, pid); | |
603 | if (r < 0) | |
604 | goto parent_error; | |
605 | ||
606 | r = pty_resize(pty, initial_term_width, initial_term_height); | |
607 | if (r < 0) | |
608 | goto parent_error; | |
609 | ||
610 | if (event) { | |
611 | r = pty_attach_event(pty, event, event_fn, event_fn_userdata); | |
612 | if (r < 0) | |
613 | goto parent_error; | |
614 | } | |
615 | ||
616 | /* sync with child */ | |
617 | if (!barrier_place_and_sync(&pty->barrier)) { | |
618 | r = -ECHILD; | |
619 | goto parent_error; | |
620 | } | |
621 | ||
622 | /* fallthrough and return the parent's PTY object */ | |
623 | } | |
624 | ||
625 | *out = pty; | |
626 | pty = NULL; | |
627 | return pid; | |
628 | ||
629 | parent_error: | |
630 | barrier_abort(&pty->barrier); | |
631 | waitpid(pty->child, NULL, 0); | |
632 | pty->child = 0; | |
633 | return r; | |
634 | } |