Fichiers et Répertoires

Cours système d'exploitation by Guillaume Chanel, Jean-Luc Falcone and University of Geneva is licensed under CC BY-NC-SA 4.0

Inodes

Schéma global

Schema des inodes

Inode

  • Un inode est une structure de donnée contenant des information sur un "fichier" (fichier, directory, socket, device, pipe, etc.).
  • Ils ne contiennent pas le nom du fichier.
  • Ils contiennent généralement (POSIX) des informations sur:
    • Numéro d'inode
    • Périphérique contenant le fichier (device ID)
    • Propriétaire et groupe
    • Permissions
    • Taille du fichier
    • Temps d'accès et de modification
    • Nombre de liens pointant vers l'inode
    • Pointeurs vers les données

Temps de l'inode

Un inode contient trois temps différents:

atimedate du dernier accès à l'inode (ou aux données)
mtimedate de la dernière modification des données
ctimedate de la dernière modifications des méta-données
crtimedate de création du fichier (non POSIX)
Information

Pour gagner en performance, il est possible de désactiver la mise à jour de atime lorsque la partition est montée.

Inspecter un inode en shell (stat)

La commande stat permet d'afficher des données sur un inode.

$ touch /tmp/myfile  # met à jour les dates (crée un fichier si inexistant)
    $ stat /tmp/myfile
      File: /tmp/myfile
      Size: 0               Blocks: 0          IO Block: 4096   regular empty file
    Device: 2fh/47d Inode: 422766      Links: 1
    Access: (0644/-rw-r--r--)  Uid: ( 1000/  chanel)   Gid: ( 1000/  chanel)
    Access: 2019-07-19 16:56:35.540113838 +0200
    Modify: 2019-07-19 16:56:35.540113838 +0200
    Change: 2019-07-19 16:56:35.540113838 +0200
     Birth: -

Lien dur (2)

  • Les entrées des répertoires sont des liens pointant vers des inodes.
  • On peut créer plusieurs liens vers un fichier
  • Un fichier est "effacé" lorsqu'il n'y a plus de liens pointant sur son inode (sauf si un processus maintient le fichier ouvert, cf unlink).

On peut créer un lien dur avec la commande ln:

$ ln /tmp/myfile /tmp/newlink
    $ stat /tmp/myfile
      File: /tmp/myfile
      Size: 0               Blocks: 0          IO Block: 4096   regular empty file
    Device: 2fh/47d Inode: 422766      Links: 2
    Access: (0644/-rw-r--r--)  Uid: ( 1000/  chanel)   Gid: ( 1000/  chanel)
    Access: 2019-07-19 16:56:35.540113838 +0200
    Modify: 2019-07-19 16:56:35.540113838 +0200
    Change: 2019-07-19 16:56:35.540113838 +0200
     Birth: -

Lien dur (question)

Quelle sera la date de modification de /tmp/myfile si je modifie le contenu du lien dur /tmp/newlink ?

Lien dur (3)

Limitations

Un répertoire ne peux posséder qu'un seul lien dur, afin d'éviter les cycles (i.e. la structure du système de fichier doit rester acyclique).

Tous les liens durs pointant sur un inode doivent se trouver sur le même système de fichier que cet inode.

Utilité

En général, il est peu utile d'avoir plusieurs liens durs vers un fichier (préférer les liens symboliques, voir slides suivantes).

Permet de faire un snapshot, par exemple pour archiver un répertoire: http://www.mikerubel.org/computers/rsync_snapshots/ .

Lien symbolique (2)

  • Un lien symbolique possède son propre inode qui pointe vers un nom
  • Contrairement aux liens durs, on peut créer des symlinks:
    • Vers un répertoire
    • Vers un fichier/répertoire sur un autre système de fichier.

On peut créer un lien dur avec la commande ln en utilisant l'option -s:

