feat: added files

This commit is contained in:
Renge 2022-04-16 11:33:13 -04:00
parent 11f28060e7
commit a39e2d1aaf
14 changed files with 7040 additions and 0 deletions

20
course_tools/README.md Normal file
View File

@ -0,0 +1,20 @@
# CSE 320 Fall 2020 Course Tools
Run the script to install the 320 course tools and packages.
```
$ bash vm-setup.sh
```
The script will ask for sudo privileges to install packages.
**NOTE THIS TOOL RUNS APT UPGRADE**
Once the script has been run, read the comments at the end of the script
in order to install packages into the Sublime Text editor. The commented
commands in the script are an old version of an automatic installation
procedure, which doesn't work any more. What you should do instead is to
first manually install "Package Control", then "SublimeLinter", "SublimeLinter-gcc",
and "TrailingSpaces". Then, copy file `SublimeLinter.sublime-settings` to
directory `~/.config/sublime-text-3/Packages/User` as described in the
comments.

View File

@ -0,0 +1,15 @@
// SublimeLinter Settings - User
{
"delay":0.25,
"linters":{
"gcc":{
"disable":false,
"c_executable":"gcc",
"args":["-Wall"],
"I":[
"${file_path}/../include",
"${project_path}/include"
]
}
}
}

View File

@ -0,0 +1,132 @@
From ac0507b3f45fe58100b528baeb8ca04270b4a8ff Mon Sep 17 00:00:00 2001
From: "Franklin \"Snaipe\" Mathieu" <me@snai.pe>
Date: Mon, 23 Mar 2020 05:52:23 +0000
Subject: timeout-posix: fix race condition
The posix timeout code was racy -- if a timeout was created, and
cancelled before the watchdog had any chance to run (because the worker
would exit too quickly, or because the thread would not be scheduled
quickly enough). This, in turn, made the watchdog wait forever for the
timeout queue to be nonempty.
This fixes the race by preventing the watchdog from ever waiting for the
queue to fill up -- it's actually not possible for the queue to be
empty during initialization, because the watchdog thread will be made to
wait for the initialization lock to be released. This means that the
only time where the queue is empty is when the watchdog has been
started, but the worker already exited/the timeout was cancelled.
In addition, this fix simplifies slightly the way that the watchdog is
collected -- we no longer try to join the thread, but we make it
detached from the get go.
This addresses Snaipe/Criterion#345.
diff --git a/src/timeout-posix.c b/src/timeout-posix.c
index 53bd181..2e9a210 100644
--- a/src/timeout-posix.c
+++ b/src/timeout-posix.c
@@ -22,13 +22,13 @@
* THE SOFTWARE.
*/
#include <assert.h>
+#include <errno.h>
#include <pthread.h>
-#include <time.h>
+#include <signal.h>
#include <stdint.h>
#include <stdlib.h>
-#include <errno.h>
-#include <signal.h>
#include <string.h>
+#include <time.h>
#include "config.h"
#include "sandbox.h"
@@ -48,11 +48,9 @@ static struct {
int thread_active;
pthread_mutex_t sync;
pthread_cond_t cond;
- pthread_cond_t termcond;
} self = {
.sync = PTHREAD_MUTEX_INITIALIZER,
.cond = PTHREAD_COND_INITIALIZER,
- .termcond = PTHREAD_COND_INITIALIZER,
};
static int timespec_cmp(struct timespec *a, struct timespec *b)
@@ -96,8 +94,6 @@ static void to_timespec(double timeout, struct timespec *timeo)
static void *timeout_killer_fn(void *nil)
{
pthread_mutex_lock(&self.sync);
- while (!self.requests)
- pthread_cond_wait(&self.cond, &self.sync);
struct bxfi_timeout_request *req;
for (;;) {
@@ -125,7 +121,7 @@ static void *timeout_killer_fn(void *nil)
free(req);
}
end:
- pthread_cond_broadcast(&self.termcond);
+ self.thread_active = 0;
pthread_mutex_unlock(&self.sync);
return nil;
}
@@ -137,10 +133,6 @@ void bxfi_reset_timeout_killer(void)
memcpy(&self.sync, &mutex, sizeof (mutex));
memcpy(&self.cond, &cond, sizeof (cond));
- memcpy(&self.termcond, &cond, sizeof (cond));
-
- if (self.requests)
- pthread_join(self.thread, NULL);
}
int bxfi_push_timeout(struct bxfi_sandbox *instance, double timeout)
@@ -159,10 +151,16 @@ int bxfi_push_timeout(struct bxfi_sandbox *instance, double timeout)
pthread_mutex_lock(&self.sync);
if (!self.requests) {
- if (self.thread_active)
- pthread_join(self.thread, NULL);
+ pthread_attr_t attrs;
+ if ((rc = pthread_attr_init(&attrs)) == -1) {
+ rc = -errno;
+ goto error;
+ }
+ pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
+
self.thread_active = 1;
- rc = -pthread_create(&self.thread, NULL, timeout_killer_fn, NULL);
+ rc = -pthread_create(&self.thread, &attrs, timeout_killer_fn, NULL);
+ pthread_attr_destroy(&attrs);
if (rc)
goto error;
}
@@ -177,7 +175,6 @@ int bxfi_push_timeout(struct bxfi_sandbox *instance, double timeout)
*nptr = req;
pthread_cond_broadcast(&self.cond);
- pthread_cond_broadcast(&self.termcond);
pthread_mutex_unlock(&self.sync);
return 0;
@@ -204,17 +201,6 @@ void bxfi_cancel_timeout(struct bxfi_sandbox *instance)
}
if (cancelled) {
pthread_cond_broadcast(&self.cond);
- if (!self.requests) {
- while (self.cancelled && !self.requests)
- pthread_cond_wait(&self.termcond, &self.sync);
- if (self.requests)
- goto end;
- if (self.thread_active) {
- pthread_join(self.thread, NULL);
- self.thread_active = 0;
- }
- }
}
-end:
pthread_mutex_unlock(&self.sync);
}

BIN
course_tools/criterion.zip Normal file

Binary file not shown.

231
course_tools/git-submit Executable file
View File

