L3 --- TP Appel Systèmes (2) : find multiproc

En shell

Le programme suivant a le même comportement que find :
#!/bin/sh
# findsequentiel.sh <rep> <nom> doit fonctionner comme find <rep> -name <nom>

findname() {
    # $1: rep $2: motif
    for f in $1/*; do
        case `basename "$f"` in
            $2) echo "$f";;
        esac
        if test -d "$f"; then
            findname "$f" "$2"
        fi
    done
}

findname "$1" "$2"

Question: modifier le script shell de façon à ce qu'un processus soit créé pour explorer chaque répertoire rencontré.

En C

L'objectif ici est de refaire le programme précédent en C. On se contentera de l'égalité des noms de fichiers (pas d'utilisation de * ou ?). Une version plus rapide se contentera de s'arrêter dès qu'un fichier est trouvé. Il faudra alors tuer les fils devenu inutiles. Il nous faut donc disposer de fonctions pour le parcours de répertoires.

Fonctions de parcours de répertoire

stat(2) permet d'avoir accès aux caractéristiques du fichier dont le chemin d'accès est passé en paramètre.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

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

struct stat {
    dev_t     st_dev;     /* Périphérique                */
    ino_t     st_ino;     /* Numéro i-noeud              */
    mode_t    st_mode;    /* Protection                  */
    nlink_t   st_nlink;   /* Nb liens matériels          */
    uid_t     st_uid;     /* UID propriétaire            */
    gid_t     st_gid;     /* GID propriétaire            */
    dev_t     st_rdev;    /* Type périphérique           */
    off_t     st_size;    /* Taille totale en octets     */
    blksize_t st_blksize; /* Taille de bloc pour E/S     */
    blkcnt_t  st_blocks;  /* Nombre de blocs alloués     */
    time_t    st_atime;   /* Heure dernier accès         */
    time_t    st_mtime;   /* Heure dernière modification */
    time_t    st_ctime;   /* Heure dernier changement    */
};
Les attributs suivants correspondent au champ st\_mode qui code outre les droits d'accès rwx sur 9 bits et les droits spéciaux « set-UID, set-GID, sticky bit » sur 3 bits, le type de fichier sur 4 bits (soit 16 bits en tout) :
S_IFMT 00170000 masque du type de fichier
S_IFSOCK 0140000 socket
S_IFLNK 0120000 lien symbolique
S_IFREG 0100000 fichier régulier
S_IFBLK 0060000 périphérique blocs
S_IFDIR 0040000 répertoire
S_IFCHR 0020000 périphérique caractères
S_IFIFO 0010000 fifo
S_ISUID 0004000 bit Set-UID
S_ISGID 0002000 bit Set-Gid
S_ISVTX 0001000 bit "sticky"
S_IRWXU 00700 lecture/écriture/exécution du propriétaire
S_IRUSR 00400 le propriétaire a le droit de lecture
S_IWUSR 00200 le propriétaire a le droit d'écriture
S_IXUSR 00100 le propriétaire a le droit d'exécution
S_IRWXG 00070 lecture/écriture/exécution du groupe
S_IRGRP 00040 le groupe a le droit de lecture
S_IWGRP 00020 le groupe a le droit d'écriture
S_IXGRP 00010 le groupe a le droit d'exécution
S_IRWXO 00007 lecture/écriture/exécution des autres
S_IROTH 00004 les autres ont le droit de lecture
S_IWOTH 00002 les autres ont le droit d'écriture
S_IXOTH 00001 les autres ont le droit d'exécution
Les macros POSIX suivantes sont fournies pour vérifier le type de fichier plutôt que d'utiliser directement des masques avec des valeurs précédentes :
S_ISREG(m) un fichier régulier ?
S_ISDIR(m) un répertoire ?
S_ISCHR(m) un péripherique en mode caractère ?
S_ISBLK(m) un périphérique en mode bloc ?
S_ISFIFO(m) une FIFO ?
S_ISLNK(m) est-ce un lien symbolique ? (Pas dans POSIX.1-1996).
S_ISSOCK(m) une socket ? (Pas dans POSIX.1-1996).
Afin de lire de façon portable la structure d'un répertoire, il est conseillé d'utiliser les primitives POSIX opendir, readdir et closedir :
#include <sys/types.h>
#include <dirent.h>

