#include #include #include #include #include #include #include #include #include #define MAX_DEPTH 256 #define CVN_DIR ".cvn" #define INDEX_FILE "index" #define HASH_LEN SHA1_DIGEST_LENGTH #define HASH_INPUT_LEN 4096 #define MALLOC(s) _xmalloc((s), __FILE__, __LINE__) #define REALLOC(p, s) _xrealloc((p), (s), __FILE__, __LINE__) 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); static inline void *_xmalloc(size_t s, const char *file, int line); static inline void *_xrealloc(void *ptr, size_t s, const char *file, int line); 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(); } static char hash_input[HASH_INPUT_LEN]; static uint8_t *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); } void update_hash_at(size_t level, const uint8_t *hash) { SHA1_CTX ctx; if (level >= 0) { if (hash_at_level[level]) { SHA1Init(&ctx); SHA1Update(&ctx, hash_at_level[level], HASH_LEN); SHA1Update(&ctx, hash, HASH_LEN); SHA1Final(hash_at_level[level], &ctx); } else { hash_at_level[level] = MALLOC(sizeof(uint8_t) * HASH_LEN); memcpy(hash_at_level[level], hash, HASH_LEN); } } } 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); } static inline void hash_dir(size_t level, uint8_t *hash) { memcpy(hash, hash_at_level[level], HASH_LEN); free(hash_at_level[level]); 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 *file_name = path + ftwbuf->base; if (ignore(file_name)) 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); update_hash_at(level - 1, hash); } else if (tflag == FTW_DP) { hash_dir(level, hash); update_hash_at(level - 1, 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; } static inline void *_xmalloc(size_t s, const char *file, int line) { void *p; if (!(p = malloc(s))) err(1, "%s:%d: malloc", file, line); return p; } static inline void *_xrealloc(void *ptr, size_t s, const char *file, int line) { void *p; if (!(p = realloc(ptr, s))) err(1, "%s:%d: realloc", file, line); return p; }