Partie 2 - Fichiers¶
Support présentationIntroduction¶
Dans les systèmes Unix/Linux, un fichier est une simple séquence d’octets enregistré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…),
pipes (tubes),
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
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 des données). 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…).
Chaîne d’accès¶
Lorsque votre programme veut lire un fichier, il appelle open.
Le noyau associe alors le fichier à un descripteur de fichier (un int, voir Descripteurs de fichiers).
Le programme utilise ensuite ce descripteur avec read, write, close…
Chaque appel traverse plusieurs couches avant d’atteindre le disque :
Le système de fichiers virtuel (VFS) est une couche commune du noyau : grâce à elle, on utilise les mêmes appels (open, read, write…) quel que soit le système de fichiers (ext4, FAT32…) ou le type d’objet (fichier, périphérique, pipe…).
Exemples de systèmes de fichiers¶
Il existe de nombreux systèmes de fichiers, chacun ayant ses spécificités en termes de performances, fiabilité, compatibilité ou fonctionnalités.
Système |
Système d’exploitation |
Journalisé |
Usage typique |
|---|---|---|---|
ext4 |
Linux |
oui |
système par défaut de la plupart des distributions Linux |
NTFS |
Windows |
oui |
système par défaut de Windows |
FAT32 / exFAT |
tous (Windows, macOS, Linux) |
non |
clés USB, cartes SD, disques externes |
APFS |
macOS |
oui |
système par défaut de macOS (optimisé pour les SSD) |
La journalisation consiste à noter les modifications dans un journal avant de les appliquer : après une coupure brutale, le système de fichiers peut retrouver un état cohérent.
Pour aller plus loin : détails et autres systèmes de fichiers
ext4 : gère de très gros fichiers et volumes (fichier jusqu’à 16 Tio, partition jusqu’à 1 Eio).
NTFS : prend en charge les droits d’accès avancés, la compression, le chiffrement ; lecture/écriture sous Linux (pilote
ntfs3du noyau), écriture limitée sous macOS.FAT32 : très compatible (clé USB, cartes SD, BIOS), mais limité à 4 Gio par fichier et 2 Tio par partition ; sans journalisation, il est moins fiable en cas de coupure brutale.
exFAT : successeur de FAT32 (optimisé pour la mémoire flash), sans limite de 4 Gio par fichier ; pris en charge par Windows, macOS et Linux.
APFS : snapshots intégrés, chiffrement natif ; non compatible nativement avec Linux ou Windows.
XFS (Linux) : système journalisé à hautes performances, optimisé pour les très gros fichiers et les serveurs de stockage ; la taille d’une partition ne peut pas être réduite.
Btrfs (Linux) : système moderne orienté fonctionnalités avancées (snapshots, sommes de contrôle, RAID intégré), encore considéré comme moins stable qu’ext4 dans certains contextes.
Inodes¶
Un fichier est décrit par une structure interne, l”inode (index node, « inœud » en français), 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 disque entier de 4 Kio (stat compte en unités de 512 octets : 8 × 512 = 4096).
Types de fichiers¶
Le type d’un fichier est visible avec ls -l : c’est le premier caractère de la ligne.
-: 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: pipe 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-xindique 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 descripteurs de fichiers, gérée par le noyau. Le noyau associe un entier (le descripteur) à une structure interne décrivant le fichier ouvert : la position courante dans le fichier (voir Position courante : lseek), le mode d’ouverture (lecture, écriture…) et l’inode du fichier. Le programme ne manipule que l’entier.
Trois descripteurs standards sont normalement ouverts au lancement :
0: entrée standard (stdin),1: sortie standard (stdout),2: sortie d’erreur (stderr).
<unistd.h> définit les constantes STDIN_FILENO, STDOUT_FILENO et STDERR_FILENO pour ces trois valeurs.
open renvoie toujours le plus petit numéro libre : le premier fichier ouvert par un programme a donc souvent le descripteur 3.
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$' # pipe : 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
L’exercice p2e2 permet d’observer ces deux types de liens avec ls -li et stat.
Accès aux fichiers en C¶
En C sous Unix/Linux, il existe deux façons principales d’accéder aux fichiers :
Avec la bibliothèque standard du C (libc,
<stdio.h>) : fonctions de haut niveau et pratiques, bufferisées en mémoire utilisateur.Avec les appels système POSIX (
<unistd.h>,<fcntl.h>) : fonctions de bas niveau, sans buffer (tampon en français) en espace utilisateur (le noyau garde son propre cache), proches du noyau, utilisées en interne par la libc.
Les valeurs de retour diffèrent selon les fonctions (NULL, EOF, -1…) : elles sont précisées pour chaque fonction ci-dessous.
Il n’y a pas d’exception en C : il faut tester la valeur de retour de chaque appel.
En cas d’erreur, il vaut mieux afficher la cause avec perror.
Bufferisation¶
La bufferisation stocke temporairement les données dans un buffer pour réduire le nombre d’appels système et améliorer les performances. C’est comme attendre d’avoir un sac plein avant de sortir la poubelle, plutôt que d’y aller pour chaque déchet.
Avec la libc, fprintf copie les données dans un buffer situé dans la mémoire du programme.
Elles ne sont transmises au noyau (par un write) que lorsque le buffer est vidé :
appel à fflush ou à
fclose,fin normale du programme (
returndumainou exit),buffer plein,
fin de ligne (
\n) si le flux est un terminal.
Avec un appel système, write transmet les données au noyau immédiatement.
Si le programme plante (segfault, abort…), le contenu du buffer de la libc est perdu : un message écrit avec fprintf juste avant le plantage peut ne jamais arriver dans le fichier.
Ce qui a déjà été transmis au noyau n’est pas perdu.
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 buffer, les états EOF/erreur, la position logique, etc.
1 #define _POSIX_C_SOURCE 200809L // déclarations POSIX (voir la note plus bas)
2 #include <stdio.h>
3 #include <stdlib.h>
4
5 int main(void) {
6 FILE *f = fopen("data.txt", "w");
7 if (!f) {
8 perror("fopen");
9 exit(EXIT_FAILURE);
10 }
11 fprintf(f, "Hello bufferisé !\n");
12 fclose(f);
13 return 0;
14 }
fopen et fclose (<stdio.h>) permettent d’ouvrir et de fermer un flux FILE :
FILE *fopen(const char *pathname, const char *mode);
int fclose(FILE *stream);
pathname: chemin du fichiermode: mode d’ouverture"r": lecture (le fichier doit exister)"w": écriture (crée le fichier s’il n’existe pas, le vide en le tronquant à 0 octet 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)
stream: flux à fermer
fopen retourne un pointeur FILE *, ou NULL en cas d’erreur (errno indique la cause).
fclose retourne 0, ou EOF en cas d’erreur (errno indique la cause).
Les autres fonctions de la libc et leurs équivalents POSIX (tableau Correspondance libc / POSIX) sont décrites dans les consignes des exercices et dans le man.
Appels système (E/S non bufferisées)¶
Les appels système manipulent directement des descripteurs de fichiers :
1#define _POSIX_C_SOURCE 200809L
2#include <fcntl.h>
3#include <unistd.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.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_tsigné, pour pouvoir renvoyer-1en cas d’erreur (retour dereadetwrite),off_t: position (décalage) dans un fichier (utilisé parlseek),mode_t: droits d’un fichier (ex. :0644).
Note
Avec -std=c2x, le compilateur suit strictement le C standard : seules les déclarations du C standard sont garanties.
Les en-têtes POSIX (<unistd.h>, <fcntl.h>…) déclarent les appels système les plus anciens (open, read, write…), mais pas forcément les autres fonctions et constantes POSIX (getline, symlink, ftruncate, nanosleep, O_CLOEXEC, O_DIRECTORY…).
Les utiliser provoque alors une erreur de compilation (implicit declaration of function pour une fonction, undeclared pour une constante).
La macro _POSIX_C_SOURCE demande aux en-têtes (<stdio.h>, <unistd.h>, <fcntl.h>…) de déclarer toutes les fonctions et constantes de la version de POSIX indiquée.
Les en-têtes la lisent au moment où ils sont inclus : on la définit donc en première ligne du fichier, avant tout #include :
#define _POSIX_C_SOURCE 200809L // utilise la version de 2008 de POSIX
#include <stdio.h>
// ...
C’est une bonne pratique : mettez cette ligne en tête de chaque programme C, même s’il compile sans elle (c’est ce que font les exemples complets du cours et les corrections des exercices).
Ouvrir et fermer : open et close¶
open (<fcntl.h> et <sys/stat.h>) ouvre un fichier :
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: chemin du fichierflags: mode d’ouverture et options, les plus courants étant :O_RDONLY(lecture seule),O_WRONLY(écriture seule),O_RDWR(lecture+écriture)O_CREAT: créer le fichier s’il n’existe pas (nécessitemode)O_TRUNC: tronquer à 0 octet si le fichier existe (avec écriture)O_APPEND: écrire en fin de fichierO_EXCL: échouer si le fichier existe déjà (à combiner avecO_CREAT)
mode: requis siO_CREAT(droits initiaux, en octal, affectés par le masqueumask, voir Droits à la création : notation octale et umask ci-dessous)
Retourne un descripteur de fichier (entier >= 0, le plus petit numéro libre), ou -1 en cas d’erreur (errno indique la cause).
Ce ne sont pas deux fonctions (en C, deux fonctions ne peuvent pas porter le même nom) : open est une seule fonction à nombre variable d’arguments (..., comme printf), que le man présente sous ces deux formes. Le troisième argument n’est lu que si flags contient O_CREAT.
Les flags se combinent avec l’opérateur | (OU bit à bit) : chaque flag correspond à un bit d’un entier, et O_WRONLY | O_CREAT | O_TRUNC active les trois bits à la fois.
Pour aller plus loin : autres flags
O_CLOEXEC: fermer automatiquement le descripteur lors d’un execve (sécurité, voir partie Processus),O_NONBLOCK: rendre les E/S non bloquantes (pipes, sockets, terminaux ; sans effet sur les fichiers réguliers),O_DIRECTORY: échouer sipathnamen’est pas un répertoire.
close (<unistd.h>) ferme le descripteur de fichier :
int close(int fd);
fd: descripteur à fermer
Retourne 0, ou -1 en cas d’erreur (errno indique la cause).
Écrire : write¶
L’écriture se fait avec write (<unistd.h>) :
ssize_t write(int fd, const void *buf, size_t count);
fd: descripteur du fichier dans lequel écrirebuf: adresse des octets à écrirecount: nombre d’octets à écrire
Retourne le nombre d’octets réellement écrits, ou -1 en cas d’erreur (errno indique la cause).
Le nombre d’octets écrits peut être inférieur à count (écriture partielle, par exemple si le disque est plein) : un programme robuste répète l’appel jusqu’à ce que tout soit écrit.
La fonction write_full ci-dessous fait exactement cela (elle utilise errno, déclaré dans <errno.h>).
Copiez-la dans vos programmes quand vous en avez besoin :
1/**
2 * Écrit exactement `count` octets de `buf` dans le descripteur `fd`,
3 * en répétant les appels à write() si nécessaire (écritures partielles,
4 * interruptions par un signal).
5 * Retourne `count`, ou -1 en cas d'erreur.
6 */
7ssize_t write_full(int fd, const void *buf, size_t count) {
8 const char *octets = buf;
9 size_t total_ecrit = 0;
10 while (total_ecrit < count) {
11 ssize_t nb_ecrits = write(fd, octets + total_ecrit, count - total_ecrit);
12 if (nb_ecrits > 0) {
13 total_ecrit += (size_t)nb_ecrits;
14 } else if (nb_ecrits == -1 && errno == EINTR) {
15 continue; // interrompu par un signal avant d'écrire : on relance
16 } else {
17 // erreur, ou write a renvoyé 0 : on s'arrête (sinon la boucle
18 // pourrait tourner indéfiniment)
19 return -1;
20 }
21 }
22 return (ssize_t)total_ecrit;
23}
Pour aller plus loin : EINTR
Un appel bloquant comme write ou read peut aussi être interrompu par un signal (voir partie Processus) avant d’avoir transféré quoi que ce soit : il renvoie alors -1 avec errno == EINTR.
Ce n’est pas une vraie erreur : il suffit de relancer l’appel (c’est ce que fait write_full).
Lire : read¶
La lecture se fait avec read (<unistd.h>) :
ssize_t read(int fd, void *buf, size_t count);
fd: descripteur du fichier à lirebuf: buffer qui reçoit les octets luscount: nombre maximal d’octets à lire (en général la taille debuf)
Retourne le nombre d’octets lus (> 0), 0 en fin de fichier (plus rien à lire), ou -1 en cas d’erreur (errno indique la cause).
Le nombre d’octets lus peut être inférieur à count : fin du fichier proche, pipe, terminal…
On ne sait pas à l’avance combien d’octets contient le fichier : on lit donc par morceaux, dans un buffer de taille fixe réutilisé à chaque tour, jusqu’à ce que read renvoie 0 :
1char buf[4096];
2ssize_t nb_lus;
3while ((nb_lus = read(fd, buf, sizeof buf)) > 0) {
4 // utiliser les nb_lus premiers octets de buf
5}
6if (nb_lus == -1) {
7 perror("read");
8}
Avertissement
read copie des octets bruts : il n’ajoute pas de '\0' à la fin.
buf n’est donc pas une chaîne de caractères : printf("%s", buf) ou strlen(buf) liraient au-delà des octets lus (voir les chaînes de caractères).
Utilisez le nombre d’octets lus (nb_lus), par exemple avec write(STDOUT_FILENO, buf, (size_t)nb_lus).
Position courante : lseek¶
Chaque fichier ouvert a une position courante (offset), gardée par le noyau : c’est le numéro du prochain octet qui sera lu ou écrit.
Elle vaut 0 après open, et chaque read ou write l’avance du nombre d’octets lus ou écrits : deux read successifs lisent donc la suite du fichier, sans relire le début.
lseek (<unistd.h>) permet de déplacer cette position :
off_t lseek(int fd, off_t offset, int whence);
fd: descripteur du fichier ouvertoffset: décalage en octets (peut être négatif)whence: point de départ du décalageSEEK_SET: la position devientoffset(compté depuis le début du fichier)SEEK_CUR: la position avance deoffsetoctets (ou recule sioffsetest négatif)SEEK_END: la position devient la taille du fichier +offset
Retourne la nouvelle position (en octets depuis le début du fichier), ou -1 en cas d’erreur (errno indique la cause).
Avec O_APPEND, la position est replacée à la fin du fichier avant chaque write : les écritures s’ajoutent toujours à la fin.
Correspondance libc / POSIX¶
La libc utilise les appels système en interne : chaque fonction de la libc a un équivalent de plus bas niveau.
Opération |
Libc ( |
POSIX (descripteur |
|---|---|---|
Ouvrir |
|
|
Fermer |
|
|
Lire des octets |
|
|
Lire une ligne |
|
pas d’équivalent (lire avec |
Écrire des octets |
|
|
Écrire du texte formaté |
|
|
Déplacer la position |
|
|
Vider le buffer |
|
pas de buffer côté programme |
Droits à la création : notation octale et umask¶
Notation octale¶
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, avec un chiffre par catégorie (propriétaire, groupe, autres).
Le 0 initial indique au compilateur que le nombre est écrit en octal : 0644 ≠ 644 (644 est un nombre décimal, qui donnerait des droits absurdes).
Chaque chiffre est la somme des droits accordés : r = 4, w = 2, x = 1.
Ainsi 0644 correspond à rw-r--r-- (6 = 4 + 2 = rw-, 4 = r--) et 0755 à rwxr-xr-x (7 = 4 + 2 + 1 = rwx, 5 = 4 + 1 = r-x).
Masque de création (umask)¶
Les droits donnés à open (ou à mkdir) ne sont pas forcément ceux qu’aura le fichier : le masque de création (umask) du processus retire certains droits à la création.
Pour connaître le masque de votre terminal, tapez la commande umask (souvent 0022, parfois 0002).
Le calcul se fait chiffre par chiffre : on garde les droits demandés, sauf ceux présents dans le masque.
propriétaire |
groupe |
autres |
|
|---|---|---|---|
droits demandés : |
|
|
|
masque : |
|
|
|
droits obtenus : |
|
|
|
Ce n’est pas une soustraction : on retire des droits, et un droit absent reste absent.
Par exemple, avec 0755 demandé et le masque 0022, on obtient 0755 (le groupe et les autres n’avaient pas le droit w).
Les droits observés sur un fichier créé par vos programmes (stat, ls -l) dépendent donc du masque de la machine.
Un programme peut changer son propre masque avec la fonction umask (<sys/stat.h>), à ne pas confondre avec la commande du même nom :
mode_t umask(mode_t mask);
mask: nouveau masque, en octal (par exemple0022)
Retourne l’ancien masque (cet appel n’échoue jamais).
Le nouveau masque s’applique aux fichiers créés ensuite par le processus : on l’appelle donc avant open, par exemple umask(0022);.
Pour aller plus loin : le calcul bit à bit
Chaque droit correspond à un bit : le noyau calcule droits_demandés & ~masque (ET avec le NON du masque).
Par exemple : 0666 & ~0022 = 0644.
Si le fichier existe déjà, open ne modifie pas ses droits.
Pour aller plus loin : verrouillage de fichiers
Quand plusieurs processus écrivent dans le même fichier, il faut éviter les conflits. Deux mécanismes existent :
flock: verrouillage simple du fichier entier (voir ci-dessous),fcntl(fd, F_SETLK, &fl)(POSIX) : verrouillage plus fin (plages d’octets, décrites par unestruct 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.
flock est déclarée dans <sys/file.h> :
int flock(int fd, int operation);
fd: descripteur du fichier à verrouilleroperation: opération demandéeLOCK_EX: verrou exclusif (un seul processus)LOCK_SH: verrou partagé (plusieurs lecteurs)LOCK_UN: libère le verrou
Retourne 0, ou -1 en cas d’erreur (errno indique la cause).
flock vient de BSD : elle est disponible sous Linux mais ne fait pas partie de POSIX (non portable).
Exemple simple avec flock :
#define _POSIX_C_SOURCE 200809L
#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;
}
Le problème des accès concurrents (et les verrous qui le résolvent) est étudié en détail dans la partie Threads.
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 informations 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