CSC 4103 – Programmation système

Portail informatique

Contrôle Final 1 – 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 (4 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.

Quel est le type de chacune de ces expressions:

  • client_list.next
  • client_list->client
  • client_list->client->pid
  • &client_list->client->client_conn
  • client_list.next est invalide (client_list est un pointeur)
  • client_list->client est de type client_t*
  • client_list->client->pid est de type pid_t
  • &client_list->client->client_conn est de type FILE**

/kick (10 points)

Envoi d'un signal (5 points)

On souhaite ajouter la commande /kick qui permet à un utilisateur d'expulser un autre utilisateur. Voici un exemple d'utilisation de la commande:

[Gael_Thomas] Francois: au fait, tu as fini de rédiger le sujet du CF ? (chatroom) $ /kick Gael_Thomas * Gael_Thomas was kicked by Francois_Trahay (chatroom) $

Implémentez la fonction void process_cmd_kick(client_t* client, char*param);. Cette fonction est appelée depuis handle_incoming_cmd lorsque le serveur reçoit la commande /kick. La fonction process_cmd_kick doit envoyer le signal SIGUSR1 au processus client de l'utilisateur dont le nom est stocké dans le paramètre param. La fonction envoie également à tous les utilisateurs le message "* X was kicked by Y" (où X est le nom de l'utilisateur expulsé et Y le nom de l'utilisateur ayant envoyé la commande /kick).

void process_cmd_kick(client_t* client, char* param) { char*dest = param; client_t *to = get_client_from_name(dest); if(! to) { sprintf(buff_out, "* There's no %s in his chatroom\n", dest); send_message(buff_out, client); return; } /* send the signal */ kill(to->pid, SIGUSR1); /* remove the client from the list of connected clients */ queue_delete(to); char buff_out[1024]; sprintf(buff_out, "* %s was kicked by %s\n", dest, client->name); send_message_all(buff_out); }

Réception d'un signal (3 points)

On souhaite que le client de l'utilisateur lui indique lorsqu'il a été expulsé. Pour reprendre l'exemple précédent, voici ce que voit l'utilisateur Gael_Thomas :
[Gael_Thomas] Francois: au fait, tu as fini de rédiger le sujet du CF ? (chatroom) $ * You were kicked from the chatroom $

Donnez le code permettant d'exécuter une fonction lorsque le client reçoit le signal SIGUSR1. Donnez également le code la fonction qui affiche un message et termine le processus.

void signal_handler(int signo) { printf("\nYou were kicked from the chatroom\n"); exit(EXIT_SUCCESS); } void init_client() { struct sigaction sa; sa.sa_handler = signal_handler; sigaction(SIGUSR1, &sa, 0); }

/kickban (2 points)

On souhaite maintenant implémenter la commande /kickban qui, en plus d'expulser un utilisateur, l'empêche de se reconnecter. Pour cela, le nom de l'utilisateur est écrit dans le fichier banned_users.txt. Ce fichier est ensuite consulté à chaque fois qu'il client se connecte.

Donnez le code de la fonction void process_cmd_kickban(client_t* client, char* param). Cette fonction ajoute le nom de l'utilisateur (stocké dans le paramètre param) au fichier banned_users.txt, puis appelle la fonction process_cmd_kick pour expulser l'utilisateur.

void process_cmd_kick(client_t* client, char* param) { FILE* f=fopen("banned_users.txt", "a"); if(!f) { fprintf(stderr, "cannot open banned_users.txt\n"); exit(EXIT_FAILURE); } fprintf(f, "%s\n", param); fclose(f); process_cmd_kick(client, param); }

Compréhension de code (6 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) { if(!fork()) { char name[1024]; sprintf(name, "[%s]", param); FILE* f_in = fopen("chat_history.log", "r"); FILE* f_out = fopen("f2.log", "w"); char buffer[1024]; while(fgets(buffer, 1024, f_in)) { // if the first strlen(name) characters of buffer // and of name are different if(strncmp(buffer, name, strlen(name)) != 0) { fprintf(f_out, "%s", buffer); } } fclose(f_in); fclose(f_out); // equivalent to running: mv f2.log fchat_history.log rename ("f2.log", "chat_history.log"); } }
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...

(2 points)

Expliquez brièvement dans quel(s) cas la fonction est appelée, et ce que fait la fonction process_cmd_mystery.
La fonction est invoquée quand un client envoie la commande /mystery NOM. Elle supprime toutes les lignes du fichier chat_history.log commençant par [nom].

(2 points)

Quel problème de concurrence peut survenir dans cette fonction ? Donnez un exemple d'exécution menant à ce problème.
Si le fichier chat_history.log est modifié pendant le déroulement de la fonction, certaines lignes du fichier risque d'être perdues. Par exemple, si le serveur traite la commande /mystery plop, puis reçoit la commande /mystery plip juste avant l'appel à rename par le premier processus, les modifications de l'une des deux commandes seront perdues.

(2 points)

Proposez une solution pour corriger le problème. Il n'est pas nécessaire de donner le code de la solution.
On peut utiliser un sémaphore pour s'assurer que la fonction n'est pas exécutée par plusieurs processus simultanément.