B.6 Reading Directory Contents

GNU/Linux provides functions for reading the contents of directories. Although these aren't directly related to the low-level I/O functions described in this appendix, we present them here anyway because they're often useful in application programs.

To read the contents of a directory, follow these steps:

1.       Call opendir, passing the path of the directory that you want to examine. The call to opendir returns a DIR* handle, which you'll use to access the directory contents. If an error occurs, the call returns NULL.

2.       Call readdir repeatedly, passing the DIR* handle that you obtained from opendir. Each time you call readdir, it returns a pointer to a struct dirent instance corresponding to the next directory entry. When you reach the end of the directory's contents, readdir returns NULL.

The struct dirent that you get back from readdir has a field d_name, which contains the name of the directory entry.

3.       Cal closedir, passing the DIR* handle, to end the directory listing operation.

Include <sys/types.h> and <dirent.h> if you use these functions in your program.

Note that if you need the contents of the directory arranged in a particular order, you'll have to sort them yourself.

The program in Listing B.8 prints out the contents of a directory. The directory may be specified on the command line, but if it is not specified, the program uses the current working directory. For each entry in the directory, it displays the type of the entry and its path. The get_file_type function uses lstat to determine the type of a file system entry.

Listing B.8 (listdir.c) Print a Directory Listing
#include <assert.h> 
#include <dirent.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <unistd.h> 
 
/* Return a string that describes the type of the file system entry PATH.  */ 
 
const char* get_file_type (const char* path) 
{
  struct stat st; 
  lstat (path, &st); 
  if (S_ISLNK (st.st_mode)) 
    return "symbolic link"; 
  else if (S_ISDIR (st.st_mode)) 
    return "directory"; 
  else if (S_ISCHR (st.st_mode)) 
    return "character device"; 
  else if (S_ISBLK (st.st_mode)) 
    return "block device"; 
    else if (S_ISFIFO (st.st_mode)) 
      return "fifo"; 
    else if (S_ISSOCK (st.st_mode)) 
      return "socket"; 
    else if (S_ISREG (st.st_mode)) 
      return "regular file"; 
    else 
      /* Unexpected. Each entry should be one of the types above.  */ 
      assert (0); 
} 
 
int main (int argc, char* argv[]) 
{
  char* dir_path; 
  DIR* dir; 
  struct dirent* entry; 
  char entry_path[PATH_MAX + 1]; 
  size_t path_len; 
 
  if (argc >= 2) 
    /* If a directory was specified on the command line, use it.  */ 
    dir_path = argv[1]; 
  else 
    /* Otherwise, use the current directory.  */ 
    dir_path = "."; 
  /* Copy the directory path into entry_path.  */ 
  strncpy (entry_path, dir_path, sizeof (entry_path)); 
  path_len = strlen (dir_path); 
  /* If the directory path doesn't end with a slash, append a slash.  */ 
  if (entry_path[path_len - 1] != '/') {
    entry_path[path_len] = '/'; 
    entry_path[path_len + 1] = '\0'; 
    ++path_len; 
  } 
 
  /* Start the listing operation of the directory specified on the 
     command line.  */ 
  dir = opendir (dir_path); 
  /* Loop over all directory entries.  */ 
  while ((entry = readdir (dir)) != NULL) {
    const char* type; 
    /* Build the path to the directory entry by appending the entry 
       name to the path name.  */ 
    strncpy (entry_path + path_len, entry->d_name, 
             sizeof (entry_path) - path_len); 
    /* Determine the type of the entry.  */ 
    type = get_file_type (entry_path); 
    /* Print the type and path of the entry.  */ 
    printf ("%-18s: %s\n", type, entry_path); 
  } 
  /* All done.  */ 
  closedir (dir); 
  return 0; 
} 

Here are the first few lines of output from listing the /dev directory. (Your output might differ somewhat.)

 
% ./listdir /dev 
directory         : /dev/. 
directory         : /dev/.. 
socket            : /dev/log 
character device  : /dev/null 
regular file      : /dev/MAKEDEV 
fifo              : /dev/initctl 
character device  : /dev/agpgart 
... 

To verify this, you can use the ls command on the same directory. Specify the -U flag to instruct ls not to sort the entries, and specify the -a flag to cause the current directory ( . ) and the parent directory ( .. ) to be included.

 
% ls -lUa /dev 
total 124 
drwxr-xr-x    7 root     root        36864 Feb   1 15:14 . 
drwxr-xr-x   22 root     root         4096 Oct  11 16:39 .. 
srw-rw-rw-    1 root     root            0 Dec  18 01:31 log 
crw-rw-rw-    1 root     root       1,   3 May   5  1998 null 
-rwxr-xr-x    1 root     root        26689 Mar   2  2000 MAKEDEV 
prw-------    1 root     root            0 Dec  11 18:37 initctl 
crw-rw-r--    1 root     root      10, 175 Feb   3  2000 agpgart 
... 

The first character of each line in the output of ls indicates the type of the entry.