2

I have a function that takes in an argument which is a file path. That path is then used in conjunction with the readdir function and stat function to print out all of the files and directories in a directory. The issue I have is when I try to sort the files and directories, they are all viewed as directories. I have looked at similar examples on here and I believe the issue is the way I have stat formatted. I just don't understand how stat works and I have even looked at the manual pages but they are not making a lot of sense.

Here is my code:

void scan(const char *dir) {
    DIR *directory;
    struct dirent *file;
    struct stat info;

    directory = opendir(dir);

    if (directory == NULL) {
        printf("Cannot open directory: %s\n", dir);
        return;
    }
    while ((file = readdir(directory)) != NULL) {
        if (file->d_name[0] == '.') {
            // then hidden file, so leave hidden
            continue;
        }
        
        if (stat(dir, &info) == -1) {
            printf("can't find %s\n", file->d_name);
            perror("ERROR");
        }

        // determine if file or directory
        if (S_ISREG(info.st_mode))
            printf("File: ");
        if (S_ISDIR(info.st_mode))
            printf("Dir: ");

        // display the name of the file
        printf("%s\n", file->d_name);
    }
    closedir(directory);
}

Here is the output that I keep getting...

Dir: par_hash_table.c
Dir: BlackBoxTesting
Dir: Makefile
Dir: in3.txt
Dir: par_hash_table.o
Dir: in5.txt
Dir: par_hash_table
Dir: in4.txt
Dir: in6.txt
Dir: in1.txt
Dir: in2.txt
Dir: in8.txt
Dir: in7.txt

Here is the command I am using, I'm using Linux and the file path doesn't need to be this particular but I just wanted to show everything...

./par_hash_table 1 2 /home/dpb/Documents/CSE_420/P3

Any help figuring out how I can differentiate a txt file from a folder would help.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Devin Bowen
  • 127
  • 1
  • 1
  • 13

2 Answers2

3

An alternative to concatenating the filename in the dirent structure onto the path so stat() will work is using fstatat() instead with a file descriptor for the directoryand the plain filename.

Also, on some filesystems, readdir() will fill in the dirent structure's d_type field with the type of the file, so if that's the case, you can get away without doing a stat at all.

Example (Also including some improvements to error reporting and handling; one issue with your code is continuing on and testing the stat structure even when the function fails):

#define _DEFAULT_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

void scan(const char *dir) {
    DIR *directory;
    struct dirent *file;

    directory = opendir(dir);

    if (directory == NULL) {
      fprintf(stderr, "Cannot open directory %s: %s\n", dir, strerror(errno));
      return;
    }
    int dfd = dirfd(directory); // directory's file descriptor

    while ((file = readdir(directory)) != NULL) {
      if (file->d_name[0] == '.') {
        // then hidden file, so leave hidden
        continue;
      }

      if (file->d_type == DT_UNKNOWN) {
        // Only stat the file if we don't already have the file type available
        struct stat info;
        if (fstatat(dfd, file->d_name, &info, 0) < 0) {
          fprintf(stderr, "Can't stat %s/%s: %s\n",
                  dir, file->d_name, strerror(errno));
          continue;
        }
        if (S_ISREG(info.st_mode)) {
          file->d_type = DT_REG;
        } else if (S_ISDIR(info.st_mode)) {
          file->d_type = DT_DIR;
        }
      }

      // determine if file or directory
      if (file->d_type == DT_REG)
        printf("File: ");
      else if (file->d_type == DT_DIR)
        printf("Dir: ");
      else
        printf("Other: ");

      // display the name of the file
      printf("%s\n", file->d_name);
    }

    closedir(directory);
}

int main(int argc, char **argv) {
  if (argc != 2) {
    fprintf(stderr, "Usage: %s DIRECTORY\n", argv[0]);
    return 1;
  }
  scan(argv[1]);
  return 0;
}
Shawn
  • 47,241
  • 3
  • 26
  • 60
2

Your error is here:

    if(stat(dir, &info) == -1){
        printf("can't find %s\n", file->d_name);
        perror("ERROR");
       }

You are getting stat for the function parameter dir every time. You want to stat the file from the directory entry, instead. So you need to concatenate the dir and the file names, to get full path for stat.


Code to concat the parts:

char filepath[strlen(dir) + 1 + strlen(file->d_name) + 1];
snprintf(filepath, sizeof filepath, "%s/%s", dir, file->d_name);
if(stat(filepath, &info) == -1){
   ....

(A side note, incidentally this is one of the very few cases where I think using a VLA is fine. Just be careful if you convert this to a recursive function, make sure the VLA goes out of scope before the recursive call... Or just use malloc and free to be safe from stack overflow.)


Also, please run your code through auto-formater, which also can add the missing optional {}. It really is quite a good idea to use {} for every block, even if it is just one statement. Or, if you don't find a C auto-formatter, your editor should at least have auto-indentation, so add the {} yourself and then auto-indent the code.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • 1
    stat is not part of the c standard library. what does that mean? baseline c does not have stat. this is the POSIX standard library youre suggesting here. – muzzletov Mar 11 '23 at 10:28
  • 3
    @muzzletov: the program uses the POSIX Standard functions. The OP documents the target is a linux system. – chqrlie Mar 11 '23 at 10:32
  • 2
    @chqrlie yes, thats why the title is wrong or misleading – muzzletov Mar 11 '23 at 10:33
  • 2
    @hyde: the function `fstatat()` is a good workaround to avoid the need for path concatenation. It is available on linux since 2.6.16 and other recent Unices. – chqrlie Mar 11 '23 at 10:37
  • 2
    @chqrlie Great minds think alike. I was putting together a demonstration of `fstatat()` when you made that comment. – Shawn Mar 11 '23 at 10:55
  • 1
    @hyde This worked but I'm not familiar with the format of stat, d_name, and snprintf so I'm looking up some stuff about it so it makes more sense. – Devin Bowen Mar 11 '23 at 20:50