Merge b8210461a295ff90f3fcb66d70cdbc75c57136b9 into 0fc69a4ca5b359504bd017acecda7843a387aa78

This commit is contained in:
GitHub Merge Button 2012-01-18 09:40:05 -08:00
commit 00385188b9
6 changed files with 655 additions and 24 deletions

View File

@ -31,7 +31,8 @@ libkmscon_core_la_SOURCES = \
src/input_xkb.c src/input_xkb.h \
external/imKStoUCS.c external\imKStoUCS.h \
src/vte.c src/vte.h \
src/terminal.c src/terminal.h
src/terminal.c src/terminal.h \
src/pty.c src/pty.h
if USE_PANGO
libkmscon_core_la_SOURCES += \

421
src/pty.c Normal file
View File

@ -0,0 +1,421 @@
/*
* kmscon - Pseudo Terminal Handling
*
* Copyright (c) 2012 Ran Benita <ran234@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* for pty functions */
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <pty.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include "log.h"
#include "pty.h"
struct kmscon_pty {
unsigned long ref;
struct kmscon_eloop *eloop;
int fd;
struct kmscon_fd *efd;
kmscon_pty_output_cb output_cb;
void *output_data;
kmscon_pty_closed_cb closed_cb;
void *closed_data;
};
int kmscon_pty_new(struct kmscon_pty **out,
kmscon_pty_output_cb output_cb, void *data)
{
struct kmscon_pty *pty;
if (!out)
return -EINVAL;
log_debug("pty: new pty object\n");
pty = malloc(sizeof(*pty));
if (!pty)
return -ENOMEM;
memset(pty, 0, sizeof(*pty));
pty->fd = -1;
pty->ref = 1;
pty->output_cb = output_cb;
pty->output_data = data;
*out = pty;
return 0;
}
void kmscon_pty_ref(struct kmscon_pty *pty)
{
if (!pty)
return;
pty->ref++;
}
void kmscon_pty_unref(struct kmscon_pty *pty)
{
if (!pty || !pty->ref)
return;
if (--pty->ref)
return;
kmscon_pty_close(pty);
free(pty);
log_debug("pty: destroying pty object\n");
}
/*
* TODO:
* - Decide which terminal we're emulating and set TERM accordingly.
* - Decide what to exec here: login, some getty equivalent, a shell...
* - Might also need to update some details in utmp wtmp and friends.
*/
static void __attribute__((noreturn))
exec_child(int pty_master)
{
const char *sh;
setenv("TERM", "linux", 1);
sh = getenv("SHELL") ?: _PATH_BSHELL;
execlp(sh, sh, "-i", NULL);
log_err("pty: failed to exec child: %m\n");
_exit(EXIT_FAILURE);
}
static int fork_pty_child(int master, struct winsize *ws)
{
int ret, saved_errno;
pid_t pid;
const char *slave_name;
int slave = -1;
/* This doesn't actually do anything on linux. */
ret = grantpt(master);
if (ret < 0) {
log_err("pty: grantpt failed: %m");
goto err_out;
}
ret = unlockpt(master);
if (ret < 0) {
log_err("pty: cannot unlock pty: %m");
goto err_out;
}
slave_name = ptsname(master);
if (!slave_name) {
log_err("pty: cannot find slave name: %m");
goto err_out;
}
/* This also loses our controlling tty. */
pid = setsid();
if (pid < 0) {
log_err("pty: cannot start a new session: %m");
goto err_out;
}
/* And the slave pty becomes our controlling tty. */
slave = open(slave_name, O_RDWR | O_CLOEXEC);
if (slave < 0) {
log_err("pty: cannot open slave: %m");
goto err_out;
}
if (ws) {
ret = ioctl(slave, TIOCSWINSZ, ws);
if (ret)
log_warning("pty: cannot set slave window size: %m");
}
if (dup2(slave, STDIN_FILENO) != STDIN_FILENO ||
dup2(slave, STDOUT_FILENO) != STDOUT_FILENO ||
dup2(slave, STDERR_FILENO) != STDERR_FILENO) {
log_err("pty: cannot duplicate slave: %m");
goto err_out;
}
close(master);
close(slave);
return 0;
err_out:
saved_errno = errno;
if (slave > 0)
close(slave);
close(master);
return -saved_errno;
}
/*
* This is functionally equivalent to forkpty(3). We do it manually to obtain
* a little bit more control of the process, and as a bonus avoid linking to
* the libutil library in glibc.
*/
static pid_t fork_pty(int *pty_out, struct winsize *ws)
{
int ret;
pid_t pid;
int master;
master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
if (master < 0) {
ret = -errno;
log_err("pty: cannot open master: %m");
goto err_out;
}
pid = fork();
switch (pid) {
case -1:
log_err("pty: cannot fork: %m");
ret = -errno;
goto err_master;
case 0:
ret = fork_pty_child(master, ws);
if (ret)
goto err_master;
*pty_out = -1;
return 0;
default:
*pty_out = master;
return pid;
}
err_master:
close(master);
err_out:
*pty_out = -1;
errno = -ret;
return -1;
}
static int pty_spawn(struct kmscon_pty *pty,
unsigned short width, unsigned short height)
{
struct winsize ws;
pid_t pid;
if (pty->fd >= 0)
return -EALREADY;
memset(&ws, 0, sizeof(ws));
ws.ws_col = width;
ws.ws_row = height;
pid = fork_pty(&pty->fd, &ws);
switch (pid) {
case -1:
log_err("pty: cannot fork or open pty pair: %m");
return -errno;
case 0:
exec_child(pty->fd);
default:
break;
}
return 0;
}
static void pty_output(struct kmscon_fd *fd, int mask, void *data)
{
int ret, nread;
ssize_t len;
struct kmscon_pty *pty = data;
if (!pty || pty->fd < 0)
return;
/*
* If we get a hangup or an error, but the pty is still readable, we
* read what's left and deal with the rest on the next dispatch.
*/
if (!(mask & KMSCON_READABLE)) {
if (mask & KMSCON_ERR)
log_warning("pty: error condition happened on pty\n");
kmscon_pty_close(pty);
return;
}
ret = ioctl(pty->fd, FIONREAD, &nread);
if (ret) {
log_warning("pty: cannot peek into pty input buffer: %m");
return;
} else if (nread <= 0) {
return;
}
char u8[nread];
len = read(pty->fd, u8, nread);
if (len == -1) {
if (errno == EWOULDBLOCK)
return;
/* EIO is hangup, although we should have caught it above. */
if (errno != EIO)
log_err("pty: cannot read from pty: %m");
kmscon_pty_close(pty);
return;
} else if (len == 0) {
kmscon_pty_close(pty);
return;
}
if (pty->output_cb)
pty->output_cb(pty, u8, len, pty->output_data);
}
static int connect_eloop(struct kmscon_pty *pty, struct kmscon_eloop *eloop)
{
int ret;
if (pty->eloop)
return -EALREADY;
ret = kmscon_eloop_new_fd(eloop, &pty->efd, pty->fd,
KMSCON_READABLE, pty_output, pty);
if (ret)
return ret;
kmscon_eloop_ref(eloop);
pty->eloop = eloop;
return 0;
}
static void disconnect_eloop(struct kmscon_pty *pty)
{
kmscon_eloop_rm_fd(pty->efd);
kmscon_eloop_unref(pty->eloop);
pty->efd = NULL;
pty->eloop = NULL;
}
int kmscon_pty_open(struct kmscon_pty *pty, struct kmscon_eloop *eloop,
unsigned short width, unsigned short height,
kmscon_pty_closed_cb closed_cb, void *data)
{
int ret;
if (!pty || !eloop)
return -EINVAL;
if (pty->fd >= 0)
return -EALREADY;
ret = pty_spawn(pty, width, height);
if (ret)
return ret;
ret = connect_eloop(pty, eloop);
if (ret == -EALREADY) {
disconnect_eloop(pty);
ret = connect_eloop(pty, eloop);
}
if (ret) {
close(pty->fd);
pty->fd = -1;
return ret;
}
pty->closed_cb = closed_cb;
pty->closed_data = data;
return 0;
}
void kmscon_pty_close(struct kmscon_pty *pty)
{
kmscon_pty_closed_cb cb;
void *data;
if (!pty || pty->fd < 0)
return;
disconnect_eloop(pty);
close(pty->fd);
pty->fd = -1;
cb = pty->closed_cb;
data = pty->closed_data;
pty->closed_cb = NULL;
pty->closed_data = NULL;
if (cb)
cb(pty, data);
}
void kmscon_pty_input(struct kmscon_pty *pty, const char *u8, size_t len)
{
if (!pty || pty->fd < 0)
return;
/* FIXME: In EWOULDBLOCK we would lose input! Need to buffer. */
len = write(pty->fd, u8, len);
if (len <= 0) {
if (errno != EWOULDBLOCK)
kmscon_pty_close(pty);
return;
}
}
void kmscon_pty_resize(struct kmscon_pty *pty,
unsigned short width, unsigned short height)
{
int ret;
struct winsize ws;
if (!pty || pty->fd < 0)
return;
memset(&ws, 0, sizeof(ws));
ws.ws_col = width;
ws.ws_row = height;
/*
* This will send SIGWINCH to the pty slave foreground process group.
* We will also get one, but we don't need it.
*/
ret = ioctl(pty->fd, TIOCSWINSZ, &ws);
if (ret) {
log_warning("pty: cannot set window size\n");
return;
}
log_debug("pty: window size set to %hdx%hd\n", ws.ws_col, ws.ws_row);
}