DIR *opendir(const char *name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);

struct dirent {
#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64
    ino64_t       d_ino;     /* inode number */
    off64_t       d_off;     /* offset to the next dirent */
#else
    ino_t         d_ino;     /* inode number */
    off_t         d_off;     /* offset to the next dirent */
#endif
    unsigned short int d_reclen; /* length of this record */
    unsigned char d_type;    /* type of file */
    char          d_name[256]; /* filename */
};
On peut ainsi faire une version simpliste de ls qui ne prend pas d'argument sur la ligne de contrôle :
// palels.c -- alexis.lechervy@info.unicaen.fr -- oct 2014
// une pale version de ls(1)

#include <stdio.h> // pour perror
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#define true 1
#define false 0


void usage(const char* nom) {
  fprintf(stderr,"%s noms...\n",nom);
}

/** teste si nom est un répertoire 
 * renvoie : -1 si erreur (fichier inexistant, 
 *                         absence des droits de lecture, ...
 *            1 si c'est un fichier de type répertoire
 *            0 sinon.
 */
int isDir(const char * nom) {
  struct stat bufstat;
  if (stat(nom, &bufstat) != 0) {    
    perror(nom);
    return -1;
  }
  return S_ISDIR(bufstat.st_mode) ? 1 : 0;
}

/** Liste le contenu d'un répertoire */
void listerep(const char* nom) {
  DIR* dir = opendir(nom);
  if (dir == NULL) {
    perror(nom);
    return;
  }
  struct dirent* ent;
  while((ent = readdir(dir)) != NULL) {
    printf("%s\n",ent->d_name);
  }
  closedir(dir);
}

short ls(const char* nom) {
  short ret = true;
  switch (isDir(nom)) {
  case -1: // erreur
    ret = false;
    break;
  case 1: // c'est un répertoire
    listerep(nom);
    break;
  case 0: // juste un simple fichier
    printf("%s\n",nom);
  }
  return ret;
}

int main(int argc, char *argv[])
{
  if (argc <= 1) { 
    usage(argv[0]);
    return 1; // ou exit(1)
  }
  
  short ret = true;
  for(int i = 1; i < argc; i++)
    ret = ret && ls(argv[i]);

  return ret ? 0 : 1; // 0 si OK
}
Vous pouvez accéder au source de palels.c et le compiler avec : gcc -Wall -o palels palels.c

Question : écrire en C ou C++, le find multiprocessus

Indications

  1. S'inspirer de l'algorithme du programme shell de la première question.
  2. Ne pas oublier de ne pas « descendre » dans les répertoires . et ...
  3. Pour comparer deux chaînes de caractères :
      int l = strlen(chaine1);
      if (strncmp(chaine1, chaine2, l) == 0)
          // les 2 chaînes sont égales
    
    
  4. Pour concaténer deux chaînes en intercalant un / :
      // on alloue un tableau de la bonne longueur 
      // +1 pour le / et +1 pour le \0 final
      char tampon[strlen(repertoire)+1+strlen(nom)+1];
    
      // on écrit dans tampon le repertoire, le /, et le nom
      // strncat et strncpy sont inutiles car on a vérifié les longueurs
      // et on peut faire
      strcat(strcat(strcpy(tampon, repertoire), "/"),nom);
      // au lieu de :
      // strncat(strcat(strncpy(tampon, repertoire, strlen(repertoire)+1), "/"),nom, strlen(nom)+1);
      // on pourrait aussi faire : 
      // sprintf(tampon, "%s/%s", repertoire, nom);
    
  5. Ne pas oublier de mettre la déclaration include suivante pour utiliser les fonctions de bibliothèques préfixées par str :
    #include <strings.h>