metaentry.c: Define PATH_MAX if it's undefined.
[metastore.git] / src / metaentry.c
blobc33d7e2b3358a3995cf914390ed1cc6172aefefc
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 <time.h>
37 #include <sys/param.h>
38 #ifndef BSD
39 # include <bsd/string.h>
40 #endif
42 #include "metastore.h"
43 #include "metaentry.h"
44 #include "utils.h"
46 #ifndef PATH_MAX
47 # define PATH_MAX 4096
48 #endif
50 /* Free's a metaentry and all its parameters */
51 static void
52 mentry_free(struct metaentry *m)
54 unsigned i;
56 if (!m)
57 return;
59 free(m->path);
60 free(m->owner);
61 free(m->group);
63 for (i = 0; i < m->xattrs; i++) {
64 free(m->xattr_names[i]);
65 free(m->xattr_values[i]);
68 free(m->xattr_names);
69 free(m->xattr_values);
70 free(m->xattr_lvalues);
72 free(m);
75 /* Allocates an empty metahash table */
76 static struct metahash *
77 mhash_alloc()
79 struct metahash *mhash;
80 mhash = xmalloc(sizeof(struct metahash));
81 memset(mhash, 0, sizeof(struct metahash));
82 return mhash;
85 /* Generates a hash key (using djb2) */
86 static unsigned
87 hash(const char *str)
89 unsigned hash = 5381;
90 int c;
92 while ((c = *str++))
93 hash = ((hash << 5) + hash) + c;
95 return hash % HASH_INDEXES;
98 /* Allocates an empty metaentry */
99 static struct metaentry *
100 mentry_alloc()
102 struct metaentry *mentry;
103 mentry = xmalloc(sizeof(struct metaentry));
104 memset(mentry, 0, sizeof(struct metaentry));
105 return mentry;
108 /* Does a bisect search for the closest match in a metaentry list */
109 struct metaentry *
110 mentry_find(const char *path, struct metahash *mhash)
112 struct metaentry *base;
113 unsigned key;
115 if (!mhash) {
116 msg(MSG_ERROR, "%s called with empty hash table\n", __func__);
117 return NULL;
120 key = hash(path);
121 for (base = mhash->bucket[key]; base; base = base->next) {
122 if (!strcmp(base->path, path))
123 return base;
126 return NULL;
129 /* Inserts a metaentry into a metaentry list */
130 static void
131 mentry_insert(struct metaentry *mentry, struct metahash *mhash)
133 unsigned key;
135 key = hash(mentry->path);
136 mentry->next = mhash->bucket[key];
137 mhash->bucket[key] = mentry;
140 #ifdef DEBUG
141 /* Prints a metaentry */
142 static void
143 mentry_print(const struct metaentry *mentry)
145 int i;
147 if (!mentry || !mentry->path) {
148 msg(MSG_DEBUG,
149 "Incorrect meta entry passed to printmetaentry\n");
150 return;
153 msg(MSG_DEBUG, "===========================\n");
154 msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
155 msg(MSG_DEBUG, "===========================\n");
157 msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
158 msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
159 msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
160 msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
161 msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
162 msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
163 for (i = 0; i < mentry->xattrs; i++) {
164 msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
165 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
166 msg(MSG_DEBUG, "\"\n");
169 msg(MSG_DEBUG, "===========================\n\n");
172 /* Prints all metaentries in a metaentry list */
173 static void
174 mentries_print(const struct metahash *mhash)
176 const struct metaentry *mentry;
177 int index;
179 for (index = 0; index < HASH_INDEXES; index++)
180 for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
181 mentry_print(mentry);
183 msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
185 #endif
187 /* Creates a metaentry for the file/dir/etc at path */
188 struct metaentry *
189 mentry_create(const char *path)
191 ssize_t lsize, vsize;
192 char *list, *attr;
193 struct stat sbuf;
194 struct passwd *pbuf;
195 struct group *gbuf;
196 int i;
197 struct metaentry *mentry;
199 if (lstat(path, &sbuf)) {
200 msg(MSG_ERROR, "lstat failed for %s: %s\n",
201 path, strerror(errno));
202 return NULL;
205 pbuf = xgetpwuid(sbuf.st_uid);
206 if (!pbuf) {
207 msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
208 path, (int)sbuf.st_uid);
209 return NULL;
212 gbuf = xgetgrgid(sbuf.st_gid);
213 if (!gbuf) {
214 msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
215 path, (int)sbuf.st_gid);
216 return NULL;
219 mentry = mentry_alloc();
220 mentry->path = xstrdup(path);
221 mentry->pathlen = strlen(mentry->path);
222 mentry->owner = xstrdup(pbuf->pw_name);
223 mentry->group = xstrdup(gbuf->gr_name);
224 mentry->mode = sbuf.st_mode & 0177777;
225 mentry->mtime = sbuf.st_mtim.tv_sec;
226 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
228 /* symlinks have no xattrs */
229 if (S_ISLNK(mentry->mode))
230 return mentry;
232 lsize = listxattr(path, NULL, 0);
233 if (lsize < 0) {
234 /* Perhaps the FS doesn't support xattrs? */
235 if (errno == ENOTSUP)
236 return mentry;
238 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
239 path, strerror(errno));
240 return NULL;
243 list = xmalloc(lsize);
244 lsize = listxattr(path, list, lsize);
245 if (lsize < 0) {
246 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
247 path, strerror(errno));
248 free(list);
249 return NULL;
252 i = 0;
253 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
254 if (*attr == '\0')
255 continue;
256 i++;
259 if (i == 0)
260 return mentry;
262 mentry->xattrs = i;
263 mentry->xattr_names = xmalloc(i * sizeof(char *));
264 mentry->xattr_values = xmalloc(i * sizeof(char *));
265 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
267 i = 0;
268 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
269 if (*attr == '\0')
270 continue;
272 mentry->xattr_names[i] = xstrdup(attr);
273 mentry->xattr_values[i] = NULL;
275 vsize = getxattr(path, attr, NULL, 0);
276 if (vsize < 0) {
277 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
278 path, strerror(errno));
279 free(list);
280 mentry->xattrs = i + 1;
281 mentry_free(mentry);
282 return NULL;
285 mentry->xattr_lvalues[i] = vsize;
286 mentry->xattr_values[i] = xmalloc(vsize);
288 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
289 if (vsize < 0) {
290 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
291 path, strerror(errno));
292 free(list);
293 mentry->xattrs = i + 1;
294 mentry_free(mentry);
295 return NULL;
297 i++;
300 free(list);
301 return mentry;
304 /* Cleans up a path and makes it relative to cwd unless it is absolute */
305 static char *
306 normalize_path(const char *orig)
308 char *real = realpath(orig, NULL);
309 char cwd[PATH_MAX];
310 char *result;
312 getcwd(cwd, PATH_MAX);
313 if (!real)
314 return NULL;
316 if (!strncmp(real, cwd, strlen(cwd))) {
317 result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
318 result[0] = '\0';
319 strcat(result, ".");
320 strcat(result, real + strlen(cwd));
321 } else {
322 result = xstrdup(real);
325 free(real);
326 return result;
329 /* Internal function for the recursive path walk */
330 static void
331 mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
333 struct stat sbuf;
334 struct metaentry *mentry;
335 char tpath[PATH_MAX];
336 DIR *dir;
337 struct dirent *dent;
339 if (!path)
340 return;
342 if (lstat(path, &sbuf)) {
343 msg(MSG_ERROR, "lstat failed for %s: %s\n",
344 path, strerror(errno));
345 return;
348 mentry = mentry_create(path);
349 if (!mentry)
350 return;
352 mentry_insert(mentry, mhash);
354 if (S_ISDIR(sbuf.st_mode)) {
355 dir = opendir(path);
356 if (!dir) {
357 msg(MSG_ERROR, "opendir failed for %s: %s\n",
358 path, strerror(errno));
359 return;
362 while ((dent = readdir(dir))) {
363 if (!strcmp(dent->d_name, ".") ||
364 !strcmp(dent->d_name, "..") ||
365 (!st->do_git && !strcmp(dent->d_name, ".git"))
367 continue;
368 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
369 tpath[PATH_MAX - 1] = '\0';
370 mentries_recurse(tpath, mhash, st);
373 closedir(dir);
377 /* Recurses opath and adds metadata entries to the metaentry list */
378 void
379 mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
381 char *path = normalize_path(opath);
383 if (!(*mhash))
384 *mhash = mhash_alloc();
385 mentries_recurse(path, *mhash, st);
386 free(path);
389 /* Stores metaentries to a file */
390 void
391 mentries_tofile(const struct metahash *mhash, const char *path)
393 FILE *to;
394 const struct metaentry *mentry;
395 int key;
396 unsigned i;
398 to = fopen(path, "w");
399 if (!to) {
400 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
401 path, strerror(errno));
402 exit(EXIT_FAILURE);
405 write_binary_string(SIGNATURE, SIGNATURELEN, to);
406 write_binary_string(VERSION, VERSIONLEN, to);
408 for (key = 0; key < HASH_INDEXES; key++) {
409 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
410 write_string(mentry->path, to);
411 write_string(mentry->owner, to);
412 write_string(mentry->group, to);
413 write_int((uint64_t)mentry->mtime, 8, to);
414 write_int((uint64_t)mentry->mtimensec, 8, to);
415 write_int((uint64_t)mentry->mode, 2, to);
416 write_int(mentry->xattrs, 4, to);
417 for (i = 0; i < mentry->xattrs; i++) {
418 write_string(mentry->xattr_names[i], to);
419 write_int(mentry->xattr_lvalues[i], 4, to);
420 write_binary_string(mentry->xattr_values[i],
421 mentry->xattr_lvalues[i], to);
426 fclose(to);
429 /* Creates a metaentry list from a file */
430 void
431 mentries_fromfile(struct metahash **mhash, const char *path)
433 struct metaentry *mentry;
434 char *mmapstart;
435 char *ptr;
436 char *max;
437 int fd;
438 struct stat sbuf;
439 unsigned i;
441 if (!(*mhash))
442 *mhash = mhash_alloc();
444 fd = open(path, O_RDONLY);
445 if (fd < 0) {
446 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
447 path, strerror(errno));
448 exit(EXIT_FAILURE);
451 if (fstat(fd, &sbuf)) {
452 msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
453 path, strerror(errno));
454 exit(EXIT_FAILURE);
457 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
458 msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
459 exit(EXIT_FAILURE);
462 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
463 MAP_SHARED, fd, 0);
464 if (mmapstart == MAP_FAILED) {
465 msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
466 path, strerror(errno));
467 exit(EXIT_FAILURE);
469 ptr = mmapstart;
470 max = mmapstart + sbuf.st_size;
472 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
473 msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
474 goto out;
476 ptr += SIGNATURELEN;
478 if (strncmp(ptr, VERSION, VERSIONLEN)) {
479 msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
480 goto out;
482 ptr += VERSIONLEN;
484 while (ptr < mmapstart + sbuf.st_size) {
485 if (*ptr == '\0') {
486 msg(MSG_CRITICAL, "Invalid characters in file %s\n",
487 path);
488 goto out;
491 mentry = mentry_alloc();
492 mentry->path = read_string(&ptr, max);
493 mentry->pathlen = strlen(mentry->path);
494 mentry->owner = read_string(&ptr, max);
495 mentry->group = read_string(&ptr, max);
496 mentry->mtime = (time_t)read_int(&ptr, 8, max);
497 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
498 mentry->mode = (mode_t)read_int(&ptr, 2, max);
499 mentry->xattrs = (unsigned)read_int(&ptr, 4, max);
501 if (!mentry->xattrs) {
502 mentry_insert(mentry, *mhash);
503 continue;
506 mentry->xattr_names = xmalloc(mentry->xattrs * sizeof(char *));
507 mentry->xattr_lvalues = xmalloc(mentry->xattrs * sizeof(ssize_t));
508 mentry->xattr_values = xmalloc(mentry->xattrs * sizeof(char *));
510 for (i = 0; i < mentry->xattrs; i++) {
511 mentry->xattr_names[i] = read_string(&ptr, max);
512 mentry->xattr_lvalues[i] = (int)read_int(&ptr, 4, max);
513 mentry->xattr_values[i] = read_binary_string(
514 &ptr,
515 mentry->xattr_lvalues[i],
519 mentry_insert(mentry, *mhash);
522 out:
523 munmap(mmapstart, sbuf.st_size);
524 close(fd);
527 /* Searches haystack for an xattr matching xattr number n in needle */
529 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
530 unsigned n)
532 unsigned i;
534 for (i = 0; i < haystack->xattrs; i++) {
535 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
536 continue;
537 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
538 return -1;
539 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n],
540 needle->xattr_lvalues[n])
542 return -1;
543 return i;
545 return -1;
548 /* Returns zero if all xattrs in left and right match */
549 static int
550 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
552 unsigned i;
554 if (left->xattrs != right->xattrs)
555 return 1;
557 /* Make sure all xattrs in left are found in right and vice versa */
558 for (i = 0; i < left->xattrs; i++) {
559 if ( mentry_find_xattr(right, left, i) < 0
560 || mentry_find_xattr(left, right, i) < 0
562 return 1;
566 return 0;
569 /* Compares two metaentries and returns an int with a bitmask of differences */
571 mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
573 int retval = DIFF_NONE;
575 if (!left || !right) {
576 msg(MSG_ERROR, "%s called with empty list\n", __func__);
577 return -1;
580 if (strcmp(left->path, right->path))
581 return -1;
583 if (strcmp(left->owner, right->owner))
584 retval |= DIFF_OWNER;
586 if (strcmp(left->group, right->group))
587 retval |= DIFF_GROUP;
589 if ((left->mode & 07777) != (right->mode & 07777))
590 retval |= DIFF_MODE;
592 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
593 retval |= DIFF_TYPE;
595 if (st->do_mtime && strcmp(left->path, st->metafile) &&
596 ( left->mtime != right->mtime
597 || left->mtimensec != right->mtimensec)
599 retval |= DIFF_MTIME;
601 if (mentry_compare_xattr(left, right)) {
602 retval |= DIFF_XATTR;
603 return retval;
606 return retval;
609 /* Compares lists of real and stored metadata and calls pfunc for each */
610 void
611 mentries_compare(struct metahash *mhashreal,
612 struct metahash *mhashstored,
613 void (*pfunc)
614 (struct metaentry *real, struct metaentry *stored, int cmp),
615 msettings *st)
617 struct metaentry *real, *stored;
618 int key;
620 if (!mhashreal || !mhashstored) {
621 msg(MSG_ERROR, "%s called with empty list\n", __func__);
622 return;
625 for (key = 0; key < HASH_INDEXES; key++) {
626 for (real = mhashreal->bucket[key]; real; real = real->next) {
627 stored = mentry_find(real->path, mhashstored);
629 if (!stored)
630 pfunc(real, NULL, DIFF_ADDED);
631 else
632 pfunc(real, stored, mentry_compare(real, stored, st));
635 for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
636 real = mentry_find(stored->path, mhashreal);
638 if (!real)
639 pfunc(NULL, stored, DIFF_DELE);
644 /* Dumps given metadata */
645 void
646 mentries_dump(struct metahash *mhash)
648 const struct metaentry *mentry;
649 char mode[11 + 1] = "";
650 char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = "";
651 char zone[5 + 1] = "";
652 struct tm cal;
654 for (int key = 0; key < HASH_INDEXES; key++) {
655 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
656 strmode(mentry->mode, mode);
657 localtime_r(&mentry->mtime, &cal);
658 strftime(date, sizeof(date), "%F %T", &cal);
659 strftime(zone, sizeof(zone), "%z", &cal);
660 printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n",
661 mode,
662 mentry->owner, mentry->group,
663 date, mentry->mtimensec, zone,
664 mentry->path, S_ISDIR(mentry->mode) ? "/" : "");
665 for (unsigned i = 0; i < mentry->xattrs; i++) {
666 printf("\t\t\t\t%s%s\t%s=",
667 mentry->path, S_ISDIR(mentry->mode) ? "/" : "",
668 mentry->xattr_names[i]);
669 ssize_t p = 0;
670 for (; p < mentry->xattr_lvalues[i]; p++) {
671 const char ch = mentry->xattr_values[i][p];
672 if ((unsigned)(ch - 32) > 126 - 32) {
673 p = -1;
674 break;
677 if (p >= 0)
678 printf("\"%.*s\"\n",
679 (int)mentry->xattr_lvalues[i],
680 mentry->xattr_values[i]);
681 else {
682 printf("0x");
683 for (p = 0; p < mentry->xattr_lvalues[i]; p++)
684 printf("%02hhx", (char)mentry->xattr_values[i][p]);
685 printf("\n");