metastore.c: Improve removing empty dirs not present in metadata.
[metastore.git] / metaentry.c
blob1f45fca03e929d5bcdde0e00444fec476e16e904
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 version 2 of the License.
9 *
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.
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>
28 #include <attr/xattr.h>
29 #include <limits.h>
30 #include <dirent.h>
31 #include <sys/mman.h>
32 #include <utime.h>
33 #include <fcntl.h>
34 #include <stdint.h>
36 #include "metastore.h"
37 #include "metaentry.h"
38 #include "utils.h"
40 /* Free's a metaentry and all its parameters */
41 static void
42 mentry_free(struct metaentry *m)
44 unsigned i;
46 if (!m)
47 return;
49 free(m->path);
50 free(m->owner);
51 free(m->group);
53 for (i = 0; i < m->xattrs; i++) {
54 free(m->xattr_names[i]);
55 free(m->xattr_values[i]);
58 free(m->xattr_names);
59 free(m->xattr_values);
60 free(m->xattr_lvalues);
62 free(m);
65 /* Allocates an empty metahash table */
66 static struct metahash *
67 mhash_alloc()
69 struct metahash *mhash;
70 mhash = xmalloc(sizeof(struct metahash));
71 memset(mhash, 0, sizeof(struct metahash));
72 return mhash;
75 /* Generates a hash key (using djb2) */
76 static unsigned int
77 hash(const char *str)
79 unsigned int hash = 5381;
80 int c;
82 while ((c = *str++))
83 hash = ((hash << 5) + hash) + c;
85 return hash % HASH_INDEXES;
88 /* Allocates an empty metaentry */
89 static struct metaentry *
90 mentry_alloc()
92 struct metaentry *mentry;
93 mentry = xmalloc(sizeof(struct metaentry));
94 memset(mentry, 0, sizeof(struct metaentry));
95 return mentry;
98 /* Does a bisect search for the closest match in a metaentry list */
99 struct metaentry *
100 mentry_find(const char *path, struct metahash *mhash)
102 struct metaentry *base;
103 unsigned int key;
105 if (!mhash) {
106 msg(MSG_ERROR, "%s called with empty hash table\n", __FUNCTION__);
107 return NULL;
110 key = hash(path);
111 for (base = mhash->bucket[key]; base; base = base->next) {
112 if (!strcmp(base->path, path))
113 return base;
116 return NULL;
119 /* Inserts a metaentry into a metaentry list */
120 static void
121 mentry_insert(struct metaentry *mentry, struct metahash *mhash)
123 unsigned int key;
125 key = hash(mentry->path);
126 mentry->next = mhash->bucket[key];
127 mhash->bucket[key] = mentry;
130 #ifdef DEBUG
131 /* Prints a metaentry */
132 static void
133 mentry_print(const struct metaentry *mentry)
135 int i;
137 if (!mentry || !mentry->path) {
138 msg(MSG_DEBUG,
139 "Incorrect meta entry passed to printmetaentry\n");
140 return;
143 msg(MSG_DEBUG, "===========================\n");
144 msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
145 msg(MSG_DEBUG, "===========================\n");
147 msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
148 msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
149 msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
150 msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
151 msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
152 msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
153 for (i = 0; i < mentry->xattrs; i++) {
154 msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
155 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
156 msg(MSG_DEBUG, "\"\n");
159 msg(MSG_DEBUG, "===========================\n\n");
162 /* Prints all metaentries in a metaentry list */
163 static void
164 mentries_print(const struct metahash *mhash)
166 const struct metaentry *mentry;
167 int index;
169 for (index = 0; index < HASH_INDEXES; index++)
170 for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next)
171 mentry_print(mentry);
173 msg(MSG_DEBUG, "%i entries in total\n", mhash->count);
175 #endif
177 /* Creates a metaentry for the file/dir/etc at path */
178 struct metaentry *
179 mentry_create(const char *path)
181 ssize_t lsize, vsize;
182 char *list, *attr;
183 struct stat sbuf;
184 struct passwd *pbuf;
185 struct group *gbuf;
186 int i;
187 struct metaentry *mentry;
189 if (lstat(path, &sbuf)) {
190 msg(MSG_ERROR, "lstat failed for %s: %s\n",
191 path, strerror(errno));
192 return NULL;
195 pbuf = xgetpwuid(sbuf.st_uid);
196 if (!pbuf) {
197 msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n",
198 path, (int)sbuf.st_uid);
199 return NULL;
202 gbuf = xgetgrgid(sbuf.st_gid);
203 if (!gbuf) {
204 msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n",
205 path, (int)sbuf.st_gid);
206 return NULL;
209 mentry = mentry_alloc();
210 mentry->path = xstrdup(path);
211 mentry->pathlen = strlen(mentry->path);
212 mentry->owner = xstrdup(pbuf->pw_name);
213 mentry->group = xstrdup(gbuf->gr_name);
214 mentry->mode = sbuf.st_mode & 0177777;
215 mentry->mtime = sbuf.st_mtim.tv_sec;
216 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
218 /* symlinks have no xattrs */
219 if (S_ISLNK(mentry->mode))
220 return mentry;
222 lsize = listxattr(path, NULL, 0);
223 if (lsize < 0) {
224 /* Perhaps the FS doesn't support xattrs? */
225 if (errno == ENOTSUP)
226 return mentry;
228 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
229 path, strerror(errno));
230 return NULL;
233 list = xmalloc(lsize);
234 lsize = listxattr(path, list, lsize);
235 if (lsize < 0) {
236 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
237 path, strerror(errno));
238 free(list);
239 return NULL;
242 i = 0;
243 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
244 if (*attr == '\0')
245 continue;
246 i++;
249 if (i == 0)
250 return mentry;
252 mentry->xattrs = i;
253 mentry->xattr_names = xmalloc(i * sizeof(char *));
254 mentry->xattr_values = xmalloc(i * sizeof(char *));
255 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
257 i = 0;
258 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
259 if (*attr == '\0')
260 continue;
262 mentry->xattr_names[i] = xstrdup(attr);
263 vsize = getxattr(path, attr, NULL, 0);
264 if (vsize < 0) {
265 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
266 path, strerror(errno));
267 free(list);
268 mentry_free(mentry);
269 return NULL;
272 mentry->xattr_lvalues[i] = vsize;
273 mentry->xattr_values[i] = xmalloc(vsize);
275 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
276 if (vsize < 0) {
277 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
278 path, strerror(errno));
279 free(list);
280 mentry_free(mentry);
281 return NULL;
283 i++;
286 free(list);
287 return mentry;
290 /* Cleans up a path and makes it relative to current working dir unless it is absolute */
291 static char *
292 normalize_path(const char *orig)
294 char *real = canonicalize_file_name(orig);
295 char cwd[PATH_MAX];
296 char *result;
298 getcwd(cwd, PATH_MAX);
299 if (!real)
300 return NULL;
302 if (!strncmp(real, cwd, strlen(cwd))) {
303 result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
304 result[0] = '\0';
305 strcat(result, ".");
306 strcat(result, real + strlen(cwd));
307 } else {
308 result = xstrdup(real);
311 free(real);
312 return result;
315 /* Internal function for the recursive path walk */
316 static void
317 mentries_recurse(const char *path, struct metahash *mhash, msettings *st)
319 struct stat sbuf;
320 struct metaentry *mentry;
321 char tpath[PATH_MAX];
322 DIR *dir;
323 struct dirent *dent;
325 if (!path)
326 return;
328 if (lstat(path, &sbuf)) {
329 msg(MSG_ERROR, "lstat failed for %s: %s\n",
330 path, strerror(errno));
331 return;
334 mentry = mentry_create(path);
335 if (!mentry)
336 return;
338 mentry_insert(mentry, mhash);
340 if (S_ISDIR(sbuf.st_mode)) {
341 dir = opendir(path);
342 if (!dir) {
343 msg(MSG_ERROR, "opendir failed for %s: %s\n",
344 path, strerror(errno));
345 return;
348 while ((dent = readdir(dir))) {
349 if (!strcmp(dent->d_name, ".") ||
350 !strcmp(dent->d_name, "..") ||
351 (!st->do_git && !strcmp(dent->d_name, ".git")))
352 continue;
353 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
354 tpath[PATH_MAX - 1] = '\0';
355 mentries_recurse(tpath, mhash, st);
358 closedir(dir);
362 /* Recurses opath and adds metadata entries to the metaentry list */
363 void
364 mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st)
366 char *path = normalize_path(opath);
368 if (!(*mhash))
369 *mhash = mhash_alloc();
370 mentries_recurse(path, *mhash, st);
371 free(path);
374 /* Stores metaentries to a file */
375 void
376 mentries_tofile(const struct metahash *mhash, const char *path)
378 FILE *to;
379 const struct metaentry *mentry;
380 int key;
381 unsigned i;
383 to = fopen(path, "w");
384 if (!to) {
385 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
386 path, strerror(errno));
387 exit(EXIT_FAILURE);
390 write_binary_string(SIGNATURE, SIGNATURELEN, to);
391 write_binary_string(VERSION, VERSIONLEN, to);
393 for (key = 0; key < HASH_INDEXES; key++) {
394 for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) {
395 write_string(mentry->path, to);
396 write_string(mentry->owner, to);
397 write_string(mentry->group, to);
398 write_int((uint64_t)mentry->mtime, 8, to);
399 write_int((uint64_t)mentry->mtimensec, 8, to);
400 write_int((uint64_t)mentry->mode, 2, to);
401 write_int(mentry->xattrs, 4, to);
402 for (i = 0; i < mentry->xattrs; i++) {
403 write_string(mentry->xattr_names[i], to);
404 write_int(mentry->xattr_lvalues[i], 4, to);
405 write_binary_string(mentry->xattr_values[i],
406 mentry->xattr_lvalues[i], to);
411 fclose(to);
414 /* Creates a metaentry list from a file */
415 void
416 mentries_fromfile(struct metahash **mhash, const char *path)
418 struct metaentry *mentry;
419 char *mmapstart;
420 char *ptr;
421 char *max;
422 int fd;
423 struct stat sbuf;
424 unsigned i;
426 if (!(*mhash))
427 *mhash = mhash_alloc();
429 fd = open(path, O_RDONLY);
430 if (fd < 0) {
431 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
432 path, strerror(errno));
433 exit(EXIT_FAILURE);
436 if (fstat(fd, &sbuf)) {
437 msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
438 path, strerror(errno));
439 exit(EXIT_FAILURE);
442 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
443 msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
444 exit(EXIT_FAILURE);
447 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
448 MAP_SHARED, fd, 0);
449 if (mmapstart == MAP_FAILED) {
450 msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
451 path, strerror(errno));
452 exit(EXIT_FAILURE);
454 ptr = mmapstart;
455 max = mmapstart + sbuf.st_size;
457 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
458 msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
459 goto out;
461 ptr += SIGNATURELEN;
463 if (strncmp(ptr, VERSION, VERSIONLEN)) {
464 msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
465 goto out;
467 ptr += VERSIONLEN;
469 while (ptr < mmapstart + sbuf.st_size) {
470 if (*ptr == '\0') {
471 msg(MSG_CRITICAL, "Invalid characters in file %s\n",
472 path);
473 goto out;
476 mentry = mentry_alloc();
477 mentry->path = read_string(&ptr, max);
478 mentry->pathlen = strlen(mentry->path);
479 mentry->owner = read_string(&ptr, max);
480 mentry->group = read_string(&ptr, max);
481 mentry->mtime = (time_t)read_int(&ptr, 8, max);
482 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
483 mentry->mode = (mode_t)read_int(&ptr, 2, max);
484 mentry->xattrs = (unsigned int)read_int(&ptr, 4, max);
486 if (!mentry->xattrs) {
487 mentry_insert(mentry, *mhash);
488 continue;
491 mentry->xattr_names = xmalloc(mentry->xattrs *
492 sizeof(char *));
493 mentry->xattr_lvalues = xmalloc(mentry->xattrs *
494 sizeof(int));
495 mentry->xattr_values = xmalloc(mentry->xattrs *
496 sizeof(char *));
498 for (i = 0; i < mentry->xattrs; i++) {
499 mentry->xattr_names[i] = read_string(&ptr, max);
500 mentry->xattr_lvalues[i] =
501 (int)read_int(&ptr, 4, max);
502 mentry->xattr_values[i] =
503 read_binary_string(&ptr,
504 mentry->xattr_lvalues[i],
505 max);
507 mentry_insert(mentry, *mhash);
510 out:
511 munmap(mmapstart, sbuf.st_size);
512 close(fd);
515 /* Searches haystack for an xattr matching xattr number n in needle */
517 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle,
518 unsigned n)
520 unsigned i;
522 for (i = 0; i < haystack->xattrs; i++) {
523 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
524 continue;
525 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
526 return -1;
527 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n],
528 needle->xattr_lvalues[n]))
529 return -1;
530 return i;
532 return -1;
535 /* Returns zero if all xattrs in left and right match */
536 static int
537 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
539 unsigned i;
541 if (left->xattrs != right->xattrs)
542 return 1;
544 /* Make sure all xattrs in left are found in right and vice versa */
545 for (i = 0; i < left->xattrs; i++) {
546 if (mentry_find_xattr(right, left, i) < 0 ||
547 mentry_find_xattr(left, right, i) < 0) {
548 return 1;
552 return 0;
555 /* Compares two metaentries and returns an int with a bitmask of differences */
557 mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st)
559 int retval = DIFF_NONE;
561 if (!left || !right) {
562 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
563 return -1;
566 if (strcmp(left->path, right->path))
567 return -1;
569 if (strcmp(left->owner, right->owner))
570 retval |= DIFF_OWNER;
572 if (strcmp(left->group, right->group))
573 retval |= DIFF_GROUP;
575 if ((left->mode & 07777) != (right->mode & 07777))
576 retval |= DIFF_MODE;
578 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
579 retval |= DIFF_TYPE;
581 if (st->do_mtime && strcmp(left->path, st->metafile) &&
582 (left->mtime != right->mtime ||
583 left->mtimensec != right->mtimensec))
584 retval |= DIFF_MTIME;
586 if (mentry_compare_xattr(left, right)) {
587 retval |= DIFF_XATTR;
588 return retval;
591 return retval;
594 /* Compares lists of real and stored metadata and calls pfunc for each */
595 void
596 mentries_compare(struct metahash *mhashreal,
597 struct metahash *mhashstored,
598 void (*pfunc)
599 (struct metaentry *real, struct metaentry *stored, int cmp),
600 msettings *st)
602 struct metaentry *real, *stored;
603 int key;
605 if (!mhashreal || !mhashstored) {
606 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
607 return;
610 for (key = 0; key < HASH_INDEXES; key++) {
611 for (real = mhashreal->bucket[key]; real; real = real->next) {
612 stored = mentry_find(real->path, mhashstored);
614 if (!stored)
615 pfunc(real, NULL, DIFF_ADDED);
616 else
617 pfunc(real, stored, mentry_compare(real, stored, st));
620 for (stored = mhashstored->bucket[key]; stored; stored = stored->next) {
621 real = mentry_find(stored->path, mhashreal);
623 if (!real)
624 pfunc(NULL, stored, DIFF_DELE);