Skip to content
Snippets Groups Projects
Commit 3e967e80 authored by David Nicolazo's avatar David Nicolazo :beers:
Browse files

exo3

parent 1083d0bb
Branches
No related merge requests found
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <semaphore.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define FILENAME "fichier"
#define ROOMCAPACITY 5
#define MAXNAMELEN 11
#define CHECK(op) { if ((op) == -1) { perror(#op); exit(1); } }
#define NCHECK(op) { if ((op) == NULL) { perror(#op); exit(1); } }
struct reunion
{
char *name;
char **members;
size_t member_count;
};
sem_t *open_filelock(void)
{
sem_open("/" FILENAME ".lock", O_CREAT, 0777, 1);
}
struct reunion *create_reunion(void)
{
struct reunion *reunion = malloc(sizeof(struct reunion));
NCHECK(reunion);
reunion->name = malloc(MAXNAMELEN * sizeof(char));
NCHECK(reunion->name);
reunion->members = malloc(ROOMCAPACITY * sizeof(char*));
NCHECK(reunion->members);
reunion->members[0] = malloc(ROOMCAPACITY * MAXNAMELEN * sizeof(char));
NCHECK(reunion->members[0]);
return reunion;
}
struct reunion *read_reunion(FILE *file)
{
struct reunion *reunion = create_reunion();
if (fgets(reunion->name, MAXNAMELEN, file) == NULL)
{
free_reunion(reunion);
return NULL;
}
reunion->name[strlen(reunion->name) - 1] = '\0'; // remove last '\n'
reunion->member_count = 0;
size_t i = 0;
while (fgets(reunion->members[0] + i, MAXNAMELEN, file))
{
reunion->members[reunion->member_count] = reunion->members[0] + i;
i += strlen(reunion->members[reunion->member_count]);
reunion->members[0][i-1] = '\0'; // remove last '\n'
reunion->member_count++;
}
return reunion;
}
void write_reunion(FILE *file, struct reunion *reunion)
{
fprintf(file, "%s\n", reunion->name);
for (size_t i = 0; i < reunion->member_count; i++)
fprintf(file, "%s\n", reunion->members[i]);
}
void free_reunion(struct reunion *reunion)
{
free(reunion->name);
free(reunion->members[0]);
free(reunion->members);
free(reunion);
}
void control(void)
{
sem_t *sem = open_filelock();
CHECK(sem_wait(sem));
bool occupied;
FILE *file = fopen(FILENAME, "r");
if (file == NULL)
{
switch (errno)
{
case EEXIST:
occupied = false;
break;
default:
perror("opening " FILENAME);
exit(EXIT_FAILURE);
}
}
else
{
struct reunion *reunion = read_reunion(file);
occupied = reunion != NULL && reunion->member_count > 0;
if (reunion != NULL)
free_reunion(reunion);
}
fclose(file);
CHECK(sem_post(sem));
CHECK(sem_close(sem));
if (occupied)
printf("salle occupée\n");
else
printf("salle libre\n");
}
void cleanup(void)
{
sem_t *sem = open_filelock();
CHECK(sem_wait(sem));
CHECK(unlink(FILENAME));
CHECK(sem_close(sem));
CHECK(sem_unlink("/" FILENAME ".lock"));
}
void meet(char *meeting, char *name, unsigned int duration)
{
sem_t *sem = open_filelock();
// begin meeting
CHECK(sem_wait(sem));
FILE *file = fopen(FILENAME, "w+");
NCHECK(file);
struct reunion *reunion = read_reunion(file);
if (reunion == NULL)
{
reunion = create_reunion();
strncpy(reunion->name, meeting, MAXNAMELEN);
strncpy(reunion->members[0], name, MAXNAMELEN);
}
}
int main(int argc, char const *argv[])
{
switch (argc)
{
case 2:
if (!strcmp(argv[1], "controler"))
{
control();
}
else if (!strcmp(argv[1], "nettoyer"))
{
cleanup();
}
else
{
fprintf(stderr, "bad usage: %s controler|nettoyer\n");
return EXIT_FAILURE;
}
break;
case 4:
meet(argv[1], argv[2], atoi(argv[3]));
break;
default:
fprintf(stderr, "bad usage: %s <réunion> <nom> <durée>\n"
" %s controler|nettoyer\n",
argv[0], argv[0]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
File added
#!/bin/sh
PROG=${PROG:=./reunion}
FICHIER=fichier # le fichier modélisant la salle
MARGE=20 # en ms, ne pas changer
#
# Script Shell de test de l'exercice 3
# Utilisation : sh ./test3.sh
#
# Si tout se passe bien, le script doit afficher "Tests ok" à la fin
# Dans le cas contraire, le nom du test échoué s'affiche.
# Les fichiers sont laissés dans /tmp/test2*, vous pouvez les examiner
# Pour avoir plus de détails sur l'exécution du script, vous pouvez
# utiliser :
# sh -x ./test3.sh
# Toutes les commandes exécutées par le script sont alors affichées.
#
# Note : le test est sensible à la charge et à la performance de la
# machine qui l'exécute. Les marges sont normalement suffisantes
# pour une machine actuelle.
#
TMP=/tmp/test3
# il ne faudrait jamais appeler cette fonction
# argument : message d'erreur
fail ()
{
local msg="$1"
echo "Échec du test '$msg'."
exit 1
}
# Si le processus est terminé, signaler une erreur
# $1 = pid
# $2 = msg
ps_termine ()
{
[ $# != 2 ] && fail "ERREUR SYNTAXE ps_termine"
local pid="$1" msg="$2"
kill -0 $pid 2> /dev/null || fail "$msg"
}
# Si le processus existe toujours, signaler une erreur
# $1 = pid
# $2 = msg
ps_existe ()
{
[ $# != 2 ] && fail "ERREUR SYNTAXE ps_existe"
local pid="$1" msg="$2"
kill -0 $pid 2> /dev/null && fail "$msg"
}
# Retourne le nombre de processus encore en vie
# $1 ... = liste des pid
nproc ()
{
local nproc=0
local pid
for pid
do
if kill -0 $pid 2> /dev/null
then nproc=$((nproc+1))
fi
done
echo $nproc
}
est_vide ()
{
local fichier="$1"
test $(wc -l < "$fichier") = 0
}
# Vérifie que le message d'erreur est envoyé sur la sortie d'erreur
# et non sur stdout
verifier_stderr ()
{
[ $# != 1 ] && fail "ERREUR SYNTAXE verifier_stderr"
local base="$1"
est_vide $base.err \
&& fail "Le message d'erreur devrait être sur la sortie d'erreur"
est_vide $base.out \
|| fail "Rien ne devrait être affiché sur la sortie standard"
}
# Vérifie que le fils s'est bien terminé par un code de retour nul
# $1 = pid à vérifier
# $2 = msg
wait_ok ()
{
[ $# != 2 ] && fail "ERREUR SYNTAXE wait_ok"
local pid="$1" msg="$2"
wait $pid || fail "$msg"
}
# Certains tests ne sont disponibles que sur Linux
islinux ()
{
[ x"$(uname)" = xLinux ]
}
# Conversion ms -> s
# Pratique pour tout gérer en ms dans les scripts de test
mstos ()
{
[ $# != 1 ] && fail "ERREUR SYNTAXE mstos"
local ms="$1"
echo "scale=5;$ms/1000" | bc
}
# Sleep en millisecondes. On suppose que la commande "sleep" accepte
# des nombres flottants, ce qui n'est pas POSIX (argh...) mais qui
# est vrai sur beaucoup de systèmes.
msleep ()
{
[ $# != 1 ] && fail "ERREUR SYNTAXE msleep"
local ms="$1"
sleep $(mstos $ms)
}
# comparer les lignes d'un fichier
# $1 = fichier
# $2 ... = lignes
comparer ()
{
[ $# = 0 ] && fail "ERREUR SYNTAXE comparer"
local fichier="$1"
local ligne
shift
for ligne
do
echo "$ligne"
done | diff - "$fichier"
}
##############################################################################
# Nettoyage pour commencer
killall $PROG 2> /dev/null
msleep $MARGE
##############################################################################
# Tests d'erreur sur les arguments
echo "Test 1 : arguments"
# Erreur sur le nombre d'arguments
$PROG > $TMP.out 2> $TMP.err && fail "T1: pas d'arg"
verifier_stderr $TMP
# Erreur sur le nombre d'arguments
$PROG 1 2 > $TMP.out 2> $TMP.err && fail "T1: 2 args"
verifier_stderr $TMP
# Erreur sur le nombre d'arguments
$PROG 1 2 3 4 > $TMP.out 2> $TMP.err && fail "T1: 4 args"
verifier_stderr $TMP
# Erreur sur la durée
$PROG 1 2 -10 > $TMP.out 2> $TMP.err && fail "T1: durée < 0"
verifier_stderr $TMP
# Erreur sur les noms
$PROG n2345678901 x 10 > $TMP.out 2> $TMP.err && fail "T1: nom réunion > max"
verifier_stderr $TMP
$PROG R n2345678901 10 > $TMP.out 2> $TMP.err && fail "T1: nom partic > max"
verifier_stderr $TMP
##############################################################################
# Test de nettoyage et création des objets
echo "Test 2 : nettoyage et création"
rm -rf $FICHIER
touch $FICHIER
$PROG nettoyer > $TMP.out 2> $TMP.err || fail "T2: erreur nettoyer"
test -f $FICHIER && fail "T2: fichier pas supprimé"
rm -rf $FICHIER
mkdir $FICHIER
$PROG nettoyer > $TMP.out 2> $TMP.err && fail "T2: nettoyer devrait retourner une erreur"
rm -rf $FICHIER
# Conserver l'état initial de /dev/shm
if islinux
then ls /dev/shm > $TMP.s1
else echo "Attention : test partiel. Utilisez Linux pour le test complet"
fi
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
# Est-ce que les sémaphores et le fichier ont été créés ?
if islinux
then
ls /dev/shm > $TMP.s2
cmp -s $TMP.s1 $TMP.s2 && fail "T2: 'ls /dev/shm' identique"
fi
test -f $FICHIER || fail "T2: controler: $FICHIER non créé"
ps_termine $PIDZ "controler ne devrait pas se terminer"
kill -TERM $PIDZ
wait $PIDZ
# vérifier ce qu'a indiqué le controler
comparer $TMP.out "salle libre" \
|| fail "T2: mauvaise sortie de controler"
# un petit coup de balai pour ne rien laisser derrière nous
$PROG nettoyer
##############################################################################
# Les choses sérieuses commencent : une réunion
echo "Test 3 : un participant à une réunion T3"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
$PROG R3 A 100 & # t0 = démarrage de A dans réunion T3
PIDA=$!
msleep $MARGE # t0 + 10
ps_termine $PIDA "T3: A terminé trop tôt"
comparer $FICHIER "R3" "A" || fail "T3: '$FICHIER' mal renseigné"
msleep 100 # t0 + 110
ps_existe $PIDA "T3: A pas encore terminé"
wait_ok $PIDA "T3: A terminé avec code retour != 0"
kill -TERM $PIDZ
wait
# vérifier ce qu'a indiqué le controler
comparer $TMP.out "salle libre" \
"salle occupée" \
"salle libre" \
|| fail "T3: mauvaise sortie de controler"
$PROG nettoyer
##############################################################################
# Deux participants à la même réunion
echo "Test 4 : deux participants à une même réunion R4"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
# ils démarrent en même temps (t0) : risque de pb de concurrence
$PROG R4 A 100 & # t0 = démarrage de A dans réunion R4
PIDA=$!
$PROG R4 B 100 &
PIDB=$!
msleep $MARGE # t0 + 20
ps_termine $PIDA "T4: A terminé trop tôt"
ps_termine $PIDB "T4: B terminé trop tôt"
comparer $FICHIER R4 A B > /dev/null
C1=$?
comparer $FICHIER R4 B A > /dev/null
C2=$?
[ $C1 != 0 -a $C2 != 0 ] && fail "T4: '$FICHIER' mal renseigné : R4 A B ou B A"
msleep 100 # t0 + 120
ps_existe $PIDA "T4: A pas encore terminé"
ps_existe $PIDB "T4: B pas encore terminé"
wait_ok $PIDA "T4: A terminé avec code retour != 0"
wait_ok $PIDB "T4: B terminé avec code retour != 0"
kill -TERM $PIDZ
wait
# vérifier ce qu'a indiqué le controler
comparer $TMP.out "salle libre" \
"salle occupée" \
"salle libre" \
|| fail "T4: mauvaise sortie de controler"
$PROG nettoyer
##############################################################################
# Trop de participants à la même réunion
echo "Test 5 : trop de participants à une même réunion R5"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
LPID=""
for i in 1 2 3 4 5
do
$PROG R5 A$i 200 &
LPID="$LPID $!"
done
msleep $MARGE # t0 + 20
$PROG R5 A6 > $TMP.a6out 2> $TMP.a6err &
PIDA6=$!
msleep $MARGE # t0 + 40
ps_existe $PIDA6 "T5: A6 pas encore terminé"
wait $PIDA6 && fail "T5: A6 terminé avec code retour = 0"
for PID in $LPID
do
ps_termine $PID "T4: A1-5 terminé trop tôt"
done
msleep 160 # t0 + 40 + 160 = t0 + 200
for PID in $LPID
do
ps_existe $PID "T4: A1-5 pas encore terminé"
wait_ok $PID "T4: A1-5 terminé avec code retour != 0"
done
kill -TERM $PIDZ
wait
$PROG nettoyer
##############################################################################
# Trois participants à deux réunions différentes
echo "Test 6 : trois participants à deux réunions différentes R61 et R62"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
# A d'abord, pour que R61 démarre avant R62
$PROG R61 A 200 & # t0 = démarrage de A dans réunion R61
PIDA=$!
msleep $MARGE # t0 + 20
$PROG R62 C 200 &
PIDC=$!
$PROG R61 B 380 &
PIDB=$!
# bilan :
# R61 :
# A démarre à t0 et se termine à t0 + 200
# puis B démarre à t0 + 20 et se termine à t0 + 20 + 380 = t0 + 400
# R62 :
# C démarre à la fin de R61 (à t0 + 400) et se termine à t0 + 600
msleep $MARGE # t0 + 20 + 20 = t0 + 40
ps_termine $PIDA "T6: A terminé trop tôt"
ps_termine $PIDB "T6: B terminé trop tôt (1)"
ps_termine $PIDC "T6: C terminé trop tôt (1)"
comparer $FICHIER R61 A B || fail "T6: '$FICHIER' mal renseigné"
msleep 180 # t0 + 40 + 180 = t0 + 220
ps_existe $PIDA "T6: A pas encore terminé"
ps_termine $PIDB "T6: B terminé trop tôt (2)"
ps_termine $PIDC "T6: C terminé trop tôt (2)"
msleep 100 # t0 + 220 + 100 = t0 + 320
ps_termine $PIDB "T6: B terminé trop tôt (3)"
ps_termine $PIDC "T6: C terminé trop tôt (3)"
msleep 100 # t0 + 320 + 100 = t0 + 420
ps_existe $PIDB "T6: B pas encore terminé"
ps_termine $PIDC "T6: C terminé trop tôt (4)"
msleep 200 # t0 + 650
ps_existe $PIDC "T6: C pas encore terminé"
wait_ok $PIDA "T6 : A terminé avec code retour != 0"
wait_ok $PIDB "T6 : B terminé avec code retour != 0"
wait_ok $PIDC "T6 : C terminé avec code retour != 0"
kill -TERM $PIDZ
wait
# vérifier ce qu'a indiqué le controler
comparer $TMP.out "salle libre" \
"salle occupée" \
"salle libre" \
"salle occupée" \
"salle libre" \
|| fail "T6: mauvaise sortie de controler"
$PROG nettoyer
##############################################################################
# Une réunion revient
echo "Test 7 : quatre participants à trois réunions différentes R71 et R72"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
# A d'abord, pour que R71 démarre avant R72
$PROG R71 A 100 & # t0 = démarrage de A dans réunion R71
PIDA=$!
msleep $MARGE # t0 + 10
$PROG R72 B 100 &
PIDB=$!
# bilan :
# R71 :
# A démarre à t0 et se termine à t0 + 100
# R72 :
# B démarre à la fin de R1 (à t0 + 100) et se termine à t0 + 200
msleep $MARGE # t0 + 20
ps_termine $PIDA "T7: A terminé trop tôt"
ps_termine $PIDB "T7: B terminé trop tôt (1)"
msleep 100 # t0 + 120
ps_existe $PIDA "T7: A pas encore terminé"
ps_termine $PIDB "T7: B terminé trop tôt (2)"
# R71 devrait (re-)démarrer à t0+200 et se terminer à t0 + 300
$PROG R71 C 100 & # t0 + 120
PIDC=$!
msleep 80 # t0 + 200
ps_existe $PIDB "T7: B pas encore terminé"
ps_termine $PIDC "T7: C terminé trop tôt"
msleep 100 # t0 + 320
ps_existe $PIDB "T7: C pas encore terminé"
wait_ok $PIDA "T7: A terminé avec code retour != 0"
wait_ok $PIDB "T7: B terminé avec code retour != 0"
wait_ok $PIDC "T7: C terminé avec code retour != 0"
kill -TERM $PIDZ
wait
$PROG nettoyer
##############################################################################
# Concurrence d'accès à une salle occupée
echo "Test 8 : concurrence d'accès à une salle occupée"
$PROG controler > $TMP.out 2> $TMP.err &
PIDZ=$!
msleep $MARGE
# A d'abord, pour que R81 démarre avant les autres
$PROG R80 A 100 & # t0 = démarrage de A dans réunion R61
PIDA=$!
msleep $MARGE # t0 + 20
# Bi essaye d'entrer dans la réunion R8i (1 <= i <= 9)
LPID=""
for i in 1 2 3 4 5 6 7 8 9
do
$PROG R8$i B$i 100 &
LPID="$LPID $!"
done
# bilan :
# R80 :
# A démarre à t0 et se termine à t0 + 100
# R81 à R89 :
# Bi démarre à t0+j*100 et se termine à t0+(j+1)*100
# où j \in [1,9] (j n'est pas forcément égal à i)
msleep 640 # t0 + 660
NPROC=$(nproc $LPID)
[ $NPROC != 4 ] && fail "T8: à t0+640, il reste $NPROC processus au lieu de 4"
msleep 300 # t0 + 960
NPROC=$(nproc $LPID)
[ $NPROC != 1 ] && fail "T8: à t0+960, il reste $NPROC processus au lieu de 1"
msleep 60 # t0 + 1020
ps_existe $PIDA "T8: A pas encore terminé"
wait_ok $PIDA "T8: A terminé avec code retour != 0"
for PID in $LPID
do
ps_existe $PID "T8: Bi pas encore terminé"
wait_ok $PID "T8: Bi terminé avec code de retour != 0"
done
comparer $FICHIER || fail "T8: '$FICHIER' mal renseigné"
# on peut redémarrer une nouvelle réunion
$PROG R81 C 100 & # nouveau t0
PIDC=$!
msleep $MARGE
ps_termine $PIDC "T8: C terminé trop tôt"
msleep 100 # t0 + 20 + 100 = t0 + 120
ps_existe $PIDC "T8: C pas encore terminé"
wait_ok $PIDC "T8: C terminé avec code de retour != 0"
kill -TERM $PIDZ
wait
$PROG nettoyer
##############################################################################
# Fini !
rm -f $TMP.*
echo "Tests ok"
exit 0
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment