pty.c revision a47d1dfd0823cd3978dd10e217dadcee7e01b265
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2014 David Herrmann <dh.herrmann@gmail.com>
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
/*
* PTY
* A PTY object represents a single PTY connection between a master and a
* child. The child process is fork()ed so the caller controls what program
* will be run.
*
* before running the login procedure. This also causes the pty master
* to get a EPOLLHUP event as long as no client has the TTY opened.
* This means, we cannot use the TTY connection as reliable way to track
* the client. Instead, we _must_ rely on the PID of the client to track
* them.
* However, this has the side effect that if the client forks and the
* parent exits, we loose them and restart the client. But this seems to
* be the expected behavior so we implement it here.
*
* Unfortunately, epoll always polls for EPOLLHUP so as long as the
* vhangup() is ongoing, we will _always_ get EPOLLHUP and cannot sleep.
* This gets worse if the client closes the TTY but doesn't exit.
* Therefore, the fd must be edge-triggered in the epoll-set so we
* only get the events once they change.
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pty.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "barrier.h"
#include "macro.h"
#include "pty.h"
#include "ring.h"
#include "util.h"
#define PTY_BUFSIZE 16384
enum {
};
struct Pty {
unsigned long ref;
int fd;
char in_buf[PTY_BUFSIZE];
void *event_fn_userdata;
bool needs_requeue : 1;
unsigned int role : 2;
};
int r;
if (!pty)
return -ENOMEM;
return -errno;
/*
* skipped. In that case, grantpt() can overwrite these, but then you
* have to be root to use chown() (or a pt_chown helper has to be
* present). In those cases grantpt() really does something,
* otherwise it's a no-op. We call grantpt() here to try supporting
* those cases, even though no-one uses that, I guess. If you need other
* access-rights, set them yourself after this call returns (no, this is
* not racy, it looks racy, but races regarding your own UID are never
* important as an attacker could ptrace you; and the slave-pty is also
* still locked).
*/
if (r < 0)
return -errno;
if (r < 0)
return r;
return 0;
}
return NULL;
return pty;
}
return NULL;
return NULL;
}
}
}
}
}
}
}
}
}
char slave_name[1024];
int r, fd;
if (r < 0)
return -errno;
if (fd < 0)
return -errno;
return 0;
}
return 0;
}
}
int r;
if (r < 0)
return r;
r = reset_all_signal_handlers();
if (r < 0)
return r;
return -errno;
if (r < 0)
return -errno;
if (r < 0)
return -errno;
/* erase character should be normal backspace, PLEASEEE! */
/* always set UTF8 flag */
if (r < 0)
return -errno;
return -errno;
/* only close FD if it's not a std-fd */
return 0;
}
if (!pty_is_open(pty))
return;
}
/*
* Drain input-queue and dispatch data via the event-handler. Returns <0 on
* error, 0 if queue is empty and 1 if we couldn't empty the input queue fast
* enough and there's still data left.
*/
unsigned int i;
int r;
/*
* We're edge-triggered, means we need to read the whole queue. This,
* however, might cause us to stall if the writer is faster than we
* are. Therefore, we read twice and if the second read still returned
* data, we reschedule.
*/
for (i = 0; i < 2; ++i) {
if (len < 0) {
continue;
} else if (len == 0) {
continue;
}
/* set terminating zero for debugging safety */
if (r < 0)
return r;
}
/* still data left, make sure we're queued again */
pty->needs_requeue = true;
return 1;
}
/*
* Drain output-queue by writing data to the pty. Returns <0 on error, 0 if the
* output queue is empty now and 1 if we couldn't empty the output queue fast
* enough and there's still data left.
*/
unsigned int i;
/*
* Same as pty_dispatch_read(), we're edge-triggered so we need to call
* write() until either all data is written or it returns EAGAIN. We
* call it twice and if it still writes successfully, we reschedule.
*/
for (i = 0; i < 2; ++i) {
if (num < 1)
return 0;
if (len < 0) {
continue;
} else if (len == 0) {
continue;
}
}
/* still data left, make sure we're queued again */
pty->needs_requeue = true;
return 1;
}
return 0;
}
/*
* Whenever we encounter I/O errors, we have to make sure to drain the
* input queue first, before we handle any HUP. A child might send us
* a message and immediately close the queue. We must not handle the
* HUP first or we loose data.
* Therefore, if we read a message successfully, we always return
* success and wait for the next event-loop iteration. Furthermore,
* whenever there is a write-error, we must try reading from the input
* queue even if EPOLLIN is not set. The input might have arrived in
* between epoll_wait() and write(). Therefore, write-errors are only
* ever handled if the input-queue is empty. In all other cases they
* are ignored until either reading fails or the input queue is empty.
*/
/* Awesome! Kernel signals HUP without IN but queues are not empty.. */
if (r_read > 0)
return 0; /* still data left to fetch next round */
}
/* PTY closed and input-queue drained */
if (r < 0)
return r;
}
return 0;
}
int r;
if (pty->needs_requeue) {
/*
* We're edge-triggered. In case we couldn't handle all events
* or in case new write-data is queued, we set needs_requeue.
* Before going asleep, we set the io-events *again*. sd-event
* notices that we're edge-triggered and forwards the call to
* the kernel even if the events didn't change. The kernel will
* check the events and re-queue us on the ready queue in case
* an event is pending.
*/
if (r >= 0)
pty->needs_requeue = false;
}
return 0;
}
int r;
if (r < 0)
return r;
return 0;
}
int r;
if (pty_is_open(pty)) {
r = sd_event_add_io(event,
pty);
if (r < 0)
goto error;
if (r < 0)
goto error;
}
if (pty_has_child(pty)) {
r = sd_event_add_child(event,
&pty->child_source,
pty);
if (r < 0)
goto error;
}
return 0;
return r;
}
if (!pty)
return;
}
bool was_empty;
int r;
if (size < 1)
return 0;
/*
* Push @buf[0..@size] into the output ring-buffer. In case the
* ring-buffer wasn't empty beforehand, we're already waiting for
* EPOLLOUT and we're done. If it was empty, we have to re-queue the
* FD for EPOLLOUT as we're edge-triggered and wouldn't get any new
* EPOLLOUT event.
*/
if (r < 0)
return r;
if (was_empty)
pty->needs_requeue = true;
return 0;
}
}
/*
* This will send SIGWINCH to the pty slave foreground process group.
* We will also get one, but we don't need it.
*/
}
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) {
int r;
if (r < 0)
return r;
r = pty_unlock(pty);
if (r < 0)
return r;
if (pid < 0)
return -errno;
if (pid == 0) {
/* child */
r = pty_make_child(pty);
if (r < 0)
_exit(-r);
r = pty_setup_child(pty);
if (r < 0)
_exit(-r);
/* sync with parent */
_exit(1);
/* fallthrough and return the child's PTY object */
} else {
/* parent */
if (r < 0)
goto parent_error;
if (r < 0)
goto parent_error;
if (event) {
if (r < 0)
goto parent_error;
}
/* sync with child */
r = -ECHILD;
goto parent_error;
}
/* fallthrough and return the parent's PTY object */
}
return pid;
return r;
}