[index]

Anton's Research Ramblings

Reading Filesystem Directory Contents in C

This post explains how I wrote directory-reading code for use in C, with links to working code.

The Operating System Portability Problem

It's a common task to have a program retrieve a list of entries in a filesystem directory for loading data from user files. Operating system-portable libraries for C are taken care of by the POSIX libraries, and there is a directory interface in POSIX; dirent.h. POSIX libraries are unfortunately are not fully implemented by Microsoft Windows for the MSVC compiler, where there is instead an equivalent Win32 interface. This includes directories, and threads. There are similar issues with socket-level networking. To make fully portable C code we must then abstract over both interfaces.

I handle small, common, tasks like this in my apg.h snippets file that I drag around between projects. This is quite a common approach in C-land.

Abstracting Operating Systems

Generally in C you can use the pre-processor to detect the operating system, or the compiler. Here, you have a choice to use POSIX on Windows if GCC is used. Either strategy works:

#ifdef _WIN32
	// All compilers on Windows using Win32 calls.
#else
	// Other operating systems use POSIX calls here.
#endif
#ifdef _MSC_VER
	// Only MSVC using Win32 calls.
#else
	// Non-Microsoft compilers, including GCC on Windows, using POSIX.
#endif

These compiler branches will be used to determine header files, and also inside your wrapper functions.

POSIX Code

Here's a minimal version, with no validation. I'll mention additional steps I took later.

#ifndef _MSC_VER

#include <dirent.h>

void print_dir_contents( const char* path_ptr ) {
	struct dirent* entry_ptr;
  DIR* dir_ptr = opendir( path_ptr );

  while ( ( entry_ptr = readdir( dir_ptr ) ) ) {
		printf( "%s\n", entry_ptr->d_name );
	}

  closedir( dir_ptr );
}

#endif

Win32 Code

Again, with validation removed:

#ifdef _MSC_VER

// You could reduce the size of this include with Windows macro tricks.
#include <windows.h>

void print_dir_contents( const char* path_ptr ) {
	char tmp[2048];
	WIN32_FIND_DATA fdFile;
  HANDLE hFind = NULL;
	// Specify a file mask. "*.*" means everything.
	sprintf( tmp, "%s/*.*", path_ptr );
	if ( ( hFind = FindFirstFile( tmp, &fdFile ) ) == INVALID_HANDLE_VALUE ) {
		return; // Error.
	}
	do {
		printf( "%s\n", fdFile.cFileName );
	} while ( FindNextFile( hFind, &fdFile ) );
	FindClose( hFind );
}

#endif

Caveats and Improvements

There are several snags I discovered here: