CSC 4103 – Programmation système

Portail informatique

Contrôle Final 1 – Année 2018/2019

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)

Pour cet exercice, on considère le Makefile suivant:

(1 point)

Le fichier tools.c vient d'être modifié. Quelle(s) ligne(s) de commande sont/seront forcément exécutées si on lance la commande make ?

(barème: 1 point)
$ make gcc -c tools.c -Wall -Werror gcc -o client client.o tools.o gcc -o server server.o tools.o

(1 point)

On souhaite pouvoir débugger le programme avec gdb en ayant accès aux symboles de debug. Que faut-il modifier dans le Makefile ?

(barème: 1 point) Il faut ajouter l'option -g lors de la compilation:
server.o: server.c tools.h gcc -c server.c -Wall -Werror -g client.o: client.c tools.h gcc -c client.c -Wall -Werror -g tools.o: tools.c tools.h gcc -c tools.c -Wall -Werror -g

(2 point)

Le compilateur affiche le message d'erreur suivant:

$ gcc -o server server.o tools.o /usr/bin/ld: server.o: in function `main': server.c:(.text+0xa): undefined reference to `foo' collect2: error: ld returned 1 exit status

Sachant que la fonction foo est déclarée dans le fichier ../include/foo.h et implémenté dans le fichier ../lib/libplop.so, que faut-il faire pour corriger l'erreur ?

(barème: 2 points)

Il faut ajouter -L../lib -lplop lors de l'édition de liens:

server: server.o tools.o gcc -o server server.o tools.o -L../lib -lplop

Jeu par équipe (11 points)

Le but de cet exercice est d'ajouter une dimension ludique à la chatroom étudiée lors du cours intégré 10 en la transformant en un jeu dans lequel deux équipes s'affrontent. Les règles du jeu sont simples:

  • Les deux équipent s'appellent blue et red ;
  • Un client peut rejoindre une équipe en lançant la commande /join nom_équipe (où nom_équipe est soit blue, soit red) ;
  • Lorsqu'un joueur rejoint une équipe, on lui attribue 100 points de vie ;
  • Un joueur peut faire perdre 15 points de vie à un autre joueur en lançant la commande /attack nom_joueur (où nom_joueur est le nom du joueur attaqué) ;
  • Un joueur peut faire gagner 5 points de vie à un autre joueur en lançant la commande /heal nom_joueur (où nom_joueur est le nom du joueur soigné) ;
  • Lorsqu'un joueur n'a plus de point de vie, il est éliminé de la partie ;
  • Une équipe gagne le match quand elle a éliminé tous les joueurs de l'équipe adverse.

Voici un exemple de partie:

* Jean-Seb_le_microseb joins the chatroom * Loana_le_puma joins the chatroom * Anémone_l'anémone joins the chatroom (chatroom) $ /join red * Anémone_l'anémone joins team red # Jean-Seb_le_microseb lance /join red * Jean-Seb_le_microseb joins team red # Loana_le_puma lance /join blue * Loana_le_puma joins team blue (chatroom) $ /attack Loana_le_puma * Jean-Seb_le_microseb attacks Loana_le_puma [team blue] * Loana_le_puma [team blue] now has 85 health points # Anémone lance /attack Loana_le_puma * Anémone_l'anémone attacks Loana_le_puma [team blue] * Loana_le_puma [team blue] now has 70 health points # Loana lance /attack Anémone_l'anémone * Loana_le_puma attacks Anémone_l'anémone [team red] * Anémone_l'anémone [team red] now has 85 health points (chatroom) $ /heal Anémone_l'anémone * Jean-Seb_le_microseb heals Anémone_l'anémone [team red] * Anémone_l'anémone [team red] now has 90 health points [...] (chatroom) $ /attack Loana_le_puma * Jean-Seb_le_microseb attacks Loana_le_puma [team blue] * Loana_le_puma [team blue] is now dead * There are no more player in team blue. Team red wins!

Pour vous aider à implémenter, nous ajoutons les lignes suivantes dans le fichier server_code_etudiants.c afin de définir une structure player (représentant un joueur) et une structure team (représentant une équipe) :

struct player { client_t* client; // client correspondant au joueur int pv; // nombre de points de vie restant struct player* next; // joueur suivant dans la liste }; struct team { struct player* players; // liste des joueurs (y compris les morts) de l'équipe int nb_players; // nombre de joueurs restant char name[10]; // nom de l'équipe }; // les équipes: // teams[0] team blue // teams[1] team red struct team teams[2];

/join (4 points)

Implémentez la fonction void process_cmd_join(client_t* client, char *param);. Cette fonction, qui est appelée lorsqu'un client lance la commande /join nom_equipe, doit ajouter le client à l'équipe nom_equipe et envoyer un message à tous, par exemple:

* Loana_le_puma joins team blue

On considère que dans handle_incoming_cmd(), le code suivant a été ajouté:

} else if(!strcmp(command, "/join")) { process_cmd_join(cli, cmd_line);
Afin de simplifier l'implémentation, on considère que le client ne fait pas d'erreur (il ne demande pas à rejoindre une équipe s'il joue déjà, il ne demande pas à rejoindre une équipe qui n'existe pas, etc.)
// 1 point pour malloc et initialisation du player // 2 point pour insertion dans la liste // 1 point si l'algo est OK void process_cmd_join(client_t*client, char*param) { char s[1024]; char*team = strsep(&param, " "); if(!team){ send_message("* which team ?\n", client); return; } for(int i=0; i<2; i++) { if(strcmp(teams[i].name, team)==0) { struct player* p = malloc(sizeof(struct player)); assert(p); p->next = teams[i].players; p->client = client; p->pv = 100; teams[i].players = p; sprintf(s, " * %s joins team %s\n", client->name, team); send_message_all(s); return; } } }

/attack (4 points)

Implémentez la fonction void process_cmd_attack(client_t* client, char *param). Cette fonction, qui est appelée lorsqu'un client lance la commande /attack nom_joueur, doit rechercher le joueur nom_joueur parmi les équipes et lui faire perdre 15 points de vie. La fonction envoie également un message à tous les clients les informant de l'attaque, par exemple:

* Jean-Seb_le_microseb attacks Loana_le_puma [team blue] * Loana_le_puma [team blue] now has 55 health points

Si le joueur attaqué n'a plus de point de vie, la fonction appelle la fonction suivante: void player_is_dead(client_t* client, struct player* p, int team_number)

La fonction player_is_dead est implémentée à la question suivante.
On considère que le joueur ne fait pas d'erreur (il n'attaque que des joueurs existants, etc.)

