Modified by: Maythas Wangcharoenwong 20231012
This ICSH (IC shell) was implemented as a part of MUIC's PCSA (Principle of System Architecture) course by Maythas Wangcharoenwong. The shell is mostly implemented in a similar manner to how bash would react to certain commands. However, we will be quite strict on syntaxes meaning that the input must strictly be in the same format as designed in order to make the shell perform as intended.
- Basic built-in commands
- Script mode
- Basic IO redirection
- Jobs control
- Using external program.
- Basic signal handling
- Piping
cmd_A | cmd_B | cmd_C
- Weird I/O redirection
a < b < c > d > e > f
rm -rf /
your Host device given you're running this in a VM.- A lot of stuffs.
- Built-in commands:
echo <text>
: prints out the text to the console.!!
: repeat the previous input per every occurrence of "!!"exit <num>
: exit the shell with the given number.
- Command Tokenizer <string to vector>
- Command Enumerator.
- Handling empty input.
- Handling bad input.
- Implemented Script mode.
./icsh <file>
- Implemented running external program on foreground.
- (Ctrl+Z) sends SIGTSTP to foreground process suspending and bringing them to background.
- (Ctrl+C) sends SIGINT to foreground process terminating the process.
- Built-in commands:
echo $?
: print the exit status of the most recently terminated process.
- Handling exit value from child processes.
- Implemented one-time I/O redirection per input for all commands.
<command> > <file>
: redirect output of the command to file.<command> < <file>
: redirect input of command to the file.
- Implemented foreground background jobs.
- Implemented terminal control switching.
- Handle SIGCHLD.
- Built-in commands:
fg %<job_id>
: continue a stopped job in the background and bring them to foreground.bg %<job_id>
: continue a stopped job in the background.jobs
: shows the list and statuses of jobs.
- Built-in Commands:
!!
: note: this functionality is implemented right from v0.1.0help
: shows basic command syntaxes.cd <dir>
: change directory.
- The shell first begins by calling functions to set up enumerators, maps, setting up signal handlers, and backing up file descriptors for STDIO.
- The shell then check what modes is being used and run accordingly.
- The program then enters the loop and receive input from terminal.
- The received input is then tokenized and is then passed to get enumerators accordingly.
- The enumerators is then used to decide which function to call.
- Return to point 3 until exit is received.
SETUP → MODE-CHECK → *RECIEVE INP → TOKENIZE → PASSED TO FUNCTIONS → *
- The background jobs will be listed by their IDs incrementally inside a global vector.
- The shell pgid is stored separately for cases of restoring control of terminal back to the shell.
- The data of each job is stored as a struct called
Job
, in this struct there are 6 fields.
status ← the status of the current job (refer to jobs(0) for details.)
pgid ← process group id
job_id ← the id of the job
is_alive ← tells if the current job is stil alive
fg_dead ← tells if the job died in the foreground
checked ← tells if job has been checked and marked dead by the update_job_list() function.
has_bg ← tells whether this job has been to the background.
pid_list ← stores the list of pids in this job
When a job is created, it can either start in the background or foreground, if the job was created in the background, job_id will be assigned to the new job, for the newly created foreground job, the job_id will be set to 0. However, once the foreground job is sent to the background, a new job_id will be assigned to it as well as marking has_bg as true meaning that no matter how many times that specific job is brought to background/foreground, it will have the same job_id as well as allowing O(1) access by job_id.
Every prompt, there will be a function to check whether checked is marked for all jobs and sending not alive jobs statues to console will run once. Once all jobs are considered finished, the count of the job id will reset allowing the next job_id to start at 1 again.
Since we're using SIGCHLD to deal with zombies and Signaling status of child Asynchronously, it is logical to try prevent possible issues from race conditions.
The field is_alive can only be set in one way meaning that, no matter what the status of the current job is, if for any reason there exist a status update that came way slower (after the the job has already been terminated) the job will not be considered nor updated to a new status (stay dead).
The exit statuses for processes getting killed by signals is calculated using 128+n
to get the real exit status and showing correct messages.
In this section we will discuss the syntaxes and functionalities of each commands.
The character ↪
will indicate the return value or value printed to console.
NAME
echo
- prints the message specified to the console.
SYNOPSIS
1. echo <input_text>
2. echo $?
DESCRIPTION case 1: the command echo will print the all the texts that comes after the command echo to the console. All this whitespaces in the start and end of the input will be dropped when printed out to the console. The consecutive whitespace characters at other positions will be truncated to at most 1 character of whitespace per gap.
Example:
echo Never gonna give you up
↪ Never gonna give you up
case 2: the command echo will print the most recent exit status of the terminated foreground process or built-in commands to the console. note: echo will behave in this manner iff the only text following the command echo
is $?
.
Example:
echo $?
↪ 130
NAME
exit
- exit the shell with the given exit status.
SYNOPSIS
exit <num>
DESCRIPTION
The shell exits and the return value is set to <num>
.
NAME
cd
- change the current directory to the specified directory.
SYNOPSIS
1. cd <dir>
2. cd ~
DESCRIPTION
case 1: The current directory is set to <dir>
if the specified directory is accessible, otherwise and error will be thrown to the console.
case 2: The current directory will be set to HOME.
NAME
cd
- change the current directory to the specified directory.
SYNOPSIS
cd
DESCRIPTION The current directory will be set to HOME.
NAME
jobs
- show the current jobs and their statuses list on the console.
SYNOPSIS
jobs
DESCRIPTION
Show the current jobs list and their statuses to the console in the format
[job_id] <status> <command>
if the job is running in the background, there will be &
sign added to the back of the job.
[1] Running sleep 2 &
[2] Done ./test
[3] Interrupted ./a
[4] Stopped sleep 50
[5] Killed sleep 5
[6] EXIT 143 sleep 1
Each statuses have these meanings accordingly:
1. Running : The job is currently running in the background.
2. Done : The job have terminated normally.
3. Interrupted : The job was terminated by SIGINT or (Ctrl+C)
4. Stopped : The job is stopped by one of the stop signals including SIGTSTP. (Ctrl+Z)
5. Killed : The job was terminated by the signal SIGKILL.
6. EXIT <num> : The job is killed, stopped by the other signals not mentioned above.
Jobs status behavior
After any jobs has entered a finished state, (any status apart from running and stopped.) After the shell has received the signal, by the next prompt, the job will stop showing in the job list when jobs
is called.
NAME
bg %<job_id>
- continue background job.
SYNOPSIS
bg %<job_id>
DESCRIPTION
This command will continue any jobs in the background with the corresponding <job_id>
, it the job doesn't exits or has already finished, "Invalid job id" will be written to the console. If the background job is already running, do nothing. If the current job with the corresponding <job_id>
is already running, a message will be printed to the console.
NAME
fg %<job_id>
- bring background job to foreground and continue.
SYNOPSIS
fg %<job_id>
DESCRIPTION
This command will bring the background job to foreground according to its <job_id>
, and continues the job.
NAME
!!
- double bang, repeats the previous input string.
SYNOPSIS
# Assume the previous command is echo a
!!
↪ echo a
↪ a
!!!!
↪ echo aecho a
↪ aecho a
!!!
↪ echo aecho a!
↪ aecho a!
DESCRIPTION
This command is implemented in the same manner as bash, using REGEX, every occurrence of !!
will now be replaced by the previous input string. After the new input string is built, it is now sent to the command processing function to act accordingly.
NAME
<command> > <file>
- Redirecting command output to file.
SYNOPSIS
<command> > <file>
DESCRIPTION
This command allow only one time redirection per input, the first occurrence of >
will be the splitting point between the command and the file.
Example:
echo a > test.txt
Doing this will write a to the file test.txt
NAME
<command> < <file>
- Redirection input of the command to the file.
SYNOPSIS
<command> < <file>
DESCRIPTION
This command allow only one time redirection per input, the first occurrence of <
will be the splitting point between the command and the file.
Example:
cat < test.txt
↪ a
NAME
<external command>
- Call other external commands.
SYNOPSIS
<external command>
DESCRIPTION
Any input that does not match with the syntaxes prior to this will fall into this case. The command <external command>
will be called after forking using execvp. The <external command>
will perform accordingly the way it is programmed to if exist and the argument is passed correctly, otherwise, an error will be thrown to the console.
END OF MANUAL