# ECS150 Project1
## Summary
This program, `sshell`, offers user interface to the operating system's services.
This program supports some well known commands such that cd, ls, pwd and so on.
This program also supports commands with up to three pipes.
## Implementation
The implementation of this program follows four distinct steps:
1. Parsing the user commands given as user input.
2. Performing based on the user's commands.
3. Response
3(a). Display the return result with a completed message if user's command is correctly executed.
3(b). Display a precise error message if the entered command is not recognized by the program.
4. Repeat step1 to step3 untill a user inputs "exit" and stops the program.
## Parsing
最初にユーザーによって入力されたコマンドラインを分割し、`myobj`と名付けられた構造体に格納する処理を行う。
## パイプラインでの区切り
コマンドラインにパイプ記号(|)があるかをチェックし、パイプの総数とパイプ記号区切りになった文字列をmyobjと名付けられた構造体に保存する。このセクションはperse_by_pipe_charの関数が担っており、ユーザーによって入力されたコマンドにパイプが含まれていた場合、文字列をパイプ区切りでmyobj構造体の中にそれぞれ保存する。ここでパイプの総数も保存する。
## コマンドごとの区切り
パイプラインによって分割されたそれぞれのコマンドライン(1から4つ)の処理を行う。このセクションはparse_each_cmdの関数が担っており、まずコマンドに<または>が含まれていないかをチェックし、もし含まれていた場合は前後に1つのスペースを挿入する。この処理は次に行うスペース区切りで文字列を分割し配列に格納する処理をするためである。文字列をスペース区切りで分割したら構造体に保存する。
## エラーチェック
先程のユーザーから入力されたコマンドの編成の途中と、終わったあとで正しいコマンドが入力されているかをチェックする機能を追加した。エラーが確認されたら、エラーに応じてユーザーに返すエラー文章をstderrにてターミナルに表示させる。これらのエラーが発生したときにはターミナルを終了するのではなく、ユーザーに新しいインプットをさせるためにsshell@UCD:を表示させる。
実装したエラーのは以下の通りである。
- [x] Error: too many process arguments
- [x] Error: missing command
- [x] Error: no output file
- [x] Error: cannot open output file
- [ ] Error: mislocated output redirection
- [x] Error: no input file
- [x] Error: cannot open input file
- [x] Error: cannot cd into directory
- [x] Error: command not found
- [x] Error: no such directory
- [x] Error: directory stack empty
### Error: too many process arguments
ex. cd 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
このエラーはargumentsが多すぎるときに表示させるエラー。先程説明したそれぞれのパイプラインからargumentsに分けて保存する際に、argumentsの数がargumentsの上限数+メインコマンドの1つを足し合わせた17個より多くなったときにこのエラーを返す。
### Error: missing command
ex. | grep hello
このエラーはそれぞれのパイプラインにて、コマンドが入力されていないときに発生する。パイプラインごとにそれぞれのargumentsを確認し、1つ目がNULLであるときはパイプラインにargumentsが1つも入っていないことになる。たとえ1つ目がNULLではなくても、1つ目が<や>などのファイルIOの場合はファイルへのコマンドが存在しないことになる。
### Error: no output file
ex. echo >
>が登場する同じパイプライン内で>の次のargumentがNULLである場合はアウトプットが入力されていないためこのエラーを返す
### Error: cannot open output file
ex. echo hello > /etc/passwd
これはアウトプットファイルは定義されているが、write権限がないために開くことが出来ないときに表示される。
### Error: no input file
`ex. cat <`
output errorと同様に<の次のargumentがNULLのときはファイル名が入力されていないとしてエラーを返す。outputとは異なり、入力されたインプットファイルが存在するかを確認して、なければエラーを返す。
### Error: cannot cd into directory
ex.cd doesnotexist
cdコマンドはexecvpで呼ばれるのではなく自作したビルトインコマンドで呼ばれる。ここではcdで与えられたdirectoryがない場合にdirectoryを移動せずにエラーを表示する。
### Error: no such directory
このコマンドは上のエラーと同じ意味、実装方法だが、こちらはスタックを使ったpushdのときに表示されるエラーである。
### Error: command not found
ex. windows98
system()関数とwhichコマンドを利用することで実行しているOSに実行しようとしているコマンドファイルが存在するかどうかを確認し、存在しないときにこのエラーを表示する。
### Error: directory stack empty
ex. popd (when thers's no element in the stack)
dirs/pushdによって作成されたパスのスタックが空のときにpopdを行うときに、popする要素がないためこのエラーが表示される。
### Error: mislocated output redirection
このエラーは実装できなかった。
## ビルトインコマンド
実装したビルトインコマンドは以下の5つである。これらはexecvpを通して実行する必要がないためforkを使う前に実行し、完了したらユーザーに次のインプットを聞く。
- exit
- cd
- pwd
- dirs
- pushd
- popd
### exit
exitが入力されたときにはstderrとしてbyeと表示し、listenを中止する。
### cd
chardir()を使ってcdで与えられたdirectoryに遷移する。
### pwd
現在のパスを取得し、ターミナルに表示する。
### dirs
dirs/pushd/popdはdirectoryの遷移をスタックで管理する。スタックを定義するときに、現在のdirectoryのパスを追加しておく。dirsコマンドでスタックの中に保存されているdirectoriesをターミナルに表示する。
### pushd
cdと実装は同じだが、問題なく遷移できたときに遷移前のパスをスタックに追加し、スタックサイズを1つ増やす。
### popd
最後に追加したパスを取得し、そのパスに遷移する。スタックのサイズを1つ減らす。スタックサイズが0の場合は、エラーを返す。
## ビルトインコマンド以外の実装
コマンドはパイプラインの数だけ再帰を行う。まずメインのプロセスからforkして、パイプを作り、更にforkします。親プロセスはstdinをパイプに繋いで右端(右から0番目)のコマンドの実行、子はstdoutをパイプに繋いで更にパイプを作ってforkして、今度は親になった先程の子が右端から1番目のコマンドを実行、子はまたforkしてパイプを作り……と繰り返していって、左端のコマンドに達したら実行する。
再帰を利用することで子のexit時に複数のreturn valueを親に返すことができなくなってしまった。更にforkされた親と子ではアドレスが同じでも環境をまるごとコピーしているため、子の中での変更は親には影響されない。親や子でもreturn valueをグローバルvariableのように利用したかったため、mmap() を利用して、return valuesを格納する配列を作成した。これによって親や子や孫でも共有され最終的にそれぞれのプロセスで実行されたコマンドのreturn valuesを取得し、すべてのコマンドが実行されたあとで表示することが出来た。
更に、それぞれのコマンドを実行する前に、インプットやアウトプット記号があるかを確認し、あればファイルをdup2()を用いてファイルのインプット元、アウトプット先をstdout,stdinに変更する。そして、インプットやアウトプット記号を消去することでそれ以降のコマンドがファイルを通して実行されることになる。
# ECS150 Project1
## Project Members
- Daisuke Horiuchi (919535721)
- Ken Taniguchi (919537457)
## Summary
This program, `sshell`, offers user interface to the operating system's
services. This program supports some well known commands such that cd, ls, pwd
and so on. This program also supports commands with up to three pipes.
## Implementation
The implementation of this program follows four distinct steps:
1. Parsing the user commands given as user input.
2. Performing based on the user's commands.
3. Response:
3(a). Display the return result with a completed message,
if user's command is correctly executed.
3(b). Display a precise error message if the entered command
is not recognized by the program.
4. Repeat step1 to step3 until a user inputs "exit" and stops the program.
## Parsing
Split the command line arguments entered by the user and store it in a
structure named `myobj`.
### Parsing by Pipelines
Check if the command line has a pipe symbol (|) and the total number of pipes
and the pipe symbol delimited string are stored in a structure named myobj.
This section is handled by the function named `perse_by_pipe_char`, which saves
each pipe-symbol-delimited string in the structure. We also store the total
number of pipes to the structure here.
### Parsing by space characters
The entire user's command line was divided into one to four string(s) by the
function named `perse_by_pipe_char`. Now we need to parse those strings by
space characters. This section is handled by the function named
`parse_each_cmd`, which first checks to see if the strings contain '<' or '>',
and if so inserts a single space before and after it. This process is for
the next process of dividing the strings by space characters and storing those
divided strings in an array. After parsing the string with space characters,
save it in a structure.
## Error checking
Added functions to check whether the correct command is entered during and
after the command input from the user. When an error is confirmed, display the
error message to the user using `stderr`. Instead of exiting the terminal when
these errors occur, print `sshell@UCD:` to prompt the user for new input.
The error messages we implemented is following:
- [x] Error: too many process arguments
- [x] Error: missing command
- [x] Error: no output file
- [x] Error: cannot open output file
- [ ] Error: mislocated output redirection
- [x] Error: no input file
- [x] Error: cannot open input file
- [x] Error: cannot cd into directory
- [x] Error: command not found
- [x] Error: no such directory
- [x] Error: directory stack empty
### Error: too many process arguments
`ex. cd 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16`
This error is displayed when there are too many arguments. When dividing and
saving arguments from each pipeline explained earlier, this error is returned
when the number of arguments exceeds 16.
### Error: missing command
`ex. | grep hello`
This error occurs when no commands have been entered in each pipeline.
Check each argument for each pipeline, and if the first is `NULL`, the pipeline
does not contain any arguments. Even if the first is not `NULL`, if the first is
a file Input/Output/Redirection such as '<' or '>', there will be no command to
the file.
### Error: no output file
`ex. echo >`
In the same pipeline where '>' appears, if the next argument of '>' is `NULL`,
this error is returned because the output has not been input.
### Error: cannot open output file
`ex. echo hello > /etc/passwd`
This is displayed when an output file is defined but cannot be opened due to
lack of write permission.
### Error: no input file
`ex.cat<`
Similar to output error, if the argument next to '<' is `NULL`, an error will be
returned as no file name has been entered. Unlike output, it checks if the
input file exists and returns an error if it doesn't.
### Error: cannot cd into directory
`ex.cd doesnotexist`
The `cd` command is not called by `execvp` but by a self-made built-in command.
Here, if there is no directory given by `cd`, an error is displayed without
moving the directory.
### Error: no such directory
This command has the same meaning and implementation method as the above error,
but this is the error displayed when pushing with the stack.
### Error: command not found
`ex. windows98`
By using the `system()` function and the `which` command, it checks whether the
command file to be executed exists in the OS, and displays this error
if the file does not exist.
### Error: directory stack empty
`ex. popd (when there's no element in the stack)`
If a user inputs `popd` when the stack of paths created by dirs/pushd is empty,
the user gets this error because there is no elements to pop.
### Error: mislocated output redirection
This error could not be implemented.
## Built-in commands
The implemented built-in commands are the following five. These don't need to
be run through `execvp` so they are executed before using fork and ask the user
for further input after the process is done.
- exit
- cd
- pwd
- dirs
- pushd
- popd
### exit
When exit is typed, print "bye..." as stderr and stop listening user inputs.
### cd
Using `chardir()` to transition to the directory given by cd.
### pwd
Get the current path and display it in the terminal.
### dirs
dirs/pushd/popd manage directory transitions in the stack.
Display the directories saved in the stack with the dirs command on the terminal.
Also the dirs always shows the current path.
### pushd
The implementation is the same as `cd`, but when the transition is successful,
the path before transition is added to the stack and the stack size is
increased by one.
### popd
Get the last added path and make a transition to that path.
Reduce the stack size by one. If the stack size is 0, return an error.
## Implementations for the other than built-in commands
The program is called recursively as many times as there are pipelines.
First fork from the main process, create a pipe, and fork again. The parent
process connects stdin to the pipe and after the wait of the child process,
executes the rightmost command. The child connects stdout to the pipe,
creates another pipe and forks, and this time the child who became the parent
starts executing from the second command from the rightmost,
fork the child again to create a pipe. Repeating this process
until the leftmost command is reached and executed.
With recursion, it becomes impossible to return multiple return values to
the parent when the child exited. Since fork copies the entire environment,
the forked parent and child have exactly the same address. However, changes in
the child are not affected to the parent because the forked parent and child
have different physical memories (They have the same virtual memories).
We wanted to store the return value into like a global variable in the parent and
child, so we used `mmap()` to create an array that stores the return values
from the forked parent, child, and grandchildren and so on.
This made it possible to keep storing the return values of commands
after all commands were executed.
Furthermore, before executing each command, check if there is an input or
output symbol. If so, use `dup2()` to change the input source and output
destination of the file to stdout and stdin. Then, by deleting the arguments
after the input or output symbols, subsequent commands, which do not have
file redirection arguments, will be executed through the file.