diff options
Diffstat (limited to 'metastore.c')
-rw-r--r-- | metastore.c | 121 |
1 files changed, 103 insertions, 18 deletions
diff --git a/metastore.c b/metastore.c index 83f8555..38901de 100644 --- a/metastore.c +++ b/metastore.c @@ -39,6 +39,7 @@ static struct metasettings settings = { .metafile = METAFILE, .do_mtime = false, .do_emptydirs = false, + .do_removeemptydirs = false, .do_git = false, }; @@ -46,6 +47,9 @@ static struct metasettings settings = { static struct metaentry *missingdirs = NULL; static struct metaentry *missingothers = NULL; +/* Used to create lists of dirs / other files which are missing in metadata */ +static struct metaentry *extradirs = NULL; + /* * Inserts an entry in a linked list ordered by pathlen */ @@ -64,6 +68,23 @@ insert_entry_plist(struct metaentry **list, struct metaentry *entry) } /* + * Inserts an entry in a linked list ordered by pathlen descendingly + */ +static void +insert_entry_pdlist(struct metaentry **list, struct metaentry *entry) +{ + struct metaentry **parent; + + for (parent = list; *parent; parent = &((*parent)->list)) { + if ((*parent)->pathlen < entry->pathlen) + break; + } + + entry->list = *parent; + *parent = entry; +} + +/* * Prints differences between real and stored actual metadata * - for use in mentries_compare */ @@ -132,6 +153,8 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) } if (!stored) { + if (S_ISDIR(real->mode)) + insert_entry_pdlist(&extradirs, real); msg(MSG_NORMAL, "%s:\tadded\n", real->path); return; } @@ -233,10 +256,8 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) } /* - * Tries to fix any empty dirs which are missing by recreating them. - * An "empty" dir is one which either: - * - is empty; or - * - only contained empty dirs + * Tries to fix any empty dirs which are missing from the filesystem by + * recreating them. */ static void fixup_emptydirs(struct metahash *real, struct metahash *stored) @@ -327,25 +348,76 @@ fixup_emptydirs(struct metahash *real, struct metahash *stored) } } +/* + * Deletes any empty dirs present in the filesystem that are missing + * from the metadata. + * An "empty" dir is one which either: + * - is empty; or + * - only contains empty dirs + */ +static void +fixup_newemptydirs(void) +{ + struct metaentry **cur; + int removed_dirs = 1; + + if (!extradirs) + return; + + /* This is a simpleminded algorithm that attempts to rmdir() all + * directories discovered missing from the metadata. Naturally, this will + * succeed only on the truly empty directories, but depending on the order, + * it may mean that parent directory removal are attempted to be removed + * *before* the children. To circumvent this, keep looping around all the + * directories until none have been successfully removed. This is a + * O(N**2) algorithm, so don't try to remove too many nested directories + * at once (e.g. thousands). + * + * Note that this will succeed only if each parent directory is writable. + */ + while (removed_dirs) { + removed_dirs = 0; + msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n"); + for (cur = &extradirs; *cur;) { + msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path); + if (rmdir((*cur)->path)) { + msg(MSG_QUIET, "failed (%s)\n", strerror(errno)); + cur = &(*cur)->list; + continue; + } + /* No freeing, because OS will do the job at the end. */ + *cur = (*cur)->list; + removed_dirs++; + msg(MSG_QUIET, "ok\n"); + } + } +} + /* Prints usage message and exits */ static void usage(const char *arg0, const char *message) { if (message) msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message); - msg(MSG_CRITICAL, "Usage: %s ACTION [OPTION...] [PATH...]\n\n", arg0); - msg(MSG_CRITICAL, "Where ACTION is one of:\n" - " -c, --compare\t\tShow differences between stored and real metadata\n" - " -s, --save\t\tSave current metadata\n" - " -a, --apply\t\tApply stored metadata\n" - " -h, --help\t\tHelp message (this text)\n\n" - "Valid OPTIONS are:\n" - " -v, --verbose\t\tPrint more verbose messages\n" - " -q, --quiet\t\tPrint less verbose messages\n" - " -m, --mtime\t\tAlso take mtime into account for diff or apply\n" - " -e, --empty-dirs\tRecreate missing empty directories (experimental)\n" - " -g, --git\t\tDo not omit .git directories\n" - " -f, --file <file>\tSet metadata file\n" + msg(MSG_CRITICAL, +"Usage: %s ACTION [OPTION...] [PATH...]\n", + arg0); + msg(MSG_CRITICAL, +"\n" +"Where ACTION is one of:\n" +" -c, --compare Show differences between stored and real metadata\n" +" -s, --save Save current metadata\n" +" -a, --apply Apply stored metadata\n" +" -h, --help Help message (this text)\n" +"\n" +"Valid OPTIONS are:\n" +" -v, --verbose Print more verbose messages\n" +" -q, --quiet Print less verbose messages\n" +" -m, --mtime Also take mtime into account for diff or apply\n" +" -e, --empty-dirs Recreate missing empty directories\n" +" -E, --remove-empty-dirs Remove extra empty directories\n" +" -g, --git Do not omit .git directories\n" +" -f, --file=FILE Set metadata file to FILE\n" ); exit(message ? EXIT_FAILURE : EXIT_SUCCESS); @@ -361,6 +433,7 @@ static struct option long_options[] = { {"quiet", 0, 0, 0}, {"mtime", 0, 0, 0}, {"empty-dirs", 0, 0, 0}, + {"remove-empty-dirs", 0, 0, 0}, {"git", 0, 0, 0}, {"file", required_argument, 0, 0}, {0, 0, 0, 0} @@ -379,7 +452,7 @@ main(int argc, char **argv, char **envp) i = 0; while (1) { int option_index = 0; - c = getopt_long(argc, argv, "csahvqmegf:", + c = getopt_long(argc, argv, "csahvqmeEgf:", long_options, &option_index); if (c == -1) break; @@ -397,6 +470,9 @@ main(int argc, char **argv, char **envp) } else if (!strcmp("empty-dirs", long_options[option_index].name)) { settings.do_emptydirs = true; + } else if (!strcmp("remove-empty-dirs", + long_options[option_index].name)) { + settings.do_removeemptydirs = true; } else if (!strcmp("git", long_options[option_index].name)) { settings.do_git = true; @@ -436,6 +512,9 @@ main(int argc, char **argv, char **envp) case 'e': settings.do_emptydirs = true; break; + case 'E': + settings.do_removeemptydirs = true; + break; case 'g': settings.do_git = true; break; @@ -455,6 +534,10 @@ main(int argc, char **argv, char **envp) if (settings.do_emptydirs && action != ACTION_APPLY) usage(argv[0], "--empty-dirs is only valid with --apply"); + /* Make sure --remove-empty-dirs is only used with apply */ + if (settings.do_removeemptydirs && action != ACTION_APPLY) + usage(argv[0], "--remove-empty-dirs is only valid with --apply"); + /* Perform action */ switch (action) { case ACTION_DIFF: @@ -523,6 +606,8 @@ main(int argc, char **argv, char **envp) if (settings.do_emptydirs) fixup_emptydirs(real, stored); + if (settings.do_removeemptydirs) + fixup_newemptydirs(); break; case ACTION_HELP: |