This commit is contained in:
~d6 2023-11-15 20:55:18 -05:00
parent 56a17bd878
commit 813cc5910c
4 changed files with 235 additions and 45 deletions

78
console.txt Normal file
View File

@ -0,0 +1,78 @@
CONSOLE DEVICE
0x10 vector* 0x18 out*
0x11 (vector) 0x19 err*
0x12 read 0x1a --
0x13 exec arguments 0x1b --
0x14 (exec arguments) 0x1c --
0x15 exec mode 0x1d --
0x16 exec action* 0x1e --
0x17 input type 0x1f --
(* denotes a register that causes an immediate effect)
vector (0x10) contains an address to jump to when input is available.
when the vector fires one byte can be read from the read port (0x12),
and its meaning is defined by the input type (0x17).
the read port (0x12) contains one byte of data to be read. it usually
occurs as the result of data being available on stdin, but may have
other meanings (such as an exit code from a finished process).
the meaning of exec args (0x13) depends on which action it is used with:
- 0x00 - unused
- 0x01 - address of a command string (e.g. "gcc -o demo demo.c")
- 0x02 - address of an env. variable name (e.g. "TERM")
- 0x03 - address of an env. variable name=value pair (e.g. "TERM=vt100")
- 0x04 - unsigned 16-bit integer (pty height, e.g. 0x0018)
- 0x05 - unsigned 16-bit integer (pty width, e.g. 0x0050)
- (all strings must be null-terminated)
exec mode (0x15) provides context for exec actions (0x16):
- lower 2 bits control which child process to use:
+ 0x00 - child 0
+ 0x01 - child 1
+ 0x02 - child 2
+ 0x03 - child 3
- (next 2 bits unused)
- upper 4 bits control which pipes to use with execute:
+ 0x80 - use child's pty (implies 0x70)
+ 0x40 - read from child's stderr
+ 0x20 - read from child's stdout
+ 0x10 - write to child's stdin
exec action (0x16) acts on the exec args address (0x13):
- 0x00 - nop: does nothing
- 0x01 - execute: reads command string, starts a subprocess
- 0x02 - getenv: reads string (NAME) from exec args
- 0x03 - setenv: reads string (NAME=value) and updates environment
- 0x04 - set-pty-height: reads u16 from exec args, updates winsize
- 0x05 - set-pty-width: reads u16 from exec args, updates winsize
(getenv data returned using console vector with input type 0x05, 0x06)
the input type (0x17) field explains how to interpret the read port
(0x12) when executing the console vector (0x10). these are the input
types that are expected:
- 0x00 - no input (read port is unused)
- 0x01 - stdin
- 0x02 - argument
- 0x03 - argument spacer (read port is \n)
- 0x04 - argument end (read port is \n)
- 0x05 - action output
- 0x06 - action output end (read port is \n)
- 0x40 - child 0 exited (read port is exit code)
- 0x41 - child 1 exited (read port is exit code)
- 0x42 - child 2 exited (read port is exit code)
- 0x43 - child 3 exited (read port is exit code)
- 0x81 - child 1
- 0x82 - child 2
- 0x83 - child 3
- (child 0 uses 0x01, i.e. "stdin", instead of 0x80)
writing a byte to the out port (0x18) will send one byte of data to
the standard output. if exec mode (0x15) is non-zero the lower 2 bits
will determine which child process to send the output to.
writing a byte to the err port (0x19) will send one byte of data to
stderr. unlike with the out port (0x18), stderr is never redirected to
child processes.

View File

