And add a newline
[metastore.git] / metaentry.c
blobac2960afcb688c45be6ff00f31b503addb7898ee
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 <pwd.h>
29 #include <grp.h>
30 #include <attr/xattr.h>
31 #include <limits.h>
32 #include <dirent.h>
33 #include <sys/mman.h>
34 #include <utime.h>
35 #include <fcntl.h>
36 #include <stdint.h>
38 #include "metastore.h"
39 #include "metaentry.h"
40 #include "utils.h"
42 #if 0
43 /* Free's a metaentry and all its parameters */
44 static void
45 mentry_free(struct metaentry *m)
47 int i;
49 if (!m)
50 return;
52 free(m->path);
53 free(m->owner);
54 free(m->group);
56 for (i = 0; i < m->xattrs; i++) {
57 free(m->xattr_names[i]);
58 free(m->xattr_values[i]);
61 free(m->xattr_names);
62 free(m->xattr_values);
63 free(m->xattr_lvalues);
65 free(m);
67 #endif
69 /* Allocates an empty metaentry */
70 static struct metaentry *
71 mentry_alloc()
73 struct metaentry *mentry;
74 mentry = xmalloc(sizeof(struct metaentry));
75 memset(mentry, 0, sizeof(struct metaentry));
76 return mentry;
79 /* Inserts a metaentry into a metaentry list */
80 static void
81 mentry_insert(struct metaentry *mentry, struct metaentry **mlist)
83 struct metaentry *prev;
84 struct metaentry *curr;
85 int comp;
87 if (!(*mlist)) {
88 *mlist = mentry;
89 return;
92 if (strcmp(mentry->path, (*mlist)->path) < 0) {
93 mentry->next = *mlist;
94 *mlist = mentry;
95 return;
98 prev = *mlist;
99 for (curr = prev->next; curr; curr = curr->next) {
100 comp = strcmp(mentry->path, curr->path);
101 if (!comp)
102 /* Two matching paths */
103 return;
104 if (comp < 0)
105 break;
106 prev = curr;
109 if (curr)
110 mentry->next = curr;
111 prev->next = mentry;
114 #ifdef DEBUG
115 /* Prints a metaentry */
116 static void
117 mentry_print(const struct metaentry *mentry)
119 int i;
121 if (!mentry || !mentry->path) {
122 msg(MSG_DEBUG,
123 "Incorrect meta entry passed to printmetaentry\n");
124 return;
127 msg(MSG_DEBUG, "===========================\n");
128 msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry);
129 msg(MSG_DEBUG, "===========================\n");
131 msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path);
132 msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner);
133 msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group);
134 msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime);
135 msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec);
136 msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode);
137 for (i = 0; i < mentry->xattrs; i++) {
138 msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]);
139 binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]);
140 msg(MSG_DEBUG, "\"\n");
143 msg(MSG_DEBUG, "===========================\n\n");
146 /* Prints all metaentries in a metaentry list */
147 static void
148 mentries_print(const struct metaentry *mlist)
150 const struct metaentry *mentry;
151 int i;
153 for (mentry = mlist; mentry; mentry = mentry->next) {
154 i++;
155 mentry_print(mentry);
158 msg(MSG_DEBUG, "%i entries in total\n", i);
160 #endif
162 /* Creates a metaentry for the file/dir/etc at path */
163 static struct metaentry *
164 mentry_create(const char *path)
166 ssize_t lsize, vsize;
167 char *list, *attr;
168 struct stat sbuf;
169 struct passwd *pbuf;
170 struct group *gbuf;
171 int i;
172 struct metaentry *mentry;
174 if (lstat(path, &sbuf)) {
175 msg(MSG_ERROR, "lstat failed for %s: %s\n",
176 path, strerror(errno));
177 return NULL;
180 pbuf = getpwuid(sbuf.st_uid);
181 if (!pbuf) {
182 msg(MSG_ERROR, "getpwuid failed for %i: %s\n",
183 (int)sbuf.st_uid, strerror(errno));
184 return NULL;
187 gbuf = getgrgid(sbuf.st_gid);
188 if (!gbuf) {
189 msg(MSG_ERROR, "getgrgid failed for %i: %s\n",
190 (int)sbuf.st_gid, strerror(errno));
191 return NULL;
194 mentry = mentry_alloc();
195 mentry->path = xstrdup(path);
196 mentry->owner = xstrdup(pbuf->pw_name);
197 mentry->group = xstrdup(gbuf->gr_name);
198 mentry->mode = sbuf.st_mode & 0177777;
199 mentry->mtime = sbuf.st_mtim.tv_sec;
200 mentry->mtimensec = sbuf.st_mtim.tv_nsec;
202 /* symlinks have no xattrs */
203 if (S_ISLNK(mentry->mode))
204 return mentry;
206 lsize = listxattr(path, NULL, 0);
207 if (lsize < 0) {
208 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
209 path, strerror(errno));
210 return NULL;
213 list = xmalloc(lsize);
214 lsize = listxattr(path, list, lsize);
215 if (lsize < 0) {
216 msg(MSG_ERROR, "listxattr failed for %s: %s\n",
217 path, strerror(errno));
218 return NULL;
221 i = 0;
222 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
223 if (*attr == '\0')
224 continue;
225 i++;
228 if (i == 0)
229 return mentry;
231 mentry->xattrs = i;
232 mentry->xattr_names = xmalloc(i * sizeof(char *));
233 mentry->xattr_values = xmalloc(i * sizeof(char *));
234 mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t));
236 i = 0;
237 for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) {
238 if (*attr == '\0')
239 continue;
241 mentry->xattr_names[i] = xstrdup(attr);
242 vsize = getxattr(path, attr, NULL, 0);
243 if (vsize < 0) {
244 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
245 path, strerror(errno));
246 return NULL;
249 mentry->xattr_lvalues[i] = vsize;
250 mentry->xattr_values[i] = xmalloc(vsize);
252 vsize = getxattr(path, attr, mentry->xattr_values[i], vsize);
253 if (vsize < 0) {
254 msg(MSG_ERROR, "getxattr failed for %s: %s\n",
255 path, strerror(errno));
256 return NULL;
258 i++;
261 return mentry;
264 /* Cleans up a path and makes it relative to current working dir unless it is absolute */
265 static char *
266 normalize_path(const char *orig)
268 char *real = canonicalize_file_name(orig);
269 char cwd[PATH_MAX];
270 char *result;
272 getcwd(cwd, PATH_MAX);
273 if (!real)
274 return NULL;
276 if (!strncmp(real, cwd, strlen(cwd))) {
277 result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1);
278 result[0] = '\0';
279 strcat(result, ".");
280 strcat(result, real + strlen(cwd));
281 } else {
282 result = xstrdup(real);
285 free(real);
286 return result;
289 /* Internal function for the recursive path walk */
290 static void
291 mentries_recurse(const char *path, struct metaentry **mlist)
293 struct stat sbuf;
294 struct metaentry *mentry;
295 char tpath[PATH_MAX];
296 DIR *dir;
297 struct dirent *dent;
299 if (!path)
300 return;
302 if (lstat(path, &sbuf)) {
303 msg(MSG_ERROR, "lstat failed for %s: %s\n",
304 path, strerror(errno));
305 return;
308 mentry = mentry_create(path);
309 if (!mentry)
310 return;
312 mentry_insert(mentry, mlist);
314 if (S_ISDIR(sbuf.st_mode)) {
315 dir = opendir(path);
316 if (!dir) {
317 msg(MSG_ERROR, "opendir failed for %s: %s\n",
318 path, strerror(errno));
319 return;
322 while ((dent = readdir(dir))) {
323 if (!strcmp(dent->d_name, ".") ||
324 !strcmp(dent->d_name, "..") ||
325 !strcmp(dent->d_name, ".git"))
326 continue;
327 snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name);
328 tpath[PATH_MAX - 1] = '\0';
329 mentries_recurse(tpath, mlist);
332 closedir(dir);
336 /* Recurses opath and adds metadata entries to the metaentry list */
337 void
338 mentries_recurse_path(const char *opath, struct metaentry **mlist)
340 char *path = normalize_path(opath);
341 mentries_recurse(path, mlist);
342 free(path);
345 /* Stores a metaentry list to a file */
346 void
347 mentries_tofile(const struct metaentry *mlist, const char *path)
349 FILE *to;
350 const struct metaentry *mentry;
351 int i;
353 to = fopen(path, "w");
354 if (!to) {
355 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
356 path, strerror(errno));
357 exit(EXIT_FAILURE);
360 write_binary_string(SIGNATURE, SIGNATURELEN, to);
361 write_binary_string(VERSION, VERSIONLEN, to);
363 for (mentry = mlist; mentry; mentry = mentry->next) {
364 write_string(mentry->path, to);
365 write_string(mentry->owner, to);
366 write_string(mentry->group, to);
367 write_int((uint64_t)mentry->mtime, 8, to);
368 write_int((uint64_t)mentry->mtimensec, 8, to);
369 write_int((uint64_t)mentry->mode, 2, to);
370 write_int(mentry->xattrs, 4, to);
371 for (i = 0; i < mentry->xattrs; i++) {
372 write_string(mentry->xattr_names[i], to);
373 write_int(mentry->xattr_lvalues[i], 4, to);
374 write_binary_string(mentry->xattr_values[i],
375 mentry->xattr_lvalues[i], to);
379 fclose(to);
382 /* Creates a metaentry list from a file */
383 void
384 mentries_fromfile(struct metaentry **mlist, const char *path)
386 struct metaentry *mentry;
387 char *mmapstart;
388 char *ptr;
389 char *max;
390 int fd;
391 struct stat sbuf;
392 int i;
394 fd = open(path, O_RDONLY);
395 if (fd < 0) {
396 msg(MSG_CRITICAL, "Failed to open %s: %s\n",
397 path, strerror(errno));
398 exit(EXIT_FAILURE);
401 if (fstat(fd, &sbuf)) {
402 msg(MSG_CRITICAL, "Failed to stat %s: %s\n",
403 path, strerror(errno));
404 exit(EXIT_FAILURE);
407 if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) {
408 msg(MSG_CRITICAL, "File %s has an invalid size\n", path);
409 exit(EXIT_FAILURE);
412 mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ,
413 MAP_SHARED, fd, 0);
414 if (mmapstart == MAP_FAILED) {
415 msg(MSG_CRITICAL, "Unable to mmap %s: %s\n",
416 path, strerror(errno));
417 exit(EXIT_FAILURE);
419 ptr = mmapstart;
420 max = mmapstart + sbuf.st_size;
422 if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) {
423 msg(MSG_CRITICAL, "Invalid signature for file %s\n", path);
424 goto out;
426 ptr += SIGNATURELEN;
428 if (strncmp(ptr, VERSION, VERSIONLEN)) {
429 msg(MSG_CRITICAL, "Invalid version of file %s\n", path);
430 goto out;
432 ptr += VERSIONLEN;
434 while (ptr < mmapstart + sbuf.st_size) {
435 if (*ptr == '\0') {
436 msg(MSG_CRITICAL, "Invalid characters in file %s\n",
437 path);
438 goto out;
441 mentry = mentry_alloc();
442 mentry->path = read_string(&ptr, max);
443 mentry->owner = read_string(&ptr, max);
444 mentry->group = read_string(&ptr, max);
445 mentry->mtime = (time_t)read_int(&ptr, 8, max);
446 mentry->mtimensec = (time_t)read_int(&ptr, 8, max);
447 mentry->mode = (mode_t)read_int(&ptr, 2, max);
448 mentry->xattrs = (unsigned int)read_int(&ptr, 4, max);
450 if (!mentry->xattrs) {
451 mentry_insert(mentry, mlist);
452 continue;
455 mentry->xattr_names = xmalloc(mentry->xattrs *
456 sizeof(char *));
457 mentry->xattr_lvalues = xmalloc(mentry->xattrs *
458 sizeof(int));
459 mentry->xattr_values = xmalloc(mentry->xattrs *
460 sizeof(char *));
462 for (i = 0; i < mentry->xattrs; i++) {
463 mentry->xattr_names[i] = read_string(&ptr, max);
464 mentry->xattr_lvalues[i] =
465 (int)read_int(&ptr, 4, max);
466 mentry->xattr_values[i] =
467 read_binary_string(&ptr,
468 mentry->xattr_lvalues[i],
469 max);
471 mentry_insert(mentry, mlist);
474 out:
475 munmap(mmapstart, sbuf.st_size);
476 close(fd);
479 /* Finds a metaentry matching path */
480 static struct metaentry *
481 mentry_find(const char *path, struct metaentry *mlist)
483 struct metaentry *m;
485 /* FIXME - We can do a bisect search here instead */
486 for (m = mlist; m; m = m->next) {
487 if (!strcmp(path, m->path))
488 return m;
490 return NULL;
493 /* Searches haystack for an xattr matching xattr number n in needle */
495 mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle, int n)
497 int i;
499 for (i = 0; i < haystack->xattrs; i++) {
500 if (strcmp(haystack->xattr_names[i], needle->xattr_names[n]))
501 continue;
502 if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n])
503 return -1;
504 if (bcmp(haystack->xattr_values[i], needle->xattr_values[n],
505 needle->xattr_lvalues[n]))
506 return -1;
507 return i;
509 return -1;
512 /* Returns zero if all xattrs in left and right match */
513 static int
514 mentry_compare_xattr(struct metaentry *left, struct metaentry *right)
516 int i;
518 if (left->xattrs != right->xattrs)
519 return 1;
521 /* Make sure all xattrs in left are found in right and vice versa */
522 for (i = 0; i < left->xattrs; i++) {
523 if (mentry_find_xattr(right, left, i) < 0 ||
524 mentry_find_xattr(left, right, i) < 0) {
525 return 1;
529 return 0;
532 /* Compares two metaentries and returns an int with a bitmask of differences */
533 static int
534 mentry_compare(struct metaentry *left, struct metaentry *right, int do_mtime)
536 int retval = DIFF_NONE;
538 if (!left || !right) {
539 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
540 return -1;
543 if (strcmp(left->path, right->path))
544 return -1;
546 if (strcmp(left->owner, right->owner))
547 retval |= DIFF_OWNER;
549 if (strcmp(left->group, right->group))
550 retval |= DIFF_GROUP;
552 if ((left->mode & 07777) != (right->mode & 07777))
553 retval |= DIFF_MODE;
555 if ((left->mode & S_IFMT) != (right->mode & S_IFMT))
556 retval |= DIFF_TYPE;
558 if (do_mtime && strcmp(left->path, METAFILE) &&
559 (left->mtime != right->mtime ||
560 left->mtimensec != right->mtimensec))
561 retval |= DIFF_MTIME;
563 if (mentry_compare_xattr(left, right)) {
564 retval |= DIFF_XATTR;
565 return retval;
568 return retval;
571 /* Compares lists of real and stored metadata and calls pfunc for each */
572 void
573 mentries_compare(struct metaentry *mlistreal,
574 struct metaentry *mliststored,
575 void (*pfunc)
576 (struct metaentry *real, struct metaentry *stored, int do_mtime),
577 int do_mtime)
579 struct metaentry *real, *stored;
580 int cmp;
582 if (!mlistreal || !mliststored) {
583 msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__);
584 return;
587 for (real = mlistreal; real; real = real->next) {
588 stored = mentry_find(real->path, mliststored);
589 if (!stored)
590 cmp = DIFF_ADDED;
591 else
592 cmp = mentry_compare(real, stored, do_mtime);
593 pfunc(real, stored, cmp);
596 for (stored = mliststored; stored; stored = stored->next) {
597 real = mentry_find(stored->path, mlistreal);
598 if (!real)
599 pfunc(real, stored, DIFF_DELE);