#include #include #include #include #include #include #include #include #include #include #include #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 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 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 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 *_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[]) { if (argc < 2) errx(1, "Usage: %s []", 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); } if (mkdir(TMP_DIR, 0755) == -1) { if (errno != EEXIST) err(1, "Failed to create %s", TMP_DIR); } printf("Repository ready\n"); } static inline void status(int argc, char *argv[]) { if (nftw(HEAD, scan_head, MAX_DEPTH, FTW_PHYS) == -1) err(1, "scan_head"); if (nftw(".", scan_tree, MAX_DEPTH, FTW_PHYS) == -1) err(1, "scan_tree"); } static char *path_buf = NULL; static size_t path_buflen = 1024; 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); if (lstat(wt_path, &wt_stat) == -1) { if (errno == ENOENT) { printf("[D] %s\n", wt_path); 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 (cmp_content(path, wt_path) == 1) printf("[M] %s\n", wt_path); } else if (S_ISLNK(wt_stat.st_mode)) { if (cmp_links(path, wt_path) == 1) printf("[M] %s\n", wt_path); } } 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, FNM_PATHNAME) == 0) return 0; if (flag == FTW_F) { const char *filename = path + ftwbuf->base; hd_path = concat_path(HEAD, filename); if (access(hd_path, F_OK) != 0) printf("[N] %s\n", path); } return 0; } static inline int cmp_content(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 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 char *concat_path(const char *dir, const char *file) { size_t path_len; if (!path_buf) path_buf = MALLOC(sizeof(path_buf[0]) * path_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); } snprintf(path_buf, path_buflen, "%s/%s", dir, file); return path_buf; } 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; }