// vidir.c - rewrite of Joey Hess's vidir in C // usage: ./vidir [--verbose] [dir|file|-] #include #include #include #include #include #include #include #include #include #include #define MAX_FILES 10000 typedef struct { int id; char *path; } Item; static int verbose = 0; static Item items[MAX_FILES]; static int n_items = 0; static int error_flag = 0; void die(const char *msg) { perror(msg); exit(1); } void add_item(const char *path) { if (n_items >= MAX_FILES) { fprintf(stderr, "too many files\n"); exit(1); } items[n_items].id = n_items + 1; items[n_items].path = strdup(path); n_items++; } void read_dir(const char *dirname) { DIR *dir = opendir(dirname); if (!dir) { fprintf(stderr, "cannot read %s: %s\n", dirname, strerror(errno)); exit(1); } struct dirent *ent; while ((ent = readdir(dir))) { if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", dirname, ent->d_name); add_item(path); } closedir(dir); } int rm_path(const char *p) { struct stat st; if (lstat(p, &st) != 0) return -1; if (S_ISDIR(st.st_mode)) return rmdir(p); else return unlink(p); } int main(int argc, char *argv[]) { char tmpname[] = "/tmp/vidirXXXXXX"; int fd; FILE *tmp; char line[PATH_MAX + 64]; char *editor; char buf[PATH_MAX + 64]; // parse args for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { verbose = 1; } else if (!strcmp(argv[i], "-")) { // read stdin list while (fgets(line, sizeof(line), stdin)) { line[strcspn(line, "\r\n")] = 0; if (*line) add_item(line); } } else { struct stat st; if (stat(argv[i], &st) == 0 && S_ISDIR(st.st_mode)) read_dir(argv[i]); else add_item(argv[i]); } } if (n_items == 0) read_dir("."); // write numbered list fd = mkstemp(tmpname); if (fd < 0) die("mkstemp"); tmp = fdopen(fd, "w"); if (!tmp) die("fdopen"); int digits = snprintf(NULL, 0, "%d", n_items); for (int i = 0; i < n_items; i++) fprintf(tmp, "%0*d\t%s\n", digits, items[i].id, items[i].path); fclose(tmp); // pick editor editor = getenv("VISUAL"); if (!editor) editor = getenv("EDITOR"); if (!editor) editor = "vi"; snprintf(buf, sizeof(buf), "%s %s", editor, tmpname); int ret = system(buf); if (ret != 0) { fprintf(stderr, "editor exited nonzero, aborting\n"); unlink(tmpname); exit(1); } // re-read edited list tmp = fopen(tmpname, "r"); if (!tmp) die("fopen tmp"); char newname[PATH_MAX]; while (fgets(line, sizeof(line), tmp)) { line[strcspn(line, "\r\n")] = 0; if (strlen(line) == 0) continue; int num; if (sscanf(line, "%d%[^\n]", &num, newname) < 1) continue; char *p = newname; while (isspace((unsigned char)*p)) p++; if (num <= 0 || num > n_items) continue; char *old = items[num - 1].path; if (*p == '\0') { // deleted line -> remove file if (rm_path(old) != 0) { fprintf(stderr, "failed to remove %s: %s\n", old, strerror(errno)); error_flag = 1; } else if (verbose) { printf("removed '%s'\n", old); } } else if (strcmp(p, old) != 0) { // rename char dirbuf[PATH_MAX]; strlcpy(dirbuf, p, sizeof(dirbuf)); char *d = dirname(dirbuf); mkdir(d, 0777); if (access(p, F_OK) == 0) { char tmpname[PATH_MAX + 16]; // allow space for suffixes int suffix = 0; snprintf(tmpname, sizeof(tmpname), "%s~", p); while (access(tmpname, F_OK) == 0) { suffix++; snprintf(tmpname, sizeof(tmpname), "%s~%d", p, suffix); } if (rename(p, tmpname) != 0) { fprintf(stderr, "failed to rename '%s' -> '%s': %s\n", p, tmpname, strerror(errno)); error_flag = 1; } else if (verbose) { printf("renamed '%s' -> '%s'\n", p, tmpname); } // update internal mapping so future renames see the new tmp location for (int j = 0; j < n_items; j++) { if (strcmp(items[j].path, p) == 0) { free(items[j].path); items[j].path = strdup(tmpname); } } } // now perform the intended rename if (rename(old, p) != 0) { fprintf(stderr, "failed to rename %s -> %s: %s\n", old, p, strerror(errno)); error_flag = 1; } else if (verbose) { printf("renamed '%s' -> '%s'\n", old, p); } free(items[num - 1].path); items[num - 1].path = strdup(p); } } fclose(tmp); unlink(tmpname); return error_flag; }