Directory Listings with opendir/readdir
Which to use? opendir/readdir or scandir?
The scandir function is essentially a wrapper to opendir/readdir providing a framework to pass predefined or custom filter functions to return only a desired subset of directory entries. With readdir, you are responsible for the implementation. Therein lies the power of readdir, you control all the details.
The primary difference between readdir and scandir is that readdir returns a pointer to a single struct dirent pointer (struct dirent*) and advances the directory pointer to the next entry (NULL if end), while scandir reads all directory listing entries and returns a pointer to an array of pointers to struct dirent entries (struct dirent**). [1]
Note: with readdir, the dirent struct pointed to by the return may be statically allocated, while the array of struct dirent entries returned by scandir is allocated with malloc. (you are ressponsible to track and free the memory allocated by scandir when no longer needed)
Basic use of opendir/readdir
The basic use of opendir/readdir is to first open a directory for reading with opendir. opendir takes a single argument (the path to the dir to open) and on success returns a file descriptor of type DIR *. readdir then reads from the open stream returing a pointer to a (struct dirent*) entry on each successive call (NULL on the next call after reading the last entry). The basic scheme is to read successive entries until NULL is encountered in a while loop:
DIR *dp = opendir (fn); struct dirent *de = NULL; while ((de = readdir (dp))) { /* process entries */ }
Note: the code above is shown without validation of the call to opendir. In practice, always validate each instruction where there is a risk of failure.
explanation -- under construction
Sample Code:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> /* opendir */ #include <dirent.h> /* opendir, readdir */ #include <limits.h> /* for PATH_MAX */ #define EMAX 64 /* general perror string character limit */ #define FMAX 128 /* initial number of file per-directory allocation */ typedef int (*fltfn) (void *, void*, void *); /* typedef for filter function */ int filter (void *de, void *m, void *e); /* filter by both type and ext */ /* return allocated list of filenames within dname, using filter fnfilter, ptr * passes the mode (DT_LNK, DT_DIR, DT_REG encoded as unsigned char), eptr a * pointer to extension to filter, nfiles updated with number of files. */ char **rddirfltc (char *dname, fltfn fnfilter, void *ptr, void *eptr, size_t *nfiles); int cmpdstr (const void *p1, const void *p2); /* qsort compare for char** */ void *xcalloc (size_t nmemb, size_t sz); /* calloc w/error check */ void *xrealloc (void *ptr, size_t psz, size_t *nelem); /* realloc w/err check */ char *fn_ext (char *fn); /* return ext within filename */ /* arguments 1 - dirname, [2 - mode, 3 - extension] */ int main (int argc, char **argv) { char **dlist = NULL, /* directory content array */ *dname = (argc > 1) ? argv[1] : "."; /* dirname supplied as argument */ size_t idx = 0, /* content array index */ nfiles = 0; /* no. of files in directory */ unsigned char fltmode = argc > 2 ? /* file mode for filter */ (unsigned char)strtoul (argv[2], NULL, 0) : 14; /* LNK=2 | DIR=4 | REG=8 */ char *eptr = argc > 3 ? argv[3] : NULL; /* ptr to extension for filter */ /* read array of directory entries into dlist */ if (!(dlist = rddirfltc (dname, filter, &fltmode, eptr, &nfiles))) { fprintf (stderr, "error: rddir failed for '%s'\n", dname); return 1; } /* output search criteria used, sorted forward and reverse listings */ printf ("\n opendir/readdir: rddirflt (%s, filter, &%hhu, %s, &nfiles)\n\n", dname, fltmode, eptr); for (idx = 0; idx < nfiles; idx++) printf (" dlist[%2zu] %-22s ext: %s\n", idx, dlist[idx], fn_ext(dlist[idx])); printf ("\n reverse:\n\n"); while (idx--) printf (" dlist[%2zu] %s\n", idx, dlist[idx]); putchar ('\n'); for (idx = 0; idx < nfiles; idx++) free (dlist[idx]); /* free filenames */ free (dlist); /* free dir list memory */ return 0; } /** filter dirent entries based on type and extension. * the m (mode) value encodes DT_LNK, DT_DIR, DT_REG as: * (symlink bit-1, dirname bit-2, regular bit-3, bit-0 reserved) */ int filter (void *de, void *m, void *e) { if (!de) return 0; unsigned char mode = *(unsigned char *) m; struct dirent *dp = (struct dirent *) de; /* select only matching file types based on mode. */ if ((mode >> 1) & 1 && dp-> d_type == DT_LNK) goto typeok; if ((mode >> 2) & 1 && dp-> d_type == DT_DIR) goto typeok; if ((mode >> 3) & 1 && dp-> d_type == DT_REG) goto typeok; return 0; typeok:; /* select only matching files based on extension. */ if (!e) return 1; char *sp = dp->d_name; char *ep = NULL; if (!(ep = strrchr (sp, '.'))) return 0; if (ep == sp) return 0; ep++; if (strcmp (ep, (char *)e) == 0) return 1; else return 0; } /** opendir/readdir selection based on generic filter function. * memory for the returned listing is allocated dynamically and * reallocated as necessary, removing any arbitrary constraint * concerning listing size. * * NOTE: * filter readdir results are based on file type and file extension. * a single function pointer passes the filter function and critera * for the function is passed as void types through ptr and eptr. * (short for pointer and extension-pointer) */ char **rddirfltc (char *dname, fltfn fnfilter, void *ptr, void *eptr, size_t *nfiles) { DIR *dp = opendir (dname); char **dlist = NULL; /* ptr to array of ptrs to de */ size_t imax = FMAX; /* initial listing allocation size */ struct dirent *de = NULL; /* dirent pointer for readdir */ if (!dp) { /* validate directory open for reading */ char errbuf[PATH_MAX] = ""; sprintf (errbuf, "opendir failed on '%s'", dname); perror (errbuf); return NULL; } /* allocate/validate imax char pointers */ if (!(dlist = xcalloc (imax, sizeof *dlist))) return NULL; while ((de = readdir (dp))) { /* skip dot files */ if (!strcmp (de->d_name, ".") || !strcmp (de->d_name, "..")) continue; /* skip files not matching filter criteria */ if (fnfilter) if (!fnfilter (de, ptr, eptr)) continue; /* allocate/copy de->d_name to dlist */ if (!(dlist[*nfiles] = strdup (de->d_name))) { char errbuf[EMAX] = ""; sprintf (errbuf, "strdup memory allocation failed. " "dlist[%2zu]", *nfiles); perror (errbuf); return NULL; } (*nfiles)++; /* check to realloc dir content array if idx >= imax */ if (*nfiles == imax) { void *tmp; if (!(tmp = xrealloc (dlist, sizeof *dlist, &imax))) break; /* return existing dlist */ dlist = tmp; /* assign reallocated block to dlist */ } } closedir (dp); /* sort readdir results */ qsort (dlist, *nfiles, sizeof *dlist, cmpdstr); return dlist; } /** comparison function for qsort (char **) */ int cmpdstr (const void *p1, const void *p2) { /* The actual arguments to this function are "pointers to * pointers to char", but strcmp(3) arguments are "pointers * to char", hence the following cast plus dereference. */ return strcmp (*(char * const *) p1, *(char * const *) p2); } /** xcalloc allocates memory using calloc and validates the return. * xcalloc allocates memory and reports an error if the value is * null, returning a memory address only if the value is nonzero * freeing the caller of validating within the body of code. */ void *xcalloc (size_t nmemb, size_t sz) { register void *memptr = calloc (nmemb, sz); if (memptr == 0) { perror ("xcalloc() virtual memory exhausted."); return NULL; } return memptr; } /** realloc 'ptr' of 'nelem' of 'psz' to 'nelem * 2' of 'psz'. * returns pointer to reallocated block of memory with new * memory initialized to 0/NULL. return must be assigned to * original pointer in caller. */ void *xrealloc (void *ptr, size_t psz, size_t *nelem) { void *memptr = realloc ((char *)ptr, *nelem * 2 * psz); if (!memptr) { /* validate reallocation */ perror ("realloc(): virtual memory exhausted."); return NULL; } /* zero new memory (optional) */ memset ((char *)memptr + *nelem * psz, 0, *nelem * psz); *nelem *= 2; return memptr; } /** return pointer to extension within filename */ char *fn_ext (char *fn) { char *sp = NULL, /* start pointer */ *ext; if (!fn) return NULL; if ((sp = strrchr (fn, '/'))) /* test for '/' to eliminate '.' in path */ sp++; else sp = fn; if ((ext = strrchr (sp, '.'))) { if (ext == fn) /* dot file case */ return NULL; ext++; } else ext = NULL; return ext; }
Compile:
gcc -Wall -Wextra -pedantic -Wshadow -Ofast -o opendir_ex opendir_readdir.c
Example Input Directory:
$ ls -l tmp drwxr-xr-x 2 david david 4096 Jul 9 2014 bin drwxr-xr-x 5 david david 4096 Jul 8 2014 d1 drwxr-xr-x 5 david david 4096 Jul 8 2014 d2 -rw-r--r-- 1 david david 4493 May 10 2014 rdrmstat.c -rw-r--r-- 1 david david 0 Jul 15 13:09 rdrmstat.h -rw-r--r-- 1 david david 96293 May 10 2014 rmftw-io-out.txt lrwxrwxrwx 1 david david 22 Jul 15 13:09 test-isdir-access.c -> ../test-isdir-access.c lrwxrwxrwx 1 david david 12 Jul 15 13:09 tstfile.c -> ../tstfile.c -rw-r--r-- 1 david david 2499 Jul 9 2014 walk-ftw-test.c -rw-r--r-- 1 david david 1527 Jul 9 2014 walk-nftw-test.c
Use/Output:
$ ./opendir_ex tmp opendir/readdir: rddirflt (tmp, filter, &14, (null)) dlist[ 0]->d_name bin ext: (null) dlist[ 1]->d_name d1 ext: (null) dlist[ 2]->d_name d2 ext: (null) dlist[ 3]->d_name rdrmstat.c ext: c dlist[ 4]->d_name rdrmstat.h ext: h dlist[ 5]->d_name rmftw-io-out.txt ext: txt dlist[ 6]->d_name test-isdir-access.c ext: c dlist[ 7]->d_name tstfile.c ext: c dlist[ 8]->d_name walk-ftw-test.c ext: c dlist[ 9]->d_name walk-nftw-test.c ext: c reverse: dlist[ 9]->d_name walk-nftw-test.c dlist[ 8]->d_name walk-ftw-test.c dlist[ 7]->d_name tstfile.c dlist[ 6]->d_name test-isdir-access.c dlist[ 5]->d_name rmftw-io-out.txt dlist[ 4]->d_name rdrmstat.h dlist[ 3]->d_name rdrmstat.c dlist[ 2]->d_name d2 dlist[ 1]->d_name d1 dlist[ 0]->d_name bin $ ./opendir_ex tmp 10 c opendir/readdir: rddirflt (tmp, filter, &10, c) dlist[ 0]->d_name rdrmstat.c ext: c dlist[ 1]->d_name test-isdir-access.c ext: c dlist[ 2]->d_name tstfile.c ext: c dlist[ 3]->d_name walk-ftw-test.c ext: c dlist[ 4]->d_name walk-nftw-test.c ext: c reverse: dlist[ 4]->d_name walk-nftw-test.c dlist[ 3]->d_name walk-ftw-test.c dlist[ 2]->d_name tstfile.c dlist[ 1]->d_name test-isdir-access.c dlist[ 0]->d_name rdrmstat.c
Memory Error and Leak Check:
As with all code that allocates and frees memory, do not forget to run it through a memory and leak check program like valgrind. They are simple to use and are an invaluable tool in detecting subtle memory errors and memory leaks that your compiler knows nothing about:
$ valgrind ./opendir_ex tmp 10 c ==11777== Memcheck, a memory error detector ==11777== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al. ==11777== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info ==11777== Command: /home/david/dev/src-c/tmp/refmt/bin/odrdflt tmp 10 c ==11777== opendir/readdir: rddirflt (tmp, filter, &10, c) dlist[ 0]->d_name rdrmstat.c ext: c dlist[ 1]->d_name test-isdir-access.c ext: c dlist[ 2]->d_name tstfile.c ext: c dlist[ 3]->d_name walk-ftw-test.c ext: c dlist[ 4]->d_name walk-nftw-test.c ext: c reverse: dlist[ 4]->d_name walk-nftw-test.c dlist[ 3]->d_name walk-ftw-test.c dlist[ 2]->d_name tstfile.c dlist[ 1]->d_name test-isdir-access.c dlist[ 0]->d_name rdrmstat.c ==11777== ==11777== HEAP SUMMARY: ==11777== in use at exit: 0 bytes in 0 blocks ==11777== total heap usage: 7 allocs, 7 frees, 35,240 bytes allocated ==11777== ==11777== All heap blocks were freed -- no leaks are possible ==11777== ==11777== For counts of detected and suppressed errors, rerun with: -v ==11777== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)