$ ln -s /tmp/myfile /tmp/newlink  # (le lien dure précédent à été supprimé)
    $ stat /tmp/myfile  # comme indiqué ci-dessous on a bien à faire à un autre inode
      File: /tmp/newlink -> /tmp/myfile
        Size: 11              Blocks: 0          IO Block: 4096   symbolic link
      Device: 2fh/47d Inode: 563876      Links: 1
      Access: (0777/lrwxrwxrwx)  Uid: ( 1000/  chanel)   Gid: ( 1000/  chanel)
      Access: 2019-07-19 18:16:26.343945684 +0200
      Modify: 2019-07-19 18:16:13.240259297 +0200
      Change: 2019-07-19 18:16:13.240259297 +0200
       Birth: -
    

Objets trouvés (Lost+Found)

  • A la suite d'une erreur du système de fichier (p.e. suite à une mise hors-tension brutale), un inode peut se retrouver sans lien.
  • Lors d'un contrôle de fichier (fsck), il sera copié dans le répertoire lost+found à la racine du système de fichier.

Structure stat (sys/stat.h)

struct stat{
      dev_t     st_dev;     //device ID
      ino_t     st_ino;     //i-node number
      mode_t    st_mode;    //protection and type
      nlink_t   st_nlink;   //number of hard links
      uid_t     st_uid;     //user ID of owner
      gid_t     st_gid;     //group ID of owner
      dev_t     st_rdev;    //device type (if special file)
      off_t     st_size;    //total size, in bytes
      blksize_t st_blksize; //blocksize for filesystem I/O
      blkcnt_t  st_blocks;  //number of 512B blocks
      time_t    st_atime;   //time of last access
      time_t    st_mtime;   //time of last modification
      time_t    st_ctime;   //time of last change
    };

Appel système stat

L'appel système stat() permet de garnir une structure stat:

int stat(const char *path, struct stat *buf);

La fonction retourne 0 si tout s'est bien passé ou -1 en cas d'erreur (cf. errno)

struct stat infos;
    char *filename = "/tmp/foo.txt";
    if( stat( filename, &infos ) < 0 )
        fprintf( stderr, "Cannot stat %s: %s\n", filename, strerror(errno) );
    else
        printf( "Filesize: %d\n", infos.st_size );
    

Déterminer le type d'un inode

  • Le champ st_mode est un champ de bits contenant les permissions et le type d'un inode.
  • Il existe plusieurs macro POSIX permettant de tester les types:
S_ISREG(m)fichier de données ?
S_ISDIR(m)répertoire ?
S_ISCHR(m)character device ?
S_ISBLK(m)block device ?
S_ISFIFO(m)FIFO (named pipe) ?
S_ISLNK(m)lien symbolique ?
S_ISSOCK(m)socket?
if( S_ISDIR( info.st_mode ) ) {
        printf( "L'inode est un repertoire.\n" );
    }

Déterminer les permissions d'un inode

On peut utiliser plusieurs flags pour accéder aux valeurs du champs de bits:

S_IRUSR00400owner has read permission
S_IWUSR00200owner has write permission
S_IXUSR00100owner has execute permission
S_IRGRP00040group has read permission
S_IWGRP00020group has write permission
S_IXGRP00010group has execute permission
S_IROTH00004others have read permission
S_IWOTH00002others have write permission
S_IXOTH00001others have execute permission

Appel système lstat

  • Si le A est un lien symbolique vers B, stat("A",...) retourne les informations sur l'inode de B.
  • On peut éviter ce comportement et obtenir les informations sur le lien symbolique lui-même grâce à lstat():
  • int lstat(const char *path, struct stat *buf);
  • Le reste du comportement est identique à stat().

Appel système fstat

  • Parfois on veut connaitre les informations sur un fichier déjà ouvert (cf suite du cours).
  • L'appel fstat() fonctionne comme stat() mais permet d'utiliser un descripteur de fichier à la place d'un nom:
int fstat(int fd, struct stat *buf);

Appel système access

  • On peut tester si le processus en cours à le droit de lire/écrire/exécuter un fichier grâce à l'appel système access():
int access(const char *pathname, int mode);
  • Le paramètre mode est un champs de bits formés des flags:
    R_OKlecture possible
    W_OKécriture possible
    X_OKéxécution possible
  • On peut aussi tester le flag F_OK (seulement) qui indique si le fichier existe.
  • Le test se fait en fonction de l'utilisateur/groupe courrant.
  • access() retourne 0 si le test réussit, -1 sinon (cf errno)