@ -1,6 +1,7 @@
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200112L
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
@ -31,6 +32,84 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE.
*/
/*
CONSOLE DEVICE
0x10 vector* 0x18 write byte* (stdout)
0x11 (vector) 0x19 write byte* (stderr)
0x12 read byte (stdin) 0x1a --
0x13 exec args 0x1b --
0x14 (exec args) 0x1c --
0x15 exec mode 0x1d --
0x16 exec action* 0x1e --
0x17 type 0x1f --
(* denotes a register that causes an immediate effect)
when vector (0x10) fires, the type (0x17) field explains what kind of
event this (such as data being available to read on stdin) is and how
to handle it.
the read byte (0x12) contains one byte of data to be read. it usually
occurs as the result of data being available on stdin, but may have
other meanings (such as an exit code from a finished process).
exec args (0x13) is usually an address pointing to a string, which is
acted on by exec action (0x16). occasionally it may be used as an 8-bit
or 16-bit number instead (such as with exec actions 0x04-0x05).
the meaning of exec args depends on which action it is used with:
- 0x00 - unused
- 0x01 - address of a command string
- 0x02 - address of an environment variable name
- 0x03 - address of an environment variable name+value (e.g. "FOO=123")
- 0x04 - unsigned 16-bit integer
- 0x05 - unsigned 16-bit integer
- (all strings must be null-terminated)
exec mode (0x015) provides context for exec actions (0x16):
- lower 2 bits control which child process to use:
+ 0x00 - child 0
+ 0x01 - child 1
+ 0x02 - child 2
+ 0x03 - child 3
- (next 2 bits unused)
- upper 4 bits control which pipes to use with execute:
+ 0x80 - use child's pty (implies 0x70)
+ 0x40 - read from child's stderr
+ 0x20 - read from child's stdin
+ 0x10 - write to child's stdin
exec action (0x16) acts on the exec args address (0x13):
- 0x00 - nop, does nothing
- 0x01 - execute, starts a subprocess
- 0x02 - getenv, reads string (NAME) from exec args, overwrites value
- 0x03 - setenv, reads string (NAME=value) from exec, writes into env
- 0x04 - set-pty-height, reads u16 from exec args, updates winsize
- 0x05 - set-pty-width, reads u16 from exec args, updates winsize
reading type (0x17) during vector:
- 0x00 - no input
- 0x01 - stdin
- 0x02 - argument
- 0x03 - argument spacer
- 0x04 - argument end
- 0x05 - action output
- 0x06 - action output end
- 0x40 - child 0 exited
- 0x41 - child 1 exited
- 0x42 - child 2 exited
- 0x43 - child 3 exited
- 0x81 - child 1
- 0x82 - child 2
- 0x83 - child 3
- (child 0 uses 0x01, i.e. "stdin")
for most types, the read byte(0x12) contains one byte of input data.
however, for exit notifications (0x40-0x43) it will intead contain an
unsigned 8-bit exit code.
*/
/* process */
static char *fork_args[32];
static int child_mode;
@ -42,6 +121,13 @@ static int saved_out;
static pid_t child_pid;
struct winsize ws = {24, 80, 8, 12};
/* new */
static pid_t child_pids[4];
static int child_modes[4];
static int child_fd_ins[4] = {0, -1, -1, -1};
static int child_fd_outs[4] = {1, -1, -1, -1};
static int child_ptys[4] = {-1, -1, -1, -1};
static void
parse_args(Uxn *u, Uint8 *d)
{
@ -59,26 +145,25 @@ parse_args(Uxn *u, Uint8 *d)
/* call after we're sure the process has exited */
static void
clean_after_child(void)
clean_after_child(int child)
{
child_pid = 0;
if(child_mode >= 0x80) {
close(pty_fd);
dup2(saved_in, 0);
dup2(saved_out, 1);
} else {
if(child_mode & 0x01) {
close(to_child_fd[1]);
dup2(saved_out, 1);
}
if(child_mode & 0x06) {
close(from_child_fd[0]);
dup2(saved_in, 0);
}
}
child_mode = 0;
int n = child & 0x3;
int is_pty = child_modes[n] >= 0x80;
if(!child_pids[n]) return;
if(is_pty) close(child_ptys[n]);
if(is_pty || child_mode & 0x1) close(child_fd_outs[n]);
if(is_pty || child_mode & 0x6) close(child_fd_ins[n]);
if(n == 0) {
if (is_pty || (child_modes[n] & 0x1)) dup2(saved_out, 1);
if (is_pty || (child_modes[n] & 0x6)) dup2(saved_in, 0);
saved_in = -1;
saved_out = -1;
}
child_pids[n] = 0;
child_modes[n] = 0;
child_ptys[n] = -1;
child_fd_ins[n] = -1;
child_fd_outs[n] = -1;
}
int
@ -105,29 +190,36 @@ start_fork_pty(Uint8 *d)
{
int fd = -1;
pid_t pid = forkpty(&fd, NULL, NULL, NULL);
int n = d[0xa] & 0x3;
if(pid < 0) { /* failure */
d[0x6] = 0xff;
fprintf(stderr, "fork failure\n");
} else if(pid == 0) { /* child */
setenv("TERM", "ansi", 1);
setenv("TERM", "ansi", 1); /* TODO: configure these */
execvp(fork_args[0], fork_args);
d[0x6] = 0xff;
fprintf(stderr, "exec failure\n");
} else { /*parent*/
child_pid = pid;
pty_fd = fd;
child_pids[n] = pid;
child_ptys[n] = fd;
ioctl(fd, TIOCSWINSZ, &ws);
if(n == 0) {
saved_in = dup(0);
saved_out = dup(1);
dup2(fd, 0);
dup2(fd, 1);
} else {
child_fd_ins[n] = fd;
child_fd_outs[n] = fd;
}
}
}
static void
start_fork_pipe(Uint8 *d)
{
if(child_mode & 0x01) {
int n = d[0xa] & 0x3;
if(child_modes[n] & 0x01) {
/* parent writes to child's stdin */
if(pipe(to_child_fd) == -1) {
d[0x6] = 0xff;
@ -136,7 +228,7 @@ start_fork_pipe(Uint8 *d)
}
}
if(child_mode & 0x06) {
if(child_modes[n] & 0x06) {
/* parent reads from child's stdout and/or stderr */
if(pipe(from_child_fd) == -1) {
d[0x6] = 0xff;
@ -150,28 +242,34 @@ start_fork_pipe(Uint8 *d)
d[0x6] = 0xff;
fprintf(stderr, "fork failure\n");
} else if(pid == 0) { /* child */
if(child_mode & 0x01) {
if(child_modes[n] & 0x01) {
dup2(to_child_fd[0], 0);
close(to_child_fd[1]);
}
if(child_mode & 0x06) {
if(child_mode & 0x02) dup2(from_child_fd[1], 1);
if(child_mode & 0x04) dup2(from_child_fd[1], 2);
if(child_modes[n] & 0x06) {
if(child_modes[n] & 0x02) dup2(from_child_fd[1], 1);
if(child_modes[n] & 0x04) dup2(from_child_fd[1], 2);
close(from_child_fd[0]);
}
execvp(fork_args[0], fork_args);
d[0x6] = 0xff;
fprintf(stderr, "exec failure\n");
} else { /*parent*/
child_pid = pid;
if(child_mode & 0x01) {
child_pids[n] = pid;
if(child_modes[n] & 0x01) {
child_fd_ins[n] = to_child_fd[1];
if(n == 0) {
saved_out = dup(1);
dup2(to_child_fd[1], 1);
}
close(to_child_fd[0]);
}
if(child_mode & 0x06) {
if(child_modes[n] & 0x06) {
child_fd_outs[n] = from_child_fd[0];
if(n == 0) {
saved_in = dup(0);
dup2(from_child_fd[0], 0);
}
close(from_child_fd[1]);
}
}
@ -180,13 +278,14 @@ start_fork_pipe(Uint8 *d)
static void
kill_child(Uint8 *d, int options)
{
if(child_pid) {
kill(child_pid, 9);
int n = d[0xa] & 0x3;
if(child_pids[n]) {
kill(child_pids[n], 9);
int wstatus;
if(waitpid(child_pid, &wstatus, options)) {
if(waitpid(child_pids[n], &wstatus, options)) {
d[0x6] = 1;
d[0x7] = WEXITSTATUS(wstatus);
clean_after_child();
clean_after_child(n);
}
}
}
@ -194,11 +293,12 @@ kill_child(Uint8 *d, int options)
static void
start_fork(Uxn *u, Uint8 *d)
{
int n = d[0xa] & 0x3;
fflush(stderr);
kill_child(d, 0);
child_mode = d[0x5];
child_modes[n] = d[0x5];
parse_args(u, d);
if(child_mode >= 0x80)
if(child_modes[n] >= 0x80)
start_fork_pty(d);
else
start_fork_pipe(d);
@ -230,3 +330,12 @@ console_deo(Uxn *u, Uint8 *d, Uint8 port)
fflush(fd);
}
}
void
update_pollfd(struct pollfd *fds)
{
for(int i = 0; i < 16; i++) {
fds[i + 2].fd = child_fd_outs[i];
fds[i + 2].events = POLLIN;
}
}

View File

@ -20,3 +20,4 @@ int console_input(Uxn *u, char c, int type);
void console_listen(Uxn *u, int i, int argc, char **argv);
Uint8 console_dei(Uxn *u, Uint8 addr);
void console_deo(Uxn *u, Uint8 *d, Uint8 port);
void update_pollfd(struct pollfd *fds);

View File

@ -220,7 +220,7 @@ emu_run(Uxn *u, char *rom)
int i = 1, n, s = uxn_screen.scale;
char expirations[8];
char coninp[CONINBUFSIZE];
struct pollfd fds[3];
struct pollfd fds[18];
static const struct itimerspec screen_tspec = {{0, 16666666}, {0, 16666666}};
loaded_rom = rom;
@ -245,6 +245,8 @@ emu_run(Uxn *u, char *rom)
timerfd_settime(fds[1].fd, 0, &screen_tspec, NULL);
fds[2].fd = STDIN_FILENO;
fds[0].events = fds[1].events = fds[2].events = POLLIN;
update_pollfd(fds);
/* main loop */
while(!u->dev[0x0f]) {
if(poll(fds, 3, 1000) <= 0)