Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • operating-systems/assignment-template
  • alin_andrei.enasoiu/assignment-async-web-server
  • operating-systems/assignment-parallel-graph
  • alexandru.braslasu/assignment-parallel-graph
  • operating-systems/assignment-async-web-server
  • bogdan.ciocea/assignment-mini-libc
  • rconstantinescu2006/assignment-mini-libc
  • alexandru.chirac/assignment-mini-libc
  • alexandru.bologan/assignment-mini-libc
  • rconstantinescu2006/assignment-parallel-graph
  • bogdan.ciocea/assignment-parallel-graph
  • matei.mantu/assignment-parallel-graph
  • alexandru.bologan/assignment-parallel-graph
  • vadim.plamadeala/assignment-parallel-graph
  • alexandru.chirac/assignment-parallel-graph
  • teodora.teodorescu/assignment-parallel-graph
  • radu.popescu0910/assignment-parallel-graph
  • bogdan.ciocea/assignment-mini-shell
  • andrei.miga/assignment-parallel-graph
  • alexandru.chirac/assignment-mini-shell
  • alexandru.chirac/assignment-async-web-server
  • rares_andrei.ticus/assignment-mini-shell
  • rconstantinescu2006/assignment-mini-shell
  • alexandru.braslasu/assignment-mini-shell
  • bogdan.ciocea/assignment-async-web-server
  • gheorghe.petrica/assignment-mini-shell
  • rconstantinescu2006/assignment-async-web-server
  • gheorghe.petrica/assignment-async-web-server
  • alexandru.braslasu/assignment-async-web-server
  • alexandru.bologan/assignment-mini-shell
  • alexandru.bologan/assignment-async-web-server
  • iudith_maria.sara/assignment-async-web-server
  • mircea.beznea/assignment-mini-libc
  • alexandru.dabija04/assignment-mini-libc
  • dinu.merceanu/assignment-mini-libc
  • george.simion2005/assignment-mini-libc
  • andrei.nicola/assignment-mini-libc
  • george.simion2005/assignment-mini-shell
  • operating-systems/assignment-parallel-firewall
  • sebastian.marcu/assignment-parallel-firewall
  • mihail.necula/assignment-parallel-firewall
  • alexandru.dabija04/assignment-parallel-firewall
  • george.simion2005/assignment-parallel-firewall
  • albert_mark.stan/assignment-parallel-firewall
  • alexandru.barbu2809/assignment-parallel-firewall
  • dana_maria.caruntu/assignment-parallel-firewall
  • rares.celescu/assignment-parallel-firewall
  • antonio.ciocodeica/assignment-parallel-firewall
  • bianca.perian/assignment-parallel-firewall
  • darius.constantin04/assignment-async-web-server
  • mihail.necula/assignment-async-web-server
  • antonio.ciocodeica/assignment-async-web-server
  • costin.spataru/assignment-async-web-server
  • dana_maria.caruntu/assignment-async-web-server
  • george.simion2005/assignment-async-web-server
  • albert_mark.stan/assignment-async-web-server
