Makefile: Change default installation prefix to /usr/local.
[metastore.git] / src / metaentry.c
blobf3ba692739f2475f18ad38dd9b94d5dbc39ee556
1 /*
2 * Various functions to work with meta entries.
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; only version 2 of the License is applicable.
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.
20 #define _GNU_SOURCE
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <sys/xattr.h>
28 #include <limits.h>
29 #include <dirent.h>
30 #include <sys/mman.h>
31 #include <utime.h>
32 #include <fcntl.h>
33 #include <stdint.h>
34 #include <errno.h>
35 #include <bsd/string.h>
36 #include <time.h>
38 #include "metastore.h"
39 #include "metaentry.h"
40 #include "utils.h"
42 /* Free's a metaentry and all its parameters */
43 static void
44 mentry_free(struct metaentry *m)
46 unsigned i;
48 if (!m)
49 return;
51 free(m->path);
52 free(m->owner);
53 free(m->group);
55 for (i = 0; i < m->xattrs; i++) {
56 free(m->xattr_names[i]);
57 free(m->xattr_values[i]);
60 free(m->xattr_names);
61 free(m->xattr_values);
62 free(m->xattr_lvalues);
64 free(m);
67 /* Allocates an empty metahash table */
68 static struct metahash *
69 mhash_alloc()
71 struct metahash *mhash;
72 mhash = xmalloc(sizeof(struct metahash));
73 memset(mhash, 0, sizeof(struct metahash));
74 return mhash;
77 /* Generates a hash key (using djb2) */
78 static unsigned int
79 hash(const char *str)
81 unsigned int hash = 5381;
82 int c;
84 while ((c = *str++))
85 hash = ((hash << 5) + hash) + c;
87 return hash % HASH_INDEXES;
90 /* Allocates an empty metaentry */
91 static struct metaentry *
92 mentry_alloc()
94 struct metaentry *mentry;
95 mentry = xmalloc(sizeof(struct metaentry));
96 memset(mentry, 0, sizeof(struct metaentry));
97 return mentry;
100 /* Does a bisect search for the closest match in a metaentry list */
101 struct metaentry *
102 mentry_find(const char *path, struct metahash *mhash)
104 struct metaentry *base;
105 unsigned int key;
107 if (!mhash) {
108 msg(MSG_ERROR, "%s called with empty hash table\n", __FUNCTION__);
109 return NULL;
112 key = hash(path);
113 for (base = mhash->bucket[key]; base; base = base->next) {
114 if (!strcmp(base->path, path))
115 return base;
118 return NULL;
121 /* Inserts a metaentry into a metaentry list */
122 static void
123 mentry_insert(struct metaentry *mentry, struct metahash *mhash)
125 unsigned int key;
127 key = hash(mentry->path);
128 mentry->next = mhash->bucket[key];
129 mhash->bucket[key] = mentry;
132 #ifdef DEBUG
133 /* Prints a metaentry */
134 static void
135 mentry_print(const struct metaentry *mentry)
137 int i;
139 if (!mentry || !mentry->path) {
140 msg(MSG_DEBUG,
141 "Incorrect meta entry passed to printmetaentry\n");
142 return;
145 msg(MSG_DEBUG, "===========================\n");
146 msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
147 msg(MSG_DEBUG, "===========================\n");
149 msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
150 msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
151 msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
152 msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
153 msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
154 msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
155 for (i = 0; i < mentry->xattrs; i++) {
156 msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
157 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
158 msg(MSG_DEBUG, "\"\n");
161 msg(MSG_DEBUG, "===========================\n\n");
164 /* Prints all metaentries in a metaentry list */
165 static void
166 mentries_print(const struct metahash *mhash)
168 const struct metaentry *mentry;
169 int index;
171 for (index = 0; index < HASH_INDEXES; index++)
172 for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
173 mentry_print(mentry);
175 msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
177 #endif
179 /* Creates a metaentry for the file/dir/etc at path */
180 struct metaentry *
181 mentry_create(const char *path)
183 ssize_t lsize, vsize;
184 char *list, *attr;
185 struct stat sbuf;
186 struct passwd *pbuf;
187 struct group *gbuf;
188 int i;
189 struct metaentry *mentry;
191 if (lstat(path, &sbuf)) {
192 msg(MSG_ERROR, "lstat failed for %s: %s\n",
193 path, strerror(errno));
194 return NULL;
197 pbuf = xgetpwuid(sbuf.st_uid);
198 if (!pbuf) {
199 msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
200 path, (int)sbuf.st_uid);
201 return NULL;
204 gbuf = xgetgrgid(sbuf.st_gid);
205 if (!gbuf) {
206 msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
207 path, (int)sbuf.st_gid);
208 return NULL;
211 mentry = mentry_alloc();
212 mentry->path = xstrdup(path);
213 mentry->pathlen = strlen(mentry->path);
214 mentry->owner = xstrdup(pbuf->pw_name);
215 mentry->group = xstrdup(gbuf->gr_name);
216 mentry->mode = sbuf.st_mode & 0177777;
217 mentry->mtime = sbuf.st_mtim.tv_sec;
218 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
220 /* symlinks have no xattrs */
221 if (S_ISLNK(mentry->mode))
222 return mentry;
224 lsize = listxattr(path, NULL, 0);
225 if (lsize < 0) {
226 /* Perhaps the FS doesn't support xattrs? */
227 if (errno == ENOTSUP)
228 return mentry;
230 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
231 path, strerror(errno));
232 return NULL;
235 list = xmalloc(lsize);
236 lsize = listxattr(path, list, lsize);
237 if (lsize < 0) {
238 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
239 path, strerror(errno));
240 free(list);
241 return NULL;
244 i = 0;
245 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
246 if (*attr == '\0')
247 continue;
248 i++;
251 if (i == 0)
252 return mentry;
254 mentry->xattrs = i;
255 mentry->xattr_names = xmalloc(i * sizeof(char *));
256 mentry->xattr_values = xmalloc(i * sizeof(char *));
257 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
259 i = 0;
260 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
261 if (*attr == '\0')
262 continue;
264 mentry->xattr_names[i] = xstrdup(attr);
265 vsize = getxattr(path, attr, NULL, 0);
266 if (vsize < 0) {
267 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
268 path, strerror(errno));
269 free(list);
270 mentry_free(mentry);
271 return NULL;
274 mentry->xattr_lvalues[i] = vsize;
275 mentry->xattr_values[i] = xmalloc(vsize);
277 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
278 if (vsize < 0) {
279 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
280 path, strerror(errno));
281 free(list);
282 mentry_free(mentry);
283 return NULL;
285 i++;
288 free(list);
289 return mentry;
292 /* Cleans up a path and makes it relative to current working dir unless it is absolute */
293 static char *
294 normalize_path(const char *orig)
296 char *real = realpath(orig, NULL);
297 char cwd[PATH_MAX];
298 char *result;
300 getcwd(cwd, PATH_MAX);
301 if (!real)
302 return NULL;
304 if (!strncmp(real, cwd, strlen(cwd))) {
305 result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
306 result[0] = '\0';
307 strcat(result, ".");
308 strcat(result, real + strlen(cwd));
309 } else {
310 result = xstrdup(real);
313 free(real);
314 return result;
317 /* Internal function for the recursive path walk */
318 static void
319 mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
321 struct stat sbuf;
322 struct metaentry *mentry;
323 char tpath[PATH_MAX];
324 DIR *dir;
325 struct dirent *dent;
327 if (!path)
328 return;
330 if (lstat(path, &sbuf)) {
331 msg(MSG_ERROR, "lstat failed for %s: %s\n",
332 path, strerror(errno));
333 return;
336 mentry = mentry_create(path);
337 if (!mentry)
338 return;
340 mentry_insert(mentry, mhash);
342 if (S_ISDIR(sbuf.st_mode)) {
343 dir = opendir(path);
344 if (!dir) {
345 msg(MSG_ERROR, "opendir failed for %s: %s\n",
346 path, strerror(errno));
347 return;
350 while ((dent = readdir(dir))) {
351 if (!strcmp(dent->d_name, ".") ||
352 !strcmp(dent->d_name, "..") ||
353 (!st->do_git && !strcmp(dent->d_name, ".git")))
354 continue;
355 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
356 tpath[PATH_MAX - 1] = '\0';
357 mentries_recurse(tpath, mhash, st);
360 closedir(dir);
364 /* Recurses opath and adds metadata entries to the metaentry list */
365 void
366 mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
368 char *path = normalize_path(opath);
370 if (!(*mhash))
371 *mhash = mhash_alloc();
372 mentries_recurse(path, *mhash, st);
373 free(path);
376 /* Stores metaentries to a file */
377 void
378 mentries_tofile(const struct metahash *mhash, const char *path)
380 FILE *to;
381 const struct metaentry *mentry;
382 int key;
383 unsigned i;
385 to = fopen(path, "w");
386 if (!to) {
387 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
388 path, strerror(errno));
389 exit(EXIT_FAILURE);
392 write_binary_string(SIGNATURE, SIGNATURELEN, to);
393 write_binary_string(VERSION, VERSIONLEN, to);
395 for (key = 0; key < HASH_INDEXES; key++) {
396 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
397 write_string(mentry->path, to);
398 write_string(mentry->owner, to);
399 write_string(mentry->group, to);
400 write_int((uint64_t)mentry->mtime, 8, to);
401 write_int((uint64_t)mentry->mtimensec, 8, to);
402 write_int((uint64_t)mentry->mode, 2, to);
403 write_int(mentry->xattrs, 4, to);
404 for (i = 0; i < mentry->xattrs; i++) {
405 write_string(mentry->xattr_names[i], to);
406 write_int(mentry->xattr_lvalues[i], 4, to);
407 write_binary_string(mentry->xattr_values[i],
408 mentry->xattr_lvalues[i], to);
413 fclose(to);
416 /* Creates a metaentry list from a file */
417 void
418 mentries_fromfile(struct metahash **mhash, const char *path)
420 struct metaentry *mentry;
421 char *mmapstart;
422 char *ptr;
423 char *max;
424 int fd;
425 struct stat sbuf;
426 unsigned i;
428 if (!(*mhash))
429 *mhash = mhash_alloc();
431 fd = open(path, O_RDONLY);
432 if (fd < 0) {
433 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
434 path, strerror(errno));
435 exit(EXIT_FAILURE);
438 if (fstat(fd, &sbuf)) {
439 msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
440 path, strerror(errno));
441 exit(EXIT_FAILURE);
444 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
445 msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
446 exit(EXIT_FAILURE);
449 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
450 MAP_SHARED, fd, 0);
451 if (mmapstart == MAP_FAILED) {
452 msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
453 path, strerror(errno));
454 exit(EXIT_FAILURE);
456 ptr = mmapstart;
457 max = mmapstart + sbuf.st_size;
459 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
460 msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
461 goto out;
463 ptr += SIGNATURELEN;
465 if (strncmp(ptr, VERSION, VERSIONLEN)) {
466 msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
467 goto out;
469 ptr += VERSIONLEN;
471 while (ptr < mmapstart + sbuf.st_size) {
472 if (*ptr == '\0') {
473 msg(MSG_CRITICAL, "Invalid characters in file %s\n",
474 path);
475 goto out;
478 mentry = mentry_alloc();
479 mentry->path = read_string(&ptr, max);
480 mentry->pathlen = strlen(mentry->path);
481 mentry->owner = read_string(&ptr, max);
482 mentry->group = read_string(&ptr, max);
483 mentry->mtime = (time_t)read_int(&ptr, 8, max);
484 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
485 mentry->mode = (mode_t)read_int(&ptr, 2, max);
486 mentry->xattrs = (unsigned int)read_int(&ptr, 4, max);
488 if (!mentry->xattrs) {
489 mentry_insert(mentry, *mhash);
490 continue;
493 mentry->xattr_names = xmalloc(mentry->xattrs *
494 sizeof(char *));
495 mentry->xattr_lvalues = xmalloc(mentry->xattrs *
496 sizeof(int));
497 mentry->xattr_values = xmalloc(mentry->xattrs *
498 sizeof(char *));
500 for (i = 0; i < mentry->xattrs; i++) {
501 mentry->xattr_names[i] = read_string(&ptr, max);
502 mentry->xattr_lvalues[i] =
503 (int)read_int(&ptr, 4, max);
504 mentry->xattr_values[i] =
505 read_binary_string(&ptr,
506 mentry->xattr_lvalues[i],
507 max);
509 mentry_insert(mentry, *mhash);
512 out:
513 munmap(mmapstart, sbuf.st_size);
514 close(fd);
517 /* Searches haystack for an xattr matching xattr number n in needle */
519 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
520 unsigned n)
522 unsigned i;
524 for (i = 0; i < haystack->xattrs; i++) {
525 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
526 continue;
527 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
528 return -1;
529 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n],
530 needle->xattr_lvalues[n]))
531 return -1;
532 return i;
534 return -1;
537 /* Returns zero if all xattrs in left and right match */
538 static int
539 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
541 unsigned i;
543 if (left->xattrs != right->xattrs)
544 return 1;
546 /* Make sure all xattrs in left are found in right and vice versa */
547 for (i = 0; i < left->xattrs; i++) {
548 if (mentry_find_xattr(right, left, i) < 0 ||
549 mentry_find_xattr(left, right, i) < 0) {
550 return 1;
554 return 0;
557 /* Compares two metaentries and returns an int with a bitmask of differences */
559 mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
561 int retval = DIFF_NONE;
563 if (!left || !right) {
564 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
565 return -1;
568 if (strcmp(left->path, right->path))
569 return -1;
571 if (strcmp(left->owner, right->owner))
572 retval |= DIFF_OWNER;
574 if (strcmp(left->group, right->group))
575 retval |= DIFF_GROUP;
577 if ((left->mode & 07777) != (right->mode & 07777))
578 retval |= DIFF_MODE;
580 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
581 retval |= DIFF_TYPE;
583 if (st->do_mtime && strcmp(left->path, st->metafile) &&
584 (left->mtime != right->mtime ||
585 left->mtimensec != right->mtimensec))
586 retval |= DIFF_MTIME;
588 if (mentry_compare_xattr(left, right)) {
589 retval |= DIFF_XATTR;
590 return retval;
593 return retval;
596 /* Compares lists of real and stored metadata and calls pfunc for each */
597 void
598 mentries_compare(struct metahash *mhashreal,
599 struct metahash *mhashstored,
600 void (*pfunc)
601 (struct metaentry *real, struct metaentry *stored, int cmp),
602 msettings *st)
604 struct metaentry *real, *stored;
605 int key;
607 if (!mhashreal || !mhashstored) {
608 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
609 return;
612 for (key = 0; key < HASH_INDEXES; key++) {
613 for (real = mhashreal->bucket[key]; real; real = real->next) {
614 stored = mentry_find(real->path, mhashstored);
616 if (!stored)
617 pfunc(real, NULL, DIFF_ADDED);
618 else
619 pfunc(real, stored, mentry_compare(real, stored, st));
622 for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
623 real = mentry_find(stored->path, mhashreal);
625 if (!real)
626 pfunc(NULL, stored, DIFF_DELE);
631 /* Dumps given metadata */
632 void
633 mentries_dump(struct metahash *mhash)
635 const struct metaentry *mentry;
636 char mode[11 + 1] = "";
637 char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = "";
638 char zone[5 + 1] = "";
639 struct tm cal;
641 for (int key = 0; key < HASH_INDEXES; key++) {
642 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
643 strmode(mentry->mode, mode);
644 localtime_r(&mentry->mtime, &cal);
645 strftime(date, sizeof(date), "%F %T", &cal);
646 strftime(zone, sizeof(zone), "%z", &cal);
647 printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n",
648 mode,
649 mentry->owner, mentry->group,
650 date, mentry->mtimensec, zone,
651 mentry->path, S_ISDIR(mentry->mode) ? "/" : "");
652 for (int i = 0; i < mentry->xattrs; i++) {
653 printf("\t\t\t\t%s%s\t%s=",
654 mentry->path, S_ISDIR(mentry->mode) ? "/" : "",
655 mentry->xattr_names[i]);
656 ssize_t p = 0;
657 for (; p < mentry->xattr_lvalues[i]; p++) {
658 const char ch = mentry->xattr_values[i][p];
659 if ((unsigned)(ch - 32) > 126 - 32) {
660 p = -1;
661 break;
664 if (p >= 0)
665 printf("\"%.*s\"\n",
666 (int)mentry->xattr_lvalues[i],
667 mentry->xattr_values[i]);
668 else {
669 printf("0x");
670 for (p = 0; p < mentry->xattr_lvalues[i]; p++)
671 printf("%02hhx", (char)mentry->xattr_values[i][p]);
672 printf("\n");