web-dev-qa-db-fra.com

Que sont les répertoires si tout sous Linux est un fichier?

Très souvent, les débutants entendent une phrase "Tout est un fichier sous Linux/Unix". Cependant, quels sont les répertoires alors? Comment sont-ils différents des fichiers?

15

Remarque: à l’origine, cela était écrit pour étayer ma réponse Pourquoi le répertoire actuel de la commande lsest-il identifié comme étant lié à lui-même? , mais j’ai pensé que c’était un sujet qui méritait de rester propre, et donc ce Q & A.

Comprendre les systèmes de fichiers Unix/Linux: tout est un inode

Essentiellement, un répertoire est juste un fichier spécial, contenant la liste des entrées et leur ID.

Avant de commencer la discussion, il est important de faire la distinction entre quelques termes et de comprendre ce que les répertoires et les fichiers représentent réellement. Vous avez peut-être entendu l'expression "Tout est un fichier" pour Unix/Linux. Ce que les utilisateurs comprennent souvent en tant que fichier est le suivant: /etc/passwd - Un objet avec un chemin et un nom. En réalité, un nom (que ce soit un répertoire, un fichier ou autre) est simplement une chaîne de texte - une propriété de l'objet réel. Cet objet est appelé inode ou numéro I et stocké sur le disque dans la table inode. Les programmes ouverts ont aussi des tables d’inodes, mais ce n’est pas notre préoccupation pour le moment.

La notion de répertoire d'Unix est comme Ken Thompson l'a dit dans une interview de 1989 :

... Et puis, certains de ces fichiers étaient des répertoires contenant uniquement le nom et le numéro I.

On peut faire une observation intéressante à partir du discours de de Dennis Ritchie en 1972 :

"... le répertoire n’est en réalité qu’un fichier, mais son contenu est contrôlé par le système et il contient des noms d’autres fichiers. (Un répertoire est parfois appelé catalogue dans d’autres systèmes.)"

... mais il n'y a aucune mention d'inodes nulle part dans la conversation. Cependant, le manuel 1971 sur format of directories indique:

Le fait qu'un fichier soit un répertoire est indiqué par un bit dans l'indicateur Word de son entrée i-node.

Les entrées du répertoire ont une longueur de 10 octets. Le premier mot est le nœud i du fichier représenté par l'entrée, s'il est différent de zéro; si zéro, l'entrée est vide.

Donc, il est là depuis le début.

Le couplage répertoire/inode est également expliqué dans Comment les structures de répertoire sont-elles stockées dans le système de fichiers UNIX? . un répertoire lui-même est une structure de données, plus précisément: une liste d'objets (fichiers et numéros d'inode) pointant vers des listes relatives à ces objets (autorisations, type, propriétaire, taille, etc.). Ainsi, chaque répertoire contient son propre numéro d'inode, puis les noms de fichiers et leurs numéros d'inode. Le plus célèbre est l’inode n ° 2, qui correspond au répertoire / . (Notez que /dev et /run sont des systèmes de fichiers virtuels. Par conséquent, comme ils sont des dossiers racine pour leur système de fichiers, , ils ont également inode 2 ; c’est-à-dire qu’un inode est unique sur son propre système de fichiers, mais avec plusieurs systèmes de fichiers attachés, vous avez des inodes non uniques). le diagramme emprunté à la question liée l'explique probablement plus succinctement:

Directory-iNode-Block

Toutes les informations stockées dans l’inode sont accessibles via les appels système stat(), conformément à Linux man 7 inode :

Chaque fichier a un inode contenant des métadonnées sur le fichier. Une application peut récupérer ces métadonnées à l'aide de stat (2) (ou des appels associés), qui renvoie une structure stat, ou statx (2), qui renvoie une structure statx.

Est-il possible d'accéder à un fichier uniquement en connaissant son numéro d'inode ( ref1 , ref2 )? Sur certaines implémentations Unix, cela est possible, mais il ignore les autorisations et les contrôles d'accès. Ainsi, sous Linux, il n'est pas implémenté et vous devez parcourir l'arborescence du système de fichiers (via find <DIR> -inum 1234 par exemple) pour obtenir un nom de fichier et son inode correspondant.

Au niveau du code source, il est défini dans la source du noyau Linux et est également adopté par de nombreux systèmes de fichiers fonctionnant sur les systèmes d'exploitation Unix/Linux, y compris les systèmes de fichiers ext3 et ext4 (Ubuntu par défaut). Ce qui est intéressant: avec des données qui ne sont que des blocs d’informations, Linux a réellement la fonction inode_init_always qui peut déterminer si un inode est un tube (inode->i_pipe). Oui, les sockets et les pipes sont techniquement aussi des fichiers - des fichiers anonymes, qui peuvent ne pas avoir de nom de fichier sur le disque. Les FIFO et les sockets Unix-Domain ont des noms de fichiers sur le système de fichiers.

Les données elles-mêmes peuvent être uniques, mais les numéros d'inode ne sont pas uniques. Si nous avons un lien dur avec foo appelé foobar, cela indiquera également l'inode 123. Cet inode lui-même contient des informations sur les blocs d'espace disque réellement occupés par cet inode. Et techniquement, vous pouvez avoir . lié au répertoire filename. Eh bien, presque: vous ne pouvez pas créer vous-même de liens durs vers des répertoires sous Linux , mais les systèmes de fichiers peuvent autoriser les liens durs vers des répertoires de manière très disciplinée, ce qui impose de n'avoir que . et ... comme des liens durs.

Arbre de répertoire

Les systèmes de fichiers implémentent une arborescence de répertoires en tant qu'une des infrastructures de données. En particulier,

  • ext3 et ext4 utilisent HTree
  • xfs utilise B + Tree
  • zfs utilise l'arbre de hachage

Le point clé ici est que les répertoires eux-mêmes sont des nœuds dans une arborescence et que les sous-répertoires sont des nœuds enfants, chaque enfant ayant un lien vers le nœud parent. Ainsi, pour un lien de répertoire, le nombre d'inodes est au minimum de 2 pour un répertoire vide (lien vers le parent .. et lien vers le propre .), et chaque sous-répertoire supplémentaire est un lien/noeud supplémentaire:

# new directory has link count of 2
$ stat --format=%h .
2
# Adding subdirectories increases link count
$ mkdir subdir1
$ stat --format=%h .
3
$ mkdir subdir2
$ stat --format=%h .
4
# Count of links for root
$ stat --format=%h /
25
# Count of subdirectories, minus .
$ find / -maxdepth 1 -type d | wc -l
24

Le diagramme trouvé sur la page du cours de Ian D. Allen montre un diagramme très clair très simplifié:

WRONG - names on things      RIGHT - names above things
=======================      ==========================

    R O O T            --->         [etc,bin,home]   <-- ROOT directory
   /   |   \                         /    |      \
etc   bin   home       --->  [passwd]  [ls,rm]  [abcd0001]
 |   /   \    \                 |      /    \       |
 |  ls   rm  abcd0001  --->     |  <data>  <data>  [.bashrc]
 |               |              |                   |
passwd       .bashrc   --->  <data>                <data>

La seule chose qui est incorrecte dans le diagramme de droite est que les fichiers ne sont pas techniquement considérés comme étant dans l'arborescence de répertoires elle-même: l'ajout d'un fichier n'a aucun effet sur le nombre de liens:

$ mkdir subdir2
$ stat --format=%h .
4
# Adding files doesn't make difference
$ cp /etc/passwd passwd.copy
$ stat --format=%h .
4

Accéder aux répertoires comme s'ils étaient des fichiers

Pour citer Linus Torvalds :

Le point entier avec "tout est un fichier" n’est pas que vous ayez un nom de fichier aléatoire (en fait, les sockets et les pipes indiquent que "fichier" et "nom de fichier" n’ont rien à voir), mais le fait que vous puissiez utiliser des fichiers communs. des outils pour opérer sur différentes choses.

Considérant qu’un répertoire n’est qu’un cas particulier de fichier, il faut naturellement des API qui nous permettent d’ouvrir / lire / écrivez / fermez-les de la même manière que les fichiers ordinaires.

C'est là que la bibliothèque dirent.h C entre en place, qui définit la structure direntname__, que vous pouvez trouver dans man 3 readdir :

   struct dirent {
       ino_t          d_ino;       /* Inode number */
       off_t          d_off;       /* Not an offset; see below */
       unsigned short d_reclen;    /* Length of this record */
       unsigned char  d_type;      /* Type of file; not supported
                                      by all filesystem types */
       char           d_name[256]; /* Null-terminated filename */
   };

Ainsi, dans votre code C, vous devez définir struct dirent *entry_p et, lorsque nous ouvrirons un répertoire avec opendir() et que nous commencerons à le lire avec readdir(), nous stockons chaque élément dans cette structure entry_p. Bien entendu, chaque élément contiendra les champs définis dans le modèle pour direntci-dessus.

L’exemple pratique de ce fonctionnement se trouve dans ma réponse à Comment répertorier les fichiers et leurs numéros d’inodes dans le répertoire de travail actuel .

Notez que le manuel POSIX sur fdopen indique que "les entrées de répertoire pour point et point-point sont facultatives" et les états readdir manualstruct dirent est requis uniquement d'avoir les champs d_name et d_ino.

Note sur "l'écriture" dans les répertoires: l'écriture dans un répertoire modifie sa "liste" d'entrées. Par conséquent, la création ou la suppression d'un fichier est directement associée à les autorisations d'écriture de répertoire , et l'ajout/la suppression de fichiers constitue l'opération d'écriture sur ce répertoire.

20