Operating Systems 2019F: Tutorial 2
In this tutorial you will be experimenting with and extending 3000shell.c (listed below).
Make sure you use the original code from 3000shell for each question.
Openstack
You should create a VM on the new SCS openstack cluster at openstack-stein.scs.carleton.ca and do your work there. Documentation on the cluster is here. While you don't need a persistent VM for this lab, it will be important for future tuturials - and it is nice to have your work stick around when you leave the lab.
Make sure you have added the ssh-ping security group to your network interface and that you have associated a floating IP address with your instance. The 192.168.X.X IP addresses are private (and cannot be accessed outside of the openstack cluster), the 134.117.X.X floating IP addresses can be accessed from the Carleton network and will allow you to access the wider Internet.
Note that you must be on the Carleton network to use openstack. When you are off campus, connect using the Carleton VPN.
Create a VM using the latest COMP 3000 snapshot image. Please create a machine with two VCPUs. The user is student, default password is student. Please change your password after you first connect to your machine (using the passwd command).
Make sure you connect via ssh. Windows 10 and MacOS have ssh clients available from their command lines, just type "ssh student@<IP address>" where the IP address is the floating IP address you assigned to your VM. PuTTY also works, and you can use x2go. DO NOT use the web console, as it is glitchy!
The image provides an "scs-backup" command that will backup the student user's directory to the SCS linux machines. So if your SCS username is janedoe, you can type
scs-backup janedoe
and it will create a copy of everything in the student account in a directory called "COMP3000VM-backup" in your home directory. You can ssh/sftp to access.scs.carleton.ca in order to access this copy of your VM's files. You should do backups at the end of every session and before you do anything dangerous.
The scs-backup bash function is listed below. Feel free to adapt to your own needs; however, realize that rsync is a very powerful command that can delete arbitrary files at the specified destination (and in fact that is what the listed command does to the backup directory). If you are changing any arguments, be sure to test with the -n option so you can see what will happen!
Note that you cannot take snapshots of your VM, so please don't try (it will keep trying and never succeed).
Getting Started
You should download 3000shell.c on a Ubuntu Linux 18.04 system or similar. Compile it using the command
gcc -O -Wall 3000shell.c -o 3000shell
Tasks/Questions
The purpose of the following questions and tasks is to help you understand how 3000shell works. At the end of this you should have an understanding of every function and every line of the code. If you understand 3000shell, then you understand the basics of all UNIX shells. Your understanding of the code will be tested in Assignment 1 and on the exams, so use this opportunity to dive deep into the code. These tasks and questions should help you build up a mental model of how 3000shell works.
- Compile and run 3000shell.c
- Try running programs in the background using & after commands entered in 3000shell. What happens to the input and output of the program? Try this for simple programs like ls and bc. Then, try it for more complex interactive programs such as nano and top.
- You may have trouble interacting with the shell after running programs in the background. How can you recover from such a situation?
- 3000shell implements a simple form of output redirection. What syntax should you use to redirect standard output to a file?
- Why are lines 207-210 there (the check for pid == -1)?
- Make find_binary show every attempt to find a binary.
- Make the shell output "Ouch!" when you send it a SIGUSR1 signal.
- Delete line 324 (SA_RESTART). How does the behavior of 3000shell change?
- Replace the use of find_env() with getenv(). How do their interfaces differ?
- Make plist output the parent process id for each process, e.g. "5123 ls (5122)". Pay attention to the stat and status files in the per-process directories in /proc.
- Implement redirection of standard error
- Implement redirection of standard out for plist() (the same as if it was an external command).
- Implement a built-in 3000kill command that works like the standard kill command.
Code
3000shell.c
/* 3000shell.c */
/* v2 Sept. 15, 2019 */
/* v1 Sept. 24, 2017 */
/* based off of csimpleshell.c, Enrico Franchi © 2005
https://web.archive.org/web/20170223203852/
http://rik0.altervista.org/snippets/csimpleshell.html */
/* Original under "BSD" license */
/* This version is under GPLv3, copyright 2017, 2019 Anil Somayaji */
/* You really shouldn't be incorporating parts of this in any other code,
it is meant for teaching, not production */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <dirent.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
#define COMM_SIZE 32
const char *proc_prefix = "/proc";
void parse_args(char *buffer, char** args,
size_t args_size, size_t *nargs)
{
char *buf_args[args_size]; /* You need C99 */
char **cp, *wbuf;
size_t i, j;
wbuf=buffer;
buf_args[0]=buffer;
args[0] =buffer;
for(cp=buf_args; (*cp=strsep(&wbuf, " \n\t")) != NULL ;){
if ((*cp != NULL) && (++cp >= &buf_args[args_size]))
break;
}
for (j=i=0; buf_args[i]!=NULL; i++){
if (strlen(buf_args[i]) > 0)
args[j++]=buf_args[i];
}
*nargs=j;
args[j]=NULL;
}
/* this is kind of like getenv() */
char *find_env(char *envvar, char *notfound, char *envp[])
{
const int MAXPATTERN = 128;
int i, p;
char c;
char pattern[MAXPATTERN];
char *value = NULL;
p = 0;
while ((c = envvar[p])) {
pattern[p] = c;
p++;
if (p == (MAXPATTERN - 2)) {
break;
}
}
pattern[p] = '=';
p++;
pattern[p] = '\0';
i = 0;
while (envp[i] != NULL) {
if (strncmp(pattern, envp[i], p) == 0) {
value = envp[i] + p;
}
i++;
}
if (value == NULL) {
return notfound;
} else {
return value;
}
}
void find_binary(char *name, char *path, char *fn, int fn_size) {
char *n, *p;
int r, stat_return;
struct stat file_status;
if (name[0] == '.' || name[0] == '/') {
strncpy(fn, name, fn_size);
return;
}
p = path;
while (*p != '\0') {
r = 0;
while (*p != '\0' && *p != ':' && r < fn_size - 1) {
fn[r] = *p;
r++;
p++;
}
fn[r] = '/';
r++;
n = name;
while (*n != '\0' && r < fn_size) {
fn[r] = *n;
n++;
r++;
}
fn[r] = '\0';
stat_return = stat(fn, &file_status);
if (stat_return == 0) {
return;
}
if (*p != '\0') {
p++;
}
}
}
void setup_comm_fn(char *pidstr, char *comm_fn)
{
char *c;
strcpy(comm_fn, proc_prefix);
c = comm_fn + strlen(comm_fn);
*c = '/';
c++;
strcpy(c, pidstr);
c = c + strlen(pidstr);
strcpy(c, "/comm");
}
void plist()
{
DIR *proc;
struct dirent *e;
int result;
char comm[COMM_SIZE]; /* seems to just need 16 */
char comm_fn[512];
int fd, i, n;
proc = opendir(proc_prefix);
if (proc == NULL) {
fprintf(stderr, "ERROR: Couldn't open /proc.\n");
}
for (e = readdir(proc); e != NULL; e = readdir(proc)) {
if (isdigit(e->d_name[0])) {
setup_comm_fn(e->d_name, comm_fn);
fd = open(comm_fn, O_RDONLY);
if (fd > -1) {
n = read(fd, comm, COMM_SIZE);
close(fd);
for (i=0; i < n; i++) {
if (comm[i] == '\n') {
comm[i] = '\0';
break;
}
}
printf("%s: %s\n", e->d_name, comm);
} else {
printf("%s\n", e->d_name);
}
}
}
result = closedir(proc);
if (result) {
fprintf(stderr, "ERROR: Couldn't close /proc.\n");
}
}
void signal_handler(int the_signal)
{
int pid, status;
if (the_signal == SIGHUP) {
fprintf(stderr, "Received SIGHUP.\n");
return;
}
if (the_signal != SIGCHLD) {
fprintf(stderr, "Child handler called for signal %d?!\n",
the_signal);
return;
}
pid = wait(&status);
if (pid == -1) {
/* nothing to wait for */
return;
}
if (WIFEXITED(status)) {
fprintf(stderr, "\nProcess %d exited with status %d.\n",
pid, WEXITSTATUS(status));
} else {
fprintf(stderr, "\nProcess %d aborted.\n", pid);
}
}
void run_program(char *args[], int background, char *stdout_fn,
char *path, char *envp[])
{
pid_t pid;
int fd, *ret_status = NULL;
char bin_fn[BUFFER_SIZE];
pid = fork();
if (pid) {
if (background) {
fprintf(stderr,
"Process %d running in the background.\n",
pid);
} else {
pid = wait(ret_status);
}
} else {
find_binary(args[0], path, bin_fn, BUFFER_SIZE);
if (stdout_fn != NULL) {
fd = creat(stdout_fn, 0666);
dup2(fd, 1);
close(fd);
}
if (execve(bin_fn, args, envp)) {
puts(strerror(errno));
exit(127);
}
}
}
void prompt_loop(char *username, char *path, char *envp[])
{
char buffer[BUFFER_SIZE];
char *args[ARR_SIZE];
int background;
size_t nargs;
char *s;
int i, j;
char *stdout_fn;
while(1){
printf("%s $ ", username);
s = fgets(buffer, BUFFER_SIZE, stdin);
if (s == NULL) {
/* we reached EOF */
printf("\n");
exit(0);
}
parse_args(buffer, args, ARR_SIZE, &nargs);
if (nargs==0) continue;
if (!strcmp(args[0], "exit")) {
exit(0);
}
if (!strcmp(args[0], "plist")) {
plist();
continue;
}
background = 0;
if (strcmp(args[nargs-1], "&") == 0) {
background = 1;
nargs--;
args[nargs] = NULL;
}
stdout_fn = NULL;
for (i = 1; i < nargs; i++) {
if (args[i][0] == '>') {
stdout_fn = args[i];
stdout_fn++;
printf("Set stdout to %s\n", stdout_fn);
for (j = i; j < nargs - 1; j++) {
args[j] = args[j+1];
}
nargs--;
args[nargs] = NULL;
break;
}
}
run_program(args, background, stdout_fn, path, envp);
}
}
int main(int argc, char *argv[], char *envp[])
{
struct sigaction signal_handler_struct;
char *username;
char *default_username = "UNKNOWN";
char *path;
char *default_path = "/usr/bin:/bin";
memset (&signal_handler_struct, 0, sizeof(signal_handler_struct));
signal_handler_struct.sa_handler = signal_handler;
signal_handler_struct.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &signal_handler_struct, NULL)) {
fprintf(stderr, "Couldn't register SIGCHLD handler.\n");
}
if (sigaction(SIGHUP, &signal_handler_struct, NULL)) {
fprintf(stderr, "Couldn't register SIGHUP handler.\n");
}
username = find_env("USER", default_username, envp);
path = find_env("PATH", default_path, envp);
prompt_loop(username, path, envp);
return 0;
}
.bash_aliases
scs-backup () {
if [ ! $1 ]; then
echo "Please give your SCS username"
else
rsync -a -v --delete --force --exclude .cache /home/student/ $1@access.scs.carleton.ca:COMP3000VM-backup/
fi
}