Partie 2 - Fichiers

Support présentation

Introduction

Dans les systèmes Unix/Linux, un fichier est une simple séquence d’octets stockée sur un périphérique de stockage. Le système ne connaît pas sa signification (texte, image, vidéo, binaire, base de données…). C’est le programme qui interprète les octets. Il existe cependant des standards de format (.png, .mp3, .mp4…) permettant à différents programmes de lire un fichier de la même manière, mais rien n’est imposé par le système. La commande file détermine le type de contenu.

Le principe fondamental d’Unix : « Tout est fichier ». Cela inclut :

  • fichiers classiques (texte, binaire, vidéo…),

  • répertoires (/home, /tmp…),

  • périphériques (clavier, disque, carte son…),

  • tubes (pipes),

  • sockets,

  • liens symboliques,

  • etc.

La plupart de ces objets peuvent être ouverts avec open puis manipulés avec les mêmes primitives système (read, write, close…). Il y a quelques exceptions : les répertoires se lisent avec opendir/readdir (et non avec read) et les sockets se créent avec socket (et non avec open), mais se lisent et s’écrivent ensuite avec read/write.

Sous Linux, les fichiers sont organisés sous forme de structure arborescente, commençant à la racine /. La commande tree permet d’afficher l’arborescence d’un répertoire :

$ tree -L 1 /
/
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib32 -> usr/lib32
├── lib64 -> usr/lib64
├── libx32 -> usr/libx32
├── lost+found
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/sbin
├── snap
├── srv
├── sys
├── tmp
├── usr
└── var

23 directories, 0 files
Résumé des différents répertoires sous la racine de Linux. Plus d'informations sur le site source.

Source de l’image.

Pour désigner un fichier, on distingue deux types de chemins :

  • absolu : commence à la racine / :

    • /home/user/file.txt,

    • /bin/bash.

  • relatif : dépend du répertoire courant :

    • ./file.txt (où . représente le répertoire courant),

    • ../autre.txt (où .. représente le répertoire parent).

Système de fichiers

Le système de fichiers (ext4, NTFS, FAT32…) définit l’organisation des fichiers sur le disque (le format sur disque). Il est pris en charge par un pilote qui fait partie du noyau.

Il gère :

  • les inodes, répertoires et blocs disque,

  • les permissions et métadonnées,

  • l’accès efficace et sécurisé aux fichiers,

  • la cohérence des données (journalisation, verrouillage…).

Nous obtenons la chaîne suivante :

votre programme → descripteur de fichier → noyau Linux (système de fichiers virtuel, puis pilote du système de fichiers, inodes et répertoires) → pilote du disque → disque physique

Lorsque votre programme veut lire un fichier, il appelle open(). Le noyau associe alors le fichier à un descripteur de fichier (un int). Le programme utilise ensuite ce descripteur avec read(), write(), close()…

Il existe de nombreux systèmes de fichiers, chacun ayant ses spécificités en termes de performances, fiabilité, compatibilité ou fonctionnalités.

Quelques exemples courants :

  • ext4 (Linux)

    • Le plus répandu sous Linux.

    • Rapide, fiable, avec journalisation.

    • Gère de très gros fichiers et volumes.

    • Limite de taille : fichier jusqu’à 16 To, partition jusqu’à 1 Eo.

  • NTFS (Windows)

    • Par défaut sur Windows modernes.

    • Supporte les droits d’accès avancés, la compression, le chiffrement.

    • Très bonne compatibilité Windows, lecture/écriture sous Linux (pilote ntfs3 du noyau), écriture limitée sous macOS.

  • FAT32 (Windows, ancien, universel)

    • Très compatible (clé USB, cartes SD, BIOS).

    • Mais limité : fichiers max 4 Go, partitions max 2 To.

    • Pas de journalisation → moins fiable en cas de coupure brutale.

  • exFAT (Microsoft, optimisé pour flash)

    • Successeur de FAT32, sans limite de 4 Go par fichier.

    • Très utilisé pour les clés USB, cartes SDXC, disques externes.

    • Supporté par Windows, macOS et Linux (nativement depuis le noyau 5.4).

  • XFS (Linux)

    • Système journalisé hautes performances.

    • Optimisé pour les très gros fichiers et les serveurs de stockage.

    • Pas de réduction de taille de partition possible.

  • Btrfs (Linux)

    • Système moderne, orienté fonctionnalités avancées (snapshots, checksums, RAID intégré).

    • Encore considéré comme moins stable qu’ext4 dans certains contextes.

    • Bon candidat pour serveurs et systèmes exigeants.

  • APFS (macOS)

    • Système de fichiers par défaut sur macOS récent.

    • Optimisé SSD, snapshots intégrés, chiffrement natif.

    • Non compatible nativement avec Linux ou Windows.

