From 8ddc1b7676da4db52d32fb4ee92d3721d7704247 Mon Sep 17 00:00:00 2001 From: d_m Date: Thu, 14 Dec 2023 23:43:37 -0500 Subject: [PATCH] fix numerous bugs, add tty controls, etc --- console.txt | 4 ++ src/devices/console.c | 106 ++++++++++++++++++++++++++++++------------ src/devices/console.h | 19 ++++---- src/uxn11.c | 11 ++--- test/femto.tal | 16 ++----- test/term.tal | 26 +++++------ 6 files changed, 109 insertions(+), 73 deletions(-) diff --git a/console.txt b/console.txt index 3f74656..e626dc2 100644 --- a/console.txt +++ b/console.txt @@ -63,6 +63,8 @@ for a host-put (0x1f). the meaning values by host-put value: - 0x01 - (execute) command string (e.g. 'gcc -o "demo" demo.c') - 0x02 - (getpid) unused - 0x03 - (kill) unused + - 0x04 - (raw-tty) unused + - 0x04 - (restore-tty) unused - 0x10 - (getenv) name string (e.g. "TERM") - 0x11 - (setenv) assignment string (e.g. "TERM=vt100") strings must be null-terminated. commands are parsed by /bin/sh -c. @@ -86,6 +88,8 @@ the host-put port (0x1f) specifies which host action to take: - 0x01 - execute: reads command string, starts a subprocess - 0x02 - getpid: responds with child process pid (if any) - 0x03 - kill: shuts down child process + - 0x04 - put stdin tty into raw mode + - 0x05 - restore stdin tty to canonical mode - 0x10 - getenv: looks up a name (e.g. TERM) in env, responds with value - 0x11 - setenv: reads an assignment (e.g. TERM=vt100), updates env diff --git a/src/devices/console.c b/src/devices/console.c index ee9d65e..434eb1a 100644 --- a/src/devices/console.c +++ b/src/devices/console.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #ifdef __linux @@ -32,27 +33,28 @@ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE. */ -UxnSubprocess children[CONSOLE_MAX_CHILDREN]; -static char *fork_args[4] = {"/bin/sh", "-c", "", NULL}; -static int to_child_fd[2], from_child_fd[2]; -static char buf[16]; - -static UxnSubprocess* -find_child_by_pid(pid_t pid) -{ - for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) { - if(children[n].pid == pid) - return &children[n]; - } - return NULL; -} - #define child_ptys(mode) (mode & CONSOLE_MODE_PTY) #define child_reads(mode) (mode & CONSOLE_CHILD_READS) #define child_writes(mode) (mode & CONSOLE_CHILD_WRITES) #define child_writes_out(mode) (mode & CONSOLE_MODE_OUT) #define child_writes_err(mode) (mode & CONSOLE_MODE_ERR) +UxnSubprocess children[CONSOLE_MAX_CHILDREN]; +static char *fork_args[4] = {"/bin/sh", "-c", "", NULL}; +static int to_child_fd[2], from_child_fd[2]; +static char buf[16]; +static int raw_tty = 0; +struct termios old_termios; + +static UxnSubprocess* +find_child_by_pid(pid_t pid) +{ + for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) + if(children[n].pid == pid) + return &children[n]; + return NULL; +} + static void handle_sigchld(int sig) { @@ -61,13 +63,16 @@ handle_sigchld(int sig) while((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) { UxnSubprocess *child = find_child_by_pid(pid); if (child != NULL) { + /* set running to negative when exiting for clean up */ child->running = WEXITSTATUS(wstatus) - 256; + /* close relevant file descriptors */ if(child_ptys(child->mode)) close(child->pty); else { if(child_reads(child->mode)) close(child->fd_in); if(child_writes(child->mode)) close(child->fd_out); } + /* reset fields to starting values */ child->pid = child->mode = 0; child->fd_out = child->fd_in = child->pty = -1; return; @@ -79,7 +84,7 @@ static void start_fork_pty(UxnSubprocess *child, int mode) { int fd = -1; - struct winsize ws = {23, 80, 8, 12}; // rows, cols, xps, ypx + struct winsize ws = {23, 80, 8, 12}; pid_t pid = forkpty(&fd, NULL, NULL, &ws); if(pid < 0) { /* failure */ fprintf(stderr, "parent fork failure\n"); @@ -95,6 +100,7 @@ start_fork_pty(UxnSubprocess *child, int mode) child->fd_in = fd; child->fd_out = fd; } while (child->pid != pid); + /* loop to avoid possible race with clean up */ } } @@ -120,6 +126,7 @@ start_fork_pipe(UxnSubprocess *child, int mode) pid_t pid = fork(); if(pid < 0) { /* failure */ fprintf(stderr, "fork failure\n"); + return; } else if(pid == 0) { /* child */ if(child_reads(mode)) { dup2(to_child_fd[0], 0); @@ -142,7 +149,10 @@ start_fork_pipe(UxnSubprocess *child, int mode) child->fd_in = child_reads(mode) ? to_child_fd[1] : -1; child->fd_out = child_writes(mode) ? from_child_fd[0] : -1; } while (child->pid != pid); - if(child_reads(mode)) close(to_child_fd[0]); + /* loop to avoid possible race with clean up */ + + /* close the unused end of pipes we opened */ + if(child_reads(mode)) close(to_child_fd[0]); if(child_writes(mode)) close(from_child_fd[1]); } } @@ -173,7 +183,7 @@ host_execute(Uxn *u, Uint8 *d) UxnSubprocess *child = &children[opts & 0x3]; fork_args[2] = cmd; - if(child->pid) + if(child->pid > 0) kill(child->pid, 9); if(opts >= 0x80) @@ -202,13 +212,42 @@ static void host_kill(Uxn *u, Uint8 *d) { int n = d[0xe] & 0x3; - kill(children[n].pid, 15); + if(children[n].pid > 0) + kill(children[n].pid, 15); +} + +static void +host_raw_tty() +{ + if(!raw_tty) { + tcgetattr(STDIN_FILENO, &old_termios); + struct termios term = old_termios; + /* corresponds to `stty raw -echo` */ + term.c_cflag |= (CS8); + term.c_iflag &= ~(IGNBRK | BRKINT | IGNPAR | PARMRK | INPCK | ISTRIP); + term.c_iflag &= ~(INLCR | IGNCR | ICRNL | IXON | IXOFF | IUCLC | IXANY | IMAXBEL); + term.c_lflag &= ~(ICANON | ISIG | ECHO); + term.c_oflag &= ~(OPOST); + term.c_cc[VMIN] = 0; + term.c_cc[VTIME] = 1; + tcsetattr(0, TCSAFLUSH, &term); + raw_tty = 1; + } +} + +static void +host_restore_tty() +{ + if(raw_tty) { + tcsetattr(0, TCSAFLUSH, &old_termios); + raw_tty = 0; + } } static void host_getenv(Uxn *u, Uint8 *d) { - int addr = PEEK2(d + 0x3); + int addr = PEEK2(d + 0xc); char *name = (char *)&u->ram[addr]; host_response(u, getenv(name)); } @@ -216,23 +255,29 @@ host_getenv(Uxn *u, Uint8 *d) static void host_setenv(Uxn *u, Uint8 *d) { - int addr = PEEK2(d + 0x3); + int addr = PEEK2(d + 0xc); char *name = (char *)&u->ram[addr]; int i = 0; - while (name[i] != '=') i++; - name[i] = '\0'; - char *value = &name[i+1]; - setenv(name, value, 1); + while(name[i] != '=') i++; + if(name[i] == '=') { + name[i] = '\0'; + char *value = &name[i+1]; + setenv(name, value, 1); + } else { + fprintf(stderr, "mal-formed setenv\n"); + } } static void host_put(Uxn *u, Uint8 *d) { switch(d[0xf]) { - case 0x0: /* nop */ break; - case 0x1: host_execute(u, d); break; - case 0x2: host_getpid(u, d); break; - case 0x3: host_kill(u, d); break; + case 0x00: /* nop */ break; + case 0x01: host_execute(u, d); break; + case 0x02: host_getpid(u, d); break; + case 0x03: host_kill(u, d); break; + case 0x04: host_raw_tty(); break; + case 0x05: host_restore_tty(); break; case 0x10: host_getenv(u, d); break; case 0x11: host_setenv(u, d); break; } @@ -266,7 +311,7 @@ UxnSubprocess void console_monitor(Uxn *u) { - fflush(stdout); + /*fflush(stdout);*/ /* TODO: needed? stderr? children? */ for(int n = 0; n < CONSOLE_MAX_CHILDREN; n++) { UxnSubprocess *child = &children[n]; if(child->running < 0) { @@ -277,7 +322,6 @@ console_monitor(Uxn *u) } } -// TODO ignore port int console_input(Uxn *u, char c, int type) { diff --git a/src/devices/console.h b/src/devices/console.h index 78fde89..c9cb9a6 100644 --- a/src/devices/console.h +++ b/src/devices/console.h @@ -25,13 +25,13 @@ WITH REGARD TO THIS SOFTWARE. #define CONSOLE_CHILD_EXIT 0x80 /* exits from child-n: 0x80 | n */ typedef struct UxnSubprocess { - int id; /* identifier: 0-3 */ - pid_t pid; /* when running, pid > 0 */ - int mode; /* bit flags: CONSOLE_MODE_ */ + int id; /* identifier: 0-3 */ + pid_t pid; /* when running, pid > 0 */ + int mode; /* bit flags: CONSOLE_MODE_ */ int running; /* 0=stopped, >0 running, <0 exiting */ - int fd_in; /* fd to send to child; -1 is unset */ - int fd_out; /* fd to read from child; -1 is unset */ - int pty; /* pty for child; -1 is unset */ + int fd_in; /* fd to send to child; -1 is unset */ + int fd_out; /* fd to read from child; -1 is unset */ + int pty; /* pty for child; -1 is unset */ } UxnSubprocess; void init_console(void); @@ -47,8 +47,11 @@ void console_deo(Uxn *u, Uint8 *d, Uint8 port); #define CONSOLE_MODE_OUT 0x20 #define CONSOLE_MODE_INP 0x10 -#define CONSOLE_CHILD_READS 0x90 /* CONSOLE_MODE_PTY | CONSOLE_MODE_INP */ -#define CONSOLE_CHILD_WRITES 0xe0 /* CONSOLE_MODE_PTY | CONSOLE_MODE_OUT | CONSOLE_MODE_ERR */ +/* CONSOLE_MODE_PTY | CONSOLE_MODE_INP */ +#define CONSOLE_CHILD_READS 0x90 + +/* CONSOLE_MODE_PTY | CONSOLE_MODE_OUT | CONSOLE_MODE_ERR */ +#define CONSOLE_CHILD_WRITES 0xe0 #define CONSOLE_MAX_CHILDREN 4 diff --git a/src/uxn11.c b/src/uxn11.c index e48e7d3..e54eaaf 100644 --- a/src/uxn11.c +++ b/src/uxn11.c @@ -268,17 +268,14 @@ 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; - for(i = 0; i < 4; i++) { - fds[i + 3].fd = get_child(i)->fd_out; - fds[i + 3].events = POLLIN; - } /* main loop */ while(!u->dev[0x0f]) { - for(i = 0; i < 4; i++) { + for(i = 0; i < CONSOLE_MAX_CHILDREN; i++) { fds[i + 3].fd = get_child(i)->fd_out; + fds[i + 3].events = POLLIN; } - if(poll(fds, 7, 1000) <= 0) + if(poll(fds, 3 + CONSOLE_MAX_CHILDREN, 1000) <= 0) continue; while(XPending(display)) emu_event(u); @@ -297,7 +294,7 @@ emu_run(Uxn *u, char *rom) handle_input(u, fds[2], coninp, CONSOLE_STDIN, CONSOLE_STDIN_END); /* read input from child processes */ - for(i = 0; i < 4; i++) + for(i = 0; i < CONSOLE_MAX_CHILDREN; i++) handle_input(u, fds[i + 3], coninp, CONSOLE_CHILD_DATA | i, CONSOLE_CHILD_END | i); /* check to see if any children exited */ diff --git a/test/femto.tal b/test/femto.tal index d2b3ce5..98e3026 100644 --- a/test/femto.tal +++ b/test/femto.tal @@ -171,16 +171,12 @@ #01 .Console/host-put DEO JMP2r ( restore the terminal to "normal" settings ) -@restore-terminal - ;stty-sane !run-cmd +@restore-terminal ( -> ) + #05 .Console/host-put DEO JMP2r ( put the terminal in raw mode ) @init-terminal ( -> ) - ;stty-raw !run-cmd - -( command strings to use ) -@stty-sane "stty 20 "sane 00 -@stty-raw "stty 20 "raw 20 "-echo 00 + #04 .Console/host-put DEO JMP2r ( import uxn regex library ) ~regex.tal @@ -487,11 +483,7 @@ ( ) ( this definition is needed so the address can be used by JCN2. ) @quit-now - ;finish-quit .Console/vector DEO2 - restore-terminal BRK - -@finish-quit - .Console/type DEI #81 NEQ ?{ #80 .System/halt DEO } BRK + restore-terminal #010f DEO BRK ( label that calls BRK ) ( ) diff --git a/test/term.tal b/test/term.tal index dfe4b1d..3751896 100644 --- a/test/term.tal +++ b/test/term.tal @@ -409,7 +409,6 @@ ( send ESC [ $c ) @arrow ( c^ -> ) -( .Console/w STH ) .Console/proc-put STH #1b STHkr DEO LIT "[ STHkr DEO STHr DEO JMP2r @@ -417,12 +416,10 @@ @paste-from-buf ( size* -> ) ;paste-buf SWP2 OVR2 ADD2 SWP2 ( limit* start* ) &loop ( limit* pos* ) -( LDAk .Console/w DEO INC2 ( limit* pos+1* ) ) LDAk .Console/proc-put DEO INC2 ( limit* pos+1* ) GTH2k ?&loop POP2 POP2 JMP2r @bracket-paste ( c^ -> ) -( .Console/w STH ) .Console/proc-put STH #1b STHkr DEO LIT "[ STHkr DEO @@ -638,7 +635,6 @@ .Controller/key DEI DUP #08 NEQ ?&done POP #7f ( send DEL instead of BS ) -( &done .Console/w DEO BRK ) &done .Console/proc-put DEO BRK @ctrl ( -> is-down? ) .Controller/button DEI #01 AND JMP2r @@ -646,10 +642,8 @@ ( alt-XYZ emits ESC and then emits XYZ ) @on-alt-key ( -> BRK ) -( #1b .Console/w DEO ) #1b .Console/proc-put DEO ctrl ?on-ctrl-key -( .Controller/key DEI .Console/w DEO BRK ) .Controller/key DEI .Console/proc-put DEO BRK ( control seqs: ) @@ -685,7 +679,6 @@ &rs #1e !&done &us #1f !&done &c1 LIT "@ SUB -( &done .Console/w DEO BRK ) &done .Console/proc-put DEO BRK @on-read-priv ( -> BRK ) @@ -911,15 +904,11 @@ @dsr ( n* -> ) #0006 NEQ2 ?&done -( #1b .Console/w DEO ) #1b .Console/proc-put DEO -( LIT "[ .Console/w DEO ) LIT "[ .Console/proc-put DEO .cur-y LDZ2 INC2 emit-dec2 -( LIT "; .Console/w DEO ) LIT "; .Console/proc-put DEO .cur-x LDZ2 INC2 emit-dec2 -( LIT "R .Console/w DEO ) LIT "R .Console/proc-put DEO &done BRK @@ -1300,14 +1289,21 @@ ( emit a signed short as a decimal ) @emit-sdec2 ( n* -> ) - DUP2k #1f SFT2 EQUk ?&s LIT2 "- 18 DEO + DUP2k #1f SFT2 EQUk ?&s LIT "- .Console/proc-put DEO &s MUL2 SUB2 ( fall-through to emit-dec2 ) ( emit an unsigned short as a decimal ) @emit-dec2 ( n* -> ) - LITr ff00 &read #000a DIV2k STH2k MUL2 SUB2 STH2r INCr ORAk ?&read - POP2 &write NIP #30 ADD #18 DEO OVRr ADDr STHkr ?&write - POP2r JMP2r + LIT2r ff00 ( n* [ff^ 0^] ) + &read ( ... x* ) + #000a DIV2k STH2k ( x* 10* x/10* [ff^ i^ x/10*] ) + MUL2 SUB2 STH2r ( x%10* x/10* [ff^ i^] ) + INCr ORAk ?&read ( x%10* x/10* [ff^ i+1^] ) + POP2 ( x0* ... xn* [ff^ i+1^] ) + &write + NIP #30 ADD .Console/proc-put DEO ( x0* ... xn-1* [ff^ j^] ) + OVRr ADDr STHkr ?&write ( x* ... xn-1* [ff^ j-1^] ) + POP2r JMP2r ( ) @debug-log "debug_term.log 00 @scratch $40 &pos $2