diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-04-04 11:55:08 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-04-04 14:52:02 +0800 |
| commit | cf421aeff4faa15a2a80495345ae76023830ca86 (patch) | |
| tree | d417dd85f75fc4c532bb0954ed59bea87d350914 /main.c | |
| parent | 3af329abee56900dd42cb305bd7a431ab3ded90a (diff) | |
| download | cvn-cf421aeff4faa15a2a80495345ae76023830ca86.tar.gz | |
Unit tests and fix hash collisions, deletions.
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 508 |
1 files changed, 0 insertions, 508 deletions
@@ -1,508 +0,0 @@ -#include <err.h> -#include <errno.h> -#include <fcntl.h> -#include <fnmatch.h> -#include <ftw.h> -#include <limits.h> -#include <sha1.h> -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <unistd.h> -#include <sys/wait.h> - -#define VCX_DIR ".vcx" -#define HEAD VCX_DIR "/head" -#define TMP_DIR VCX_DIR "/tmp" -#define OBJ_DIR VCX_DIR "/obj" - -#define EXCLUDE_PATHS ".vcxignore" - -#define MAX_DEPTH 256 -#define BUF_LEN 8192 -#define PATH_LEN PATH_MAX -#define HASH_LEN SHA1_DIGEST_STRING_LENGTH - -#define KIND_DEL 'D' -#define KIND_MOD 'M' -#define KIND_NEW 'N' - -#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 add(int argc, char *argv[]); - -static inline int cmplink(const char *link1, const char *link2); -static inline int cmpfile(const char *file1, const char *file2); - -static inline void stage_init(void); -static inline void stage_entry(const char *path, char kind, int islnk); -static inline void print_status(const char *file, char kind, int islnk); - -static inline int scan_head(const char * path, const struct stat *st, int flag, struct FTW *ftwbuf); -static inline int scan_tree(const char * path, const struct stat *st, int flag, struct FTW *ftwbuf); - -static inline void mkcopy(const char *path, char *dst); -static inline void mklink(const char *src, const char *obj); - -static inline void mkdirs(const char *path); -static inline void copy_link(const char *src); -static inline void copy_file(const char *src, const char *dst); - -static inline int lnklen(const char *lnk); -static inline char *format_path(const char *fmt, ...); - -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}, - {"add", add}, - {"status", status}, - {NULL, NULL} -}; - -int main(int argc, char *argv[]) -{ - if (argc < 2) - errx(1, "Usage: %s <command> [<args>]", argv[0]); - - for (int 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[]) -{ - if (mkdir(VCX_DIR, 0755) == -1) { - if (errno != EEXIST) - err(1, "Failed to create %s", VCX_DIR); - } - - if (mkdir(HEAD, 0755) == -1) { - if (errno != EEXIST) - err(1, "Failed to create %s", HEAD); - } - - if (mkdir(OBJ_DIR, 0755) == -1) { - if (errno != EEXIST) - err(1, "Failed to create %s", OBJ_DIR); - } - - printf("Repository ready\n"); -} - -static void (*scan_res_cb)(const char *file, char kind, int islnk) = NULL; - -static inline void status(int argc, char *argv[]) -{ - scan_res_cb = print_status; - - if (nftw(HEAD, scan_head, MAX_DEPTH, FTW_PHYS) == -1) - err(1, "Failed to scan head"); - - if (nftw(".", scan_tree, MAX_DEPTH, FTW_PHYS) == -1) - err(1, "Failed to scan work tree"); -} - -static inline void add(int argc, char *argv[]) -{ - size_t i; - struct stat st; - char *wt_path, *hd_path; - - if (argc < 2) - errx(1, "Usage: %s [<files>]", argv[0]); - - stage_init(); - scan_res_cb = stage_entry; - - for (i = 1; i < argc; i++) { - wt_path = argv[i]; - if (lstat(wt_path, &st) == -1) - err(1, "%s", wt_path); - - hd_path = format_path("%s/%s", HEAD, wt_path); - - if (nftw(hd_path, scan_head, MAX_DEPTH, FTW_PHYS) == -1) - err(1, "Failed to scan head"); - - if (nftw(wt_path, scan_tree, MAX_DEPTH, FTW_PHYS) == -1) - err(1, "Failed to scan work tree"); - } -} - -int scan_head(const char * path, const struct stat *sb, int flag, - struct FTW *ftwbuf) -{ - char *wt_path; - struct stat wt_stat; - - if (flag == FTW_F) { - const char *filename = path + ftwbuf->base; - wt_path = format_path("./%s", filename); - - if (lstat(wt_path, &wt_stat) == -1) { - if (errno == ENOENT) { - // file/link doesn't matter for deletions - scan_res_cb(wt_path, KIND_DEL, 0); - return 0; - } - else - err(1, "lstat failed for %s", wt_path); - } - - if (wt_stat.st_size == sb->st_size && - wt_stat.st_mtime == sb->st_mtime) - return 0; // no change - - if (S_ISREG(wt_stat.st_mode)) { - if (cmpfile(path, wt_path) == 1) - scan_res_cb(wt_path, KIND_MOD, 0); - } else if (S_ISLNK(wt_stat.st_mode)) { - if (cmplink(path, wt_path) == 1) - scan_res_cb(wt_path, KIND_MOD, 1); - } - } - - return 0; -} - -static inline int scan_tree(const char * path, const struct stat *st, - int flag, struct FTW *ftwbuf) -{ - char *hd_path; - - if (fnmatch("./.vcx/*", path, 0) == 0) - return 0; - - if (flag == FTW_F) { - const char *filename = path + ftwbuf->base; - hd_path = format_path("%s/%s", HEAD, filename); - if (access(hd_path, F_OK) != 0) - scan_res_cb(path, KIND_NEW, S_ISLNK(st->st_mode) != 0); - } - - return 0; -} - -static inline int cmpfile(const char *file1, const char *file2) -{ - int rc; - int fd1, fd2; - ssize_t r1, r2; - char buf1[BUF_LEN], buf2[BUF_LEN]; - - if ((fd1 = open(file1, O_RDONLY)) < 0) - err(1, "Couldn't open %s to compare", file1); - - if ((fd2 = open(file2, O_RDONLY)) < 0) - err(1, "Couldn't open %s to compare", file2); - - rc = 0; - do { - r1 = read(fd1, buf1, BUF_LEN); - r2 = read(fd2, buf2, BUF_LEN); - - if (r1 != r2 || memcmp(buf1, buf2, r1) != 0) { - rc = 1; - break; - } - } while (r1 > 0); - - close(fd1); - close(fd2); - return rc; -} - -static inline int cmplink(const char *link1, const char *link2) -{ - ssize_t len1, len2; - int buflen1, buflen2; - - buflen1 = lnklen(link1); - char buf1[buflen1]; - len1 = readlink(link1, buf1, sizeof(buf1) - 1); - - buflen2 = lnklen(link2); - char buf2[buflen2]; - len2 = readlink(link2, buf2, sizeof(buf2) - 1); - - if (len1 < 0 || len2 < 0) - err(1, "readlink error on %s or %s", link1, link2); - - if (len1 != len2) - return 1; - - buf1[len1] = '\0'; - buf2[len2] = '\0'; - - return strcmp(buf1, buf2); -} - -static inline void stage_init(void) -{ - pid_t rm_pid, cp_pid; - - rm_pid = fork(); - if (rm_pid == 0) { - if (access(TMP_DIR, F_OK) == 0) { - execlp("rm", "rm", "-rf", TMP_DIR, NULL); - err(1, "Failed to delete %s", TMP_DIR); - } - _exit(0); - } else { - waitpid(rm_pid, NULL, 0); - cp_pid = fork(); - if (cp_pid == 0) { - execlp("cp", "cp", "-RP", HEAD, TMP_DIR, NULL); - err(1, "Failed to copy %s to %s", HEAD, TMP_DIR); - } - waitpid(cp_pid, NULL, 0); - } -} - -static inline void stage_entry(const char *path, char kind, int islnk) -{ - char objname[HASH_LEN]; - - switch (kind) { - case KIND_MOD: - break; - case KIND_NEW: - if (!islnk) { - mkcopy(path, objname); - mklink(path, objname); - } else - copy_link(path); - break; - case KIND_DEL: - break; - default: - break; - } -} - -static inline void mkcopy(const char *path, char *dst) -{ - int len; - char *obj_path; - - len = strlen(path); - - if (SHA1Data((const uint8_t *)path, len, dst) == NULL) - err(1, "Failed to compute hash for %s", path); - - obj_path = format_path("%s/%s.tmp", OBJ_DIR, dst); - copy_file(path, obj_path); -} - -static inline void mklink(const char *src, const char *obj) -{ - char *lnk, *dst; - - lnk = format_path("../obj/%s.tmp", obj); - dst = format_path("%s/%s", TMP_DIR, src); - - mkdirs(dst); - - if (symlink(lnk, dst) != 0) - err(1, "symlink %s", src); -} - -static inline void print_status(const char *file, char kind, int islnk) -{ - printf("[%c] %s\n", kind, file); -} - -static inline int lnklen(const char *lnk) -{ - int len; - struct stat st; - - if (lstat(lnk, &st) != 0) - err(1, "lstat %s", lnk); - - len = st.st_size + 1; - if (PATH_LEN < len) - errx(1, "Link too long: %s", lnk); - - return len; -} - -static inline int diff(const char *file1, const char *file2) -{ - pid_t pid; - int null_fd, status, rc; - - pid = fork(); - - if (pid == 0) { - null_fd = open("/dev/null", O_WRONLY); - if (null_fd == -1) - err(1, "open() failed on /dev/null"); - - dup2(null_fd, STDOUT_FILENO); - dup2(null_fd, STDERR_FILENO); - close(null_fd); - - execlp("diff", "diff", "-q", file1, file2, (char *)NULL); - err(1, "execlp failed for diff"); - } else if (pid > 0) { - waitpid(pid, &status, 0); - // diff returns 0 if files are same, 1 if different, >1 if error - rc = WEXITSTATUS(status); - if (rc > 1) - err(1, "diff error"); - return rc; - } - - err(1, "fork"); -} - -static inline void copy_file(const char *src, const char *dst) -{ - int fdin, fdout; - struct stat st; - char buf[BUF_LEN], *outptr; - ssize_t nread, nwrite, res; - - if ((fdin = open(src, O_RDONLY)) < 0) - err(1, "open failed for %s", src); - - if (fstat(fdin, &st) < 0) { - close(fdin); - err(1, "fstat failed for %s", src); - } - - mkdirs(dst); - - fdout = open(dst, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode & 0777); - if (fdout < 0) { - close(fdin); - err(1, "open failed for %s", dst); - } - - while ((nread = read(fdin, buf, sizeof(buf))) > 0) { - nwrite = 0; - outptr = buf; - - // Handle partial writes (possible with large files/slow disks) - while (nwrite < nread) { - res = write(fdout, outptr + nwrite, nread - nwrite); - if (res < 0) { - if (errno == EINTR) // interrupted by a signal - retry - continue; - close(fdin); - close(fdout); - err(1, "Copy error on %s", src); // bad error: e.g, disk full - } - nwrite += res; - } - } - - close(fdin); - close(fdout); -} - -static inline void copy_link(const char *src) -{ - int len; - ssize_t n; - char *dst; - - // todo: use path_buf - len = lnklen(src); - char target[len]; - n = readlink(src, target, len); - if (n == -1) - err(1, "readlink %s", src); - - target[n] = '\0'; - - dst = format_path("%s/%s", TMP_DIR, src); - mkdirs(dst); - - if (symlink(target, dst) != 0) - err(1, "Link copy error %s", src); -} - -static inline void mkdirs(const char *path) -{ - char *p; - int pathlen; - - static char buf[PATH_LEN]; - - pathlen = strlen(path); - if (PATH_LEN < pathlen + 1) - errx(1, "Path too long: %s", path); - - buf[0] = '\0'; - strcpy(buf, path); - - for (p = buf; *p; p++) { - if (*p == '/') { - *p = '\0'; - if (mkdir(buf, 0755) != 0) { - if (errno != EEXIST) - err(1, "mkdir failed for %s", buf); - } - *p = '/'; - } - } -} - -static char *path_buf[2][PATH_LEN]; - -static inline char *format_path(const char *fmt, ...) -{ - int rc; - va_list args; - static uint8_t i = 1; - - i ^= 1; - va_start(args, fmt); - rc = vsnprintf((char *)path_buf[i], PATH_LEN, fmt, args); - va_end(args); - - if (rc < 0) - err(1, "vsnprintf"); - - if (rc >= PATH_LEN) - errx(1, "Path too long"); - - return (char *)path_buf[i]; -} - -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; -} - |
