diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2026-03-17 20:17:06 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2026-03-22 16:38:00 +0800 |
| commit | a21b1fbf041bbd09ba7ae41bea5e26335b9c3e91 (patch) | |
| tree | 8ef8a9baa6716bb9464441cf540033145bee6b95 | |
| parent | 8006b2bdddf7f863cd65053be77b98935041047d (diff) | |
| download | cvn-a21b1fbf041bbd09ba7ae41bea5e26335b9c3e91.tar.gz | |
wip: add.
| -rw-r--r-- | main.c | 348 |
1 files changed, 288 insertions, 60 deletions
@@ -4,19 +4,27 @@ #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 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 BUFLEN 8192 +#define MAX_DEPTH 256 + +#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__) @@ -25,14 +33,20 @@ 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 void print_status(const char *file, char kind); +static inline void init_stg_area(void); static inline int cmp_links(const char *link1, const char *link2); -static inline int cmp_content(const char *file1, const char *file2); -static inline char *concat_path(const char *dir, const char *file); +static inline int cmp_files(const char *file1, const char *file2); + +static inline void stage_file(const char *file, 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 mkdirs(const char *path); +static inline int copy_file(const char *src, const char *dst); +static inline void concat(char *dst, size_t n, const char *arg1, ...); + 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); @@ -42,7 +56,9 @@ struct command { }; struct command cmd[] = { - {"init", init}, {"status", status}, {"add", add}, + {"init", init}, + {"add", add}, + {"status", status}, {NULL, NULL} }; @@ -77,15 +93,10 @@ static inline void init(int argc, char *argv[]) err(1, "Failed to create %s", OBJ_DIR); } - if (mkdir(TMP_DIR, 0755) == -1) { - if (errno != EEXIST) - err(1, "Failed to create %s", TMP_DIR); - } - printf("Repository ready\n"); } -static void (*scan_res_cb)(const char *file, char kind) = NULL; +static void (*scan_res_cb)(const char *file, char kind, int islnk) = NULL; static inline void status(int argc, char *argv[]) { @@ -100,26 +111,54 @@ static inline void status(int argc, char *argv[]) static inline void add(int argc, char *argv[]) { - char **paths; - + size_t i; + struct stat st; + char *wt_path; + if (argc < 2) errx(1, "Usage: %s [<files>]", argv[0]); - paths = &argv[1]; + init_stg_area(); + scan_res_cb = stage_file; + + for (i = 1; i < argc; i++) { + wt_path = argv[i]; + if (lstat(wt_path, &st) == -1) + err(1, "%s", wt_path); + + printf("Loop index %zu, path: %s\n", i, wt_path); + + size_t hd_sz = strlen(HEAD) + strlen(wt_path) + 2; + char hd_path[hd_sz]; + concat(hd_path, hd_sz, HEAD, "/", wt_path, NULL); + + size_t tmp_sz = strlen(TMP_DIR) + strlen(wt_path) + 2; + char tmp_path[tmp_sz]; + concat(tmp_path, tmp_sz, TMP_DIR, "/", wt_path, NULL); + + 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 = concat_path(".", filename); + + size_t wt_sz = strlen(filename) + 3; + char wt_path[wt_sz]; + concat(wt_path, wt_sz, ".", "/", filename, NULL); + if (lstat(wt_path, &wt_stat) == -1) { if (errno == ENOENT) { - scan_res_cb(wt_path, 'D'); + // file/link doesn't matter for deletions + scan_res_cb(wt_path, KIND_DEL, 0); return 0; } else @@ -131,11 +170,11 @@ int scan_head(const char * path, const struct stat *sb, int flag, return 0; // no change if (S_ISREG(wt_stat.st_mode)) { - if (cmp_content(path, wt_path) == 1) - scan_res_cb(wt_path, 'M'); + if (cmp_files(path, wt_path) == 1) + scan_res_cb(wt_path, KIND_MOD, 0); } else if (S_ISLNK(wt_stat.st_mode)) { if (cmp_links(path, wt_path) == 1) - scan_res_cb(wt_path, 'M'); + scan_res_cb(wt_path, KIND_MOD, 1); } } @@ -145,22 +184,179 @@ int scan_head(const char * path, const struct stat *sb, int flag, static inline int scan_tree(const char * path, const struct stat *st, int flag, struct FTW *ftwbuf) { - char *hd_path; - if (fnmatch("./.vcx/*", path, FNM_PATHNAME) == 0) return 0; if (flag == FTW_F) { const char *filename = path + ftwbuf->base; - hd_path = concat_path(HEAD, filename); + + size_t hd_sz = strlen(HEAD) + strlen(filename) + 2; + char hd_path[hd_sz]; + concat(hd_path, hd_sz, HEAD, "/", filename, NULL); + if (access(hd_path, F_OK) != 0) - scan_res_cb(path, 'N'); + scan_res_cb(path, KIND_NEW, S_ISLNK(st->st_mode) != 0); } return 0; } -static inline int cmp_content(const char *file1, const char *file2) +static inline int cmp_files(const char *file1, const char *file2) +{ + int rc; + int fd1, fd2; + ssize_t r1, r2; + char buf1[BUFLEN], buf2[BUFLEN]; + + 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, BUFLEN); + r2 = read(fd2, buf2, BUFLEN); + + if (r1 != r2 || memcmp(buf1, buf2, r1) != 0) { + rc = 1; + break; + } + } while (r1 > 0); + + close(fd1); + close(fd2); + return rc; +} + +static inline int cmp_links(const char *link1, const char *link2) +{ + ssize_t len1, len2; + char buf1[PATH_MAX], buf2[PATH_MAX]; + + len1 = readlink(link1, buf1, sizeof(buf1) - 1); + 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 init_stg_area(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_file(const char *file, char kind, int islnk) +{ + int len, obj_len; + char obj_name[SHA1_DIGEST_STRING_LENGTH]; + + switch (kind) { + case KIND_MOD: + break; + case KIND_NEW: + if (!islnk) { + len = strlen(file); + obj_len = SHA1_DIGEST_STRING_LENGTH - 1; + + if (SHA1Data((const uint8_t *)file, len, obj_name) == NULL) + err(1, "Failed to compute hash for %s", file); + + size_t obj_sz = strlen(OBJ_DIR) + obj_len + 6; + char obj_path[obj_sz]; + concat(obj_path, obj_sz, OBJ_DIR, "/", obj_name, ".tmp", NULL); + copy_file(file, obj_path); + + // create link relative to tmp/ + size_t lnkt_sz = strlen("../obj/") + obj_len + 5; + char lnk_target[lnkt_sz]; + concat(lnk_target, lnkt_sz, "../obj/", obj_name, ".tmp", NULL); + + size_t lnkp_sz = strlen(TMP_DIR) + len + 2; + char lnk_path[lnkp_sz]; + concat(lnk_path, lnkp_sz, TMP_DIR, "/", file, NULL); + + printf("Link: %s -> %s\n", lnk_path, lnk_target); + mkdirs(lnk_path); + if (symlink(lnk_target, lnk_path) != 0) + err(1, "symlink error on %s", file); + } else { + + } + break; + case KIND_DEL: + break; + default: + break; + } +} + +static inline void print_status(const char *file, char kind, int islnk) +{ + printf("[%c] %s\n", kind, file); +} + +static inline void concat(char *dst, size_t n, const char *arg1, ...) +{ + va_list ap; + const char *part; + int curlen, partlen; + + if (!dst || n == 0) + return; + + dst[0] = '\0'; + curlen = 0; + + if (!arg1) + return; + + va_start(ap, arg1); + part = arg1; + + while (part) { + partlen = strlen(part); + if (curlen + partlen + 1 > n) { + dst[curlen] = '\0'; + va_end(ap); + errx(1, "Path too long: %s%s", dst, part); + } + memcpy(dst + curlen, part, partlen); + curlen += partlen; + part = va_arg(ap, const char *); + } + + dst[curlen] = '\0'; + va_end(ap); +} + +static inline int diff(const char *file1, const char *file2) { pid_t pid; int null_fd, status, rc; @@ -190,49 +386,81 @@ static inline int cmp_content(const char *file1, const char *file2) err(1, "fork"); } -static inline int cmp_links(const char *link1, const char *link2) +static inline int copy_file(const char *src, const char *dst) { - ssize_t len1, len2; - char buf1[PATH_MAX], buf2[PATH_MAX]; - - len1 = readlink(link1, buf1, sizeof(buf1) - 1); - len2 = readlink(link2, buf2, sizeof(buf2) - 1); - - if (len1 < 0 || len2 < 0) - err(1, "readlink error on %s or %s", link1, link2); + int fdin, fdout; + struct stat st; + char buf[BUFLEN], *outptr; + ssize_t nread, nwrite, res; - if (len1 != len2) - return 1; + if ((fdin = open(src, O_RDONLY)) < 0) + err(1, "open failed for %s", src); - buf1[len1] = '\0'; - buf2[len2] = '\0'; + if (fstat(fdin, &st) < 0) { + close(fdin); + err(1, "fstat failed for %s", src); + } - return strcmp(buf1, buf2); -} + mkdirs(dst); -static inline void print_status(const char *file, char kind) -{ - printf("[%c] %s\n", kind, file); + 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); + return (nread < 0) ? -1 : 0; } -static inline char *concat_path(const char *dir, const char *file) +static inline void mkdirs(const char *path) { - static char *path_buf = NULL; - static size_t path_buflen = 1024; + char *p; + int pathlen; - size_t path_len; + static char *buf = NULL; + static size_t buflen = 1024; - if (!path_buf) - path_buf = MALLOC(sizeof(path_buf[0]) * path_buflen); + if (!buf) + buf = MALLOC(buflen); - path_len = strlen(dir) + strlen(file) + 2; - if (path_len > path_buflen) { - path_buflen <<= 1; - path_buf = REALLOC(path_buf, sizeof(path_buf[0]) * path_buflen); + pathlen = strlen(path); + if (buflen < pathlen + 1) { + buflen *= 2; + buf = REALLOC(buf, buflen); + } + 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 = '/'; + } } - - snprintf(path_buf, path_buflen, "%s/%s", dir, file); - return path_buf; } static inline void *_xmalloc(size_t s, const char *file, int line) |
