From 2d6671c648942d46522fc53c5cff2142d4572eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=A4rdeman?= Date: Thu, 20 Mar 2008 14:25:47 +0100 Subject: First stab at supporting the option of automatically creating empty directories which are missing. This should help with Debian BR #460998 --- metaentry.c | 12 +++-- metaentry.h | 19 ++++++- metastore.1 | 6 +++ metastore.c | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 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 + /* 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: -- cgit v1.2.1