#include #include #include #include #include #include #include #include #include #include #include #include #define REPO ".cvn" #define INDEX "index" #define HASH_LEN SHA1_DIGEST_LENGTH #define FLAG_DIR (1 << 0) // 0x01 #define FLAG_EXE (1 << 1) // 0x02 #define FLAG_LNK (1 << 2) // 0x04 #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[]); struct stack; struct stack_ent; static inline void stack_alloc(struct stack *st); static inline struct stack_ent *pop(struct stack *st); static inline void push(struct stack *st, struct stack_ent *new_ent); static inline void stack_free(struct stack *st); static inline void traverse(void); static inline void hash(struct stack *st); 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(REPO, 0755) == -1) { if (errno != EEXIST) err(1, "Failed to create repository"); } if ((repo_fd = open(REPO, O_RDONLY | O_DIRECTORY)) == -1) err(1, "Failed to open repository"); if ((idx_fd = openat(repo_fd, INDEX, 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", REPO); } static inline void status(int argc, char *argv[]) { traverse(); } struct stack_ent { char *path; unsigned char flags; unsigned char hash[HASH_LEN]; struct stack_ent *parent; }; struct stack { struct stack_ent **ents; size_t len; size_t cap; }; static inline void traverse(void) { DIR *dir; char *rel_path; struct stat st; struct dirent *entry; struct stack work_tree, stk; struct stack_ent *cur_dir, *root, *new_ent; stack_alloc(&stk); stack_alloc(&work_tree); root = MALLOC(sizeof(struct stack_ent)); root->path = strdup("."); root->flags = FLAG_DIR; root->parent = NULL; push(&stk, root); while (stk.len > 0) { cur_dir = pop(&stk); if (!(dir = opendir(cur_dir->path))) { warn("Failed to open directory %s", cur_dir->path); continue; } push(&work_tree, cur_dir); while ((entry = readdir(dir))) { if (ignore(entry->d_name)) continue; if (asprintf(&rel_path, "%s/%s", cur_dir->path, entry->d_name) == -1) err(1, "asprintf() failed"); if (lstat(rel_path, &st) == -1) { warn("lstat() failed: %s", rel_path); free(rel_path); continue; } if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) { new_ent = MALLOC(sizeof(struct stack_ent)); new_ent->path = rel_path; new_ent->flags = 0; new_ent->parent = cur_dir; if (S_ISLNK(st.st_mode)) new_ent->flags |= FLAG_LNK; if (S_ISDIR(st.st_mode)) { new_ent->flags |= FLAG_DIR; push(&stk, new_ent); } else push(&work_tree, new_ent); } } closedir(dir); } hash(&work_tree); for (ssize_t i = work_tree.len - 1; i >= 0; i--) { printf("Entry: %s (p: %s): 0x", work_tree.ents[i]->path, work_tree.ents[i]->parent ? work_tree.ents[i]->parent->path : "NONE"); for (int j = 0; j < HASH_LEN; j++) printf("%02x", work_tree.ents[i]->hash[j]); putchar('\n'); } stack_free(&stk); stack_free(&work_tree); } static inline void hash(struct stack *st) { int fd; ssize_t i, j; SHA1_CTX ctx; unsigned char *buf; ssize_t bytes_read, lnk_len; struct stack_ent *ent, *prev_ent; const int buflen = 8192 > PATH_MAX ? 8192 : PATH_MAX; buf = MALLOC(sizeof(buf[0]) * buflen); for (i = st->len - 1; i >= 0; i--) { SHA1Init(&ctx); ent = st->ents[i]; if (!ent->flags) { /* regular file */ if ((fd = open(ent->path, O_RDONLY)) == -1) err(1, "Cannot open %s", ent->path); while ((bytes_read = read(fd, buf, buflen)) > 0) SHA1Update(&ctx, buf, (size_t)bytes_read); if (bytes_read == -1) err(1, "Read error on %s", ent->path); close(fd); } else if ((ent->flags & FLAG_LNK)) { lnk_len = readlink(ent->path, buf, buflen - 1); if (lnk_len != -1) { buf[lnk_len] = '\0'; SHA1Update(&ctx, buf, lnk_len); } else err(1, "readlink error on %s", ent->path); } else { // directory: walk the stack backwards and collect // hashes from children for (j = i + 1; j < st->len; j++) { prev_ent = st->ents[j]; if (prev_ent->parent != ent) break; SHA1Update(&ctx, prev_ent->hash, HASH_LEN); } } SHA1Final(ent->hash, &ctx); } free(buf); } static inline void stack_alloc(struct stack *st) { st->len = 0; st->cap = 512; st->ents = MALLOC(sizeof(st->ents[0]) * st->cap); } static inline struct stack_ent *pop(struct stack *st) { return st->ents[--(st->len)]; } static inline void push(struct stack *st, struct stack_ent *new_ent) { if (st->len >= st->cap) { st->cap <<= 1; st->ents = REALLOC(st->ents, sizeof(struct stack_ent *) * st->cap); } st->ents[st->len++] = new_ent; } static inline void stack_free(struct stack *st) { size_t i; for (i = 0; i < st->len; i++) { free(st->ents[i]->path); free(st->ents[i]); } free(st->ents); } static inline int ignore(const char *path) { return strcmp(path, ".") == 0 || strcmp(path, "..") == 0 || strcmp(path, REPO) == 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; }