2
Copyright (c) 2012 Red Hat, Inc. <http://www.redhat.com>
3
This file is part of GlusterFS.
5
This file is licensed to you under your choice of the GNU Lesser
6
General Public License, version 3 or any later version (LGPLv3 or
7
later), or the GNU General Public License, version 2 (GPLv2), in all
8
cases as published by the Free Software Foundation.
15
#define _XOPEN_SOURCE 500
33
* FTW_ACTIONRETVAL is a GNU libc extension. It is used here to skip
34
* hierarchies. On other systems we will still walk the tree, ignoring
37
#ifndef FTW_ACTIONRETVAL
38
#define FTW_ACTIONRETVAL 0
44
char test_directory[4096];
45
char **ignored_directory;
46
unsigned int directories_ignored;
49
static arequal_config_t arequal_config;
52
arequal_parse_opts(int key, char *arg, struct argp_state *_state);
54
static struct argp_option arequal_options[] = {
55
{"ignore", 'i', "IGNORED", 0, "entry in the given path to be ignored"},
56
{"path", 'p', "PATH", 0, "path where arequal has to be run"},
62
fprintf(stderr, "D "); \
63
fprintf(stderr, fmt); \
68
add_to_list(char *arg);
70
get_absolute_path(char directory[], char *arg);
75
return ((((a) + (b)-1) / ((b) ? (b) : 1)) * (b));
84
index = arequal_config.directories_ignored - 1;
87
if (!arequal_config.ignored_directory) {
88
arequal_config.ignored_directory = calloc(1, sizeof(char *));
90
arequal_config.ignored_directory = realloc(
91
arequal_config.ignored_directory, sizeof(char *) * (index + 1));
93
arequal_config.ignored_directory[index] = string;
97
arequal_parse_opts(int key, char *arg, struct argp_state *_state)
101
arequal_config.directories_ignored++;
106
strcpy(arequal_config.test_directory, arg);
108
get_absolute_path(arequal_config.test_directory, arg);
111
.test_directory[strlen(arequal_config.test_directory) -
114
.test_directory[strlen(arequal_config.test_directory) - 1] =
118
case ARGP_KEY_NO_ARGS:
123
if (_state->argc == 1) {
132
get_absolute_path(char directory[], char *arg)
138
if (getcwd(cwd, sizeof(cwd)) == NULL)
139
printf("some error in getting cwd\n");
141
if (strcmp(arg, ".") != 0) {
142
if (cwd[strlen(cwd)] != '/')
143
cwd[strlen(cwd)] = '/';
146
strcpy(directory, cwd);
149
static struct argp argp = {
150
arequal_options, arequal_parse_opts, "",
151
"arequal - Tool which calculates the checksum of all the entries"
152
"present in a given directory"};
154
/* All this runs in single thread, hence using 'global' variables */
156
unsigned long long avg_uid_file = 0;
157
unsigned long long avg_uid_dir = 0;
158
unsigned long long avg_uid_symlink = 0;
159
unsigned long long avg_uid_other = 0;
161
unsigned long long avg_gid_file = 0;
162
unsigned long long avg_gid_dir = 0;
163
unsigned long long avg_gid_symlink = 0;
164
unsigned long long avg_gid_other = 0;
166
unsigned long long avg_mode_file = 0;
167
unsigned long long avg_mode_dir = 0;
168
unsigned long long avg_mode_symlink = 0;
169
unsigned long long avg_mode_other = 0;
171
unsigned long long global_ctime_checksum = 0;
173
unsigned long long count_dir = 0;
174
unsigned long long count_file = 0;
175
unsigned long long count_symlink = 0;
176
unsigned long long count_other = 0;
178
unsigned long long checksum_file1 = 0;
179
unsigned long long checksum_file2 = 0;
180
unsigned long long checksum_dir = 0;
181
unsigned long long checksum_symlink = 0;
182
unsigned long long checksum_other = 0;
185
checksum_path(const char *path)
187
unsigned long long csum = 0;
188
unsigned long long *nums = 0;
192
len = roof(strlen(path), sizeof(csum));
193
cnt = len / sizeof(csum);
195
nums = __builtin_alloca(len);
196
memset(nums, 0, len);
197
strcpy((char *)nums, path);
209
checksum_md5(const char *path, const struct stat *sb)
211
uint64_t this_data_checksum = 0;
214
char strvalue[17] = {
219
const char *pos = NULL;
222
/* Have to escape single-quotes in filename.
223
* First, calculate the size of the buffer I'll need.
225
for (pos = path; *pos; pos++) {
232
cmd = malloc(sizeof(char) * (len + 20));
235
/* Now, build the command with single quotes escaped. */
239
strcpy(cpos, "md5sum '");
241
#elif defined(__NetBSD__)
242
strcpy(cpos, "md5 -n '");
244
#elif defined(__FreeBSD__) || defined(__APPLE__)
245
strcpy(cpos, "md5 -q '");
248
#error "Please add system-specific md5 command"
251
/* Add the file path, with every single quotes replaced with this sequence:
255
for (pos = path; *pos; pos++) {
257
strcpy(cpos, "'\\''");
265
/* Add on the trailing single-quote and null-terminate. */
268
filep = popen(cmd, "r");
274
if (fread(strvalue, sizeof(char), 16, filep) != 16) {
275
fprintf(stderr, "%s: short read\n", path);
279
this_data_checksum = strtoull(strvalue, NULL, 16);
280
if (-1 == this_data_checksum) {
281
fprintf(stderr, "%s: %s\n", strvalue, strerror(errno));
284
checksum_file1 ^= this_data_checksum;
286
if (fread(strvalue, sizeof(char), 16, filep) != 16) {
287
fprintf(stderr, "%s: short read\n", path);
291
this_data_checksum = strtoull(strvalue, NULL, 16);
292
if (-1 == this_data_checksum) {
293
fprintf(stderr, "%s: %s\n", strvalue, strerror(errno));
296
checksum_file2 ^= this_data_checksum;
310
checksum_filenames(const char *path, const struct stat *sb)
313
struct dirent *entry = NULL;
314
unsigned long long csum = 0;
318
dirp = opendir(path);
325
while ((entry = readdir(dirp))) {
326
/* do not calculate the checksum of the entries which user has
327
told to ignore and proceed to other siblings.*/
328
if (arequal_config.ignored_directory) {
329
for (i = 0; i < arequal_config.directories_ignored; i++) {
330
if ((strcmp(entry->d_name,
331
arequal_config.ignored_directory[i]) == 0)) {
333
DBG("ignoring the entry %s\n", entry->d_name);
342
csum = checksum_path(entry->d_name);
343
checksum_dir ^= csum;
359
process_file(const char *path, const struct stat *sb)
365
avg_uid_file ^= sb->st_uid;
366
avg_gid_file ^= sb->st_gid;
367
avg_mode_file ^= sb->st_mode;
369
ret = checksum_md5(path, sb);
375
process_dir(const char *path, const struct stat *sb)
377
unsigned long long csum = 0;
381
avg_uid_dir ^= sb->st_uid;
382
avg_gid_dir ^= sb->st_gid;
383
avg_mode_dir ^= sb->st_mode;
385
csum = checksum_filenames(path, sb);
387
checksum_dir ^= csum;
393
process_symlink(const char *path, const struct stat *sb)
399
unsigned long long csum = 0;
403
avg_uid_symlink ^= sb->st_uid;
404
avg_gid_symlink ^= sb->st_gid;
405
avg_mode_symlink ^= sb->st_mode;
407
ret = readlink(path, buf, 4096);
413
DBG("readlink (%s) => %s\n", path, buf);
415
csum = checksum_path(buf);
417
DBG("checksum_path (%s) => %llx\n", buf, csum);
419
checksum_symlink ^= csum;
427
process_other(const char *path, const struct stat *sb)
431
avg_uid_other ^= sb->st_uid;
432
avg_gid_other ^= sb->st_gid;
433
avg_mode_other ^= sb->st_mode;
435
checksum_other ^= sb->st_rdev;
441
ignore_entry(const char *bname, const char *dname)
445
for (i = 0; i < arequal_config.directories_ignored; i++) {
446
if (strcmp(bname, arequal_config.ignored_directory[i]) == 0 &&
447
strncmp(arequal_config.test_directory, dname,
448
strlen(arequal_config.test_directory)) == 0)
456
process_entry(const char *path, const struct stat *sb, int typeflag,
465
/* The if condition below helps in ignoring some directories in
466
the given path. If the name of the entry is one of the directory
467
names that the user told to ignore, then that directory will not
468
be processed and will return FTW_SKIP_SUBTREE to nftw which will
469
not crawl this directory and move on to other siblings.
470
Note that for nftw to recognize FTW_SKIP_TREE, FTW_ACTIONRETVAL
471
should be passed as an argument to nftw.
473
This mainly helps in calculating the checksum of network filesystems
474
(client-server), where the server might have some hidden directories
475
for managing the filesystem. So to calculate the sanity of filesystem
476
one has to get the checksum of the client and then the export directory
477
of server by telling arequal to ignore some of the directories which
478
are not part of the namespace.
481
if (arequal_config.ignored_directory) {
482
#ifndef FTW_SKIP_SUBTREE
486
dname = dirname(name);
488
for (cp = strtok(name, "/"); cp; cp = strtok(NULL, "/")) {
489
if (ignore_entry(cp, dname)) {
490
DBG("ignoring %s\n", path);
496
#else /* FTW_SKIP_SUBTREE */
499
name[strlen(name)] = '\0';
501
bname = strrchr(name, '/');
505
dname = dirname(name);
506
if (ignore_entry(bname, dname)) {
507
DBG("ignoring %s\n", bname);
508
ret = FTW_SKIP_SUBTREE;
513
#endif /* FTW_SKIP_SUBTREE */
516
DBG("processing entry %s\n", path);
518
switch ((S_IFMT & sb->st_mode)) {
520
ret = process_dir(path, sb);
523
ret = process_file(path, sb);
526
ret = process_symlink(path, sb);
529
ret = process_other(path, sb);
539
display_counts(FILE *fp)
542
fprintf(fp, "Entry counts\n");
543
fprintf(fp, "Regular files : %lld\n", count_file);
544
fprintf(fp, "Directories : %lld\n", count_dir);
545
fprintf(fp, "Symbolic links : %lld\n", count_symlink);
546
fprintf(fp, "Other : %lld\n", count_other);
547
fprintf(fp, "Total : %lld\n",
548
(count_file + count_dir + count_symlink + count_other));
554
display_checksums(FILE *fp)
557
fprintf(fp, "Checksums\n");
558
fprintf(fp, "Regular files : %llx%llx\n", checksum_file1, checksum_file2);
559
fprintf(fp, "Directories : %llx\n", checksum_dir);
560
fprintf(fp, "Symbolic links : %llx\n", checksum_symlink);
561
fprintf(fp, "Other : %llx\n", checksum_other);
562
fprintf(fp, "Total : %llx\n",
563
(checksum_file1 ^ checksum_file2 ^ checksum_dir ^ checksum_symlink ^
570
display_metadata(FILE *fp)
573
fprintf(fp, "Metadata checksums\n");
574
fprintf(fp, "Regular files : %llx\n",
575
(avg_uid_file + 13) * (avg_gid_file + 11) * (avg_mode_file + 7));
576
fprintf(fp, "Directories : %llx\n",
577
(avg_uid_dir + 13) * (avg_gid_dir + 11) * (avg_mode_dir + 7));
578
fprintf(fp, "Symbolic links : %llx\n",
579
(avg_uid_symlink + 13) * (avg_gid_symlink + 11) *
580
(avg_mode_symlink + 7));
581
fprintf(fp, "Other : %llx\n",
582
(avg_uid_other + 13) * (avg_gid_other + 11) * (avg_mode_other + 7));
588
display_stats(FILE *fp)
592
display_metadata(fp);
594
display_checksums(fp);
600
main(int argc, char *argv[])
605
ret = argp_parse(&argp, argc, argv, 0, 0, NULL);
607
fprintf(stderr, "parsing arguments failed\n");
611
/* Use FTW_ACTIONRETVAL to take decision on what to do depending upon */
612
/* the return value of the callback function */
613
/* (process_entry in this case) */
614
ret = nftw(arequal_config.test_directory, process_entry, 30,
615
FTW_ACTIONRETVAL | FTW_PHYS | FTW_MOUNT);
617
fprintf(stderr, "ftw (%s) returned %d (%s), terminating\n", argv[1],
618
ret, strerror(errno));
622
display_stats(stdout);
624
if (arequal_config.ignored_directory) {
625
for (i = 0; i < arequal_config.directories_ignored; i++) {
626
if (arequal_config.ignored_directory[i])
627
free(arequal_config.ignored_directory[i]);
629
free(arequal_config.ignored_directory);