- Paweł Kutyła - kierownik
- Władysław Młynik
- Konrad Jurczyński-Chu
Napisać prosty interaktywny interpreter poleceń (shell) posiadający minimum następujące funkcje:
- Możliwość uruchamiania programów w postaci potoku arbitralnej długości: „proga | progb | progc”.
- Interpreter powinien uruchamiać dowolne standardowe programy wraz z argumentami.
- Możliwość przekierowania we/wy do plików: „>”, „<”; łączenie konstrukcji przekierowania z potokami
- Podstawowe operacje wbudowane: cd, pwd, echo
- Obsługa cytowania, tj. ‘ … ‘ (pojedyncze zwykłe cudzysłowy) .
- Obsługa zmiennych shella i środowiskowych (np. PATH; HOME, itp.); poprzez konstrukcję „=” oraz - „export” – ta funkcja nie jest wymagana, ale jej realizacja ułatwia testowanie i demonstrację - funkcjonalności, zwłaszcza w odniesieniu do niektórych wariantów.
- Pozostałych funkcji typowego shell-a (konstrukcje warunkowe i sterujące, funkcje, aliasy, tablice, rozwijanie wzorców plików, itd.) nie trzeba realizować, chyba że wynikają z zadanego wariant (niżej).
- W przypadku wątpliwości, co do zachowania i składni, należy bazować na shellu „sh/bash”.
- Dozwolone, a nawet wskazane, jest użycie jednego ze standardowych narzędzi systemu Unix służących do realizacji analizy leksykalnej i syntaktycznej.
- Wymaganie dodatkowe: W12 - wzorce nazw plików: tj. *, […], \ .
(tj. doprecyzowanie treści), w tym definicje i opisy wymaganych i dodatkowych funkcji.
-
Możliwość uruchamiania potoków arbitralnej długości - dowolne wywołania programów (zgodne z dalszymi wymaganiami) rozdzielone znakiem | uruchamiają wszystkie programy jednocześnie, przekazując wyjście programu przed znakiem | na wejście programu z prawej strony znaku |. Można tworzyć dowolnej długości potoki (dowolna liczba wywołań programów oddzielonych znakiem |)
-
Możliwość uruchamiania programów z podaniem ścieżki względnej lub bezwzględnej lub w przypadku podania nazwy bez ścieżki, uruchamianie programów z katalogów w zmiennej PATH; do programów przekazywane są argumenty oddzielone od nazwy programu i siebie nawzajem dowolną liczbą białych znaków
-
Na końcu wywołania programu (po wszystkich argumentach) można wpisać znak > lub <, a następnie nazwę lub ścieżkę do pliku. w przypadku znaku > wyjście programu jest przekazywane do tego pliku, w przypadku znaku < wejście programu jest wczytywane z tego pliku. Znak < lub > musi być oddzielony od poprzedzającej części polecenia dowolną liczbą białych znaków. Po znaku < lub > mogą ale nie muszą pojawić się białe znaki. Można użyć obydwu przekierowań dla jednego programu, w dowolnej kolejności. W przypadku łączenia z potokami, kiedy jeden koniec potoku jest zastąpiony przekierowaniem, drugi jest zamykany.
-
Polecenia cd, pwd i echo są poleceniami wbudowanymi, nie uruchamiają zewnętrznych programów.
- cd zmienia bieżący katalog na podany
- pwd wypisuje bieżący katalog
- echo wypisuje swoje argumenty, oddzielone spacją
- W tekście zamkniętym w pojedynczych cudzysłowach ('...') żadne znaki nie są traktowane specjalnie z wyjątkiem:
- znak ' - kończy cytowany tekst, jeśli nie jest poprzedzony przez \
- znak \ - sprawie że następny znak nie jest traktowany specjalnie, użyteczne kombinacje: ', \\
- Polecenie ZMIENNA=wartość, jeśli nie jest cytowane, przypisuje wartość do zmiennej powłoki ZMIENNA. ZMIENNA może być dowolnym ciągiem liter i cyfr, zaczynającym się od litery. Polecenie export ZMIENNA ustawia zmienną środowiskową ZMIENNA na wartość zmiennej powłoki ZMIENNA. Polecenie export ZMIENNA=wartość ustawia zmienną środowiskową ZMIENNA na podaną wartość.
- W12 - W argumentach mogą występować wyrażenia, zastępowane tworząc ścieżkę istniejącego pliku. jeśli jest więcej pasujących plików, są przekazywane jako osobne argumenty. jeśli nie ma pasującego pliku, nie są zastępowane:
- * - zastępowane dowolnym ciągiem znaków innych niż /
- [...] - zastępowane dowolnym znakiem wewnątrz nawiasów kwadratowych, mogą być podane zakresy znaków, np. a-z, znaki specjalne (spacja, ], , *, -, itp muszą być poprzedzone znakiem \). Dowolny znak, łącznie ze znakami specjalnymi, poprzedzony znakiem \ jest traktowany dosłownie.
najlepiej w punktach: tj. Podać i krótko opisać docelową formę prototypów głównych funkcji I definicje struktur danych.
-
void run_line(char *line){.c};
Przyjmuje linię z poleceniem, przetwarza ją i wykonuje. -
string_arr_t split_tokens(char *line){.c};
Rozdziela linię na tokeny, oddzielając je białymi znakami, które nie są poprzedzone znakiem \ ani nie są częścią cytowania. Nie usuwa znaków \ ani cytowania. -
string_arr_t expand_wildcards(char *pattern){.c};
Obsługuje wzorce nazw plików (np. *, [...]), zwracając tablicę pasujących nazw plików. -
command_arr_t parse_line(string_arr_t tokens){.c}; Interpretuje tokeny jako elementy poleceń, obsługując cytowanie oraz znak . -
void execute_line(command_arr_t commands);
Wykonuje wszystkie polecenia w potoku lub wykonuje pojedyncze polecenie, jeśli jest tylko jedno. -
void execute_program(char* program, int argc, char **argv, int input_fd, int output_fd){.c};
Uruchamia program z podanymi argumentami oraz odpowiednimi deskryptorami wejścia i wyjścia. -
bool execute_builtin(char* program, int argc, char **argv, int input_fd, int output_fd){.c};
Wykonuje polecenie wbudowane, zwraca true, jeśli takie polecenie istnieje. -
pipe_t create_pipe(){.c};
Tworzy nowy potok. -
void set_env_variable(char *name, char *value){.c};
Ustala zmienną środowiskową. -
void set_shell_variable(char *name, char *value, shell_var_t *shell_vars){.c};
Ustala zmienną powłoki.
typedef struct {
int size;
char **data;
} string_arr_t;
typedef struct {
int size;
command_t *data;
} command_arr_t;
typedef struct {
int input_fd;
int output_fd;
} pipe_t;
typedef struct {
bool is_set_shell_var;
char *program;
int argc;
char **argc;
char *redirect_in;
char *redirect_out;
char *shell_var_name;
char *shell_var_value;
} command_t;
typedef struct {
char *name;
char *val;
shell_var_t *next;
} shell_var_t;-
Polecenie jest wczytywane przy użyciu funkcji
getline. -
Polecenie wpisane przez użytkownika trafia do funkcji
run_line(char *line), gdzie rozpoczyna się proces parsowania. -
Funkcja
split_tokens(char *line)dzieli linię na tokeny, uwzględniając zasady cytowania i specjalne znaki. Wynikiem jest tablica tokenów przechowywana w strukturzestring_arr_t. Odbywać się to będzie poprzez przeglądanie linii znak po znaku i wyszukiwanie białych znaków, które nie są poprzedzone znakiem \ ani nie znajdują się w pojedynczym cudzysłowie. -
Funkcja
expand_wildcards(char *pattern)zastępuje wzorce nazw plików odpowiednimi ścieżkami, jeśli istnieje pasujący plik, co pozwala na dynamiczne generowanie listy argumentów. Wykorzystana będzie funkcjaglobze standardowej biblioteki C. -
Tokeny trafiają do funkcji
parse_line(string_arr_t tokens), która tworzy strukturęcommand_arr_t, interpretując ich znaczenie (np. identyfikacja potoków, przekierowań, zmiennych). Rozpoznawane są specjalne tokeny jak |, >, ZMIENNA=wartość. Wykorzystując fakt, że funkcja split_tokens pozostawia cytowanie, można odróżnić tokeny specjalne od argumentów poleceń je zawierających, np. | oznacza potok, a ‘|’ jest traktowane jako argument polecenia. Na tym etapie cytowanie jest usuwane. -
Każdy element
command_tzawiera komplet informacji o jednym poleceniu: nazwę programu, argumenty, deskryptory wejścia/wyjścia lub informacje o ustawieniu zmiennej powłoki: nazwę i wartość zmiennej. -
Funkcja
execute_line(command_arr_t commands)uruchamia wszystkie polecenia w kolejności wynikającej z parsowania. Tworzy potoki za pomocąpipe()i synchronizuje procesy za pomocąwaitpid(). Najpierw próbując uruchomić polecenie wbudowane przy użyciu funkcjiexecute_builtin, jeśli nie ma takiego polecenia wbudowanego, funkcja zwraca false i uruchamiany jest program przy użyciu funkcjiexecute_program. Funkcjom tym przekazywane są deskryptory plików potoków lub przekierowań.
Wykorzystanie zostanie składnia podobna do składni powłoki bash, i zasadniczo została opisana w sekcji interpretacja treści zadania. Elementy składni i ich przykłady użycia:
- Przypisanie zmiennej: ZMIENNA=wartość, ZMIENNA=’war tość’
- Wykonanie programu lub polecenia wbudowanego: program arg1 arg2, program arg1, /bin/program arg1 arg2, ./program arg1, ‘./pro gram’ ‘arg 1’
- Wykonanie programu lub polecenia wbudowanego z potokiem: program1 arg1 arg2 | ‘prog ram2’ arg3 arg4
- Wykonanie programu lub polecenia wbudowanego z przekierowaniem do pliku: program arg1 arg2 > plik, plik > ‘pro gram’, plik1 > program1 arg1 | /bin/program2 arg2 > plik2
- Wykorzystanie wzorców nazw plików: /katalog/, katalog/.txt, plik_[a-z]*-[1-3].txt
- Cytowanie: ‘pro gram’, ‘\\katalog\\pl ik’, ‘cy \’ tat’
struktur danych, metod komunikacji, metod synchronizacji, moduły wraz z przepływem sterowania i danych między nimi.
shell_var_t- do przechowywania zmiennych powłoki wykorzystana zostanie lista jednokierunkowa z powodu nieznanej z góry ilości zmiennych. Składa się z nazwy zmiennej, wartości zmiennej oraz wskaźnika na następną zmienną.command_t- do przechowywania informacji o komendzie. Zawiera zmienną informującą czy komenda jest ustawieniem zmiennej powłoki. Na jej podstawie niektóre zmienne, które w danym przypadku nie mają zastosowania, mogą mieć wartość null.- W pozostałych przypadkach wykorzystano proste struct-y do przechowywania danych.
- Zgodnie z zadaniem do komunikacji między procesami wykorzystane zostaną potoki nienazwane.
- Przewiduje się wykorzystanie funkcji wait() lub waitpid() do synchronizacji. Ponieważ nie przewiduje się możliwości uruchamiania programów w tle, ww. proste metody są wystarczające.
- Nie przewiduje się podziału projektu na moduły.
Podstawowa powłoka wśród wielu dostępnych rozwiązań jest jednowątkowa, więc na tym etapie projektu nie została podjęta dokładna decyzja na temat potencjalnej realizacji wielowątkowości w projekcie i jej szczegółach. W kolejnych iteracjach wymaganie to może zostać uwzględnione, jeśli wystąpi potrzeba jej realizacji. W przypadku wieloprocesowości powłoka może uruchamiać wiele procesów, szczególnie w przypadku realizacji potoków, gdzie każdy program stanowi oddzielny proces, a jego wyjście jest przekierowywane na wejście kolejnego procesu. Potoki, w naszym przypadku nienazwane, umożliwiają komunikację między procesami, korzystając z mechanizmów pipe() i fork(). Proces nadrzędny tworzy potoki i przekazuje deskryptory plików do procesów potomnych. Po uruchomieniu procesów, proces nadrzędny czeka na ich zakończenie za pomocą waitpid() lub wait().
Głównym językiem programowania wykorzystywanym w ramach projektu będzie język C. Dodatkowo ważną częścią projektu będzie CI&CD, w ramach którego wykorzystane zostanie narzędzie Jenkins i język Groovy. Wstępnie jako bibliotekę używaną do testowania będzie GoogleTest, jednak wybór ten może ulec zmianie o czym wspomniano w dalszej części sprawozdania. Jako narzędzie do code review wykorzystane zostanie narzędzie Gerrit. System do budowania użyty w projekcie to Bazel. Docelowe działanie oprogramowania będzie sprawdzane na systemach Linuxowych oraz macOS.
Testy w ramach projektu przeprowadzone zostaną na różnych poziomach dla poszczególnych funkcjonalności. Pierwszym poziomem, a zarazem najliczniejszym, będą testy modułowe (jednostkowe) przeprowadzane dla pojedynczych modułów. Kolejną grupą testów będą testy integracyjne, w których zakres będzie wchodzić badanie wzajemnego współdziałania modułów. Grupa ta będzie mniej liczna, jednak stanowić będzie integralną część projektu. Ostatnim zbiorem testów będą testy akceptacyjnego, które zostaną przeprowadzone manualnie wraz z wymaganiami dostarczonymi w wymaganiach projektu i sprawozdaniu wstępnym. Wstępnie wybrana biblioteka do testowania to GoogleTest, jednak wybór ten może ulec zmianie w zależności od potrzeb i potencjalnych integracji.
W ramach projektu zastosowanie będzie podejście CI&CD z wykorzystaniem narzędzia Jenkins. Testy modułowe (jednostkowe) będą wykonywane zawsze przed integracją z główną gałęzią projektu i tylko 100% przechodzenie testów będzie pozwalało na integrację z wcześniej wspomnianym branchem. Testy integracyjne będą wykonywane tylko podczas budowania paczki w procesie ciągłego wdrażanie.