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:
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!