@ -0,0 +1,231 @@
#!/usr/bin/env python3
import requests
from requests import get
from requests.exceptions import ConnectTimeout, ConnectionError, HTTPError
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from datetime import datetime
from signal import signal, SIGINT, SIGTSTP, SIG_IGN
from subprocess import run, PIPE
from shlex import split
from argparse import ArgumentParser
from sys import exit
RED = '\033[1;31m'
GRE = '\033[1;32m'
YEL = '\033[0;33m'
BLU = '\033[1;34m'
PUR = '\033[1;35m'
CYA = '\033[1;36m'
BOLD = '\033[1m'
DEF = '\033[0m'
BASE_URL = 'https://cse320.starkeffect.com:3000/submit'
server_timeout = None
def yn_prompt():
prompt = input('{}Continue? Please enter Y/n{}: '.format(PUR, DEF))
while prompt != 'Y' and prompt.lower() != 'n':
prompt = input('{}Continue? Please enter Y/n{}: '.format(PUR, DEF))
if prompt != 'Y':
print('{}Aborting...{}'.format(YEL, DEF))
exit(1)
def first_api_call():
global server_timeout
try:
res = get(BASE_URL + '/timeout', params=None, timeout=15, verify=False)
res_json = res.json()
server_timeout = res_json['timeout']
except (ConnectionError, ConnectTimeout, HTTPError):
print('Error connecting to the server. Try again. (FIRST_SERVER_CALL)')
exit(1)
def initial_message():
print('Only commits made from master or merged into master will be ',
'considered. If you would like to submit a commit from a different ',
'branch, please merge the branch into master first.')
yn_prompt()
def api_call(endpoint, query):
global server_timeout
try:
res = get(BASE_URL + endpoint, params=query, timeout=server_timeout,
verify=False)
except (ConnectionError, ConnectTimeout, HTTPError):
print('Error connecting to the server. Try again. ',
'(SERVER_CONNECTION_ERROR)')
exit(1)
res_json = res.json()
if 'errmsg' in res_json:
print(res_json['errmsg'])
exit(1)
return res_json
def validate_git_directory():
cmd = split('git remote show origin')
proc = run(cmd, stdout=PIPE)
if proc.returncode:
exit(proc.returncode)
# You are not expected to understand this
return [line for line in proc.stdout.decode().split('\n') if
line.strip().startswith('Push')][0].split('/')[-1].split('.')[0]
def validate_commit(commit):
# check current user branch
branch_cmd = split('git rev-parse --abbrev-ref HEAD')
branch_proc = run(branch_cmd, stdout=PIPE, stderr=PIPE)
if branch_proc.returncode:
print('{}Error{}: Uninitialized repository. Please initialize your repo.'.format(RED, DEF))
exit(branch_proc.returncode)
current_branch = [line for line in branch_proc.stdout.decode().split('\n')][0]
if not current_branch == 'master':
print('{}Error{}: Run git submit from the master branch'.format(RED, DEF))
exit(1)
# check branch commit was made from
if not commit == 'master':
if len(commit) < 8:
print('{}Error{}: Use at least the first 8 characters of the commit hash.'.format(RED, DEF))
exit(1)
commit_branch_cmd = split('git branch --contains {}'.format(commit))
commit_branch_proc = run(commit_branch_cmd, stdout=PIPE, stderr=PIPE)
if commit_branch_proc.returncode:
print('{}Error{}: The specified commit hash is invalid.'.format(RED, DEF))
exit(commit_branch_proc.returncode)
branches = [branch for branch in commit_branch_proc.stdout.decode().split('\n')]
on_master = False
for branch in branches:
branch_name = [word.strip() for word in branch.split('*')][-1]
if branch_name == 'master':
on_master = True
if not on_master:
print('{}Error{}: The submitted commit should be from master. Merge the branch with the commit into master.'.format(RED, DEF))
exit(1)
cmd = "bash -c 'git log --branches=master* --not --remotes --pretty=oneline'"
proc = run(cmd, stdout=PIPE, shell=True)
if proc.returncode:
print(proc.stdout)
exit(proc.returncode)
elif commit == 'master' and proc.stdout:
print('{}Error{}: Push your commits to the remote using: git push'.format(RED, DEF))
exit(1)
elif commit in proc.stdout.decode():
print('{}Error{}: Push your commits to the remote using: git push'.format(RED, DEF))
exit(1)
def validate_tag(tag):
response = api_call('/verify/{}'.format(tag), None)
if 'err' in response:
print('{}Error{}: {}'.format(RED, DEF, response['err']))
exit(1)
if not response['valid']:
print('{}Error{}: {}'.format(RED, DEF, response['msg']))
exit(1)
def check_for_resubmission(args):
status = api_call('/status', args)
print('{}Submission Time{}: {}'.format(BLU, DEF, status['time']))
if status['submitted']:
print('{}Resubmitting Homework{}: {} -- Are you {}sure{} you wish to resubmit '
'(this may result in lateday penalties)?'.format(YEL, DEF, CYA + args['tag'] + DEF, BOLD, DEF))
else:
print('{}Submitting Homework{}: {} -- Are you {}sure{}? '
.format(YEL, DEF, CYA + args['tag'] + DEF, BOLD, DEF))
yn_prompt()
return status['submitted'], status['time']
def confirm_repo_state():
run(split('git -c color.status=always status'))
print('{}Current Repo State{}: Are you {}sure{} you have committed and pushed all the files needed '
'(such as .h files)?'.format(YEL, DEF, BOLD, DEF))
yn_prompt()
def confirm_commit(commit):
cmd = split('git --no-pager -c color.ui=always show {} --pretty=fuller --quiet'.format(commit))
proc = run(cmd)
if proc.returncode:
exit(proc.returncode)
print('{}Submission Commit{}: Are you {}sure{} this is the commit you wish to submit?'.format(YEL, DEF, BOLD, DEF))
yn_prompt()
def confirm_submission(tag):
print('{}Confirm Submission{}: Are you {}sure{} you want to submit {}? Your previous submission (if any) will be '
'{}overwritten{}!'.format(YEL, DEF, BOLD, DEF, CYA + tag + DEF, BOLD, DEF))
yn_prompt()
def trigger_submission(args):
info = api_call('', args)
return info['late'], info['lateDays']
def submission_info(**kwargs):
run(split('git pull --quiet'))
late = kwargs['late']
tag = kwargs['tag']
resubmit = kwargs['submit']
late_days = kwargs['days']
attempt_time = kwargs['time']
if late:
print('{}Urgent{}: {} was overdue. You did not have enough late days remaining.'.format(RED, DEF, tag))
print('{}Info{}: You have {} lateday(s) remaining.'.format(BLU, DEF, late_days))
if resubmit:
print('{}Alert{}: Your last submission will be taken into consideration.'.format(YEL, DEF))
else:
print('{}Urgent{}: You will be given an automatic zero for this assignment. Please meet your Professor\n'
'after lecture or during office hours or please email us at cse320@cs.stonybrook.edu with\n'
'{}[CSE320] - {} Overdue{} as the subject.'.format(RED, DEF, BLU, tag, DEF))
else:
print('{}Success{}: {} submission successful. Your assignment was submitted on {}.'
.format(GRE, DEF, tag, attempt_time))
print('{}Info{}: You have {} lateday(s) remaining.'.format(BLU, DEF, late_days))
print('Thank you for submitting your homework! We are working hard to get your grades out as soon as possible.')
print('If you have any concerns please email us at cse320@cs.stonybrook.edu with {}[{}]{} in the subject.'
.format(BLU, tag, DEF))
print('{}The CSE 320 Team{}'.format(PUR, DEF))
def main(arg_parser):
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
first_api_call()
initial_message()
net_id = validate_git_directory()
args = arg_parser.parse_args()
attempt_time = datetime.now()
validate_tag(args.TAG)
validate_commit(args.commit)
resubmit, time = check_for_resubmission({'tag': args.TAG, 'repo': net_id, 'attemptTime': attempt_time.isoformat()})
confirm_repo_state()
confirm_commit(args.commit)
confirm_submission(args.TAG)
signal(SIGINT, SIG_IGN)
signal(SIGTSTP, SIG_IGN)
late, late_days = trigger_submission({'tag': args.TAG, 'repo': net_id, 'commit': args.commit})
submission_info(tag=args.TAG, time=time, submit=resubmit, late=late, days=late_days)
exit(0)
class GitArgParser(ArgumentParser):
def error(self, message):
print('{}Error{}: {}'.format(RED, DEF, message))
self.print_help()
exit(2)
if __name__ == '__main__':
parser = GitArgParser(prog='git submit', description='Submit your homework assignment using git.')
parser.add_argument('TAG', type=str, help='The homework you wish to submit. Can have one of the following values: '
'hw0, hw1, hw2, hw3, hw4, hw5')
parser.add_argument('-c', dest='commit', type=str, required=False, default='master',
help='Used if you wish to submit a commit that is not the latest. COMMIT is the SHA value of '
'your commit.')
main(parser)

124
course_tools/vm-setup.sh Executable file
View File