On considère que dans handle_incoming_cmd(), le code suivant a été ajouté:

} else if(!strcmp(command, "/attack")) { process_cmd_attack(cli, cmd_line);
// 1 point pour strcmp // 2 point pour le parcours de la liste // 1 point si l'algo est OK void process_cmd_attack(client_t*client, char*param) { char s[1024]; char*dest = strsep(&param, " "); if(!dest){ send_message("* who ?\n", client); return; } for(int i=0; i<2; i++) { struct player* player = teams[i].players; while(player) { if(strcmp(player->client->name, dest) == 0) { sprintf(s, " * %s attacks %s [team %s]\n", client->name, player->client->name, teams[i].name); send_message_all(s); player->pv -= 15; if(player->pv <=0 ) { player_is_dead(client, player, i); return; } sprintf(s, " * %s [team %s] now has %d health points\n", player->client->name, teams[i].name, player->pv); send_message_all(s); return; } player = player->next; } } sprintf(s, " * %s tries to attack. It is not very effective!\n", client->name); send_message(s, client); }

player_is_dead (3 points)

Lorsqu'un joueur n'a plus de point de vie, la fonction void player_is_dead(client_t* client, struct player* p, int team_number) est appelée. Cette fonction doit:

  • envoyer un message à tous les clients (par exemple : * Loana_le_puma [team blue] is now dead);
  • décrémenter le nombre de joueurs de l'équipe;
  • si l'équipe n'a plus de joueur vivant, afficher un message indiquant quelle équipe a gagné;
  • créer un processus pour qu'il exécute update_death_statistics nom_joueur qui est chargé de mettre à jour des statistiques sur le nombre de morts des joueurs.
Il ne faut pas utiliser la fonction system ici pour invoquer update_death_statistics, car system ne rend la main que lorsque la commande est terminée, et update_death_statistics peut être très long à s'exécuter.
// 2.5 points pour fork/exec // 0.5 points pour la logique générale de la fonction void player_is_dead(client_t*client, struct player* p, int team_no) { char s[1024]; sprintf(s, " * %s [team %s] is now dead\n", p->client->name, teams[team_no].name); send_message_all(s); teams[team_no].nb_players --; if(teams[team_no].nb_players <= 0) { sprintf(s, " * There are no more player in team %s. Team %s wins!\n", teams[team_no].name, teams[(team_no+1)%2].name); send_message_all(s); } if(fork() == 0) { execlp("update_death_statistics", "update_death_statistics", p->client->name, NULL); abort(); } }

Compréhension de code (6 points)

Un développeur a ajouté la fonction process_cmd_mystery au fichier server_code_etudiant.c:

struct stat{ char str[NAME_MAX_LENGTH]; int counter; }; void process_cmd_mystery(client_t*client, char*param) { char s[1024]; char*dest = strsep(&param, " "); if(!dest){ send_message("* who ?\n", client); return; } FILE*f = fopen("stats.dat", "r+"); int line_number = 0; struct stat cur_stat; while(fread(&cur_stat, sizeof(struct stat), 1, f) > 0) { if(strcmp(cur_stat.str, dest) == 0) { cur_stat.counter++; int position = line_number*sizeof(struct stat); fseek(f, position, SEEK_SET); fwrite(&cur_stat, sizeof(struct stat), 1, f); sprintf(s, " * %s : %d\n", dest, cur_stat.counter); send_message_all(s); fclose(f); return; } line_number++; } fclose(f); }

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...

(4 points)

Expliquez brièvement ce que fait la fonction process_cmd_mystery, et donnez un exemple de commande lancée par le client pour illustrer votre explication.

Cette fonction recherche dans le fichier stats.dat le nom passé en paramètre. Si le nom est trouvé, le compteur associé à ce nom est incrémenté et mis à jour dans le fichier, et un message est envoyé à tous.

Voici un exemple d'utilisation:

(chatroom) $ /mystery Fernande_le_scolopendre * Fernande_le_scolopendre : 2

(2 points)

Que se passe-t-il si la valeur recherchée n'est pas trouvée ? Proposez une solution pour que dans ce cas, la valeur soit ajoutée au fichier.

Donnez les quelques lignes de code permettant d'implémenter votre solution et expliquez où elles doivent être insérées.

Si le nom n'est pas dans le fichier, le fichier est fermé sans que le couple (nom, compteur) ne soit inséré. Pour remédier à ce problème, on peut ajouter après la fin de la boucle while, et avant l'appel à fclose le code suivant:
while(...) { ... } // debut du code à ajouter strcpy(cur_stat.str, dest); cur_stat.counter = 1; fwrite(&cur_stat, sizeof(struct stat), 1, f); // fin code à ajouter fclose(f);