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 <attr/xattr.h>
31 #include "metastore.h"
33 #include "metaentry.h"
35 /* Used to store the path to the file containing the metadata */
36 char *metafile
= METAFILE
;
38 /* Used to indicate whether mtimes should be corrected */
39 static bool do_mtime
= false;
41 /* Used to indicate whether empty dirs should be recreated */
42 static bool do_emptydirs
= false;
44 /* Used to indicate whether .git dirs should be processed */
45 static bool do_git
= false;
47 /* Used to create lists of dirs / other files which are missing in the fs */
48 static struct metaentry
*missingdirs
= NULL
;
49 static struct metaentry
*missingothers
= NULL
;
52 * Inserts an entry in a linked list ordered by pathlen
55 insert_entry_plist(struct metaentry
**list
, struct metaentry
*entry
)
57 struct metaentry
**parent
;
59 for (parent
= list
; *parent
; parent
= &((*parent
)->list
)) {
60 if ((*parent
)->pathlen
> entry
->pathlen
)
64 entry
->list
= *parent
;
69 * Prints differences between real and stored actual metadata
70 * - for use in mentries_compare
73 compare_print(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
75 if (!real
&& !stored
) {
76 msg(MSG_ERROR
, "%s called with incorrect arguments\n", __FUNCTION__
);
80 if (cmp
== DIFF_NONE
) {
81 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
85 msg(MSG_QUIET
, "%s:\t", real
? real
->path
: stored
->path
);
88 msg(MSG_QUIET
, "added ", real
->path
);
90 msg(MSG_QUIET
, "removed ", stored
->path
);
92 msg(MSG_QUIET
, "owner ");
94 msg(MSG_QUIET
, "group ");
96 msg(MSG_QUIET
, "mode ");
98 msg(MSG_QUIET
, "type ");
100 msg(MSG_QUIET
, "mtime ");
101 if (cmp
& DIFF_XATTR
)
102 msg(MSG_QUIET
, "xattr ");
103 msg(MSG_QUIET
, "\n");
107 * Tries to change the real metadata to match the stored one
108 * - for use in mentries_compare
111 compare_fix(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
114 struct passwd
*owner
;
120 if (!real
&& !stored
) {
121 msg(MSG_ERROR
, "%s called with incorrect arguments\n",
127 if (S_ISDIR(stored
->mode
))
128 insert_entry_plist(&missingdirs
, stored
);
130 insert_entry_plist(&missingothers
, stored
);
132 msg(MSG_NORMAL
, "%s:\tremoved\n", stored
->path
);
137 msg(MSG_NORMAL
, "%s:\tadded\n", real
->path
);
141 if (cmp
== DIFF_NONE
) {
142 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
146 if (cmp
& DIFF_TYPE
) {
147 msg(MSG_NORMAL
, "%s:\tnew type, will not change metadata\n",
152 msg(MSG_QUIET
, "%s:\tchanging metadata\n", real
->path
);
154 while (cmp
& (DIFF_OWNER
| DIFF_GROUP
)) {
155 if (cmp
& DIFF_OWNER
) {
156 msg(MSG_NORMAL
, "%s:\tchanging owner from %s to %s\n",
157 real
->path
, real
->group
, stored
->group
);
158 owner
= xgetpwnam(stored
->owner
);
160 msg(MSG_DEBUG
, "\tgetpwnam failed: %s\n",
167 if (cmp
& DIFF_GROUP
) {
168 msg(MSG_NORMAL
, "%s:\tchanging group from %s to %s\n",
169 real
->path
, real
->group
, stored
->group
);
170 group
= xgetgrnam(stored
->group
);
172 msg(MSG_DEBUG
, "\tgetgrnam failed: %s\n",
179 if (lchown(real
->path
, uid
, gid
)) {
180 msg(MSG_DEBUG
, "\tlchown failed: %s\n",
187 if (cmp
& DIFF_MODE
) {
188 msg(MSG_NORMAL
, "%s:\tchanging mode from 0%o to 0%o\n",
189 real
->path
, real
->mode
& 07777, stored
->mode
& 07777);
190 if (chmod(real
->path
, stored
->mode
& 07777))
191 msg(MSG_DEBUG
, "\tchmod failed: %s\n", strerror(errno
));
194 /* FIXME: Use utimensat here, or even better - lutimensat */
195 if ((cmp
& DIFF_MTIME
) && S_ISLNK(real
->mode
)) {
196 msg(MSG_NORMAL
, "%s:\tsymlink, not changing mtime\n", real
->path
);
197 } else if (cmp
& DIFF_MTIME
) {
198 msg(MSG_NORMAL
, "%s:\tchanging mtime from %ld to %ld\n",
199 real
->path
, real
->mtime
, stored
->mtime
);
200 tbuf
.actime
= stored
->mtime
;
201 tbuf
.modtime
= stored
->mtime
;
202 if (utime(real
->path
, &tbuf
)) {
203 msg(MSG_DEBUG
, "\tutime failed: %s\n", strerror(errno
));
208 if (cmp
& DIFF_XATTR
) {
209 for (i
= 0; i
< real
->xattrs
; i
++) {
210 /* Any attrs to remove? */
211 if (mentry_find_xattr(stored
, real
, i
) >= 0)
214 msg(MSG_NORMAL
, "%s:\tremoving xattr %s\n",
215 real
->path
, real
->xattr_names
[i
]);
216 if (lremovexattr(real
->path
, real
->xattr_names
[i
]))
217 msg(MSG_DEBUG
, "\tlremovexattr failed: %s\n",
221 for (i
= 0; i
< stored
->xattrs
; i
++) {
222 /* Any xattrs to add? (on change they are removed above) */
223 if (mentry_find_xattr(real
, stored
, i
) >= 0)
226 msg(MSG_NORMAL
, "%s:\tadding xattr %s\n",
227 stored
->path
, stored
->xattr_names
[i
]);
228 if (lsetxattr(stored
->path
, stored
->xattr_names
[i
],
229 stored
->xattr_values
[i
],
230 stored
->xattr_lvalues
[i
], XATTR_CREATE
))
231 msg(MSG_DEBUG
, "\tlsetxattr failed: %s\n",
238 * Tries to fix any empty dirs which are missing by recreating them.
239 * An "empty" dir is one which either:
241 * - only contained empty dirs
244 fixup_emptydirs(struct metahash
*real
, struct metahash
*stored
)
246 struct metaentry
*entry
;
247 struct metaentry
*cur
;
248 struct metaentry
**parent
;
252 struct metaentry
*new;
256 msg(MSG_DEBUG
, "\nAttempting to recreate missing dirs\n");
258 /* If directory x/y is missing, but file x/y/z is also missing,
259 * we should prune directory x/y from the list of directories to
260 * recreate since the deletition of x/y is likely to be genuine
261 * (as opposed to empty dir pruning like git/cvs does).
263 * Also, if file x/y/z is missing, any child directories of
264 * x/y should be pruned as they are probably also intentionally
268 msg(MSG_DEBUG
, "List of candidate dirs:\n");
269 for (cur
= missingdirs
; cur
; cur
= cur
->list
)
270 msg(MSG_DEBUG
, " %s\n", cur
->path
);
272 for (entry
= missingothers
; entry
; entry
= entry
->list
) {
273 msg(MSG_DEBUG
, "Pruning using file %s\n", entry
->path
);
274 bpath
= xstrdup(entry
->path
);
275 delim
= strrchr(bpath
, '/');
277 msg(MSG_NORMAL
, "No delimiter found in %s\n", bpath
);
283 parent
= &missingdirs
;
284 for (cur
= *parent
; cur
; cur
= cur
->list
) {
285 if (strcmp(cur
->path
, bpath
)) {
290 msg(MSG_DEBUG
, "Prune phase 1 - %s\n", cur
->path
);
294 /* Now also prune subdirs of the base dir */
297 blen
= strlen(bpath
);
299 parent
= &missingdirs
;
300 for (cur
= *parent
; cur
; cur
= cur
->list
) {
301 if (strncmp(cur
->path
, bpath
, blen
)) {
306 msg(MSG_DEBUG
, "Prune phase 2 - %s\n", cur
->path
);
312 msg(MSG_DEBUG
, "\n");
314 for (cur
= missingdirs
; cur
; cur
= cur
->list
) {
315 msg(MSG_QUIET
, "%s:\trecreating...", cur
->path
);
316 if (mkdir(cur
->path
, cur
->mode
)) {
317 msg(MSG_QUIET
, "failed (%s)\n", strerror(errno
));
320 msg(MSG_QUIET
, "ok\n");
322 new = mentry_create(cur
->path
);
324 msg(MSG_QUIET
, "Failed to get metadata for %s\n");
328 compare_fix(new, cur
, mentry_compare(new, cur
, do_mtime
));
332 /* Prints usage message and exits */
334 usage(const char *arg0
, const char *message
)
337 msg(MSG_CRITICAL
, "%s: %s\n\n", arg0
, message
);
338 msg(MSG_CRITICAL
, "Usage: %s ACTION [OPTION...] [PATH...]\n\n", arg0
);
339 msg(MSG_CRITICAL
, "Where ACTION is one of:\n"
340 " -c, --compare\t\tShow differences between stored and real metadata\n"
341 " -s, --save\t\tSave current metadata\n"
342 " -a, --apply\t\tApply stored metadata\n"
343 " -h, --help\t\tHelp message (this text)\n\n"
344 "Valid OPTIONS are:\n"
345 " -v, --verbose\t\tPrint more verbose messages\n"
346 " -q, --quiet\t\tPrint less verbose messages\n"
347 " -m, --mtime\t\tAlso take mtime into account for diff or apply\n"
348 " -e, --empty-dirs\tRecreate missing empty directories (experimental)\n"
349 " -g, --git\t\tDo not omit .git directories\n"
350 " -f, --file <file>\tSet metadata file\n"
353 exit(message
? EXIT_FAILURE
: EXIT_SUCCESS
);
357 static struct option long_options
[] = {
358 {"compare", 0, 0, 0},
362 {"verbose", 0, 0, 0},
365 {"empty-dirs", 0, 0, 0},
367 {"file", required_argument
, 0, 0},
373 main(int argc
, char **argv
, char **envp
)
376 struct metahash
*real
= NULL
;
377 struct metahash
*stored
= NULL
;
383 int option_index
= 0;
384 c
= getopt_long(argc
, argv
, "csahvqmegf:",
385 long_options
, &option_index
);
390 if (!strcmp("verbose",
391 long_options
[option_index
].name
)) {
393 } else if (!strcmp("quiet",
394 long_options
[option_index
].name
)) {
395 adjust_verbosity(-1);
396 } else if (!strcmp("mtime",
397 long_options
[option_index
].name
)) {
399 } else if (!strcmp("empty-dirs",
400 long_options
[option_index
].name
)) {
402 } else if (!strcmp("git",
403 long_options
[option_index
].name
)) {
405 } else if (!strcmp("file",
406 long_options
[option_index
].name
)) {
409 action
|= (1 << option_index
);
414 action
|= ACTION_DIFF
;
418 action
|= ACTION_SAVE
;
422 action
|= ACTION_APPLY
;
426 action
|= ACTION_HELP
;
433 adjust_verbosity(-1);
448 usage(argv
[0], "unknown option");
452 /* Make sure only one action is specified */
454 usage(argv
[0], "incorrect option(s)");
456 /* Make sure --empty-dirs is only used with apply */
457 if (do_emptydirs
&& action
!= ACTION_APPLY
)
458 usage(argv
[0], "--empty-dirs is only valid with --apply");
463 mentries_fromfile(&stored
, metafile
);
465 msg(MSG_CRITICAL
, "Failed to load metadata from %s\n",
471 while (optind
< argc
)
472 mentries_recurse_path(argv
[optind
++], &real
, do_git
);
474 mentries_recurse_path(".", &real
, do_git
);
479 "Failed to load metadata from file system\n");
483 mentries_compare(real
, stored
, compare_print
, do_mtime
);
488 while (optind
< argc
)
489 mentries_recurse_path(argv
[optind
++], &real
, do_git
);
491 mentries_recurse_path(".", &real
, do_git
);
496 "Failed to load metadata from file system\n");
500 mentries_tofile(real
, metafile
);
504 mentries_fromfile(&stored
, metafile
);
506 msg(MSG_CRITICAL
, "Failed to load metadata from %s\n",
512 while (optind
< argc
)
513 mentries_recurse_path(argv
[optind
++], &real
, do_git
);
515 mentries_recurse_path(".", &real
, do_git
);
520 "Failed to load metadata from file system\n");
524 mentries_compare(real
, stored
, compare_fix
, do_mtime
);
527 fixup_emptydirs(real
, stored
);
531 usage(argv
[0], NULL
);