Information wants to be free...

Directory Tree Diff

Another month, another tool. Here is a small program that recursively compares two directory trees and prints the changes. Specifically; missing files/directories, additional files/directories and changed files are listed with a different color and prefix, like this screenshot example shows:

Screenshot of difftree.


Files are compared by first checking the size, then a byte by byte comparison.

Here's the code:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>

static void diff_print(int level, char symbol, char *name, int is_dir)
{
  while (level-- > 0)
    printf("  ");

#ifdef WINNT
  printf("%c%s", symbol, name);
#else
  switch (symbol) {
  case '=':
    printf("%c%s", symbol, name);
    break;
  case '*':
    printf("%c\e[1;31m%s\e[0m", symbol, name);
    break;
  case '+':
    printf("%c\e[1;34m%s\e[0m", symbol, name);
    break;
  case '-':
    printf("%c\e[1;35m%s\e[0m", symbol, name);
    break;
  }
#endif

  if (is_dir)
    printf("/");

  printf("\n");
}

static int diff_file(char *path1, char *path2)
{
  FILE *fh1, *fh2;
  int c1, c2;

  fh1 = fopen(path1, "rb");
  if (fh1 == NULL) {
    fprintf(stderr, "Warning: Unable to open file: %s\n", path1);
    return 0;
  }

  fh2 = fopen(path2, "rb");
  if (fh2 == NULL) {
    fprintf(stderr, "Warning: Unable to open file: %s\n", path2);
    return 0;
  }

  while ((c1 = fgetc(fh1)) != EOF) {
    c2 = fgetc(fh2);
    if (c1 != c2) {
      fclose(fh1);
      fclose(fh2);
      return 1;
    }
  }

  fclose(fh1);
  fclose(fh2);
  return 0;
}

static void diff_dir(char *path1, char *path2, int level)
{
  DIR *dh;
  struct dirent *entry;
  struct stat st1, st2;
  char fullpath1[PATH_MAX], fullpath2[PATH_MAX];

  dh = opendir(path1);
  if (dh == NULL) {
    fprintf(stderr, "Warning: Unable to open directory: %s\n", path1);
    return;
  }

  while ((entry = readdir(dh))) {
    if (entry->d_name[0] == '.')
      continue; /* Ignore files with leading dot. */

#ifdef WINNT
    snprintf(fullpath1, PATH_MAX, "%s\\%s", path1, entry->d_name);
    snprintf(fullpath2, PATH_MAX, "%s\\%s", path2, entry->d_name);
#else
    snprintf(fullpath1, PATH_MAX, "%s/%s", path1, entry->d_name);
    snprintf(fullpath2, PATH_MAX, "%s/%s", path2, entry->d_name);
#endif

    if (stat(fullpath1, &st1) == -1) {
      fprintf(stderr, "Warning: Unable to stat() path: %s\n", fullpath1);
      continue;
    }

    if (S_ISDIR(st1.st_mode)) {
      if (stat(fullpath2, &st2) == -1) {
        diff_print(level, '+', entry->d_name, 1);
      } else {
        if (S_ISDIR(st2.st_mode)) {
          diff_print(level, '=', entry->d_name, 1);
          diff_dir(fullpath1, fullpath2, level + 1);
        } else {
          diff_print(level, '+', entry->d_name, 1);
        }
      }
      
    } else if (S_ISREG(st1.st_mode)) {
      if (stat(fullpath2, &st2) == -1) {
        diff_print(level, '+', entry->d_name, 0);
      } else {
        if (S_ISREG(st2.st_mode)) {
          if (st1.st_size == st2.st_size) {
            if (diff_file(fullpath1, fullpath2)) {
              diff_print(level, '*', entry->d_name, 0);
            } else {
              diff_print(level, '=', entry->d_name, 0);
            }
          } else {
            diff_print(level, '*', entry->d_name, 0);
          }
        } else {
          diff_print(level, '+', entry->d_name, 0);
        }
      }
    }
  }
  closedir(dh);

  dh = opendir(path2);
  if (dh == NULL) {
    fprintf(stderr, "Warning: Unable to open directory: %s\n", path2);
    return;
  }

  while ((entry = readdir(dh))) {
    if (entry->d_name[0] == '.')
      continue; /* Ignore files with leading dot. */

#ifdef WINNT
    snprintf(fullpath1, PATH_MAX, "%s\\%s", path1, entry->d_name);
    snprintf(fullpath2, PATH_MAX, "%s\\%s", path2, entry->d_name);
#else
    snprintf(fullpath1, PATH_MAX, "%s/%s", path1, entry->d_name);
    snprintf(fullpath2, PATH_MAX, "%s/%s", path2, entry->d_name);
#endif

    if (stat(fullpath2, &st2) == -1) {
      fprintf(stderr, "Warning: Unable to stat() path: %s\n", fullpath2);
      continue;
    }

    if (S_ISDIR(st2.st_mode)) {
      if (stat(fullpath1, &st1) == -1) {
        diff_print(level, '-', entry->d_name, 1);
      } else {
        if (! S_ISDIR(st1.st_mode)) {
          diff_print(level, '-', entry->d_name, 1);
        }
      }
      
    } else if (S_ISREG(st2.st_mode)) {
      if (stat(fullpath1, &st1) == -1) {
        diff_print(level, '-', entry->d_name, 0);
      } else {
        if (! S_ISREG(st1.st_mode)) {
          diff_print(level, '-', entry->d_name, 0);
        }
      }
    }
  }
  closedir(dh);

  return;
}

int main(int argc, char *argv[])
{
  if (argc < 3) {
    fprintf(stderr, "Usage: %s <directory 1> <directory 2>\n", argv[0]);
    return 1;
  }

  diff_dir(argv[1], argv[2], 0);
  return 0;
}
          


I have also compiled a Windows binary binary for convenience, but note that this does not support colorized output!

Topic: Scripts and Code, by Kjetil @ 01/05-2013, Article Link