Examen de Systèmes I (Licence - Janvier 2001)

Exercice

Soit la séquence de commandes suivantes (rien d'autre ne se passe dans le système pendant cette séquence) :

$ ls rep
f1 f2
$ ls -l rep
rep/f1: Autorisation refusée
rep/f2: Autorisation refusée
  1. Comment une telle situation est-elle possible ?

Exercice

Soit l'exécutable a obtenu après compilation du code source suivant :

int main(int argc,char *argv[]) {
  int d, i;

  d = open("f",O_WRONLY|O_TRUNC);
  if (d==-1) exit(EXIT_FAILURE);
  for (i=0; i<100000; i++) {
    if (write(d,argv[0],1)==-1) { close(d); exit(EXIT_FAILURE); }
  }
  close(d);
  return EXIT_SUCCESS;
}

Soit l'exécutable c obtenu après compilation du code source suivant :

#define LG 2

int main(int argc,char *argv[]) {
  int d, l, t;
  char c[LG];

  l = t = 0;
  d = open("f",O_RDONLY);
  if (d==-1) exit(EXIT_FAILURE);
  while ( (l=read(d,c,LG))>0 ) t += l;
  close(d);
  printf("J'ai lu %d octets\n",t);
  return EXIT_SUCCESS;
}

On pourra supposer qu'avant toute commande le fichier f est vide.

  1. On exécute la commande $ ln a b.
    1. On exécute la commande suivante a & a &. Que contient le fichier f après la terminaison ?
    2. On exécute la commande suivante a & b &. Que contient le fichier f après la terminaison ?
  2. On exécute la commande sed -e "s/O_TRUNC/O_TRUNC|O_APPEND/" a.c > b.c. Puis on compile pour obtenir l'exécutable b.
    1. Que contient le fichier f après exécution de la commande a & b & ?
    2. Que contient le fichier f après exécution de la commande b & b & ?
    1. Qu'affiche la commande a & c ?
    2. Quelle est l'influence de la constante LG ?

Exercice

Soit le programme p obtenu après compilation du code source suivant :

int main(int argc,char *argv[]) {
  int d1, d2; ssize_t r; char c;

  if (argc<3) exit(EXIT_FAILURE);
  d1 = open(argv[1],O_RDONLY);
  if (d1==-1) exit(EXIT_FAILURE);
  d2 = open(argv[2],O_WRONLY);
  if (d2==-1) { close(d1); exit(EXIT_FAILURE); }
  while ( (r=read(d1,&c,1))==1 ) {
    if ( write(d2,&c,1)!=1 ) { close(d1); close(d2); exit(EXIT_FAILURE); }
  }
  close(d1); close(d2);
  return r==0?EXIT_SUCCESS:EXIT_FAILURE;
}

Avant toute exécution on effectue les commandes suivantes :

$ echo "Il était une fois..." > f1 # Création d'un fichier f1
$ touch f2 # Création d'un fichier vide de nom f2
$ mkfifo t t1 t2 # Création de trois tubes nommés t t1 et t2
$
  1. Que produit le programme précédent lorsqu'on lance la commande p f1 f2 ?
  2. Que se passe-t'il si on exécute p f1 t & p t f2 ou p t f2 & p f1 t ?
  3. Que se passe-t'il si on exécute p t1 t2 & p t2 t1 ?
  4. On modifie le programme de façon à remplacer l'instruction open(argv[1],O_RDONLY); par
    open(argv[1],O_RDONLY|O_NONBLOCK);. Cela change-t'il le comportement des trois commandes précédentes ? Si oui, lesquelles et en quoi ?

Problème

Il s'agit d'écrire une application de messagerie interactive contruite sur un modèle client/serveur. Il existe deux types de clients : les lecteurs (qui se contentent de recevoir des messages du serveur) et les écrivains (qui se contentent d'envoyer des messages au serveur).

Le serveur utilise un tube nommé de nom /tmp/tube_serveur sur lequel il reçoit des requêtes de quatre types :

CNX tube
indiquant qu'un nouveau lecteur écoute sur le tube spécifié.
BRDCST message
indique qu'un message est à diffuser à tous les lecteurs.
CLO tube
indique qu'un lecteur désire se retirer.
SHUT
termine le serveur et donc la messagerie.

Le lecteur reçoit sur un tube nommé qu'il a choisi les réponses suivantes :

MSG message
indiquant qu'un message a été posté dans la messagerie.
CLO message
qui indique que le lecteur doit maintenant cesser d'écouter pour les raisons invoquées dans le message.

On vous donne le fichier d'entête suivant :

/* Types des messages client -> serveur */
#define CNX    0
#define BRDCST 1
#define SHUT   -1

/* Types des messages serveur -> client */
#define MSG 1

/* Types des messages serveur et client */
#define CLO    2

struct msg {
  int type;
  char msg[MSGLEN]; // Message â diffuser ou nom du tube
};

Première partie

Écrire le code de la commande serveur.

Deuxième partie

Écrire le code de la commande lecteur.

Cette commande se contente de créer un tube nommé dont le nom sera obtenu par un appel à la fonction char *tmpnam(NULL);, puis d'indiquer au serveur qu'il doit maintenant diffuser les messages sur ce tube. Ensuite elle se contente de réceptionner les messages en provenance du serveur, de les interpréter et d'afficher leur contenu sous forme lisible à l'utilisateur.

Troisième partie

Écrire le code de la commande ecrivain.

Cette commande est destinée à envoyer des messages au serveur. Elle prend divers paramètres :

D message
qui envoie un message à diffuser au serveur.
T tube
qui demande au serveur de cesser d'envoyer des messages sur le tube spécifié.
X
qui demande au serveur de s'arrêter.