3

I am trying to search for a file given as parameter through all the sub-directories. The problem with my code is that when it gets to a file which is not a directory it stops with perror("Error opening the directory\n");.

I can't find a way to get over this. I tried with another if(S_ISREG...), but it doesn't work.

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

void check_file_existence(char *dirName,char *file){
    struct stat *metadata;
    char name[1000];
    struct dirent *dirEntry;
    struct stat bufstat;

    DIR *dir;

    dir = opendir(dirName);
    if (dir==NULL)
    {
        perror("Error opening the directory\n");
        exit(1);
    }

    while ((dirEntry = readdir(dir))!=NULL){
        lstat(dirEntry->d_name,&bufstat);

        if(S_ISDIR(bufstat.st_mode)){
            if (strcmp(dirEntry->d_name,".")==0 || strcmp(dirEntry->d_name,"..")==0){
                continue;
            }
            sprintf(name,"%s/%s",dirName,dirEntry->d_name);
            printf("%s\n",name);
            check_file_existence(name,file);
        }       
    }

    closedir(dir);
}

int main(int argc,char **argv){
    if (argc!=3){
        perror("Number of arguments is wrong.\n");
        exit(1);
    }

    check_file_existence(argv[1],argv[2]);  
}
sg7
  • 6,108
  • 2
  • 32
  • 40
sylar12
  • 107
  • 1
  • 8
  • 2
    Have you thought of printing out the name of the directory that it cannot open at that point? Does it exist? – Jongware Mar 17 '18 at 17:35
  • 2
    This is the perfect time to [learn how to debug your programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/). – Some programmer dude Mar 17 '18 at 17:36
  • @usr2564301 yes, it prints out the name of the file, but because it is a file at the next iteration it can`t open it because it`s not a directory – sylar12 Mar 17 '18 at 17:40
  • 1
    So you are trying to open a directory and it fails because it's actually a file name? Okay, case closed: There is your problem. – Jongware Mar 17 '18 at 17:42
  • @usr2564301 it opens all the subdirectories, until it gets to a file, then it stops. – sylar12 Mar 17 '18 at 17:43
  • 3
    After calling `readdir`, if you call `stat(dirEntry->d_name)` it will usually fail, becuase `d_name` is just the name of the entry, not the path to it. You generally have to concatenate the path to the directory you're reading, `dirName`, to `d_name` before statting. This is an easy mistake to make, and a frequent question: see [Why is stat not working after readdir?](https://stackoverflow.com/questions/34168266/) – Steve Summit Mar 17 '18 at 17:45
  • 3
    Note that you don't need that `lstat` call, If you read [the `readdir` manual page](http://man7.org/linux/man-pages/man3/readdir.3.html) you will see that the `dirent` structure have a `d_type` member that tells you if the file is a directory or not. Incidentally this will also solve your problem (or *check for errors* from your `lstat` call which you *always* should do). – Some programmer dude Mar 17 '18 at 17:47
  • 1
    @Someprogrammerdude That's not a standard feature of `readdir`, though. (In fact I'd never heard of it. So I thank you for that, because it's useful, though nonportable.) – Steve Summit Mar 17 '18 at 17:51
  • 2
    @SteveSummit No it's Linux specific, but since the question is tagged `linux` I thought it would be okay to mention. – Some programmer dude Mar 17 '18 at 17:52
  • @Someprogrammerdude I changed the code to verify with d_type if its a dir and it works. Thank you all. – sylar12 Mar 17 '18 at 17:53
  • 1
    Also, unless you have it just for debugging, I wouldn't have `exit()` invoked in production code just because `readdir` failed. There's all kinds of reasons why `readdir` can fail. For starters, your process doesn't have permission. – selbie Mar 17 '18 at 17:56
  • 2
    Comment on my earlier comment: I see that you *are* concatenating `dirName` and `d_name` before your recursive call. If you had done that earler, before the call to `lstat`, your original program probably would have worked. (You should check for errors when you call l/stat, though, as Some programmer dude mentioned.) – Steve Summit Mar 17 '18 at 18:00

1 Answers1

1

Here is your code with some simplifications that incidentally resolve your bug, and two improvements. It now works to recursively search through a directory tree to find the first occurrence of a specified file.

We use the dirEntry structure to identify the file type and add the strcmp() to check for the specified file. Using d_type in the dirEntry structure is the simplest way to do identify the file type and consequently tends to reduce your bug rate.

We check the input for an extraneous trailing slash. An extra slash on the input will not stop anything but it makes the output less clear.

And, to facilitate debugging, we make copious use of printf() and add a routine to byte-dump the contents of the dirEntry structure to help you see what is happening in a more detailed way as it recurses and loops through the directories and files.

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

void debugbytes( char *s, unsigned short nlen, char dtype, char *nameptr ) {
  int n;
  printf( "\n%d %x ", nlen, dtype );
  switch( dtype )
    {
    case DT_BLK:
      printf( "BLK" );
      break;
    case DT_CHR:
      printf( "CHR" );
      break;
    case DT_DIR:
      printf( "DIR" );
      break;
    case DT_FIFO:
      printf( "FIFO" );
      break;
    case DT_LNK:
      printf( "LNK" );
      break;
    case DT_REG:
      printf( "REG" );
      break;
    case DT_SOCK:
      printf( "SOCK" );
      break;
    case DT_UNKNOWN:
      printf( "UNKOWN" );
      break;
    default:
      printf( "not recognized" );
    }
  printf( " %s :", nameptr );
  for (n = 0; n < nlen; n++ ) {
    printf( "%x", s[n] );
  }
  printf( "\n" );
}

void check_file_existence(char *dirName,char *file){

  DIR *dir;

  struct dirent *dirEntry;

  char name[1000];

  printf( "opening %s\n", dirName );

  dir = opendir(dirName);
  if (dir==NULL)
    {
      perror("Error opening the directory\n");
      exit(1);
    }

    while ((dirEntry = readdir(dir))!=NULL){

      debugbytes( (char *) dirEntry,  dirEntry->d_reclen, dirEntry->d_type, dirEntry->d_name );

      if ( dirEntry->d_type == DT_DIR ) {
        if (strcmp(dirEntry->d_name,".")==0 || strcmp(dirEntry->d_name,"..")==0){
        continue;
      }
      printf( "directory\n" );
      sprintf(name,"%s/%s",dirName,dirEntry->d_name);
      printf("\n%s\n",name);
      check_file_existence(name,file);
    }

    else if ( dirEntry->d_type == DT_REG ) {

      printf( "file %s/%s\n", dirName, (char *)dirEntry->d_name );

      if ( !strcmp( dirEntry->d_name, file ) ) {
        printf( "file found\n" );
        break;
      }
    }
  }

  closedir(dir);
}

int main(int argc,char **argv){
  char dirspec[256] = { 0 };
  int n;

    if (argc!=3){
        perror("Number of arguments is wrong.\n");
        exit(1);
    }

    n = strlen( argv[1] );
    while( (n > 1) && argv[1][n-1] == '/' ) n--; 

    strncpy(dirspec, argv[1], n );

    check_file_existence( dirspec, argv[2] );  
}

And here is a sample output:

$ ./temp1 gEDA/ 1206P.fp
opening gEDA
.
.
.
32 4 DIR Footprints :3b04250000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f2004466f6f747072696e7473004
directory
.
.
.
32 8 REG 1206P.fp :ffffff8084250000ffffffd0ffffffff12ffffffeb7afffffff77052200831323036502e6670054ffffffa7ffffffce8
file gEDA/Footprints/1206P.fp
file found

$
DrM
  • 2,404
  • 15
  • 29