Appel système access (2)

char *fn = "/tmp/foo.txt";
    if ( access( fn, R_OK|W_OK ) == 0 )
        printf( "On peut lire et ecrire sur %s\n", fn );
    else if ( errno == EACCES )
        printf("Pas le droit de lire et/ou d'ecrire sur %s\n", fn);
    else
        perror( fn );
    

Appel système chmod

On peut changer les permissions d'un fichier grâce à l'appel système chmod, similaire à la commande shell du même nom:


    int chmod(const char *path, mode_t mode);   
    //Utilise un descripteur de fichier ouvert
    int fchmod(int fd, mode_t mode);

Le paramètre mode est un champs de bits formés des mêmes flags que le champs st_mode de la structure stat.

Répertoires / Directories

Les répertoires (directories)

  • représentent l'organisation des fichiers sous forme d'arborescence
  • sont des inodes dont le contenu est une liste d'entrées dirent associants un lien à un inode
  • chaque liste d'entrée contient au moins . et ..

Inodes de répertoires

Schema des inodes de repertoire

Répertoire courant

Un processus possède un répertoire courant qui permet d'interpréter les chemins relatifs (e.g. src/monfichier.c).

Au démarrage du programme ce répertoire est celui depuis lequel le programme est lancé. Ce n'est donc pas forcément le répertoire de l'exécutable.

Connaitre le répertoire courant

