Readdir and Directories on Xfs

Recently I had some pretty unexpected results from a piece of code I wrote quite a while ago, and never had any issues with. I ran my program on a brand new CentOS 7 installation, and the results weren’t at all what I was used to!

Consider the following code (abridged and simplified):

readdir_xfs.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
#include <dirent.h>
#include <sys/types.h>

void recursive_dir(const char *path){

  DIR *dir;
  struct dirent *de;

  if (!(dir = opendir(path))){
    perror("opendir");
    return;
  }
  if (!(de = readdir(dir))){
    perror("readdir");
    return;
  }

  do {

    if (strncmp (de->d_name, ".", 1) == 0 || strncmp (de->d_name, "..", 2) == 0) {
      continue;
    }

    if (de->d_type == DT_DIR){
      char full_path[PATH_MAX];
      snprintf(full_path, PATH_MAX, "%s/%s", path, de->d_name);
      printf("Dir: %s\n", full_path);
      recursive_dir(full_path);
    }
    else {
      printf("\tFile: %s%s\n", path, de->d_name);
    }
  } while (de = readdir(dir));
  closedir(dir);

}

int main(int argc, char *argv[]){

  if (argc < 2){
    fprintf(stderr, "Usage: %s <dir>\n", argv[0]);
    return 1;
  }

  recursive_dir(argv[1]);
  return 0;
}

Pretty straight forward - reads directories, prints out them and the files within them. Now here’s the kicker:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ gcc -g dirtraverse.c -o dirtraverse && ./dirtraverse /data_ext4/
Dir: /data_ext4//dir1
        File: /data_ext4//dir1file3
        File: /data_ext4//dir1file1
        File: /data_ext4//dir1file2
Dir: /data_ext4//dir2
        File: /data_ext4//dir2file1
Dir: /data_ext4//dir3
$ rsync -a --delete /data_ext4/ /data_xfs/  # Ensure directories are identical
$ gcc -g dirtraverse.c -o dirtraverse && ./dirtraverse /data_xfs/
        File: /data_xfs/dir1
        File: /data_xfs/dir2
        File: /data_xfs/dir3

No traversal?

After a bit of head scratching, and a few debug statements, I found that when using readdir(3) on XFS, dirent->d_type is always 0! No matter what type of file it is. This means that line #25 can never be true.

To be fair though, the manpage states that POSIX only mandates dirent->d_name.

So to be absolutely sure your directory traversal code is more portable, make use of stat(2) and the S_ISDIR() macro!

Jan 16th, 2015