profile for David C. Rankin at Stack Overflow, Q&A for professional and enthusiast programmers

C References


Tutorials

Of all the online tutorials, these tutorials are among the best for the areas they discuss.


Libraries

These libraries will make your life easier. They are complete with Makefiles and test programs showing the library use. Just untar the files into a test directory and issue 'make'.


Code Tools

The tools below are general utilities for code and web development.


Comments/Bugs

If you have a comment or suggestion or find a bug in the code, please feel free to send a report.

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)


  1. A follow-on example using scandir can be found here: C scandir Example with Extension Filter

Developed in KDE3:

Quanta+ from KDE3 KDE3 now developed as Trinity Desktop