56 results
Show changes
Commits on Source (2)
......@@ -26,6 +26,6 @@ build:
checker:
stage: test
image:
name: gitlab.cs.pub.ro:5050/operating-systems/assignment-template
name: gitlab.cs.pub.ro:5050/operating-systems/assignment-mini-shell
script:
- echo ""
......@@ -11,4 +11,5 @@ filter=-runtime/int
filter=-runtime/printf
filter=-build/include_subdir
filter=-readability/todo
filter=-build/include_what_you_use
linelength=120
......@@ -3,3 +3,5 @@ FROM gitlab.cs.pub.ro:5050/operating-systems/assignments-docker-base
COPY ./checker ${CHECKER_DATA_DIRECTORY}
RUN mkdir ${CHECKER_DATA_DIRECTORY}/../tests
COPY ./tests ${CHECKER_DATA_DIRECTORY}/../tests
RUN mkdir ${CHECKER_DATA_DIRECTORY}/../util
COPY ./util ${CHECKER_DATA_DIRECTORY}/../util
# Minishell
## Objectives
- Learn how shells create new child processes and connect the I/O to the terminal.
- Gain a better understanding of the `fork()` function wrapper.
- Learn to correctly execute commands written by the user and treat errors.
## Statement
### Introduction
A shell is a command-line interpreter that provides a text-based user interface for operating systems.
Bash is both an interactive command language and a scripting language.
It is used to interact with the file system, applications, operating system and more.
For this assignment you will build a Bash-like shell with minimal functionalities like traversing the file system, running applications, redirecting their output or piping the output from one application into the input of another.
The details of the functionalities that must be implemented will be further explained.
### Shell Functionalities
#### Changing the Current Directory
The shell will support a built-in command for navigating the file system, called `cd`.
To implement this feature you will need to store the current directory path because the user can provide either relative or absolute paths as arguments to the `cd` command.
The built-in `pwd` command will show the current directory path.
Check the following examples below to understand these functionalities.
```sh
> pwd
/home/student
> cd operating-systems/assignments/minishell
> pwd
/home/student/operating-systems/assignments/minishell
> cd inexitent
no such file or directory
> cd /usr/lib
> pwd
/usr/lib
```
> **_NOTE:_** Using the `cd` command without any arguments or with more than one argument doesn't affect the current directory path.
> Make sure this edge case is handled in a way that prevents crashes.
>
> **_BONUS 1:_** You can implement `cd` (without parameters) which will change the current directory to `$HOME` variable, if defined.
>
> **_BONUS 2:_** You can implement `cd -` which will change the current directory to `$OLDPWD` variable, if defined.
#### Closing the Shell
Inputting either `quit` or `exit` should close the minishell.
#### Running an Application
Suppose you have an executable named `sum` in the current directory.
It takes arbitrarily many numbers as arguments and prints their sum to `stdout`.
The following example shows how the minishell implemented by you should behave.
```sh
> ./sum 2 4 1
7
```
If the executable is located at the `/home/student/sum` absolute path, the following example should also be valid.
```sh
> /home/student/sum 2 4 1
7
```
Each application will run in a separate child process of the minishell created using [fork](https://man7.org/linux/man-pages/man2/fork.2.html).
#### Environment Variables
Your shell will support using environment variables.
The environment variables will be initially inherited from the `bash` process that started your minishell application.
If an undefined variable is used, its value is the empty string: `""`.
> **_NOTE:_** The following examples contain comments which don't need to be supported by the minishell.
> They are present here only to give a better understanding of the minishell's functionalities.
```sh
> NAME="John Doe" # Will assign the value "John Doe" to the NAME variable
> AGE=27 # Will assign the value 27 to the AGE variable
> ./identify $NAME $LOCATION $AGE # Will translate to ./identify "John Doe" "" 27 because $LOCATION is not defined
```
A variable can be assigned to another variable.
```sh
> OLD_NAME=$NAME # Will assign the value of the NAME variable to OLD_NAME
```
#### Operators
##### Sequential Operator
By using the `;` operator, you can chain multiple commands that will run sequentially, one after another.
In the command `expr1; expr2` it is guaranteed that `expr1` will finish before `expr2` is be evaluated.
```sh
> echo "Hello"; echo "world!"; echo "Bye!"
Hello
world!
Bye!
```
##### Parallel Operator
By using the `&` operator you can chain multiple commands that will run in parallel.
When running the command `expr1 & expr2`, both expressions are evaluated at the same time (by different processes).
The order in which the two commands finish is not guaranteed.
```sh
> echo "Hello" & echo "world!" & echo "Bye!" # The words may be printed in any order
world!
Bye!
Hello
```
##### Pipe Operator
With the `|` operator you can chain multiple commands so that the standard output of the first command is redirected to the standard input of the second command.
Hint: Look into [anonymous pipes](https://man7.org/linux/man-pages/man2/pipe.2.html) and file descriptor inheritance while using [fork](https://man7.org/linux/man-pages/man2/fork.2.html).
```sh
> echo "Bye" # command outputs "Bye"
Bye
> ./reverse_input
Hello # command reads input "Hello"
olleH # outputs the reversed string "olleH"
> echo "world" | ./reverse_input # the output generated by the echo command will be used as input for the reverse_input executable
dlrow
```
##### Chain Operators for Conditional Execution
The `&&` operator allows chaining commands that are executed sequentially, from left to right.
The chain of execution stops at the first command **that exits with an error (return code not 0)**.
```sh
# throw_error always exits with a return code different than 0 and outputs to stderr "ERROR: I always fail"
> echo "H" && echo "e" && echo "l" && ./throw_error && echo "l" && echo "o"
H
e
l
ERROR: I always fail
```
The `||` operator allows chaining commands that are executed sequentially, from left to right.
The chain of execution stops at the first command **that exits successfully (return code is 0)**.
```sh
# throw_error always exits with a return code different than 0 and outputs to stderr "ERROR: I always fail"
> ./throw_error || ./throw_error || echo "Hello" || echo "world!" || echo "Bye!"
ERROR: I always fail
ERROR: I always fail
Hello
```
##### Operator Priority
The priority of the available operators is the following.
The lower the number, the **higher** the priority:
1. Pipe operator (`|`)
1. Conditional execution operators (`&&` or `||`)
1. Parallel operator (`&`)
1. Sequential operator (`;`)
#### I/O Redirection
The shell must support the following redirection options:
- `< filename` - redirects `filename` to standard input
- `> filename` - redirects standard output to `filename`
- `2> filename` - redirects standard error to `filename`
- `&> filename` - redirects standard output and standard error to `filename`
- `>> filename` - redirects standard output to `filename` in append mode
- `2>> filename` - redirects standard error to `filename` in append mode
Hint: Look into [open](https://man7.org/linux/man-pages/man2/open.2.html), [dup2](https://man7.org/linux/man-pages/man2/dup.2.html) and [close](https://man7.org/linux/man-pages/man2/close.2.html).
## Support Code
The support code consists of three directories:
- `src/` is the skeleton mini-shell implementation.
You will have to implement missing parts marked as `TODO` items.
- `util/` stores a parser to be used as support code for implementing the assignment.
- `tests/` are tests used to validate (and grade) the assignment.
### Building mini-shell
To build mini-shell, run `make` in the `src/` directory:
```console
student@so:~/.../assignment-mini-shell$ cd src/
student@so:~/.../assignment-mini-shell/src$ make
```
## Testing and Grading
The testing is automated.
Tests are located in the `tests/` directory.
```console
student@so:~/.../assignment-mini-shell/tests$ ls -F
Makefile run_all.sh* _test/
```
To run the checker and everything else required, use the `make check` command in the `tests/` directory:
```console
student@so:~/.../assignment-mini-shell/tests$ make check
make[1]: Entering directory '...'
rm -f *~
[...]
16) Testing sleep command...................................failed [ 0/100]
17) Testing fscanf function.................................failed [ 0/100]
18) Testing unknown command.................................failed [ 0/100]
Total: 0/100
```
For starters, tests will fail.
Each test is worth a number of points.
The total number of points is `90`.
The maximum grade is obtained by dividing the number of points to `10`, for a maximum grade of `9.00`.
A successful test run will show the output:
```console
student@so:~/.../assignment-mini-shell/tests$ make check
make[1]: Entering directory '...'
rm -f *~
[...]
00) Sources check...........................................passed [ 5/100]
01) Testing commands without arguments......................passed [ 5/100]
02) Testing commands with arguments.........................passed [ 5/100]
03) Testing simple redirect operators.......................passed [ 5/100]
04) Testing append redirect operators.......................passed [ 5/100]
05) Testing current directory...............................passed [ 5/100]
06) Testing conditional operators...........................passed [ 5/100]
07) Testing sequential commands.............................passed [ 5/100]
08) Testing environment variables...........................passed [ 5/100]
09) Testing single pipe.....................................passed [ 5/100]
10) Testing multiple pipes..................................passed [ 5/100]
11) Testing variables and redirect..........................passed [ 5/100]
12) Testing overwritten variables...........................passed [ 5/100]
13) Testing all operators...................................passed [ 5/100]
14) Testing parallel operator...............................passed [ 5/100]
15) Testing big file........................................passed [ 5/100]
16) Testing sleep command...................................passed [ 5/100]
17) Testing fscanf function.................................passed [ 5/100]
18) Testing unknown command.................................passed [ 5/100]
Total: 90/100
```
The actual tests are located in the `inputs/` directory.
```console
student@os:~/.../assignment-mini-shell/tests/$ ls -F _test/inputs
test_01.txt test_03.txt test_05.txt test_07.txt test_09.txt test_11.txt test_13.txt test_15.txt test_17.txt
test_02.txt test_04.txt test_06.txt test_08.txt test_10.txt test_12.txt test_14.txt test_16.txt test_18.txt
```
### Debug
To inspect the differences between the output of the mini-shell and the reference binary set `DO_CLEANUP=no` in `tests/_test/run_test.sh`.
To see the results of the tests, you can check `tests/_test/outputs/` directory.
### Memory leaks
To inspect the unreleased resources (memory leaks, file descriptors) set `USE_VALGRIND=yes` and `DO_CLEANUP=no` in `tests/_test/run_test.sh`.
You can modify both the path to the Valgrind log file and the command parameters.
To see the results of the tests, you can check `tests/_test/outputs/` directory.
......@@ -9,7 +9,7 @@ cd "$(dirname "$0")" || exit 1
RED='\033[0;31m'
NC='\033[0m'
DEFAULT_IMAGE_NAME=operating-systems/assignment-tutorial
DEFAULT_IMAGE_NAME=operating-systems/assignment-mini-shell
DEFAULT_TAG='latest'
DEFAULT_REGISTRY='gitlab.cs.pub.ro:5050'
......
/mini-shell
UTIL_PATH ?= ../util
CC = gcc
CFLAGS = -g -Wall
OBJ_PARSER = $(UTIL_PATH)/parser/parser.tab.o $(UTIL_PATH)/parser/parser.yy.o
OBJ = main.o cmd.o utils.o
TARGET = mini-shell
.PHONY = build clean build_parser
all: $(TARGET)
$(TARGET): build_parser $(OBJ) $(OBJ_PARSER)
$(CC) $(CFLAGS) $(OBJ) $(OBJ_PARSER) -o $(TARGET)
build_parser:
$(MAKE) -C $(UTIL_PATH)/parser/
clean:
-rm -rf $(OBJ) $(OBJ_PARSER) $(TARGET) *~
// SPDX-License-Identifier: BSD-3-Clause
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include "cmd.h"
#include "utils.h"
#define READ 0
#define WRITE 1
/**
* Internal change-directory command.
*/
static bool shell_cd(word_t *dir)
{
/* TODO: Execute cd. */
return 0;
}
/**
* Internal exit/quit command.
*/
static int shell_exit(void)
{
/* TODO: Execute exit/quit. */
return 0; /* TODO: Replace with actual exit code. */
}
/**
* Parse a simple command (internal, environment variable assignment,
* external command).
*/
static int parse_simple(simple_command_t *s, int level, command_t *father)
{
/* TODO: Sanity checks. */
/* TODO: If builtin command, execute the command. */
/* TODO: If variable assignment, execute the assignment and return
* the exit status.
*/
/* TODO: If external command:
* 1. Fork new process
* 2c. Perform redirections in child
* 3c. Load executable in child
* 2. Wait for child
* 3. Return exit status
*/
return 0; /* TODO: Replace with actual exit status. */
}
/**
* Process two commands in parallel, by creating two children.
*/
static bool run_in_parallel(command_t *cmd1, command_t *cmd2, int level,
command_t *father)
{
/* TODO: Execute cmd1 and cmd2 simultaneously. */
return true; /* TODO: Replace with actual exit status. */
}
/**
* Run commands by creating an anonymous pipe (cmd1 | cmd2).
*/
static bool run_on_pipe(command_t *cmd1, command_t *cmd2, int level,
command_t *father)
{
/* TODO: Redirect the output of cmd1 to the input of cmd2. */
return true; /* TODO: Replace with actual exit status. */
}
/**
* Parse and execute a command.
*/
int parse_command(command_t *c, int level, command_t *father)
{
/* TODO: sanity checks */
if (c->op == OP_NONE) {
/* TODO: Execute a simple command. */
return 0; /* TODO: Replace with actual exit code of command. */
}
switch (c->op) {
case OP_SEQUENTIAL:
/* TODO: Execute the commands one after the other. */
break;
case OP_PARALLEL:
/* TODO: Execute the commands simultaneously. */
break;
case OP_CONDITIONAL_NZERO:
/* TODO: Execute the second command only if the first one
* returns non zero.
*/
break;
case OP_CONDITIONAL_ZERO:
/* TODO: Execute the second command only if the first one
* returns zero.
*/
break;
case OP_PIPE:
/* TODO: Redirect the output of the first command to the
* input of the second.
*/
break;
default:
return SHELL_EXIT;
}
return 0; /* TODO: Replace with actual exit code of command. */
}
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef _CMD_H
#define _CMD_H
#include "../util/parser/parser.h"
#define SHELL_EXIT -100
/**
* Parse and execute a command.
*/
int parse_command(command_t *cmd, int level, command_t *father);
#endif /* _CMD_H */
// SPDX-License-Identifier: BSD-3-Clause
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../util/parser/parser.h"
#include "cmd.h"
#include "utils.h"
#define PROMPT "> "
#define CHUNK_SIZE 1024
void parse_error(const char *str, const int where)
{
fprintf(stderr, "Parse error near %d: %s\n", where, str);
}
/**
* Readline from mini-shell.
*/
static char *read_line(void)
{
char *line = NULL;
int line_length = 0;
char chunk[CHUNK_SIZE];
int chunk_length;
char *rc;
int endline = 0;
while (!endline) {
rc = fgets(chunk, CHUNK_SIZE, stdin);
if (rc == NULL)
break;
chunk_length = strlen(chunk);
if (chunk[chunk_length - 1] == '\n') {
if (chunk_length > 1 && chunk[chunk_length - 2] == '\r')
/* Windows */
chunk[chunk_length - 2] = 0;
else
chunk[chunk_length - 1] = 0;
endline = 1;
}
line = realloc(line, line_length + CHUNK_SIZE);
DIE(line == NULL, "Error allocating command line");
line[line_length] = '\0';
strcat(line, chunk);
line_length += CHUNK_SIZE;
}
return line;
}
static void start_shell(void)
{
char *line;
command_t *root;
int ret;
for (;;) {
printf(PROMPT);
fflush(stdout);
ret = 0;
root = NULL;
line = read_line();
if (line == NULL)
return;
parse_line(line, &root);
if (root != NULL)
ret = parse_command(root, 0, NULL);
free_parse_memory();
free(line);
if (ret == SHELL_EXIT)
break;
}
}
int main(void)
{
start_shell();
return EXIT_SUCCESS;
}
// SPDX-License-Identifier: BSD-3-Clause
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "utils.h"
/**
* Concatenate parts of the word to obtain the command.
*/
char *get_word(word_t *s)
{
char *string = NULL;
int string_length = 0;
const char *substring = NULL;
int substring_length = 0;
while (s != NULL) {
if (s->expand == true) {
substring = getenv(s->string);
/* Prevents strlen from failing. */
if (substring == NULL)
substring = "";
} else {
substring = s->string;
}
substring_length = strlen(substring);
string = realloc(string, string_length + substring_length + 1);
DIE(string == NULL, "Error allocating word string.");
string[string_length] = '\0';
strcat(string, substring);
string_length += substring_length;
s = s->next_part;
}
return string;
}
/**
* Concatenate command arguments in a NULL terminated list in order to pass
* them directly to execv.
*/
char **get_argv(simple_command_t *command, int *size)
{
char **argv;
int argc;
word_t *param;
argc = 1;
/* Get parameters number. */
param = command->params;
while (param != NULL) {
param = param->next_word;
argc++;
}
argv = calloc(argc + 1, sizeof(char *));
DIE(argv == NULL, "Error allocating argv.");
argv[0] = get_word(command->verb);
DIE(argv[0] == NULL, "Error retrieving word.");
param = command->params;
argc = 1;
while (param != NULL) {
argv[argc] = get_word(param);
DIE(argv[argc] == NULL, "Error retrieving word.");
param = param->next_word;
argc++;
}
*size = argc;
return argv;
}
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef _UTILS_H
#define _UTILS_H
#include "../util/parser/parser.h"
/* Useful macro for handling error codes. */
#define DIE(assertion, call_description) \
do { \
if (assertion) { \
fprintf(stderr, "(%s, %s, %d): ", \
__FILE__, __func__, __LINE__); \
perror(call_description); \
exit(EXIT_FAILURE); \
} \
} while (0)
/**
* Concatenate parts of the word to obtain the command.
*/
char *get_word(word_t *s);
/**
* Concatenate command arguments in a NULL terminated list in order to pass
* them directly to execv.
*/
char **get_argv(simple_command_t *command, int *size);
#endif /* _UTILS_H */
_test/outputs
mini-shell
SRC_PATH ?= ../src
SOURCEDIR = src
BUILDDIR = bin
SRCS = $(sort $(wildcard $(SOURCEDIR)/*.c))
BINS = $(patsubst $(SOURCEDIR)/%.c, $(BUILDDIR)/%, $(SRCS))
.PHONY: all clean src check lint
all: src
src:
make -C $(SRC_PATH) UTIL_PATH=$(shell pwd)/../util CPPFLAGS=-I$(shell pwd)/../src
check:
make -C $(SRC_PATH) UTIL_PATH=$(shell pwd)/../util clean
make clean
make -i SRC_PATH=$(SRC_PATH)
SRC_PATH=$(SRC_PATH) ./run_all.sh
lint:
-cd .. && checkpatch.pl -f src/*.c
-cd .. && cpplint --recursive src/
-cd .. && shellcheck checker/*.sh
-cd .. && shellcheck tests/*.sh
-cd .. && shellcheck tests/_test/*.sh
clean:
-rm -f *~
uname
whoami
exit
whoami --help
uname --help
uname -a
uname -s -m
exit
echo mumu > out_01.txt
cat < out_01.txt > out_02.txt
echo <out_01.txt> out_03.txt
gcc 2> err1.txt
gcc &>err2.txt
uname -a > overwrite.txt
uname > overwrite.txt
exit
\ No newline at end of file
echo '#include <stdio.h>' > main.c
echo 'int main(void) {' >> main.c
echo 'fprintf(stderr, "test_err\n");' >> main.c
echo 'fprintf(stdout, "test_out\n");' >> main.c
echo 'return 0;' >> main.c
echo '}' >> main.c
gcc -o main main.c
./main >> out.txt
cp out.txt out2.txt
./main &> out2.txt
cp out2.txt out3.txt
./main 2>> out3.txt
exit
rm -fr _x_y_z_/
mkdir -p _x_y_z_/_a_b_c_/_1_2_3_
cd _x_y_z_/_a_b_c_/_1_2_3_
cd /new/york/san/francisco > out-err.txt
pwd > pwd.txt
cd ..
cd .. > out.txt
pwd > pwd.txt
cd
exit
uname > out_seq.txt ; echo "test" >> out_seq.txt
true || echo "test" > out_nzero1.txt
false || echo "test" > out_nzero2.txt
true && echo "test" > out_zero1.txt
false && echo "test" > out_zero2.txt
echo "beta" > out_par1.txt && echo "alfa" > out_par2.txt
rm -fr _x_y_z_
mkdir -p _x_y_z_/_a_b_c_
cd _x_y_z_/_a_b_c_ && echo "test" > out_zero3.txt
cd _non_existent_ || echo "test" > out_nzero3.txt
exit