Inodes

Un fichier est décrit par une structure interne, l”inode (index node), qui contient ses métadonnées :

  • type (fichier, répertoire, lien…),

  • taille (en octets),

  • droits d’accès (lecture/écriture/exécution),

  • propriétaire (UID) et groupe (GID),

  • dates : dernier accès (atime), modification du contenu (mtime), changement de l’inode (ctime) et, selon le système de fichiers, création (birth),

  • nombre de liens,

  • pointeurs vers les blocs disque.

Le nom du fichier n’est pas dans l’inode, il est stocké dans le répertoire qui le contient (voir Répertoires). C’est ce qui permet à un même inode d’avoir plusieurs noms (liens physiques).

La commande stat affiche les métadonnées :

$ echo "coucou" > mon_fichier.txt
$ stat mon_fichier.txt
Fichier : mon_fichier.txt
Taille : 7          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17309649    Liens : 1
Accès : (0644/-rw-r--r--)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-08-19 13:43:30.226795166 +0200
Modif. : 2025-08-19 13:43:30.227795178 +0200
Changt : 2025-08-19 13:43:30.227795178 +0200
Créé : 2025-08-19 13:43:30.226795166 +0200
$ file mon_fichier.txt
mon_fichier.txt: ASCII text

Taille logique vs physique

Un fichier non vide occupe en général au moins un bloc disque (souvent 4 Kio). La taille logique (octets de données) peut être bien plus petite que la place réellement allouée.

Dans l’exemple ci-dessus avec stat, le fichier ne contient que 7 octets, mais occupe un bloc entier (8 blocs de 512 octets, soit 4 Kio).

Types de fichiers

On peut les voir avec ls -l : le premier caractère indique le type.

  • - : fichier régulier,

  • d : répertoire (directory),

  • l : lien symbolique (symlink),

  • c : périphérique caractère (terminal, clavier…),

  • b : périphérique bloc (disque dur…),

  • s : socket,

  • p : tube nommé (FIFO).

Exemple :

$ ls -l /dev/null /bin/ls /
-rwxr-xr-x  1 root root 138216 févr.  8  2024 /bin/ls
crw-rw-rw-  1 root root   1, 3 août  18 09:33 /dev/null
...
drwxr-xr-x 169 root root 12288 août  18 10:08 etc
...

Droits des fichiers

Après le type, ls -l affiche les droits (permissions) du fichier. Il y a trois droits :

  • r (read) : droit de lecture,

  • w (write) : droit de modification,

  • x (execute) : droit d’exécution.

Ils sont donnés trois fois : pour le propriétaire, puis pour le groupe, puis pour les autres.

Ainsi :

  • rwxr-xr-x indique que le propriétaire a les droits de lecture, d’écriture et d’exécution, et que le groupe et les autres ont les droits de lecture et d’exécution,

  • rw-r--r-- indique que le propriétaire a les droits de lecture et d’écriture, et que le groupe et les autres ont seulement le droit de lecture.

La commande chmod permet de changer ces droits. La commande chown permet de changer le propriétaire et le groupe. Plus de détails dans le cours de Bash.

Descripteurs de fichiers

Chaque processus possède une table des fichiers ouverts. Le noyau associe un entier (le descripteur) à une structure interne décrivant le fichier.

Trois descripteurs standards sont normalement ouverts au lancement :

  • 0 : entrée standard (stdin),

  • 1 : sortie standard (stdout),

  • 2 : sortie d’erreur (stderr).

Ce mécanisme permet les redirections :

$ ls > out.txt       # redirige stdout vers un fichier
$ ls 2> err.txt      # redirige stderr
$ sort < data.txt    # lit sur stdin depuis un fichier
$ ls | grep '\.c$'   # tube : stdout de ls vers stdin de grep

