utils

tiny programs I use on my system
Download | Log | Files | Refs | README | LICENSE

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 }