NEWS: Mention normalize_path() bugfix.
[metastore.git] / src / metaentry.c
blobfe1c6d10069852bf86c43d886c9aa5b0f2babc58
1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3 * Various functions to work with meta entries.
5 * Copyright (C) 2007-2008 David Härdeman <david@hardeman.nu>
6 * Copyright (C) 2012-2018 Przemyslaw Pawelczyk <przemoc@gmail.com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; only version 2 of the License is applicable.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 * See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #define _GNU_SOURCE
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
29 #if !defined(NO_XATTR) || !(NO_XATTR+0)
30 # include <sys/xattr.h>
31 #endif /* !NO_XATTR */
33 #include <limits.h>
34 #include <dirent.h>
35 #include <sys/mman.h>
36 #include <utime.h>
37 #include <fcntl.h>
38 #include <stdint.h>
39 #include <errno.h>
40 #include <time.h>
42 #include <sys/param.h>
43 #ifndef BSD
44 # include <bsd/string.h>
45 #endif
47 #include "metastore.h"
48 #include "metaentry.h"
49 #include "utils.h"
51 #ifndef PATH_MAX
52 # define PATH_MAX 4096
53 #endif
55 #if !defined(NO_XATTR) || !(NO_XATTR+0)
56 /* Free's a metaentry and all its parameters */
57 static void
58 mentry_free(struct metaentry *m)
60 unsigned i;
62 if (!m)
63 return;
65 free(m->path);
66 free(m->owner);
67 free(m->group);
69 for (i = 0; i < m->xattrs; i++) {
70 free(m->xattr_names[i]);
71 free(m->xattr_values[i]);
74 free(m->xattr_names);
75 free(m->xattr_values);
76 free(m->xattr_lvalues);
78 free(m);
80 #endif /* !NO_XATTR */
82 /* Allocates an empty metahash table */
83 static struct metahash *
84 mhash_alloc(void)
86 struct metahash *mhash;
87 mhash = xmalloc(sizeof(struct metahash));
88 memset(mhash, 0, sizeof(struct metahash));
89 return mhash;
92 /* Generates a hash key (using djb2) */
93 static unsigned
94 hash(const char *str)
96 unsigned hash = 5381;
97 int c;
99 while ((c = *str++))
100 hash = ((hash << 5) + hash) + c;
102 return hash % HASH_INDEXES;
105 /* Allocates an empty metaentry */
106 static struct metaentry *
107 mentry_alloc(void)
109 struct metaentry *mentry;
110 mentry = xmalloc(sizeof(struct metaentry));
111 memset(mentry, 0, sizeof(struct metaentry));
112 return mentry;
115 /* Does a bisect search for the closest match in a metaentry list */
116 static struct metaentry *
117 mentry_find(const char *path, struct metahash *mhash)
119 struct metaentry *base;
120 unsigned key;
122 if (!mhash) {
123 msg(MSG_ERROR, "%s called with empty hash table\n", __func__);
124 return NULL;
127 key = hash(path);
128 for (base = mhash->bucket[key]; base; base = base->next) {
129 if (!strcmp(base->path, path))
130 return base;
133 return NULL;
136 /* Inserts a metaentry into a metaentry list */
137 static void
138 mentry_insert(struct metaentry *mentry, struct metahash *mhash)
140 unsigned key;
142 key = hash(mentry->path);
143 mentry->next = mhash->bucket[key];
144 mhash->bucket[key] = mentry;
147 #ifdef DEBUG
148 /* Prints a metaentry */
149 static void
150 mentry_print(const struct metaentry *mentry)
152 int i;
154 if (!mentry || !mentry->path) {
155 msg(MSG_DEBUG,
156 "Incorrect meta entry passed to printmetaentry\n");
157 return;
160 msg(MSG_DEBUG, "===========================\n");
161 msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
162 msg(MSG_DEBUG, "===========================\n");
164 msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
165 msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
166 msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
167 msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
168 msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
169 msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
170 for (i = 0; i < mentry->xattrs; i++) {
171 msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
172 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
173 msg(MSG_DEBUG, "\"\n");
176 msg(MSG_DEBUG, "===========================\n\n");
179 /* Prints all metaentries in a metaentry list */
180 static void
181 mentries_print(const struct metahash *mhash)
183 const struct metaentry *mentry;
184 int index;
186 for (index = 0; index < HASH_INDEXES; index++)
187 for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
188 mentry_print(mentry);
190 msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
192 #endif
194 /* Creates a metaentry for the file/dir/etc at path */
195 struct metaentry *
196 mentry_create(const char *path)
198 #if !defined(NO_XATTR) || !(NO_XATTR+0)
199 ssize_t lsize, vsize;
200 char *list, *attr;
201 #endif /* !NO_XATTR */
202 struct stat sbuf;
203 struct passwd *pbuf;
204 struct group *gbuf;
205 #if !defined(NO_XATTR) || !(NO_XATTR+0)
206 int i;
207 #endif /* !NO_XATTR */
208 struct metaentry *mentry;
210 if (lstat(path, &sbuf)) {
211 msg(MSG_ERROR, "lstat failed for %s: %s\n",
212 path, strerror(errno));
213 return NULL;
216 pbuf = xgetpwuid(sbuf.st_uid);
217 if (!pbuf) {
218 msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
219 path, (int)sbuf.st_uid);
220 return NULL;
223 gbuf = xgetgrgid(sbuf.st_gid);
224 if (!gbuf) {
225 msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
226 path, (int)sbuf.st_gid);
227 return NULL;
230 mentry = mentry_alloc();
231 mentry->path = xstrdup(path);
232 mentry->pathlen = strlen(mentry->path);
233 mentry->owner = xstrdup(pbuf->pw_name);
234 mentry->group = xstrdup(gbuf->gr_name);
235 mentry->mode = sbuf.st_mode & 0177777;
236 mentry->mtime = sbuf.st_mtim.tv_sec;
237 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
239 /* symlinks have no xattrs */
240 if (S_ISLNK(mentry->mode))
241 return mentry;
243 #if !defined(NO_XATTR) || !(NO_XATTR+0)
244 lsize = listxattr(path, NULL, 0);
245 if (lsize < 0) {
246 /* Perhaps the FS doesn't support xattrs? */
247 if (errno == ENOTSUP)
248 return mentry;
250 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
251 path, strerror(errno));
252 return NULL;
255 list = xmalloc(lsize);
256 lsize = listxattr(path, list, lsize);
257 if (lsize < 0) {
258 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
259 path, strerror(errno));
260 free(list);
261 return NULL;
264 i = 0;
265 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
266 if (*attr == '\0')
267 continue;
268 i++;
271 if (i == 0)
272 return mentry;
274 mentry->xattrs = i;
275 mentry->xattr_names = xmalloc(i * sizeof(char *));
276 mentry->xattr_values = xmalloc(i * sizeof(char *));
277 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
279 i = 0;
280 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
281 if (*attr == '\0')
282 continue;
284 mentry->xattr_names[i] = xstrdup(attr);
285 mentry->xattr_values[i] = NULL;
287 vsize = getxattr(path, attr, NULL, 0);
288 if (vsize < 0) {
289 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
290 path, strerror(errno));
291 free(list);
292 mentry->xattrs = i + 1;
293 mentry_free(mentry);
294 return NULL;
297 mentry->xattr_lvalues[i] = vsize;
298 mentry->xattr_values[i] = xmalloc(vsize);
300 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
301 if (vsize < 0) {
302 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
303 path, strerror(errno));
304 free(list);
305 mentry->xattrs = i + 1;
306 mentry_free(mentry);
307 return NULL;
309 i++;
312 free(list);
313 #endif /* !NO_XATTR */
315 return mentry;
318 /* Cleans up a path and makes it relative to cwd unless it is absolute */
319 static char *
320 normalize_path(const char *orig)
322 char *real = realpath(orig, NULL);
323 char cwd[PATH_MAX];
324 char *result;
325 size_t cwdlen;
327 getcwd(cwd, PATH_MAX);
328 if (!real)
329 return NULL;
331 cwdlen = strlen(cwd);
332 /* If CWD=="/", make cwdlen=0 to not omit slash when building rel path. */
333 cwdlen -= !strcmp("/", cwd);
335 if (!strncmp(real, cwd, cwdlen)) {
336 result = xmalloc(strlen(real) - cwdlen + 1 + 1);
337 result[0] = '\0';
338 strcat(result, ".");
339 strcat(result, real + cwdlen);
340 } else {
341 result = xstrdup(real);
344 free(real);
345 return result;
348 /* Internal function for the recursive path walk */
349 static void
350 mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
352 struct stat sbuf;
353 struct metaentry *mentry;
354 char tpath[PATH_MAX];
355 DIR *dir;
356 struct dirent *dent;
358 if (!path)
359 return;
361 if (lstat(path, &sbuf)) {
362 msg(MSG_ERROR, "lstat failed for %s: %s\n",
363 path, strerror(errno));
364 return;
367 mentry = mentry_create(path);
368 if (!mentry)
369 return;
371 mentry_insert(mentry, mhash);
373 if (S_ISDIR(sbuf.st_mode)) {
374 dir = opendir(path);
375 if (!dir) {
376 msg(MSG_ERROR, "opendir failed for %s: %s\n",
377 path, strerror(errno));
378 return;
381 while ((dent = readdir(dir))) {
382 if (!strcmp(dent->d_name, ".") ||
383 !strcmp(dent->d_name, "..") ||
384 (!st->do_git && !strcmp(dent->d_name, ".git"))
386 continue;
387 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
388 tpath[PATH_MAX - 1] = '\0';
389 mentries_recurse(tpath, mhash, st);
392 closedir(dir);
396 /* Recurses opath and adds metadata entries to the metaentry list */
397 void
398 mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
400 char *path = normalize_path(opath);
402 if (!(*mhash))
403 *mhash = mhash_alloc();
404 mentries_recurse(path, *mhash, st);
405 free(path);
408 /* Stores metaentries to a file */
409 void
410 mentries_tofile(const struct metahash *mhash, const char *path)
412 FILE *to;
413 const struct metaentry *mentry;
414 int key;
415 unsigned i;
417 to = fopen(path, "w");
418 if (!to) {
419 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
420 path, strerror(errno));
421 exit(EXIT_FAILURE);
424 write_binary_string(SIGNATURE, SIGNATURELEN, to);
425 write_binary_string(VERSION, VERSIONLEN, to);
427 for (key = 0; key < HASH_INDEXES; key++) {
428 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
429 write_string(mentry->path, to);
430 write_string(mentry->owner, to);
431 write_string(mentry->group, to);
432 write_int((uint64_t)mentry->mtime, 8, to);
433 write_int((uint64_t)mentry->mtimensec, 8, to);
434 write_int((uint64_t)mentry->mode, 2, to);
435 write_int(mentry->xattrs, 4, to);
436 for (i = 0; i < mentry->xattrs; i++) {
437 write_string(mentry->xattr_names[i], to);
438 write_int(mentry->xattr_lvalues[i], 4, to);
439 write_binary_string(mentry->xattr_values[i],
440 mentry->xattr_lvalues[i], to);
445 fclose(to);
448 /* Creates a metaentry list from a file */
449 void
450 mentries_fromfile(struct metahash **mhash, const char *path)
452 struct metaentry *mentry;
453 char *mmapstart;
454 char *ptr;
455 char *max;
456 int fd;
457 struct stat sbuf;
458 unsigned i;
460 if (!(*mhash))
461 *mhash = mhash_alloc();
463 fd = open(path, O_RDONLY);
464 if (fd < 0) {
465 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
466 path, strerror(errno));
467 exit(EXIT_FAILURE);
470 if (fstat(fd, &sbuf)) {
471 msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
472 path, strerror(errno));
473 exit(EXIT_FAILURE);
476 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
477 msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
478 exit(EXIT_FAILURE);
481 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
482 MAP_SHARED, fd, 0);
483 if (mmapstart == MAP_FAILED) {
484 msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
485 path, strerror(errno));
486 exit(EXIT_FAILURE);
488 ptr = mmapstart;
489 max = mmapstart + sbuf.st_size;
491 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
492 msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
493 goto out;
495 ptr += SIGNATURELEN;
497 if (strncmp(ptr, VERSION, VERSIONLEN)) {
498 msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
499 goto out;
501 ptr += VERSIONLEN;
503 while (ptr < mmapstart + sbuf.st_size) {
504 if (*ptr == '\0') {
505 msg(MSG_CRITICAL, "Invalid characters in file %s\n",
506 path);
507 goto out;
510 mentry = mentry_alloc();
511 mentry->path = read_string(&ptr, max);
512 mentry->pathlen = strlen(mentry->path);
513 mentry->owner = read_string(&ptr, max);
514 mentry->group = read_string(&ptr, max);
515 mentry->mtime = (time_t)read_int(&ptr, 8, max);
516 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
517 mentry->mode = (mode_t)read_int(&ptr, 2, max);
518 mentry->xattrs = (unsigned)read_int(&ptr, 4, max);
520 if (!mentry->xattrs) {
521 mentry_insert(mentry, *mhash);
522 continue;
525 mentry->xattr_names = xmalloc(mentry->xattrs * sizeof(char *));
526 mentry->xattr_lvalues = xmalloc(mentry->xattrs * sizeof(ssize_t));
527 mentry->xattr_values = xmalloc(mentry->xattrs * sizeof(char *));
529 for (i = 0; i < mentry->xattrs; i++) {
530 mentry->xattr_names[i] = read_string(&ptr, max);
531 mentry->xattr_lvalues[i] = (int)read_int(&ptr, 4, max);
532 mentry->xattr_values[i] = read_binary_string(
533 &ptr,
534 mentry->xattr_lvalues[i],
538 mentry_insert(mentry, *mhash);
541 out:
542 munmap(mmapstart, sbuf.st_size);
543 close(fd);
546 /* Searches haystack for an xattr matching xattr number n in needle */
548 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
549 unsigned n)
551 unsigned i;
553 for (i = 0; i < haystack->xattrs; i++) {
554 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
555 continue;
556 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
557 return -1;
558 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n],
559 needle->xattr_lvalues[n])
561 return -1;
562 return i;
564 return -1;
567 /* Returns zero if all xattrs in left and right match */
568 static int
569 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
571 unsigned i;
573 if (left->xattrs != right->xattrs)
574 return 1;
576 /* Make sure all xattrs in left are found in right and vice versa */
577 for (i = 0; i < left->xattrs; i++) {
578 if ( mentry_find_xattr(right, left, i) < 0
579 || mentry_find_xattr(left, right, i) < 0
581 return 1;
585 return 0;
588 /* Compares two metaentries and returns an int with a bitmask of differences */
590 mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
592 int retval = DIFF_NONE;
594 if (!left || !right) {
595 msg(MSG_ERROR, "%s called with empty list\n", __func__);
596 return -1;
599 if (strcmp(left->path, right->path))
600 return -1;
602 if (strcmp(left->owner, right->owner))
603 retval |= DIFF_OWNER;
605 if (strcmp(left->group, right->group))
606 retval |= DIFF_GROUP;
608 if ((left->mode & 07777) != (right->mode & 07777))
609 retval |= DIFF_MODE;
611 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
612 retval |= DIFF_TYPE;
614 if (st->do_mtime && strcmp(left->path, st->metafile) &&
615 ( left->mtime != right->mtime
616 || left->mtimensec != right->mtimensec)
618 retval |= DIFF_MTIME;
620 if (mentry_compare_xattr(left, right)) {
621 retval |= DIFF_XATTR;
622 return retval;
625 return retval;
628 /* Compares lists of real and stored metadata and calls pfunc for each */
629 void
630 mentries_compare(struct metahash *mhashreal,
631 struct metahash *mhashstored,
632 void (*pfunc)
633 (struct metaentry *real, struct metaentry *stored, int cmp),
634 msettings *st)
636 struct metaentry *real, *stored;
637 int key;
639 if (!mhashreal || !mhashstored) {
640 msg(MSG_ERROR, "%s called with empty list\n", __func__);
641 return;
644 for (key = 0; key < HASH_INDEXES; key++) {
645 for (real = mhashreal->bucket[key]; real; real = real->next) {
646 stored = mentry_find(real->path, mhashstored);
648 if (!stored)
649 pfunc(real, NULL, DIFF_ADDED);
650 else
651 pfunc(real, stored, mentry_compare(real, stored, st));
654 for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
655 real = mentry_find(stored->path, mhashreal);
657 if (!real)
658 pfunc(NULL, stored, DIFF_DELE);
663 /* Dumps given metadata */
664 void
665 mentries_dump(struct metahash *mhash)
667 const struct metaentry *mentry;
668 char mode[11 + 1] = "";
669 char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = "";
670 char zone[5 + 1] = "";
671 struct tm cal;
673 for (int key = 0; key < HASH_INDEXES; key++) {
674 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
675 strmode(mentry->mode, mode);
676 localtime_r(&mentry->mtime, &cal);
677 strftime(date, sizeof(date), "%F %T", &cal);
678 strftime(zone, sizeof(zone), "%z", &cal);
679 printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n",
680 mode,
681 mentry->owner, mentry->group,
682 date, mentry->mtimensec, zone,
683 mentry->path, S_ISDIR(mentry->mode) ? "/" : "");
684 for (unsigned i = 0; i < mentry->xattrs; i++) {
685 printf("\t\t\t\t%s%s\t%s=",
686 mentry->path, S_ISDIR(mentry->mode) ? "/" : "",
687 mentry->xattr_names[i]);
688 ssize_t p = 0;
689 for (; p < mentry->xattr_lvalues[i]; p++) {
690 const char ch = mentry->xattr_values[i][p];
691 if ((unsigned)(ch - 32) > 126 - 32) {
692 p = -1;
693 break;
696 if (p >= 0)
697 printf("\"%.*s\"\n",
698 (int)mentry->xattr_lvalues[i],
699 mentry->xattr_values[i]);
700 else {
701 printf("0x");
702 for (p = 0; p < mentry->xattr_lvalues[i]; p++)
703 printf("%02hhx", (char)mentry->xattr_values[i][p]);
704 printf("\n");