vidir.c (5146B)
1 /* 2 * vidir.c - rewrite of Joey Hess's vidir in C 3 * usage: ./vidir [--verbose] [dir|file|-] 4 */ 5 6 #include <ctype.h> 7 #include <dirent.h> 8 #include <errno.h> 9 #include <libgen.h> 10 #include <limits.h> 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <sys/stat.h> 15 #include <sys/wait.h> 16 #include <unistd.h> 17 18 #define MAX_FILES 10000 19 20 typedef struct { 21 int id; 22 char *path; 23 } Item; 24 25 static int verbose = 0; 26 static Item items[MAX_FILES]; 27 static int n_items = 0; 28 static int error_flag = 0; 29 30 void die(const char *msg) { 31 perror(msg); 32 exit(1); 33 } 34 35 void add_item(const char *path) { 36 if (n_items >= MAX_FILES) { 37 fprintf(stderr, "too many files\n"); 38 exit(1); 39 } 40 items[n_items].id = n_items + 1; 41 items[n_items].path = strdup(path); 42 n_items++; 43 } 44 45 void read_dir(const char *dirname) { 46 DIR *dir = opendir(dirname); 47 if (!dir) { 48 fprintf(stderr, "cannot read %s: %s\n", dirname, strerror(errno)); 49 exit(1); 50 } 51 struct dirent *ent; 52 while ((ent = readdir(dir))) { 53 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) 54 continue; 55 char path[PATH_MAX]; 56 snprintf(path, sizeof(path), "%s/%s", dirname, ent->d_name); 57 add_item(path); 58 } 59 closedir(dir); 60 } 61 62 int rm_path(const char *p) { 63 struct stat st; 64 if (lstat(p, &st) != 0) 65 return -1; 66 return S_ISDIR(st.st_mode) ? rmdir(p) : unlink(p); 67 } 68 69 void run_editor(const char *editor, const char *filename) { 70 pid_t pid = fork(); 71 if (pid == 0) { 72 execlp(editor, editor, filename, (char *)NULL); 73 _exit(127); 74 } else if (pid > 0) { 75 waitpid(pid, NULL, 0); 76 } else { 77 die("fork"); 78 } 79 } 80 81 int main(int argc, char *argv[]) { 82 #ifdef __OpenBSD__ 83 if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1) 84 die("pledge"); 85 #endif 86 87 char tmpname[] = "/tmp/vidirXXXXXX"; 88 char line[PATH_MAX + 64]; 89 int fd = mkstemp(tmpname); 90 if (fd < 0) 91 die("mkstemp"); 92 93 /* Parse args */ 94 for (int i = 1; i < argc; i++) { 95 if (!strcmp(argv[i], "--verbose") || !strcmp(argv[i], "-v")) { 96 verbose = 1; 97 } else if (!strcmp(argv[i], "-")) { 98 while (fgets(line, sizeof(line), stdin)) { 99 line[strcspn(line, "\r\n")] = 0; 100 if (*line) 101 add_item(line); 102 } 103 } else { 104 struct stat st; 105 if (stat(argv[i], &st) == 0 && S_ISDIR(st.st_mode)) 106 read_dir(argv[i]); 107 else 108 add_item(argv[i]); 109 } 110 } 111 if (n_items == 0) 112 read_dir("."); 113 114 /* Write numbered list */ 115 FILE *tmp = fdopen(fd, "w"); 116 if (!tmp) 117 die("fdopen"); 118 int digits = snprintf(NULL, 0, "%d", n_items); 119 for (int i = 0; i < n_items; i++) 120 fprintf(tmp, "%0*d\t%s\n", digits, items[i].id, items[i].path); 121 fclose(tmp); 122 123 /* Pick editor */ 124 char *editor = getenv("VISUAL"); 125 if (!editor) 126 editor = getenv("EDITOR"); 127 if (!editor) 128 editor = "vi"; 129 130 run_editor(editor, tmpname); 131 132 int seen[MAX_FILES] = {0}; 133 134 /* Re-read edited list */ 135 tmp = fopen(tmpname, "r"); 136 if (!tmp) 137 die("fopen tmp"); 138 139 char newname[PATH_MAX]; 140 while (fgets(line, sizeof(line), tmp)) { 141 line[strcspn(line, "\r\n")] = 0; 142 if (strlen(line) == 0) 143 continue; 144 145 int num; 146 if (sscanf(line, "%d%[^\n]", &num, newname) < 1) 147 continue; 148 149 if (num <= 0 || num > n_items) 150 continue; 151 152 /* Mark this file as still existing in the list */ 153 seen[num - 1] = 1; 154 155 char *p = newname; 156 while (isspace((unsigned char)*p)) 157 p++; 158 159 char *old = items[num - 1].path; 160 161 /* Logic for renaming */ 162 if (strcmp(p, old) != 0) { 163 char dirbuf[PATH_MAX]; 164 strncpy(dirbuf, p, sizeof(dirbuf) - 1); 165 dirbuf[sizeof(dirbuf) - 1] = '\0'; 166 char *d = dirname(dirbuf); 167 mkdir(d, 0777); 168 169 /* Handle collisions */ 170 if (access(p, F_OK) == 0) { 171 char collision_tmp[PATH_MAX + 16]; 172 int suffix = 0; 173 snprintf(collision_tmp, sizeof(collision_tmp), "%s~", p); 174 while (access(collision_tmp, F_OK) == 0) { 175 suffix++; 176 snprintf(collision_tmp, sizeof(collision_tmp), "%s~%d", p, suffix); 177 } 178 if (rename(p, collision_tmp) == 0) { 179 if (verbose) 180 printf("collision: renamed '%s' -> '%s'\n", p, collision_tmp); 181 for (int j = 0; j < n_items; j++) { 182 if (strcmp(items[j].path, p) == 0) { 183 free(items[j].path); 184 items[j].path = strdup(collision_tmp); 185 } 186 } 187 } 188 } 189 190 if (rename(old, p) != 0) { 191 fprintf(stderr, "%s -> %s: ", old, newname); 192 perror(""); 193 error_flag = 1; 194 } else { 195 if (verbose) 196 printf("renamed '%s' -> '%s'\n", old, p); 197 free(items[num - 1].path); 198 items[num - 1].path = strdup(p); 199 } 200 } 201 } 202 fclose(tmp); 203 204 /* Delete files that were not in the edited file */ 205 for (int i = 0; i < n_items; i++) { 206 if (!seen[i]) { 207 if (rm_path(items[i].path) != 0) { 208 fprintf(stderr, "%s: ", items[i].path); 209 perror("cannot remove"); 210 error_flag = 1; 211 } else if (verbose) { 212 printf("removed '%s'\n", items[i].path); 213 } 214 } 215 free(items[i].path); 216 } 217 218 unlink(tmpname); 219 return error_flag; 220 }