CSC 4103 – Programmation système

Portail informatique

Contrôle Final 2 – Année 2016/2017

Le barème est donné à titre indicatif
  • Durée du CF: 1h30
  • Tous les documents sont autorisés
  • Vous avez à votre disposition le code des fichiers serveur_code_etudiant.h et serveur_code_etudiant.c

Echauffement (5 points)

Pointeurs

Pour cet exercice, on s'intéresse à la liste struct list* client_list initialisée à la ligne 16, et modifiée par la fonction queue_add (ligne 95). On considère que cette liste est dans un état cohérent.

Pour chacune de ces expressions, indiquez si l'expression est valide, et si c'est le cas, le type de l'expression :

  • *client
  • client_list->next
  • &client_list->client
  • &client_list->client->client_conn
  • client_list->client->fd
  • *client est de type struct list
  • client_list->next est de type struct list*
  • &client_list->client est de type client_t**
  • &client_list->client->client_conn est de type FILE**
  • client_list->client->fd est de type int

/exec (8 points)

On souhaite ajouter la commande /exec qui permet de faire exécuter une commande shell par le serveur. Voici l'usage de la commande:

/exec cmd

Par exemple, si un client envoie /exec ls, le serveur exécutera la commande ls.

Pour simplifier, on considère que la commande shell cmd ne prend pas d'argument.

(5 pt)

Implémentez la fonction void process_cmd_exec(client_t* client, char*param);. Cette fonction est appelée depuis handle_incoming_cmd lorsque le serveur reçoit la commande /exec.

La fonction process_cmd_exec doit créer un processus fils qui exécute la commande param passé en paramètre. Le processus père attend ensuite que le processus fils se termine. En cas d'erreur du processus fils, celui-ci envoie un message au client contenant le numéro de l'erreur (par exemple: cannot execute command: error 17). On considère que la commande ne prend pas de paramètre.

void process_cmd_exec(client_t* cli, char*param) { if(!fork() ) { execlp(param, param, NULL); char buffer[1024]; sprintf(buffer, "cannot execute %s: %s\n", param, strerror(errno)); send_message(buffer, cli); exit(EXIT_FAILURE); } int status; wait(&status); }

(3 pt)

On souhaite maintenant pouvoir exécuter des commandes en arrière-plan. Pour cela, les clients peuvent envoyer la commande /exec_bg au serveur. Cette commande est traitée par la fonction void process_cmd_exec_bg(client_t* client, char*param);, appelée par handle_incoming_cmd. Le traitement de la commande /exec_bg est similaire au traitement de /exec, à deux exceptions près :

  • Le processus père n'attend pas la terminaison du processus fils, mais envoie le message "executing a command in bg: [PID]" (où PID est le PID du processus fils) au client ayant envoyé la commande.
  • Le processus père, après avoir créé un processus fils, teste si l'un de ses processus fils s'est terminé. Si c'est le cas, il envoie un message à tous les clients: "bg command [PID] finished" (où PID est le PID du processus fils qui s'est terminé

Donnez le code de la fonction void process_cmd_exec_bg(client_t* client, char*param);

void process_cmd_exec_bg(client_t* cli, char*param) { pid_t pid = fork(); if(!pid ) { execlp(param, param, NULL); char buffer[1024]; sprintf(buffer, "cannot execute %s: %s\n", param, strerror(errno)); send_message( buffer, cli); exit(EXIT_FAILURE); } char buffer[1024]; sprintf(buffer, "executing a command in bg: [%d]", pid); send_message(cli, buffer); pid_t pid_finished = waitpid(-1, &status, WNOHANG); if(pid_finished) { sprintf(buffer, "bg command [%d] finished", pid); send_message_all(buffer); } }

Compréhension de code (7 points)

Un développeur a ajouté la fonction process_cmd_mystery au fichier server_code_etudiant.c:
void process_cmd_mystery(client_t* client, char* param) { struct list* l = clients; while(l) { if(l->client->name[0] == param[0]) { char* buffer = malloc(sizeof(char)*strlen(l->client->name)); sprintf(buffer, "%s est idiot", l->client->name); send_message_all(buffer); } l = l->next; } }
Cette fonction est appelée par la fonction handle_incoming_cmd:
void handle_incoming_cmd(client_t *cli) { [...] command = strsep(&cmd_line," "); if(!strcmp(command, "/quit")){ return; } else if(!strcmp(command, "/ping")) { process_cmd_ping(cli, cmd_line); } else if(!strcmp(command, "/mystery")) { process_cmd_mystery(cli, cmd_line); } else if(!strcmp(command, "/msg")) { process_cmd_msg(cli, cmd_line); } [...]
Malheureusement, la fonction n'est pas documentée, et son nom n'est pas très explicite...

(3 points)

Donnez un exemple de commande envoyée par le client et le résultat attendu pour cette fonction.
La fonction est invoquée quand un client envoie la commande /mystery STR. (1 pt) Elle parcoure la liste des clients connectés, et pour chaque client c donc le nom commence par la même lettre que STR, elle envoie le message suivant à tous: "cl est un idiot".(1 pt)

(2 points)

Quel problème cette fonction présente-t-elle ?
Une fuite mémoire: buffer est alloué, mais pas désalloué. On perd donc de la mémoire à chaque itération.

(2 points)

Expliquez comment corriger le problème. Donnez le code de la fonction corrigée.
fuite mémoire: ajouter un free(buffer) après send_message_all
void process_cmd_mystery(client_t* client, char* param) { struct list* l = clients; while(l) { if(l->client->name[0] == param[0]) { char* buffer = malloc(sizeof(char)*strlen(l->client->name)); sprintf(buffer, "%s est idiot", l->client->name); send_message_all(buffer); free(buffer); // corrige la fuite mémoire } l = l->next; } }