Alessandro Chiarelli
Alessandro Chiarelli

Reputation: 25

How to check if entries in a directory are files or subdirectories?

I am using Raspbian and this is a part of an exercise for my advanced C programming in Linux environments. I have to open a directory and list its content, checking if entries are subdirectories or files.

Here is what I did:

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

 //declarations

int main(int argc, char ** argv){

if(argc != 2){
    fprintf(stderr, "Wrong number of arguments\n");
    exit(EXIT_FAILURE);
}

DIR *dir;
dir = opendir(argv[1]);
if(!dir){
    fprintf(stderr, "Error: unable to open directory\n");
    exit(EXIT_FAILURE);
}

struct dirent * entry;
struct stat filestat;

while((entry = readdir(dir))){
    if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
        stat(entry->d_name, &filestat);
        //printf("S_ISREG(%s) value is:%d\n", entry->d_name, S_ISREG(filestat.st_mode));
        //printf("S_ISDIR(%s) value is:%d\n", entry->d_name, S_ISDIR(filestat.st_mode));
        if(S_ISDIR(filestat.st_mode) == 0){
            printf("Dir: %s\n", entry->d_name);
        } else {
            printf("File: %s\n", entry->d_name);
        }
    }
    
}

closedir(dir);
printf("END\n");

return EXIT_SUCCESS;
}

Now, it works if I am working with relative paths. If I am working on absolute paths, S_ISREG and S_ISDIR macros always return 0. What am I doing wrong?

Upvotes: 1

Views: 728

Answers (3)

Tuxlike
Tuxlike

Reputation: 156

Use

    if (fstatat(dirfd(dir), entry->d_name, &filestat, AT_SYMLINK_NOFOLLOW) == -1) {
        fprintf(stderr, "Cannot stat %s/%s: %s.\n",
                        argv[1], entry->d_name, strerror(errno));
        /* Optionally, exit(EXIT_FAILURE); */
    } else {
        /* Directory entry information in filestat */
    }

As explained in the man 3 readdir manual page, if entry->d_type == DT_UNKNOWN, you need to do the above fstatat() to obtain the type.

This is because some filesystems do not provide the information in the directory entries, and return d_type == DT_UNKNOWN for all directory entries. All applications are required to handle DT_UNKNOWN correctly.

Note that you need

#define _POSIX_C_SOURCE 200809L
#define _ATFILE_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

at the beginning of your file, to tell the C library to expose the functions (fstatat() and strerror()).