72
src/pty.h Normal file
View File

@ -0,0 +1,72 @@
/*
* kmscon - Pseudo Terminal Handling
*
* Copyright (c) 2012 Ran Benita <ran234@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The pty object provides an interface for communicating with a child process
* over a pseudo terminal. The child is the host, we act as the TTY terminal,
* and the kernel is the driver.
*
* To use this, create a new pty object and open it. You will start receiving
* output notifications through the output_cb callback. To communicate with
* the other end of the terminal, use the kmscon_pty_input method. All
* communication is done using byte streams (presumably UTF-8).
*
* The pty can be closed voluntarily using the kmson_pty_close method. The
* child process can also exit at will; this will be communicated through the
* closed_cb callback. The pty object does not wait on the child processes it
* spawns; this is the responsibility of the object's user.
*/
#ifndef KMSCON_PTY_H
#define KMSCON_PTY_H
#include "eloop.h"
struct kmscon_pty;
typedef void (*kmscon_pty_output_cb)
(struct kmscon_pty *pty, char *u8, size_t len, void *data);
typedef void (*kmscon_pty_closed_cb) (struct kmscon_pty *pty, void *data);
int kmscon_pty_new(struct kmscon_pty **out,
kmscon_pty_output_cb output_cb, void *data);
void kmscon_pty_ref(struct kmscon_pty *pty);
void kmscon_pty_unref(struct kmscon_pty *pty);
int kmscon_pty_open(struct kmscon_pty *pty, struct kmscon_eloop *eloop,
unsigned short width, unsigned short height,
kmscon_pty_closed_cb closed_cb, void *data);
void kmscon_pty_close(struct kmscon_pty *pty);
void kmscon_pty_input(struct kmscon_pty *pty, const char *u8, size_t len);
/*
* Call this whenever the size of the screen (rows or columns) changes. The
* kernel and child process need to be notified.
*/
void kmscon_pty_resize(struct kmscon_pty *pty,
unsigned short width, unsigned short height);
#endif /* KMSCON_PTY_H */

View File

@ -39,7 +39,9 @@
#include "console.h"
#include "eloop.h"
#include "font.h"
#include "input.h"
#include "log.h"
#include "pty.h"
#include "terminal.h"
#include "unicode.h"
#include "vte.h"
@ -59,6 +61,10 @@ struct kmscon_terminal {
struct kmscon_console *console;
struct kmscon_idle *redraw;
struct kmscon_vte *vte;
struct kmscon_pty *pty;
kmscon_terminal_closed_cb closed_cb;
void *closed_data;
};
static void draw_all(struct kmscon_idle *idle, void *data)
@ -101,20 +107,17 @@ static void schedule_redraw(struct kmscon_terminal *term)
log_warning("terminal: cannot schedule redraw\n");
}
static const char help_text[] =
"terminal subsystem - KMS based console test\n"
"This is some default text to test the drawing operations.\n\n";
static void print_help(struct kmscon_terminal *term)
static void pty_output(struct kmscon_pty *pty, char *u8, size_t len, void *data)
{
unsigned int i, len;
kmscon_symbol_t ch;
size_t i;
struct kmscon_terminal *term = data;
len = sizeof(help_text) - 1;
for (i = 0; i < len; ++i) {
ch = kmscon_symbol_make(help_text[i]);
kmscon_terminal_input(term, ch);
}
/* FIXME: UTF-8. */
for (i=0; i < len; i++)
if (u8[i] < 128)
kmscon_vte_input(term->vte, u8[i]);
schedule_redraw(term);
}
int kmscon_terminal_new(struct kmscon_terminal **out,
@ -147,11 +150,16 @@ int kmscon_terminal_new(struct kmscon_terminal **out,
if (ret)
goto err_con;
kmscon_vte_bind(term->vte, term->console);
print_help(term);
ret = kmscon_pty_new(&term->pty, pty_output, term);
if (ret)
goto err_vte;
*out = term;
return 0;
err_vte:
kmscon_vte_unref(term->vte);
err_con:
kmscon_console_unref(term->console);
err_idle:
@ -177,16 +185,17 @@ void kmscon_terminal_unref(struct kmscon_terminal *term)
if (--term->ref)
return;
term->closed_cb = NULL;
kmscon_terminal_close(term);
kmscon_terminal_rm_all_outputs(term);
kmscon_pty_unref(term->pty);
kmscon_vte_unref(term->vte);
kmscon_console_unref(term->console);
kmscon_terminal_disconnect_eloop(term);
free(term);
log_debug("terminal: destroying terminal object\n");
}
int kmscon_terminal_connect_eloop(struct kmscon_terminal *term,
struct kmscon_eloop *eloop)
int connect_eloop(struct kmscon_terminal *term, struct kmscon_eloop *eloop)
{
if (!term || !eloop)
return -EINVAL;
@ -200,7 +209,7 @@ int kmscon_terminal_connect_eloop(struct kmscon_terminal *term,
return 0;
}
void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term)
void disconnect_eloop(struct kmscon_terminal *term)
{
if (!term)
return;
@ -209,6 +218,63 @@ void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term)
term->eloop = NULL;
}
static void pty_closed(struct kmscon_pty *pty, void *data)
{
struct kmscon_terminal *term = data;
kmscon_terminal_close(term);
}
int kmscon_terminal_open(struct kmscon_terminal *term,
struct kmscon_eloop *eloop,
kmscon_terminal_closed_cb closed_cb, void *data)
{
int ret;
unsigned short width, height;
if (!term)
return -EINVAL;
ret = connect_eloop(term, eloop);
if (ret == -EALREADY) {
disconnect_eloop(term);
ret = connect_eloop(term, eloop);
}
if (ret)
return ret;
width = kmscon_console_get_width(term->console);
height = kmscon_console_get_height(term->console);
ret = kmscon_pty_open(term->pty, eloop, width, height, pty_closed, term);
if (ret) {
disconnect_eloop(term);
return ret;
}
term->closed_cb = closed_cb;
term->closed_data = data;
return 0;
}
void kmscon_terminal_close(struct kmscon_terminal *term)
{
kmscon_terminal_closed_cb cb;
void *data;
if (!term)
return;
cb = term->closed_cb;
data = term->closed_data;
term->closed_data = NULL;
term->closed_cb = NULL;
disconnect_eloop(term);
kmscon_pty_close(term->pty);
if (cb)
cb(term, data);
}
int kmscon_terminal_add_output(struct kmscon_terminal *term,
struct kmscon_output *output)
{
@ -263,6 +329,7 @@ void kmscon_terminal_rm_all_outputs(struct kmscon_terminal *term)
void kmscon_terminal_input(struct kmscon_terminal *term, kmscon_symbol_t ch)
{
kmscon_vte_input(term->vte, ch);
schedule_redraw(term);
/* FIXME: UTF-8. */
if (ch < 128)
kmscon_pty_input(term->pty, (char *)&ch, 1);
}

View File

@ -41,14 +41,18 @@
struct kmscon_terminal;
typedef void (*kmscon_terminal_closed_cb) (struct kmscon_terminal *term,
void *data);
int kmscon_terminal_new(struct kmscon_terminal **out,
struct kmscon_font_factory *ff);
void kmscon_terminal_ref(struct kmscon_terminal *term);
void kmscon_terminal_unref(struct kmscon_terminal *term);
int kmscon_terminal_connect_eloop(struct kmscon_terminal *term,
struct kmscon_eloop *eloop);
void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term);
int kmscon_terminal_open(struct kmscon_terminal *term,
struct kmscon_eloop *eloop,
kmscon_terminal_closed_cb closed_cb, void *data);
void kmscon_terminal_close(struct kmscon_terminal *term);
int kmscon_terminal_add_output(struct kmscon_terminal *term,
struct kmscon_output *output);

