interpreter : readCommand.c - owanesh/unifi-eos GitHub Wiki
L’acquisizione del comando e il relativo parsing per distinguerne i campi è un passo fondamentale, in particolare non essendo possibile prevedere la lunghezza della stringa digitata è stato scelto di sfruttare la memoria dinamica.
/*
* Restituisce il comando digitato dall'utente "trimmato" (senza spazi ne' all'inizio ne' alla fine)
*/
char* readCommand() {
char *str;
int ch, size = 21, len = 0;
str = realloc(NULL, sizeof(char) * size); //20 caratteri disponibili + '\0'
if (str == NULL) //errore allocazione spazio
return NULL;
while ((ch = fgetc(stdin)) != EOF && ch != '\n') { //lettura fino a EOF o '\n'
str[len++] = ch;
if (len == size) {
str = realloc(str, sizeof(char) * (size += 16)); //rialloco 15 carateri + '\0'
if (str == NULL) //errore allocazione spazio
return NULL;
}
}
str[len++] = '\0'; //Terminazione stringa
str = realloc(str, sizeof(char) * len);
//Rialloco esattamente lo spazio occupato
str = trim(str); // elimino spazi iniziali e finali
return str;
}
La funzione readCommand() restituisce la linea di testo digitato dall’utente eseguendo il trim della stringa (operazione implementata in utilities.c).
Inizialmente si allocano 20 caratteri + 1 (‘\0’), i quali vengono incrementalmente aumentati di 15 caratteri se necessario: la funzione fgetc() permette di acquisire un carattere alla volta e quindi contarne la quantità. La stringa ottenuta viene riallocata in base all’effettiva lunghezza, viene “trimmata” ed infine restituita. La liberazione dello spazio occupato dal puntatore “char* str” (istanziato dal padre ma copiato anche nel figlio) verrà effettivamente eseguita nella funzione parseCommand() esposta sotto.
/*
* Restituisce una array di stringhe, il numero di righe è pari al numero dei campi
* del comando passato piu' una riga NULL finale (necessaria per execvp)
*/
char** parseCommand(char *command) {
int num_of_args = countFields(command, ' ');
char** args = malloc(sizeof(char*) * num_of_args);
str_split(command, ' ', args);
free(command); //free del comando istanziato in readCommand
return args;
}
/*
* Determina il numero di campi del comando str piu' una riga NULL
* es: ls -la -> (ls)+(-la)+NULL = 3
*/
int countFields(char* str, const char token_delimiter) {
int token_counter = 0;
/* Conteggio di quanti token troviamo, scorrendo la stringa carattere */
while (*str) {
if (token_delimiter == *str) {
token_counter++;
}
str++;
}
//+2 per il comando e per il NULL finale
token_counter += 2;
return token_counter;
}
/*
* Associa ad ogni riga di args un campo del comando input_string
*/
void str_split(char* input_string, const char token_delimiter, char** args) {
int id_tok = 0;
char delim[] = { token_delimiter, '\0' };
char* token = strtok(input_string, delim);
/* Preso il token successivo (strtok), controllo se e' valido*/
while (token != NULL) {
args[id_tok] = strdup(token);
token = strtok(0, delim);
id_tok++;
}
args[id_tok] = NULL;
}
Come spiegato più avanti nella sezione sequentialExec.c e parallelExec.c, la decisione di separare l’acquisizione della stringa e il parsing deriva anche dal seguente fatto: la readCommand() è chiamata dal processo padre, mentre ogni parseCommand() viene richiamata dal figlio sulla stringa ottenuta. Come esposto in questi articoli:
- https://stackoverflow.com/questions/5429141/what-happens-to-malloced-memory-after-exec-changes-the-program-image
- https://stackoverflow.com/questions/14492971/how-to-free-memory-created-by-malloc-after-using-execvp
La memoria dinamica allocata da un processo viene automaticamente riacquisita dal SO quando interviene una chiamata exec(); dato che i figli devono allocare un numero imprevedibile di stringhe (pari al numero dei campi che viene determinato dalla funzione countFields() ) è necessario allocare nuovamente memoria e farlo fare al processo padre sarebbe stato più complicato (oltre che più oneroso dato che la prerogativa è che esso possa tornare quanto prima a ricevere il nuovo comando dell’utente). Visitare il paragrafo successivo per un’ulteriore approfondimento di questa scelta implementativa.
La funzione str_split() si affida alla funzione strtok() per delimitare i campi del comando: ovviamente ogni campo è suddiviso da uno o più spazi ‘ ‘ e tale carattere viene passato come argomento “token_delimiter”. (Nota: il comando viene accettato anche con spazi).
Ad ogni sotto-stringa individuata da strtok() viene eseguita la funzione strdup(): quest’ultima consente di allocare una quantità esatta di memoria atta a contenere la stringa puntata (riguardo la liberazione di tale memoria vedere il paragrafo successivo).
Infine in parseCommand() è presente la free() del puntatore istanziato in readCommand().