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.
22 #include <sys/types.h>
26 #include <sys/xattr.h>
32 #include "metastore.h"
35 #include "metaentry.h"
37 /* metastore settings */
38 static struct metasettings settings
= {
41 .do_emptydirs
= false,
42 .do_removeemptydirs
= false,
46 /* Used to create lists of dirs / other files which are missing in the fs */
47 static struct metaentry
*missingdirs
= NULL
;
48 static struct metaentry
*missingothers
= NULL
;
50 /* Used to create lists of dirs / other files which are missing in metadata */
51 static struct metaentry
*extradirs
= NULL
;
54 * Inserts an entry in a linked list ordered by pathlen
57 insert_entry_plist(struct metaentry
**list
, struct metaentry
*entry
)
59 struct metaentry
**parent
;
61 for (parent
= list
; *parent
; parent
= &((*parent
)->list
)) {
62 if ((*parent
)->pathlen
> entry
->pathlen
)
66 entry
->list
= *parent
;
71 * Inserts an entry in a linked list ordered by pathlen descendingly
74 insert_entry_pdlist(struct metaentry
**list
, struct metaentry
*entry
)
76 struct metaentry
**parent
;
78 for (parent
= list
; *parent
; parent
= &((*parent
)->list
)) {
79 if ((*parent
)->pathlen
< entry
->pathlen
)
83 entry
->list
= *parent
;
88 * Prints differences between real and stored actual metadata
89 * - for use in mentries_compare
92 compare_print(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
94 if (!real
&& !stored
) {
95 msg(MSG_ERROR
, "%s called with incorrect arguments\n", __FUNCTION__
);
99 if (cmp
== DIFF_NONE
) {
100 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
104 msg(MSG_QUIET
, "%s:\t", real
? real
->path
: stored
->path
);
106 if (cmp
& DIFF_ADDED
)
107 msg(MSG_QUIET
, "added ", real
->path
);
109 msg(MSG_QUIET
, "removed ", stored
->path
);
110 if (cmp
& DIFF_OWNER
)
111 msg(MSG_QUIET
, "owner ");
112 if (cmp
& DIFF_GROUP
)
113 msg(MSG_QUIET
, "group ");
115 msg(MSG_QUIET
, "mode ");
117 msg(MSG_QUIET
, "type ");
118 if (cmp
& DIFF_MTIME
)
119 msg(MSG_QUIET
, "mtime ");
120 if (cmp
& DIFF_XATTR
)
121 msg(MSG_QUIET
, "xattr ");
122 msg(MSG_QUIET
, "\n");
126 * Tries to change the real metadata to match the stored one
127 * - for use in mentries_compare
130 compare_fix(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
133 struct passwd
*owner
;
139 if (!real
&& !stored
) {
140 msg(MSG_ERROR
, "%s called with incorrect arguments\n",
146 if (S_ISDIR(stored
->mode
))
147 insert_entry_plist(&missingdirs
, stored
);
149 insert_entry_plist(&missingothers
, stored
);
151 msg(MSG_NORMAL
, "%s:\tremoved\n", stored
->path
);
156 if (S_ISDIR(real
->mode
))
157 insert_entry_pdlist(&extradirs
, real
);
158 msg(MSG_NORMAL
, "%s:\tadded\n", real
->path
);
162 if (cmp
== DIFF_NONE
) {
163 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
167 if (cmp
& DIFF_TYPE
) {
168 msg(MSG_NORMAL
, "%s:\tnew type, will not change metadata\n",
173 msg(MSG_QUIET
, "%s:\tchanging metadata\n", real
->path
);
175 while (cmp
& (DIFF_OWNER
| DIFF_GROUP
)) {
176 if (cmp
& DIFF_OWNER
) {
177 msg(MSG_NORMAL
, "%s:\tchanging owner from %s to %s\n",
178 real
->path
, real
->group
, stored
->group
);
179 owner
= xgetpwnam(stored
->owner
);
181 msg(MSG_DEBUG
, "\tgetpwnam failed: %s\n",
188 if (cmp
& DIFF_GROUP
) {
189 msg(MSG_NORMAL
, "%s:\tchanging group from %s to %s\n",
190 real
->path
, real
->group
, stored
->group
);
191 group
= xgetgrnam(stored
->group
);
193 msg(MSG_DEBUG
, "\tgetgrnam failed: %s\n",
200 if (lchown(real
->path
, uid
, gid
)) {
201 msg(MSG_DEBUG
, "\tlchown failed: %s\n",
208 if (cmp
& DIFF_MODE
) {
209 msg(MSG_NORMAL
, "%s:\tchanging mode from 0%o to 0%o\n",
210 real
->path
, real
->mode
& 07777, stored
->mode
& 07777);
211 if (chmod(real
->path
, stored
->mode
& 07777))
212 msg(MSG_DEBUG
, "\tchmod failed: %s\n", strerror(errno
));
215 /* FIXME: Use utimensat here, or even better - lutimensat */
216 if ((cmp
& DIFF_MTIME
) && S_ISLNK(real
->mode
)) {
217 msg(MSG_NORMAL
, "%s:\tsymlink, not changing mtime\n", real
->path
);
218 } else if (cmp
& DIFF_MTIME
) {
219 msg(MSG_NORMAL
, "%s:\tchanging mtime from %ld to %ld\n",
220 real
->path
, real
->mtime
, stored
->mtime
);
221 tbuf
.actime
= stored
->mtime
;
222 tbuf
.modtime
= stored
->mtime
;
223 if (utime(real
->path
, &tbuf
)) {
224 msg(MSG_DEBUG
, "\tutime failed: %s\n", strerror(errno
));
229 if (cmp
& DIFF_XATTR
) {
230 for (i
= 0; i
< real
->xattrs
; i
++) {
231 /* Any attrs to remove? */
232 if (mentry_find_xattr(stored
, real
, i
) >= 0)
235 msg(MSG_NORMAL
, "%s:\tremoving xattr %s\n",
236 real
->path
, real
->xattr_names
[i
]);
237 if (lremovexattr(real
->path
, real
->xattr_names
[i
]))
238 msg(MSG_DEBUG
, "\tlremovexattr failed: %s\n",
242 for (i
= 0; i
< stored
->xattrs
; i
++) {
243 /* Any xattrs to add? (on change they are removed above) */
244 if (mentry_find_xattr(real
, stored
, i
) >= 0)
247 msg(MSG_NORMAL
, "%s:\tadding xattr %s\n",
248 stored
->path
, stored
->xattr_names
[i
]);
249 if (lsetxattr(stored
->path
, stored
->xattr_names
[i
],
250 stored
->xattr_values
[i
],
251 stored
->xattr_lvalues
[i
], XATTR_CREATE
))
252 msg(MSG_DEBUG
, "\tlsetxattr failed: %s\n",
259 * Tries to fix any empty dirs which are missing from the filesystem by
263 fixup_emptydirs(struct metahash
*real
, struct metahash
*stored
)
265 struct metaentry
*entry
;
266 struct metaentry
*cur
;
267 struct metaentry
**parent
;
271 struct metaentry
*new;
275 msg(MSG_DEBUG
, "\nAttempting to recreate missing dirs\n");
277 /* If directory x/y is missing, but file x/y/z is also missing,
278 * we should prune directory x/y from the list of directories to
279 * recreate since the deletition of x/y is likely to be genuine
280 * (as opposed to empty dir pruning like git/cvs does).
282 * Also, if file x/y/z is missing, any child directories of
283 * x/y should be pruned as they are probably also intentionally
287 msg(MSG_DEBUG
, "List of candidate dirs:\n");
288 for (cur
= missingdirs
; cur
; cur
= cur
->list
)
289 msg(MSG_DEBUG
, " %s\n", cur
->path
);
291 for (entry
= missingothers
; entry
; entry
= entry
->list
) {
292 msg(MSG_DEBUG
, "Pruning using file %s\n", entry
->path
);
293 bpath
= xstrdup(entry
->path
);
294 delim
= strrchr(bpath
, '/');
296 msg(MSG_NORMAL
, "No delimiter found in %s\n", bpath
);
302 parent
= &missingdirs
;
303 for (cur
= *parent
; cur
; cur
= cur
->list
) {
304 if (strcmp(cur
->path
, bpath
)) {
309 msg(MSG_DEBUG
, "Prune phase 1 - %s\n", cur
->path
);
313 /* Now also prune subdirs of the base dir */
316 blen
= strlen(bpath
);
318 parent
= &missingdirs
;
319 for (cur
= *parent
; cur
; cur
= cur
->list
) {
320 if (strncmp(cur
->path
, bpath
, blen
)) {
325 msg(MSG_DEBUG
, "Prune phase 2 - %s\n", cur
->path
);
331 msg(MSG_DEBUG
, "\n");
333 for (cur
= missingdirs
; cur
; cur
= cur
->list
) {
334 msg(MSG_QUIET
, "%s:\trecreating...", cur
->path
);
335 if (mkdir(cur
->path
, cur
->mode
)) {
336 msg(MSG_QUIET
, "failed (%s)\n", strerror(errno
));
339 msg(MSG_QUIET
, "ok\n");
341 new = mentry_create(cur
->path
);
343 msg(MSG_QUIET
, "Failed to get metadata for %s\n");
347 compare_fix(new, cur
, mentry_compare(new, cur
, &settings
));
352 * Deletes any empty dirs present in the filesystem that are missing
354 * An "empty" dir is one which either:
356 * - only contains empty dirs
359 fixup_newemptydirs(void)
361 struct metaentry
**cur
;
362 int removed_dirs
= 1;
367 /* This is a simpleminded algorithm that attempts to rmdir() all
368 * directories discovered missing from the metadata. Naturally, this will
369 * succeed only on the truly empty directories, but depending on the order,
370 * it may mean that parent directory removal are attempted to be removed
371 * *before* the children. To circumvent this, keep looping around all the
372 * directories until none have been successfully removed. This is a
373 * O(N**2) algorithm, so don't try to remove too many nested directories
374 * at once (e.g. thousands).
376 * Note that this will succeed only if each parent directory is writable.
378 while (removed_dirs
) {
380 msg(MSG_DEBUG
, "\nAttempting to delete empty dirs\n");
381 for (cur
= &extradirs
; *cur
;) {
382 msg(MSG_QUIET
, "%s:\tremoving...", (*cur
)->path
);
383 if (rmdir((*cur
)->path
)) {
384 msg(MSG_QUIET
, "failed (%s)\n", strerror(errno
));
388 /* No freeing, because OS will do the job at the end. */
391 msg(MSG_QUIET
, "ok\n");
396 /* Prints usage message and exits */
398 usage(const char *arg0
, const char *message
)
401 msg(MSG_CRITICAL
, "%s: %s\n\n", arg0
, message
);
403 "Usage: %s ACTION [OPTION...] [PATH...]\n",
407 "Where ACTION is one of:\n"
408 " -c, --compare Show differences between stored and real metadata\n"
409 " -s, --save Save current metadata\n"
410 " -a, --apply Apply stored metadata\n"
411 " -h, --help Help message (this text)\n"
413 "Valid OPTIONS are:\n"
414 " -v, --verbose Print more verbose messages\n"
415 " -q, --quiet Print less verbose messages\n"
416 " -m, --mtime Also take mtime into account for diff or apply\n"
417 " -e, --empty-dirs Recreate missing empty directories\n"
418 " -E, --remove-empty-dirs Remove extra empty directories\n"
419 " -g, --git Do not omit .git directories\n"
420 " -f, --file=FILE Set metadata file to FILE\n"
423 exit(message
? EXIT_FAILURE
: EXIT_SUCCESS
);
427 static struct option long_options
[] = {
428 {"compare", 0, 0, 0},
432 {"verbose", 0, 0, 0},
435 {"empty-dirs", 0, 0, 0},
436 {"remove-empty-dirs", 0, 0, 0},
438 {"file", required_argument
, 0, 0},
444 main(int argc
, char **argv
, char **envp
)
447 struct metahash
*real
= NULL
;
448 struct metahash
*stored
= NULL
;
454 int option_index
= 0;
455 c
= getopt_long(argc
, argv
, "csahvqmeEgf:",
456 long_options
, &option_index
);
461 if (!strcmp("verbose",
462 long_options
[option_index
].name
)) {
464 } else if (!strcmp("quiet",
465 long_options
[option_index
].name
)) {
466 adjust_verbosity(-1);
467 } else if (!strcmp("mtime",
468 long_options
[option_index
].name
)) {
469 settings
.do_mtime
= true;
470 } else if (!strcmp("empty-dirs",
471 long_options
[option_index
].name
)) {
472 settings
.do_emptydirs
= true;
473 } else if (!strcmp("remove-empty-dirs",
474 long_options
[option_index
].name
)) {
475 settings
.do_removeemptydirs
= true;
476 } else if (!strcmp("git",
477 long_options
[option_index
].name
)) {
478 settings
.do_git
= true;
479 } else if (!strcmp("file",
480 long_options
[option_index
].name
)) {
481 settings
.metafile
= optarg
;
483 action
|= (1 << option_index
);
488 action
|= ACTION_DIFF
;
492 action
|= ACTION_SAVE
;
496 action
|= ACTION_APPLY
;
500 action
|= ACTION_HELP
;
507 adjust_verbosity(-1);
510 settings
.do_mtime
= true;
513 settings
.do_emptydirs
= true;
516 settings
.do_removeemptydirs
= true;
519 settings
.do_git
= true;
522 settings
.metafile
= optarg
;
525 usage(argv
[0], "unknown option");
529 /* Make sure only one action is specified */
531 usage(argv
[0], "incorrect option(s)");
533 /* Make sure --empty-dirs is only used with apply */
534 if (settings
.do_emptydirs
&& action
!= ACTION_APPLY
)
535 usage(argv
[0], "--empty-dirs is only valid with --apply");
537 /* Make sure --remove-empty-dirs is only used with apply */
538 if (settings
.do_removeemptydirs
&& action
!= ACTION_APPLY
)
539 usage(argv
[0], "--remove-empty-dirs is only valid with --apply");
544 mentries_fromfile(&stored
, settings
.metafile
);
546 msg(MSG_CRITICAL
, "Failed to load metadata from %s\n",
552 while (optind
< argc
)
553 mentries_recurse_path(argv
[optind
++], &real
, &settings
);
555 mentries_recurse_path(".", &real
, &settings
);
560 "Failed to load metadata from file system\n");
564 mentries_compare(real
, stored
, compare_print
, &settings
);
569 while (optind
< argc
)
570 mentries_recurse_path(argv
[optind
++], &real
, &settings
);
572 mentries_recurse_path(".", &real
, &settings
);
577 "Failed to load metadata from file system\n");
581 mentries_tofile(real
, settings
.metafile
);
585 mentries_fromfile(&stored
, settings
.metafile
);
587 msg(MSG_CRITICAL
, "Failed to load metadata from %s\n",
593 while (optind
< argc
)
594 mentries_recurse_path(argv
[optind
++], &real
, &settings
);
596 mentries_recurse_path(".", &real
, &settings
);
601 "Failed to load metadata from file system\n");
605 mentries_compare(real
, stored
, compare_fix
, &settings
);
607 if (settings
.do_emptydirs
)
608 fixup_emptydirs(real
, stored
);
609 if (settings
.do_removeemptydirs
)
610 fixup_newemptydirs();
614 usage(argv
[0], NULL
);