CSC 4103 – Programmation système

Portail informatique
L'objectif de cette session d'exercices est de manipuler des fichiers.

Une banque stocke le solde de chacun des comptes qu'elle gère dans un fichier sous la forme : solde_compte_0solde_compte_1solde_compte_2...solde_compte_n

Le système de gestion des comptes de la banque peut générer des fichiers dans 2 formats différents: les soldes peuvent être stockés sous forme d'une chaîne de caractères ASCII, ou sous la forme d'un int. Vous avez à votre disposition 2 fichiers représentant les même données: fichier_ascii.dat fichier_binaire.dat

Utilisez l'éditeur hexadécimal bless (ou xxd) pour consulter le contenu des deux fichiers.

Écrivez maintenant un programme read_bank_ascii.c affichant le contenu du fichier fichier_ascii.dat, et un programme read_bank_binary.c affichant le contenu du fichier fichier_binaire.dat.

Dans cet exercice, nous concevons un programme permettant de chiffrer le contenu d'un fichier, de façon à en protéger le contenu. L'algorithme que nous mettons en œuvre est simple et facile à casser, le but est surtout de vous faire manipuler des fichiers. Notre algorithme utilise le chiffrage xor : à partir d'une clé K (comprise entre 0 et 255), il s'agit d'appliquer l'opérateur xor avec comme opérande K sur chacun des octets d'un fichier. Pour décrypter le fichier, il suffit de le ré-encrypter avec la même clé, puisque A xor K xor K = A.

Votre programme doit recevoir trois arguments :
  • une clé,
  • le chemin d'un fichier d'entrée,
  • le chemin d'un fichier de sortie.

Votre programme doit simplement appliquer le chiffrage xor avec la clé passée en argument sur le fichier d'entrée, et écrire le résultat dans le fichier de sortie.

