206 lines
4.9 KiB
C
206 lines
4.9 KiB
C
// vidir.c - rewrite of Joey Hess's vidir in C
|
|
// usage: ./vidir [--verbose] [dir|file|-]
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#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[]) {
|
|
#ifdef __OpenBSD__
|
|
if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1) {
|
|
perror("pledge");
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|