Make all license notices in source files formatted the same way.
[metastore.git] / metastore.c
blob32fec90af83171fd2e9b8144b2f31ad25b1481f6
1 /*
2 * Main functions of the program.
4 * Copyright (C) 2007 David Härdeman <david@hardeman.nu>
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation version 2 of the License.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #define _BSD_SOURCE
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <getopt.h>
24 #include <utime.h>
25 #include <sys/xattr.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <errno.h>
31 #include "metastore.h"
32 #include "settings.h"
33 #include "utils.h"
34 #include "metaentry.h"
36 /* metastore settings */
37 static struct metasettings settings = {
38 .metafile = METAFILE,
39 .do_mtime = false,
40 .do_emptydirs = false,
41 .do_removeemptydirs = false,
42 .do_git = false,
45 /* Used to create lists of dirs / other files which are missing in the fs */
46 static struct metaentry *missingdirs = NULL;
47 static struct metaentry *missingothers = NULL;
49 /* Used to create lists of dirs / other files which are missing in metadata */
50 static struct metaentry *extradirs = NULL;
53 * Inserts an entry in a linked list ordered by pathlen
55 static void
56 insert_entry_plist(struct metaentry **list, struct metaentry *entry)
58 struct metaentry **parent;
60 for (parent = list; *parent; parent = &((*parent)->list)) {
61 if ((*parent)->pathlen > entry->pathlen)
62 break;
65 entry->list = *parent;
66 *parent = entry;
70 * Inserts an entry in a linked list ordered by pathlen descendingly
72 static void
73 insert_entry_pdlist(struct metaentry **list, struct metaentry *entry)
75 struct metaentry **parent;
77 for (parent = list; *parent; parent = &((*parent)->list)) {
78 if ((*parent)->pathlen < entry->pathlen)
79 break;
82 entry->list = *parent;
83 *parent = entry;
87 * Prints differences between real and stored actual metadata
88 * - for use in mentries_compare
90 static void
91 compare_print(struct metaentry *real, struct metaentry *stored, int cmp)
93 if (!real && !stored) {
94 msg(MSG_ERROR, "%s called with incorrect arguments\n", __FUNCTION__);
95 return;
98 if (cmp == DIFF_NONE) {
99 msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
100 return;
103 msg(MSG_QUIET, "%s:\t", real ? real->path : stored->path);
105 if (cmp & DIFF_ADDED)
106 msg(MSG_QUIET, "added ", real->path);
107 if (cmp & DIFF_DELE)
108 msg(MSG_QUIET, "removed ", stored->path);
109 if (cmp & DIFF_OWNER)
110 msg(MSG_QUIET, "owner ");
111 if (cmp & DIFF_GROUP)
112 msg(MSG_QUIET, "group ");
113 if (cmp & DIFF_MODE)
114 msg(MSG_QUIET, "mode ");
115 if (cmp & DIFF_TYPE)
116 msg(MSG_QUIET, "type ");
117 if (cmp & DIFF_MTIME)
118 msg(MSG_QUIET, "mtime ");
119 if (cmp & DIFF_XATTR)
120 msg(MSG_QUIET, "xattr ");
121 msg(MSG_QUIET, "\n");
125 * Tries to change the real metadata to match the stored one
126 * - for use in mentries_compare
128 static void
129 compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
131 struct group *group;
132 struct passwd *owner;
133 gid_t gid = -1;
134 uid_t uid = -1;
135 struct utimbuf tbuf;
136 unsigned i;
138 if (!real && !stored) {
139 msg(MSG_ERROR, "%s called with incorrect arguments\n",
140 __FUNCTION__);
141 return;
144 if (!real) {
145 if (S_ISDIR(stored->mode))
146 insert_entry_plist(&missingdirs, stored);
147 else
148 insert_entry_plist(&missingothers, stored);
150 msg(MSG_NORMAL, "%s:\tremoved\n", stored->path);
151 return;
154 if (!stored) {
155 if (S_ISDIR(real->mode))
156 insert_entry_pdlist(&extradirs, real);
157 msg(MSG_NORMAL, "%s:\tadded\n", real->path);
158 return;
161 if (cmp == DIFF_NONE) {
162 msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
163 return;
166 if (cmp & DIFF_TYPE) {
167 msg(MSG_NORMAL, "%s:\tnew type, will not change metadata\n",
168 real->path);
169 return;
172 msg(MSG_QUIET, "%s:\tchanging metadata\n", real->path);
174 while (cmp & (DIFF_OWNER | DIFF_GROUP)) {
175 if (cmp & DIFF_OWNER) {
176 msg(MSG_NORMAL, "%s:\tchanging owner from %s to %s\n",
177 real->path, real->group, stored->group);
178 owner = xgetpwnam(stored->owner);
179 if (!owner) {
180 msg(MSG_DEBUG, "\tgetpwnam failed: %s\n",
181 strerror(errno));
182 break;
184 uid = owner->pw_uid;
187 if (cmp & DIFF_GROUP) {
188 msg(MSG_NORMAL, "%s:\tchanging group from %s to %s\n",
189 real->path, real->group, stored->group);
190 group = xgetgrnam(stored->group);
191 if (!group) {
192 msg(MSG_DEBUG, "\tgetgrnam failed: %s\n",
193 strerror(errno));
194 break;
196 gid = group->gr_gid;
199 if (lchown(real->path, uid, gid)) {
200 msg(MSG_DEBUG, "\tlchown failed: %s\n",
201 strerror(errno));
202 break;
204 break;
207 if (cmp & DIFF_MODE) {
208 msg(MSG_NORMAL, "%s:\tchanging mode from 0%o to 0%o\n",
209 real->path, real->mode & 07777, stored->mode & 07777);
210 if (chmod(real->path, stored->mode & 07777))
211 msg(MSG_DEBUG, "\tchmod failed: %s\n", strerror(errno));
214 /* FIXME: Use utimensat here, or even better - lutimensat */
215 if ((cmp & DIFF_MTIME) && S_ISLNK(real->mode)) {
216 msg(MSG_NORMAL, "%s:\tsymlink, not changing mtime\n", real->path);
217 } else if (cmp & DIFF_MTIME) {
218 msg(MSG_NORMAL, "%s:\tchanging mtime from %ld to %ld\n",
219 real->path, real->mtime, stored->mtime);
220 tbuf.actime = stored->mtime;
221 tbuf.modtime = stored->mtime;
222 if (utime(real->path, &tbuf)) {
223 msg(MSG_DEBUG, "\tutime failed: %s\n", strerror(errno));
224 return;
228 if (cmp & DIFF_XATTR) {
229 for (i = 0; i < real->xattrs; i++) {
230 /* Any attrs to remove? */
231 if (mentry_find_xattr(stored, real, i) >= 0)
232 continue;
234 msg(MSG_NORMAL, "%s:\tremoving xattr %s\n",
235 real->path, real->xattr_names[i]);
236 if (lremovexattr(real->path, real->xattr_names[i]))
237 msg(MSG_DEBUG, "\tlremovexattr failed: %s\n",
238 strerror(errno));
241 for (i = 0; i < stored->xattrs; i++) {
242 /* Any xattrs to add? (on change they are removed above) */
243 if (mentry_find_xattr(real, stored, i) >= 0)
244 continue;
246 msg(MSG_NORMAL, "%s:\tadding xattr %s\n",
247 stored->path, stored->xattr_names[i]);
248 if (lsetxattr(stored->path, stored->xattr_names[i],
249 stored->xattr_values[i],
250 stored->xattr_lvalues[i], XATTR_CREATE))
251 msg(MSG_DEBUG, "\tlsetxattr failed: %s\n",
252 strerror(errno));
258 * Tries to fix any empty dirs which are missing from the filesystem by
259 * recreating them.
261 static void
262 fixup_emptydirs(void)
264 struct metaentry *entry;
265 struct metaentry *cur;
266 struct metaentry **parent;
267 char *bpath;
268 char *delim;
269 size_t blen;
270 struct metaentry *new;
272 if (!missingdirs)
273 return;
274 msg(MSG_DEBUG, "\nAttempting to recreate missing dirs\n");
276 /* If directory x/y is missing, but file x/y/z is also missing,
277 * we should prune directory x/y from the list of directories to
278 * recreate since the deletition of x/y is likely to be genuine
279 * (as opposed to empty dir pruning like git/cvs does).
281 * Also, if file x/y/z is missing, any child directories of
282 * x/y should be pruned as they are probably also intentionally
283 * removed.
286 msg(MSG_DEBUG, "List of candidate dirs:\n");
287 for (cur = missingdirs; cur; cur = cur->list)
288 msg(MSG_DEBUG, " %s\n", cur->path);
290 for (entry = missingothers; entry; entry = entry->list) {
291 msg(MSG_DEBUG, "Pruning using file %s\n", entry->path);
292 bpath = xstrdup(entry->path);
293 delim = strrchr(bpath, '/');
294 if (!delim) {
295 msg(MSG_NORMAL, "No delimiter found in %s\n", bpath);
296 free(bpath);
297 continue;
299 *delim = '\0';
301 parent = &missingdirs;
302 for (cur = *parent; cur; cur = cur->list) {
303 if (strcmp(cur->path, bpath)) {
304 parent = &cur->list;
305 continue;
308 msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path);
309 *parent = cur->list;
312 /* Now also prune subdirs of the base dir */
313 *delim++ = '/';
314 *delim = '\0';
315 blen = strlen(bpath);
317 parent = &missingdirs;
318 for (cur = *parent; cur; cur = cur->list) {
319 if (strncmp(cur->path, bpath, blen)) {
320 parent = &cur->list;
321 continue;
324 msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path);
325 *parent = cur->list;
328 free(bpath);
330 msg(MSG_DEBUG, "\n");
332 for (cur = missingdirs; cur; cur = cur->list) {
333 msg(MSG_QUIET, "%s:\trecreating...", cur->path);
334 if (mkdir(cur->path, cur->mode)) {
335 msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
336 continue;
338 msg(MSG_QUIET, "ok\n");
340 new = mentry_create(cur->path);
341 if (!new) {
342 msg(MSG_QUIET, "Failed to get metadata for %s\n");
343 continue;
346 compare_fix(new, cur, mentry_compare(new, cur, &settings));
351 * Deletes any empty dirs present in the filesystem that are missing
352 * from the metadata.
353 * An "empty" dir is one which either:
354 * - is empty; or
355 * - only contains empty dirs
357 static void
358 fixup_newemptydirs(void)
360 struct metaentry **cur;
361 int removed_dirs = 1;
363 if (!extradirs)
364 return;
366 /* This is a simpleminded algorithm that attempts to rmdir() all
367 * directories discovered missing from the metadata. Naturally, this will
368 * succeed only on the truly empty directories, but depending on the order,
369 * it may mean that parent directory removal are attempted to be removed
370 * *before* the children. To circumvent this, keep looping around all the
371 * directories until none have been successfully removed. This is a
372 * O(N**2) algorithm, so don't try to remove too many nested directories
373 * at once (e.g. thousands).
375 * Note that this will succeed only if each parent directory is writable.
377 while (removed_dirs) {
378 removed_dirs = 0;
379 msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n");
380 for (cur = &extradirs; *cur;) {
381 msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path);
382 if (rmdir((*cur)->path)) {
383 msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
384 cur = &(*cur)->list;
385 continue;
387 /* No freeing, because OS will do the job at the end. */
388 *cur = (*cur)->list;
389 removed_dirs++;
390 msg(MSG_QUIET, "ok\n");
395 /* Prints usage message and exits */
396 static void
397 usage(const char *arg0, const char *message)
399 if (message)
400 msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message);
401 msg(MSG_CRITICAL,
402 "Usage: %s ACTION [OPTION...] [PATH...]\n",
403 arg0);
404 msg(MSG_CRITICAL,
405 "\n"
406 "Where ACTION is one of:\n"
407 " -c, --compare Show differences between stored and real metadata\n"
408 " -s, --save Save current metadata\n"
409 " -a, --apply Apply stored metadata\n"
410 " -h, --help Help message (this text)\n"
411 "\n"
412 "Valid OPTIONS are:\n"
413 " -v, --verbose Print more verbose messages\n"
414 " -q, --quiet Print less verbose messages\n"
415 " -m, --mtime Also take mtime into account for diff or apply\n"
416 " -e, --empty-dirs Recreate missing empty directories\n"
417 " -E, --remove-empty-dirs Remove extra empty directories\n"
418 " -g, --git Do not omit .git directories\n"
419 " -f, --file=FILE Set metadata file to FILE\n"
422 exit(message ? EXIT_FAILURE : EXIT_SUCCESS);
425 /* Options */
426 static struct option long_options[] = {
427 {"compare", 0, 0, 0},
428 {"save", 0, 0, 0},
429 {"apply", 0, 0, 0},
430 {"help", 0, 0, 0},
431 {"verbose", 0, 0, 0},
432 {"quiet", 0, 0, 0},
433 {"mtime", 0, 0, 0},
434 {"empty-dirs", 0, 0, 0},
435 {"remove-empty-dirs", 0, 0, 0},
436 {"git", 0, 0, 0},
437 {"file", required_argument, 0, 0},
438 {0, 0, 0, 0}
441 /* Main function */
443 main(int argc, char **argv)
445 int i, c;
446 struct metahash *real = NULL;
447 struct metahash *stored = NULL;
448 int action = 0;
450 /* Parse options */
451 i = 0;
452 while (1) {
453 int option_index = 0;
454 c = getopt_long(argc, argv, "csahvqmeEgf:",
455 long_options, &option_index);
456 if (c == -1)
457 break;
458 switch (c) {
459 case 0:
460 if (!strcmp("verbose",
461 long_options[option_index].name)) {
462 adjust_verbosity(1);
463 } else if (!strcmp("quiet",
464 long_options[option_index].name)) {
465 adjust_verbosity(-1);
466 } else if (!strcmp("mtime",
467 long_options[option_index].name)) {
468 settings.do_mtime = true;
469 } else if (!strcmp("empty-dirs",
470 long_options[option_index].name)) {
471 settings.do_emptydirs = true;
472 } else if (!strcmp("remove-empty-dirs",
473 long_options[option_index].name)) {
474 settings.do_removeemptydirs = true;
475 } else if (!strcmp("git",
476 long_options[option_index].name)) {
477 settings.do_git = true;
478 } else if (!strcmp("file",
479 long_options[option_index].name)) {
480 settings.metafile = optarg;
481 } else {
482 action |= (1 << option_index);
483 i++;
485 break;
486 case 'c':
487 action |= ACTION_DIFF;
488 i++;
489 break;
490 case 's':
491 action |= ACTION_SAVE;
492 i++;
493 break;
494 case 'a':
495 action |= ACTION_APPLY;
496 i++;
497 break;
498 case 'h':
499 action |= ACTION_HELP;
500 i++;
501 break;
502 case 'v':
503 adjust_verbosity(1);
504 break;
505 case 'q':
506 adjust_verbosity(-1);
507 break;
508 case 'm':
509 settings.do_mtime = true;
510 break;
511 case 'e':
512 settings.do_emptydirs = true;
513 break;
514 case 'E':
515 settings.do_removeemptydirs = true;
516 break;
517 case 'g':
518 settings.do_git = true;
519 break;
520 case 'f':
521 settings.metafile = optarg;
522 break;
523 default:
524 usage(argv[0], "unknown option");
528 /* Make sure only one action is specified */
529 if (i != 1)
530 usage(argv[0], "incorrect option(s)");
532 /* Make sure --empty-dirs is only used with apply */
533 if (settings.do_emptydirs && action != ACTION_APPLY)
534 usage(argv[0], "--empty-dirs is only valid with --apply");
536 /* Make sure --remove-empty-dirs is only used with apply */
537 if (settings.do_removeemptydirs && action != ACTION_APPLY)
538 usage(argv[0], "--remove-empty-dirs is only valid with --apply");
540 /* Perform action */
541 switch (action) {
542 case ACTION_DIFF:
543 mentries_fromfile(&stored, settings.metafile);
544 if (!stored) {
545 msg(MSG_CRITICAL, "Failed to load metadata from %s\n",
546 settings.metafile);
547 exit(EXIT_FAILURE);
550 if (optind < argc) {
551 while (optind < argc)
552 mentries_recurse_path(argv[optind++], &real, &settings);
553 } else {
554 mentries_recurse_path(".", &real, &settings);
557 if (!real) {
558 msg(MSG_CRITICAL,
559 "Failed to load metadata from file system\n");
560 exit(EXIT_FAILURE);
563 mentries_compare(real, stored, compare_print, &settings);
564 break;
566 case ACTION_SAVE:
567 if (optind < argc) {
568 while (optind < argc)
569 mentries_recurse_path(argv[optind++], &real, &settings);
570 } else {
571 mentries_recurse_path(".", &real, &settings);
574 if (!real) {
575 msg(MSG_CRITICAL,
576 "Failed to load metadata from file system\n");
577 exit(EXIT_FAILURE);
580 mentries_tofile(real, settings.metafile);
581 break;
583 case ACTION_APPLY:
584 mentries_fromfile(&stored, settings.metafile);
585 if (!stored) {
586 msg(MSG_CRITICAL, "Failed to load metadata from %s\n",
587 settings.metafile);
588 exit(EXIT_FAILURE);
591 if (optind < argc) {
592 while (optind < argc)
593 mentries_recurse_path(argv[optind++], &real, &settings);
594 } else {
595 mentries_recurse_path(".", &real, &settings);
598 if (!real) {
599 msg(MSG_CRITICAL,
600 "Failed to load metadata from file system\n");
601 exit(EXIT_FAILURE);
604 mentries_compare(real, stored, compare_fix, &settings);
606 if (settings.do_emptydirs)
607 fixup_emptydirs();
608 if (settings.do_removeemptydirs)
609 fixup_newemptydirs();
610 break;
612 case ACTION_HELP:
613 usage(argv[0], NULL);
616 exit(EXIT_SUCCESS);