Portail informatique Année 2018 – 2019

CSC 3102 – Introduction aux systèmes d’exploitation

Pour faire les exercices, vous avez besoin de connaître le langage bash. Vous pouvez vous référer à l'annexe shell. Vous pouvez aussi trouver une liste d'astuces ici. Tous les exercices sont obligatoires, sauf les exercices notés « défi » ou « optionnel » qui sont optionnels. En particulier, les exercices notés « hors présentiel » sont supposés fait d'une séance sur la suivante.

Vous aurez aussi besoin des scripts et .

  • revoir la plupart des notions étudiées dans le module.
Le but de cet exercice est de concevoir un mini serveur facebook. La figure vous présente l'architecture globale de l'application, n'hésitez pas à vous y référer tout au long de l'exercice pour comprendre à quelle étape vous êtes. Les différentes parties vous seront bien sûr expliquées au fur et à mesure. Pour démarrer, il faut comprendre que le script mini-facebook.sh met en œuvre le serveur et qu'il utilise les scripts create.sh, add-friend.sh, post-message.sh et display-wall.sh. Le client est mis en œuvre par le script client.sh, et il communique avec le serveur via deux tubes nommés : mini-facebook.sh (partagé par tous les clients) et clientid.pipe (un tube par client).
Architecture de l'application.
Mise en place des commandes Dans un premier temps, nous allons créer l'ensemble des commandes permettant de manipuler l'état du serveur.

Le serveur gère un ensemble d'utilisateurs. Un utilisateur d'identifiant $id possède un répertoire $id contenant deux fichiers :
  • un fichier nommé wall, contenant une suite de lignes. Chaque ligne est un message posté par un ami sous la forme identifiant-ami: message ;
  • un fichier nommé friends, dans lequel chaque ligne contient l'identifiant d'un ami. Seul les amis d'un utilisateur peuvent poster des messages sur son mur.

Le serveur doit être capable d'effectuer quatre actions différentes :
  • créer un utilisateur, c'est-à-dire créer le répertoire associé à un utilisateur et initialiser les fichiers de l'utilisateur,
  • ajouter un ami à un utilisateur,
  • poster un message sur le mur d'un ami,
  • lire son mur.
Vous commencez par écrire un script create.sh id prenant un unique argument $id et permettant de créer le répertoire et, dans cet ordre, les fichiers wall puis friends de l'utilisateur d'identifiant $id. En effectuant les actions dans cet ordre, vous pouvez noter que :
  • si le répertoire $id existe, c'est que l'utilisateur est en cours de création,
  • si le fichier $id/friends existe, c'est que l'utilisateur est entièrement créé.

Votre script doit afficher sur le terminal :
  • nok please provide an identifier si le script ne reçoit pas d'argument,
  • nok user '$id' already exists si l'utilisateur est déjà en cours de création, c'est-à-dire si le répertoire $id existe déjà,
  • ok sinon (n'oubliez pas d'afficher ok, cet affichage deviendra essentiel par la suite).

Seuls ces trois affichages sont autorisés. Notez que chacun de ces affichages n'est constitué que d'une unique ligne. Tout autre affichage finira par engendrer des erreurs lorsque vous répondrez aux questions de la fin du TP.
On souhaite maintenant pouvoir ajouter un ami à la liste des amis d'un utilisateur d'identifiant $id.

Votre script doit d'abord vérifier que l'utilisateur $id existe, c'est-à-dire que le répertoire $id existe.

Ensuite, un ami, identifié par $friend, est aussi un utilisateur. Avant d'ajouter l'utilisateur $friend à la liste d'amis de l'utilisateur $id, il faut donc vérifier que l'utilisateur $friend existe, c'est-à-dire que le répertoire $friend existe.

Enfin, avant d'ajouter l'utilisateur $friend à la liste d'amis de $id, il faut vérifier que l'utilisateur $friend n'est pas déjà ami de l'utilisateur $id, c'est-à-dire que la chaîne de caractère $friend n'est pas présente dans le fichier d'amis $id/friends de $id. Vous pourrez utiliser le code du retour de la commande grep pour détecter la présence de la chaîne de caractères friend dans $id/friends. Vous utiliserez par exemple if grep "^$friend$" "$id"/friends > /dev/null; then ... fi. La redirection vers /dev/null est importante pour éviter de polluer la sortie de add-friend.sh avec la sortie de grep, ce qui deviendra essentiel par la suite.

