mii_emu/libmish/src/mish_session.c

307 lines
7.2 KiB
C

/*
* mish_session.c
*
* Copyright (C) 2020 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> // for isatty etc
#include <ctype.h> // for isdigit
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
#include <util.h> // for openpty
#endif
#ifdef __linux__
#include <pty.h>
#endif
#include "mish_priv.h"
#include "mish.h"
/*
* get a localtime equivalent, with milliseconds added
*/
uint64_t
_mish_stamp_ms()
{
struct timespec tim;
#ifdef __MACH__
clock_serv_t cclock;
mach_timespec_t mts;
host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
clock_get_time(cclock, &mts);
mach_port_deallocate(mach_task_self(), cclock);
tim.tv_sec = mts.tv_sec;
tim.tv_nsec = mts.tv_nsec;
#else
clock_gettime(CLOCK_REALTIME, &tim);
#endif
uint64_t res = (tim.tv_sec * 1000) + (tim.tv_nsec / 1000000);
return res;
}
/*
* need to keep this around for atexit()
*/
static mish_p _mish = NULL;
static void
_mish_atexit()
{
if (_mish)
mish_terminate(_mish);
}
mish_p
mish_prepare(
unsigned long caps )
{
/* In production, of if you want to make sure mish is disabled without
* re-linking, set this env variable.
*/
if (getenv("MISH_OFF")) {
if (atoi(getenv("MISH_OFF"))) {
unsetenv("MISH_TELNET_PORT");
printf("mish: Disabled my MISH_OFF\n");
return NULL;
}
}
mish_p m = (mish_p)calloc(1, sizeof(*m));
FD_ZERO(&m->select.read);
FD_ZERO(&m->select.write);
TAILQ_INIT(&m->backlog.log);
TAILQ_INIT(&m->clients);
m->flags = caps;
int tty = 0;
if (getenv("MISH_TTY")) {
tty = atoi(getenv("MISH_TTY"));
} else
tty = (isatty(0) && isatty(1) && isatty(2)) ||
(caps & MISH_CAP_FORCE_PTY);
int io[2]; // stdout pipe
int ie[2]; // stderr pipe
#if !defined(__wasm__)
{
char pty[80];
if (openpty(&io[0], &io[1], pty, NULL, NULL) == -1) {
perror("openpty");
goto error;
}
if (!(caps & MISH_CAP_NO_STDERR)) {
if (openpty(&ie[0], &ie[1], pty, NULL, NULL) == -1) {
perror("openpty");
goto error;
}
}
if (tty)
m->flags |= MISH_CONSOLE_TTY;
if (tcgetattr(0, &m->orig_termios))
;//perror("tcgetattr");
struct termios raw = m->orig_termios;
raw.c_iflag &= ~(ICRNL | IXON);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // ISIG
if (tcsetattr(0, TCSAFLUSH, &raw))
;//perror("tcsetattr");
}
#endif
if (!(caps & MISH_CAP_NO_TELNET)) {
uint16_t port = 0; // suggested telnet port
if (getenv("MISH_TELNET_PORT"))
port = atoi(getenv("MISH_TELNET_PORT"));
if (mish_telnet_prepare(m, port) == 0) {
char port[8];
snprintf(port, sizeof(port), "%d", m->telnet.port);
setenv("MISH_TELNET_PORT", port, 1);
} else
unsetenv("MISH_TELNET_PORT");
}
// backup the existing descriptors, to make a 'client', we replace
// the original 1,2 with out own pipe/pty
m->originals[0] = dup(1);
m->originals[1] = dup(2);
// we do another dup(0,1) here, as that client will close() them
m->console = mish_client_new(m, dup(0), dup(1), tty);
m->stamp_start = _mish_stamp_ms();
_mish_input_init(m, &m->origin[0], io[0]);
if (dup2(io[1], 1) == -1) {
perror("dup2");
goto error;
}
if (!(caps & MISH_CAP_NO_STDERR)) {
_mish_input_init(m, &m->origin[1], ie[0]);
if (dup2(ie[1], 2) == -1) {
perror("dup2");
goto error;
}
}
atexit(_mish_atexit);
// m->main = pthread_self();
// TODO: Make an epoll polling thread for linux
pthread_create(&m->capture, NULL, _mish_capture_select, m);
_mish = m;
return m;
error:
if (io[0]) {
close(io[0]);
close(io[1]);
}
if (ie[0]) {
close(ie[0]);
close(ie[1]);
}
free(m);
return NULL;
}
unsigned long
mish_get_flags(
struct mish_t *m)
{
return m ? m->flags : 0;
}
/*
* This can be called at any time; if the thread is running, tell it to
* close everything and exit... then wait for a little bit for it to
* terminate, then restore the terminal.
*/
void
mish_terminate(
mish_p m)
{
if (!_mish)
return;
// restore normal IOs
dup2(m->originals[0], 1);
dup2(m->originals[1], 2);
// the thread does this, but we also do it in case this function is
// called from another exit(x)
#if !defined(__wasm__)
if ((m->flags & MISH_CONSOLE_TTY) &&
tcsetattr(0, TCSAFLUSH, &m->orig_termios))
perror("mish_terminate tcsetattr");
#endif
close(m->originals[0]); close(m->originals[1]);
if (m->capture) {
m->flags |= MISH_QUIT;
// this will wake the select() call from sleep
if (write(1, "\n", 1))
;
time_t start = time(NULL);
time_t now;
while (((now = time(NULL)) - start < 2) && m->capture)
usleep(1000);
}
printf("\033[4l\033[;r\033[999;1H"); fflush(stdout);
printf("%s done\n", __func__);
free(m);
_mish = NULL;
}
static void
_mish_cmd_quit(
void * param,
int argc,
const char * argv[])
{
printf(MISH_COLOR_RED
"mish: Quitting."
MISH_COLOR_RESET "\n");
mish_p m = ((mish_client_p)param)->mish;
m->flags |= MISH_QUIT;
}
MISH_CMD_NAMES(quit, "q", "quit");
MISH_CMD_HELP(quit,
"exit running program",
"Close all clients and exit(0)");
MISH_CMD_REGISTER(quit, _mish_cmd_quit);
#define VT_COL(_c) "\033[" #_c "G"
static void
_mish_cmd_mish(
void * param,
int argc,
const char * argv[])
{
printf(MISH_COLOR_RED
"mish: mish command."
MISH_COLOR_RESET "\n");
mish_p m = ((mish_client_p)param)->mish;
printf("Backlog: %6d lines (%5dKB)" VT_COL(40) "Telnet Port: %5d\n",
m->backlog.size,
(int)m->backlog.alloc / 1024,
m->telnet.port);
#if 0
printf(" read: ");
for (int i = 0; i < m->select.max; i++)
if (FD_ISSET(i, &m->select.read)) printf("%d ", i);
printf(VT_COL(40) " write: ");
for (int i = 0; i < m->select.max; i++)
if (FD_ISSET(i, &m->select.write)) printf("%d ", i);
printf("\n");
#endif
mish_client_p c;
TAILQ_FOREACH(c, &m->clients, self) {
printf(" Client: r: %d w: %d %s %s\n",
c->input.fd, c->output.fd,
c->input.is_telnet ? "telnet session" :
c == m->console ? "console" : "*unknown*",
c == m->console ?
m->flags & MISH_CONSOLE_TTY ? "(tty)": "(dumb)"
: "");
printf(" max sizes: vector: %d input: %d\n",
c->output.size, c->input.line ? c->input.line->size : 0);
}
if (argv[1] && !strcmp(argv[1], "clear")) {
printf("Clearing backlog\n");
m->flags |= MISH_CLEAR_BACKLOG;
}
if (argv[1] && !strcmp(argv[1], "backlog")) {
if (argv[2]) {
if (!strcmp(argv[2], "clear")) {
m->flags |= MISH_CLEAR_BACKLOG;
} else if (!strcmp(argv[2], "max") && argv[3] && isdigit(argv[3][0])) {
m->backlog.max_lines = atoi(argv[3]);
printf("Backlog max lines set to %d\n", m->backlog.max_lines);
} else if (isdigit(argv[2][0])) {
m->backlog.max_lines = atoi(argv[2]);
printf("Backlog max lines set to %d\n", m->backlog.max_lines);
} else
fprintf(stderr, "Unknown backlog command '%s'\n", argv[2]);
} else {
printf("Backlog: %6d/%6d lines (%5dKB)\n",
m->backlog.size, m->backlog.max_lines,
(int)m->backlog.alloc / 1024);
}
}
}
MISH_CMD_NAMES(mish, "mish");
MISH_CMD_HELP(mish,
"[cmd...] Displays mish status.",
"backlog [clear] [max <n>] - show backlog status\n"
" also set the maximum lines in the backlog\n"
" (0 = unlimited)\n"
"Show status and a few bits of internals.");
MISH_CMD_REGISTER(mish, _mish_cmd_mish);