Sistemi di Calcolo

Corso di Laurea in Ingegneria Informatica e Automatica - A.A. 2017-2018

HomePage | Avvisi | Diario lezioni | Programma | Materiale didattico | Esami | Forum | Login

Esercitazione 5 - 2 dicembre 2016 (120 min)


[ soluzioni ]

Esercizio 1 - shell comandi minimale

Lo scopo dell'esercizio è sviluppare una shell minimale che realizza una CLI (command-line interface) in grado di eseguire:

Si lavori sullo scheletro fornito di seguito, complentando le funzioni:

Seguire le indicazioni e i suggerimenti contenuti come commenti nel codice.

shell.c
#include <stdio.h>              // printf, puts
#include <string.h>             // strtok, strcpy

#define MAX_LINE_SIZE 1024      // massima lunghezza riga di comando
#define MAX_NUM_ARGS  64        // massimo numero di token sulla riga di comando


// ---------------------------------------------------------------------
//  parse_cmd_line -- [fornita]
// ---------------------------------------------------------------------
// estrae argomenti dalla riga di comando
// parametri:
//   - cmd_line: [input] stringa di input contenente la riga di comando
//   - tok_buf: [output] array in cui scrivere i token della riga di comando
//   - tok_num_ptr: [output] puntatore a buffer in cui scrivere numero di token in tok_buf

void parse_cmd_line(char cmd_line[MAX_LINE_SIZE],
                    char tok_buf[MAX_NUM_ARGS][MAX_LINE_SIZE],
                    unsigned* tok_num_ptr) {
    unsigned tok_num = 0;                   // numero token estratti da cmd_line
    char buf[MAX_LINE_SIZE];                // buffer per non modificare cmd_line passata
    strcpy(buf, cmd_line);                  // copia cmd_line: strtok fa side-effect
    const char* delim = " \n\t";            // delimitatori tokenizzazione
    char* token = strtok(buf, delim);       // tokenizza buffer
    while (token != NULL) {
        strcpy(tok_buf[tok_num], token);    // copia token corrente in tok_buf
        token = strtok(NULL, delim);        // estrae token successivo
        tok_num++;
    }
    *tok_num_ptr = tok_num;                 // passa numero token estratti al chiamante
}


// ---------------------------------------------------------------------
//  do_internal_cmd -- [fornita]
// ---------------------------------------------------------------------
// prova ad eseguire comando interno
// parametri:
//   - tok_buf: [input] array di token della riga di comando
//   - tok_num: [input] numero di token in tok_buf
// restituisce:
//    -1 se quit
//     0 se non e' un comando interno
//     1 se e' un comando interno

int do_internal_cmd(char tok_buf[MAX_NUM_ARGS][MAX_LINE_SIZE],
                    unsigned tok_num) {

    if (strcmp(tok_buf[0], "quit") == 0) return -1;

    // altri comandi interni qui (vedi esercizio 2)...
    // if (strcmp(tok_buf[0], "miocomando") {
    //     do_mio_comando_interno(tok_buf, tok_num); // esegue cmd interno
    //     return 1;                                 // cmd eseguito
    // }

    return 0;
}


// ---------------------------------------------------------------------
//  make_argv -- [da completare]
// ---------------------------------------------------------------------
// crea array di puntatori argv per comando esterno
// parametri:
//   - argv: [output] buffer preallocato di puntatori agli argomenti del comando
//   - tok_buf: [input] array di token della riga di comando
//   - tok_num: [input] numero di token in tok_buf

void make_argv(char* argv[MAX_NUM_ARGS+1],
               char tok_buf[MAX_NUM_ARGS][MAX_LINE_SIZE],
               unsigned tok_num) {

    // esempio risultato della funzione
    // input:
    //   tok_num    = 2
    //   tok_buf[0] = "/bin/ls"
    //   tok_buf[1] = "-l"
    // output:
    //   argv[0] = "ls" (puntatore alla sottostringa ls in
    //                   tok_buf[0])
    //   argv[1] = "-l" (puntatore a tok_buf[1])
    //   argv[2] = NULL (terminatore)

    // note:
    // 1. argv[i] deve puntare a zone di memoria gia' allocate
    //    all'interno dei buffer tok_buf e argv0
    // 2. per inizializzare argv[0], si suggerisce di farlo puntare alla
    //    sottostringa di tok_buf[0] che rappresenta il nome del file
    //    (esempio, ls in /bin/ls) -- si suggerisce di usare la funzione
    //    POSIX strrchr
}


// ---------------------------------------------------------------------
//  do_external_cmd -- [da completare]
// ---------------------------------------------------------------------
// esegue comando esterno
// parametri:
//   - tok_buf: [input] array di token della riga di comando
//   - tok_num: [input] numero di token in tok_buf