Pour commencer, écrivez un programme vérifiant qu'il a bien reçu le nombre adéquat d'arguments. Souvenez-vous que l'argument 0 est toujours le nom du programme. Si votre programme ne reçoit pas le nombre adéquat de paramètres, il doit écrire un message adéquat sur la sortie d'erreur standard (stderr) avec la fonction fprintf, et quitter avec le code de retour EXIT_FAILURE (qui, on vous le rappelle, signifie par convention que le programme s'est terminé avec une erreur).

Pour vérifier que votre programme écrit correctement sur la sortie d'erreur standard, vous pouvez lancer votre programme en redirigeant la sortie standard vers /dev/null. Par exemple :

$ ./encode >/dev/null Usage: ./encode key in out
Convertissez l'argument clé en entier non signé sur 8 bits (avec le type uint8_t défini dans stdint.h) avec la fonction atoi, et affichez ce paramètre sur la sortie d'erreur standard, accompagné des noms des fichiers d'entrée et de sortie. Vous devriez avoir un affichage similaire à celui-ci : $ ./encode 10 /etc/passwd plip encode /etc/passwd with the key 10 to plip Ouvrez un flux nommé in à partir du fichier d'entrée. En cas d'erreur (i.e., si la fonction fopen renvoie NULL), vous afficherez la cause de l'erreur sur la sortie d'erreur avant de quitter (avec le code de retour EXIT_FAILURE).

Il faut savoir que fopen, comme la plupart des fonctions C standards, stocke la cause de l'erreur sous la forme d'un entier dans la variable errno définie dans errno.h, et que la fonction char* strerror(int e) définie dans string.h vous donnera une chaîne de caractère expliquant l'erreur numéro e de façon conviviale pour un humain. Par exemple, si le fichier toto n'existe pas, vous devriez obtenir un affichage similaire à celui-ci  $ ./encode 10 toto plop encode toto with the key 10 to plop fopen toto: No such file or directory
De façon similaire, ouvrez un flux nommé out à partir du fichier de sortie. En cas d'erreur, vous afficherez un message expliquant la cause de l'erreur. Vous pouvez par exemple essayer d'utiliser un répertoire comme chemin de sortie : $ ./encode 10 /etc/passwd /bin encode /etc/passwd with the key 10 to /bin fopen /bin: Is a directory Nous pouvons maintenant encoder le fichier. Pour cela, nous mettons en œuvre une fonction nommée encode prenant trois paramètres :
  • une clé d'encodage (un entier non signé sur 8 bits),
  • un flux d'entrée,
  • un flux de sortie.

Dans un premier temps, votre fonction doit lire le fichier octet par octet avec la fonction fread, encoder le caractère avec la clé (le caractère ^ est l'opérateur xor en C), puis écrire le caractère dans le flux de sortie.

Vérifiez que votre programme est correct en encodant le fichier /etc/passwd avec la clé 10 dans un fichier plop. Vérifiez que le contenu de plop est bien radicalement différent de celui de /etc/passwd. Ensuite, vérifiez que si vous encodez plop avec la clé 10 dans un fichier plip, le fichier plip est bien identique au fichier /etc/passwd, i.e., que vous avez bien décrypter votre fichier plop.
Actuellement, votre programme appelle la fonction fread pour chaque caractère, ce qui est loin d'être optimal. Nous optimisons donc notre fonction dans cet exercice.

Au lieu de lire votre fichier octet par octet, nous vous suggérons de définir un tampon de caractères de 256 octets, et d'effectuer vos lectures par blocs de 256 caractères avec fread. De façon à bien écrire exactement le nombre d'octets du fichier d'entrée dans le fichier de sortie, pensez à utiliser le retour de la fonction fread, qui indique le nombre de caractères lus.
Comme il est bien agréable de travailler aussi avec l'entrée ou la sortie standard, modifiez votre programme de façon à utiliser le flux stdin si le nom du fichier d'entrée est égal à la chaîne de caractères "-", et à utiliser le flux stdout si le nom du fichier de sortie est égal à la chaîne de caractères "-". Souvenez-vous que la fonction strcmp permet de comparer des chaînes de caractères.
Dans cet exercice, nous gérons des fichiers de notes. Vous pouvez trouver une liste d'étudiants à cette adresse. L'exercice se compose de deux parties. Dans une première partie, vous générez un fichier de notes à partir de la liste d'étudiants. Dans la seconde partie, vous apprenez à modifier ce fichier de notes. Création du fichier de note Dans cette partie, nous écrivons un programme nommé generate. Ce programme doit recevoir trois arguments :
  • un fichier d'entrée contenant des noms d'étudiants (le fichier student.txt),
  • un fichier de sortie contenant des noms d'étudiants et des notes,
  • une note initiale.

Le programme generate doit affecter la note initiale à chacun des étudiants du fichier d'entrée, et écrire le résultat dans le fichier de sortie. Pour commencer, vérifiez que votre programme reçoit bien trois arguments. Si ce n'est pas le cas, votre programme doit se terminer avec un message d'erreur adéquat. Ensuite, votre programme doit ouvrir les fichiers d'entrée et de sortie, respectivement en lecture seul, et en écriture tronquée (i.e., si le fichier existe, il est tronqué). Si vous rencontrez une erreur pendant l'une des ouvertures, vous veillerez à quitter votre programme avec un message d'erreur adéquat. Pour cela, pensez à utiliser strerror(errno). Maintenant, votre programme doit lire le fichier d'entrée ligne à ligne, jusqu'à la fin du fichier, en utilisant la fonction fgets. Dans un premier temps, votre programme doit simplement afficher le nom de chaque étudiant dans le terminal. On considère qu'un nom est correct si il fait moins de 256 caractères. Dans le code, au lieu d'utiliser directement la valeur 256, on vous demande de définir cette valeur à l'aide d'une macro, et de placer cette macro dans un fichier d'entête nommé student.h, que vous devez inclure avec la directive #include. Ajoutez au fichier student.h la définition d'une structure nommée student et contenant les champs suivants :
  • name: le nom de l'étudiant. Ce nom est une chaîne faisant au plus 256 caractères. Pensez à utiliser votre macro plutôt que d'écrire directement ce 256.
  • rank: la note associé à l'étudiant. On ne considère que des notes entières dans l'exercice.
Dans generate, au lieu d'afficher le nom de l'étudiant, vous devez remplir une instance de la structure student avec le nom de l'étudiant se trouvant dans le fichier d'entrée et avec la note initiale passée en argument. Après avoir rempli cette structure, écrivez là dans le fichier de sortie avec la fonction fwrite. Comme vous êtes une promotion exceptionnelle, vous pouvez naturellement affecter la note initiale de 17 à tous les étudiants ! Bravo à toutes et à tous ! Modification du fichier de notes Dans cette partie, nous écrivons un nouveau programme nommé modify, prenant trois arguments :
  • un fichier de notes,
  • un nom d'étudiant,
  • une note.
Vous remarquerez qu'un nom d'étudiant est composé d'un ou plusieurs prénoms et d'un ou plusieurs noms, le tout séparé par des espaces. Comme l'espace sur la ligne de commande est le séparateur d'argument, appeler modify notes.dat Zabeth La Chouette 20 revient à appeler le programme modify avec 5 arguments et non 3.

Comme vous vous souvenez parfaitement, et avec nostalgie, du module CSC3102, vous n'avez, bien entendu, pas oublié qu'une chaîne de caractère entourée de guillemets compte comme un unique argument. En d'autres termes, modify notes.dat "Zabeth La Chouette" 20 appelle bien le programme modify avec trois arguments.
Pour commencer, vérifiez que le nombre d'arguments est correct et que, lors de l'ouverture du fichier de notes passé en argument en mode lecture-écriture, vous n'avez pas d'erreur. Pensez à utiliser strerror(errno) pour afficher de façon conviviale l'éventuelle erreur d'ouverture. Plutôt que de lire les notes d'étudiants une par une, nous souhaitons lire l'ensemble des notes avec un unique appel à fread. Pour cela, il faut être capable de connaître la taille de votre fichier. En utilisant judicieusement fseek et ftell, essayez de trouver la taille du fichier en nombre d'octets. Affichez cette taille, et vérifiez que cette taille est correcte en la comparant à ce que vous donne la commande ls -l. Maintenant que nous connaissons la taille du fichier, nous pouvons en déduire le nombre d'étudiants en divisant la taille du fichier par la taille d'une structure student. Après avoir inclus le fichier student avec la directive #include, calculez et affichez le nombre d'étudiants. Allouez maintenant un tableau pouvant recevoir les étudiants avec la fonction malloc. Utilisez ce tableau pour lire l'ensemble du fichier de notes avec un unique appel à fread. Affichez maintenant la note de chaque étudiant en parcourant votre tableau d'étudiants. Après l'affichage de la note de chaque étudiant effectué à la question précédente, effectuez un second parcours du tableau d'étudiant. Pendant ce parcours, vous devez trouver l'étudiant dont le nom est passé en argument. Une fois cet étudiant identifié :
  • modifiez sa note en utilisant le dernier paramètre de modify,
  • positionnez de façon adéquat la tête de lecture du fichier de notes sur son entrée,
  • écrivez les nouvelles valeurs de sa structure student.
Vous pouvez vérifier que votre modification est correcte en lançant une seconde fois modify, puisque modify affiche aussi la note de chaque étudiant (voir question précédente).