Pour connaitre le répertoire courant:


                                #include <unistd.h>
                                char *getcwd(char *buf, size_t size);
                            
  • buf: chaine de caractère (déclarée par l'utilisateur) qui contiendra le répertoire courant;
  • size: taille du buffer;
  • retourne NULL en case d'erreur, l'adresse de buf sinon.

Changer le répertoire courant

Pour changer le répertoire courant:


                                #include <unistd.h>
                                int chdir(const char *path);
                            
  • path: chaine de caractère indiquant le nouveau répertoire (relatif ou absolu)
  • retourne -1 en case d'erreur, 0 sinon.

Structure dirent

Les entrées d'un répertoire sont représentées par la structure:


    struct dirent {                  /* dirent.h */
        ino_t   d_ino;               /* inode number */
        off_t   d_off;               /* opaque value used to get next dirent (do not use) */
        unsigned short  d_reclen;    /* length of this record */
        unsinged char   d_type;      /* type of file; not supported by all file systems */
        char            d_name[256]; /* filename (NULL terminated), sometimes d_name[0] */
    };
                        

Seulement deux champs sont décrit par POSIX: d_ino et d_name.

Ne jamais compter sur la taille du tableau d_name, uniquement sur la constante MAX_NAME, qui indique la longueur maximale des noms d'entrées, ou sur strlen

Structure dirent (3)

Le champs d_type est un champs de bits contenant des informations sur le type de l'inode associé:

DT_DIRRépertoire
DT_LNKLien symbolique
DT_REGFichier de données
DT_UNKNOWNType inconnu
DT_...Voir man readdir pour tous les types.
Attention
  • Même sous GNU/Linux, tous les systèmes de fichiers ne donnent pas un accès au type par la structure dir_ent
  • Dans ce cas, le d_type est toujours égal à DT_UNKNOWN.

Entrées

Entrées usuelles


    foo/
    foo/goo/
    foo/goo/bar.txt
    foo/goo/baz.txt
                            

Flot de répertoires

Pour accéder aux entrées d'un répertoire, il faut:

  1. "Ouvrir" le répertoire avec opendir()
  2. "Lire" l'entrée suivante avec readdir()
  3. Répéter 2, jusqu'à épuisement des entrées ou tout autre critère
  4. "Fermer" le répertoire avec closedir()

Note: les fonctions ci-dessus ne sont pas des appels système. Pour manipuler des dossiers avec des appels système il faut utiliser les appels open et getdents. Toutefois en pratique on utilise les fonctions ci-dessus.

Ouvrir un répertoire (opendir)

  • On peut ouvrir un répertoire grâce aux fonctions:
  • 
        DIR *opendir(const char *name);
        DIR *fdopendir(int fd);
                                
  • DIR est un type opaque
  • En cas d'erreur DIR sera NULL
  • Exemples de codes d'erreurs (voir man):
  • EACCESSopération interdite (permissions)
    ENOENTLe répertoire n'existe pas ou le nom est une chaîne vide.
    ENOTDIRLe nom existe mais n'est pas un répertoire.

Lire l'entrée suivante (readdir)

  • On peut lire l'entrée suivante d'un répertoire ouvert avec:
  • struct dirent *readdir(DIR *dirp);
  • Retourne soit:
    • un pointeur sur une instance de la structure dirent
    • NULL s'il n'y a plus d'entrée ou en cas d'erreur.
  • A chaque appel, une nouvelle entrée est retournée (s'il y en a encore).
  • Un seul code d'erreur:
    EBADFLe descripteur dirp n'est pas valide.

Question: Devez-vous libérer la mémoire de la structure retournée ?

Lire l'entrée suivante (readdir)

Attention
  • La structure retournée est susceptible d'être modifiée par chaque appel.
  • Ne jamais appeler free sur le pointeur retourné.
  • readdir n'est pas thread-safe.

Fermer un répertoire (closedir)

  • On peut fermer répertoire ouvert avec:
  • int closedir(DIR *dirp);
  • Retourne 0 en cas de succès et -1 en cas d'erreur.
  • Un seul code d'erreur:
    EBADFLe descripteur dirp n'est pas valide.

Exemple (examples/listDir.c)

#include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <string.h>  //snprintf
    #include <errno.h> 
    #include <dirent.h>
    #include <limits.h>  //PATH_MAX
    
    static void list_dir (const char * dir_name){
    
      DIR *d = opendir(dir_name);
      struct dirent *entry;
      const char *d_name;   //nom d'une entrée
       
      //En cas d'erreur d'ouverture
      if (! d) {
        fprintf(stderr, "Cannot open directory '%s': %s\n",
             dir_name, strerror(errno));
        exit(EXIT_FAILURE);
      }
      
      //Boucle sur chaque entrée
      while( (entry = readdir(d)) != NULL ) {
    
        // Obtient le nom de l'entrée et affiche
        d_name = entry->d_name;
        printf("%s/%s\n", dir_name, d_name);
    
        //Est-ce que 'entry' est un sous-répertoire
        if (entry->d_type & DT_DIR) {
          //Est-ce que 'entry' n'est pas '..' ou '.'
          if (strcmp(d_name, "..") != 0 && strcmp(d_name, ".") != 0) {
        char path[PATH_MAX];
    
        //forme le nom du sous-répertoire et affiche
        int path_length = snprintf (path, PATH_MAX,
                        "%s/%s", dir_name, d_name);
        printf("%s\n", path);
    
        //Vérifie que le nom du sous-répertoire n'est pas trop long
        if (path_length >= PATH_MAX) {
          fprintf(stderr, "Path length has got too long.\n");
          exit(EXIT_FAILURE);
        }
    
        //Appel récursif
        list_dir(path);
          }
        }
      } //while(1)
    
      //On ferme le répertoite
      if( closedir(d) ) {
        fprintf(stderr, "Could not close '%s': %s\n",
            dir_name, strerror (errno));
        exit (EXIT_FAILURE);
      }
    }
    
    int main () {
      list_dir("/var/log/");
      return EXIT_SUCCESS;
    }
    

Créer un répertoire (mkdir)

  • On peut créer un répertoire avec:
  • int mkdir(const char *pathname, mode_t mode);
  • pathname est le nom du répertoire
  • mode spécifie les permissions à utiliser, il est modifié par le umask du processus:
  • mode & ~umask & 0777
  • Retourne 0 en cas de succès et -1 en cas d'erreur
  • voir man 2 mkdir pour les codes d'erreurs

Effacer un répertoire (rmdir)

  • On efface un répertoire vide avec:
  • int rmdir(const char *pathname);
  • Retourne 0 en cas de succès et -1 en cas d'erreur
  • voir man 2 rmdir pour les codes d'erreurs