summaryrefslogtreecommitdiff
path: root/metastore.c
diff options
context:
space:
mode:
Diffstat (limited to 'metastore.c')
-rw-r--r--metastore.c121
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: