blob: 8be9384c5f10aefeafc171c7b91aa7ee3af9044c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
* You may elect to redistribute this code under either of these licenses.
*
* Contributors:
* Wind River Systems - initial API and implementation
* Intel - implemented terminals service
*******************************************************************************/
/*
* TCF Terminals service implementation.
*/
#include <config.h>
#if SERVICE_Terminals
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <assert.h>
#include <framework/myalloc.h>
#include <framework/protocol.h>
#include <framework/trace.h>
#include <framework/context.h>
#include <framework/json.h>
#include <framework/asyncreq.h>
#include <framework/exceptions.h>
#include <framework/waitpid.h>
#include <framework/signames.h>
#include <services/streamsservice.h>
#include <services/terminals.h>
#ifndef TERMINALS_NO_LOGIN
#define TERMINALS_NO_LOGIN 1
#endif
static const char * TERMINALS = "Terminals";
#if defined(WIN32)
# include <tlhelp32.h>
# ifdef _MSC_VER
# pragma warning(disable:4201) /* nonstandard extension used : nameless struct/union (in winternl.h) */
# include <winternl.h>
# else
# include <ntdef.h>
# endif
# ifndef STATUS_INFO_LENGTH_MISMATCH
# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
# endif
# ifndef SystemHandleInformation
# define SystemHandleInformation 16
# endif
# define TERM_LAUNCH_EXEC "cmd"
# define TERM_LAUNCH_ARGS {TERM_LAUNCH_EXEC, NULL}
struct winsize {
unsigned ws_col;
unsigned ws_row;
};
#else
# include <termios.h>
# ifndef TIOCGWINSZ
# include <sys/ioctl.h>
# endif
# include <sys/stat.h>
# include <unistd.h>
# include <dirent.h>
# if TERMINALS_NO_LOGIN
# define TERM_LAUNCH_EXEC "/bin/bash"
# define TERM_LAUNCH_ARGS {TERM_LAUNCH_EXEC, "-l", NULL}
# define TERM_EXIT_SIGNAL SIGHUP
# else
# define TERM_LAUNCH_EXEC "/bin/login"
# define TERM_LAUNCH_ARGS {TERM_LAUNCH_EXEC, "-p", NULL}
# define TERM_EXIT_SIGNAL SIGTERM
# endif
#endif
#define PIPE_SIZE 0x1000
#define TERM_PROP_DEF_SIZE 256
typedef struct Terminal {
LINK link;
int pid; /* pid of the login process of the terminal */
TCFBroadcastGroup * bcg;
struct TerminalInput * inp_struct;
struct TerminalOutput * out_struct;
struct TerminalOutput * err_struct;
char pty_type[TERM_PROP_DEF_SIZE];
char encoding[TERM_PROP_DEF_SIZE];
unsigned long width;
unsigned long height;
int prompt_ok;
int terminated;
long exit_code;
Channel * channel;
} Terminal;
typedef struct TerminalOutput {
int fd;
char id[256];
Terminal * prs;
AsyncReqInfo req;
int req_posted;
char buf[PIPE_SIZE];
size_t buf_pos;
int eos;
VirtualStream * vstream;
} TerminalOutput;
typedef struct TerminalInput {
int fd;
char id[256];
Terminal * prs;
AsyncReqInfo req;
int req_posted;
char buf[PIPE_SIZE];
size_t buf_pos;
size_t buf_len;
int eos;
VirtualStream * vstream;
} TerminalInput;
#define link2term(A) ((Terminal *)((char *)(A) - offsetof(Terminal, link)))
static LINK terms_list;
static Terminal * find_terminal(int pid) {
LINK * qhp = &terms_list;
LINK * qp = qhp->next;
while (qp != qhp) {
Terminal * prs = link2term(qp);
if (prs->pid == pid) return prs;
qp = qp->next;
}
return NULL;
}
static char * tid2id(int tid) {
static char s[64];
char * p = s + sizeof(s);
unsigned long n = (long) tid;
*(--p) = 0;
do {
*(--p) = (char) (n % 10 + '0');
n = n / 10;
}
while (n != 0);
*(--p) = 'T';
return p;
}
static int id2tid(const char * id) {
int tid = 0;
if (id == NULL) return 0;
if (id[0] != 'T') return 0;
if (id[1] == 0) return 0;
tid = (unsigned) strtol(id + 1, (char **) &id, 10);
if (id[0] != 0) return 0;
return tid;
}
static void write_context(OutputStream * out, int tid) {
Terminal * prs = find_terminal(tid);
write_stream(out, '{');
if (prs != NULL) {
json_write_string(out, "ProcessID");
write_stream(out, ':');
json_write_string(out, pid2id(prs->pid, 0));
write_stream(out, ',');
if (*prs->pty_type) {
json_write_string(out, "PtyType");
write_stream(out, ':');
json_write_string(out, prs->pty_type);
write_stream(out, ',');
}
if (*prs->encoding) {
json_write_string(out, "Encoding");
write_stream(out, ':');
json_write_string(out, prs->encoding);
write_stream(out, ',');
}
json_write_string(out, "Width");
write_stream(out, ':');
json_write_ulong(out, prs->width);
write_stream(out, ',');
json_write_string(out, "Height");
write_stream(out, ':');
json_write_ulong(out, prs->height);
write_stream(out, ',');
if (prs->inp_struct) {
json_write_string(out, "StdInID");
write_stream(out, ':');
json_write_string(out, prs->inp_struct->id);
write_stream(out, ',');
}
if (prs->out_struct) {
json_write_string(out, "StdOutID");
write_stream(out, ':');
json_write_string(out, prs->out_struct->id);
write_stream(out, ',');
}
if (prs->err_struct) {
json_write_string(out, "StdErrID");
write_stream(out, ':');
json_write_string(out, prs->err_struct->id);
write_stream(out, ',');
}
}
json_write_string(out, "ID");
write_stream(out, ':');
json_write_string(out, tid2id(tid));
write_stream(out, '}');
}
static void send_event_terminal_exited(OutputStream * out, Terminal * prs) {
write_stringz(out, "E");
write_stringz(out, TERMINALS);
write_stringz(out, "exited");
json_write_string(out, tid2id(prs->pid));
write_stream(out, 0);
json_write_ulong(out, prs->exit_code);
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
static void send_event_terminal_win_size_changed(OutputStream * out, Terminal * prs) {
write_stringz(out, "E");
write_stringz(out, TERMINALS);
write_stringz(out, "winSizeChanged");
json_write_string(out, tid2id(prs->pid));
write_stream(out, 0);
json_write_long(out, prs->width);
write_stream(out, 0);
json_write_long(out, prs->height);
write_stream(out, 0);
write_stream(out, MARKER_EOM);
}
static int kill_term(Terminal * term) {
int err = 0;
#if defined(WIN32)
HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, term->pid);
if (h == NULL) {
err = set_win32_errno(GetLastError());
}
else {
if (!TerminateProcess(h, 1)) err = set_win32_errno(GetLastError());
if (!CloseHandle(h) && !err) err = set_win32_errno(GetLastError());
}
#else
if (kill(term->pid, term->prompt_ok ? TERM_EXIT_SIGNAL : SIGKILL) < 0) err = errno;
#endif
term->terminated = 1;
return err;
}
static void command_exit(char * token, Channel * c) {
int err = 0;
char id[256];
unsigned tid;
Terminal * term = NULL;
json_read_string(&c->inp, id, sizeof(id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
tid = id2tid(id);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
if (tid == 0 || (term = find_terminal(tid)) == NULL) {
err = ERR_INV_CONTEXT;
}
else {
err = kill_term(term);
}
write_errno(&c->out, err);
write_stream(&c->out, MARKER_EOM);
}
static void terminal_exited(Terminal * prs) {
Trap trap;
if (set_trap(&trap)) {
send_event_terminal_exited(&prs->bcg->out, prs);
clear_trap(&trap);
}
else {
trace(LOG_ALWAYS, "Exception sending terminal exited event: %d %s",
trap.error, errno_to_str(trap.error));
}
list_remove(&prs->link);
if (prs->inp_struct) {
TerminalInput * inp = prs->inp_struct;
if (!inp->req_posted) {
virtual_stream_delete(inp->vstream);
close(inp->fd);
loc_free(inp);
}
else {
inp->prs = NULL;
}
}
if (prs->out_struct) prs->out_struct->prs = NULL;
if (prs->err_struct) prs->err_struct->prs = NULL;
channel_unlock(prs->channel);
loc_free(prs);
}
static void terminal_input_streams_callback(VirtualStream * stream, int event_code, void * args) {
TerminalInput * inp = (TerminalInput *) args;
assert(inp->vstream == stream);
if (!inp->req_posted) {
if (inp->buf_pos >= inp->buf_len && !inp->eos) {
inp->buf_pos = inp->buf_len = 0;
virtual_stream_get_data(stream, inp->buf, sizeof(inp->buf), &inp->buf_len, &inp->eos);
}
if (inp->buf_pos < inp->buf_len) {
inp->req.u.fio.bufp = inp->buf + inp->buf_pos;
inp->req.u.fio.bufsz = inp->buf_len - inp->buf_pos;
inp->req_posted = 1;
async_req_post(&inp->req);
}
}
}
static void write_terminal_input_done(void * x) {
AsyncReqInfo * req = (AsyncReqInfo *) x;
TerminalInput * inp = (TerminalInput *) req->client_data;
inp->req_posted = 0;
if (inp->prs == NULL) {
/* Process has exited */
virtual_stream_delete(inp->vstream);
close(inp->fd);
loc_free(inp);
}
else {
int wr = inp->req.u.fio.rval;
if (wr < 0) {
int err = inp->req.error;
trace(LOG_ALWAYS, "Can't write terminal input stream: %d %s", err, errno_to_str(err));
inp->buf_pos = inp->buf_len = 0;
}
else {
inp->buf_pos += wr;
}
terminal_input_streams_callback(inp->vstream, 0, inp);
}
}
static TerminalInput * write_terminal_input(Terminal * prs, int fd) {
TerminalInput * inp = (TerminalInput *) loc_alloc_zero(sizeof(TerminalInput));
inp->fd = fd;
inp->prs = prs;
inp->req.client_data = inp;
inp->req.done = write_terminal_input_done;
inp->req.type = AsyncReqWrite;
inp->req.u.fio.fd = inp->fd;
virtual_stream_create(TERMINALS, tid2id(prs->pid), PIPE_SIZE, VS_ENABLE_REMOTE_WRITE,
terminal_input_streams_callback, inp, &inp->vstream);
virtual_stream_get_id(inp->vstream, inp->id, sizeof(inp->id));
return inp;
}
static void terminal_output_streams_callback(VirtualStream * stream, int event_code, void * args) {
TerminalOutput * out = (TerminalOutput *) args;
assert(out->vstream == stream);
if (!out->req_posted) {
int buf_len = out->req.u.fio.rval;
int err = 0;
int eos = 0;
if (buf_len < 0) {
buf_len = 0;
err = out->req.error;
}
if (buf_len == 0) eos = 1;
if (out->prs == NULL) {
eos = 1;
err = 0;
}
assert(buf_len <= (int) sizeof(out->buf));
assert(out->buf_pos <= (size_t)buf_len);
assert(out->req.u.fio.bufp == out->buf);
#ifdef __linux__
if (err == EIO) err = 0;
#endif
if (err) trace(LOG_ALWAYS, "Can't read terminal output stream: %d %s", err, errno_to_str(err));
if (out->buf_pos < (size_t)buf_len || out->eos != eos) {
size_t done = 0;
virtual_stream_add_data(stream, out->buf + out->buf_pos, buf_len - out->buf_pos, &done, eos);
out->buf_pos += done;
if (eos) out->eos = 1;
}
if (out->buf_pos >= (size_t)buf_len) {
if (!eos) {
out->req_posted = 1;
async_req_post(&out->req);
}
else if (virtual_stream_is_empty(stream)) {
if (out->prs != NULL) {
if (out == out->prs->out_struct) out->prs->out_struct = NULL;
if (out == out->prs->err_struct) out->prs->err_struct = NULL;
}
virtual_stream_delete(stream);
close(out->fd);
loc_free(out);
}
}
}
}
static void read_terminal_output_done(void * x) {
AsyncReqInfo * req = (AsyncReqInfo *) x;
TerminalOutput * out = (TerminalOutput *)req->client_data;
out->buf_pos = 0;
out->req_posted = 0;
if (out->prs && !out->prs->prompt_ok && out->req.u.fio.rval > 0) out->prs->prompt_ok = 1;
terminal_output_streams_callback(out->vstream, 0, out);
}
static TerminalOutput * read_terminal_output(Terminal * prs, int fd) {
TerminalOutput * out = (TerminalOutput *)loc_alloc_zero(sizeof(TerminalOutput));
out->fd = fd;
out->prs = prs;
out->req.client_data = out;
out->req.done = read_terminal_output_done;
out->req.type = AsyncReqRead;
out->req.u.fio.bufp = out->buf;
out->req.u.fio.bufsz = sizeof(out->buf);
out->req.u.fio.fd = fd;
virtual_stream_create(TERMINALS, tid2id(prs->pid), PIPE_SIZE, VS_ENABLE_REMOTE_READ,
terminal_output_streams_callback, out, &out->vstream);
virtual_stream_get_id(out->vstream, out->id, sizeof(out->id));
out->req_posted = 1;
async_req_post(&out->req);
return out;
}
#if !defined(WIN32)
/*
* Set the environment variable "name" to the value "value". If the variable
* exists already, override it or just skip.
*/
static void envp_add(char *** envp, int * env_len, const char * name, const char * value, int env_override) {
char **env;
size_t len;
int i;
assert(*envp || *env_len == 0);
assert(name);
assert(value);
if (*envp == NULL && *env_len == 0) {
*envp = (char **)loc_alloc(sizeof(char *));
*envp[0] = NULL;
*env_len = 1;
}
for (env = *envp, i = 0, len = strlen(name); env[i]; i++)
if (strncmp(env[i], name, len) == 0 && env[i][len] == '=') break;
if (env[i]) {
/* override */
if (env_override) loc_free(env[i]);
else return;
}
else {
/* new variable */
if (i >= *env_len - 1) {
*env_len += 10;
env = *envp = (char **)loc_realloc(env, *env_len * sizeof(char *));
}
env[i + 1] = NULL;
}
env[i] = (char *)loc_alloc_zero(len + 1 + strlen(value) + 1);
snprintf(env[i], len + 1 + strlen(value) + 1, "%s=%s", name, value);
}
static void set_terminal_env(char *** envp, int * env_len, const char * pty_type,
const char * encoding, const char * exe) {
#if TERMINALS_NO_LOGIN
char * value;
const char *env_array[] = { "USER", "LOGNAME", "HOME", "PATH", NULL };
#endif
int i = 0;
char ** new_envp = NULL;
/* convert the envp memory layout */
new_envp = (char **)loc_alloc((*env_len + 1) * sizeof(char *));
for (i = 0; i < *env_len; i++) {
new_envp[i] = (char *)loc_alloc(strlen((*envp)[i]) + 1);
memcpy(new_envp[i], (*envp)[i], strlen((*envp)[i]) + 1);
}
new_envp[i] = NULL;
loc_free(*envp);
*envp = new_envp;
*env_len = i + 1;
if (*pty_type) envp_add(envp, env_len, "TERM", pty_type, 1);
if (*encoding) envp_add(envp, env_len, "LANG", encoding, 1);
envp_add(envp, env_len, "SHELL", exe, 1);
#if TERMINALS_NO_LOGIN
i = 0;
while (env_array[i]) {
value = getenv(env_array[i]);
if (value) envp_add(envp, env_len, env_array[i], value, 0);
++i;
}
#endif
}
#endif
#if defined(WIN32)
static int start_terminal(Channel * c, const char * pty_type, const char * encoding, char ** envp,
int envp_len, const char * exe, const char ** args, int * pid, Terminal ** prs) {
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG Count;
struct HANDLE_INFORMATION {
USHORT ProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeNumber;
UCHAR Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} Handles[1];
} SYSTEM_HANDLE_INFORMATION;
typedef NTSTATUS (FAR WINAPI * QuerySystemInformationTypedef)(int, PVOID, ULONG, PULONG);
QuerySystemInformationTypedef QuerySystemInformationProc = (QuerySystemInformationTypedef)GetProcAddress(
GetModuleHandle("NTDLL.DLL"), "NtQuerySystemInformation");
DWORD size;
NTSTATUS status;
SYSTEM_HANDLE_INFORMATION * hi = NULL;
int fpipes[3][2];
HANDLE hpipes[3][2];
char * cmd = NULL;
int err = 0;
int i;
if (args != NULL) {
int i = 0;
int cmd_size = 0;
int cmd_pos = 0;
# define cmd_append(ch) { \
if (!cmd) { \
cmd_size = 0x1000; \
cmd = (char *)loc_alloc(cmd_size); \
} \
else if (cmd_pos >= cmd_size) { \
char * tmp = (char *)loc_alloc(cmd_size * 2); \
memcpy(tmp, cmd, cmd_pos); \
loc_free(cmd); \
cmd = tmp; \
cmd_size *= 2; \
}; \
cmd[cmd_pos++] = (ch); \
}
while (args[i] != NULL) {
const char * p = args[i++];
if (cmd_pos > 0) cmd_append(' ');
cmd_append('"');
while (*p) {
if (*p == '"') cmd_append('\\');
cmd_append(*p);
p++;
}
cmd_append('"');
}
cmd_append(0);
# undef cmd_append
}
size = sizeof(SYSTEM_HANDLE_INFORMATION) * 16;
hi = (SYSTEM_HANDLE_INFORMATION *)loc_alloc(size);
for (;;) {
status = QuerySystemInformationProc(SystemHandleInformation, hi, size, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH) break;
hi = (SYSTEM_HANDLE_INFORMATION *)loc_realloc(hi, size);
}
if (status == 0) {
ULONG i;
DWORD id = GetCurrentProcessId();
for (i = 0; i < hi->Count; i++) {
if (hi->Handles[i].ProcessId != id) continue;
SetHandleInformation((HANDLE)(int)hi->Handles[i].Handle, HANDLE_FLAG_INHERIT, FALSE);
}
}
else {
err = set_win32_errno(status);
trace(LOG_ALWAYS, "Can't start process '%s': %s", exe, errno_to_str(err));
}
loc_free(hi);
memset(hpipes, 0, sizeof(hpipes));
for (i = 0; i < 3; i++) fpipes[i][0] = fpipes[i][1] = -1;
if (!err) {
#if defined(__CYGWIN__)
for (i = 0; i < 3; i++) {
if (pipe(fpipes[i]) < 0) {
err = errno;
break;
}
hpipes[i][0] = (HANDLE)get_osfhandle(fpipes[i][0]);
hpipes[i][1] = (HANDLE)get_osfhandle(fpipes[i][1]);
}
#else
for (i = 0; i < 3; i++) {
if (!CreatePipe(&hpipes[i][0], &hpipes[i][1], NULL, PIPE_SIZE)) {
err = set_win32_errno(GetLastError());
break;
}
fpipes[i][0] = _open_osfhandle((intptr_t)hpipes[i][0], O_TEXT);
fpipes[i][1] = _open_osfhandle((intptr_t)hpipes[i][1], O_TEXT);
}
#endif
}
if (!err) {
STARTUPINFO si;
PROCESS_INFORMATION prs_info;
SetHandleInformation(hpipes[0][0], HANDLE_FLAG_INHERIT, TRUE);
SetHandleInformation(hpipes[1][1], HANDLE_FLAG_INHERIT, TRUE);
SetHandleInformation(hpipes[2][1], HANDLE_FLAG_INHERIT, TRUE);
memset(&si, 0, sizeof(si));
memset(&prs_info, 0, sizeof(prs_info));
si.cb = sizeof(si);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = hpipes[0][0];
si.hStdOutput = hpipes[1][1];
si.hStdError = hpipes[2][1];
if (CreateProcess(NULL, cmd, NULL, NULL, TRUE, 0,
(envp ? envp[0] : NULL), NULL, &si, &prs_info) == 0)
{
err = set_win32_errno(GetLastError());
}
else {
*pid = prs_info.dwProcessId;
if (!CloseHandle(prs_info.hThread)) err = set_win32_errno(GetLastError());
if (!CloseHandle(prs_info.hProcess)) err = set_win32_errno(GetLastError());
}
}
if (close(fpipes[0][0]) < 0 && !err) err = errno;
if (close(fpipes[1][1]) < 0 && !err) err = errno;
if (close(fpipes[2][1]) < 0 && !err) err = errno;
if (!err) {
*prs = (Terminal *)loc_alloc_zero(sizeof(Terminal));
(*prs)->pid = *pid;
(*prs)->bcg = c->bcg;
(*prs)->channel = c;
strlcpy((*prs)->pty_type, pty_type, sizeof((*prs)->pty_type));
strlcpy((*prs)->encoding, encoding, sizeof((*prs)->encoding));
(*prs)->inp_struct = write_terminal_input(*prs, fpipes[0][1]);
(*prs)->out_struct = read_terminal_output(*prs, fpipes[1][0]);
(*prs)->err_struct = read_terminal_output(*prs, fpipes[2][0]);
list_add_first(&(*prs)->link, &terms_list);
channel_lock(c);
}
else {
close(fpipes[0][1]);
close(fpipes[1][0]);
close(fpipes[2][0]);
}
loc_free(cmd);
if (!err) return 0;
trace(LOG_ALWAYS, "Can't start process '%s': %s", exe, errno_to_str(err));
errno = err;
return -1;
}
#else
static int start_terminal(Channel * c, const char * pty_type, const char * encoding, char ** envp,
int envp_len, const char * exe, const char ** args, int * pid, Terminal ** prs) {
int err = 0;
int fd_tty_master = -1;
int fd_tty_slave = -1;
int fd_tty_out = -1;
char * tty_slave_name = NULL;
struct winsize size;
memset(&size, 0, sizeof(struct winsize));
fd_tty_master = posix_openpt(O_RDWR | O_NOCTTY);
if (fd_tty_master < 0 || grantpt(fd_tty_master) < 0 || unlockpt(fd_tty_master) < 0) err = errno;
if (!err && (tty_slave_name = ptsname(fd_tty_master)) == NULL) err = EINVAL;
if (!err && (fd_tty_slave = open(tty_slave_name, O_RDWR | O_NOCTTY)) < 0) err = errno;
if (ioctl(fd_tty_master, TIOCGWINSZ, &size) < 0 || size.ws_col <= 0 || size.ws_row <= 0) {
size.ws_col = 80;
size.ws_row = 24;
}
if (!err) {
*pid = fork();
if (*pid < 0) err = errno;
if (*pid == 0) {
int fd = -1;
char * path;
set_terminal_env(&envp, &envp_len, pty_type, encoding, exe);
path = getenv("HOME");
if (path && chdir(path) < 0) err = errno;
if (!err && setsid() < 0) err = errno;
if (!err && (fd = open(tty_slave_name, O_RDWR)) < 0) err = errno;
while (!err && fd < 3) {
int fd0 = fd;
if ((fd = dup(fd)) < 0 || close(fd0)) err = errno;
}
#if defined(TIOCSCTTY)
if (!err && (ioctl(fd, TIOCSCTTY, NULL)) < 0) err = errno;
#endif
if (!err && dup2(fd, 0) < 0) err = errno;
if (!err && dup2(fd, 1) < 0) err = errno;
if (!err && dup2(fd, 2) < 0) err = errno;
if (!err && (fd = sysconf(_SC_OPEN_MAX)) < 0) err = errno;
while (!err && fd > 3) close(--fd);
if (!err) {
execve(exe, (char **)args, envp);
err = errno;
}
fprintf(stderr, "Cannot start %s: %s\n", exe, errno_to_str(err));
exit(1);
}
}
if (!err && (fd_tty_out = dup(fd_tty_master)) < 0) err = errno;
if (!err) {
*prs = (Terminal *)loc_alloc_zero(sizeof(Terminal));
(*prs)->pid = *pid;
(*prs)->bcg = c->bcg;
(*prs)->channel = c;
strlcpy((*prs)->pty_type, pty_type, sizeof((*prs)->pty_type));
strlcpy((*prs)->encoding, encoding, sizeof((*prs)->encoding));
(*prs)->width = size.ws_row;
(*prs)->height = size.ws_col;
(*prs)->inp_struct = write_terminal_input(*prs, fd_tty_master);
(*prs)->out_struct = read_terminal_output(*prs, fd_tty_out);
list_add_first(&(*prs)->link, &terms_list);
channel_lock(c);
}
else {
if (fd_tty_master >= 0) close(fd_tty_master);
if (fd_tty_out >= 0) close(fd_tty_out);
}
if (fd_tty_slave >= 0) close(fd_tty_slave);
if (!err) return 0;
errno = err;
return -1;
}
#endif
static void command_get_context(char * token, Channel * c) {
int err = 0;
char id[256];
int tid;
Terminal * term = NULL;
json_read_string(&c->inp, id, sizeof(id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
tid = id2tid(id);
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
if (tid == 0 || (term = find_terminal(tid)) == NULL) {
err = ERR_INV_CONTEXT;
}
write_errno(&c->out, err);
if (term != NULL) {
write_context(&c->out, tid);
write_stream(&c->out, 0);
}
else {
write_stringz(&c->out, "null");
}
write_stream(&c->out, MARKER_EOM);
}
static void command_launch(char * token, Channel * c) {
int pid = 0;
int err = 0;
char encoding[TERM_PROP_DEF_SIZE];
char pty_type[TERM_PROP_DEF_SIZE];
const char * args[] = TERM_LAUNCH_ARGS;
const char * exec = TERM_LAUNCH_EXEC;
char ** envp = NULL;
int envp_len = 0;
Terminal * prs = NULL;
Trap trap;
if (set_trap(&trap)) {
json_read_string(&c->inp, pty_type, sizeof(pty_type));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
json_read_string(&c->inp, encoding, sizeof(encoding));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
envp = json_read_alloc_string_array(&c->inp, &envp_len);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
#if !defined(WIN32)
{
struct stat st;
if (err == 0 && stat(exec, &st) != 0) {
int n = errno;
static char fnm[FILE_PATH_SIZE];
/* On some systems (e.g. Free DSB) bash is installed under /usr/local */
assert(exec[0] == '/');
snprintf(fnm, sizeof(fnm), "/usr/local%s", exec);
if (stat(fnm, &st) == 0) {
args[0] = exec = fnm;
}
else {
err = n;
}
}
}
#endif
if (err == 0 && start_terminal(
c, pty_type, encoding, envp, envp_len, exec,
args, &pid, &prs) < 0) err = errno;
if (!err) {
assert(find_terminal(pid) != NULL);
add_waitpid_process(pid);
}
/* write result back */
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, err);
if (err || pid == 0) {
write_stringz(&c->out, "null");
}
else {
write_context(&c->out, pid);
write_stream(&c->out, 0);
}
write_stream(&c->out, MARKER_EOM);
clear_trap(&trap);
}
loc_free(envp);
if (trap.error) exception(trap.error);
}
static void command_set_win_size(char * token, Channel * c) {
int err = 0;
struct winsize size;
char id[256];
unsigned tid;
Terminal * term = NULL;
json_read_string(&c->inp, id, sizeof(id));
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
size.ws_col = json_read_ulong(&c->inp);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
size.ws_row = json_read_ulong(&c->inp);
if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX);
if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX);
tid = id2tid(id);
if (tid == 0 || (term = find_terminal(tid)) == NULL) {
err = ERR_INV_CONTEXT;
}
else if (term->width != size.ws_col || term->height != size.ws_row) {
#if defined(WIN32)
#else
if (ioctl(term->inp_struct->fd, TIOCSWINSZ, &size) < 0) err = errno;
#endif
if (!err) {
term->width = size.ws_col;
term->height = size.ws_row;
send_event_terminal_win_size_changed(&term->bcg->out, term);
}
}
write_stringz(&c->out, "R");
write_stringz(&c->out, token);
write_errno(&c->out, err);
write_stream(&c->out, MARKER_EOM);
}
static void waitpid_listener(int pid, int exited, int exit_code,
int signal, int event_code, int syscall, void * args) {
assert(find_terminal(pid) == NULL || exited);
if (exited) {
Terminal * prs = find_terminal(pid);
if (prs) {
if (signal != 0) prs->exit_code = -signal;
else prs->exit_code = exit_code;
terminal_exited(prs);
}
}
}
static void channel_close_listener(Channel * c) {
LINK * l = NULL;
for (l = terms_list.next; l != &terms_list;) {
Terminal * term = link2term(l);
l = l->next;
if (term->channel == c && !term->terminated) {
trace(LOG_ALWAYS, "Terminal is left launched: T%d", term->pid);
kill_term(term);
}
}
}
void ini_terminals_service(Protocol * proto) {
list_init(&terms_list);
add_waitpid_listener(waitpid_listener, NULL);
add_channel_close_listener(channel_close_listener);
add_command_handler(proto, TERMINALS, "getContext", command_get_context);
add_command_handler(proto, TERMINALS, "launch", command_launch);
add_command_handler(proto, TERMINALS, "exit", command_exit);
add_command_handler(proto, TERMINALS, "setWinSize", command_set_win_size);
}
#endif /* SERVICE_Terminals */