Getting Started
You should download 3000shell.c on your openstack instance (or a Ubuntu Linux 21.04 system or similar). Compile it using the command
gcc -O -g -Wall 3000shell.c -o 3000shell
Standard I/O
Recall that the shell is a command interpreter (program). In comparison, a terminal is a device used to enter data into and display data from a computer. It used to be physical – teletypes, or “TTY”‘s. Today they are virtual – pseudoterminals, pseudo teletypes, or pts’s. /dev/pts/1 is a pts for example. By themselves, a pts does nothing; however, they allow any program to appear as a virtual teletype to any other program. (To learn more, man ptmx. However, this goes into more detail than you need for this tutorial.)
When you ssh to a system interactively, the remote ssh process uses a pseudo tty to connect network I/O with the programs you run at the command line. In this context you can thus think of ssh (plus a pseudo tty) as an adaptor that converts network traffic into terminal-like I/O.
Standard input, (e.g., /dev/stdin), output (e.g., /dev/stdout) and error (/dev/stderr) are just references to file descriptors 0, 1, and 2 for the current process. They can refer to basically any file. If a process is run in a terminal, stdin, stdout, and stderr are set to refer to the terminal’s I/O channels via a tty or pts.
Shells provide easy interfaces for changing these file descriptors. For example:
ls > ls.log
Will redirect ls’s standard output to the file ls.log. Similarly,
bc -l < math.txt will take math input from /tmp/math.txt and output it to the current terminal. We can also use the pipe operator to direct the standard output of one program to the standard input of another: ls -l | less (This is really good if you have lots of files in a directory.) Part of the magic of UNIX is that it allows problems to be solved by combining multiple programs together. For example, to get a list of unique words in a file, you can do something like the following: tr ' \t' '\n' < file.txt | tr -d ',.!()?-' | sort | uniq | less In summary, shells run in terminals, and the terminal interface is implemented by teletype-like devices, either a "tty" or "pts" device. Standard in, out, and error are abstractions for interactions with such terminals or, with I/O redirection, other arbitrary files. Tasks/Questions The purpose of the following questions and tasks is to help you understand how 3000shell works. At the end of this you should have an understanding of every function and every line of the code. If you understand 3000shell, then you understand the basics of all UNIX shells. Your understanding of the code will be tested later, so use this opportunity to dive deep into the code. These tasks and questions should help you build up a mental model of how 3000shell works. Compile and run 3000shell.c Try running programs in the background using & after commands entered in 3000shell. What happens to the input and output of the program? Try this for simple programs like ls and bc. Then, try it for more complex interactive programs such as nano and top. You may have trouble interacting with the shell after running programs in the background. How can you recover from such a situation? Run 3000shell under gdb and observe all the system calls it makes using catch syscall (after setting a breakpoint on main so you don't see the syscalls when it starts). Where does each system call happen? In what context (source and assembly)? Consider both parent and child processes (by setting follow-fork-mode to parent and child). Compare with the output of strace (e.g., run strace -fqo 3000shell.log ./3000shell). 3000shell implements a simple form of output redirection. What syntax should you use to redirect standard output to a file? Why are lines 207-210 there (the check for pid == -1)? Make find_binary show every attempt to find a binary. Make the shell output "Ouch!" when you send it a SIGUSR1 signal. Delete line 324 (SA_RESTART). How does the behavior of 3000shell change? Replace the use of find_env() with getenv(). How do their interfaces differ? Make plist output the parent process id for each process, e.g. "5123 ls (5122)". Pay attention to the stat and status files in the per-process directories in /proc. Implement redirection of standard error Implement redirection of standard out for plist() (the same as if it was an external command). Implement a built-in 3000kill command that works like the standard kill command. (What system call/library call is used to send signals?) Code