1 /* SPDX-License-Identifier: GPL-2.0-only */
3 * Main functions of the program.
5 * Copyright (C) 2007-2008 David Härdeman <david@hardeman.nu>
6 * Copyright (C) 2012-2016 Przemyslaw Pawelczyk <przemoc@gmail.com>
7 * Copyright (C) 2014-2015 Dan Fandrich <dan@coneharvesters.com>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU General Public License as published by the
11 * Free Software Foundation; only version 2 of the License is applicable.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 * See the GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #define _DEFAULT_SOURCE
24 #include <sys/types.h>
29 #if !defined(NO_XATTR) || !(NO_XATTR+0)
30 # include <sys/xattr.h>
31 #endif /* !NO_XATTR */
38 #include "metastore.h"
41 #include "metaentry.h"
43 /* metastore settings */
44 static struct metasettings settings
= {
47 .do_emptydirs
= false,
48 .do_removeemptydirs
= false,
52 /* Used to create lists of dirs / other files which are missing in the fs */
53 static struct metaentry
*missingdirs
= NULL
;
54 static struct metaentry
*missingothers
= NULL
;
56 /* Used to create lists of dirs / other files which are missing in metadata */
57 static struct metaentry
*extradirs
= NULL
;
60 * Inserts an entry in a linked list ordered by pathlen
63 insert_entry_plist(struct metaentry
**list
, struct metaentry
*entry
)
65 struct metaentry
**parent
;
67 for (parent
= list
; *parent
; parent
= &((*parent
)->list
)) {
68 if ((*parent
)->pathlen
> entry
->pathlen
)
72 entry
->list
= *parent
;
77 * Inserts an entry in a linked list ordered by pathlen descendingly
80 insert_entry_pdlist(struct metaentry
**list
, struct metaentry
*entry
)
82 struct metaentry
**parent
;
84 for (parent
= list
; *parent
; parent
= &((*parent
)->list
)) {
85 if ((*parent
)->pathlen
< entry
->pathlen
)
89 entry
->list
= *parent
;
94 * Prints differences between real and stored actual metadata
95 * - for use in mentries_compare
98 compare_print(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
100 if (!real
&& (!stored
|| (cmp
== DIFF_NONE
|| cmp
& DIFF_ADDED
))) {
101 msg(MSG_ERROR
, "%s called with incorrect arguments\n", __func__
);
105 if (cmp
== DIFF_NONE
) {
106 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
110 msg(MSG_QUIET
, "%s:\t", real
? real
->path
: stored
->path
);
112 if (cmp
& DIFF_ADDED
)
113 msg(MSG_QUIET
, "added ", real
->path
);
115 msg(MSG_QUIET
, "removed ", stored
->path
);
116 if (cmp
& DIFF_OWNER
)
117 msg(MSG_QUIET
, "owner ");
118 if (cmp
& DIFF_GROUP
)
119 msg(MSG_QUIET
, "group ");
121 msg(MSG_QUIET
, "mode ");
123 msg(MSG_QUIET
, "type ");
124 if (cmp
& DIFF_MTIME
)
125 msg(MSG_QUIET
, "mtime ");
126 if (cmp
& DIFF_XATTR
)
127 msg(MSG_QUIET
, "xattr ");
128 msg(MSG_QUIET
, "\n");
130 if ((NO_XATTR
+0) && cmp
& DIFF_XATTR
) {
131 msg(MSG_WARNING
, "%s:\txattr difference may be bogus: %s\n",
132 real
->path
, NO_XATTR_MSG
);
137 * Tries to change the real metadata to match the stored one
138 * - for use in mentries_compare
141 compare_fix(struct metaentry
*real
, struct metaentry
*stored
, int cmp
)
144 struct passwd
*owner
;
147 struct timespec times
[2];
150 if (!real
&& !stored
) {
151 msg(MSG_ERROR
, "%s called with incorrect arguments\n", __func__
);
156 if (S_ISDIR(stored
->mode
))
157 insert_entry_plist(&missingdirs
, stored
);
159 insert_entry_plist(&missingothers
, stored
);
161 msg(MSG_NORMAL
, "%s:\tremoved\n", stored
->path
);
166 if (S_ISDIR(real
->mode
))
167 insert_entry_pdlist(&extradirs
, real
);
168 msg(MSG_NORMAL
, "%s:\tadded\n", real
->path
);
172 if (cmp
== DIFF_NONE
) {
173 msg(MSG_DEBUG
, "%s:\tno difference\n", real
->path
);
177 if (cmp
& DIFF_TYPE
) {
178 msg(MSG_NORMAL
, "%s:\tnew type, will not change metadata\n",
183 msg(MSG_QUIET
, "%s:\tchanging metadata\n", real
->path
);
185 while (cmp
& (DIFF_OWNER
| DIFF_GROUP
)) {
186 if (cmp
& DIFF_OWNER
) {
187 msg(MSG_NORMAL
, "%s:\tchanging owner from %s to %s\n",
188 real
->path
, real
->owner
, stored
->owner
);
189 owner
= xgetpwnam(stored
->owner
);
191 msg(MSG_DEBUG
, "\tgetpwnam failed: %s\n",
198 if (cmp
& DIFF_GROUP
) {
199 msg(MSG_NORMAL
, "%s:\tchanging group from %s to %s\n",
200 real
->path
, real
->group
, stored
->group
);
201 group
= xgetgrnam(stored
->group
);
203 msg(MSG_DEBUG
, "\tgetgrnam failed: %s\n",
210 if (lchown(real
->path
, uid
, gid
)) {
211 msg(MSG_DEBUG
, "\tlchown failed: %s\n",
218 if (cmp
& DIFF_MODE
) {
219 msg(MSG_NORMAL
, "%s:\tchanging mode from 0%o to 0%o\n",
220 real
->path
, real
->mode
& 07777, stored
->mode
& 07777);
221 if (chmod(real
->path
, stored
->mode
& 07777))
222 msg(MSG_DEBUG
, "\tchmod failed: %s\n", strerror(errno
));
225 if (cmp
& DIFF_MTIME
) {
226 msg(MSG_NORMAL
, "%s:\tchanging mtime from %ld.%09ld to %ld.%09ld\n",
227 real
->path
, real
->mtime
, real
->mtimensec
, stored
->mtime
, stored
->mtimensec
);
228 times
[0].tv_nsec
= UTIME_OMIT
; // atime (last access time)
229 times
[1].tv_sec
= stored
->mtime
; // mtime (last modification time)
230 times
[1].tv_nsec
= stored
->mtimensec
;
231 if (utimensat(AT_FDCWD
, real
->path
, times
, AT_SYMLINK_NOFOLLOW
)) {
232 msg(MSG_DEBUG
, "\tutimensat failed: %s\n", strerror(errno
));
237 if (cmp
& DIFF_XATTR
) {
238 for (i
= 0; i
< real
->xattrs
; i
++) {
239 /* Any attrs to remove? */
240 if (mentry_find_xattr(stored
, real
, i
) >= 0)
243 msg(MSG_NORMAL
, "%s:\tremoving xattr %s\n",
244 real
->path
, real
->xattr_names
[i
]);
246 msg(MSG_WARNING
, "%s:\tremoving xattr %s failed: %s\n",
247 real
->path
, real
->xattr_names
[i
], NO_XATTR_MSG
);
249 #if !defined(NO_XATTR) || !(NO_XATTR+0)
251 if (lremovexattr(real
->path
, real
->xattr_names
[i
]))
252 msg(MSG_DEBUG
, "\tlremovexattr failed: %s\n",
254 #endif /* !NO_XATTR */
257 for (i
= 0; i
< stored
->xattrs
; i
++) {
258 /* Any xattrs to add? (on change they are removed above) */
259 if (mentry_find_xattr(real
, stored
, i
) >= 0)
262 msg(MSG_NORMAL
, "%s:\tadding xattr %s\n",
263 stored
->path
, stored
->xattr_names
[i
]);
265 msg(MSG_WARNING
, "%s:\tadding xattr %s failed: %s\n",
266 stored
->path
, stored
->xattr_names
[i
], NO_XATTR_MSG
);
268 #if !defined(NO_XATTR) || !(NO_XATTR+0)
270 if (lsetxattr(stored
->path
, stored
->xattr_names
[i
],
271 stored
->xattr_values
[i
],
272 stored
->xattr_lvalues
[i
], XATTR_CREATE
)
274 msg(MSG_DEBUG
, "\tlsetxattr failed: %s\n",
276 #endif /* !NO_XATTR */
282 * Tries to fix any empty dirs which are missing from the filesystem by
286 fixup_emptydirs(void)
288 struct metaentry
*entry
;
289 struct metaentry
*cur
;
290 struct metaentry
**parent
;
294 struct metaentry
*new;
298 msg(MSG_DEBUG
, "\nAttempting to recreate missing dirs\n");
300 /* If directory x/y is missing, but file x/y/z is also missing,
301 * we should prune directory x/y from the list of directories to
302 * recreate since the deletition of x/y is likely to be genuine
303 * (as opposed to empty dir pruning like git/cvs does).
305 * Also, if file x/y/z is missing, any child directories of
306 * x/y should be pruned as they are probably also intentionally
310 msg(MSG_DEBUG
, "List of candidate dirs:\n");
311 for (cur
= missingdirs
; cur
; cur
= cur
->list
)
312 msg(MSG_DEBUG
, " %s\n", cur
->path
);
314 for (entry
= missingothers
; entry
; entry
= entry
->list
) {
315 msg(MSG_DEBUG
, "Pruning using file %s\n", entry
->path
);
316 bpath
= xstrdup(entry
->path
);
317 delim
= strrchr(bpath
, '/');
319 msg(MSG_NORMAL
, "No delimiter found in %s\n", bpath
);
325 parent
= &missingdirs
;
326 for (cur
= *parent
; cur
; cur
= cur
->list
) {
327 if (strcmp(cur
->path
, bpath
)) {
332 msg(MSG_DEBUG
, "Prune phase 1 - %s\n", cur
->path
);
336 /* Now also prune subdirs of the base dir */
339 blen
= strlen(bpath
);
341 parent
= &missingdirs
;
342 for (cur
= *parent
; cur
; cur
= cur
->list
) {
343 if (strncmp(cur
->path
, bpath
, blen
)) {
348 msg(MSG_DEBUG
, "Prune phase 2 - %s\n", cur
->path
);
354 msg(MSG_DEBUG
, "\n");
356 for (cur
= missingdirs
; cur
; cur
= cur
->list
) {
357 msg(MSG_QUIET
, "%s:\trecreating...", cur
->path
);
358 if (mkdir(cur
->path
, cur
->mode
)) {
359 msg(MSG_QUIET
, "failed (%s)\n", strerror(errno
));
362 msg(MSG_QUIET
, "ok\n");
364 new = mentry_create(cur
->path
);
366 msg(MSG_QUIET
, "Failed to get metadata for %s\n", cur
->path
);
370 compare_fix(new, cur
, mentry_compare(new, cur
, &settings
));
375 * Deletes any empty dirs present in the filesystem that are missing
377 * An "empty" dir is one which either:
379 * - only contains empty dirs
382 fixup_newemptydirs(void)
384 struct metaentry
**cur
;
385 int removed_dirs
= 1;
390 /* This is a simpleminded algorithm that attempts to rmdir() all
391 * directories discovered missing from the metadata. Naturally, this will
392 * succeed only on the truly empty directories, but depending on the order,
393 * it may mean that parent directory removal are attempted to be removed
394 * *before* the children. To circumvent this, keep looping around all the
395 * directories until none have been successfully removed. This is a
396 * O(N**2) algorithm, so don't try to remove too many nested directories
397 * at once (e.g. thousands).
399 * Note that this will succeed only if each parent directory is writable.
401 while (removed_dirs
) {
403 msg(MSG_DEBUG
, "\nAttempting to delete empty dirs\n");
404 for (cur
= &extradirs
; *cur
;) {
405 msg(MSG_QUIET
, "%s:\tremoving...", (*cur
)->path
);
406 if (rmdir((*cur
)->path
)) {
407 msg(MSG_QUIET
, "failed (%s)\n", strerror(errno
));
411 /* No freeing, because OS will do the job at the end. */
414 msg(MSG_QUIET
, "ok\n");
419 /* Outputs version information and exits */
423 printf("metastore %s\n", METASTORE_VER
);
426 printf("Built with %s.\n", NO_XATTR_MSG
);
432 /* Prints usage message and exits */
434 usage(const char *arg0
, const char *message
)
437 msg(MSG_CRITICAL
, "%s: %s\n", arg0
, message
);
438 msg(MSG_ERROR
, "\n");
441 msg(message
? MSG_ERROR
: MSG_QUIET
,
442 "Usage: %s ACTION [OPTION...] [PATH...]\n",
444 msg(message
? MSG_ERROR
: MSG_QUIET
,
446 "Where ACTION is one of:\n"
447 " -c, --compare Show differences between stored and real metadata\n"
448 " -s, --save Save current metadata\n"
449 " -a, --apply Apply stored metadata\n"
450 " -d, --dump Dump stored (if no PATH is given) or real metadata\n"
451 " (if PATH is present, e.g. ./) in human-readable form\n"
452 " -V, --version Output version information and exit\n"
453 " -h, --help Help message (this text)\n"
455 "Valid OPTIONS are:\n"
456 " -v, --verbose Print more verbose messages\n"
457 " -q, --quiet Print less verbose messages\n"
458 " -m, --mtime Also take mtime into account for diff or apply\n"
459 " -e, --empty-dirs Recreate missing empty directories\n"
460 " -E, --remove-empty-dirs Remove extra empty directories\n"
461 " -g, --git Do not omit .git directories\n"
462 " -f, --file=FILE Set metadata file (" METAFILE
" by default)\n"
465 exit(message
? EXIT_FAILURE
: EXIT_SUCCESS
);
469 static struct option long_options
[] = {
470 { "compare", no_argument
, NULL
, 'c' },
471 { "save", no_argument
, NULL
, 's' },
472 { "apply", no_argument
, NULL
, 'a' },
473 { "dump", no_argument
, NULL
, 'd' },
474 { "version", no_argument
, NULL
, 'V' },
475 { "help", no_argument
, NULL
, 'h' },
476 { "verbose", no_argument
, NULL
, 'v' },
477 { "quiet", no_argument
, NULL
, 'q' },
478 { "mtime", no_argument
, NULL
, 'm' },
479 { "empty-dirs", no_argument
, NULL
, 'e' },
480 { "remove-empty-dirs", no_argument
, NULL
, 'E' },
481 { "git", no_argument
, NULL
, 'g' },
482 { "file", required_argument
, NULL
, 'f' },
488 main(int argc
, char **argv
)
491 struct metahash
*real
= NULL
;
492 struct metahash
*stored
= NULL
;
498 int option_index
= 0;
499 c
= getopt_long(argc
, argv
, "csadVhvqmeEgf:",
500 long_options
, &option_index
);
504 case 'c': /* compare */ action
|= ACTION_DIFF
; i
++; break;
505 case 's': /* save */ action
|= ACTION_SAVE
; i
++; break;
506 case 'a': /* apply */ action
|= ACTION_APPLY
; i
++; break;
507 case 'd': /* dump */ action
|= ACTION_DUMP
; i
++; break;
508 case 'V': /* version */ action
|= ACTION_VER
; i
++; break;
509 case 'h': /* help */ action
|= ACTION_HELP
; i
++; break;
510 case 'v': /* verbose */ adjust_verbosity(1); break;
511 case 'q': /* quiet */ adjust_verbosity(-1); break;
512 case 'm': /* mtime */ settings
.do_mtime
= true; break;
513 case 'e': /* empty-dirs */ settings
.do_emptydirs
= true; break;
514 case 'E': /* remove-empty-dirs */ settings
.do_removeemptydirs
= true;
516 case 'g': /* git */ settings
.do_git
= true; break;
517 case 'f': /* file */ settings
.metafile
= optarg
; break;
519 usage(argv
[0], "unknown option");
523 /* Make sure only one action is specified */
525 usage(argv
[0], "incorrect option(s)");
527 /* Make sure --empty-dirs is only used with apply */
528 if (settings
.do_emptydirs
&& action
!= ACTION_APPLY
)
529 usage(argv
[0], "--empty-dirs is only valid with --apply");
531 /* Make sure --remove-empty-dirs is only used with apply */
532 if (settings
.do_removeemptydirs
&& action
!= ACTION_APPLY
)
533 usage(argv
[0], "--remove-empty-dirs is only valid with --apply");
535 if (action
== ACTION_VER
)
538 if (action
== ACTION_HELP
)
539 usage(argv
[0], NULL
);
542 if (action
& ACTIONS_READING
&& !(action
== ACTION_DUMP
&& optind
< argc
)) {
543 mentries_fromfile(&stored
, settings
.metafile
);
545 msg(MSG_CRITICAL
, "Failed to load metadata from %s\n",
552 while (optind
< argc
)
553 mentries_recurse_path(argv
[optind
++], &real
, &settings
);
554 } else if (action
!= ACTION_DUMP
) {
555 mentries_recurse_path(".", &real
, &settings
);
558 if (!real
&& (action
!= ACTION_DUMP
|| optind
< argc
)) {
560 "Failed to load metadata from file system\n");
566 mentries_compare(real
, stored
, compare_print
, &settings
);
569 mentries_tofile(real
, settings
.metafile
);
572 mentries_compare(real
, stored
, compare_fix
, &settings
);
573 if (settings
.do_emptydirs
)
575 if (settings
.do_removeemptydirs
)
576 fixup_newemptydirs();
579 mentries_dump(real
? real
: stored
);