Répertoires

Un répertoire est un fichier spécial : c’est une table d’association entre des noms de fichiers et des numéros d’inodes (nom → inode).

L’option -i de ls affiche le numéro d’inode de chaque entrée (-a affiche aussi les fichiers cachés, -h affiche les tailles en format lisible) :

$ ls -laih
total 8,0K
18117141 drwxr-xr-x  2 user user 4,0K oct.  21 13:52 .
17301514 drwxr-xr-x 65 user user 4,0K oct.  21 13:52 ..
18117143 -rw-r--r--  1 user user    0 oct.  21 13:52 mon_fichier1.txt
18117145 -rw-r--r--  1 user user    0 oct.  21 13:52 mon_fichier2.txt

. est le répertoire lui-même et .. son parent.

Liens symboliques et physiques

Deux mécanismes Unix permettent d’accéder à un fichier par plusieurs chemins :

  • Lien physique (hard link) : un second nom (une seconde entrée de répertoire) pointant vers le même inode. Le fichier reste valide tant qu’au moins un lien existe.

  • Lien symbolique (soft link / symlink) : un fichier distinct (avec son propre inode) qui contient le chemin d’un autre fichier, comme un raccourci. Si la cible est supprimée, le lien devient cassé.

Commandes :

ln fichier_cible lien     # lien physique
ln -s fichier_cible lien  # lien symbolique

Accès aux fichiers en C

En C sous Unix/Linux, il existe deux façons principales d’accéder aux fichiers :

  1. Avec la bibliothèque standard du C (libc, <stdio.h>) : fonctions de haut niveau, bufferisées en mémoire utilisateur, pratiques, mais le moment réel de l’écriture dépend du tampon (il est vidé par fflush, fclose ou quand il est plein).

  2. Avec les appels système POSIX (<unistd.h>, <fcntl.h>) : fonctions de bas niveau, sans tampon en espace utilisateur (le noyau garde son propre cache), proches du noyau, utilisées en interne par la libc.

Note

La bufferisation stocke temporairement les données dans un tampon pour réduire les appels système et améliorer les performances.

Un équivalent serait d’attendre que le réservoir d’essence d’une voiture soit vide (ou presque vide) pour aller le remplir plutôt que d’aller faire le plein à chaque trajet.

Les valeurs de retour diffèrent selon les fonctions (NULL, EOF, -1…) : elles sont précisées pour chaque fonction ci-dessous. En cas d’erreur, il vaut mieux afficher la cause avec perror.

Libc (E/S bufferisées)

Note

E/S signifie Entrée/Sortie en anglais I/O pour Input/Output

La manipulation via la libc se fait avec la structure FILE (flux, stream) qui encapsule un descripteur de fichier noyau, un tampon, les états EOF/erreur, la position logique, etc.

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3
 4 int main(void) {
 5     FILE *f = fopen("data.txt", "w");
 6     if (!f) {
 7         perror("fopen");
 8         exit(EXIT_FAILURE);
 9     }
10     fprintf(f, "Hello bufferisé !\n");
11     fclose(f);
12     return 0;
13 }

fopen() et fclose() permettent d’ouvrir et fermer un flux FILE.

#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict mode);
int fclose(FILE *stream);

Paramètres :

  • pathname : chemin du fichier.

  • mode :

    • "r" : lecture (le fichier doit exister),

    • "w" : écriture (crée le fichier s’il n’existe pas, l’efface s’il existe),

    • "a" : ajout en fin (crée le fichier s’il n’existe pas),

    • "r+" : lecture + écriture (le fichier doit exister),

    • "b" : modificateur « binaire » à ajouter au mode (ex. : "rb", "wb"), sans effet sous Linux/POSIX mais utile pour la portabilité (Windows).

Retour :

  • fopen : pointeur FILE* ou NULL en cas d’erreur (errno est positionné).

  • fclose : 0 si succès, EOF sinon.

Pour les autres fonctions de la libc, voir les consignes dans les exercices ; en cas de doute, le man peut vous aider.

Appels système (E/S non bufferisées)

