Fix UB: Make read_int() follow SEI CERT INT13-C and INT34-C.
[metastore.git] / src / metastore.c
blob844e707a05dbbad5a9b5167a978c7d12787bf0a9
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
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/>.
22 #define _BSD_SOURCE
23 #define _DEFAULT_SOURCE
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <getopt.h>
27 #include <fcntl.h>
29 #if !defined(NO_XATTR) || !(NO_XATTR+0)
30 # include <sys/xattr.h>
31 #endif /* !NO_XATTR */
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <errno.h>
38 #include "metastore.h"
39 #include "settings.h"
40 #include "utils.h"
41 #include "metaentry.h"
43 /* metastore settings */
44 static struct metasettings settings = {
45 .metafile = METAFILE,
46 .do_mtime = false,
47 .do_emptydirs = false,
48 .do_removeemptydirs = false,
49 .do_git = 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
62 static void
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)
69 break;
72 entry->list = *parent;
73 *parent = entry;
77 * Inserts an entry in a linked list ordered by pathlen descendingly
79 static void
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)
86 break;
89 entry->list = *parent;
90 *parent = entry;
94 * Prints differences between real and stored actual metadata
95 * - for use in mentries_compare
97 static void
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__);
102 return;
105 if (cmp == DIFF_NONE) {
106 msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
107 return;
110 msg(MSG_QUIET, "%s:\t", real ? real->path : stored->path);
112 if (cmp & DIFF_ADDED)
113 msg(MSG_QUIET, "added ", real->path);
114 if (cmp & DIFF_DELE)
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 ");
120 if (cmp & DIFF_MODE)
121 msg(MSG_QUIET, "mode ");
122 if (cmp & DIFF_TYPE)
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
140 static void
141 compare_fix(struct metaentry *real, struct metaentry *stored, int cmp)
143 struct group *group;
144 struct passwd *owner;
145 gid_t gid = -1;
146 uid_t uid = -1;
147 struct timespec times[2];
148 unsigned i;
150 if (!real && !stored) {
151 msg(MSG_ERROR, "%s called with incorrect arguments\n", __func__);
152 return;
155 if (!real) {
156 if (S_ISDIR(stored->mode))
157 insert_entry_plist(&missingdirs, stored);
158 else
159 insert_entry_plist(&missingothers, stored);
161 msg(MSG_NORMAL, "%s:\tremoved\n", stored->path);
162 return;
165 if (!stored) {
166 if (S_ISDIR(real->mode))
167 insert_entry_pdlist(&extradirs, real);
168 msg(MSG_NORMAL, "%s:\tadded\n", real->path);
169 return;
172 if (cmp == DIFF_NONE) {
173 msg(MSG_DEBUG, "%s:\tno difference\n", real->path);
174 return;
177 if (cmp & DIFF_TYPE) {
178 msg(MSG_NORMAL, "%s:\tnew type, will not change metadata\n",
179 real->path);
180 return;
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);
190 if (!owner) {
191 msg(MSG_DEBUG, "\tgetpwnam failed: %s\n",
192 strerror(errno));
193 break;
195 uid = owner->pw_uid;
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);
202 if (!group) {
203 msg(MSG_DEBUG, "\tgetgrnam failed: %s\n",
204 strerror(errno));
205 break;
207 gid = group->gr_gid;
210 if (lchown(real->path, uid, gid)) {
211 msg(MSG_DEBUG, "\tlchown failed: %s\n",
212 strerror(errno));
213 break;
215 break;
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));
233 return;
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)
241 continue;
243 msg(MSG_NORMAL, "%s:\tremoving xattr %s\n",
244 real->path, real->xattr_names[i]);
245 if ((NO_XATTR+0)) {
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)
250 else
251 if (lremovexattr(real->path, real->xattr_names[i]))
252 msg(MSG_DEBUG, "\tlremovexattr failed: %s\n",
253 strerror(errno));
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)
260 continue;
262 msg(MSG_NORMAL, "%s:\tadding xattr %s\n",
263 stored->path, stored->xattr_names[i]);
264 if ((NO_XATTR+0)) {
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)
269 else
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",
275 strerror(errno));
276 #endif /* !NO_XATTR */
282 * Tries to fix any empty dirs which are missing from the filesystem by
283 * recreating them.
285 static void
286 fixup_emptydirs(void)
288 struct metaentry *entry;
289 struct metaentry *cur;
290 struct metaentry **parent;
291 char *bpath;
292 char *delim;
293 size_t blen;
294 struct metaentry *new;
296 if (!missingdirs)
297 return;
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
307 * removed.
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, '/');
318 if (!delim) {
319 msg(MSG_NORMAL, "No delimiter found in %s\n", bpath);
320 free(bpath);
321 continue;
323 *delim = '\0';
325 parent = &missingdirs;
326 for (cur = *parent; cur; cur = cur->list) {
327 if (strcmp(cur->path, bpath)) {
328 parent = &cur->list;
329 continue;
332 msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path);
333 *parent = cur->list;
336 /* Now also prune subdirs of the base dir */
337 *delim++ = '/';
338 *delim = '\0';
339 blen = strlen(bpath);
341 parent = &missingdirs;
342 for (cur = *parent; cur; cur = cur->list) {
343 if (strncmp(cur->path, bpath, blen)) {
344 parent = &cur->list;
345 continue;
348 msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path);
349 *parent = cur->list;
352 free(bpath);
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));
360 continue;
362 msg(MSG_QUIET, "ok\n");
364 new = mentry_create(cur->path);
365 if (!new) {
366 msg(MSG_QUIET, "Failed to get metadata for %s\n", cur->path);
367 continue;
370 compare_fix(new, cur, mentry_compare(new, cur, &settings));
375 * Deletes any empty dirs present in the filesystem that are missing
376 * from the metadata.
377 * An "empty" dir is one which either:
378 * - is empty; or
379 * - only contains empty dirs
381 static void
382 fixup_newemptydirs(void)
384 struct metaentry **cur;
385 int removed_dirs = 1;
387 if (!extradirs)
388 return;
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) {
402 removed_dirs = 0;
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));
408 cur = &(*cur)->list;
409 continue;
411 /* No freeing, because OS will do the job at the end. */
412 *cur = (*cur)->list;
413 removed_dirs++;
414 msg(MSG_QUIET, "ok\n");
419 /* Outputs version information and exits */
420 static void
421 version(void)
423 printf("metastore %s\n", METASTORE_VER);
425 if ((NO_XATTR+0)) {
426 printf("Built with %s.\n", NO_XATTR_MSG);
429 exit(EXIT_SUCCESS);
432 /* Prints usage message and exits */
433 static void
434 usage(const char *arg0, const char *message)
436 if (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",
443 arg0);
444 msg(message ? MSG_ERROR : MSG_QUIET,
445 "\n"
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"
454 "\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);
468 /* Options */
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' },
483 { NULL, 0, NULL, 0 }
486 /* Main function */
488 main(int argc, char **argv)
490 int i, c;
491 struct metahash *real = NULL;
492 struct metahash *stored = NULL;
493 int action = 0;
495 /* Parse options */
496 i = 0;
497 while (1) {
498 int option_index = 0;
499 c = getopt_long(argc, argv, "csadVhvqmeEgf:",
500 long_options, &option_index);
501 if (c == -1)
502 break;
503 switch (c) {
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;
515 break;
516 case 'g': /* git */ settings.do_git = true; break;
517 case 'f': /* file */ settings.metafile = optarg; break;
518 default:
519 usage(argv[0], "unknown option");
523 /* Make sure only one action is specified */
524 if (i != 1)
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)
536 version();
538 if (action == ACTION_HELP)
539 usage(argv[0], NULL);
541 /* Perform action */
542 if (action & ACTIONS_READING && !(action == ACTION_DUMP && optind < argc)) {
543 mentries_fromfile(&stored, settings.metafile);
544 if (!stored) {
545 msg(MSG_CRITICAL, "Failed to load metadata from %s\n",
546 settings.metafile);
547 exit(EXIT_FAILURE);
551 if (optind < argc) {
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)) {
559 msg(MSG_CRITICAL,
560 "Failed to load metadata from file system\n");
561 exit(EXIT_FAILURE);
564 switch (action) {
565 case ACTION_DIFF:
566 mentries_compare(real, stored, compare_print, &settings);
567 break;
568 case ACTION_SAVE:
569 mentries_tofile(real, settings.metafile);
570 break;
571 case ACTION_APPLY:
572 mentries_compare(real, stored, compare_fix, &settings);
573 if (settings.do_emptydirs)
574 fixup_emptydirs();
575 if (settings.do_removeemptydirs)
576 fixup_newemptydirs();
577 break;
578 case ACTION_DUMP:
579 mentries_dump(real ? real : stored);
580 break;
583 exit(EXIT_SUCCESS);