View File

@ -36,6 +36,7 @@
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include "eloop.h"
#include "input.h"
@ -48,6 +49,7 @@ struct app {
struct kmscon_eloop *eloop;
struct kmscon_signal *sig_term;
struct kmscon_signal *sig_int;
struct kmscon_signal *sig_chld;
struct kmscon_symbol_table *st;
struct kmscon_font_factory *ff;
struct kmscon_compositor *comp;
@ -63,6 +65,63 @@ static void sig_term(struct kmscon_signal *sig, int signum, void *data)
terminate = 1;
}
static void sig_chld(struct kmscon_signal *sig, int signum, void *data)
{
pid_t pid;
int status;
/*
* If multiple children exit at the same time, signalfd would put them
* all in one event. So we reap in a loop.
*/
while (1) {
pid = waitpid(-1, &status, WNOHANG);
if (pid == -1) {
if (errno != ECHILD)
log_warning("test: cannot wait on child: %m\n");
break;
} else if (pid == 0) {
break;
} else if (WIFEXITED(status)) {
if (WEXITSTATUS(status) != 0)
log_info("test: child %d exited with status "
"%d\n", pid, WEXITSTATUS(status));
else
log_debug("test: child %d exited "
"successfully\n", pid);
} else if (WIFSIGNALED(status)) {
log_debug("test: child %d exited by signal %d\n", pid,
WTERMSIG(status));
}
}
}
static void terminal_closed(struct kmscon_terminal *term, void *data)
{
#if 0
/*
* Alternativly, we could spwan a new login/shell here, like what
* happens when the user exits the shell in a linux console:
*/
int ret;
struct app *app = data;
if (!app)
goto err_out;
ret = kmscon_terminal_open(app->term, app->eloop,
terminal_closed, app);
if (ret)
goto err_out;
return;
err_out:
#endif
terminate = 1;
}
static void read_input(struct kmscon_input *input,
struct kmscon_input_event *ev, void *data)
{
@ -131,6 +190,7 @@ static void destroy_app(struct app *app)
kmscon_compositor_unref(app->comp);
kmscon_font_factory_unref(app->ff);
kmscon_symbol_table_unref(app->st);
kmscon_eloop_rm_signal(app->sig_chld);
kmscon_eloop_rm_signal(app->sig_int);
kmscon_eloop_rm_signal(app->sig_term);
kmscon_eloop_unref(app->eloop);
@ -154,6 +214,11 @@ static int setup_app(struct app *app)
if (ret)
goto err_loop;
ret = kmscon_eloop_new_signal(app->eloop, &app->sig_chld, SIGCHLD,
sig_chld, NULL);
if (ret)
goto err_loop;
ret = kmscon_symbol_table_new(&app->st);
if (ret)
goto err_loop;
@ -186,7 +251,8 @@ static int setup_app(struct app *app)
if (ret)
goto err_loop;
ret = kmscon_terminal_connect_eloop(app->term, app->eloop);
ret = kmscon_terminal_open(app->term, app->eloop,
terminal_closed, app);
if (ret)
goto err_loop;