Les appels système manipulent directement des descripteurs de fichiers :

 1#include <fcntl.h>
 2#include <unistd.h>
 3#include <stdio.h>
 4#include <stdlib.h>
 5#include <string.h>
 6#include <errno.h>
 7
 8int main(void) {
 9    int fd = open("data.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
10    if (fd == -1) {
11        perror("open");
12        exit(EXIT_FAILURE);
13    }
14
15    const char *msg = "Hello bas niveau !\n";
16    ssize_t n = write(fd, msg, strlen(msg));
17    if (n == -1) {
18        perror("write");
19        close(fd);
20        exit(EXIT_FAILURE);
21    }
22
23    close(fd);
24    return 0;
25}

Note

Les appels système utilisent quelques types définis par POSIX :

  • ssize_t : size_t signé, pour pouvoir renvoyer -1 en cas d’erreur (retour de read et write),

  • off_t : position (décalage) dans un fichier (utilisé par lseek),

  • mode_t : droits d’un fichier (ex. : 0644).

open() ouvre un fichier et renvoie un descripteur de fichier, close() ferme le descripteur de fichier.

#include <fcntl.h>
#include <sys/stat.h>
int open(const char *pathname, int flags, ... /* mode_t mode */);

Paramètres :

  • pathname : chemin du fichier.

  • flags : voir ci-dessous.

  • mode : requis si O_CREAT (droits initiaux, affectés par umask).

Retour : un descripteur de fichier (entier >= 0), ou -1 en cas d’erreur (errno est positionné).

Flags courants :

  • O_RDONLY (lecture seule), O_WRONLY (écriture seule), O_RDWR (lecture+écriture),

  • O_CREAT : créer si inexistant (nécessite mode),

  • O_TRUNC : tronquer à 0 si existant (avec écriture),

  • O_APPEND : ajout en fin,

  • O_EXCL : échec si le fichier existe déjà (à combiner avec O_CREAT),

  • O_CLOEXEC : ferme automatiquement le fd lors d’un execve (sécurité),

  • O_NONBLOCK : E/S non bloquantes (si pertinent),

  • O_DIRECTORY : échouer si pathname n’est pas un répertoire.

Pour les autres fonctions POSIX, voir les consignes dans les exercices ; en cas de doute, le man peut vous aider.

Masque de création (umask)

Les droits r, w, x du propriétaire, du groupe et des autres sont présentés dans Droits des fichiers. En C, on les écrit en octal : 0644 correspond à rw-r--r-- (6 = rw-, 4 = r--).

La fonction umask définit un masque par défaut qui supprime certains droits à la création.

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void) {
    umask(0022); // retire le droit d'écriture au groupe et aux autres à la création
    int fd = open("fichier.txt", O_CREAT | O_WRONLY, 0666);
    // droits effectifs : 0666 & ~0022 = 0644
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    close(fd);
    return 0;
}

Verrouillage de fichiers

Quand plusieurs processus écrivent dans le même fichier, il faut éviter les conflits. Deux mécanismes existent :

  • flock(fd, LOCK_EX) / flock(fd, LOCK_SH) : verrouillage simple, exclusif (un seul processus) ou partagé (plusieurs lecteurs), flock(fd, LOCK_UN) libère le verrou,

  • fcntl(fd, F_SETLK, &fl) : verrouillage plus fin (plages d’octets, décrites par une struct flock).

Ces verrous sont consultatifs (advisory) : ils n’empêchent pas un processus d’écrire dans le fichier. Ils n’ont d’effet que si tous les processus qui accèdent au fichier demandent le verrou avant d’y accéder.

Exemple simple avec flock :

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <unistd.h>

int main(void) {
    int fd = open("data.txt", O_WRONLY | O_APPEND | O_CREAT, 0644);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    if (flock(fd, LOCK_EX) == -1) { // verrou exclusif (attend s'il est déjà pris)
        perror("flock");
        close(fd);
        exit(EXIT_FAILURE);
    }
    if (write(fd, "Hello\n", 6) == -1) {
        perror("write");
    }
    flock(fd, LOCK_UN); // libère le verrou
    close(fd);
    return 0;
}

Fichiers spéciaux

Sous Unix, certains fichiers n’ont pas de données réelles mais sont des interfaces système :

  • /dev/null : tout ce qui est écrit est jeté, lecture = EOF,

  • /dev/urandom : générateur pseudo-aléatoire,

  • /proc : expose des infos sur les processus et le noyau,

  • /sys : interface avec les périphériques.

