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 :

#!/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 :

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 :

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 :

#define _POSIX_C_SOURCE 200809L // utilise la version de 2008 de POSIX
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

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 <stdint.h>) 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 :

ABCDEFGHIJKLMNOPQRSTUVWXYZ

Le programme affiche :

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 estNULL, 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.txtsous_rep_1/sous_rep_1_fichier_1.txt

    • lien symbolique lien_symbolique_vers_sous_rep_1_fichier_2.txtsous_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).

// Active les extensions POSIX récentes (getline, etc.)
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

/**
* É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é :

# pour télécharger Dracula de Bram Stoker (domaine public - Project Gutenberg)
wget https://www.gutenberg.org/cache/epub/345/pg345.txt
$ # -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 :

> # 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