summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--metaentry.c12
-rw-r--r--metaentry.h19
-rw-r--r--metastore.16
-rw-r--r--metastore.c170
4 files changed, 185 insertions, 22 deletions
diff --git a/metaentry.c b/metaentry.c
index efd8d3a..c06fd3d 100644
--- a/metaentry.c
+++ b/metaentry.c
@@ -175,7 +175,7 @@ mentries_print(const struct metahash *mhash)
#endif
/* Creates a metaentry for the file/dir/etc at path */
-static struct metaentry *
+struct metaentry *
mentry_create(const char *path)
{
ssize_t lsize, vsize;
@@ -208,6 +208,7 @@ mentry_create(const char *path)
mentry = mentry_alloc();
mentry->path = xstrdup(path);
+ mentry->pathlen = strlen(mentry->path);
mentry->owner = xstrdup(pbuf->pw_name);
mentry->group = xstrdup(gbuf->gr_name);
mentry->mode = sbuf.st_mode & 0177777;
@@ -473,6 +474,7 @@ mentries_fromfile(struct metahash **mhash, const char *path)
mentry = mentry_alloc();
mentry->path = read_string(&ptr, max);
+ mentry->pathlen = strlen(mentry->path);
mentry->owner = read_string(&ptr, max);
mentry->group = read_string(&ptr, max);
mentry->mtime = (time_t)read_int(&ptr, 8, max);
@@ -549,8 +551,8 @@ mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
}
/* Compares two metaentries and returns an int with a bitmask of differences */
-static int
-mentry_compare(struct metaentry *left, struct metaentry *right, int do_mtime)
+int
+mentry_compare(struct metaentry *left, struct metaentry *right, bool do_mtime)
{
int retval = DIFF_NONE;
@@ -592,8 +594,8 @@ void
mentries_compare(struct metahash *mhashreal,
struct metahash *mhashstored,
void (*pfunc)
- (struct metaentry *real, struct metaentry *stored, int do_mtime),
- int do_mtime)
+ (struct metaentry *real, struct metaentry *stored, int cmp),
+ bool do_mtime)
{
struct metaentry *real, *stored;
int key;
diff --git a/metaentry.h b/metaentry.h
index 22f061e..fe3351a 100644
--- a/metaentry.h
+++ b/metaentry.h
@@ -18,15 +18,22 @@
*
*/
+#include <stdbool.h>
+
/* Data structure to hold all metadata for a file/dir */
struct metaentry {
- struct metaentry *next;
+ struct metaentry *next; /* For the metahash chains */
+ struct metaentry *list; /* For creating additional lists of entries */
+
char *path;
+ unsigned int pathlen;
+
char *owner;
char *group;
mode_t mode;
time_t mtime;
long mtimensec;
+
unsigned int xattrs;
char **xattr_names;
ssize_t *xattr_lvalues;
@@ -41,6 +48,9 @@ struct metahash {
unsigned int count;
};
+/* Create a metaentry for the file/dir/etc at path */
+struct metaentry *mentry_create(const char *path);
+
/* Recurses opath and adds metadata entries to the metaentry list */
void mentries_recurse_path(const char *opath, struct metahash **mhash);
@@ -65,11 +75,16 @@ int mentry_find_xattr(struct metaentry *haystack,
#define DIFF_ADDED 0x40
#define DIFF_DELE 0x80
+/* Compares two metaentries and returns an int with a bitmask of differences */
+int mentry_compare(struct metaentry *left,
+ struct metaentry *right,
+ bool do_mtime);
+
/* Compares lists of real and stored metadata and calls pfunc for each */
void mentries_compare(struct metahash *mhashreal,
struct metahash *mhashstored,
void (*pfunc)(struct metaentry *real,
struct metaentry *stored,
int cmp),
- int do_mtime);
+ bool do_mtime);
diff --git a/metastore.1 b/metastore.1
index fc72b1e..eeecdc9 100644
--- a/metastore.1
+++ b/metastore.1
@@ -40,6 +40,12 @@ once for even less verbosity.
.TP
.B -m, --mtime
Causes metastore to also take mtime into account for the compare or apply actions.
+.TP
+.B -e, --empty-dirs
+Also attempts to recreate missing empty directories. May be useful where
+empty directories are not tracked (e.g. by git or cvs).
+Only works in combination with the \fBapply\fR option.
+This is currently an experimental feature.
.\"
.SH PATHS
If no path is specified, metastore will use the current directory as the basis
diff --git a/metastore.c b/metastore.c
index 667a450..fdc634d 100644
--- a/metastore.c
+++ b/metastore.c
@@ -31,9 +31,33 @@
#include "utils.h"
#include "metaentry.h"
-/* Used to signal whether mtimes should be corrected */
-static int do_mtime = 0;
+/* Used to indicate whether mtimes should be corrected */
+static bool do_mtime = false;
+/* Used to indicate whether empty dirs should be recreated */
+static bool do_emptydirs = false;
+
+/* Used to create lists of dirs / other files which are missing in the fs */
+static struct metaentry *missingdirs = NULL;
+static struct metaentry *missingothers = NULL;
+
+/*
+ * Inserts an entry in a linked list ordered by pathlen
+ */
+static void
+insert_entry_plist(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
@@ -93,6 +117,11 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
}
if (!real) {
+ if (S_ISDIR(stored->mode))
+ insert_entry_plist(&missingdirs, stored);
+ else
+ insert_entry_plist(&missingothers, stored);
+
msg(MSG_NORMAL, "%s:\tremoved\n", stored->path);
return;
}
@@ -117,7 +146,7 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
while (cmp & (DIFF_OWNER | DIFF_GROUP)) {
if (cmp & DIFF_OWNER) {
- msg(MSG_NORMAL, "\tchanging owner from %s to %s\n",
+ msg(MSG_NORMAL, "%s:\tchanging owner from %s to %s\n",
real->path, real->group, stored->group);
owner = xgetpwnam(stored->owner);
if (!owner) {
@@ -129,7 +158,7 @@ compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
}
if (cmp & DIFF_GROUP) {
- msg(MSG_NORMAL, "\tchanging group from %s to %s\n",
+ msg(MSG_NORMAL, "%s:\tchanging group from %s to %s\n",
real->path, real->group, stored->group);
group = xgetgrnam(stored->group);
if (!group) {
@@ -198,6 +227,101 @@ 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
+ */
+static void
+fixup_emptydirs(struct metahash *real, struct metahash *stored)
+{
+ struct metaentry *entry;
+ struct metaentry *cur;
+ struct metaentry **parent;
+ char *bpath;
+ char *delim;
+ size_t blen;
+ struct metaentry *new;
+
+ if (!missingdirs)
+ return;
+ msg(MSG_DEBUG, "\nAttempting to recreate missing dirs\n");
+
+ /* If directory x/y is missing, but file x/y/z is also missing,
+ * we should prune directory x/y from the list of directories to
+ * recreate since the deletition of x/y is likely to be genuine
+ * (as opposed to empty dir pruning like git/cvs does).
+ *
+ * Also, if file x/y/z is missing, any child directories of
+ * x/y should be pruned as they are probably also intentionally
+ * removed.
+ */
+
+ msg(MSG_DEBUG, "List of candidate dirs:\n");
+ for (cur = missingdirs; cur; cur = cur->list)
+ msg(MSG_DEBUG, " %s\n", cur->path);
+
+ for (entry = missingothers; entry; entry = entry->list) {
+ msg(MSG_DEBUG, "Pruning using file %s\n", entry->path);
+ bpath = xstrdup(entry->path);
+ delim = strrchr(bpath, '/');
+ if (!delim) {
+ msg(MSG_NORMAL, "No delimiter found in %s\n", bpath);
+ free(bpath);
+ continue;
+ }
+ *delim = '\0';
+
+ parent = &missingdirs;
+ for (cur = *parent; cur; cur = cur->list) {
+ if (strcmp(cur->path, bpath)) {
+ parent = &cur->list;
+ continue;
+ }
+
+ msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path);
+ *parent = cur->list;
+ }
+
+ /* Now also prune subdirs of the base dir */
+ *delim++ = '/';
+ *delim = '\0';
+ blen = strlen(bpath);
+
+ parent = &missingdirs;
+ for (cur = *parent; cur; cur = cur->list) {
+ if (strncmp(cur->path, bpath, blen)) {
+ parent = &cur->list;
+ continue;
+ }
+
+ msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path);
+ *parent = cur->list;
+ }
+
+ free(bpath);
+ }
+ msg(MSG_DEBUG, "\n");
+
+ for (cur = missingdirs; cur; cur = cur->list) {
+ msg(MSG_QUIET, "%s:\trecreating...", cur->path);
+ if (mkdir(cur->path, cur->mode)) {
+ msg(MSG_QUIET, "failed (%s)\n", strerror(errno));
+ continue;
+ }
+ msg(MSG_QUIET, "ok\n");
+
+ new = mentry_create(cur->path);
+ if (!new) {
+ msg(MSG_QUIET, "Failed to get metadata for %s\n");
+ continue;
+ }
+
+ compare_fix(new, cur, mentry_compare(new, cur, do_mtime));
+ }
+}
+
/* Prints usage message and exits */
static void
usage(const char *arg0, const char *message)
@@ -206,14 +330,16 @@ usage(const char *arg0, const char *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\tShow differences between stored and real metadata\n"
- " -s, --save\tSave current metadata\n"
- " -a, --apply\tApply stored metadata\n"
- " -h, --help\tHelp message (this text)\n\n"
- "Valid OPTIONS are (can be given more than once):\n"
- " -v, --verbose\tPrint more verbose messages\n"
- " -q, --quiet\tPrint less verbose messages\n"
- " -m, --mtime\tAlso take mtime into account for diff or apply\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"
+ );
exit(message ? EXIT_FAILURE : EXIT_SUCCESS);
}
@@ -227,6 +353,7 @@ static struct option long_options[] = {
{"verbose", 0, 0, 0},
{"quiet", 0, 0, 0},
{"mtime", 0, 0, 0},
+ {"empty-dirs", 0, 0, 0},
{0, 0, 0, 0}
};
@@ -243,7 +370,7 @@ main(int argc, char **argv, char **envp)
i = 0;
while (1) {
int option_index = 0;
- c = getopt_long(argc, argv, "csahvqm",
+ c = getopt_long(argc, argv, "csahvqme",
long_options, &option_index);
if (c == -1)
break;
@@ -257,7 +384,10 @@ main(int argc, char **argv, char **envp)
adjust_verbosity(-1);
} else if (!strcmp("mtime",
long_options[option_index].name)) {
- do_mtime = 1;
+ do_mtime = true;
+ } else if (!strcmp("empty-dirs",
+ long_options[option_index].name)) {
+ do_emptydirs = true;
} else {
action |= (1 << option_index);
i++;
@@ -286,7 +416,10 @@ main(int argc, char **argv, char **envp)
adjust_verbosity(-1);
break;
case 'm':
- do_mtime = 1;
+ do_mtime = true;
+ break;
+ case 'e':
+ do_emptydirs = true;
break;
default:
usage(argv[0], "unknown option");
@@ -297,6 +430,10 @@ main(int argc, char **argv, char **envp)
if (i != 1)
usage(argv[0], "incorrect option(s)");
+ /* Make sure --empty-dirs is only used with apply */
+ if (do_emptydirs && action != ACTION_APPLY)
+ usage(argv[0], "--empty-dirs is only valid with --apply");
+
/* Perform action */
switch (action) {
case ACTION_DIFF:
@@ -362,6 +499,9 @@ main(int argc, char **argv, char **envp)
}
mentries_compare(real, stored, compare_fix, do_mtime);
+
+ if (do_emptydirs)
+ fixup_emptydirs(real, stored);
break;
case ACTION_HELP: