#include #include #include #include #include #include #include #include #include #include "mem.h" #include "stack.h" #define MAX_DEPTH 256 #define CVN_DIR ".cvn" #define INDEX_FILE "index" #define HASH_LEN SHA1_DIGEST_LENGTH #define HASH_INPUT_LEN 4096 static inline void init(int argc, char *argv[]); static inline void status(int argc, char *argv[]); static inline void update_index(void); static inline int ignore(const char *path); struct command { char *name; void (*func)(int argc, char *argv[]); }; struct command cmd[] = { {"init", init}, {"status", status}, {NULL, NULL} }; int main(int argc, char *argv[]) { int i; if (argc < 2) errx(1, "Usage: %s []", argv[0]); for (i = 0; cmd[i].name != NULL; i++) { if (strcmp(argv[1], cmd[i].name) == 0) { cmd[i].func(argc - 1, argv + 1); return 0; } } return 0; } static inline void init(int argc, char *argv[]) { char *branch; int opt, repo_fd, idx_fd; optind = 1; while ((opt = getopt(argc, argv, "b:")) != -1) { switch (opt) { case 'b': branch = optarg; break; default: break; } } if (mkdir(CVN_DIR, 0755) == -1) { if (errno != EEXIST) err(1, "Failed to create repository"); } if ((repo_fd = open(CVN_DIR, O_RDONLY | O_DIRECTORY)) == -1) err(1, "Failed to open repository"); if ((idx_fd = openat(repo_fd, INDEX_FILE, O_WRONLY | O_CREAT | O_EXCL, 0644)) == -1) { if (errno != EEXIST) { close(repo_fd); err(1, "Failed to create index"); } } else close(idx_fd); close(repo_fd); printf("Initialized repository in %s\n", CVN_DIR); } static inline void status(int argc, char *argv[]) { update_index(); } struct hashed_file { char *name; uint8_t hash[HASH_LEN]; }; static char hash_input[HASH_INPUT_LEN]; static struct stack *hash_at_level[MAX_DEPTH] = {0}; static inline void print_hash(const uint8_t hash[HASH_LEN], const char *path) { printf("0x"); for (int i = 0; i < HASH_LEN; i++) printf("%02x", hash[i]); printf(": %s\n", path); } static inline void insert_hash(size_t level, const char *filename, const uint8_t *hash) { struct stack *st; struct hashed_file *hf; if (level >= 0) { hf = MALLOC(sizeof(struct hashed_file)); hf->name = strdup(filename); memcpy(hf->hash, hash, HASH_LEN); st = hash_at_level[level]; if (st) push(st, (void *)hf); else { st = MALLOC(sizeof(struct stack)); stack_alloc(st); push(st, (void *)hf); hash_at_level[level] = st; } } } void hash_file(const char *path, uint8_t *hash) { int fd; SHA1_CTX ctx; ssize_t bytes_read; if ((fd = open(path, O_RDONLY)) == -1) err(1, "Failed to open %s", path); SHA1Init(&ctx); while ((bytes_read = read(fd, hash_input, sizeof(hash_input))) > 0) SHA1Update(&ctx, hash_input, (size_t)bytes_read); if (bytes_read == -1) err(1, "Failed to read %s", path); SHA1Final(hash, &ctx); close(fd); } void hash_path(const char *path, uint8_t *hash) { SHA1_CTX ctx; ssize_t lnk_len; lnk_len = readlink(path, hash_input, HASH_INPUT_LEN - 1); if (lnk_len == -1) err(1, "readlink failed for %s", path); hash_input[lnk_len] = '\0'; SHA1Init(&ctx); SHA1Update(&ctx, hash_input, lnk_len); SHA1Final(hash, &ctx); } int compare_items(const void *a, const void *b) { const void *pa = *(const void **)a; const void *pb = *(const void **)b; const struct hashed_file *hfa = (const struct hashed_file *)pa; const struct hashed_file *hfb = (const struct hashed_file *)pb; return strcmp(hfb->name, hfa->name); } static inline void hash_dir(size_t level, uint8_t *hash) { SHA1_CTX ctx; struct stack *st; struct hashed_file *hf; st = hash_at_level[level]; if (st && st->len > 0) { SHA1Init(&ctx); qsort(st->items, st->len, sizeof(st->items[0]), compare_items); while (st->len > 0) { hf = pop(st); SHA1Update(&ctx, hf->hash, HASH_LEN); free(hf->name); free(hf); } SHA1Final(hash, &ctx); stack_free(st); hash_at_level[level] = NULL; } } int compute_hash(const char * path, const struct stat *st, int tflag, struct FTW *ftwbuf) { int level; mode_t mode; unsigned char hash[HASH_LEN]; const char *filename = path + ftwbuf->base; if (ignore(filename)) return 0; mode = st->st_mode; level = ftwbuf->level; if (tflag == FTW_F) { if (S_ISREG(mode)) hash_file(path, hash); else if (S_ISLNK(mode)) hash_path(path, hash); } else if (tflag == FTW_DP) { hash_dir(level, hash); } insert_hash(level - 1, filename, hash); print_hash(hash, path); return 0; } static inline void update_index(void) { if (nftw(".", compute_hash, MAX_DEPTH, FTW_DEPTH | FTW_PHYS) == -1) err(1, "nftw"); } static inline int ignore(const char *path) { return strcmp(path, CVN_DIR) == 0; }