Écrivez le script add-friend.sh id friend prenant les deux arguments $id et $friend et ajoutant l'utilisateur $friend à la liste d'amis de $id. Votre script doit afficher sur le terminal :
  • nok user '$id' does not exist si l'utilisateur $id n'a pas été entièrement créé, c'est-à-dire si le fichier $id/friends n'existe pas.
  • nok user '$friend' does not exist si l'utilisateur $friend n'a pas été entièrement créé, c'est-à-dire si le fichier $friend/friends n'existe pas.
  • nok user '$friend' is already a friend of '$id' si l'utilisateur $friend est déjà un ami de $id.
  • ok sinon (n'oubliez pas cet affichage).

De la même façon, seuls ces affichages (sur une unique ligne) sont autorisés et il faut que add-friend.sh ne puisse engendrer aucun autre affichage (en particulier, pensez à rediriger la sortie de la commande grep vers /dev/null).
On souhaite maintenant pouvoir poster des messages. Écrivez un script post-message.sh sender receiver ... prenant au moins trois arguments. Les deux premiers arguments sont des utilisateurs : $sender est l'émetteur du message et $receiver est le récepteur du message. Les arguments suivant constituent le message. Ajouter un message revient à ajouter la ligne $sender: ...message... dans le mur de l'utilisateur $receiver, c'est-à-dire dans le fichier $receiver/wall.

Votre script doit afficher sur le terminal :
  • nok user '$sender' does not exist si l'utilisateur $sender n'a pas été entièrement créé (fichier $sender/friends n'existe pas),
  • nok user '$receiver' does not exist si l'utilisateur $receiver n'a pas été entièrement crée,
  • nok user '$sender' is not a friend of '$receiver' si l'utilisateur $sender n'est pas un ami de l'utilisateur $receiver ;
  • ok sinon.

Comme précédemment, veillez à ne produire que les affichages (d'une unique ligne) décrits ici.

Pensez à utiliser la construction donnée avec grep à la question précédente.
On souhaite enfin pouvoir afficher le mur d'un utilisateur. Écrivez une commande display-wall.sh id prenant l'identifiant $id en argument. Votre script doit afficher sur le terminal :
  • nok user '$id' does not exist si $id n'a pas été entièrement créé ;
  • la ligne start-of-file suivie du contenu du mur de $id suivi de end-of-file. Pensez à utiliser la commande cat pour afficher le contenu du mur.

Comme pour les questions précédentes, l'affichage généré par votre programme doit exactement respecter les consignes. Cette commande est la seule qui peut générer des affichages de plus d'une ligne, par exemple : start-of-file darth-vader: bonjour, tu veux être mon ami ? C3P0: joli photo yoda: grosse soirée thème Teletubbies chez Palatine jeudi soir, venez nombreux ! chewbacca: uuuarrrgg ! end-of-file
Mise en place du serveur On souhaite maintenant mettre en place le serveur. Dans un premier temps, votre serveur interprète des requêtes de façon interactive, c'est-à-dire qu'il lit des requêtes à partir du terminal et les exécute. Écrivez un script mini-facebook.sh. Ce script exécute une boucle infinie. À chaque pas de la boucle, le script lit une requête à partir du terminal. Une requête est toujours sous la forme req id args.

Pour lire la requête de l'utilisateur, il vous suffit d'utiliser la commande read req id args. En effet, si l'utilisateur rentre plus de deux mots, req contiendra le premier mot, id le second mot, et args tous les mots qui suivent.

Votre serveur doit ensuite être capable d'interpréter quatre requêtes différentes en utilisant les quatre scripts que vous avez écrits dans la première partie :
  • create id : crée l'utilisateur id ;
  • add id friend : ajoute l'ami friend aux amis de id ;
  • post sender receiver message : poste un message ;
  • display id : affiche le mur de id.
Si l'utilisateur écrit une requête incompréhensible, votre serveur affiche nok bad request dans le terminal.

Pour sélectionner l'action adéquate en fonction de la requête, nous vous invitons à utiliser la commande case que vous trouverez en annexe. Enfin, pour écrire une boucle infinie, il suffit d'écrire while true; do ... done.
À terme, votre serveur est utilisé en parallèle par de nombreux utilisateurs. On souhaite donc rendre l'exécution des commandes parallèles. Il faut donc exécuter les commandes en arrière plan. Malheureusement, vous pourriez avoir des incohérences lorsque deux requêtes accédant aux fichiers ou répertoires du même utilisateur s'exécutent en parallèle.

Il faut donc modifier tous vos scripts pour éviter ces incohérences. Pour cela, vous devez utiliser un verrou nommé $id.lock lorsque vous accédez aux fichiers ou répertoires associés à l'utilisateur $id.

Modifiez mini-facebook.sh pour lancer les commandes en arrière plan et modifiez les quatre fichiers de commandes pour assurer la cohérence.

Dans post-message.sh, les messages sont postés chez le récepteur et non chez l'émetteur. Il faut donc protéger le répertoire du récepteur.
Mise en place des clients On souhaite maintenant mettre en place des clients pour le serveur. Écrivez un script client.sh id prenant en argument un identifiant $id. Ce script est une première ébauche de client. Il vérifie d'abord que l'argument a bien été donné. Ensuite, votre client exécute une boucle infinie. À chaque pas de la boucle, le client lit une requête sous la forme req args. Si $req n'est pas vide, le programme affiche alors sur l'écran la ligne req id args, où $id est l'identifiant passé en argument de client.sh. On souhaite maintenant connecter le client et le serveur avec des tubes nommés. Le serveur doit créer un tube nommé mini-facebook.pipe, alors que le client doit créer un tube nommé $id.pipe, dans lequel $id est l'identifiant passé en argument de client.sh.

De façon à éviter des ouvertures et fermetures de tube intempestives pouvant mener à des blocages ou des erreurs lorsque le tube est fermé alors qu'il reste un interlocuteur, vous devez impérativement utiliser des redirections avancées. On vous demande donc d'ajouter la commande suivante après votre création du tube : exec 3<> nom-du-tubenom-du-tube est le nom du tube que vous venez de créer.

Modifiez vos scripts mini-facebook.sh et client.sh en conséquence.

Les tubes sont juste crées dans cette question, ils seront utilisés plus tard dans l'exercice (questions j et k).
Que ce soit pour le serveur ou le client, le tube nommé doit être détruit lorsque le programme quitte. Actuellement, le programme quitte car vous utilisez vraisemblablement la combinaison de touche control-c pour envoyer un signal INT au processus. Dans le serveur et le client, vous devez donc intercepter ce signal pour détruire le tube et quitter le processus avec la commande exit 0. Modifiez vos scripts mini-facebook.sh et client.sh en conséquence. Au lieu d'afficher req id args sur le terminal, le client doit maintenant envoyer ses requêtes sur le tube du serveur. Le serveur, de son côté, doit lire les requêtes à partir de son tube nommé au lieu de les lire à partir du terminal. Modifiez client.sh et mini-facebook.sh en conséquence.

Pour tester vos programmes, nous vous conseillons d'ouvrir deux terminaux, l'un pour exécuter un client, l'autre pour exécuter le serveur.
Au lieu d'afficher les réponses dans son terminal, votre serveur doit maintenant envoyer les réponses au client. Modifiez votre serveur pour que chacune des quatre commandes écrivent dans le tube du client au lieu d'écrire sur le terminal. Pensez à utiliser $cat $id.pipe pour vérifier que les données envoyées par le serveur à l'utilisateur $id sont bien écrites dans le tube associés à l'utilisateur $id.

Sur l'ensemble du code, vous n'avez à faire que quatre redirections.
Maintenant, on souhaite afficher le contenu du tube nommé chez le client. Le serveur peut envoyer trois types de messages différents, identifiés par le premier mot reçu :
  • Le premier mot est égale à start-of-file. Dans ce cas, la réponse à afficher se trouve sur les lignes suivantes, jusqu'au end-of-file.
  • Le premier mot est égal à ok. Dans ce cas, tout s'est bien passé et vous pouvez afficher un message comme Command successfully executed.
  • Le premier mot est égal à nok. Dans ce cas, une erreur est apparue sur le serveur. Vous pouvez afficher un message comme Error: $msg, où $msg contient la fin de la ligne reçue (rappelez-vous que $msg stocke la fin de la ligne dans read ok msg).
Bravo, vous venez d'écrire votre premier serveur multiprocessus, bienvenue en système !