However, opendir()/readdir()/closedir() is the wrong way to check directory contents, because the contents may change during your traversal. You would be better off using [scandir()](https://man7.org/linux/man-pages/man3/scandir.3.html9, glob() (if searching for files matching a pattern), or nftw() (if traversing entire trees) which are all POSIX.1 standard functions provided by POSIXy C libraries (basically all except Windows).

An exercise claiming to show how to list files in a directory and using opendir()/readdir()/closedir() is at best misleading, because it leaves all the complexity – handling changes to the directory contents, like renamed files – to you the writer, without telling you such work is necessary! Yes, two decades ago filesystems were typically so simple that opendir()/readdir()/closedir() worked without issues, but that is not the case anymore. The other functions listed above are supposed to handle such cases gracefully, so you the programmer shouldn't need to do it yourself.

Here is one way to properly implement the directory scan in Linux:

#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <ftw.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

static int  report_entry(const char *path,
                         const struct stat *info,
                         int typeflag,
                         struct FTW *ftwbuf)
{
    /* If you need it, entry name is (path + ftwbuf->base). */
    switch (typeflag) {

    case FTW_D: /* Directory */
        printf("%s is a directory\n", path);
        /* If this is the path given to nftw(), recurse into it: */
        if (ftwbuf->level == 0)
            return FTW_CONTINUE;
        /* Do not recurse into any other directories. */
        return FTW_SKIP_SUBTREE;

    case FTW_DNR: /* Directory, but no access to contents */
        printf("%s is a directory but there is no read access\n", path);
        return FTW_CONTINUE;

    case FTW_DP: /* Directory that was already mentioned */
        return FTW_CONTINUE;

    case FTW_F: /* Regular file */
        printf("%s is a file\n", path);
        return FTW_CONTINUE;

    case FTW_SL: /* Symlink, and FTW_PHYS was set for nftw() */
        printf("%s is a symbolic link\n", path);
        return FTW_CONTINUE;

    case FTW_SLN: /* Symlink to a nonexistent file */
        /* This will NOT be reported if FTW_PHYS is set in the nftw() call. */
        printf("%s is a symbolic link to a nonexistent file\n", path);
        return FTW_CONTINUE;

    case FTW_NS: /* stat() failed */
        printf("%s is unknown, and cannot be stat()'d\n", path);
        return FTW_CONTINUE;

    default: /* Should never occur */
        printf("%s is of unknown an unexpected type (%d)\n", path, typeflag);
        return FTW_STOP;
    }
}

/* Number of file descriptors nftw() is allowed to use.
   It mostly matters to applications that use many file descriptors,
   like service daemons (servers).  If nftw() runs out, it slows down,
   but does not fail.  In Linux, processes usually have at least
   a thousand file descriptors available, so 64 is very conservative. */
#ifndef  NFTW_FDS
#define  NFTW_FDS  64
#endif

int report_directory(const char *path)
{
    int  result;

    if (!path || !*path) {
        /* No path specified; invalid parameter */
        errno = EINVAL;
        return -1;
    }

    result = nftw(path, report_entry, NFTW_FDS, FTW_ACTIONRETVAL | FTW_PHYS);
    if (result == -1) {
        /* nftw() error, errno set. */
        return -1;

    } else
    if (result == FTW_STOP) {
         /* report_entry() returned FTW_STOP. We assume error was already printed. */
        errno = 0;
        return -1;

    } else
    if (result != 0) {
        /* Unexpected error */
        errno = EIO;

        return -1;
    }

    /* Done successfully. */
    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *thisname = (argc >= 1 && argv && argv[0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", thisname);
        fprintf(stderr, "       %s DIRECTORY-OR-FILE\n", thisname);
        fprintf(stderr, "\n");
        return (argc == 2) ? EXIT_SUCCESS : EXIT_FAILURE;
    }

    if (report_directory(argv[1]) != 0) {
        if (errno) {
            fprintf(stderr, "Error: %s: %s.\n", argv[1], strerror(errno));
        }
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Upvotes: 1

Alessandro Burzio
Alessandro Burzio

Reputation: 21

It seems by doing stat(entry -> d_name, &filestat) you're giving as directory path just the name of the folder you're looking at, which should work if it belongs to the directory from which the file is being executed but might not work with absolute paths

Also, you are not checking if stat is producing any error, which might also be an issue here - or at least provide some insight to the real problem

you might want to try concatenating your path to entry->d_name and use that when you call stat instead, e.g.

char fullPath[MAX_LEN];

while((entry = readdir(dir))){
    if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){

        sprintf(fullPath, "%s/%s", argv[1], entry->dname);

        if(stat(fullPath, &filestat) < 0){
            //handle error
        }

        if(S_ISDIR(filestat.st_mode) == 0){
            printf("Dir: %s\n", entry->d_name);
        } else {
            printf("File: %s\n", entry->d_name);
        }

    }
    
}

where MAX_LEN is some predefined constant

Upvotes: 2

Jabberwocky
Jabberwocky

Reputation: 50778

Try this:

while((entry = readdir(dir))){
    if(!(strcmp(entry->d_name, ".") == 0) && !(strcmp(entry->d_name,"..") == 0)){
        if(entry->d_type == DT_DIR){
            printf("Dir: %s\n", entry->d_name);
        } else {
            printf("File: %s\n", entry->d_name);
        }
    }    
}

It might not work on your platform though, read the readdir documentation for more information.

Upvotes: 0

Related Questions