void do_external_cmd(char tok_buf[MAX_NUM_ARGS][MAX_LINE_SIZE],
                     unsigned tok_num) {

    char* argv[MAX_NUM_ARGS+1];                  // argv da passare a processo
    make_argv(argv, tok_buf, tok_num);           // crea argv per processo

    // lancia un nuovo processo che esegue il comando esterno e ne
    // attende la terminazione usando i parametri passati in argv

    // usare lo schema fork/execvp/wait e gestire gli errori stampandoli
    // con perror
}


// ---------------------------------------------------------------------
//  do_cmd_loop -- [da completare]
// ---------------------------------------------------------------------
// esegue il ciclo di lettura/esecuzione dei comandi

void do_cmd_loop() {

    char     cmd_line[MAX_LINE_SIZE];                    // buffer per contenere riga di cmd
    char     tok_buf[MAX_NUM_ARGS][MAX_LINE_SIZE];       // buffer token riga di comando
    unsigned tok_num;                                    // numero token riga di comando

    // completa come segue:
    // ciclo che ripete i seguenti passi
    //    1. stampa su stdout il prompt dei comandi, es. "SC> "
    //    2. legge da stdin una riga in cmd_line (usare fgets)
    //       ed esce dalla funzione se il canale stdin è terminato (CRTL+D durante fgets fornisce NULL)
    //    3. chiama parse_cmd_line per estrarre i token da cmd_line e
    //       copiarli in tok_buf, con il loro numero in tok_num
    //    4. se sono stati immessi token, allora prova ad eseguire
    //       il comando come interno richiamando do_internal_cmd: se non e'
    //       interno e non e' quit prova ad eseguire il comando come
    //       esterno. Se e' quit esce dalla funzione.
}


// ---------------------------------------------------------------------
//  main -- [fornito]
// ---------------------------------------------------------------------

int main() {
    do_cmd_loop();
    puts("\nsayonara.");
    return 0;
}



Esercizio 2 - estensione shell con comandi interni

In questo esercizio è richiesta l'implementazione dei due comandi interni pwd e cd.

Osservazione: i comandi interni devono essere implementati modificando la funzione do_internal_cmd della shell.
Nota bene: lo studente deve gestire correttamente eventuali errori dovuti all'invocazione di funzioni POSIX. In particolare, in caso di errore, occorre verificare l'errore utilizzando la variabile esterna errno ed emettere un messaggio di errore utilizzando la funzione POSIX perror.

Il comando pwd deve supportare la seguente sintassi:
pwd

L'output del comando è la working directory corrente. L'implementazione deve far uso della funzione POSIX getcwd.

Il comando cd deve supportare la seguente sintassi:
cd <path>

Il comando deve cambiare la working directory della shell in base all'argomento path. In caso di path non valido, un errore deve essere emesso in standard output dalla shell. L'implementazione deve far uso della funzione POSIX chdir.


Esercizio 3 - comando esterno cat

In questo esercizio è richiesto di implementare il comando esterno unix cat. Per i nostri scopi, il comando cat dovrà supportare la seguente sintassi:
cat [<path-to-file>]

Se viene indicato un file (path-to-file), allora cat legge il contenuto del file e lo stampa in standard output. Se il file viene omesso, cat legge dallo standard input. E' richiesto di implementare cat utilizzando le funzioni POSIX open, close, read, write, e exit. Eventuali errori ritornati da queste funzioni devono essere opportunamente verificati e gestiti. Sviluppare un implementazione di cat in base al seguente scheletro:
cat.c
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

// ---------------------------------------------------------------------
//  is_regular_file
// ---------------------------------------------------------------------
// Funzione che verifica se un path è relativo ad un file regolare.
// Non occorre modificare questa funzione. Va invocata ove necessario.
// Parametri:
//    - path: un percorso nel filesystem
// Restituisce:
//    - 0 se il path è un file regolare,
//    - altrimenti -1
int is_regular_file(const char *path) {

    struct stat path_stat;
    int res = stat(path, &path_stat);
    if (res == 0 && S_ISREG(path_stat.st_mode) == 1)
        return 0;
    else
        return -1;
}

// ---------------------------------------------------------------------
//  get_read_descriptor
// ---------------------------------------------------------------------
// Funzione che ritorna il file descriptor in lettura
// In caso di errore, termina l'esecuzione del programma.
// Parametri (uguali alla funzione main):
//    - argc: numero di argomenti passati al comando
//    - argv: argomenti passati al programma
// Restituisce: il file descriptor
int get_read_descriptor(int argc, char * argv[