/* $OpenBSD: dired.c,v 1.51 2012/03/14 13:56:35 lum Exp $ */ /* This file is in the public domain. */ /* dired module for mg 2a * by Robert A. Larson */ #include "def.h" #include "funmap.h" #include "kbd.h" #include #include #include #include #include #include #include #include #include #include #include void dired_init(void); static int dired(int, int); static int d_otherwindow(int, int); static int d_undel(int, int); static int d_undelbak(int, int); static int d_findfile(int, int); static int d_ffotherwindow(int, int); static int d_expunge(int, int); static int d_copy(int, int); static int d_del(int, int); static int d_rename(int, int); static int d_exec(int, struct buffer *, const char *, const char *, ...); static int d_shell_command(int, int); static int d_create_directory(int, int); static int d_makename(struct line *, char *, size_t); static int d_warpdot(struct line *, int *); static int d_forwpage(int, int); static int d_backpage(int, int); static int d_forwline(int, int); static int d_backline(int, int); static void reaper(int); extern struct keymap_s helpmap, cXmap, metamap; static PF dirednul[] = { setmark, /* ^@ */ gotobol, /* ^A */ backchar, /* ^B */ rescan, /* ^C */ d_del, /* ^D */ gotoeol, /* ^E */ forwchar, /* ^F */ ctrlg, /* ^G */ NULL, /* ^H */ }; static PF diredcl[] = { reposition, /* ^L */ d_findfile, /* ^M */ d_forwline, /* ^N */ rescan, /* ^O */ d_backline, /* ^P */ rescan, /* ^Q */ backisearch, /* ^R */ forwisearch, /* ^S */ rescan, /* ^T */ universal_argument, /* ^U */ d_forwpage, /* ^V */ rescan, /* ^W */ NULL /* ^X */ }; static PF diredcz[] = { spawncli, /* ^Z */ NULL, /* esc */ rescan, /* ^\ */ rescan, /* ^] */ rescan, /* ^^ */ rescan, /* ^_ */ d_forwline, /* SP */ d_shell_command, /* ! */ rescan, /* " */ rescan, /* # */ rescan, /* $ */ rescan, /* % */ rescan, /* & */ rescan, /* ' */ rescan, /* ( */ rescan, /* ) */ rescan, /* * */ d_create_directory /* + */ }; static PF diredc[] = { d_copy, /* c */ d_del, /* d */ d_findfile, /* e */ d_findfile /* f */ }; static PF diredn[] = { d_forwline, /* n */ d_ffotherwindow, /* o */ d_backline, /* p */ rescan, /* q */ d_rename, /* r */ rescan, /* s */ rescan, /* t */ d_undel, /* u */ rescan, /* v */ rescan, /* w */ d_expunge /* x */ }; static PF direddl[] = { d_undelbak /* del */ }; static PF diredbp[] = { d_backpage /* v */ }; static PF dirednull[] = { NULL }; #ifndef DIRED_XMAPS #define NDIRED_XMAPS 0 /* number of extra map sections */ #endif /* DIRED_XMAPS */ static struct KEYMAPE (1 + IMAPEXT) d_backpagemap = { 1, 1 + IMAPEXT, rescan, { { 'v', 'v', diredbp, NULL } } }; static struct KEYMAPE (7 + NDIRED_XMAPS + IMAPEXT) diredmap = { 7 + NDIRED_XMAPS, 7 + NDIRED_XMAPS + IMAPEXT, rescan, { { CCHR('@'), CCHR('H'), dirednul, (KEYMAP *) & helpmap }, { CCHR('L'), CCHR('X'), diredcl, (KEYMAP *) & cXmap }, { CCHR('['), CCHR('['), dirednull, (KEYMAP *) & d_backpagemap }, { CCHR('Z'), '+', diredcz, (KEYMAP *) & metamap }, { 'c', 'f', diredc, NULL }, { 'n', 'x', diredn, NULL }, { CCHR('?'), CCHR('?'), direddl, NULL }, #ifdef DIRED_XMAPS DIRED_XMAPS, /* map sections for dired mode keys */ #endif /* DIRED_XMAPS */ } }; void dired_init(void) { funmap_add(dired, "dired"); funmap_add(d_undelbak, "dired-backup-unflag"); funmap_add(d_copy, "dired-copy-file"); funmap_add(d_expunge, "dired-do-deletions"); funmap_add(d_findfile, "dired-find-file"); funmap_add(d_ffotherwindow, "dired-find-file-other-window"); funmap_add(d_del, "dired-flag-file-deleted"); funmap_add(d_forwline, "dired-next-line"); funmap_add(d_otherwindow, "dired-other-window"); funmap_add(d_backline, "dired-previous-line"); funmap_add(d_rename, "dired-rename-file"); funmap_add(d_backpage, "dired-scroll-down"); funmap_add(d_forwpage, "dired-scroll-up"); funmap_add(d_undel, "dired-unflag"); maps_add((KEYMAP *)&diredmap, "dired"); dobindkey(fundamental_map, "dired", "^Xd"); } /* ARGSUSED */ int dired(int f, int n) { char dname[NFILEN], *bufp, *slash; struct buffer *bp; if (curbp->b_fname && curbp->b_fname[0] != '\0') { (void)strlcpy(dname, curbp->b_fname, sizeof(dname)); if ((slash = strrchr(dname, '/')) != NULL) { *(slash + 1) = '\0'; } } else { if (getcwd(dname, sizeof(dname)) == NULL) dname[0] = '\0'; } if ((bufp = eread("Dired: ", dname, NFILEN, EFDEF | EFNEW | EFCR)) == NULL) return (ABORT); if (bufp[0] == '\0') return (FALSE); if ((bp = dired_(bufp)) == NULL) return (FALSE); curbp = bp; return (showbuffer(bp, curwp, WFFULL | WFMODE)); } /* ARGSUSED */ int d_otherwindow(int f, int n) { char dname[NFILEN], *bufp, *slash; struct buffer *bp; struct mgwin *wp; if (curbp->b_fname && curbp->b_fname[0] != '\0') { (void)strlcpy(dname, curbp->b_fname, sizeof(dname)); if ((slash = strrchr(dname, '/')) != NULL) { *(slash + 1) = '\0'; } } else { if (getcwd(dname, sizeof(dname)) == NULL) dname[0] = '\0'; } if ((bufp = eread("Dired other window: ", dname, NFILEN, EFDEF | EFNEW | EFCR)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); if ((bp = dired_(bufp)) == NULL) return (FALSE); if ((wp = popbuf(bp, WNONE)) == NULL) return (FALSE); curbp = bp; curwp = wp; return (TRUE); } /* ARGSUSED */ int d_del(int f, int n) { if (n < 0) return (FALSE); while (n--) { if (llength(curwp->w_dotp) > 0) lputc(curwp->w_dotp, 0, 'D'); if (lforw(curwp->w_dotp) != curbp->b_headp) curwp->w_dotp = lforw(curwp->w_dotp); } curwp->w_rflag |= WFEDIT | WFMOVE; curwp->w_doto = 0; return (TRUE); } /* ARGSUSED */ int d_undel(int f, int n) { if (n < 0) return (d_undelbak(f, -n)); while (n--) { if (llength(curwp->w_dotp) > 0) lputc(curwp->w_dotp, 0, ' '); if (lforw(curwp->w_dotp) != curbp->b_headp) curwp->w_dotp = lforw(curwp->w_dotp); } curwp->w_rflag |= WFEDIT | WFMOVE; curwp->w_doto = 0; return (TRUE); } /* ARGSUSED */ int d_undelbak(int f, int n) { if (n < 0) return (d_undel(f, -n)); while (n--) { if (llength(curwp->w_dotp) > 0) lputc(curwp->w_dotp, 0, ' '); if (lback(curwp->w_dotp) != curbp->b_headp) curwp->w_dotp = lback(curwp->w_dotp); } curwp->w_doto = 0; curwp->w_rflag |= WFEDIT | WFMOVE; return (TRUE); } /* ARGSUSED */ int d_findfile(int f, int n) { struct buffer *bp; int s; char fname[NFILEN]; if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT) return (FALSE); if (s == TRUE) bp = dired_(fname); else bp = findbuffer(fname); if (bp == NULL) return (FALSE); curbp = bp; if (showbuffer(bp, curwp, WFFULL) != TRUE) return (FALSE); if (bp->b_fname[0] != 0) return (TRUE); return (readin(fname)); } /* ARGSUSED */ int d_ffotherwindow(int f, int n) { char fname[NFILEN]; int s; struct buffer *bp; struct mgwin *wp; if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT) return (FALSE); if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL) return (FALSE); if ((wp = popbuf(bp, WNONE)) == NULL) return (FALSE); curbp = bp; curwp = wp; if (bp->b_fname[0] != 0) return (TRUE); /* never true for dired buffers */ return (readin(fname)); } /* ARGSUSED */ int d_expunge(int f, int n) { struct line *lp, *nlp; char fname[NFILEN], sname[NFILEN]; for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) { nlp = lforw(lp); if (llength(lp) && lgetc(lp, 0) == 'D') { switch (d_makename(lp, fname, sizeof(fname))) { case ABORT: ewprintf("Bad line in dired buffer"); return (FALSE); case FALSE: if (unlink(fname) < 0) { (void)xbasename(sname, fname, NFILEN); ewprintf("Could not delete '%s'", sname); return (FALSE); } break; case TRUE: if (rmdir(fname) < 0) { (void)xbasename(sname, fname, NFILEN); ewprintf("Could not delete directory " "'%s'", sname); return (FALSE); } break; } lfree(lp); curwp->w_bufp->b_lines--; curwp->w_rflag |= WFFULL; } } return (TRUE); } /* ARGSUSED */ int d_copy(int f, int n) { char frname[NFILEN], toname[NFILEN], sname[NFILEN], *bufp; int ret; size_t off; struct buffer *bp; if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { ewprintf("Not a file"); return (FALSE); } off = strlcpy(toname, curbp->b_fname, sizeof(toname)); if (off >= sizeof(toname) - 1) { /* can't happen, really */ ewprintf("Directory name too long"); return (FALSE); } (void)xbasename(sname, frname, NFILEN); bufp = eread("Copy %s to: ", toname, sizeof(toname), EFDEF | EFNEW | EFCR, sname); if (bufp == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); ret = (copy(frname, toname) >= 0) ? TRUE : FALSE; if (ret != TRUE) return (ret); bp = dired_(curbp->b_fname); return (showbuffer(bp, curwp, WFFULL | WFMODE)); } /* ARGSUSED */ int d_rename(int f, int n) { char frname[NFILEN], toname[NFILEN], *bufp; int ret; size_t off; struct buffer *bp; char sname[NFILEN]; if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) { ewprintf("Not a file"); return (FALSE); } off = strlcpy(toname, curbp->b_fname, sizeof(toname)); if (off >= sizeof(toname) - 1) { /* can't happen, really */ ewprintf("Directory name too long"); return (FALSE); } (void)xbasename(sname, frname, NFILEN); bufp = eread("Rename %s to: ", toname, sizeof(toname), EFDEF | EFNEW | EFCR, sname); if (bufp == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); ret = (rename(frname, toname) >= 0) ? TRUE : FALSE; if (ret != TRUE) return (ret); bp = dired_(curbp->b_fname); return (showbuffer(bp, curwp, WFFULL | WFMODE)); } /* ARGSUSED */ void reaper(int signo __attribute__((unused))) { int save_errno = errno, status; while (waitpid(-1, &status, WNOHANG) >= 0) ; errno = save_errno; } /* * Pipe the currently selected file through a shell command. */ /* ARGSUSED */ int d_shell_command(int f, int n) { char command[512], fname[MAXPATHLEN], *bufp; struct buffer *bp; struct mgwin *wp; char sname[NFILEN]; bp = bfind("*Shell Command Output*", TRUE); if (bclear(bp) != TRUE) return (ABORT); if (d_makename(curwp->w_dotp, fname, sizeof(fname)) != FALSE) { ewprintf("bad line"); return (ABORT); } command[0] = '\0'; (void)xbasename(sname, fname, NFILEN); bufp = eread("! on %s: ", command, sizeof(command), EFNEW, sname); if (bufp == NULL) return (ABORT); if (d_exec(0, bp, fname, "sh", "-c", command, NULL) != TRUE) return (ABORT); if ((wp = popbuf(bp, WNONE)) == NULL) return (ABORT); /* XXX - free the buffer?? */ curwp = wp; curbp = wp->w_bufp; return (TRUE); } /* * Pipe input file to cmd and insert the command's output in the * given buffer. Each line will be prefixed with the given * number of spaces. */ static int d_exec(int space, struct buffer *bp, const char *input, const char *cmd, ...) { char buf[BUFSIZ]; va_list ap; struct sigaction olda, newa; char **argv = NULL, *cp; FILE *fin; int fds[2] = { -1, -1 }; int infd = -1; int ret = (ABORT), n; pid_t pid; if (sigaction(SIGCHLD, NULL, &olda) == -1) return (ABORT); /* Find the number of arguments. */ va_start(ap, cmd); for (n = 2; va_arg(ap, char *) != NULL; n++) ; va_end(ap); /* Allocate and build the argv. */ if ((argv = calloc(n, sizeof(*argv))) == NULL) { ewprintf("Can't allocate argv : %s", strerror(errno)); goto out; } n = 1; argv[0] = (char *)cmd; va_start(ap, cmd); while ((argv[n] = va_arg(ap, char *)) != NULL) n++; va_end(ap); if (input == NULL) input = "/dev/null"; if ((infd = open(input, O_RDONLY)) == -1) { ewprintf("Can't open input file : %s", strerror(errno)); goto out; } if (pipe(fds) == -1) { ewprintf("Can't create pipe : %s", strerror(errno)); goto out; } newa.sa_handler = reaper; newa.sa_flags = 0; if (sigaction(SIGCHLD, &newa, NULL) == -1) goto out; if ((pid = fork()) == -1) { ewprintf("Can't fork"); goto out; } switch (pid) { case 0: /* Child */ close(fds[0]); dup2(infd, STDIN_FILENO); dup2(fds[1], STDOUT_FILENO); dup2(fds[1], STDERR_FILENO); if (execvp(argv[0], argv) == -1) ewprintf("Can't exec %s: %s", argv[0], strerror(errno)); exit(1); break; default: /* Parent */ close(infd); close(fds[1]); infd = fds[1] = -1; if ((fin = fdopen(fds[0], "r")) == NULL) goto out; while (fgets(buf, sizeof(buf), fin) != NULL) { cp = strrchr(buf, '\n'); if (cp == NULL && !feof(fin)) { /* too long a line */ int c; addlinef(bp, "%*s%s...", space, "", buf); while ((c = getc(fin)) != EOF && c != '\n') ; continue; } else if (cp) *cp = '\0'; addlinef(bp, "%*s%s", space, "", buf); } fclose(fin); break; } ret = (TRUE); out: if (sigaction(SIGCHLD, &olda, NULL) == -1) ewprintf("Warning, couldn't reset previous signal handler"); if (fds[0] != -1) close(fds[0]); if (fds[1] != -1) close(fds[1]); if (infd != -1) close(infd); if (argv != NULL) free(argv); return ret; } /* ARGSUSED */ int d_create_directory(int f, int n) { char tocreate[MAXPATHLEN], *bufp; size_t off; struct buffer *bp; off = strlcpy(tocreate, curbp->b_fname, sizeof(tocreate)); if (off >= sizeof(tocreate) - 1) return (FALSE); if ((bufp = eread("Create directory: ", tocreate, sizeof(tocreate), EFDEF | EFNEW | EFCR)) == NULL) return (ABORT); else if (bufp[0] == '\0') return (FALSE); if (mkdir(tocreate, 0755) == -1) { ewprintf("Creating directory: %s, %s", strerror(errno), tocreate); return (FALSE); } bp = dired_(curbp->b_fname); return (showbuffer(bp, curwp, WFFULL | WFMODE)); } static int d_makename(struct line *lp, char *fn, size_t len) { int start, nlen; char *namep; if (d_warpdot(lp, &start) == FALSE) return (ABORT); namep = &lp->l_text[start]; nlen = llength(lp) - start; if (snprintf(fn, len, "%s%.*s", curbp->b_fname, nlen, namep) >= len) return (ABORT); /* Name is too long. */ /* Return TRUE if the entry is a directory. */ return ((lgetc(lp, 2) == 'd') ? TRUE : FALSE); } #define NAME_FIELD 9 static int d_warpdot(struct line *dotp, int *doto) { char *tp = dotp->l_text; int off = 0, field = 0, len; /* * Find the byte offset to the (space-delimited) filename * field in formatted ls output. */ len = llength(dotp); while (off < len) { if (tp[off++] == ' ') { if (++field == NAME_FIELD) { *doto = off; return (TRUE); } /* Skip the space. */ while (off < len && tp[off] == ' ') off++; } } /* We didn't find the field. */ *doto = 0; return (FALSE); } static int d_forwpage(int f, int n) { forwpage(f | FFRAND, n); return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); } static int d_backpage (int f, int n) { backpage(f | FFRAND, n); return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); } static int d_forwline (int f, int n) { forwline(f | FFRAND, n); return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); } static int d_backline (int f, int n) { backline(f | FFRAND, n); return (d_warpdot(curwp->w_dotp, &curwp->w_doto)); } /* * XXX dname needs to have enough place to store an additional '/'. */ struct buffer * dired_(char *dname) { struct buffer *bp; int len, i; if ((fopen(dname,"r")) == NULL) { if (errno == EACCES) ewprintf("Permission denied"); return (NULL); } if ((dname = adjustname(dname, FALSE)) == NULL) { ewprintf("Bad directory name"); return (NULL); } /* this should not be done, instead adjustname() should get a flag */ len = strlen(dname); if (dname[len - 1] != '/') { dname[len++] = '/'; dname[len] = '\0'; } if ((bp = findbuffer(dname)) == NULL) { ewprintf("Could not create buffer"); return (NULL); } if (bclear(bp) != TRUE) return (NULL); bp->b_flag |= BFREADONLY; if ((d_exec(2, bp, NULL, "ls", "-al", dname, NULL)) != TRUE) return (NULL); /* Find the line with ".." on it. */ bp->b_dotp = bfirstlp(bp); for (i = 0; i < bp->b_lines; i++) { bp->b_dotp = lforw(bp->b_dotp); if (d_warpdot(bp->b_dotp, &bp->b_doto) == FALSE) continue; if (strcmp(ltext(bp->b_dotp) + bp->b_doto, "..") == 0) break; } /* We want dot on the entry right after "..", if possible. */ if (++i < bp->b_lines - 2) bp->b_dotp = lforw(bp->b_dotp); d_warpdot(bp->b_dotp, &bp->b_doto); (void)strlcpy(bp->b_fname, dname, sizeof(bp->b_fname)); (void)strlcpy(bp->b_cwd, dname, sizeof(bp->b_cwd)); if ((bp->b_modes[1] = name_mode("dired")) == NULL) { bp->b_modes[0] = name_mode("fundamental"); ewprintf("Could not find mode dired"); return (NULL); } bp->b_nmodes = 1; return (bp); }