C scandir Example with Extension Filter
scandir Usage
Continuing from the opendir/readir example, here is an implementation with scandir that reads each entry in the directory given as the first argument (or “.” by default) with a .c, .h extnsion and uses stat to obtain the file size and a helper to count the lines in each file. scandir being a wrapper to opendir/readdir, will essentially open the directory and repeatedly call readdir allocating space to read all entries into an array of struct dirent (technically a pointer-to-pointer-to-struct-dirent). Since the memory is dynamically allocated, you are responsible for freeing the list when it is no longer needed.
Sample Code:
#define _SVID_SOURCE /* scandir */ #include <stdio.h> /* printf */ #include <stdlib.h> /* free */ #include <string.h> /* strlen, strrchr */ #include <dirent.h> /* scandir, struct dirent */ #include <sys/types.h> /* stat, struct stat */ #include <sys/stat.h> #include <unistd.h> int filter (const struct dirent *d); size_t nlines (char *fn); char *stripfwd (char *fn); char *addpath (char *ffn, const char *path, const char *fn); int main (int argc, char **argv) { int i, n; char *dir = argc > 1 ? argv[1] : "."; struct dirent **namelist; stripfwd (dir); /* tidy up dir, remove any trailing '/' */ if ((n = scandir (dir, &namelist, filter, alphasort)) == -1) { fprintf (stderr, "error: scandir failed for '%s'.\n", dir); return 1; } for (i = 0; i < n; i++) { /* for each entry */ struct stat st; char fn[FILENAME_MAX] = ""; /* add path before filename */ addpath (fn, dir, namelist[i]->d_name); if (stat (fn, &st) == -1) /* stat filename to get size (bytes) */ fprintf (stderr, "error: stat failed for '%s'\n", fn); else { size_t lines = nlines (fn); /* get lines in file */ printf ("%-32s %9lu %zu\n", fn, st.st_size, lines); } } for (i = 0; i < n; i++) free (namelist[i]); /* free each entry */ free (namelist); /* free pointers */ return 0; } /** filter function to select only files with '.c' and '.h' * file extensions */ int filter (const struct dirent *d) { if (!d) return 0; /* validate struct ptr */ size_t len = strlen (d->d_name); /* lengh of filename */ char *p = strrchr (d->d_name, '.'); /* position of last '.' */ if (!p || len < 3) return 0; /* no '.' or len < 3 no match */ if ((size_t)(p - d->d_name) == len - 2 && /* next to last '.' */ (*(p + 1) == 'c' || *(p + 1) == 'h')) /* last 'c' or 'h' */ return 1; return 0; } /** open and read each line in 'fn' returning the number of lines */ size_t nlines (char *fn) { if (!fn) return 0; size_t lines = 0, n = 0; char *buf = NULL; FILE *fp = fopen (fn, "r"); if (!fp) return 0; while (getline (&buf, &n, fp) != -1) lines++; fclose (fp); free (buf); return lines; } /** remove '/' at end of 'fn' */ char *stripfwd (char *fn) { size_t len = strlen (fn); while (len && fn[len - 1] == '/') fn[--len] = 0; return fn; } /** add 'path' component to beginning of 'fn', return 'ffn' */ char *addpath (char *ffn, const char *path, const char *fn) { if (!ffn || !path || !fn) return NULL; if (strcmp (path, ".")) { /* if path isn't ".", add path to fn */ strcpy (ffn, path); strcat (ffn, "/"); strcat (ffn, fn); } else strcpy (ffn, fn); return ffn; }
Compile:
gcc -Wall -Wextra -finline-functions -O3 -o scandir_ex scandir_ex.c
Example Input:
$ ls -l tmp drwxr-xr-x 5 david david 4096 Jul 20 2015 . drwxr-xr-x 7 david david 4096 Jun 17 02:14 .. 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 2015 rdrmstat.h -rw-r--r-- 1 david david 96293 May 10 2014 rmftw-io-out.txt lrwxrwxrwx 1 david david 22 Jul 15 2015 test-isdir-access.c -> ../test-isdir-access.c lrwxrwxrwx 1 david david 12 Jul 15 2015 tstfile.c -> ../tstfile.c -rw-r--r-- 1 david david 2580 Jul 20 2015 walk-ftw-test.c -rw-r--r-- 1 david david 1527 Jul 9 2014 walk-nftw-test.c
Use/Output:
$ ./scandir_ex tmp tmp/rdrmstat.c 4493 144 tmp/rdrmstat.h 0 0 tmp/test-isdir-access.c 2234 90 tmp/tstfile.c 4324 167 tmp/walk-ftw-test.c 2580 99 tmp/walk-nftw-test.c 1527 66
Memory Error and Leak Check:
In any code your write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you haven't written beyond/outside your allocated block of memory, attempted to read or base a jump on an unintitialized value and finally to confirm that you have freed all the memory you have allocated.
For Linux valgrind is the normal choice. There are many subtle ways to misuse a pointer or new block of memory. Using a memory error checker allows you to identify any problems and validate proper use of of the memory you allocate rather than finding a problem exists through a segfault. There are similar memory checkers for every platform. They are all simple to use, just run your program through it. The output of valgrind is easy to interpret, e.g.:
$ valgrind ./yourprogram arguments ==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: /full/path/to/yourprogram arguments ==11777== your program output and any errors are listed here. ==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)
Always confirm under HEAP SUMMARY that All heap blocks were freed -- no leaks are possible and equally important ERROR SUMMARY: 0 errors from 0 contexts.