Partie 2 - Fichiers =================== Pour les exercices ne demandant pas d’écrire un code C, créez un fichier Markdown (``p2e[numero exo].md``) pour lister les commandes utilisées et les résultats. p2e1 — Métadonnées et types (shell) ----------------------------------- Créez 3 fichiers : - un fichier texte : ``echo "hello" > p2e1.txt`` - un fichier binaire : ``head -c 5000 /dev/urandom > p2e1.dat`` - un script shell ``p2e1.sh`` que vous rendrez exécutable (``chmod +x p2e1.sh``) contenant : .. code-block:: bash #!/usr/bin/env bash echo "coucou" Affichez leurs métadonnées avec ``stat`` et leur nature avec ``file``. Comparez les champs clés (type, taille, blocs, droits). Commandes : - ``stat FICHIER`` : taille logique, blocs, mode (permissions), inode. - ``ls -lhi`` : taille lisible, numéros d’inodes. - ``file FICHIER`` : type de contenu (texte ASCII, binaire, script, etc.). p2e2 — Liens physiques et symboliques (shell) --------------------------------------------- Créez un fichier ``p2e2_source.txt``. Créez un lien symbolique ``p2e2_sym.txt`` et un lien physique ``p2e2_hard.txt`` vers ``p2e2_source.txt``. Observez ``ls -li`` / ``stat`` des 3 fichiers. Modifiez le contenu d’un des fichiers et regardez le contenu dans les autres fichiers. Supprimez le fichier ``p2e2_source.txt`` puis observez à nouveau ``ls -li``, ``stat`` et ``cat`` sur ``p2e2_hard.txt`` et ``p2e2_symbolique.txt`` Commandes : - ``ln SOURCE DEST`` : voir ``man`` pour les options physique/symbolique p2e3 — I/O fichier (libc/stdio) ------------------------------- Écrire un programme ``p2e3.c`` qui utilise les fonctions de la libc pour manipuler les fichiers (``fopen``, ``fclose``, …). Il doit : ouvrir un fichier ``p2e3_anneau.txt`` en écriture et écrire : .. code-block:: text Un Anneau pour les gouverner tous, un Anneau pour les trouver, un Anneau pour les amener tous et dans les ténèbres les lier. Puis ferme le fichier et l’ouvre en lecture pour afficher son contenu ligne par ligne avec ``fgets``. Fonctions : - ``FILE *fopen(const char *pathname, const char *mode)`` pour ouvrir le fichier avec droits d’écriture/de lecture (``"w"``/``"r"`` pour écriture/lecture en binaire) - ``int fprintf(FILE *stream, const char *format, ...)`` pour écrire une chaine formatée ``format`` dans le flux (fichier/stdout/stderr) - ``int fputs(const char *s, FILE *stream)`` écrit ``s`` dans le flux (fichier/stdout/stderr) - ``char *fgets(char *s, int size, FILE *stream)`` écrit dans ``s`` ``size`` octets depuis le flux ``stream`` - ``int fclose(FILE *stream)`` ferme le flux p2e4 — I/O texte POSIX (bas niveau) ----------------------------------- Écrire un programme ``p2e4.c`` qui utilise les fonctions POSIX pour manipuler les fichiers (``open``, ``close``, …). Il doit : ouvrir un fichier ``p2e4_nazg.txt`` en écriture et écrire : .. code-block:: text Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk agh burzum-ishi krimpatul Puis ferme le fichier et l’ouvre en lecture pour afficher son contenu avec ``read``. Pour les includes : .. code-block:: c #define _POSIX_C_SOURCE 200809L // utilise la version de 2008 de POSIX #include #include #include #include #include #include #include Fonctions : - ``int open(const char*, int flags, mode_t mode)`` pour ouvrir le fichier, à la création les flags à ``O_WRONLY|O_CREAT|O_TRUNC`` et le mode à ``0644`` (mode obligatoire si création du fichier avec ``O_CREAT``) - ``int open(const char *pathname, int flags)`` pour ouvrir le fichier avec le flag ``O_RDONLY`` pour la lecture - ``ssize_t write(int fd, const void *buf, size_t count)`` pour écrire dans ``fd`` (file descriptor) ``count`` octets de ``buf`` - ``ssize_t read(int fd, void*, size_t)`` : lectures partielles possibles → boucler jusqu’à ``0`` (EOF) - ``int close(int fd)`` p2e5 — I/O binaire (libc) ------------------------- Écrire un programme ``p2e5.c`` qui initialise un tableau d’\ ``int32_t`` avec ``{INT32_MIN, INT32_MIN + 1, -2048, -1, 0, 1, 2048, INT32_MAX - 1, INT32_MAX}`` (``int32_t``, ``INT32_MIN/MAX`` disponibles avec ``#include ``) puis l’écrit dans un fichier binaire ``p2e5.dat`` via ``fwrite``. Puis ouvre le fichier pour lire les valeurs avec ``fread``. Vérifier la taille du fichier avec stat et comparer à ``sizeof(int32_t)*9``. Inspecter le contenu du fichier avec ``xxd p2e5.dat`` ou ``hexdump -C p2e5.dat`` et ``od -t d4 p2e5.dat``. xxd affiche les octets dans l’ordre mémoire. En little-endian, un mot 32 bits 0xAABBCCDD est stocké DD CC BB AA. Pour reconstituer la valeur, il faut réassembler les 4 octets dans l’ordre inverse (ou utiliser od/hexdump formatés). Fonctions : - ``FILE *fopen(const char *pathname, const char *mode)`` pour ouvrir le fichier avec droits d’écriture/de lecture (``"wb"``/``"rb"`` pour écriture/lecture en binaire) - ``size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)`` pour écrite le contenu pointé par ``ptr`` dont les éléments ont une taille de ``size`` octets (ici ``sizeof(int32_t)``) et au nombre de ``nmemb`` (le nombre d’éléments dans le tableau) à écrire dans le flux ``stream`` - ``int fclose(FILE *stream)`` ferme le flux - ``size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)`` lit le contenu du flux ``stream`` et écrit dans ``ptr`` les données de taille ``size`` au nombre de ``nmemb`` p2e6 — Renommer un fichier (libc) --------------------------------- Écrire un programme ``p2e6.c`` : ``p2e6 OLD NEW`` → qui renomme le fichier ``OLD`` en ``NEW``, si ``NEW`` n’existe pas, sinon, affiche un message d’erreur. Fonctions : - ``int access(const char *path, int amode)`` avec ``amode = F_OK``, retourne 0 si le fichier ``path`` existe - ``int rename(const char *old, const char *new)`` renomme ``old`` en ``new`` p2e7 — Lecture partielle (seek POSIX) ------------------------------------- Écrire un programme ``p2e7`` qui lit les 10 octets après les 15 premiers d’un fichier avec les fonctions POSIX. Avec un fichier contenant : .. code-block:: text ABCDEFGHIJKLMNOPQRSTUVWXYZ Le programme affiche : .. code-block:: text PQRSTUVWXY Fonctions : - ``int fd = open(path, O_RDONLY)`` pour ouvrir le fichier en lecture - ``off_t lseek(int fd, off_t offset, int whence)`` pour se déplacer au 15ème octet (offset), ``whence`` = ``SEEK_SET`` (pour le début du fichier) - ``ssize_t read(int fd, void *buf, size_t count)`` pour lire ``count`` octets de ``fd`` à écrire dans ``buf`` - ``int close(int fd)`` pour fermer le fichier p2e8 — cat (libC) ----------------- Afficher le contenu d’un fichier avec préfixe ``N:`` à largeur fixe (min. 4), similaire à un ``cat -n``, une ligne par ``getline``. Utilisez les fonctions de la lib C : - ``FILE* fopen(..., "r")`` et ``fclose`` - ``ssize_t getline(char **lineptr, size_t *n, FILE *stream)`` retourne ``ssize_t`` (nombre de caractères lus) ou -1 (si EOF/erreur, vérifier ``feof`` vs ``ferror``) et si lineptr est\ ``NULL``, ``getline`` alloue ``n`` octets dans ``lineptr`` qu’il faudra libérer. ``lineptr`` contient ensuite la prochaine ligne du flux ``stream`` p2e9 — ``head -n`` (libC) ------------------------- Écrire un programme ``p2e9 [-n N] FILE`` qui affiche les ``N`` premières lignes du fichier ``FILE`` (défaut ``10`` lignes). - ``int strcmp(const char *s1, const char *s2)`` retourne 0 si ``s1`` et ``s2`` sont égaux - ``long strtol(const char *nptr, char **endptr, int base)`` retourne un long int correspondant au nombre écrit dans ``nptr`` avec la base donnée, pour convertir ``argv[i]`` en nombre : ``n = (int)strtol(argv[++i], NULL, 10);`` p2e10 — Créer une arborescence + liens -------------------------------------- ``p2e10 rep_name nb_d nb_f`` crée : - un répertoire ``rep_name`` - pour ``d ∈ [1..nb_d]`` : ``rep_name/sous_rep_d/`` - pour chaque ``f ∈ [1..nb_f]`` : fichier vides ``rep_name/sous_rep_d/sous_rep_d_fichier_f.txt`` - au niveau racine ``rep_name/`` : - lien physique ``lien_physique_vers_sous_rep_1_fichier_1.txt`` → ``sous_rep_1/sous_rep_1_fichier_1.txt`` - lien symbolique ``lien_symbolique_vers_sous_rep_1_fichier_2.txt`` → ``sous_rep_1/sous_rep_1_fichier_2.txt`` Fonctions : - ``int mkdir(const char *path, mode_t mode);`` (ex : ``mode=0755``) - Création fichier : ``open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644)`` puis ``close`` - Liens : ``int link(const char *oldpath, const char *newpath);`` (physique) ``int symlink(const char *target, const char *linkpath);`` (symbolique) - Construction de chemins : ``snprintf`` dans des buffers dimensionnés. p2e11 — Copier un fichier (POSIX) --------------------------------- Écrire ``p2e11 SOURCE DEST`` qui copie ``SOURCE`` vers ``DEST`` en utilisant les appels système POSIX de bas niveau. Fonctions : - ``int open(const char *path, int flags, ...);`` - Source : ``open(path, O_RDONLY)`` - Dest : ``open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644)`` - ``ssize_t read(int fd, void *buf, size_t count);`` - ``ssize_t write(int fd, const void *buf, size_t count);`` - ``int close(int fd);`` Utilisez un tampon (``char buffer[]``) de 8192 octets (2 pages mémoire). .. code-block:: c // Active les extensions POSIX récentes (getline, etc.) #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include /** * Écrit exactement `count` octets de `buf` dans le descripteur `fd`, * en répétant les appels à write() si nécessaire (cas d'écritures partielles * ou interruptions par un signal EINTR). * Retourne le nombre total d'octets écrits, ou -1 en cas d'erreur. */ ssize_t write_full(int fd, const void *buf, size_t count) { size_t done = 0; while (done < count) { ssize_t w = write(fd, (const char *)buf + done, count - done); if (w > 0) { done += (size_t)w; } else if (w < 0 && errno == EINTR) { continue; } else { return -1; } } return (ssize_t)done; } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s SOURCE DEST\n", argv[0]); return EXIT_FAILURE; } // TODO return EXIT_SUCCESS; } Pour le fichier, en prendre un de plus de 8192 octets et vérifier que tout est bien copié : .. code-block:: bash # pour télécharger Dracula de Bram Stoker (domaine public - Project Gutenberg) wget https://www.gutenberg.org/cache/epub/345/pg345.txt .. code-block:: bash $ # -Wconversion vérifie les types $ gcc -std=c2x -Wall -Wextra -pedantic -Wconversion -g p2e11.c -o p2e11 $ strace ./p2e11 pg345.txt dracula.txt ... ... $ # si les valeurs de sha256sum sont les mêmes, le contenu est le même $ sha256sum dracula.txt pg345.txt 50a66d0773e9476e97bd731947ba03ecf7f7f92eefc59c0027acf48129dc6cbf dracula.txt 50a66d0773e9476e97bd731947ba03ecf7f7f92eefc59c0027acf48129dc6cbf pg345.txt $ # si cmp n'affiche rien, les fichiers sont les mêmes $ cmp dracula.txt pg345.txt p2e12 — Éviter les accès concurrents ------------------------------------ Écrire un programme ``p2e12`` qui ouvre un fichier ``p2e12.txt``, lit un entier, l’incrémente et réécrit la valeur, en utilisant ``flock`` pour empêcher deux exécutions simultanées. Lancer plusieurs instances du programme en parallèle et observer que la valeur finale reste cohérente : .. code-block:: bash > # lance 1000 instances en parallèle (-P100 tente d'en lancer 100 simultanées) > seq 1 1000 | xargs -n1 -P100 -I{} ./p2e12 > /dev/null 2>&1