Add debug output for compare action
[metastore.git] / metastore.c
blobdb9589657d0bc4e9d1b73ac416bad25bcff6d603
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <unistd.h>
7 #include <attr/xattr.h>
8 #include <string.h>
9 #include <pwd.h>
10 #include <grp.h>
11 #include <limits.h>
12 #include <dirent.h>
13 #include <sys/mman.h>
14 #include <fcntl.h>
15 #include <stdint.h>
16 #include <getopt.h>
17 #include <stdarg.h>
18 #include <utime.h>
20 #include "metastore.h"
21 #include "utils.h"
23 int verbosity = 0;
24 int do_mtime = 0;
26 int
27 msg(int level, const char *fmt, ...)
29 int ret;
30 va_list ap;
32 if (level > verbosity)
33 return 0;
35 va_start(ap, fmt);
36 ret = vfprintf(stderr, fmt, ap);
37 va_end(ap);
38 return ret;
41 void
42 mentry_free(struct metaentry *m)
44 int i;
46 if (!m)
47 return;
49 free(m->path);
50 free(m->owner);
51 free(m->group);
53 for (i = 0; i < m->xattrs; i++) {
54 free(m->xattr_names[i]);
55 free(m->xattr_values[i]);
58 free(m->xattr_names);
59 free(m->xattr_values);
60 free(m->xattr_lvalues);
62 free(m);
65 struct metaentry *
66 mentry_alloc()
68 struct metaentry *mentry;
69 mentry = xmalloc(sizeof(struct metaentry));
70 memset(mentry, 0, sizeof(struct metaentry));
71 return mentry;
74 void
75 mentry_insert(struct metaentry *mentry, struct metaentry **mhead)
77 struct metaentry *prev;
78 struct metaentry *curr;
79 int comp;
81 if (!(*mhead)) {
82 *mhead = mentry;
83 return;
86 if (strcmp(mentry->path, (*mhead)->path) < 0) {
87 mentry->next = *mhead;
88 *mhead = mentry;
89 return;
92 prev = *mhead;
93 for (curr = prev->next; curr; curr = curr->next) {
94 comp = strcmp(mentry->path, curr->path);
95 if (!comp)
96 /* Two matching paths */
97 return;
98 if (comp < 0)
99 break;
100 prev = curr;
103 if (curr)
104 mentry->next = curr;
105 prev->next = mentry;
108 void
109 mentry_print(const struct metaentry *mentry)
111 int i;
113 if (!mentry || !mentry->path) {
114 fprintf(stderr, "Incorrect meta entry passed to printmetaentry\n");
115 return;
118 printf("===========================\n");
119 printf("Dump of metaentry %p\n", mentry);
120 printf("===========================\n");
122 printf("path\t\t: %s\n", mentry->path);
123 printf("owner\t\t: %s\n", mentry->owner);
124 printf("group\t\t: %s\n", mentry->group);
125 printf("mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
126 printf("mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
127 printf("mode\t\t: %ld\n", (unsigned long)mentry->mode);
128 for (i = 0; i < mentry->xattrs; i++) {
129 printf("xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
130 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
131 printf("\"\n");
134 printf("===========================\n\n");
137 void
138 mentries_print(const struct metaentry *mhead)
140 const struct metaentry *mentry;
141 int i;
143 for (mentry = mhead; mentry; mentry = mentry->next) {
144 i++;
145 mentry_print(mentry);
148 printf("%i entries in total\n", i);
151 struct metaentry *
152 mentry_create(const char *path)
154 ssize_t lsize, vsize;
155 char *list, *attr;
156 struct stat sbuf;
157 struct passwd *pbuf;
158 struct group *gbuf;
159 int i;
160 struct metaentry *mentry;
162 if (lstat(path, &sbuf)) {
163 perror("lstat");
164 return NULL;
167 pbuf = getpwuid(sbuf.st_uid);
168 if (!pbuf) {
169 perror("getpwuid");
170 return NULL;
173 gbuf = getgrgid(sbuf.st_gid);
174 if (!gbuf) {
175 perror("getgrgid");
176 return NULL;
179 mentry = mentry_alloc();
180 mentry->path = xstrdup(path);
181 mentry->owner = xstrdup(pbuf->pw_name);
182 mentry->group = xstrdup(gbuf->gr_name);
183 mentry->mode = sbuf.st_mode & 0177777;
184 mentry->mtime = sbuf.st_mtim.tv_sec;
185 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
187 /* symlinks have no xattrs */
188 if (S_ISLNK(mentry->mode))
189 return mentry;
191 lsize = listxattr(path, NULL, 0);
192 if (lsize < 0) {
193 perror("listxattr");
194 return NULL;
197 list = xmalloc(lsize);
198 lsize = listxattr(path, list, lsize);
199 if (lsize < 0) {
200 perror("listxattr");
201 return NULL;
204 i = 0;
205 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
206 if (*attr == '\0')
207 continue;
208 i++;
211 if (i == 0)
212 return mentry;
214 mentry->xattrs = i;
215 mentry->xattr_names = xmalloc(i * sizeof(char *));
216 mentry->xattr_values = xmalloc(i * sizeof(char *));
217 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
219 i = 0;
220 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
221 if (*attr == '\0')
222 continue;
224 mentry->xattr_names[i] = xstrdup(attr);
225 vsize = getxattr(path, attr, NULL, 0);
226 if (vsize < 0) {
227 perror("getxattr");
228 return NULL;
231 mentry->xattr_lvalues[i] = vsize;
232 mentry->xattr_values[i] = xmalloc(vsize);
234 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
235 if (vsize < 0) {
236 perror("getxattr");
237 return NULL;
239 i++;
242 return mentry;
245 char *
246 normalize_path(const char *orig)
248 char *real = canonicalize_file_name(orig);
249 char cwd[PATH_MAX];
250 char *result;
252 getcwd(cwd, PATH_MAX);
253 if (!real)
254 return NULL;
256 if (!strncmp(real, cwd, strlen(cwd))) {
257 result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
258 result[0] = '\0';
259 strcat(result, ".");
260 strcat(result, real + strlen(cwd));
261 } else {
262 result = xstrdup(real);
265 free(real);
266 return result;
269 void
270 mentries_recurse(const char *opath, struct metaentry **mhead)
272 struct stat sbuf;
273 struct metaentry *mentry;
274 char tpath[PATH_MAX];
275 DIR *dir;
276 struct dirent *dent;
277 char *path = normalize_path(opath);
279 if (!path)
280 return;
282 if (lstat(path, &sbuf)) {
283 printf("Failed to stat %s\n", path);
284 goto out;
287 mentry = mentry_create(path);
288 if (!mentry) {
289 printf("Failed to get metadata for %s\n", path);
290 goto out;
293 mentry_insert(mentry, mhead);
295 if (S_ISDIR(sbuf.st_mode)) {
296 dir = opendir(path);
297 if (!dir) {
298 printf("Failed to open dir %s\n", path);
299 return;
302 while ((dent = readdir(dir))) {
303 if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..") || !strcmp(dent->d_name, ".git"))
304 continue;
305 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
306 tpath[PATH_MAX - 1] = '\0';
307 mentries_recurse(tpath, mhead);
310 closedir(dir);
313 out:
314 free(path);
317 void
318 mentries_tofile(const struct metaentry *mhead, const char *path)
320 FILE *to;
321 const struct metaentry *mentry;
322 int i;
324 to = fopen(path, "w");
325 if (!to) {
326 perror("fopen");
327 exit(EXIT_FAILURE);
330 write_binary_string(SIGNATURE, SIGNATURELEN, to);
331 write_binary_string(VERSION, VERSIONLEN, to);
333 for (mentry = mhead; mentry; mentry = mentry->next) {
334 write_string(mentry->path, to);
335 write_string(mentry->owner, to);
336 write_string(mentry->group, to);
337 write_int((uint64_t)mentry->mtime, 8, to);
338 write_int((uint64_t)mentry->mtimensec, 8, to);
339 write_int((uint64_t)mentry->mode, 2, to);
340 write_int(mentry->xattrs, 4, to);
341 for (i = 0; i < mentry->xattrs; i++) {
342 write_string(mentry->xattr_names[i], to);
343 write_int(mentry->xattr_lvalues[i], 4, to);
344 write_binary_string(mentry->xattr_values[i], mentry->xattr_lvalues[i], to);
348 fclose(to);
351 void
352 mentries_fromfile(struct metaentry **mhead, const char *path)
354 struct metaentry *mentry;
355 char *mmapstart;
356 char *ptr;
357 char *max;
358 int fd;
359 struct stat sbuf;
360 int i;
362 fd = open(path, O_RDONLY);
363 if (fd < 0) {
364 perror("open");
365 exit(EXIT_FAILURE);
368 if (fstat(fd, &sbuf)) {
369 perror("fstat");
370 exit(EXIT_FAILURE);
373 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
374 fprintf(stderr, "Invalid size for file %s\n", path);
375 exit(EXIT_FAILURE);
378 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
379 if (mmapstart == MAP_FAILED) {
380 perror("mmap");
381 exit(EXIT_FAILURE);
383 ptr = mmapstart;
384 max = mmapstart + sbuf.st_size;
386 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
387 printf("Invalid signature for file %s\n", path);
388 goto out;
390 ptr += SIGNATURELEN;
392 if (strncmp(ptr, VERSION, VERSIONLEN)) {
393 printf("Invalid version for file %s\n", path);
394 goto out;
396 ptr += VERSIONLEN;
398 while (ptr < mmapstart + sbuf.st_size) {
399 if (*ptr == '\0') {
400 fprintf(stderr, "Invalid characters in file %s\n", path);
401 goto out;
404 mentry = mentry_alloc();
405 mentry->path = read_string(&ptr, max);
406 mentry->owner = read_string(&ptr, max);
407 mentry->group = read_string(&ptr, max);
408 mentry->mtime = (time_t)read_int(&ptr, 8, max);
409 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
410 mentry->mode = (mode_t)read_int(&ptr, 2, max);
411 mentry->xattrs = (unsigned int)read_int(&ptr, 4, max);
413 if (mentry->xattrs > 0) {
414 mentry->xattr_names = xmalloc(mentry->xattrs * sizeof(char *));
415 mentry->xattr_lvalues = xmalloc(mentry->xattrs * sizeof(int));
416 mentry->xattr_values = xmalloc(mentry->xattrs * sizeof(char *));
418 for (i = 0; i < mentry->xattrs; i++) {
419 mentry->xattr_names[i] = read_string(&ptr, max);
420 mentry->xattr_lvalues[i] = (int)read_int(&ptr, 4, max);
421 mentry->xattr_values[i] = read_binary_string(&ptr, mentry->xattr_lvalues[i], max);
424 mentry_insert(mentry, mhead);
427 out:
428 munmap(mmapstart, sbuf.st_size);
429 close(fd);
432 struct metaentry *
433 mentry_find(const char *path, struct metaentry *mhead)
435 struct metaentry *m;
437 /* FIXME - We can do a bisect search here instead */
438 for (m = mhead; m; m = m->next) {
439 if (!strcmp(path, m->path))
440 return m;
442 return NULL;
445 /* Returns xattr index in haystack which corresponds to xattr n in needle */
447 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle, int n)
449 int i;
451 for (i = 0; i < haystack->xattrs; i++) {
452 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
453 continue;
454 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
455 return -1;
456 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n], needle->xattr_lvalues[n]))
457 return -1;
458 return i;
460 return -1;
463 /* Returns zero if all xattrs in left and right match */
465 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
467 int i;
469 if (left->xattrs != right->xattrs)
470 return 1;
472 /* Make sure all xattrs in left are found in right and vice versa */
473 for (i = 0; i < left->xattrs; i++) {
474 if (mentry_find_xattr(right, left, i) < 0 ||
475 mentry_find_xattr(left, right, i) < 0) {
476 return 1;
480 return 0;
484 mentry_compare(struct metaentry *left, struct metaentry *right)
486 int retval = DIFF_NONE;
488 if (!left || !right) {
489 fprintf(stderr, "mentry_compare called with empty arguments\n");
490 return -1;
493 if (strcmp(left->path, right->path))
494 return -1;
496 if (strcmp(left->owner, right->owner))
497 retval |= DIFF_OWNER;
499 if (strcmp(left->group, right->group))
500 retval |= DIFF_GROUP;
502 if ((left->mode & 07777) != (right->mode & 07777))
503 retval |= DIFF_MODE;
505 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
506 retval |= DIFF_TYPE;
508 if (do_mtime && strcmp(left->path, METAFILE) &&
509 (left->mtime != right->mtime || left->mtimensec != right->mtimensec))
510 retval |= DIFF_MTIME;
512 if (mentry_compare_xattr(left, right)) {
513 retval |= DIFF_XATTR;
514 return retval;
517 return retval;
520 void
521 compare_print(struct metaentry *left, struct metaentry *right, int cmp)
523 if (!left) {
524 printf("Path %s: removed\n", right->path);
525 return;
528 if (!right) {
529 printf("Path %s: added\n", left->path);
530 return;
533 if (cmp == DIFF_NONE) {
534 msg(MSG_DEBUG, "Path %s: no difference\n", left->path);
535 return;
538 printf("Path %s: ", left->path);
539 if (cmp & DIFF_OWNER)
540 printf("owner ");
541 if (cmp & DIFF_GROUP)
542 printf("group ");
543 if (cmp & DIFF_MODE)
544 printf("mode ");
545 if (cmp & DIFF_TYPE)
546 printf("type ");
547 if (cmp & DIFF_MTIME)
548 printf("mtime ");
549 if (cmp & DIFF_XATTR)
550 printf("xattr ");
551 printf("\n");
554 void
555 compare_fix(struct metaentry *left, struct metaentry *right, int cmp)
557 struct group *group;
558 struct passwd *owner;
559 gid_t gid = -1;
560 uid_t uid = -1;
561 struct utimbuf tbuf;
562 int i;
564 if (!left && !right) {
565 printf("%s called with incorrect arguments\n", __FUNCTION__);
566 return;
569 if (!left) {
570 printf("Path %s: removed\n", right->path);
571 return;
574 if (!right) {
575 printf("Path %s: added\n", left->path);
576 return;
579 if (cmp == DIFF_NONE) {
580 msg(MSG_DEBUG, "Path %s: no difference\n", left->path);
581 return;
584 if (cmp & DIFF_TYPE) {
585 printf("Path %s: new type, will not change metadata\n", left->path);
586 return;
589 if (cmp & (DIFF_OWNER | DIFF_GROUP)) {
590 if (cmp & DIFF_OWNER) {
591 printf("Path %s: fixing owner from %s to %s\n", left->path, left->group, right->group);
592 owner = getpwnam(right->owner);
593 if (!owner) {
594 perror("getpwnam");
595 return;
597 uid = owner->pw_uid;
600 if (cmp & DIFF_GROUP) {
601 printf("Path %s: fixing group from %s to %s\n", left->path, left->group, right->group);
602 group = getgrnam(right->group);
603 if (!group) {
604 perror("getgrnam");
605 return;
607 gid = group->gr_gid;
610 if (lchown(left->path, uid, gid)) {
611 perror("lchown");
612 return;
614 printf("Success\n");
617 if (cmp & DIFF_MODE) {
618 printf("Path %s: fixing mode from 0%o to 0%o\n", left->path, left->mode, right->mode);
619 if (chmod(left->path, left->mode)) {
620 perror("chmod");
621 return;
625 if (cmp & DIFF_MTIME) {
626 printf("Path %s: fixing mtime %ld to %ld\n", left->path, left->mtime, right->mtime);
627 /* FIXME: Use utimensat here */
628 tbuf.actime = right->mtime;
629 tbuf.modtime = right->mtime;
630 if (utime(left->path, &tbuf)) {
631 perror("utime");
632 return;
636 if (cmp & DIFF_XATTR) {
637 for (i = 0; i < left->xattrs; i++) {
638 /* Any attrs to remove? */
639 if (mentry_find_xattr(right, left, i) >= 0)
640 continue;
642 msg(MSG_NORMAL, "Path %s: removing xattr %s\n",
643 left->path, left->xattr_names[i]);
644 if (lremovexattr(left->path, left->xattr_names[i]))
645 perror("lremovexattr");
648 for (i = 0; i < right->xattrs; i++) {
649 /* Any xattrs to add? (on change they are removed above) */
650 if (mentry_find_xattr(left, right, i) >= 0)
651 continue;
653 msg(MSG_NORMAL, "Path %s: adding xattr %s\n",
654 right->path, right->xattr_names[i]);
655 if (lsetxattr(right->path, right->xattr_names[i],
656 right->xattr_values[i], right->xattr_lvalues[i], XATTR_CREATE))
657 perror("lsetxattr");
662 void
663 mentries_compare(struct metaentry *mheadleft,
664 struct metaentry *mheadright,
665 void (*printfunc)(struct metaentry *, struct metaentry *, int))
667 struct metaentry *left, *right;
668 int cmp;
670 if (!mheadleft || !mheadright) {
671 fprintf(stderr, "mentries_compare called with empty list\n");
672 return;
675 for (left = mheadleft; left; left = left->next) {
676 right = mentry_find(left->path, mheadright);
677 if (!right)
678 cmp = DIFF_ADDED;
679 else
680 cmp = mentry_compare(left, right);
681 printfunc(left, right, cmp);
684 for (right = mheadright; right; right = right->next) {
685 left = mentry_find(right->path, mheadleft);
686 if (!left)
687 printfunc(left, right, DIFF_DELE);
691 void
692 usage(const char *arg0, const char *msg)
694 if (msg)
695 fprintf(stderr, "%s: %s\n\n", arg0, msg);
696 fprintf(stderr, "Usage: %s ACTION [OPTIONS] [PATH]...\n\n", arg0);
697 fprintf(stderr, "Where ACTION is one of:\n"
698 " -d, --diff\tShow differences between stored and actual metadata\n"
699 " -s, --save\tSave current metadata\n"
700 " -a, --apply\tApply stored metadata\n"
701 " -h, --help\tHelp message (this text)\n\n"
702 "Valid OPTIONS are (can be given more than once):\n"
703 " -v, --verbose\tPrint more verbose messages\n"
704 " -q, --quiet\tPrint less verbose messages\n");
706 if (msg)
707 exit(EXIT_FAILURE);
708 exit(EXIT_SUCCESS);
711 static struct option long_options[] = {
712 {"compare", 0, 0, 0},
713 {"save", 0, 0, 0},
714 {"apply", 0, 0, 0},
715 {"help", 0, 0, 0},
716 {"verbose", 0, 0, 0},
717 {"quiet", 0, 0, 0},
718 {"mtime", 0, 0, 0},
719 {0, 0, 0, 0}
723 main(int argc, char **argv, char **envp)
725 int i, c;
726 struct metaentry *mhead = NULL;
727 struct metaentry *mfhead = NULL;
728 int action = 0;
730 i = 0;
731 while (1) {
732 int option_index = 0;
733 c = getopt_long(argc, argv, "csahvqm", long_options, &option_index);
734 if (c == -1)
735 break;
736 switch (c) {
737 case 0:
738 if (!strcmp("verbose", long_options[option_index].name)) {
739 verbosity++;
740 } else if (!strcmp("quiet", long_options[option_index].name)) {
741 verbosity--;
742 } else if (!strcmp("mtime", long_options[option_index].name)) {
743 do_mtime = 1;
744 } else {
745 action |= (1 << option_index);
746 i++;
748 break;
749 case 'c':
750 action |= ACTION_DIFF;
751 i++;
752 break;
753 case 's':
754 action |= ACTION_SAVE;
755 i++;
756 break;
757 case 'a':
758 action |= ACTION_APPLY;
759 i++;
760 break;
761 case 'h':
762 action |= ACTION_HELP;
763 i++;
764 break;
765 case 'v':
766 verbosity++;
767 break;
768 case 'q':
769 verbosity--;
770 break;
771 case 'm':
772 do_mtime = 1;
773 break;
774 default:
775 usage(argv[0], "unknown option");
779 if (i != 1)
780 usage(argv[0], "incorrect option(s)");
782 switch (action) {
783 case ACTION_DIFF:
784 mentries_fromfile(&mfhead, METAFILE);
785 if (!mfhead) {
786 fprintf(stderr, "Failed to load metadata from file\n");
787 exit(EXIT_FAILURE);
790 if (optind < argc)
791 while (optind < argc)
792 mentries_recurse(argv[optind++], &mhead);
793 else
794 mentries_recurse(".", &mhead);
795 if (!mhead) {
796 fprintf(stderr, "Failed to load metadata from fs\n");
797 exit(EXIT_FAILURE);
799 mentries_compare(mhead, mfhead, compare_print);
800 break;
801 case ACTION_SAVE:
802 if (optind < argc)
803 while (optind < argc)
804 mentries_recurse(argv[optind++], &mhead);
805 else
806 mentries_recurse(".", &mhead);
807 if (!mhead) {
808 fprintf(stderr, "Failed to load metadata from fs\n");
809 exit(EXIT_FAILURE);
811 mentries_tofile(mhead, METAFILE);
812 break;
813 case ACTION_APPLY:
814 mentries_fromfile(&mfhead, METAFILE);
815 if (!mfhead) {
816 fprintf(stderr, "Failed to load metadata from file\n");
817 exit(EXIT_FAILURE);
820 if (optind < argc)
821 while (optind < argc)
822 mentries_recurse(argv[optind++], &mhead);
823 else
824 mentries_recurse(".", &mhead);
825 if (!mhead) {
826 fprintf(stderr, "Failed to load metadata from fs\n");
827 exit(EXIT_FAILURE);
829 mentries_compare(mhead, mfhead, compare_fix);
830 break;
831 case ACTION_HELP:
832 usage(argv[0], NULL);
835 exit(EXIT_SUCCESS);