Exemple :

$ cat /dev/null
$ head -c 16 /dev/urandom | hexdump -C
$ cat /proc/cpuinfo

Exemple long de l’utilisation de stat

Ci-dessous, on crée un fichier vide avec touch, puis on affiche les métadonnées avec stat. Ensuite, on ajoute des données dans le fichier, puis on constate que la taille et la date de modification changent ; on modifie ensuite les permissions avec chmod. Enfin, on crée un lien symbolique avec ln -s de salut vers coucou. C’est un raccourci, il pointe donc vers un autre fichier par son nom. Puis un lien physique de bonjour vers coucou. Quand coucou est supprimé, salut devient un lien mort. Le lien physique ajoute un nouveau nom à un même inode qui reste fonctionnel tant qu’un nom le référence.

$ touch coucou

$ stat coucou
Fichier : coucou
Taille : 0          Blocs : 0          Blocs d'E/S : 4096   fichier vide
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 1
Accès : (0644/-rw-r--r--)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:10.187566033 +0200
Changt : 2025-07-21 17:36:10.187566033 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ echo "salut" >> coucou

$ stat coucou
Fichier : coucou
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 1
Accès : (0644/-rw-r--r--)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:36:26.285805213 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ chmod +x coucou

$ stat coucou
Fichier : coucou
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 1
Accès : (0755/-rwxr-xr-x)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:36:40.146011146 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ ln -s coucou salut

$ stat coucou
Fichier : coucou
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 1
Accès : (0755/-rwxr-xr-x)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:36:40.146011146 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ stat salut
Fichier : salut -> coucou
Taille : 6          Blocs : 0          Blocs d'E/S : 4096   lien symbolique
Périphérique : fc01h/64513d Inœud : 17387173    Liens : 1
Accès : (0777/lrwxrwxrwx)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:52.110188907 +0200
Modif. : 2025-07-21 17:36:52.109188892 +0200
Changt : 2025-07-21 17:36:52.109188892 +0200
Créé : 2025-07-21 17:36:52.109188892 +0200

$ ln coucou bonjour

$ stat coucou
Fichier : coucou
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 2
Accès : (0755/-rwxr-xr-x)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:37:11.421475831 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ stat salut
Fichier : salut -> coucou
Taille : 6          Blocs : 0          Blocs d'E/S : 4096   lien symbolique
Périphérique : fc01h/64513d Inœud : 17387173    Liens : 1
Accès : (0777/lrwxrwxrwx)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:52.110188907 +0200
Modif. : 2025-07-21 17:36:52.109188892 +0200
Changt : 2025-07-21 17:36:52.109188892 +0200
Créé : 2025-07-21 17:36:52.109188892 +0200

$ stat bonjour
Fichier : bonjour
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 2
Accès : (0755/-rwxr-xr-x)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:37:11.421475831 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ rm coucou

$ stat coucou
stat: impossible d'exécuter statx 'coucou': Aucun fichier ou dossier de ce nom

$ stat bonjour
Fichier : bonjour
Taille : 6          Blocs : 8          Blocs d'E/S : 4096   fichier
Périphérique : fc01h/64513d Inœud : 17386826    Liens : 1
Accès : (0755/-rwxr-xr-x)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:10.187566033 +0200
Modif. : 2025-07-21 17:36:26.285805213 +0200
Changt : 2025-07-21 17:37:29.461743875 +0200
Créé : 2025-07-21 17:36:10.186566018 +0200

$ stat salut
Fichier : salut -> coucou
Taille : 6          Blocs : 0          Blocs d'E/S : 4096   lien symbolique
Périphérique : fc01h/64513d Inœud : 17387173    Liens : 1
Accès : (0777/lrwxrwxrwx)  UID : (1000/user)   GID : (1000/user)
Accès : 2025-07-21 17:36:52.110188907 +0200
Modif. : 2025-07-21 17:36:52.109188892 +0200
Changt : 2025-07-21 17:36:52.109188892 +0200
Créé : 2025-07-21 17:36:52.109188892 +0200

$ cat salut
cat: salut: Aucun fichier ou dossier de ce nom