@ -0,0 +1,124 @@
#! /usr/bin/env bash
echo
if hash figlet 2> /dev/null; then
echo "320 Setup" | figlet -f banner
else
echo "320 Setup"
fi
echo
echo "Updating..."
# General updates
sudo apt-get update -y
echo "Graphics issues from 8/2021 now fixed with VirtualBox 6.1.27 or later -- doing apt-get upgrade"
sudo apt-get upgrade -y
sudo apt-get autoremove -y 2>&1 > /dev/null
# Extras
sudo apt-get install -y figlet terminator htop dialog wget tree 2>&1 > /dev/null
echo "Installing..."
echo "vm-tools" | figlet -f mini
sudo apt-get install -y open-vm-tools-desktop 2>&1 > /dev/null
echo "Installing..."
echo "Git" | figlet -f mini
sudo apt-get install -y git 2>&1 > /dev/null
echo "Installing..."
echo "Gitk" | figlet -f mini
sudo apt-get install -y gitk 2>&1 > /dev/null
echo "Installing..."
echo Git Submit | figlet -f mini
mkdir -p $HOME/.local
mkdir -p $HOME/.local/bin
cp -p git-submit $HOME/.local/bin
chmod +x $HOME/.local/bin/git-submit
echo "Installing..."
echo "Readline" | figlet -f mini
sudo apt-get install -y libreadline-dev readline-doc 2>&1 > /dev/null
echo "Installing..."
echo "Clang" | figlet -f mini
sudo apt-get install -y clang 2>&1 > /dev/null
echo "Installing..."
echo "GDB" | figlet -f mini
sudo apt-get install -y gdb cgdb 2>&1 > /dev/null
echo "Installing..."
echo "Valgrind" | figlet -f mini
sudo apt-get install -y valgrind 2>&1 > /dev/null
echo "Installing..."
echo "GCC and tools" | figlet -f mini
sudo apt-get install -y gcc make binutils 2>&1 > /dev/null
echo "Installing..."
echo "POSIX man pages" | figlet -f mini
sudo apt-get install -y manpages-posix-dev 2>&1 > /dev/null
echo "Installing..."
echo "Ncurses" | figlet -f mini
sudo apt-get install -y libncurses-dev 2>&1 > /dev/null
echo "Installing..."
echo "Criterion" | figlet -f mini
#sudo add-apt-repository -y ppa:snaipewastaken/ppa
#sudo apt-get update
#sudo apt-get install -y criterion-dev
sudo unzip -d / criterion.zip
dialog --keep-tite --title "Sublime Text" --yesno "Do you want to install Sublime with plugins?" 5 50
if [ $? -eq 0 ]; then
# Add Sublime key
wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add - 2>&1 > /dev/null
echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list 2>&1 > /dev/null
sudo apt-get update -y
echo "Installing..."
echo "Sublime Editor" | figlet -f mini
sudo apt-get install sublime-text 2>&1 > /dev/null
mkdir -p "$HOME/.config"
mkdir -p "$HOME/.config/sublime-text-3"
mkdir -p "$HOME/.config/sublime-text-3/Installed Packages"
mkdir -p "$HOME/.config/sublime-text-3/Packages"
mkdir -p "$HOME/.config/sublime-text-3/Packages/User"
# EWS (8/8/2020)
# The following configuration is somewhat obsolete, and in any case is too sensitive to
# changes to Sublime and its plugins. When I tried to run it just now, it left things
# broken. Instead I have manually installed the following for use in CSE 320:
#
# Package Control
# SublimeLinter
# SublimeLinter-gcc
# TrailingSpaces
#
# The only really important thing is to copy SublimeLinter.sublime-settings to
# ~/.config/sublime-text-3/Packages/User as in the last commented line below, so that
# the linter works correctly with our project setup.
#
# The rest of this stuff I have commented out for now.
#
#touch "$HOME/.config/sublime-text-3/Packages/User/Package Control.sublime-settings"
#touch "$HOME/.config/sublime-text-3/Installed Packages/Package Control.sublime-package"
#wget -qO - https://packagecontrol.io/Package%20Control.sublime-package > "$HOME/.config/sublime-text-3/Installed Packages/Package Control.sublime-package"
#
#echo "{\"bootstrapped\":true,\"installed_packages\":[\"Package Control\",\"TrailingSpaces\",\"SublimeLinter\",\"SublimeLinter-contrib-gcc\"]}" > "$HOME/.config/sublime-text-3/Packages/User/Package Control.sublime-settings"
#echo "{\"trailing_spaces_trim_on_save\": true}" > "$HOME/.config/sublime-text-3/Packages/User/trailing_spaces.sublime-settings"
#echo "{\"ignored_packages\":[\"Vintage\"],\"hot_exit\":false,\"save_on_focus_lost\":true,\"translate_tabs_to_spaces\":true}" > "$HOME/.config/sublime-text-3/Packages/User/Preferences.sublime-settings"
cp -p SublimeLinter.sublime-settings ~/.config/sublime-text-3/Packages/User
fi
echo "-----------------------------"
echo "!ATTN!" | figlet
echo "-----------------------------"
echo -e "If you \e[31;1mcannot\e[0m execute git submit add the following to your ~/.bashrc or other relevant terminal config"
echo "export PATH=\$PATH:$HOME/.local/bin"

754
hw0-doc/README.md Normal file
View File

