kern/user/shell.c
2025-03-31 12:41:04 -04:00

346 lines
8 KiB
C

#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[N_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);
}