622 lines
30 KiB
Markdown
622 lines
30 KiB
Markdown
# Homework 4 Scripting Language - CSE 320 - Spring 2022
|
|
#### Professor Eugene Stark
|
|
|
|
### **Due Date: Friday 4/15/2022 @ 11:59pm**
|
|
|
|
## Introduction
|
|
|
|
The goal of this assignment is to become familiar with low-level Unix/POSIX system
|
|
calls related to processes, signal handling, files, and I/O redirection.
|
|
You will implement an interpreter, called `mush`, for a simple scripting language
|
|
that is capable of managing multiple concurrently executing "jobs".
|
|
|
|
### Takeaways
|
|
|
|
After completing this assignment, you should:
|
|
|
|
* Understand process execution: forking, executing, and reaping.
|
|
* Understand signal handling.
|
|
* Understand the use of "dup" to perform I/O redirection.
|
|
* Have a more advanced understanding of Unix commands and the command line.
|
|
* Have gained experience with C libraries and system calls.
|
|
* Have enhanced your C programming abilities.
|
|
|
|
## Hints and Tips
|
|
|
|
* We **strongly recommend** that you check the return codes of **all** system calls
|
|
and library functions. This will help you catch errors.
|
|
* **BEAT UP YOUR OWN CODE!** Use a "monkey at a typewriter" approach to testing it
|
|
and make sure that no sequence of operations, no matter how ridiculous it may
|
|
seem, can crash the program.
|
|
* Your code should **NEVER** crash, and we will deduct points every time your
|
|
program crashes during grading. Especially make sure that you have avoided
|
|
race conditions involving process termination and reaping that might result
|
|
in "flaky" behavior. If you notice odd behavior you don't understand:
|
|
**INVESTIGATE**.
|
|
* You should use the `debug` macro provided to you in the base code.
|
|
That way, when your program is compiled without `-DDEBUG`, all of your debugging
|
|
output will vanish, preventing you from losing points due to superfluous output.
|
|
|
|
> :nerd: When writing your program, try to comment as much as possible and stay
|
|
> consistent with code formatting. Keep your code organized, and don't be afraid
|
|
> to introduce new source files if/when appropriate.
|
|
|
|
### Reading Man Pages
|
|
|
|
This assignment will involve the use of many system calls and library functions
|
|
that you probably haven't used before.
|
|
As such, it is imperative that you become comfortable looking up function
|
|
specifications using the `man` command.
|
|
|
|
The `man` command stands for "manual" and takes the name of a function or command
|
|
(programs) as an argument.
|
|
For example, if I didn't know how the `fork(2)` system call worked, I would type
|
|
`man fork` into my terminal.
|
|
This would bring up the manual for the `fork(2)` system call.
|
|
|
|
> :nerd: Navigating through a man page once it is open can be weird if you're not
|
|
> familiar with these types of applications.
|
|
> To scroll up and down, you simply use the **up arrow key** and **down arrow key**
|
|
> or **j** and **k**, respectively.
|
|
> To exit the page, simply type **q**.
|
|
> That having been said, long `man` pages may look like a wall of text.
|
|
> So it's useful to be able to search through a page.
|
|
> This can be done by typing the **/** key, followed by your search phrase,
|
|
> and then hitting **enter**.
|
|
> Note that man pages are displayed with a program known as `less`.
|
|
> For more information about navigating the `man` pages with `less`,
|
|
> run `man less` in your terminal.
|
|
|
|
Now, you may have noticed the `2` in `fork(2)`.
|
|
This indicates the section in which the `man` page for `fork(2)` resides.
|
|
Here is a list of the `man` page sections and what they are for.
|
|
|
|
| Section | Contents |
|
|
| ----------------:|:--------------------------------------- |
|
|
| 1 | User Commands (Programs) |
|
|
| 2 | System Calls |
|
|
| 3 | C Library Functions |
|
|
| 4 | Devices and Special Files |
|
|
| 5 | File Formats and Conventions |
|
|
| 6 | Games, et al |
|
|
| 7 | Miscellanea |
|
|
| 8 | System Administration Tools and Daemons |
|
|
|
|
From the table above, we can see that `fork(2)` belongs to the system call section
|
|
of the `man` pages.
|
|
This is important because there are functions like `printf` which have multiple
|
|
entries in different sections of the `man` pages.
|
|
If you type `man printf` into your terminal, the `man` program will start looking
|
|
for that name starting from section 1.
|
|
If it can't find it, it'll go to section 2, then section 3 and so on.
|
|
However, there is actually a Bash user command called `printf`, so instead of getting
|
|
the `man` page for the `printf(3)` function which is located in `stdio.h`,
|
|
we get the `man` page for the Bash user command `printf(1)`.
|
|
If you specifically wanted the function from section 3 of the `man` pages,
|
|
you would enter `man 3 printf` into your terminal.
|
|
|
|
> :scream: Remember this: **`man` pages are your bread and butter**.
|
|
> Without them, you will have a very difficult time with this assignment.
|
|
|
|
## Getting Started
|
|
|
|
Fetch and merge the base code for `hw4` as described in `hw1`.
|
|
You can find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw4
|
|
|
|
Here is the structure of the base code:
|
|
<pre>
|
|
.
|
|
├── .gitlab-ci.yml
|
|
└── hw4
|
|
├── demo
|
|
│ └── mush
|
|
├── include
|
|
│ ├── debug.h
|
|
│ ├── mush.h
|
|
│ ├── mush.tab.h
|
|
│ └── syntax.h
|
|
├── Makefile
|
|
├── rsrc
|
|
│ ├── bg_test.mush
|
|
│ ├── cancel_test.mush
|
|
│ ├── delete_test.mush
|
|
│ ├── fg_test.mush
|
|
│ ├── goto_test.mush
|
|
│ ├── list_test.mush
|
|
│ ├── loop1.mush
|
|
│ ├── loop2.mush
|
|
│ ├── pause_test.mush
|
|
│ ├── pipeline_test.mush
|
|
│ ├── run_test.mush
|
|
│ ├── stop_test.mush
|
|
│ └── wait_test.mush
|
|
├── src
|
|
│ ├── execution.c
|
|
│ ├── jobs.c
|
|
│ ├── main.c
|
|
│ ├── mush.lex.c
|
|
│ ├── mush.tab.c
|
|
│ ├── program.c
|
|
│ ├── store.c
|
|
│ └── syntax.c
|
|
└── tests
|
|
└── base_tests.c
|
|
</pre>
|
|
|
|
If you run `make`, the code should compile correctly, resulting in an
|
|
executable `bin/mush`. If you run this program, it doesn't do very
|
|
much, because there are a number of pieces that you have to fill in.
|
|
|
|
## `Mush`: Overview
|
|
|
|
The `mush` language is a simple programming language which was roughly
|
|
inspired by the classic programming language BASIC.
|
|
A `mush` program consists of a set of *statements*, with one statement
|
|
per line of program text.
|
|
The syntax of statements is given by the following context-free grammar:
|
|
|
|
```
|
|
<statement> ::= list
|
|
| delete <lineno> , <lineno>
|
|
| run
|
|
| cont
|
|
| <lineno> stop
|
|
| <optional_lineno> set <name> = <expr>
|
|
| <optional_lineno> unset <name>
|
|
| <optional_lineno> goto <lineno>
|
|
| <optional_lineno> if <expr> goto <lineno>
|
|
| <optional_lineno> source <file>
|
|
| <optional_lineno> <pipeline>
|
|
| <optional_lineno> <pipeline> &
|
|
| <optional_lineno> wait <expr>
|
|
| <optional_lineno> poll <expr>
|
|
| <optional_lineno> cancel <expr>
|
|
| <optional_lineno> pause
|
|
```
|
|
|
|
Some kinds of statements have required *line numbers*, other kinds of
|
|
statements have no line numbers, and for some statements the line numbers
|
|
are optional. In general, when the `mush` interpreter reads a statement
|
|
without a line number, it is executed immediately, whereas when it reads
|
|
a statement with a line number it is not immediately executed, but instead
|
|
is saved in the *program store*.
|
|
The program store maintains a set of statements, each of which has a
|
|
line number. In addition, the program store maintains a *program counter*,
|
|
which keeps track of the next statement to be executed when `mush` is
|
|
in "run mode".
|
|
|
|
The `list`, `delete`, `run`, and `cont` statements have no line numbers,
|
|
and so can only be executed immediately. The `list` statement causes
|
|
`mush` to list the contents of the program store. The `delete` statement
|
|
deletes statements from the program store whose line numbers lie within
|
|
a specified range. The `run` statement causes `mush` to reset the program
|
|
counter to the lowest-numbered statement in the program store and to begin
|
|
running automatically. The `cont` statement causes `mush` to continue
|
|
automatic execution that has been stopped by the execution of a `stop` statement.
|
|
Since a `stop` statement has a required line number, such a statement
|
|
can never be executed immediately, but rather only from the program store
|
|
during automatic execution.
|
|
|
|
The remaining statements have optional line numbers, and so can be executed
|
|
either immediately or from the program store.
|
|
The `set` statement is used to set the value of a variable to be the result
|
|
of evaluating an expression.
|
|
The `unset` statement is used to un-set the value of a variable, leaving it
|
|
with no value.
|
|
The `goto` statement resets the program counter so that the next statement to
|
|
be executed is the one with the specified line number.
|
|
The `if` statement causes control to be transferred conditionally to the
|
|
statement with the specified line number, if the specified expression evaluates
|
|
to a non-zero number.
|
|
The `source` statement causes `mush` to interpret the statements in the specified
|
|
file before continuing on with the current program.
|
|
|
|
A statement can also consist of a *pipeline*, to be executed either in the
|
|
"foreground" or in the "background". A pipeline consists of a sequence of
|
|
*commands*, separated by vertical bars (`|`), with possible
|
|
*input redirection*, specified using `<` followed by a filename,
|
|
*output redirection*, specified using `>` followed by a filename,
|
|
or *output capturing*, specified using `>@`.
|
|
A pipeline is executed by `mush` in much the same fashion as it would be
|
|
executed by a shell such as `bash`: a group of processes is created to run
|
|
the commands concurrently, with the output of each command in the pipeline
|
|
redirected to become the input of the next command in the pipeline.
|
|
If input redirection is specified, then the first command in the pipeline
|
|
has its input redirected from the specified file.
|
|
If output redirection is specified, then the last command in the pipeline
|
|
has its output redirected to the specified file.
|
|
If output capturing is specified, then the output of the last command in the
|
|
pipeline is read by the `mush` interpreter itself, which makes it available
|
|
as the value of a variable that can be referenced by the execution of
|
|
subsequent statements in the program.
|
|
|
|
Each command in a pipeline consists of a nonempty sequence of *args*,
|
|
where the first arg in the command specifies the name of a program to be run
|
|
and the remaining args are supplied to the program as part of its argument
|
|
vector. In `mush`, an arg takes the form of an *atomic expression*,
|
|
which can be either a *string variable*, a *numeric variable*,
|
|
a *string literal*, or an arbitrary expression enclosed in parentheses.
|
|
|
|
The syntax of pipelines, commands, and args is given by the following grammar:
|
|
|
|
```
|
|
<pipeline> ::= <command_list>
|
|
| <pipeline> < <file>
|
|
| <pipeline> > <file>
|
|
| <pipeline> >@
|
|
|
|
<command_list> ::= <command>
|
|
| <command> | <command_list>
|
|
|
|
<command> ::= <arg_list>
|
|
|
|
<arg_list> ::= <arg>
|
|
| <arg> <arg_list>
|
|
|
|
<arg> ::= <atomic_expr>
|
|
|
|
<atomic_expr> ::= <literal_string>
|
|
| <numeric_var>
|
|
| <string_var>
|
|
| ( <expr> )
|
|
```
|
|
|
|
`Mush` supports *expressions* built up from *string variables*,
|
|
*numeric variables*, and *literal strings*, using various unary
|
|
and binary operators, as given by the following grammar:
|
|
|
|
```
|
|
<expr> ::= <atomic_expr>
|
|
| <expr> == <expr>
|
|
| <expr> < <expr>
|
|
| <expr> > <expr>
|
|
| <expr> <= <expr>
|
|
| <expr> >= <expr>
|
|
| <expr> && <expr>
|
|
| <expr> || <expr>
|
|
| ! <expr>
|
|
| <expr> + <expr>
|
|
| <expr> - <expr>
|
|
| <expr> * <expr>
|
|
| <expr> / <expr>
|
|
| <expr> % <expr>
|
|
```
|
|
|
|
A *string variable* consists of a `$` symbol followed by a *name*,
|
|
which is a sequence of alphanumeric characters and underscores,
|
|
beginning with an alphabetic character or an underscore.
|
|
A *numeric variable* is similar, except it uses a `#` symbol in place
|
|
of the `$`.
|
|
A *literal string* is either a *number*, which consists of digits,
|
|
a *word*, which consists of non-whitespace characters which do not otherwise
|
|
have some special meaning to `mush`, or a *quoted string*, which is enclosed
|
|
in double quotes and which may contain special characters.
|
|
A *filename* that appears in the input or output redirection part of a
|
|
pipeline is permitted to be either a word or a quoted string.
|
|
This allows simple filenames without special characters to be specified
|
|
without quotes. Filenames that contain special characters (including `/`)
|
|
must be specified as quoted strings.
|
|
|
|
Here is a simple example of a `mush` program:
|
|
|
|
```
|
|
10 echo "Let's start!"
|
|
20 set x = 0
|
|
30 date >@
|
|
40 set d = $OUTPUT
|
|
50 echo The date and time is: $d
|
|
60 sleep 1
|
|
70 set x = #x + 1
|
|
80 if #x <= 10 goto 30
|
|
90 stop
|
|
```
|
|
|
|
The remaining types of statements that `mush` understands have to do with
|
|
the manipulation of concurrently executing *jobs*.
|
|
Each time `mush` executes a pipeline statement, a new job is created.
|
|
`Mush` keeps track of the existing jobs in a *jobs table*.
|
|
Each job in the jobs table has an associated *job ID*, which is a nonnegative
|
|
integer that uniquely identifies the job.
|
|
After starting a job, `mush` sets the value of the `JOB` variable to be
|
|
the job ID of the job that was started.
|
|
For a foreground job, `mush` waits for the job to complete and then sets the
|
|
value of the `STATUS` variable to be the exit status of the job.
|
|
`Mush` then *expunges* the job from the jobs table.
|
|
For a background job, `mush` does not wait for the job to complete, but instead
|
|
continues execution. At a later time, a `wait` statement can be executed
|
|
in order to wait for the background job to complete, to collect its
|
|
exit status, and to expunge the job. Alternatively, a `poll` statement can
|
|
be executed to check whether the job has terminated without waiting if it
|
|
has not. If the job has terminated, then the exit status is collected and
|
|
the job is expunged with a `poll` statement, similarly to a `wait` statement.
|
|
Execution of a `cancel` statement makes an attempt to cancel a specified
|
|
background job. A `SIGKILL` signal is sent to the process group to which the
|
|
processes in the jobs belong. If the processes have not already terminated,
|
|
then they will terminate upon receiving the `SIGKILL` signal.
|
|
A `wait` statement may be used to wait for this termination to occur and
|
|
to expunge the canceled job from the jobs table.
|
|
Note that the `wait`, `poll`, and `cancel` statements all permit the use of an
|
|
arbitrary expression to specify the job ID.
|
|
|
|
The final kind of statement that `mush` supports is the `pause` statement.
|
|
This statement causes execution to be suspended pending the receipt of a signal
|
|
that might indicate a change in the status of jobs in the jobs table.
|
|
When such a signal is received, execution continues.
|
|
This way, `mush` can wait for a change in job status without consuming an
|
|
excessive amount of CPU time.
|
|
|
|
### Demonstration version
|
|
|
|
To help you understand how `mush` is intended to behave, I have provided a
|
|
demonstration version as a binary with the assignment basecode.
|
|
This can be found as the executable `demo/mush`.
|
|
This demonstration version is intended as an aid to understanding only;
|
|
it should not be regarded as a specification of what you are to do.
|
|
It is likely that the demonstration version has some bugs or that its
|
|
behavior does not conform in some respects to what is stated here and in
|
|
the specifications in the basecode.
|
|
|
|
## Tasks to be Completed
|
|
|
|
Included in the basecode for this assignment is an implementation of a
|
|
parser for `mush` statements and the basic control structure of the
|
|
`mush` interpreter. A number of modules have been left for you to
|
|
implement. These are:
|
|
|
|
* A *program store* module, which is used to hold a `mush` program
|
|
and manage the program counter.
|
|
|
|
* A *data store* module, which is used to keep track of the current values
|
|
of the variables used in a `mush` program.
|
|
|
|
* A *jobs* module, which keeps track of the currently executing jobs using
|
|
a jobs table, and implements job manipulation functions used to execute
|
|
and wait for pipelines, collect exit status, perform input and output
|
|
redirection, and implement the output capture feature of `mush`.
|
|
|
|
### The Program Store Module
|
|
|
|
Specifications and stubs for the functions that make up the program store module
|
|
of `mush` are given in the source file `src/program.c`.
|
|
Implementation of these functions from the specifications should be relatively
|
|
straightforward, so I will not spend additional space on them here.
|
|
The choice of data structure used to represent the program store has been left
|
|
to you.
|
|
Pay close attention to what the specifications say about who has the responsibility
|
|
for freeing the memory associated with statements in the store.
|
|
A correct implementation should not leak memory associated with program statements,
|
|
and of course it should not suffer from double free bugs and the like.
|
|
|
|
### The Data Store Module
|
|
|
|
Specifications and stubs for the functions that make up the data store module
|
|
of `mush` are given in the source file `src/store.c`.
|
|
Once again, I expect that implementation of these functions should be relatively
|
|
straightforward. As for the program store, the choice of data structure used
|
|
to implement the data store is for you to make and you should pay attention to
|
|
what the specifications say about who is responsible for freeing memory.
|
|
|
|
### The Jobs Module
|
|
|
|
Specifications and stubs for the functions that make up the jobs module
|
|
of `mush` are given in the source file `src/jobs.c`.
|
|
It is this module that is likely to be unfamiliar and to present some challenges
|
|
to you, so I am providing some additional guidance here.
|
|
|
|
* You will need to implement some form of "jobs table" in this module,
|
|
to keep track of the jobs that have been created but not yet expunged.
|
|
The data structure you use is up to you. If you find it convenient,
|
|
you may assume that at most `JOBS_MAX` jobs can exist at one time,
|
|
where `JOBS_MAX` is a C preprocessor symbol defined in `mush.h`.
|
|
Write your code so that it does not depend on a particular value for
|
|
`JOBS_MAX`; do not hard-code the value into your implementation.
|
|
|
|
* Your jobs module will need to make use of handlers for two types of signals.
|
|
The first is the `SIGCHLD` signal used to obtain notifications when a child
|
|
process terminates. This has been discussed in class and can also be found
|
|
in the textbook.
|
|
The second type of signal you will need to handle is the `SIGIO` signal used
|
|
to obtain notifications when a file descriptor is ready for reading.
|
|
This will be important to enable your program to capture output from
|
|
concurrently executing background jobs without the need to commit to waiting
|
|
for data from any one of them at any particular time. This is discussed
|
|
further below.
|
|
|
|
* For correct operation, your implementation will likely have to make use of
|
|
the `sigprocmask()` function to mask signals during times when a signal handler
|
|
should be prevented from running. You will likely also need to use the
|
|
`sigsuspend()` function under certain circumstances to await the arrival of a
|
|
signal.
|
|
|
|
* When executing a pipeline consisting of N commands, a total of N+1 processes
|
|
should be used. One of these processes, which we will call the pipeline
|
|
*leader*, should be the direct child of the main `mush` process.
|
|
The remaining `N` processes will be children of the leader process, and will
|
|
each execute one of the commands in the pipeline.
|
|
The leader process should set itself into a new process group using its own
|
|
process ID as the process group ID, and its `N` child processes should belong
|
|
to this process group. This is so that job cancellation can be performed by
|
|
sending just one `SIGKILL`, directed at the process group for the job.
|
|
The leader process should wait for and reap its `N` children before terminating.
|
|
The main `mush` process should use its `SIGCHLD` handler to receive notifications
|
|
about the termination of pipeline leader processes and to collect their
|
|
exit status.
|
|
|
|
* Besides the `fork()` system call used to create the processes, the creation of the pipeline
|
|
will involve the use of the `open()`, `pipe()`, and `dup2()` system calls to set up the pipes
|
|
and redirections, and the `execvp()` system call must be used to execute the individual
|
|
commands.
|
|
|
|
> **Important:** You **must** create the processes in a pipeline using calls to
|
|
> `fork()` and `execvp()`. You **must not** use the `system()` function, nor use any
|
|
> form of shell in order to create the pipeline, as the purpose of the assignment is
|
|
> to give you experience with using the system calls involved in doing this.
|
|
|
|
* Once having set up the pipeline, the pipeline leader will use `wait()` or `waitpid()`
|
|
to await the completion of the processes in the pipeline.
|
|
The leader process should wait for all of its children to terminate before
|
|
terminating itself. The leader should return the exit status of the process
|
|
running the last command in the pipeline as its own exit status, if that
|
|
process terminated normally. If the last process terminated with a signal,
|
|
then the leader should terminate via SIGABRT.
|
|
|
|
* The `pipe()` and `dup2()` system calls should be used to perform the input
|
|
and output redirection associated with a pipeline, as discussed in class and
|
|
in the textbook. Files used for input and output redirection should be opened
|
|
using the `open()` system call. For correct operation of a pipeline, care
|
|
should be taken while setting up the pipeline that each process makes sure to
|
|
`close()` pipe file descriptors that it does not use.
|
|
|
|
* The capturing of output from a pipeline by the main `mush` process is to be
|
|
accomplished as follows. Before forking the pipeline leader, a pipe should
|
|
be created to provide a way to redirect output from the last process in the
|
|
pipeline back to the main `mush` process. The redirection will be accomplished
|
|
using `dup2()` as usual. The main `mush` process will need to save the file
|
|
descriptor for the read side of the pipe in the jobs table along with other
|
|
state information from that job. Output from the pipeline will be collected
|
|
by the main `mush` process by reading from the read side of the pipe and
|
|
saving what is read in memory. Automatic dynamic allocation of however much
|
|
memory is required to hold the output can be accomplished by using the
|
|
`open_memstream()` function to obtain a `FILE` object to which the data can
|
|
be written.
|
|
|
|
The main technical issue involved in output capturing is how to arrange for
|
|
the main `mush` process to collect the output produced from multiple
|
|
concurrently executing pipelines, without having to block waiting for any one
|
|
of them to produce output at any given time. This can be done using so-called
|
|
*asynchronous I/O*. When the main `mush` process creates the pipe from which
|
|
it will read the captured data, it should perform the following system calls
|
|
(`readfd` is the file descriptor for the read side of the pipe):
|
|
|
|
```
|
|
fcntl(readfd, F_SETFL, O_NONBLOCK);
|
|
fcntl(readfd, F_SETFL, O_ASYNC);
|
|
fcntl(readfd, F_SETOWN, getpid());
|
|
```
|
|
|
|
The first of these calls enables *non-blocking I/O* on the file descriptor.
|
|
This means that an attempt to `read()` the file descriptor when no data is
|
|
available will not cause the main `mush` process to block (*i.e.* wait for
|
|
data to arrive); rather the `read()` will return immediately with an error
|
|
and `errno` set to `EWOULDBLK`.
|
|
The second call sets *asynchronous mode* on the file descriptor.
|
|
When this is set, the operating system kernel will send a `SIGIO` signal
|
|
whenever there has been a change in status of the file descriptor; for example,
|
|
whenever data becomes available for reading.
|
|
The third call is necessary to set the "ownership" of the file descriptor
|
|
to the main `mush` process, so that the kernel knows to which process
|
|
the `SIGIO` signals should be directed.
|
|
|
|
Once you have done this, then the main `mush` process can use a handler for
|
|
`SIGIO` signals to become notified when there is output that needs to be
|
|
captured. It can then poll each of the file descriptors from which output
|
|
is supposed to be captured, using `read()` to read input from each of them
|
|
and save it in memory, until `EWOULDBLK` indicates that there is no more data
|
|
currently available. This way, it can collect the captured output in a timely
|
|
fashion without getting "stuck" waiting for output that might take an
|
|
indefinite amount of time to arrive.
|
|
|
|
For more information, you will have to look at the man pages for the various
|
|
system calls involved, including `pipe()`, `dup2()`, `fcntl()`, `open()`, `read()`,
|
|
`signal()` (or `sigaction()`), `sigprocmask()`, and `sigsuspend()`.
|
|
|
|
## Using `gdb` to Debug Multi-process Programs
|
|
|
|
Although it gets harder to debug using `gdb` once multiple processes are involved,
|
|
there is some support for it. The `gdb` command `set follow-fork-mode parent`
|
|
causes `gdb` to follow the parent process after a `fork()` (this is the default).
|
|
Similarly, the command `set follow-fork-mode child` causes `gdb` to follow the child
|
|
process instead.
|
|
|
|
## Provided Components
|
|
|
|
### The `mush.h` Header File
|
|
|
|
The `mush.h` header file that we have provided gives function prototypes for
|
|
the functions that you are to implement, and contains a few other related
|
|
definitions. The actual specifications for the functions will be found
|
|
as comments attached to stubs for these functions in the various C source files.
|
|
|
|
> :scream: **Do not make any changes to `mush.h`. It will be replaced
|
|
> during grading, and if you change it, you will get a zero!**
|
|
|
|
### The `syntax.h` Header File
|
|
|
|
The `syntax.h` header file that we have provided defines the data structures
|
|
used to represent parsed `mush` statements. Mostly, you don't have to know
|
|
much about the details of these data structures, except, for example,
|
|
that you will need to be able to extract some information from them,
|
|
such as the pipeline from a foreground or background pipeline statement.
|
|
To avoid memory leaks, you will need to use the various `free_xxx()`
|
|
functions provided to free syntactic objects when they are no longer being used.
|
|
You will also need to use the function provided to make a copy of a pipeline
|
|
object in a certain situation -- see the specification for `jobs_run()` for
|
|
more information.
|
|
|
|
> :scream: **Do not make any changes to `syntax.h`. It will be replaced
|
|
> during grading, and if you change it, you will get a zero!**
|
|
|
|
### The `syntax.c` Source File
|
|
|
|
The `syntax.c` source file that we have provided contains the implementations
|
|
of the various functions for which prototypes are given in `syntax.h`.
|
|
|
|
> :scream: **Do not make any changes to `syntax.c`. It will be replaced
|
|
> during grading, and if you change it, you will get a zero!**
|
|
|
|
### The `mush.lex.c`, `mush.tab.c`, and `mush.tab.h` Files
|
|
|
|
The basecode provides a parser for the `mush` language. This parser is
|
|
implemented using the GNU `bison` parser generator. and the GNU `flex`
|
|
lexical analyzer generator. The `mush.lex.c`, `mush.tab.c`, and `mush.tab.h`
|
|
files are auto-generated files produced by the `bison` and `flex` programs.
|
|
|
|
> :scream: **None of these files should be changed or edited.
|
|
> Do *not* do the sloppy things that lots of people seem to do,
|
|
> namely, editing these files, reformatting them or otherwise mutating them,
|
|
> and then committing the changed results to `git`. You will regret it
|
|
> if you do this, and you have been duly warned!**
|
|
|
|
### The `demo/mush` Executable
|
|
|
|
The file `demo/mush` is an executable program that behaves more or less like
|
|
how your program should behave when it is finished.
|
|
|
|
> :scream: The behavior of the demo program should be regarded as an example
|
|
> implementation only, not a specification. If there should be any discrepancy
|
|
> between the behavior of the demo program and what it says either in this document
|
|
> or in the specifications in the header files, the latter should be regarded
|
|
> as authoritative.
|
|
|
|
### The `rsrc` Directory
|
|
|
|
The `rsrc` directory contains some sample `mush` scripts which I used while
|
|
writing the demo version. They were mostly designed very quickly to exercise
|
|
the basic features of `mush`, to verify that they worked to a first cut.
|
|
One way to run them is to type *e.g.* `source rsrc/xxx_test.mush` to the
|
|
`mush` prompt, to get it to read and execute the test.
|
|
If you have run one test and you want to run another, you should use the
|
|
`delete` command to clear any statements from the program store that might
|
|
have been left by the first test, otherwise they might interfere with the
|
|
new test.
|
|
|
|
### The `tests` Directory
|
|
|
|
The `tests` directory contains just one file, `base_tests.c`, which contains one
|
|
Criterion test that isn't very interesting. This file is basically just a
|
|
placeholder where you can put tests you might think of yourself.
|
|
|
|
## Hand-in instructions
|
|
As usual, make sure your homework compiles before submitting.
|
|
Test it carefully to be sure that doesn't crash or exhibit "flaky" behavior
|
|
due to race conditions.
|
|
Use `valgrind` to check for memory errors and leaks.
|
|
Besides `--leak-check=full`, also use the option `--track-fds=yes`
|
|
to check whether your program is leaking file descriptors because
|
|
they haven't been properly closed.
|
|
You might also want to look into the `valgrind` `--trace-children` and related
|
|
options.
|
|
|
|
Submit your work using `git submit` as usual.
|
|
This homework's tag is: `hw4`.
|