@ -0,0 +1,754 @@
# CSE320 Spring 2022
In this course you will be using Linux as your primary development
environment. In addition, we will be providing you with a git
repository hosted on a department GitLab server. This document will
briefly explain the course tools and outline the required setup for
this course.
## Setting up your CSE320 Git repository
Git is an open-source distributed version control system. We will use
git repositories to manage your homework submissions. In addition, the
use of git allows the Professor and TAs to access and view your your
code remotely in order to assist you. While some students may be
familiar with version control and git, we ask that everyone complete
the following tutorial and instructions. This will ensure that
everyone in the course has the same background knowledge and can
submit their homeworks.
We are using a CSE department supported git web interface, called
gitlab. This is similar to github, bitbucket, etc. It is an interface
to help manage git repositories. These services are INTERFACES to git,
not git itself. You *may not* use external repositories as we will
use the repo provided to you to grade your submitted work and share
gradesheets with you.
To setup your repository:
1. Navigate to
[https://gitlab02.cs.stonybrook.edu](https://gitlab02.cs.stonybrook.edu/)
and log into it with your CS email account (user name only, do not
include the `@cs.stonybrook.edu`). If you forgot your CS email
password you can reset it by following the instructions
[here](https://auth01.cs.stonybrook.edu:10443/). If those
instructions fail, please email `rt@cs.stonybrook.edu` requesting a
password reset. A response may take up to 24-48 hours.
2. Once you have logged in the creation of your repo will be triggered.
Normally this will occur within a few minutes. If not, then send an
email to `cse320@cs.stonybrook.edu` and we will look into it.
Sometimes the 'bot responsible for creating the repos has to be reset.
## Setting up Linux Environment
Since C is a systems level language, frequently the behavior from one
persons computer to another can vary. In the past, we have provided a
common server for students to use, but this presented a few
problems. When you wanted to compile your assignment, you would have
to continuously transfer the file to the server and then compile
it. If you had any mistakes, you would have to either edit it on the
server or make the change locally and upload it again. This became
very tedious which often led to students compiling and testing
locally on their own machines. This was not always a good idea as
something that seemed to work for you didnt always work for the
grader which caused many issues. Also, many tools, which assist in
locating and fixing errors in C code, do not exist in Windows and OSX
environments. So students who installed operating systems such as
[Linux](https://en.wikipedia.org/wiki/Linux) were at an advantage over
the students who did not.
> :nerd: This document will also outline the homework management and
submission process. In this class, you will be creating increasingly
complex C projects which may involve many files. To satisfy these
requirements, we will be using git to manage & submit your homework
assignments.
> :nerd: While we will try to provide the basics for what needs to be
done, it will ultimately be up to you to learn how to use these
tools.
To help alleviate the above issues and to setup a local environment
with the necessary course tools, you must install your working
environment using one of these two options:
- Option 1: A Virtual Machine running Linux (Encouraged Option)
- Option 2: Multi-Boot/Install Linux on your machine
Option 1 is encouraged for the following reasons:
- Quick setup
- Ease of use in your native OS
- Easy to reset if errors in VM environment
- All course tools are pre-installed
- Simulate multiple cores on a single core system
We have put a lot of effort into setting up a pre-configured VM. If
for some reason you are unable or unwilling to use this, we have
provided basic instructions for Option 2 with a script to install all
the course tools.
If you choose option 2, you should have some idea what you are doing,
already be comfortable with Linux, and be aware that we probably won't
have the resources to debug any issues you might encounter. If you
deviate in any other way from these procedures, it is completely at
your peril.
### Option 1: A Virtual Machine running Linux
Students often use either [VMware](https://www.vmware.com) or
[VirtualBox](https://www.virtualbox.org/) to run virtual machines.
We recommend that you use VirtualBox. It is free, and it runs
on all of the most popular platforms.
In order to run a virtual machine, your machine must support 64-bit
hardware virtualization. Most machines built after 2006 should support
this. However, not all machines have the option enabled. You may need
to modify your BIOS settings to enable this feature. As each machine
has a different BIOS, it is up to you to find and enable this feature
on your own machine.
Download and install the VirtualBox platform package appropriate
for your computer from [this site](https://www.virtualbox.org/wiki/Downloads).
> :exclamation: Because of recent changes made to the way VirtualBox interfaces
> with the graphics drivers on various platforms, it is important that you make
> sure to install VirtualBox version 6.1.27 or greater. With older versions,
> the course VM image will probably not be able to access the display properly.
#### Running the Linux VM
We will be using Linux Mint 20 "Ulyana" -- Cinnamon as this semester's OS. We
have taken the time to set up the VM so it simply needs to be opened
in your virtualization program. The provided Linux virtual machine
has all the tools required for various aspects of this course; for
example, homework submission is pre-installed.
To get started, download the VM from here:
[Google Drive]
(https://drive.google.com/file/d/1rwUM_rm4sEC-we-i-siOPWDnQEtH6mdC/view?usp=sharing)
(it's nearly 5 gb so give it some time).
This should result in your having a file called **CSE320_Spring22.ova**.
This can be imported directly into VirtualBox by choosing
"Import Appliance" from the "File" menu and then browsing to select
the file you downloaded. Click "Next", review the VM settings,
and then click on "Import". Once the import has completed, you should
have a VM called "CSE 320". Select this and click on
"Start" to boot the VM.
#### Login Info
Upon booting, you will be automatically logged in as user `student`.
The login info for your reference is:
| Username | Password |
|:----------------|:-------------|
| `student` | `cse320` |
You will need the password in order to obtain superuser access via `sudo`
to install software, and you might need to enter both the user name and
the password if the screen lock should kick in after you have left the VM
idle for some time.
#### VirtualBox Guest Additions
The VirtualBox Guest Additions are software components that are added
to the guest operating system that runs in your VM, to make the VM more
convenient to use. Examples of things in the Guest Additions are accelerated
video drivers, support for clipboard and drag-and-drop between the VM
and the host system, ability to resize the VM window, and so on.
There is a version of the Guest Additions installed in the VM,
but since the Guest Additions need to match the version of VirtualBox
that you are using, you should reinstall them. To do this, you should
start the VM, then from the "Devices" menu (probably in the titlebar of
the VM window, or wherever top-level application menus appear on your
system) select "Insert Guest Additions CD Image". This might cause
a CD image to be downloaded over the network. If the system offers to
auto-run the CD, allow it to do so. Otherwise you might have to use
file manager (under Linux Mint) to open the CD manually. Once started,
it can take several minutes for the installation to complete.
#### VM Snapshots
If you choose to install additional tools or other programs to your
environment, you may want to take a snapshot of your VM. This may save
you the time of installing your additional software again, in the
unfortunate event of an unusable VM. Refer to the appropriate VirtualBox
documentation to learn how to take a snapshot of your VM.
### Option 2: Multi-Boot/Install Linux on your machine
> Remember, if you choose this option, you should have some idea what
you are doing, already be comfortable with Linux, and be aware that
we probably won't have the resources to debug any issues you might
encounter. If you deviate in any other way from these procedures,
it is completely at your peril.
Install [Linux Mint 20 "Ulyana" - Cinnamon 64-bit](https://linuxmint.com/edition.php?id=281)
or 20.04 Ubuntu variant (as long as you are using gcc 9.3.0) as a dual-boot or fresh
install.
Clone the [CSE320 course tools](https://gitlab02.cs.stonybrook.edu/cse320/course_tools)
(https://gitlab02.cs.stonybrook.edu/cse320/course_tools) repository
into your Linux environment. You may need to install git first.
Follow the README in the `course_tools` repo.
#### Note about MacOS with Apple M1 Processor
We are aware that a number of students are now using Macs with an M1 processor.
The M1 hardware uses the ARM instruction set, which is different than the
x86-64 instruction set which the course Linux Mint VM uses. At the time of this
writing, we do not have any reliable information that would indicate that it would
be possible to run the Linux Mint VM on an M1. It might be possible to run
it using QEMU, which is a full x86-64 emulator that is independent of the
underlying host system hardware and for which versions exist for Macs running
on the M1, though to date we do not have any information from anyone who has
succeeded in running the VM this way (please tell us if you have managed to do it).
However, even if in fact the VM can be run this way it is likely to be very slow.
So, our best advice at this time would be to try to identify some x86-64-based
computer that you can use for the course, rather than supposing that you will
be able to use an M1-based computer.
### Working in Unix
We understand that many of the students taking this class are new to
CLI (Command-line interface). You can find a quick crash course in
[Appendix A of Learn Python the Hard Way](https://learnpythonthehardway.org/book/appendixa.html).
> :nerd: For more advanced usage refer
[here](http://www.ibm.com/developerworks/library/l-lpic1-103-1/). This
is a REALLY good resource so we recommend bookmarking it for later
reference.
> :nerd: It is **very** important that you properly shut down the Linux Mint
operating system when you are finished using it, rather than just
"X-ing out" the VirtualBox VM window. The latter is equivalent to
going and yanking your desktop PC's power plug out of the wall without
shutting down Windows, and it can cause data loss and even corruption.
Use the shutdown icon from the "Mint" menu in the lower left corner
of the desktop to shutdown Linux Mint. At that point, it will be safe
to power off the VM.
> :nerd: Depending on the host system on which you installed VirtualBox,
"keyboard integration" and "mouse integration" might or might not be
supported. If they are supported, then you will be able to fairly
seamlessly move your mouse in and out of the VM window and what you
type on the keyboard will go to the proper place. If these features
are not supported, then you will need to click on the VM window in
order to use it, at which point the mouse and keyboard will be "captured"
by the VM. In order to regain control of the mouse and cursor, you
will need to press the "host key", which is identified at the right-hand
side of the bottom icon tray of the VirtualBox window. On some systems,
the default host key is "Right Ctrl".
> :nerd: To open a terminal window, you can click on the terminal
icon (which should be fairly evident), or you can press CTRL + ALT + T.
#### Text Editor
A _good_ basic text editor is the key for C development.
We have pre-installed Sublime Text with plugins such as a C linter to
assist with C development on the given VM. A linter displays compiler
errors on top of your code much like an IDE. If you do install another
editor we recommend looking into a similar feature described as it
will aid development.
You may use another text editor if you so desire. Some popular ones
are Atom, Vim, Emacs and VSCode. Each have their own linters that you
can look into installing.
**DO NOT** install and use a full IDE (Clion, Netbeans, or Eclipse);
there are many parts of the compilation process that are hidden from
you. Not only would you miss out on valuable information pertinent
to the course but your project is not guaranteed to build in an
environment separate from the IDE.
## Homework Management & Submission
#### Setting up your CSE320 repository
Once your repository has been created on gitlab, you must clone it in
your Linux environment. Open a new terminal window
and type `git clone GIT_URL`. You should replace `GIT_URL` with the
URL to your repository. You can find it by navigating to your projects
page on GitLab and selecting the https option.
> Your repo should be cloned into your home directory (`/home/student/` or AKA `~/`)
Alternatively if you add an ssh-key to your gitlab account you can
clone, pull, push, etc. using the URL under the SSH option (**highly
recommended** An SSH key can be done at any time).
Reference:
- [Generating SSH key](http://docs.gitlab.com/ce/ssh/README.html)
#### First Commit to your Repo
Open a terminal and from the home directory enter the following command:
(replacing REPO_NAME with your repo's name)
<pre>
$ subl REPO_NAME
</pre>
The text editor, Sublime, will open and your repo's contents will be
shown on the sidebar. Open the `README.md` file and add the text with
the following information relevant to you.
```markdown
# FIRST_NAME LAST_NAME
## ID_NUMBER
:FAVORITE_EMOJI:
PROFESSOR_NAME - SECTION_NUMBER
```
You can find your favorite emoji code among these
[https://gist.github.com/rxaviers/7360908](https://gist.github.com/rxaviers/7360908).
After that you can save and close the file and return to your terminal.
In your terminal, type the following commands, replacing `EMAIL` with
your CS email address and `NAME` with your name:
<pre>
$ git config --global user.email "EMAIL"
$ git config --global user.name "FIRST_NAME LAST_NAME"
$ git config --global push.default simple
</pre>
**NOTE:** This will change your settings for all repos. If you want to
have different settings for other repos on your machine then omit
`--global`
Change directories into your repo `cd REPO_NAME`
Then run the following commands:
<pre>
$ git status
$ git add README.md
$ git commit -m "My First Commit"
$ git push
</pre>
> The `git push` command will prompt for username and password if you used HTTPS.
The output will look **similar** to:
<pre>
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add file..." to update what will be committed)
(use "git checkout -- file..." to discard changes in working directory)
modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
$ git add README.md
$ git commit -m "My First Commit"
[master XXXXXXX] My First Commit
1 files changed, X insertions(+), X deletions(-)
$ git push
Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 980 bytes | 0 bytes/s, done.
Total 4 (delta 2), reused 0 (delta 0)
To ssh://git@gitlab02.cs.stonybrook.edu:130/CSE320_Fall20/REPONAME.git
XXXXXXX..XXXXXXX master -> master
Branch master set up to track remote branch master from origin.
$
</pre>
This is the basic usage of git. We check the `status` of which files
are tracked/untracked. Then we `add` them and we `commit` them along
with a message. Lastly and most importantly we `push` them to the
remote repository on the gitlab server. If the push was successful,
you can navigate back to the page `https://gitlab02.cs.stonybrook.edu`
and select your repository. Inside your repository, select the files
option on the left menu. You should now see the file `README.md` with
the contents you added to it.
> :scream: Once a commit has been made, its contents cannot be changed.
> In addition, the GitLab server has been configured so that it is not
> possible to delete any commits that have been pushed to the "master"
> branch. This means that any junk you commit to the master branch and
> push to the server will persist there forever in your repo, as well as
> in copies that we have to store. In view of this, it is is important that
> you take great care not to commit junk files, especially files that are
> very large or binary files that are generated by the compiler.
> Each time you commit, you should first use `git status` to carefully review
> the set of files to be committed. Use `git reset` to remove any files that
> are staged for commit but should not be. We strongly recommend that you
> *never* use commands such as `git add .` or `git add --all`, as these have
> the potential to add a lot of junk to your commit. Instead, `git add` each
> file individually, after perhaps using `git diff` to remind yourself of the
> reason for the commit and to see if the changes are as they should be.
#### Git Tutorial
We recommend you complete Codecademys git tutorial found
[here](https://www.codecademy.com/learn/learn-git) if you are
unfamilar with git.
If youre interested in learning more information about git or
expanding your knowledge, refer to these references:
- [git-book](https://git-scm.com/book/en/v2) - Chapter 2 is a MUST
read chapter, checkout git aliases!
- [Learn Git Branching](http://learngitbranching.js.org/) - An
interactive tutorial on git branching
- [git cheat sheet](https://scotch.io/bar-talk/git-cheat-sheet)
# Homework 0
#### Obtaining Assignment Code
1. Navigate to your repository directory (`cd ~/REPO_NAME`) in your VM
(using the terminal).
2. An assignment, such as this one, will tell you the code is located
at a particular address. For `hw0` it is:
`https://gitlab02.cs.stonybrook.edu/cse320/hw0.git`
3. Add this remote repository as an additional remote into your
existing repository. We will name the new remote HW0_CODE.
If you use HTTPS:
```
$ git remote add HW0_CODE https://gitlab02.cs.stonybrook.edu/cse320/hw0.git
```
If you use SSH:
```
$ git remote add HW0_CODE ssh://git@gitlab02.cs.stonybrook.edu:130/cse320/hw0.git
```
4. Fetch all the refs in this new repository. This command will prompt
for username and password if you used HTTPS.
```
$ git fetch HW0_CODE
```
5. Finally, merge and commit the files from the `HW0_CODE` remote's
`master` branch into your existing repositorys `master` branch.
```
$ git merge -m "Merging HW0_CODE" HW0_CODE/master
```
> :nerd: If you get an error mentioning 'unrelated histories' try
again adding this flag: `--allow-unrelated-histories`
6. If you type the command `ls` you should now see a directory called `hw0`.
7. Push these base files to your remote repository (gitlab). This
command will prompt for username and password if you used HTTPS.
```
$ git push
```
#### Your Homework 0 Working Directory
The directory structure of your repo will now look **similar** to
this. Use `ls -a` or `tree -a` to see the hidden files that begin with
`.`
<pre>
YOUR_REPO
├── .git
│ ├── ...
├── .gitignore
├── .gitlab-ci.yml
├── hw0
│ ├── academic_honesty.txt
│ ├── include
│ │ └── hi.h
│ ├── Makefile
│ ├── README.md
│ ├── src
│ │ ├── hi.c
│ │ └── main.c
│ └── tests
│ └── test.c
└── README.md
</pre>
Information about each file is explained below.
> :nerd: Enter `subl REPO_NAME` (or `subl .` if you are in your repo
already) as you did before to easily follow along and look inside
each file
- `.gitignore` - This is a file that tells git to ignore certain
directories or files that you don't want committed. For example, the
`bin` and `build` directories are ignored.
This is because we don't want executables and other generated binary files
pushed to your remote repository, only source code.
- `.gitlab-ci.yml` This is gitlab's own continuous integration
configuration file, explained in a later section.
- `hw0/` - This is your first homework directory, throughout the
semester we'll be adding each homework directory in this fashion
'hw#' where # is the homework number. Inside the `hw0/` directory,
you will find:
- `README.md` - This is a file where you can detail notes about the project.
- `Makefile` - This is your ultimate compilation automation
tool. The program `make` will use this file to properly compile
your assignment.
- `include/` - This is where we keep our `.h` headers. Unlike
Java, C is a one pass compilation language, which keeps the
overhead of creating symbol tables and structures low. Since all
functions must be defined before use, we utilize a header file
to make the symbols available across multiple files.
- `hi.h` - This is our header file for `hw0`. **Examine the
contents of this file.**
- `src/` - This is where we keep our `.c` source files. These
files contain the actual definitions of the functions declared
in our headers.
- `main.c` - This file contains the C main function, in this
course you will **ALWAYS** need to keep your main function
in its own C file isolated from the rest of your functions
that you implement.
- `hi.c` - The helper function, `hi`, is defined (implemented)
here. Each function **does not** need its own file, only
`main()` needs to be in its own file.
- `tests/` - This is where we keep our unit tests. There is an
EXCELLENT unit testing framework called
[criterion](http://criterion.readthedocs.io/en/master/intro.html)
that we will be using in the course.
- `test.c` - This file contains the implementation of our
testing framework. The Makefile will compile these files
with all of the non-`main.c` files. This gives the testing
framework access to your helper functions.
> **Do not** modify or alter `.gitlab-ci.yml`, the `Makefile` or the
`README.md` for any assignment unless otherwise instructed.
### Academic Honesty Statement
In this course we take Academic Honesty EXTREMELY seriously. Read the
statement in `academic_honesty.txt` using your favorite text editor or
using the following command (type `man cat` for more information on
this tool).
From your repository's root directory type:
<pre>
$ cat hw0/academic_honesty.txt
</pre>
Next, we will append the Academic Honesty Statement into your
repository's README along with the date and your "signature"
confirming that you have read the statement and agree with the policy
and commit it to your repo.
From your repository's root directory type the following commands into
your terminal, filling in `YOUR_NAME` with the appropriate information
in the second command.
> :nerd: The second "crazy" command is an example of redirection which
can be done between programs on the command-line. We will learn more
about redirection and how it works later in the semester.
<pre>
$ cd hw0
$ cat academic_honesty.txt &lt;(echo "$(date -u) - YOUR_NAME") >> ../README.md
$ git add --all
$ git commit -m "Academic Honesty statement"
$ git push
</pre>
#### CI File
This semester we want to ensure that students don't get caught by
silly mistakes or overlook anything while working on their
assignments. We will use GitLab's [_Continuous
Integration_](https://en.wikipedia.org/wiki/Continuous_integration)
feature to minimize such incidents. It is an automated tool that will
make sure your work compiles and passes basic tests.
There is a `.gitlab-ci.yml` file in the base code. This file is used
to set up a clean vm, compile your code, run your unit tests, and
lastly run your program on the gitlab server. When looking on gitlab
you will notice a red :x: or green :heavy_check_mark:. Each represents
the result of a 'CI pipeline'. Go to the Pipelines tab to view the
results of your run. We will provide this file with each homework. The
CI runs when gitlab "gets around" to doing it so don't be alarmed if
you don't see your result right away. Also, note that sometimes
a "Runner System Failure" might occur due to problems on the server.
A failure reported by CI system does *not* mean that "your commit failed"
in the sense that what you committed and pushed failed to make it to the
gitlab server; it means that an error occurred while the system was trying
to compile and run what you committed. You can always use the `Repository`
tab of the gitlab web interface to see what commits you have pushed to the
server. If a commit is shown there, then it is safely on the server.
#### Hello, World!
In the terminal, navigate into the `hw0` folder of your repository
(ie. `~/REPO_NAME/hw0/`). Open the file `include/hi.h` and examine its
contents, read the comments, and follow the directions in the
files. These directions step you through the base files in the HW to
familiarize you with basic files structure and the included code
complements.
In the terminal, type `make` to build the `hw0` base code. This will
compile your c code into executables.
<pre>
$ make
mkdir -p bin build
gcc build/hi.o build/main.o -o bin/hi
gcc -Wall -Werror -std=gnu11 -g -DDEBUG -I include build/hi.o tests/test.c -lcriterion -o bin/hi_tests
</pre>
An executable named `hi` and `hi_tests` will be created in the `bin`
folder of the `hw0` directory. You can execute either program by
typing `bin/hi` or `bin/hi_tests` from the `hw0` directory into the
terminal.
Running `bin/hi` will print "Hello, World!" before exiting. Running
`bin/hi_tests` will fail a unit test and print the warning `"Assertion
failed: say_hi() function did not say 'Hi'"`.
<pre>
$ bin/hi
Hello, World!
$ bin/hi_tests
[----] tests/test.c:15: Assertion failed: say_hi() function did not say 'Hi'
[FAIL] CSE320_Suite::test_it_really_does_say_hi: (0.00s)
[====] Synthesis: Tested: 1 | Passing: 0 | Failing: 1 | Crashing: 0
</pre>
To do this assignment, modify the `say_hi()` function in `src/hi.c` to
satisfy the unit test (i.e. `return "Hi"`). This is will now make the
program do what the unit test expects and as a result pass the unit
test.
Rebuild the hw0 executables by typing in make to your terminal. Run
the program again to make sure it satisfies requirements
<pre>
$ make
mkdir -p bin build
gcc -Wall -Werror -std=gnu11 -g -DDEBUG -I include -c -o build/hi.o src/hi.c
gcc build/hi.o build/main.o -o bin/hi
gcc -Wall -Werror -std=gnu11 -g -DDEBUG -I include build/hi.o tests/test.c -lcriterion -o bin/hi_tests
$ bin/hi
Hi, World!
$ bin/hi_tests
[====] Synthesis: Tested: 1 | Passing: 1 | Failing: 0 | Crashing: 0
</pre>
To save your code changes, `add` them to the staging area of
git. `Commit` the changes to a local commit on your VM. Then `push`
the commits to the remote server (gitlab).
You can be sure that everything compiles correctly if at some point a
check appears next to your repo on gitlab.
<pre>
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add file..." to update what will be committed)
(use "git checkout -- file..." to discard changes in working directory)
modified: src/hi.c
no changes added to commit (use "git add" and/or "git commit -a")
$ git add src/hi.c
$ git commit -m "Hi Fix"
[master XXXXXXX] updated hi.c
1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Username for 'https://gitlab02.cs.stonybrook.edu': REPO_NAME
Password for 'https://REPO_NAME@gitlab02.cs.stonybrook.edu':
</pre>
## How to submit with `git submit`
This semester you will be submitting all assignments using our custom
git command: `git submit`
The usage for `git submit` is:
<pre>
$ git submit [-c COMMIT_HASH] TAG
</pre>
The **TAG** is which assignment you are tagging, for example: `hw0` or
`hw1` or `hw2`. This is the format for all tags (with a few
exceptions) that we will use this semester `hw#` where `#` is the
assignment number.
The `-c` flag is optional. **COMMIT** is the SHA of the commit you
wish to submit. In case you wanted to submit a different commit than
your most current one you would just provide the SHA for the commit to
be submitted. You can view your commit SHAs using the following
command:
> The SHA is the alphanumeric string before the first hyphen.
<pre>
$ git log --pretty=format:"%h - %s - %ar"
</pre>
With `git submit`, you may:
* Submit your assignment **only** from the master branch.
* The commits that you are submitting must be on the master branch or
merged into master (commit hash is on master).
* You may use other branches if you wish, but you cannot use
git-submit from these branches.
<pre>
$ git submit hw0
</pre>
This will submit your latest pushed commit for `hw0`. You can submit
as many times as you wish prior to any deadline.
#### How to check your submission
It creates a special branch in your repo called 'submit'. The only
reason it is special is that only instructors have permission to push
to it, hence why you need a special tool to submit your assignment. So
the submit tool tags the commit that you want to submit and merges
that commit into the submit branch under an authorized user.
Also, you should see a submission commit on the 'submit' branch. The
submission commit is a commit with a commit message formatted as
"<tag-name> submission commit". This you can see by navigating to
Repository > Commits and selecting the 'submit' branch from the
dropdown.
If you see both of these things, you have successfully submitted the
assignment. A successfully submitted homework assignment will have a
tag for that particular homework which you can see if you go to your
project in Gitlab and navigate to Repository > Tags.
---------------------------------------------
These are the tools and the dev environment you will be working with
for the rest of the semester. We have provided you with these tools to
ease the protocols of this course and prevent mishaps from
occurring.
So that you do not delay your ability to start on `hw1`, you should make
every effort to complete the steps above by **February 4, 2022**, 11:59PM.
After that date it will no longer be possible to `git submit hw0`,
however you should still make the commits to your repository described
above prior to submitting `hw1`.
Although `hw0` will not be formally scored and weighted into the final grade
calculations, you will not receive credit for any other assignments
in this course unless the signed Academic Honesty statement has previously
been committed to your repository and thereby incorporated into the
submissions for those assignments.

Binary file not shown.

1196
hw1-doc/README.md Normal file

File diff suppressed because it is too large Load Diff

1168
hw2-doc/DebuggingRef.md Normal file

File diff suppressed because it is too large Load Diff

420
hw2-doc/README.md Normal file
View File

@ -0,0 +1,420 @@
# Homework 2 Debugging and Fixing - CSE 320 - Spring 2022
#### Professor Eugene Stark
### **Due Date: Friday 3/4/2022 @ 11:59pm**
# Introduction
In this assignment you are tasked with updating an old piece of
software, making sure it compiles, and that it works properly
in your VM environment.
Maintaining old code is a chore and an often hated part of software
engineering. It is definitely one of the aspects which are seldom
discussed or thought about by aspiring computer science students.
However, it is prevalent throughout industry and a worthwhile skill to
learn. Of course, this homework will not give you a remotely
realistic experience in maintaining legacy code or code left behind by
previous engineers but it still provides a small taste of what the
experience may be like. You are to take on the role of an engineer
whose supervisor has asked you to correct all the errors in the
program, plus add additional functionality.
By completing this homework you should become more familiar
with the C programming language and develop an understanding of:
- How to use tools such as `gdb` and `valgrind` for debugging C code.
- Modifying existing C code.
- C memory management and pointers.
- Working with files and the C standard I/O library.
## The Existing Program
Your goal will be to debug and extend an old program called `par`,
which was written by Adam M. Costello and posted to Usenet in 1993.
I have rearranged the original source code and re-written the `Makefile`
to conform to the format we are using for the assignments in this course.
Besides a bug that was present in the original version, I have introduced
a few additional bugs here and there to make things more interesting
and educational for you :wink:.
Although you will need to correct these bugs in order to make the program
function, they do not otherwise change the program behavior from what
the author intended.
The `par` program is a simple paragraph reformatter. It is basically
designed to read text from the standard input, parse the text into
paragraphs, which are delimited by empty lines, chop each paragraph up
into a sequence of words (forgetting about the original line breaks),
choose new line breaks to optimize some criteria that are designed to
produce a pleasing result, and the finally output the paragraph with
the new line breaks. There are several parameters that can be set
which affect the result: the width of the output text, the length of
a "prefix" and a "suffix" to be prepended and appended to each output line,
a parameter "hang", which affects the default value of "prefix", and
a boolean parameter "last", which affects the way the last line of a
paragraph is treated.
What you have to do is to first get the program to compile (for the most part,
I did not modify the original code, which requires some changes for it
to compile cleanly with the compiler and settings we are using).
Then, you need to test the program and find and fix the bugs that prevent it
from functioning properly. Some of the bugs existed in the original version and
some I introduced for the purposes of this assignment.
Finally, you will make some modifications to the program.
As you work on the program, limit the changes you make to the minimum necessary
to achieve the specified objectives. Don't rewrite the program;
assume that it is essentially correct and just fix a few compilation errors and
bugs as described below. You will likely find it helpful to use `git` for this (I did).
Make exploratory changes first on a side branch (*i.e.* not the master branch),
then when you think you have understood the proper changes that need to be made,
go back and apply those changes to the master branch. Using `git` will help you
to back up if you make changes that mess something up.
### Getting Started - Obtain the Base Code
Fetch base code for `hw2` as you did for the previous assignments.
You can find it at this link:
[https://gitlab02.cs.stonybrook.edu/cse320/hw2](https://gitlab02.cs.stonybrook.edu/cse320/hw2).
Once again, to avoid a merge conflict with respect to the file `.gitlab-ci.yml`,
use the following command to merge the commits:
<pre>
git merge -m "Merging HW2_CODE" HW2_CODE/master --strategy-option=theirs
</pre>
> :nerd: I hope that by now you would have read some `git` documentation to find
> out what the `--strategy-option=theirs` does, but in case you didn't :angry:
> I will say that merging in `git` applies a "strategy" (the default strategy
> is called "recursive", I believe) and `--strategy-option` allows an option
> to be passed to the strategy to modify its behavior. In this case, `theirs`
> means that whenever a conflict is found, the version of the file from
> the branch being merged (in this case `HW2_CODE/master`) is to be used in place
> of the version from the currently checked-out branch. An alternative to
> `theirs` is `ours`, which makes the opposite choice. If you don't specify
> one of these options, `git` will leave conflict indications in the file itself
> and it will be necessary for you to edit the file and choose the code you want
> to use for each of the indicated conflicts.
Here is the structure of the base code:
<pre>
.
├── .gitlab-ci.yml
└── hw2
├── doc
│   ├── par.1
│   ├── par.doc
│   └── protoMakefile
├── hw2.sublime-project
├── include
│   ├── buffer.h
│   ├── debug.h
│   ├── errmsg.h
│   └── reformat.h
├── Makefile
├── rsrc
│   ├── banner.txt
│   ├── gettysburg.txt
│   └── loremipsum.txt
├── src
│   ├── buffer.c
│   ├── errmsg.c
│   ├── main.c
│   ├── par.c
│   └── reformat.c
├── test_output
│   └── .git-keep
└── tests
├── basecode_tests.c
├── rsrc
│   ├── banner.txt
│   ├── basic.in -> gettysburg.txt
│   ├── basic.out
│   ├── blank_lines.txt
│   ├── EOF.in
│   ├── EOF.out
│   ├── gettysburg.txt
│   ├── loremipsum.txt
│   ├── prefix_suffix.in -> banner.txt
│   ├── prefix_suffix.out
│   ├── valgrind_leak.in -> gettysburg.txt
│   ├── valgrind_leak.out
│   ├── valgrind_uninitialized.err
│   ├── valgrind_uninitialized.in -> loremipsum.txt
│   └── valgrind_uninitialized.out
├── test_common.c
└── test_common.h
</pre>
The `src` directory contains C source code files `buffer.c`. `par.c`, `reformat.c`,
and `errmsg.c`, which were part of the original code. In addition, I have added
a new file `main.c`, with a single `main()` function that simply calls
`original_main()` in `par.c`. This is to satisfy our requirement (for Criterion)
that `main()` is the only function in `main.c`.
The `include` directory contains C header files `buffer.h`, `reformat.h`, and
`errmsg.h`, which were part of the original source code. I have also added our
`debug.h` header file which may be of use to you.
The `doc` directory contains documentation files that were part of the original
distribution of `par`. The file `par.1` is in the format traditionally used
for Unix manual pages. This file `par.` is intended to be processed with the
the formatting program `nroff` with argument `-man`; for example:
`nroff -man doc/par.1 | less` could be used to format and view its contents.
The `tests` directory contains C source code (in file `basecode_tests.c`) for some Criterion
tests that can help guide you toward bugs in the program. These are not guaranteed
to be complete or exhaustive. The `test_common.c` and `test_common.h` contain auxiliary code
used by the tests. The subdirectory `tests/rsrc` contains input files and reference output files
that are used by the tests.
The `par` program was not designed to be particularly conducive to unit testing,
so all the tests we will make (including the tests used in grading) will be so-called
"black box" tests, which test the input-output behavior of the program running as a
separate process from the test driver.
The `test_common.c` file contains helper functions for launching an instance of `par`
as a separate process, redirecting `stdin` from an input file, collecting the
output produced on `stdout` and `stderr`, checking the exit status of the program,
and comparing the output against reference output.
The `test_output` directory is a "dummy" directory which is used to hold the output
produced when you run the Criterion tests. Look there if you want to understand,
for example, why the tests reported that the output produced by your program was
not as expected.
Before you begin work on this assignment, you should read the rest of this
document. In addition, we additionally advise you to read the
[Debugging Document](DebuggingRef.md). One of the main goals of this assignment
is to get you to learn how to use the `gdb` debugger, so you should right away
be looking into how to use this while working on the tasks in the following sections.
# Part 1: Debugging and Fixing
You are to complete the following steps:
1. Clean up the code; fixing any compilation issues, so that it compiles
without error using the compiler options that have been set for you in
the `Makefile`.
Use `git` to keep track of the changes you make and the reasons for them, so that you can
later review what you have done and also so that you can revert any changes you made that
don't turn out to be a good idea in the end.
2. Fix bugs.
Run the program, exercising the various options, and look for cases in which the program
crashes or otherwise misbehaves in an obvious way. We are only interested in obvious
misbehavior here; don't agonize over program behavior that might just have been the choice
of the original author. You should use the provided Criterion tests to help point the way,
though they are not guaranteed to be exhaustive.
3. Use `valgrind` to identify any memory leaks or other memory access errors.
Fix any errors you find.
Run `valgrind` using a command of the following form:
<pre>
$ valgrind --leak-check=full --show-leak-kinds=all --undef-value-errors=yes [PAR PROGRAM AND ARGS]
</pre>
Note that the bugs that are present will all manifest themselves in some way
either as incorrect output, program crashes or as memory errors that can be
detected by `valgrind`. It is not necessary to go hunting for obscure issues
with the program output.
Also, do not make gratuitous changes to the program output, as this will
interfere with our ability to test your code.
> :scream: The author of this program was pretty fastidious about freeing memory before
> exiting the program. Once you have fixed the bugs, the program should exit without
> any type of memory leak reported by `valgrind`, including memory that is "still reachable"
> at the time of exit. "Still reachable" memory corresponds to memory that is in use
> when the program exits and can still be reached by following pointers from variables
> in the program. Although some people consider it to be untidy for a program
> to exit with "still reachable" memory, it doesn't cause any particular problem.
> For the present program, however, there should not be any "still reachable" memory.
> :scream: You are **NOT** allowed to share or post on PIAZZA
> solutions to the bugs in this program, as this defeats the point of
> the assignment. You may provide small hints in the right direction,
> but nothing more.
# Part 2: Changes to the Program
## Rewrite/Extend Options Processing
The basecode version of `par` performs its own *ad hoc* processing of command-line options.
This is likely due to the fact that there did not exist a commonly accepted library
package for performing this function at the time the program was written.
However, as options processing is a common function that is performed by most programs,
and it is desirable for programs on the same system to be consistent in how they interpret
their arguments, there have been more elaborate standardized libraries that have been written
for this purpose. In particular, the POSIX standard specifies a `getopt()` function,
which you can read about by typing `man 3 getopt`. A significant advantage to using a
standard library function like `getopt()` for processing command-line arguments,
rather than implementing *ad hoc* code to do it, is that all programs that use
the standard function will perform argument processing in the same way
rather than having each program implement its own quirks that the user has to remember.
For this part of the assignment, you are to replace the original argument-processing
code in `main()` by code that uses the GNU `getopt` library package.
In addition to the POSIX standard `getopt()` function, the GNU `getopt` package
provides a function `getopt_long()` that understands "long forms" of option
arguments in addition to the traditional single-letter options.
In your revised program, `main()` should use `getopt_long()` to traverse the
command-line arguments, and it should support the following option syntax
(in place of what was originally used by the program):
- `--version` (long form only):
Print the version number of the program.
- `-w WIDTH` (short form) or `--width WIDTH` (long form):
Set the output paragraph width to `WIDTH`.
- `-p PREFIX` (short form) or `--prefix PREFIX` (long form):
Set the value of the "prefix" parameter to `PREFIX`.
- `-s SUFFIX` (short form) or `--suffix SUFFIX` (long form):
Set the value of the "suffix" parameter to `SUFFIX`.
- `-h HANG` (short form) or `--hang HANG` (long form):
Set the value of the "hang" parameter to `HANG`.
- `-l LAST` (short form) or either `--last` or
`--no-last` (long form):
Set the value of the boolean "last" parameter.
For the short form, the values allowed for `LAST` should be either
`0` or `1`.
- `-m MIN` (short form) or either `--min` or `--no-min` (long form).
Set the value of the boolean "min" parameter.
For the short form, the values allowed for `MIN` should be either
`0` or `1`.
You will probably need to read the Linux "man page" on the `getopt` package.
This can be accessed via the command `man 3 getopt`. If you need further information,
search for "GNU getopt documentation" on the Web.
> :scream: You MUST use the `getopt_long()` function to process the command line
> arguments passed to the program. Your program should be able to handle cases where
> the (non-positional) flags are passed IN ANY order. Make sure that you test the
> program with prefixes of the long option names, as well as the full names.
## Revise the Error Message Scheme
The original program uses a very *ad hoc* scheme for error-message reporting:
if an error occurs, a string describing the error is stored into a global
character array `errmsg` with a hard-coded maximum size. (This hard-coded
size has an occurrence in the `fprintf()` format string in `par.c`,
which creates undesirable implicit coupling between `par.c` and `errmsg.c`.)
At various points in the program, the existence of an error condition is checked
by looking to see if the first character of the error message string is a null
character `'\0'`. Before the program terminates, if an error message exists,
then it is printed and the program exits with an error status, otherwise it exits
with a success indication.
Your job is to revise the error message scheme to make it somewhat more general
and to eliminate the hard-coded limitation on the length of an error message.
In particular, you should replace the interface defined in `errmsg.h` by the
following function prototypes (exactly as shown):
```c
/**
* @brief Set an error indication, with a specified error message.
* @param msg Pointer to the error message. The string passed by the caller
* will be copied.
*/
void set_error(char *msg);
/**
* @brief Test whether there is currently an error indication.
* @return 1 if an error indication currently exists, 0 otherwise.
*/
int is_error();
/**
* @brief Issue any existing error message to the specified output stream.
* @param file Stream to which the error message is to be issued.
* @return 0 if either there was no existing error message, or else there
* was an existing error message and it was successfully output.
* Return non-zero if the attempt to output an existing error message
* failed.
*/
int report_error(FILE *file);
/**
* Clear any existing error indication and free storage occupied by
* any existing error message.
*/
void clear_error();
```
The global array `errmsg` should be removed from `errmsg.h` and replaced
by a pointer variable declared as `static char *` in `errmsg.c`.
The functions whose prototypes are given above should be implemented so
that there is no fixed maximum imposed on the length of an error message.
This means that error messages should be dynamically allocated on the
heap (for example, using `strdup()`). The implementation should take care
not to leak any memory used for error messages; for example if a new error
message is set when one already exists. Before exiting, the program should
call `clear_error()` to cause any existing error message to be freed.
# Part 3: Testing the Program
For this assignment, you have been provided with a basic set of
Criterion tests to help you debug the program.
In the `tests/basecode_tests.c` file, there are five test examples.
You can run these with the following command:
<pre>
$ bin/par_tests
</pre>
To obtain more information about each test run, you can supply the
additional option `--verbose=1`.
You can also specify the option `-j1` to cause the tests to be run sequentially,
rather than in parallel using multiple processes, as is the default.
The `-j1` flag is necessary if the tests could interfere with each other in
some way if they are run in parallel (such as writing the same output file).
You will probably find it useful to know this; however the basecode tests have
been written so that they each use output files named after the test and
(hopefully) will not interfere with each other.
The tests have been constructed so that they will point you at most of the
problems with the program.
Each test has one or more assertions to make sure that the code functions
properly. If there was a problem before an assertion, such as a "segfault",
the test will print the error to the screen and continue to run the
rest of the tests.
The basecode test cases check the program operation by reading input from
a pre-defined input file, redirecting `stdout` and `stderr` to output files,
and comparing the output produced against pre-defined reference files.
Some of the tests use `valgrind` to verify that no memory errors are found.
If errors are found, then you can look at the log file that is left behind
(in the `test_output` directory) by the test code.
Alternatively, you can better control the information that `valgrind` provides
if you run it manually.
The tests included in the base code are not true "unit tests", because they all
run the program as a black box using `system()`.
You should be able to follow the pattern to construct some additional tests of
your own, and you might find this helpful while working on the program.
You are encouraged to try to write some of these tests so that you learn how
to do it. Note that in the next homework assignment unit tests will likely
be very helpful to you and you will be required to write some of your own.
Criterion documentation for writing your own tests can be found
[here](http://criterion.readthedocs.io/en/master/).
> :scream: Be sure that you test non-default program options to make sure that
> the program does not crash or otherwise misbehave when they are used.
# Hand-in Instructions
Ensure that all files you expect to be on your remote repository are committed
and pushed prior to submission.
This homework's tag is: `hw2`
<pre>
$ git submit hw2
</pre>

1079
hw3-doc/README.md Normal file

File diff suppressed because it is too large Load Diff

621
hw4-doc/README.md Normal file
View File

@ -0,0 +1,621 @@
# 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`.

File diff suppressed because it is too large Load Diff