N-PN White-Hat Project
[C] Parcours récursif des dossiers - Version imprimable

+- N-PN White-Hat Project (https://n-pn.fr/forum)
+-- Forum : Programmation (https://n-pn.fr/forum/forumdisplay.php?fid=72)
+--- Forum : Langages compilés (https://n-pn.fr/forum/forumdisplay.php?fid=25)
+--- Sujet : [C] Parcours récursif des dossiers (/showthread.php?tid=2933)



[C] Parcours récursif des dossiers - InstinctHack - 20-04-2013

io,

j'essaye de démarrer en C, mais c'est dur :')
Je voudras afficher la totalité de mes fichiers et répertoires et ça à partir de la racine.
Le problème c'est que ça compile pas et que j'ignore comment corriger les erreurs.
Quelqu'un pour m'aider à avancer ?

Code C :

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

int main()
{
    parcours_rep("/");
}

int parcours_rep(path)
{
    struct dirent *lecture;
    DIR *rep;
    rep = opendir(path);
    while (lecture = readdir(rep))//parcours des elements du repertoire
    {
        if(lecture->d_name!='.' && lecture->d_name!='..')//on ignore les "." et les ".."
        {
            printf("%s\n", lecture->d_name);//petit affichage
            if ( opendir(path.'/'.lecture->d_name) == ENOTDIR)//si l'ouverture échoue parce que c'est pas un dossier
            {}
            else
            {
            parcours_rep(path.'/'.lecture->d_name);//si on est encore là, c'est que c'est un repertoire (ou qu'une erreur est survenue, mais tampis...), donc on le parcours
            }
        }
    }
    closedir(rep);
}

 


Et voilà le message d'erreurs lors de la compilation :
Code BASH :

khaled@loopinfinity:~/LIFE/Informatique/Programmation/C/malware$ gcc file.c
file.c: In function ‘parcours_rep’:
file.c:14:2: warning: passing argument 1 of ‘opendir’ makes pointer from integer without a cast [enabled by default]
In file included from file.c:3:0:
/usr/include/dirent.h:136:13: note: expected ‘const char *’ but argument is of type ‘int’
file.c:18:45: error: ‘ENOTDIR’ undeclared (first use in this function)
file.c:18:45: note: each undeclared identifier is reported only once for each function it appears in
 



RE: [C] Parcours récursif des dossiers - Kiwazaru - 20-04-2013

Déjà, je crois qu'on ne peut pas faire : if (opendir(arg) == ENOTDIR) puisque opendir attend de renvoyer quelque chose dans une déclaration DIR* .
On devrais donc faire DIR* new; -> new = opendir(new_path); + if ( new != ENOTDIR )..
Ensuite pour l'histoire de récursivité, je pense pas qu'on puisse fonctionner avec une seule fonction, j'avais pensé à deux fonction qui auraient une interaction continuelle entre elles, et ces deux fonctions serait dynamique entre elle aussi, cet à dire qu'elles se complèterait et donc permettrais de parcourir un nouveau dossier sans devoir déclarer une infinité de DIR* ce qui serais totalement bête et de toute façon impossible puisqu'on ne peut prédire le nombre de dossier présent dans un dossier de dossier à l'avance...
Pour finir une fonction "int" qui ne renvoi rien ça sert à rien donc fait une fonction "void" :p.

Bref on aurais un schéma : int main(); void dParcours(char* path); void dParcours_bis(char *path);
Avec dans chacune des fonctions une déclaration de DIR* qui parcourrait le nouveau "path" et qui se renverrais à la fonction dParcours (si la fonction courante est dParcours_bis et vice versa) avec le nouveau dossier trouvé Smile

Je ne sais pas si c'est la solution la plus simple, mais c'est là seule qui m'est venu à l'esprit à la vue de ce post pour éviter une infinité de déclaration DIR* :p
Sinon pour l'indentation de ton code, -> if() sans instruction dedans tu peux faire
Code C :
if( x == y ){
}
 

Ou encore,
Code C :
if( x == y ) { }

Je sais pas si des compilateurs acceptent
Code C :
if( x == y )
else{
...
}
 



RE: [C] Parcours récursif des dossiers - b0fh - 21-04-2013

Plop,

opendir() retourne NULL en cas d'erreur. ENOTDIR sera dans errno si il y a lieu. Par contre il n'y a aucun problème pour faire l'affectation directement dans le if, à part peut-être un souci de lisibilité

Plutôt que d'avoir une branche if vide, c'est un meilleur style d'inverser la condition.

Attention, tu ne peux pas comparer des strings avec == et != en C. Ici j'ai triché comme un porc en me contentant d'ignorer tout ce qui commence par un point.

Tu ne peux pas non plus concaténer des strings avec .

Il manquait la déclaration de type dans l'argument de ta fonction.

Si tu n'utilises pas de prototypes, tu dois déclarer les fonctions avant leur utilisation (donc bouger le main à la fin)

Comme ta fonction récursive ne retourne rien, j'ai changé son type de retour en void. J'ai aussi ajouté un return au main.

Tu peux aussi utiliser continue pour sauter au début d'une boucle (certains disent que c'est du mauvais style. Je les emmerde.)

J'utilise 4096 comme taille maximum pour un path, c'est ce qui est défini dans linux (mais ça n'est pas forcément portable). Plutôt que d'accumuler les allocations mémoire, j'utilise un seul buffer qui contient le chemin complet. La fonction récursive garde deux pointeurs, un vers le début du buffer (qui est utilisée pour le printf du chemin complet), et un vers la fin qui sert a ajouter le / et le nom de la dentry)

Je fais le print au début de la fonction récursive, et je ne teste pas si une dentry est un répertoire avant de descendre, parce que si ce n'est pas le cas, le opendir() va rater plus loin (philosophie "let it fail").

Il faut quand même tester si un répertoire est un symlink avant de descendre plus loin, sous peine de se retrouver coincé dans des boucles infinies.

Au final ça donne ça:

Code :
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <unistd.h>

#define PATH_MAX 4096


void parcours_rep(char *path, char *end)
{
        struct dirent *lecture;
        struct stat statbuf;
        DIR *rep;

        printf("%s\n", path);

        if (lstat(path, &statbuf))  // on saute si lstat échoue
                return;

        if (S_ISLNK(statbuf.st_mode))  // on saute les symlinks;
                return;

        if (!(rep = opendir(path)))
                return;  // opendir échoue, on se contente de retourner
        
        *end++ = '/';   // on ajoute un / a la fin du path

        while ((lecture = readdir(rep)))//parcours des elements du repertoire
        {
                if (lecture->d_name[0] == '.')
                        continue;
                strcpy(end, lecture->d_name);
                parcours_rep(path, end + strlen(lecture->d_name));
        }

        closedir(rep);
}

int main()
{
        char path[4096] = "/";
        parcours_rep(path, path + strlen(path));
        return 0;
}

Il y a encore plusieurs améliorations possibles. Par exemple un meilleur traitement d'erreur, l'utilisation de qqch de moins dangereux que strcat(), et surtout un mécanisme d'ouverture du répertoire cible en temps constant (ici, on fait à chaque fois un opendir() sur le path complet, ce qui force le kernel a tout retraverser depuis la racine. Il serait beaucoup mieux de faire des chdir() et de ne faire des opendir() que sur les chemins relatifs trouvés dans les dentry. L'amélioration est laissée comme exercice au lecteur Smile


RE: [C] Parcours récursif des dossiers - Kiwazaru - 21-04-2013

Le problème dans ton code B0fh c'est que ça va opendir(); sur le premier dossier venu et ça ne va pas lister les fichiers qui pourraient être présent à la suite de ce dossier :/


RE: [C] Parcours récursif des dossiers - supersnail - 21-04-2013

@ReVeRse: ben si... si t'arrives à ouvrir le dossier, tu parcours les fichiers, t'appelles la fonction récursive qui va afficher le nom du fichier et retourner à la fonction appelante si c'est pas un dossier (vu que opendir() va fail); la fonction appelante qui va continuer tranquillement à parcourir les fichiers et s'appeler elle-même...


RE: [C] Parcours récursif des dossiers - Kiwazaru - 21-04-2013

Le truc là c'est qu'on veut faire par exemple:

fichier
fichier
fichier
dossier -> fichier, fichier, dossier, fichier, fichier -> (dans dossier) fichier, fichier
fichier

On parcourt donc 3 fois la fonction qui va afficher : "Fichier + Fichier + Fichier" et là il va detecter dossier, du coup avec votre code on va rentrer dans dossier qui va nous lister "Fichier + Fichier" qui va detecter le dossier et rentrer dedans et lister "Fichier + Fichier", sauf qu'il aura zappé 3 fichiers, donc il faudra faire une genre de sauvegarde du dossier précédent à chaque dossier parcouru pour aller parcourir la fin de chaque dossier etc...


RE: [C] Parcours récursif des dossiers - supersnail - 21-04-2013

Ben c'est le principe de la récursivité ça.... Quand tu sors d'une fonction, tu reviens dans la fonction parente juste après l'appel de la fonction (la magie des stack frames & tout le bordel), so jvois pas pourquoi ça zapperait des fichiers.

La récursivité, learn how 2 use it Wink


RE: [C] Parcours récursif des dossiers - Kiwazaru - 21-04-2013

Yep mais là, la fonction qui s'appelle elle même (je savais pas qu'on pouvais faire ça btw :p d'où mes deux fonctions x) ) elle fait toujours la même chose cet à dire check si on a un dossier et si on a un dossier on rentre dedans et on appelle la même fonction et ainsi de suite, je vois pas le moment où on check si il y a d'autre(s) fichier derrière...


RE: [C] Parcours récursif des dossiers - supersnail - 21-04-2013

Ben quand tu fais un "return;" ? (ou simplement à la fin de la fonction)

Pour simplifier, il se passe ceci:
Code :
print du dossier1
opendir du "dossier1"
readdir du dossier1 et appel de la fonction avec le path du fichier1
  | print du fichier1
  | opendir du "fichier1" => FAIL, return
readdir du dossier1 et appel de la fonction avec le path du sousdossier1
  | print du sousdossier1
  | opendir du "sousdossier1"
  | readdir du fichier2 et appel de la fonction avec le path du fichier1 (qui affiche le fichier2 et retourne)
  | [...]
  | La boucle est terminée, on sort de la fonction
readdir du dossier1 et appel de la fonction avec le path du fichier3
[...]
sortie de la fonction

J'espère que mon schéma pourri t'aidera à mieux comprendre ce qui se passe Wink


RE: [C] Parcours récursif des dossiers - Kiwazaru - 21-04-2013

Ahhh ! Return peut s'utiliser donc comme ça aussi? Big GrinD
Intéréssant ça :>

Au temps pour moi je savais pas Smile On en apprend des choses :p


RE: [C] Parcours récursif des dossiers - InstinctHack - 21-04-2013

Merci encore bofh pour ton aide ici et sur irc.
L'objectif est désormais de ne lister QUE les répertoires et de lancer une fonction à chaque fois :
Code :
on compte les fichiers qui match à un motif dans ce répertoire
on tente de remontrer d'un niveau puis on refait l'opération précédante
on tente de remontrer d'un niveau puis on refait l'opération précédante
En gros, je compte les fichiers .flv dans le répertoire, dans ../ et dans ../../
voilà le code en php
Code PHP :

<?php
$data=array();
function parcourir_repertoire($repertoire)
{
    global $data;
    $le_repertoire = @opendir($repertoire);
        if($le_repertoire==false)return;//en cas d'erreur (403 par exemple) on return
    while($fichier = @readdir($le_repertoire))// parcours des fichiers
    {
        $element=$repertoire.'/'.$fichier;
        if(in_array($element,array('//proc','//sys')))continue;//on évite certains répertoires
        if ($fichier == "." || $fichier == "..") continue;

        if(is_dir($element))
        {
            $data[$element]=nombre_reps($element,2,array('flv'));// on stocke le nombre de fichier qui possède les extensions dans l'array et cela sur x niveaux (3 == . ../ ../../ )
            parcourir_repertoire($element);//on parcours le répertoire
        }
    }
    closedir($le_repertoire);
}

function nombre_reps($repertoire,$nombre,$ext)
{
    $a=0;
    $a+=nombre_rep($repertoire,$ext);//on ajoute le nombre de fichier correspondants dans le repertoire

    for($b=0;$b<$nombre;$b++)//on fait autant de boucles que nécessaire
    {
        $repertoire=dirname($repertoire);//on remonte dans l'arbo
        $a+=nombre_rep($repertoire,$ext);//on refait la meme
    }
    return $a;
}

function nombre_rep($repertoire,$ext)
{
    $a=0;
    if(is_dir($repertoire))//si c'est bien un répertoire
    {
        $b=@scandir($repertoire);// comment ça c'est mal ? :')
        if($b!=false)//si pas d'erreur
        {
            foreach($b as $key=>$value)//on parcours les elements
            {
                $c=pathinfo($repertoire.'/'.$value);//on récupère les données sur lui
                if(isset($c['extension']) && in_array($c['extension'],$ext))//si il as une extension et qu'elles nous intéresse
                    $a++;
            }
        }
    }
    return $a;
}

$temps_debut = microtime(true);
parcourir_repertoire('/home');
$temps_fin = microtime(true);
echo "Temps d'exécution : ".round($temps_fin - $temps_debut, 3).PHP_EOL;

arsort($data,SORT_NATURAL);
$a=0;
foreach($data as $key=>$value)
{
    $a++;
    if($a==10)break;
    echo $key." > ".$value.PHP_EOL;
}
 


Si le challenge intéresse quelqu'un de le transcoder en C Wink


RE: [C] Parcours récursif des dossiers - Sh4dows - 22-04-2013

Sinon dans ma présentation il y avait mon myls avec l'option -R (ls -R) Tongue
Ce n'était pas une fonction récursive par contre il fonctionnait malgré un code dégueulasse..

https://github.com/Sh4dows/myls/blob/master/main.c <-- fonctions spider


RE: [C] Parcours récursif des dossiers - sakiir - 22-04-2013

j'ai deja essayé de faire ca sous Win , et ca été tres difficile .. ^^
Bonne chance