diff options
author | Przemyslaw Pawelczyk <przemoc@gmail.com> | 2015-09-13 22:12:14 +0200 |
---|---|---|
committer | Przemyslaw Pawelczyk <przemoc@gmail.com> | 2015-09-13 22:12:14 +0200 |
commit | 16ab153f3a54194e3217fcf1a235904e4b61623b (patch) | |
tree | c27f9326815ebf4454e753028dcb3d388b3c0990 /src | |
parent | 295428a550bb55a509ce15c5e27a0ab173799ebb (diff) |
Move source files to src/ directory.
As a bonus you can build out-of-tree now via make -f.
It's part of the work related to issue #22.
Diffstat (limited to 'src')
-rw-r--r-- | src/metaentry.c | 677 | ||||
-rw-r--r-- | src/metaentry.h | 97 | ||||
-rw-r--r-- | src/metastore.c | 539 | ||||
-rw-r--r-- | src/metastore.h | 43 | ||||
-rw-r--r-- | src/settings.h | 35 | ||||
-rw-r--r-- | src/utils.c | 303 | ||||
-rw-r--r-- | src/utils.h | 90 |
7 files changed, 1784 insertions, 0 deletions
diff --git a/src/metaentry.c b/src/metaentry.c new file mode 100644 index 0000000..f3ba692 --- /dev/null +++ b/src/metaentry.c @@ -0,0 +1,677 @@ +/* + * Various functions to work with meta entries. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/xattr.h> +#include <limits.h> +#include <dirent.h> +#include <sys/mman.h> +#include <utime.h> +#include <fcntl.h> +#include <stdint.h> +#include <errno.h> +#include <bsd/string.h> +#include <time.h> + +#include "metastore.h" +#include "metaentry.h" +#include "utils.h" + +/* Free's a metaentry and all its parameters */ +static void +mentry_free(struct metaentry *m) +{ + unsigned i; + + if (!m) + return; + + free(m->path); + free(m->owner); + free(m->group); + + for (i = 0; i < m->xattrs; i++) { + free(m->xattr_names[i]); + free(m->xattr_values[i]); + } + + free(m->xattr_names); + free(m->xattr_values); + free(m->xattr_lvalues); + + free(m); +} + +/* Allocates an empty metahash table */ +static struct metahash * +mhash_alloc() +{ + struct metahash *mhash; + mhash = xmalloc(sizeof(struct metahash)); + memset(mhash, 0, sizeof(struct metahash)); + return mhash; +} + +/* Generates a hash key (using djb2) */ +static unsigned int +hash(const char *str) +{ + unsigned int hash = 5381; + int c; + + while ((c = *str++)) + hash = ((hash << 5) + hash) + c; + + return hash % HASH_INDEXES; +} + +/* Allocates an empty metaentry */ +static struct metaentry * +mentry_alloc() +{ + struct metaentry *mentry; + mentry = xmalloc(sizeof(struct metaentry)); + memset(mentry, 0, sizeof(struct metaentry)); + return mentry; +} + +/* Does a bisect search for the closest match in a metaentry list */ +struct metaentry * +mentry_find(const char *path, struct metahash *mhash) +{ + struct metaentry *base; + unsigned int key; + + if (!mhash) { + msg(MSG_ERROR, "%s called with empty hash table\n", __FUNCTION__); + return NULL; + } + + key = hash(path); + for (base = mhash->bucket[key]; base; base = base->next) { + if (!strcmp(base->path, path)) + return base; + } + + return NULL; +} + +/* Inserts a metaentry into a metaentry list */ +static void +mentry_insert(struct metaentry *mentry, struct metahash *mhash) +{ + unsigned int key; + + key = hash(mentry->path); + mentry->next = mhash->bucket[key]; + mhash->bucket[key] = mentry; +} + +#ifdef DEBUG +/* Prints a metaentry */ +static void +mentry_print(const struct metaentry *mentry) +{ + int i; + + if (!mentry || !mentry->path) { + msg(MSG_DEBUG, + "Incorrect meta entry passed to printmetaentry\n"); + return; + } + + msg(MSG_DEBUG, "===========================\n"); + msg(MSG_DEBUG, "Dump of metaentry %p\n", mentry); + msg(MSG_DEBUG, "===========================\n"); + + msg(MSG_DEBUG, "path\t\t: %s\n", mentry->path); + msg(MSG_DEBUG, "owner\t\t: %s\n", mentry->owner); + msg(MSG_DEBUG, "group\t\t: %s\n", mentry->group); + msg(MSG_DEBUG, "mtime\t\t: %ld\n", (unsigned long)mentry->mtime); + msg(MSG_DEBUG, "mtimensec\t: %ld\n", (unsigned long)mentry->mtimensec); + msg(MSG_DEBUG, "mode\t\t: %ld\n", (unsigned long)mentry->mode); + for (i = 0; i < mentry->xattrs; i++) { + msg(MSG_DEBUG, "xattr[%i]\t: %s=\"", i, mentry->xattr_names[i]); + binary_print(mentry->xattr_values[i], mentry->xattr_lvalues[i]); + msg(MSG_DEBUG, "\"\n"); + } + + msg(MSG_DEBUG, "===========================\n\n"); +} + +/* Prints all metaentries in a metaentry list */ +static void +mentries_print(const struct metahash *mhash) +{ + const struct metaentry *mentry; + int index; + + for (index = 0; index < HASH_INDEXES; index++) + for (mentry = mhash->bucket[index]; mentry; mentry = mentry->next) + mentry_print(mentry); + + msg(MSG_DEBUG, "%i entries in total\n", mhash->count); +} +#endif + +/* Creates a metaentry for the file/dir/etc at path */ +struct metaentry * +mentry_create(const char *path) +{ + ssize_t lsize, vsize; + char *list, *attr; + struct stat sbuf; + struct passwd *pbuf; + struct group *gbuf; + int i; + struct metaentry *mentry; + + if (lstat(path, &sbuf)) { + msg(MSG_ERROR, "lstat failed for %s: %s\n", + path, strerror(errno)); + return NULL; + } + + pbuf = xgetpwuid(sbuf.st_uid); + if (!pbuf) { + msg(MSG_ERROR, "getpwuid failed for %s: uid %i not found\n", + path, (int)sbuf.st_uid); + return NULL; + } + + gbuf = xgetgrgid(sbuf.st_gid); + if (!gbuf) { + msg(MSG_ERROR, "getgrgid failed for %s: gid %i not found\n", + path, (int)sbuf.st_gid); + return NULL; + } + + mentry = mentry_alloc(); + mentry->path = xstrdup(path); + mentry->pathlen = strlen(mentry->path); + mentry->owner = xstrdup(pbuf->pw_name); + mentry->group = xstrdup(gbuf->gr_name); + mentry->mode = sbuf.st_mode & 0177777; + mentry->mtime = sbuf.st_mtim.tv_sec; + mentry->mtimensec = sbuf.st_mtim.tv_nsec; + + /* symlinks have no xattrs */ + if (S_ISLNK(mentry->mode)) + return mentry; + + lsize = listxattr(path, NULL, 0); + if (lsize < 0) { + /* Perhaps the FS doesn't support xattrs? */ + if (errno == ENOTSUP) + return mentry; + + msg(MSG_ERROR, "listxattr failed for %s: %s\n", + path, strerror(errno)); + return NULL; + } + + list = xmalloc(lsize); + lsize = listxattr(path, list, lsize); + if (lsize < 0) { + msg(MSG_ERROR, "listxattr failed for %s: %s\n", + path, strerror(errno)); + free(list); + return NULL; + } + + i = 0; + for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) { + if (*attr == '\0') + continue; + i++; + } + + if (i == 0) + return mentry; + + mentry->xattrs = i; + mentry->xattr_names = xmalloc(i * sizeof(char *)); + mentry->xattr_values = xmalloc(i * sizeof(char *)); + mentry->xattr_lvalues = xmalloc(i * sizeof(ssize_t)); + + i = 0; + for (attr = list; attr < list + lsize; attr = strchr(attr, '\0') + 1) { + if (*attr == '\0') + continue; + + mentry->xattr_names[i] = xstrdup(attr); + vsize = getxattr(path, attr, NULL, 0); + if (vsize < 0) { + msg(MSG_ERROR, "getxattr failed for %s: %s\n", + path, strerror(errno)); + free(list); + mentry_free(mentry); + return NULL; + } + + mentry->xattr_lvalues[i] = vsize; + mentry->xattr_values[i] = xmalloc(vsize); + + vsize = getxattr(path, attr, mentry->xattr_values[i], vsize); + if (vsize < 0) { + msg(MSG_ERROR, "getxattr failed for %s: %s\n", + path, strerror(errno)); + free(list); + mentry_free(mentry); + return NULL; + } + i++; + } + + free(list); + return mentry; +} + +/* Cleans up a path and makes it relative to current working dir unless it is absolute */ +static char * +normalize_path(const char *orig) +{ + char *real = realpath(orig, NULL); + char cwd[PATH_MAX]; + char *result; + + getcwd(cwd, PATH_MAX); + if (!real) + return NULL; + + if (!strncmp(real, cwd, strlen(cwd))) { + result = xmalloc(strlen(real) - strlen(cwd) + 1 + 1); + result[0] = '\0'; + strcat(result, "."); + strcat(result, real + strlen(cwd)); + } else { + result = xstrdup(real); + } + + free(real); + return result; +} + +/* Internal function for the recursive path walk */ +static void +mentries_recurse(const char *path, struct metahash *mhash, msettings *st) +{ + struct stat sbuf; + struct metaentry *mentry; + char tpath[PATH_MAX]; + DIR *dir; + struct dirent *dent; + + if (!path) + return; + + if (lstat(path, &sbuf)) { + msg(MSG_ERROR, "lstat failed for %s: %s\n", + path, strerror(errno)); + return; + } + + mentry = mentry_create(path); + if (!mentry) + return; + + mentry_insert(mentry, mhash); + + if (S_ISDIR(sbuf.st_mode)) { + dir = opendir(path); + if (!dir) { + msg(MSG_ERROR, "opendir failed for %s: %s\n", + path, strerror(errno)); + return; + } + + while ((dent = readdir(dir))) { + if (!strcmp(dent->d_name, ".") || + !strcmp(dent->d_name, "..") || + (!st->do_git && !strcmp(dent->d_name, ".git"))) + continue; + snprintf(tpath, PATH_MAX, "%s/%s", path, dent->d_name); + tpath[PATH_MAX - 1] = '\0'; + mentries_recurse(tpath, mhash, st); + } + + closedir(dir); + } +} + +/* Recurses opath and adds metadata entries to the metaentry list */ +void +mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st) +{ + char *path = normalize_path(opath); + + if (!(*mhash)) + *mhash = mhash_alloc(); + mentries_recurse(path, *mhash, st); + free(path); +} + +/* Stores metaentries to a file */ +void +mentries_tofile(const struct metahash *mhash, const char *path) +{ + FILE *to; + const struct metaentry *mentry; + int key; + unsigned i; + + to = fopen(path, "w"); + if (!to) { + msg(MSG_CRITICAL, "Failed to open %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + + write_binary_string(SIGNATURE, SIGNATURELEN, to); + write_binary_string(VERSION, VERSIONLEN, to); + + for (key = 0; key < HASH_INDEXES; key++) { + for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) { + write_string(mentry->path, to); + write_string(mentry->owner, to); + write_string(mentry->group, to); + write_int((uint64_t)mentry->mtime, 8, to); + write_int((uint64_t)mentry->mtimensec, 8, to); + write_int((uint64_t)mentry->mode, 2, to); + write_int(mentry->xattrs, 4, to); + for (i = 0; i < mentry->xattrs; i++) { + write_string(mentry->xattr_names[i], to); + write_int(mentry->xattr_lvalues[i], 4, to); + write_binary_string(mentry->xattr_values[i], + mentry->xattr_lvalues[i], to); + } + } + } + + fclose(to); +} + +/* Creates a metaentry list from a file */ +void +mentries_fromfile(struct metahash **mhash, const char *path) +{ + struct metaentry *mentry; + char *mmapstart; + char *ptr; + char *max; + int fd; + struct stat sbuf; + unsigned i; + + if (!(*mhash)) + *mhash = mhash_alloc(); + + fd = open(path, O_RDONLY); + if (fd < 0) { + msg(MSG_CRITICAL, "Failed to open %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (fstat(fd, &sbuf)) { + msg(MSG_CRITICAL, "Failed to stat %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + + if (sbuf.st_size < (SIGNATURELEN + VERSIONLEN)) { + msg(MSG_CRITICAL, "File %s has an invalid size\n", path); + exit(EXIT_FAILURE); + } + + mmapstart = mmap(NULL, (size_t)sbuf.st_size, PROT_READ, + MAP_SHARED, fd, 0); + if (mmapstart == MAP_FAILED) { + msg(MSG_CRITICAL, "Unable to mmap %s: %s\n", + path, strerror(errno)); + exit(EXIT_FAILURE); + } + ptr = mmapstart; + max = mmapstart + sbuf.st_size; + + if (strncmp(ptr, SIGNATURE, SIGNATURELEN)) { + msg(MSG_CRITICAL, "Invalid signature for file %s\n", path); + goto out; + } + ptr += SIGNATURELEN; + + if (strncmp(ptr, VERSION, VERSIONLEN)) { + msg(MSG_CRITICAL, "Invalid version of file %s\n", path); + goto out; + } + ptr += VERSIONLEN; + + while (ptr < mmapstart + sbuf.st_size) { + if (*ptr == '\0') { + msg(MSG_CRITICAL, "Invalid characters in file %s\n", + path); + goto out; + } + + mentry = mentry_alloc(); + mentry->path = read_string(&ptr, max); + mentry->pathlen = strlen(mentry->path); + mentry->owner = read_string(&ptr, max); + mentry->group = read_string(&ptr, max); + mentry->mtime = (time_t)read_int(&ptr, 8, max); + mentry->mtimensec = (time_t)read_int(&ptr, 8, max); + mentry->mode = (mode_t)read_int(&ptr, 2, max); + mentry->xattrs = (unsigned int)read_int(&ptr, 4, max); + + if (!mentry->xattrs) { + mentry_insert(mentry, *mhash); + continue; + } + + mentry->xattr_names = xmalloc(mentry->xattrs * + sizeof(char *)); + mentry->xattr_lvalues = xmalloc(mentry->xattrs * + sizeof(int)); + mentry->xattr_values = xmalloc(mentry->xattrs * + sizeof(char *)); + + for (i = 0; i < mentry->xattrs; i++) { + mentry->xattr_names[i] = read_string(&ptr, max); + mentry->xattr_lvalues[i] = + (int)read_int(&ptr, 4, max); + mentry->xattr_values[i] = + read_binary_string(&ptr, + mentry->xattr_lvalues[i], + max); + } + mentry_insert(mentry, *mhash); + } + +out: + munmap(mmapstart, sbuf.st_size); + close(fd); +} + +/* Searches haystack for an xattr matching xattr number n in needle */ +int +mentry_find_xattr(struct metaentry *haystack, struct metaentry *needle, + unsigned n) +{ + unsigned i; + + for (i = 0; i < haystack->xattrs; i++) { + if (strcmp(haystack->xattr_names[i], needle->xattr_names[n])) + continue; + if (haystack->xattr_lvalues[i] != needle->xattr_lvalues[n]) + return -1; + if (bcmp(haystack->xattr_values[i], needle->xattr_values[n], + needle->xattr_lvalues[n])) + return -1; + return i; + } + return -1; +} + +/* Returns zero if all xattrs in left and right match */ +static int +mentry_compare_xattr(struct metaentry *left, struct metaentry *right) +{ + unsigned i; + + if (left->xattrs != right->xattrs) + return 1; + + /* Make sure all xattrs in left are found in right and vice versa */ + for (i = 0; i < left->xattrs; i++) { + if (mentry_find_xattr(right, left, i) < 0 || + mentry_find_xattr(left, right, i) < 0) { + return 1; + } + } + + return 0; +} + +/* Compares two metaentries and returns an int with a bitmask of differences */ +int +mentry_compare(struct metaentry *left, struct metaentry *right, msettings *st) +{ + int retval = DIFF_NONE; + + if (!left || !right) { + msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__); + return -1; + } + + if (strcmp(left->path, right->path)) + return -1; + + if (strcmp(left->owner, right->owner)) + retval |= DIFF_OWNER; + + if (strcmp(left->group, right->group)) + retval |= DIFF_GROUP; + + if ((left->mode & 07777) != (right->mode & 07777)) + retval |= DIFF_MODE; + + if ((left->mode & S_IFMT) != (right->mode & S_IFMT)) + retval |= DIFF_TYPE; + + if (st->do_mtime && strcmp(left->path, st->metafile) && + (left->mtime != right->mtime || + left->mtimensec != right->mtimensec)) + retval |= DIFF_MTIME; + + if (mentry_compare_xattr(left, right)) { + retval |= DIFF_XATTR; + return retval; + } + + return retval; +} + +/* Compares lists of real and stored metadata and calls pfunc for each */ +void +mentries_compare(struct metahash *mhashreal, + struct metahash *mhashstored, + void (*pfunc) + (struct metaentry *real, struct metaentry *stored, int cmp), + msettings *st) +{ + struct metaentry *real, *stored; + int key; + + if (!mhashreal || !mhashstored) { + msg(MSG_ERROR, "%s called with empty list\n", __FUNCTION__); + return; + } + + for (key = 0; key < HASH_INDEXES; key++) { + for (real = mhashreal->bucket[key]; real; real = real->next) { + stored = mentry_find(real->path, mhashstored); + + if (!stored) + pfunc(real, NULL, DIFF_ADDED); + else + pfunc(real, stored, mentry_compare(real, stored, st)); + } + + for (stored = mhashstored->bucket[key]; stored; stored = stored->next) { + real = mentry_find(stored->path, mhashreal); + + if (!real) + pfunc(NULL, stored, DIFF_DELE); + } + } +} + +/* Dumps given metadata */ +void +mentries_dump(struct metahash *mhash) +{ + const struct metaentry *mentry; + char mode[11 + 1] = ""; + char date[12 + 2 + 2 + 2*1 + 1 + 2 + 2 + 2 + 2*1 + 1] = ""; + char zone[5 + 1] = ""; + struct tm cal; + + for (int key = 0; key < HASH_INDEXES; key++) { + for (mentry = mhash->bucket[key]; mentry; mentry = mentry->next) { + strmode(mentry->mode, mode); + localtime_r(&mentry->mtime, &cal); + strftime(date, sizeof(date), "%F %T", &cal); + strftime(zone, sizeof(zone), "%z", &cal); + printf("%s\t%s\t%s\t%s.%09ld %s\t%s%s\n", + mode, + mentry->owner, mentry->group, + date, mentry->mtimensec, zone, + mentry->path, S_ISDIR(mentry->mode) ? "/" : ""); + for (int i = 0; i < mentry->xattrs; i++) { + printf("\t\t\t\t%s%s\t%s=", + mentry->path, S_ISDIR(mentry->mode) ? "/" : "", + mentry->xattr_names[i]); + ssize_t p = 0; + for (; p < mentry->xattr_lvalues[i]; p++) { + const char ch = mentry->xattr_values[i][p]; + if ((unsigned)(ch - 32) > 126 - 32) { + p = -1; + break; + } + } + if (p >= 0) + printf("\"%.*s\"\n", + (int)mentry->xattr_lvalues[i], + mentry->xattr_values[i]); + else { + printf("0x"); + for (p = 0; p < mentry->xattr_lvalues[i]; p++) + printf("%02hhx", (char)mentry->xattr_values[i][p]); + printf("\n"); + } + } + } + } +} diff --git a/src/metaentry.h b/src/metaentry.h new file mode 100644 index 0000000..666c5af --- /dev/null +++ b/src/metaentry.h @@ -0,0 +1,97 @@ +/* + * Various functions to work with meta entries. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef METAENTRY_H +#define METAENTRY_H + +#include <stdbool.h> + +#include "settings.h" + +/* Data structure to hold all metadata for a file/dir */ +struct metaentry { + struct metaentry *next; /* For the metahash chains */ + struct metaentry *list; /* For creating additional lists of entries */ + + char *path; + unsigned int pathlen; + + char *owner; + char *group; + mode_t mode; + time_t mtime; + long mtimensec; + + unsigned int xattrs; + char **xattr_names; + ssize_t *xattr_lvalues; + char **xattr_values; +}; + +#define HASH_INDEXES 1024 + +/* Data structure to hold a number of metadata entries */ +struct metahash { + struct metaentry *bucket[HASH_INDEXES]; + unsigned int count; +}; + +/* Create a metaentry for the file/dir/etc at path */ +struct metaentry *mentry_create(const char *path); + +/* Recurses opath and adds metadata entries to the metaentry list */ +void mentries_recurse_path(const char *opath, struct metahash **mhash, msettings *st); + +/* Stores a metaentry list to a file */ +void mentries_tofile(const struct metahash *mhash, const char *path); + +/* Creates a metaentry list from a file */ +void mentries_fromfile(struct metahash **mhash, const char *path); + +/* Searches haystack for an xattr matching xattr number n in needle */ +int mentry_find_xattr(struct metaentry *haystack, + struct metaentry *needle, + unsigned n); + +#define DIFF_NONE 0x00 +#define DIFF_OWNER 0x01 +#define DIFF_GROUP 0x02 +#define DIFF_MODE 0x04 +#define DIFF_TYPE 0x08 +#define DIFF_MTIME 0x10 +#define DIFF_XATTR 0x20 +#define DIFF_ADDED 0x40 +#define DIFF_DELE 0x80 + +/* Compares two metaentries and returns an int with a bitmask of differences */ +int mentry_compare(struct metaentry *left, + struct metaentry *right, + msettings *st); + +/* Compares lists of real and stored metadata and calls pfunc for each */ +void mentries_compare(struct metahash *mhashreal, + struct metahash *mhashstored, + void (*pfunc)(struct metaentry *real, + struct metaentry *stored, + int cmp), + msettings *st); + +void mentries_dump(struct metahash *mhash); + +#endif /* METAENTRY_H */ diff --git a/src/metastore.c b/src/metastore.c new file mode 100644 index 0000000..2956cab --- /dev/null +++ b/src/metastore.c @@ -0,0 +1,539 @@ +/* + * Main functions of the program. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _BSD_SOURCE +#include <sys/types.h> +#include <sys/stat.h> +#include <getopt.h> +#include <utime.h> +#include <sys/xattr.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "metastore.h" +#include "settings.h" +#include "utils.h" +#include "metaentry.h" + +/* metastore settings */ +static struct metasettings settings = { + .metafile = METAFILE, + .do_mtime = false, + .do_emptydirs = false, + .do_removeemptydirs = false, + .do_git = false, +}; + +/* Used to create lists of dirs / other files which are missing in the fs */ +static struct metaentry *missingdirs = NULL; +static struct metaentry *missingothers = NULL; + +/* Used to create lists of dirs / other files which are missing in metadata */ +static struct metaentry *extradirs = NULL; + +/* + * Inserts an entry in a linked list ordered by pathlen + */ +static void +insert_entry_plist(struct metaentry **list, struct metaentry *entry) +{ + struct metaentry **parent; + + for (parent = list; *parent; parent = &((*parent)->list)) { + if ((*parent)->pathlen > entry->pathlen) + break; + } + + entry->list = *parent; + *parent = entry; +} + +/* + * Inserts an entry in a linked list ordered by pathlen descendingly + */ +static void +insert_entry_pdlist(struct metaentry **list, struct metaentry *entry) +{ + struct metaentry **parent; + + for (parent = list; *parent; parent = &((*parent)->list)) { + if ((*parent)->pathlen < entry->pathlen) + break; + } + + entry->list = *parent; + *parent = entry; +} + +/* + * Prints differences between real and stored actual metadata + * - for use in mentries_compare + */ +static void +compare_print(struct metaentry *real, struct metaentry *stored, int cmp) +{ + if (!real && !stored) { + msg(MSG_ERROR, "%s called with incorrect arguments\n", __FUNCTION__); + return; + } + + if (cmp == DIFF_NONE) { + msg(MSG_DEBUG, "%s:\tno difference\n", real->path); + return; + } + + msg(MSG_QUIET, "%s:\t", real ? real->path : stored->path); + + if (cmp & DIFF_ADDED) + msg(MSG_QUIET, "added ", real->path); + if (cmp & DIFF_DELE) + msg(MSG_QUIET, "removed ", stored->path); + if (cmp & DIFF_OWNER) + msg(MSG_QUIET, "owner "); + if (cmp & DIFF_GROUP) + msg(MSG_QUIET, "group "); + if (cmp & DIFF_MODE) + msg(MSG_QUIET, "mode "); + if (cmp & DIFF_TYPE) + msg(MSG_QUIET, "type "); + if (cmp & DIFF_MTIME) + msg(MSG_QUIET, "mtime "); + if (cmp & DIFF_XATTR) + msg(MSG_QUIET, "xattr "); + msg(MSG_QUIET, "\n"); +} + +/* + * Tries to change the real metadata to match the stored one + * - for use in mentries_compare + */ +static void +compare_fix(struct metaentry *real, struct metaentry *stored, int cmp) +{ + struct group *group; + struct passwd *owner; + gid_t gid = -1; + uid_t uid = -1; + struct utimbuf tbuf; + unsigned i; + + if (!real && !stored) { + msg(MSG_ERROR, "%s called with incorrect arguments\n", + __FUNCTION__); + return; + } + + if (!real) { + if (S_ISDIR(stored->mode)) + insert_entry_plist(&missingdirs, stored); + else + insert_entry_plist(&missingothers, stored); + + msg(MSG_NORMAL, "%s:\tremoved\n", stored->path); + return; + } + + if (!stored) { + if (S_ISDIR(real->mode)) + insert_entry_pdlist(&extradirs, real); + msg(MSG_NORMAL, "%s:\tadded\n", real->path); + return; + } + + if (cmp == DIFF_NONE) { + msg(MSG_DEBUG, "%s:\tno difference\n", real->path); + return; + } + + if (cmp & DIFF_TYPE) { + msg(MSG_NORMAL, "%s:\tnew type, will not change metadata\n", + real->path); + return; + } + + msg(MSG_QUIET, "%s:\tchanging metadata\n", real->path); + + while (cmp & (DIFF_OWNER | DIFF_GROUP)) { + if (cmp & DIFF_OWNER) { + msg(MSG_NORMAL, "%s:\tchanging owner from %s to %s\n", + real->path, real->group, stored->group); + owner = xgetpwnam(stored->owner); + if (!owner) { + msg(MSG_DEBUG, "\tgetpwnam failed: %s\n", + strerror(errno)); + break; + } + uid = owner->pw_uid; + } + + if (cmp & DIFF_GROUP) { + msg(MSG_NORMAL, "%s:\tchanging group from %s to %s\n", + real->path, real->group, stored->group); + group = xgetgrnam(stored->group); + if (!group) { + msg(MSG_DEBUG, "\tgetgrnam failed: %s\n", + strerror(errno)); + break; + } + gid = group->gr_gid; + } + + if (lchown(real->path, uid, gid)) { + msg(MSG_DEBUG, "\tlchown failed: %s\n", + strerror(errno)); + break; + } + break; + } + + if (cmp & DIFF_MODE) { + msg(MSG_NORMAL, "%s:\tchanging mode from 0%o to 0%o\n", + real->path, real->mode & 07777, stored->mode & 07777); + if (chmod(real->path, stored->mode & 07777)) + msg(MSG_DEBUG, "\tchmod failed: %s\n", strerror(errno)); + } + + /* FIXME: Use utimensat here, or even better - lutimensat */ + if ((cmp & DIFF_MTIME) && S_ISLNK(real->mode)) { + msg(MSG_NORMAL, "%s:\tsymlink, not changing mtime\n", real->path); + } else if (cmp & DIFF_MTIME) { + msg(MSG_NORMAL, "%s:\tchanging mtime from %ld to %ld\n", + real->path, real->mtime, stored->mtime); + tbuf.actime = stored->mtime; + tbuf.modtime = stored->mtime; + if (utime(real->path, &tbuf)) { + msg(MSG_DEBUG, "\tutime failed: %s\n", strerror(errno)); + return; + } + } + + if (cmp & DIFF_XATTR) { + for (i = 0; i < real->xattrs; i++) { + /* Any attrs to remove? */ + if (mentry_find_xattr(stored, real, i) >= 0) + continue; + + msg(MSG_NORMAL, "%s:\tremoving xattr %s\n", + real->path, real->xattr_names[i]); + if (lremovexattr(real->path, real->xattr_names[i])) + msg(MSG_DEBUG, "\tlremovexattr failed: %s\n", + strerror(errno)); + } + + for (i = 0; i < stored->xattrs; i++) { + /* Any xattrs to add? (on change they are removed above) */ + if (mentry_find_xattr(real, stored, i) >= 0) + continue; + + msg(MSG_NORMAL, "%s:\tadding xattr %s\n", + stored->path, stored->xattr_names[i]); + if (lsetxattr(stored->path, stored->xattr_names[i], + stored->xattr_values[i], + stored->xattr_lvalues[i], XATTR_CREATE)) + msg(MSG_DEBUG, "\tlsetxattr failed: %s\n", + strerror(errno)); + } + } +} + +/* + * Tries to fix any empty dirs which are missing from the filesystem by + * recreating them. + */ +static void +fixup_emptydirs(void) +{ + struct metaentry *entry; + struct metaentry *cur; + struct metaentry **parent; + char *bpath; + char *delim; + size_t blen; + struct metaentry *new; + + if (!missingdirs) + return; + msg(MSG_DEBUG, "\nAttempting to recreate missing dirs\n"); + + /* If directory x/y is missing, but file x/y/z is also missing, + * we should prune directory x/y from the list of directories to + * recreate since the deletition of x/y is likely to be genuine + * (as opposed to empty dir pruning like git/cvs does). + * + * Also, if file x/y/z is missing, any child directories of + * x/y should be pruned as they are probably also intentionally + * removed. + */ + + msg(MSG_DEBUG, "List of candidate dirs:\n"); + for (cur = missingdirs; cur; cur = cur->list) + msg(MSG_DEBUG, " %s\n", cur->path); + + for (entry = missingothers; entry; entry = entry->list) { + msg(MSG_DEBUG, "Pruning using file %s\n", entry->path); + bpath = xstrdup(entry->path); + delim = strrchr(bpath, '/'); + if (!delim) { + msg(MSG_NORMAL, "No delimiter found in %s\n", bpath); + free(bpath); + continue; + } + *delim = '\0'; + + parent = &missingdirs; + for (cur = *parent; cur; cur = cur->list) { + if (strcmp(cur->path, bpath)) { + parent = &cur->list; + continue; + } + + msg(MSG_DEBUG, "Prune phase 1 - %s\n", cur->path); + *parent = cur->list; + } + + /* Now also prune subdirs of the base dir */ + *delim++ = '/'; + *delim = '\0'; + blen = strlen(bpath); + + parent = &missingdirs; + for (cur = *parent; cur; cur = cur->list) { + if (strncmp(cur->path, bpath, blen)) { + parent = &cur->list; + continue; + } + + msg(MSG_DEBUG, "Prune phase 2 - %s\n", cur->path); + *parent = cur->list; + } + + free(bpath); + } + msg(MSG_DEBUG, "\n"); + + for (cur = missingdirs; cur; cur = cur->list) { + msg(MSG_QUIET, "%s:\trecreating...", cur->path); + if (mkdir(cur->path, cur->mode)) { + msg(MSG_QUIET, "failed (%s)\n", strerror(errno)); + continue; + } + msg(MSG_QUIET, "ok\n"); + + new = mentry_create(cur->path); + if (!new) { + msg(MSG_QUIET, "Failed to get metadata for %s\n"); + continue; + } + + compare_fix(new, cur, mentry_compare(new, cur, &settings)); + } +} + +/* + * Deletes any empty dirs present in the filesystem that are missing + * from the metadata. + * An "empty" dir is one which either: + * - is empty; or + * - only contains empty dirs + */ +static void +fixup_newemptydirs(void) +{ + struct metaentry **cur; + int removed_dirs = 1; + + if (!extradirs) + return; + + /* This is a simpleminded algorithm that attempts to rmdir() all + * directories discovered missing from the metadata. Naturally, this will + * succeed only on the truly empty directories, but depending on the order, + * it may mean that parent directory removal are attempted to be removed + * *before* the children. To circumvent this, keep looping around all the + * directories until none have been successfully removed. This is a + * O(N**2) algorithm, so don't try to remove too many nested directories + * at once (e.g. thousands). + * + * Note that this will succeed only if each parent directory is writable. + */ + while (removed_dirs) { + removed_dirs = 0; + msg(MSG_DEBUG, "\nAttempting to delete empty dirs\n"); + for (cur = &extradirs; *cur;) { + msg(MSG_QUIET, "%s:\tremoving...", (*cur)->path); + if (rmdir((*cur)->path)) { + msg(MSG_QUIET, "failed (%s)\n", strerror(errno)); + cur = &(*cur)->list; + continue; + } + /* No freeing, because OS will do the job at the end. */ + *cur = (*cur)->list; + removed_dirs++; + msg(MSG_QUIET, "ok\n"); + } + } +} + +/* Prints usage message and exits */ +static void +usage(const char *arg0, const char *message) +{ + if (message) + msg(MSG_CRITICAL, "%s: %s\n\n", arg0, message); + msg(MSG_CRITICAL, +"Usage: %s ACTION [OPTION...] [PATH...]\n", + arg0); + msg(MSG_CRITICAL, +"\n" +"Where ACTION is one of:\n" +" -c, --compare Show differences between stored and real metadata\n" +" -s, --save Save current metadata\n" +" -a, --apply Apply stored metadata\n" +" -d, --dump Dump stored (if no PATH is given) or real metadata\n" +" (if PATH is present, e.g. ./) in human-readable form\n" +" -h, --help Help message (this text)\n" +"\n" +"Valid OPTIONS are:\n" +" -v, --verbose Print more verbose messages\n" +" -q, --quiet Print less verbose messages\n" +" -m, --mtime Also take mtime into account for diff or apply\n" +" -e, --empty-dirs Recreate missing empty directories\n" +" -E, --remove-empty-dirs Remove extra empty directories\n" +" -g, --git Do not omit .git directories\n" +" -f, --file=FILE Set metadata file (" METAFILE " by default)\n" + ); + + exit(message ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/* Options */ +static struct option long_options[] = { + { "compare", no_argument, NULL, 'c' }, + { "save", no_argument, NULL, 's' }, + { "apply", no_argument, NULL, 'a' }, + { "dump", no_argument, NULL, 'd' }, + { "help", no_argument, NULL, 'h' }, + { "verbose", no_argument, NULL, 'v' }, + { "quiet", no_argument, NULL, 'q' }, + { "mtime", no_argument, NULL, 'm' }, + { "empty-dirs", no_argument, NULL, 'e' }, + { "remove-empty-dirs", no_argument, NULL, 'E' }, + { "git", no_argument, NULL, 'g' }, + { "file", required_argument, NULL, 'f' }, + { NULL, 0, NULL, 0 } +}; + +/* Main function */ +int +main(int argc, char **argv) +{ + int i, c; + struct metahash *real = NULL; + struct metahash *stored = NULL; + int action = 0; + + /* Parse options */ + i = 0; + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "csadhvqmeEgf:", + long_options, &option_index); + if (c == -1) + break; + switch (c) { + case 'c': /* compare */ action |= ACTION_DIFF; i++; break; + case 's': /* save */ action |= ACTION_SAVE; i++; break; + case 'a': /* apply */ action |= ACTION_APPLY; i++; break; + case 'd': /* dump */ action |= ACTION_DUMP; i++; break; + case 'h': /* help */ action |= ACTION_HELP; i++; break; + case 'v': /* verbose */ adjust_verbosity(1); break; + case 'q': /* quiet */ adjust_verbosity(-1); break; + case 'm': /* mtime */ settings.do_mtime = true; break; + case 'e': /* empty-dirs */ settings.do_emptydirs = true; break; + case 'E': /* remove-empty-dirs */ settings.do_removeemptydirs = true; + break; + case 'g': /* git */ settings.do_git = true; break; + case 'f': /* file */ settings.metafile = optarg; break; + default: + usage(argv[0], "unknown option"); + } + } + + /* Make sure only one action is specified */ + if (i != 1) + usage(argv[0], "incorrect option(s)"); + + /* Make sure --empty-dirs is only used with apply */ + if (settings.do_emptydirs && action != ACTION_APPLY) + usage(argv[0], "--empty-dirs is only valid with --apply"); + + /* Make sure --remove-empty-dirs is only used with apply */ + if (settings.do_removeemptydirs && action != ACTION_APPLY) + usage(argv[0], "--remove-empty-dirs is only valid with --apply"); + + if (action == ACTION_HELP) + usage(argv[0], NULL); + + /* Perform action */ + if (action & ACTIONS_READING && !(action == ACTION_DUMP && optind < argc)) { + mentries_fromfile(&stored, settings.metafile); + if (!stored) { + msg(MSG_CRITICAL, "Failed to load metadata from %s\n", + settings.metafile); + exit(EXIT_FAILURE); + } + } + + if (optind < argc) { + while (optind < argc) + mentries_recurse_path(argv[optind++], &real, &settings); + } else if (action != ACTION_DUMP) { + mentries_recurse_path(".", &real, &settings); + } + + if (!real && (action != ACTION_DUMP || optind < argc)) { + msg(MSG_CRITICAL, + "Failed to load metadata from file system\n"); + exit(EXIT_FAILURE); + } + + switch (action) { + case ACTION_DIFF: + mentries_compare(real, stored, compare_print, &settings); + break; + case ACTION_SAVE: + mentries_tofile(real, settings.metafile); + break; + case ACTION_APPLY: + mentries_compare(real, stored, compare_fix, &settings); + if (settings.do_emptydirs) + fixup_emptydirs(); + if (settings.do_removeemptydirs) + fixup_newemptydirs(); + break; + case ACTION_DUMP: + mentries_dump(real ? real : stored); + break; + } + + exit(EXIT_SUCCESS); +} + diff --git a/src/metastore.h b/src/metastore.h new file mode 100644 index 0000000..384808d --- /dev/null +++ b/src/metastore.h @@ -0,0 +1,43 @@ +/* + * Main functions of the program. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef METASTORE_H +#define METASTORE_H + +/* Each file starts with SIGNATURE and VERSION */ +#define SIGNATURE "MeTaSt00r3" +#define SIGNATURELEN 10 +#define VERSION "\0\0\0\0\0\0\0\0" +#define VERSIONLEN 8 + +/* Default filename */ +#define METAFILE "./.metadata" + +/* Utility defines for the action to take */ +#define ACTION_APPLY 0x01 +#define ACTION_DIFF 0x02 +#define ACTION_DUMP 0x04 +#define ACTION_SAVE 0x10 +#define ACTION_HELP 0x80 + +/* Action masks */ +#define ACTIONS_READING 0x07 +#define ACTIONS_WRITING 0x70 + +#endif /* METASTORE_H */ diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..a9ef996 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 Przemyslaw Pawelczyk <przemoc@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef SETTINGS_H +#define SETTINGS_H + +#include <stdbool.h> + +/* Data structure to hold metastore settings */ +struct metasettings { + char *metafile; /* path to the file containing the metadata */ + bool do_mtime; /* should mtimes be corrected? */ + bool do_emptydirs; /* should empty dirs be recreated? */ + bool do_removeemptydirs; /* should new empty dirs be removed? */ + bool do_git; /* should .git dirs be processed? */ +}; + +/* Convenient typedef for immutable settings */ +typedef const struct metasettings msettings; + +#endif /* SETTINGS_H */ diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..7c85b38 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,303 @@ +/* + * Main functions of the program. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define _BSD_SOURCE +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <stdint.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <grp.h> +#include <pwd.h> + +#include "utils.h" + +/* Controls the verbosity level for msg() */ +static int verbosity = 0; + +/* Adjusts the verbosity level for msg() */ +void +adjust_verbosity(int adj) +{ + verbosity += adj; +} + +/* + * Prints messages to console according to the current verbosity + * - see utils.h for level defines + */ +int +msg(int level, const char *fmt, ...) +{ + int ret; + va_list ap; + + if (level > verbosity) + return 0; + + va_start(ap, fmt); + + if (level < MSG_QUIET) + ret = vfprintf(stderr, fmt, ap); + else + ret = vfprintf(stdout, fmt, ap); + + va_end(ap); + return ret; +} + +/* Malloc which either succeeds or exits */ +void * +xmalloc(size_t size) +{ + void *result = malloc(size); + if (!result) { + msg(MSG_CRITICAL, "Failed to malloc %zi bytes\n", size); + exit(EXIT_FAILURE); + } + return result; +} + +/* Ditto for strdup */ +char * +xstrdup(const char *s) +{ + char *result = strdup(s); + if (!result) { + msg(MSG_CRITICAL, "Failed to strdup %zi bytes\n", strlen(s)); + exit(EXIT_FAILURE); + } + return result; +} + +/* Human-readable printout of binary data */ +void +binary_print(const char *s, ssize_t len) +{ + ssize_t i; + + for (i = 0; i < len; i++) { + if (isprint(s[i])) + msg(MSG_DEBUG, "%c", s[i]); + else + msg(MSG_DEBUG, "0x%02X", (int)s[i]); + } +} + +/* Writes data to a file or exits on failure */ +void +xfwrite(const void *ptr, size_t size, FILE *stream) +{ + if (size && fwrite(ptr, size, 1, stream) != 1) { + msg(MSG_CRITICAL, "Failed to write to file: %s\n", + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +/* Writes an int to a file, using len bytes, in bigendian order */ +void +write_int(uint64_t value, size_t len, FILE *to) +{ + char buf[len]; + size_t i; + + for (i = 0; i < len; i++) + buf[i] = ((value >> (8 * i)) & 0xff); + xfwrite(buf, len, to); +} + +/* Writes a binary string to a file */ +void +write_binary_string(const char *string, size_t len, FILE *to) +{ + xfwrite(string, len, to); +} + +/* Writes a normal C string to a file */ +void +write_string(const char *string, FILE *to) +{ + xfwrite(string, strlen(string) + 1, to); +} + +/* Reads an int from a file, using len bytes, in bigendian order */ +uint64_t +read_int(char **from, size_t len, const char *max) +{ + uint64_t result = 0; + size_t i; + + if (*from + len > max) { + msg(MSG_CRITICAL, + "Attempt to read beyond end of file, corrupt file?\n"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < len; i++) + result += (((*from)[i] & 0xff) << (8 * i)); + *from += len; + return result; +} + +/* Reads a binary string from a file */ +char * +read_binary_string(char **from, size_t len, const char *max) +{ + char *result; + + if (*from + len > max) { + msg(MSG_CRITICAL, + "Attempt to read beyond end of file, corrupt file?\n"); + exit(EXIT_FAILURE); + } + + result = xmalloc(len); + memcpy(result, *from, len); + *from += len; + return result; +} + +/* Reads a normal C string from a file */ +char * +read_string(char **from, const char *max) +{ + return read_binary_string(from, strlen(*from) + 1, max); +} + +/* For group caching */ +static struct group *gtable = NULL; + +/* Initial setup of the gid table */ +static void +create_group_table() +{ + struct group *tmp; + int count, index; + + for (count = 0; getgrent(); count++) /* Do nothing */; + + gtable = xmalloc(sizeof(struct group) * (count + 1)); + memset(gtable, 0, sizeof(struct group) * (count + 1)); + setgrent(); + + for (index = 0; (tmp = getgrent()) && index < count; index++) { + gtable[index].gr_gid = tmp->gr_gid; + gtable[index].gr_name = xstrdup(tmp->gr_name); + } + + endgrent(); +} + +/* Caching version of getgrnam */ +struct group * +xgetgrnam(const char *name) +{ + int i; + + if (!gtable) + create_group_table(); + + for (i = 0; gtable[i].gr_name; i++) { + if (!strcmp(name, gtable[i].gr_name)) + return &(gtable[i]); + } + + return NULL; +} + +/* Caching version of getgrgid */ +struct group * +xgetgrgid(gid_t gid) +{ + int i; + + if (!gtable) + create_group_table(); + + for (i = 0; gtable[i].gr_name; i++) { + if (gtable[i].gr_gid == gid) + return &(gtable[i]); + } + + return NULL; +} + +/* For user caching */ +static struct passwd *ptable = NULL; + +/* Initial setup of the passwd table */ +static void +create_passwd_table() +{ + struct passwd *tmp; + int count, index; + + for (count = 0; getpwent(); count++) /* Do nothing */; + + ptable = xmalloc(sizeof(struct passwd) * (count + 1)); + memset(ptable, 0, sizeof(struct passwd) * (count + 1)); + setpwent(); + + for (index = 0; (tmp = getpwent()) && index < count; index++) { + ptable[index].pw_uid = tmp->pw_uid; + ptable[index].pw_name = xstrdup(tmp->pw_name); + } + + endpwent(); +} + +/* Caching version of getpwnam */ +struct passwd * +xgetpwnam(const char *name) +{ + int i; + + if (!ptable) + create_passwd_table(); + + for (i = 0; ptable[i].pw_name; i++) { + if (!strcmp(name, ptable[i].pw_name)) + return &(ptable[i]); + } + + return NULL; +} + +/* Caching version of getpwuid */ +struct passwd * +xgetpwuid(uid_t uid) +{ + int i; + + if (!ptable) + create_passwd_table(); + + for (i = 0; ptable[i].pw_name; i++) { + if (ptable[i].pw_uid == uid) + return &(ptable[i]); + } + + return NULL; +} + diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..2dd61c9 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,90 @@ +/* + * Main functions of the program. + * + * Copyright (C) 2007 David Härdeman <david@hardeman.nu> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; only version 2 of the License is applicable. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef UTILS_H +#define UTILS_H + +/* For uint64_t */ +#include <stdint.h> +/* For ssize_t */ +#include <unistd.h> +/* For FILE */ +#include <stdio.h> +/* For struct passwd */ +#include <pwd.h> +/* For struct group */ +#include <grp.h> + +/* Adjusts the verbosity level for msg() */ +void adjust_verbosity(int adj); + +/* Verbosity levels using stdout */ +#define MSG_NORMAL 0 +#define MSG_DEBUG 1 +#define MSG_QUIET -1 +/* Verbosity levels using stderr */ +#define MSG_ERROR -2 +#define MSG_CRITICAL -3 + +/* Prints messages to console according to the current verbosity */ +int msg(int level, const char *fmt, ...); + +/* Malloc which either succeeds or exits */ +void *xmalloc(size_t size); + +/* Ditto for strdup */ +char *xstrdup(const char *s); + +/* Human-readable printout of binary data */ +void binary_print(const char *s, ssize_t len); + +/* Writes data to a file or exits on failure */ +void xfwrite(const void *ptr, size_t size, FILE *stream); + +/* Writes an int to a file, using len bytes, in bigendian order */ +void write_int(uint64_t value, size_t len, FILE *to); + +/* Writes a binary string to a file */ +void write_binary_string(const char *string, size_t len, FILE *to); + +/* Writes a normal C string to a file */ +void write_string(const char *string, FILE *to); + +/* Reads an int from a file, using len bytes, in bigendian order */ +uint64_t read_int(char **from, size_t len, const char *max); + +/* Reads a binary string from a file */ +char *read_binary_string(char **from, size_t len, const char *max); + +/* Reads a normal C string from a file */ +char *read_string(char **from, const char *max); + +/* Caching version of getgrnam */ +struct group *xgetgrnam(const char *name); + +/* Caching version of getgrgid */ +struct group *xgetgrgid(gid_t gid); + +/* Caching version of getpwnam */ +struct passwd *xgetpwnam(const char *name); + +/* Caching version of getpwuid */ +struct passwd *xgetpwuid(uid_t uid); + +#endif /* UTILS_H */ |