summaryrefslogtreecommitdiff
path: root/user/shell.c
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-03-25 17:36:52 -0400
committerFreya Murphy <freya@freyacat.org>2025-03-25 17:38:22 -0400
commit6af21e6a4f2251e71353562d5df7f376fdffc270 (patch)
treede20c7afc9878422c81e34f30c6b010075e9e69a /user/shell.c
downloadcomus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.gz
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.tar.bz2
comus-6af21e6a4f2251e71353562d5df7f376fdffc270.zip
initial checkout from wrc
Diffstat (limited to 'user/shell.c')
-rw-r--r--user/shell.c343
1 files changed, 343 insertions, 0 deletions
diff --git a/user/shell.c b/user/shell.c
new file mode 100644
index 0000000..5412033
--- /dev/null
+++ b/user/shell.c
@@ -0,0 +1,343 @@
+#include <common.h>
+
+// should we keep going?
+static bool_t time_to_stop = false;
+
+// number of spawned but uncollected children
+static int children = 0;
+
+/*
+** For the test programs in the baseline system, command-line arguments
+** follow these rules. The first two entries are as follows:
+**
+** argv[0] the name used to "invoke" this process
+** argv[1] the "character to print" (identifies the process)
+**
+** Most user programs have one or more additional arguments.
+**
+** See the comment at the beginning of each user-code source file for
+** information on the argument list that code expects.
+*/
+
+/*
+** "Spawn table" process entry. Similar to that in init.c,
+** except this one has no place to store the PID of the child.
+*/
+typedef struct proc_s {
+ uint_t index; // process table index
+ int8_t prio; // process priority
+ char select[3]; // identifying character, NUL, extra
+ char *args[MAX_ARGS]; // argument vector strings
+} proc_t;
+
+/*
+** Create a spawn table entry for a process with a string literal
+** as its argument buffer. We rely on the fact that the C standard
+** ensures our array of pointers will be filled out with NULLs
+*/
+#define PROCENT(e, p, s, ...) { e, p, s, { __VA_ARGS__ , NULL } }
+
+// sentinel value for the end of the table - must be updated
+// if you have more than 90,210 user programs in the table
+#define TBLEND 90210
+
+/*
+** The spawn table contains entries for processes that are started
+** by the shell.
+*/
+static proc_t spawn_table[] = {
+
+ // Users A-C each run ProgABC, which loops printing its character
+#if defined(SPAWN_A)
+ PROCENT( ProgABC, PRIO_STD, "A", "userA", "A", "30" ),
+#endif
+#if defined(SPAWN_B)
+ PROCENT( ProgABC, PRIO_STD, "B", "userB", "B", "30" ),
+#endif
+#if defined(SPAWN_C)
+ PROCENT( ProgABC, PRIO_STD, "C", "userC", "C", "30" ),
+#endif
+
+ // Users D and E run ProgDE, which is like ProgABC but doesn't exit()
+#if defined(SPAWN_D)
+ PROCENT( ProgDE, PRIO_STD, "D", "userD", "D", "20" ),
+#endif
+#if defined(SPAWN_E)
+ PROCENT( ProgDE, PRIO_STD, "E", "userE", "E", "20" ),
+#endif
+
+ // Users F and G run ProgFG, which sleeps between write() calls
+#if defined(SPAWN_F)
+ PROCENT( ProgFG, PRIO_STD, "F", "userF", "F", "20" ),
+#endif
+#if defined(SPAWN_G)
+ PROCENT( ProgFG, PRIO_STD, "G", "userG", "G", "10" ),
+#endif
+
+ // User H tests reparenting of orphaned children
+#if defined(SPAWN_H)
+ PROCENT( ProgH, PRIO_STD, "H", "userH", "H", "4" ),
+#endif
+
+ // User I spawns several children, kills one, and waits for all
+#if defined(SPAWN_I)
+ PROCENT( ProgI, PRIO_STD, "I", "userI", "I" ),
+#endif
+
+ // User J tries to spawn 2 * N_PROCS children
+#if defined(SPAWN_J)
+ PROCENT( ProgJ, PRIO_STD, "J", "userJ", "J" ),
+#endif
+
+ // Users K and L iterate spawning userX and sleeping
+#if defined(SPAWN_K)
+ PROCENT( ProgKL, PRIO_STD, "K", "userK", "K", "8" ),
+#endif
+#if defined(SPAWN_L)
+ PROCENT( ProgKL, PRIO_STD, "L", "userL", "L", "5" ),
+#endif
+
+ // Users M and N spawn copies of userW and userZ via ProgMN
+#if defined(SPAWN_M)
+ PROCENT( ProgMN, PRIO_STD, "M", "userM", "M", "5", "f" ),
+#endif
+#if defined(SPAWN_N)
+ PROCENT( ProgMN, PRIO_STD, "N", "userN", "N", "5", "t" ),
+#endif
+
+ // There is no user O
+
+ // User P iterates, reporting system time and stats, and sleeping
+#if defined(SPAWN_P)
+ PROCENT( ProgP, PRIO_STD, "P", "userP", "P", "3", "2" ),
+#endif
+
+ // User Q tries to execute a bad system call
+#if defined(SPAWN_Q)
+ PROCENT( ProgQ, PRIO_STD, "Q", "userQ", "Q" ),
+#endif
+
+ // User R reports its PID, PPID, and sequence number; it
+ // calls fork() but not exec(), with each child getting the
+ // next sequence number, to a total of five copies
+#if defined(SPAWN_R)
+ PROCENT( ProgR, PRIO_STD, "R", "userR", "R", "20", "1" ),
+#endif
+
+ // User S loops forever, sleeping 13 sec. on each iteration
+#if defined(SPAWN_S)
+ PROCENT( ProgS, PRIO_STD, "S", "userS", "S", "13" ),
+#endif
+
+ // Users T-V run ProgTUV(); they spawn copies of userW
+ // User T waits for any child
+ // User U waits for each child by PID
+ // User V kills each child
+#if defined(SPAWN_T)
+ PROCENT( ProgTUV, PRIO_STD, "T", "userT", "T", "6", "w" ),
+#endif
+#if defined(SPAWN_U)
+ PROCENT( ProgTUV, PRIO_STD, "U", "userU", "U", "6", "W" ),
+#endif
+#if defined(SPAWN_V)
+ PROCENT( ProgTUV, PRIO_STD, "V", "userV", "V", "6", "k" ),
+#endif
+
+ // a dummy entry to use as a sentinel
+ { TBLEND }
+
+ // these processes are spawned by the ones above, and are never
+ // spawned directly.
+
+ // PROCENT( ProgW, PRIO_STD, "?", "userW", "W", "20", "3" ),
+ // PROCENT( ProgX, PRIO_STD, "?", "userX", "X", "20" ),
+ // PROCENT( ProgY, PRIO_STD, "?", "userY", "Y", "10" ),
+ // PROCENT( ProgZ, PRIO_STD, "?", "userZ", "Z", "10" )
+};
+
+/*
+** usage function
+*/
+static void usage( void ) {
+ swrites( "\nTests - run with '@x', where 'x' is one or more of:\n " );
+ proc_t *p = spawn_table;
+ while( p->index != TBLEND ) {
+ swritech( ' ' );
+ swritech( p->select[0] );
+ }
+ swrites( "\nOther commands: @* (all), @h (help), @x (exit)\n" );
+}
+
+/*
+** run a program from the program table, or a builtin command
+*/
+static int run( char which ) {
+ char buf[128];
+ register proc_t *p;
+
+ if( which == 'h' ) {
+
+ // builtin "help" command
+ usage();
+
+ } else if( which == 'x' ) {
+
+ // builtin "exit" command
+ time_to_stop = true;
+
+ } else if( which == '*' ) {
+
+ // torture test! run everything!
+ for( p = spawn_table; p->index != TBLEND; ++p ) {
+ int status = spawn( p->index, p->args );
+ if( status > 0 ) {
+ ++children;
+ }
+ }
+
+ } else {
+
+ // must be a single test; find and run it
+ for( p = spawn_table; p->index != TBLEND; ++p ) {
+ if( p->select[0] == which ) {
+ // found it!
+ int status = spawn( p->index, p->args );
+ if( status > 0 ) {
+ ++children;
+ }
+ return status;
+ }
+ }
+
+ // uh-oh, made it through the table without finding the program
+ sprint( buf, "shell: unknown cmd '%c'\n", which );
+ swrites( buf );
+ usage();
+ }
+
+ return 0;
+}
+
+/**
+** edit - perform any command-line editing we need to do
+**
+** @param line Input line buffer
+** @param n Number of valid bytes in the buffer
+*/
+static int edit( char line[], int n ) {
+ char *ptr = line + n - 1; // last char in buffer
+
+ // strip the EOLN sequence
+ while( n > 0 ) {
+ if( *ptr == '\n' || *ptr == '\r' ) {
+ --n;
+ } else {
+ break;
+ }
+ }
+
+ // add a trailing NUL byte
+ if( n > 0 ) {
+ line[n] = '\0';
+ }
+
+ return n;
+}
+
+/**
+** shell - extremely simple shell for spawning test programs
+**
+** Scheduled by _kshell() when the character 'u' is typed on
+** the console keyboard.
+*/
+USERMAIN( main ) {
+
+ // keep the compiler happy
+ (void) argc;
+ (void) argv;
+
+ // report that we're up and running
+ swrites( "Shell is ready\n" );
+
+ // print a summary of the commands we'll accept
+ usage();
+
+ // loop forever
+ while( !time_to_stop ) {
+ char line[128];
+ char *ptr;
+
+ // the shell reads one line from the keyboard, parses it,
+ // and performs whatever command it requests.
+
+ swrites( "\n> " );
+ int n = read( CHAN_SIO, line, sizeof(line) );
+
+ // shortest valid command is "@?", so must have 3+ chars here
+ if( n < 3 ) {
+ // ignore it
+ continue;
+ }
+
+ // edit it as needed; new shortest command is 2+ chars
+ if( (n=edit(line,n)) < 2 ) {
+ continue;
+ }
+
+ // find the '@'
+ int i = 0;
+ for( ptr = line; i < n; ++i, ++ptr ) {
+ if( *ptr == '@' ) {
+ break;
+ }
+ }
+
+ // did we find an '@'?
+ if( i < n ) {
+
+ // yes; process any commands that follow it
+ ++ptr;
+
+ for( ; *ptr != '\0'; ++ptr ) {
+ char buf[128];
+ int pid = run( *ptr );
+
+ if( pid < 0 ) {
+ // spawn() failed
+ sprint( buf, "+++ Shell spawn %c failed, code %d\n",
+ *ptr, pid );
+ cwrites( buf );
+ }
+
+ // should we end it all?
+ if( time_to_stop ) {
+ break;
+ }
+ } // for
+
+ // now, wait for all the spawned children
+ while( children > 0 ) {
+ // wait for the child
+ int32_t status;
+ char buf[128];
+ int whom = waitpid( 0, &status );
+
+ // figure out the result
+ if( whom == E_NO_CHILDREN ) {
+ break;
+ } else if( whom < 1 ) {
+ sprint( buf, "shell: waitpid() returned %d\n", whom );
+ } else {
+ --children;
+ sprint( buf, "shell: PID %d exit status %d\n",
+ whom, status );
+ }
+ // report it
+ swrites( buf );
+ }
+ } // if i < n
+ } // while
+
+ cwrites( "!!! shell exited loop???\n" );
+ exit( 1 );
+}