xref: /openbsd-src/usr.bin/mg/dired.c (revision 7bbe964f6b7d22ad07ca46292495604f942eba4e)
1 /*	$OpenBSD: dired.c,v 1.45 2009/06/04 23:39:37 kjell Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /* dired module for mg 2a
6  * by Robert A. Larson
7  */
8 
9 #include "def.h"
10 #include "funmap.h"
11 #include "kbd.h"
12 
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <sys/time.h>
16 #include <sys/resource.h>
17 #include <sys/wait.h>
18 
19 #include <ctype.h>
20 #include <signal.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <libgen.h>
24 
25 void		 dired_init(void);
26 static int	 dired(int, int);
27 static int	 d_otherwindow(int, int);
28 static int	 d_undel(int, int);
29 static int	 d_undelbak(int, int);
30 static int	 d_findfile(int, int);
31 static int	 d_ffotherwindow(int, int);
32 static int	 d_expunge(int, int);
33 static int	 d_copy(int, int);
34 static int	 d_del(int, int);
35 static int	 d_rename(int, int);
36 static int	 d_shell_command(int, int);
37 static int	 d_create_directory(int, int);
38 static int	 d_makename(struct line *, char *, size_t);
39 
40 extern struct keymap_s helpmap, cXmap, metamap;
41 
42 static PF dirednul[] = {
43 	setmark,		/* ^@ */
44 	gotobol,		/* ^A */
45 	backchar,		/* ^B */
46 	rescan,			/* ^C */
47 	d_del,			/* ^D */
48 	gotoeol,		/* ^E */
49 	forwchar,		/* ^F */
50 	ctrlg,			/* ^G */
51 #ifndef NO_HELP
52 	NULL,			/* ^H */
53 #endif /* !NO_HELP */
54 };
55 
56 static PF diredcl[] = {
57 	reposition,		/* ^L */
58 	d_findfile,		/* ^M */
59 	forwline,		/* ^N */
60 	rescan,			/* ^O */
61 	backline,		/* ^P */
62 	rescan,			/* ^Q */
63 	backisearch,		/* ^R */
64 	forwisearch,		/* ^S */
65 	rescan,			/* ^T */
66 	universal_argument,	/* ^U */
67 	forwpage,		/* ^V */
68 	rescan,			/* ^W */
69 	NULL			/* ^X */
70 };
71 
72 static PF diredcz[] = {
73 	spawncli,		/* ^Z */
74 	NULL,			/* esc */
75 	rescan,			/* ^\ */
76 	rescan,			/* ^] */
77 	rescan,			/* ^^ */
78 	rescan,			/* ^_ */
79 	forwline,		/* SP */
80 	d_shell_command,	/* ! */
81 	rescan,			/* " */
82 	rescan,			/* # */
83 	rescan,			/* $ */
84 	rescan,			/* % */
85 	rescan,			/* & */
86 	rescan,			/* ' */
87 	rescan,			/* ( */
88 	rescan,			/* ) */
89 	rescan,			/* * */
90 	d_create_directory	/* + */
91 };
92 
93 static PF diredc[] = {
94 	d_copy,			/* c */
95 	d_del,			/* d */
96 	d_findfile,		/* e */
97 	d_findfile		/* f */
98 };
99 
100 static PF diredn[] = {
101 	forwline,		/* n */
102 	d_ffotherwindow,	/* o */
103 	backline,		/* p */
104 	rescan,			/* q */
105 	d_rename,		/* r */
106 	rescan,			/* s */
107 	rescan,			/* t */
108 	d_undel,		/* u */
109 	rescan,			/* v */
110 	rescan,			/* w */
111 	d_expunge		/* x */
112 };
113 
114 static PF direddl[] = {
115 	d_undelbak		/* del */
116 };
117 
118 #ifndef	DIRED_XMAPS
119 #define	NDIRED_XMAPS	0	/* number of extra map sections */
120 #endif /* DIRED_XMAPS */
121 
122 static struct KEYMAPE (6 + NDIRED_XMAPS + IMAPEXT) diredmap = {
123 	6 + NDIRED_XMAPS,
124 	6 + NDIRED_XMAPS + IMAPEXT,
125 	rescan,
126 	{
127 #ifndef NO_HELP
128 		{
129 			CCHR('@'), CCHR('H'), dirednul, (KEYMAP *) & helpmap
130 		},
131 #else /* !NO_HELP */
132 		{
133 			CCHR('@'), CCHR('G'), dirednul, NULL
134 		},
135 #endif /* !NO_HELP */
136 		{
137 			CCHR('L'), CCHR('X'), diredcl, (KEYMAP *) & cXmap
138 		},
139 		{
140 			CCHR('Z'), '+', diredcz, (KEYMAP *) & metamap
141 		},
142 		{
143 			'c', 'f', diredc, NULL
144 		},
145 		{
146 			'n', 'x', diredn, NULL
147 		},
148 		{
149 			CCHR('?'), CCHR('?'), direddl, NULL
150 		},
151 #ifdef	DIRED_XMAPS
152 		DIRED_XMAPS,	/* map sections for dired mode keys	 */
153 #endif /* DIRED_XMAPS */
154 	}
155 };
156 
157 void
158 dired_init(void)
159 {
160 	funmap_add(dired, "dired");
161 	funmap_add(d_undelbak, "dired-backup-unflag");
162 	funmap_add(d_copy, "dired-copy-file");
163 	funmap_add(d_expunge, "dired-do-deletions");
164 	funmap_add(d_findfile, "dired-find-file");
165 	funmap_add(d_ffotherwindow, "dired-find-file-other-window");
166 	funmap_add(d_del, "dired-flag-file-deleted");
167 	funmap_add(d_otherwindow, "dired-other-window");
168 	funmap_add(d_rename, "dired-rename-file");
169 	funmap_add(d_undel, "dired-unflag");
170 	maps_add((KEYMAP *)&diredmap, "dired");
171 	dobindkey(fundamental_map, "dired", "^Xd");
172 }
173 
174 /* ARGSUSED */
175 int
176 dired(int f, int n)
177 {
178 	char		 dname[NFILEN], *bufp, *slash;
179 	struct buffer	*bp;
180 
181 	if (curbp->b_fname && curbp->b_fname[0] != '\0') {
182 		(void)strlcpy(dname, curbp->b_fname, sizeof(dname));
183 		if ((slash = strrchr(dname, '/')) != NULL) {
184 			*(slash + 1) = '\0';
185 		}
186 	} else {
187 		if (getcwd(dname, sizeof(dname)) == NULL)
188 			dname[0] = '\0';
189 	}
190 
191 	if ((bufp = eread("Dired: ", dname, NFILEN,
192 	    EFDEF | EFNEW | EFCR)) == NULL)
193 		return (ABORT);
194 	if (bufp[0] == '\0')
195 		return (FALSE);
196 	if ((bp = dired_(bufp)) == NULL)
197 		return (FALSE);
198 
199 	curbp = bp;
200 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
201 }
202 
203 /* ARGSUSED */
204 int
205 d_otherwindow(int f, int n)
206 {
207 	char		 dname[NFILEN], *bufp, *slash;
208 	struct buffer	*bp;
209 	struct mgwin	*wp;
210 
211 	if (curbp->b_fname && curbp->b_fname[0] != '\0') {
212 		(void)strlcpy(dname, curbp->b_fname, sizeof(dname));
213 		if ((slash = strrchr(dname, '/')) != NULL) {
214 			*(slash + 1) = '\0';
215 		}
216 	} else {
217 		if (getcwd(dname, sizeof(dname)) == NULL)
218 			dname[0] = '\0';
219 	}
220 
221 	if ((bufp = eread("Dired other window: ", dname, NFILEN,
222 	    EFDEF | EFNEW | EFCR)) == NULL)
223 		return (ABORT);
224 	else if (bufp[0] == '\0')
225 		return (FALSE);
226 	if ((bp = dired_(bufp)) == NULL)
227 		return (FALSE);
228 	if ((wp = popbuf(bp, WNONE)) == NULL)
229 		return (FALSE);
230 	curbp = bp;
231 	curwp = wp;
232 	return (TRUE);
233 }
234 
235 /* ARGSUSED */
236 int
237 d_del(int f, int n)
238 {
239 	if (n < 0)
240 		return (FALSE);
241 	while (n--) {
242 		if (llength(curwp->w_dotp) > 0)
243 			lputc(curwp->w_dotp, 0, 'D');
244 		if (lforw(curwp->w_dotp) != curbp->b_headp)
245 			curwp->w_dotp = lforw(curwp->w_dotp);
246 	}
247 	curwp->w_rflag |= WFEDIT | WFMOVE;
248 	curwp->w_doto = 0;
249 	return (TRUE);
250 }
251 
252 /* ARGSUSED */
253 int
254 d_undel(int f, int n)
255 {
256 	if (n < 0)
257 		return (d_undelbak(f, -n));
258 	while (n--) {
259 		if (llength(curwp->w_dotp) > 0)
260 			lputc(curwp->w_dotp, 0, ' ');
261 		if (lforw(curwp->w_dotp) != curbp->b_headp)
262 			curwp->w_dotp = lforw(curwp->w_dotp);
263 	}
264 	curwp->w_rflag |= WFEDIT | WFMOVE;
265 	curwp->w_doto = 0;
266 	return (TRUE);
267 }
268 
269 /* ARGSUSED */
270 int
271 d_undelbak(int f, int n)
272 {
273 	if (n < 0)
274 		return (d_undel(f, -n));
275 	while (n--) {
276 		if (llength(curwp->w_dotp) > 0)
277 			lputc(curwp->w_dotp, 0, ' ');
278 		if (lback(curwp->w_dotp) != curbp->b_headp)
279 			curwp->w_dotp = lback(curwp->w_dotp);
280 	}
281 	curwp->w_doto = 0;
282 	curwp->w_rflag |= WFEDIT | WFMOVE;
283 	return (TRUE);
284 }
285 
286 /* ARGSUSED */
287 int
288 d_findfile(int f, int n)
289 {
290 	struct buffer	*bp;
291 	int		 s;
292 	char		 fname[NFILEN];
293 
294 	if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
295 		return (FALSE);
296 	if (s == TRUE)
297 		bp = dired_(fname);
298 	else
299 		bp = findbuffer(fname);
300 	if (bp == NULL)
301 		return (FALSE);
302 	curbp = bp;
303 	if (showbuffer(bp, curwp, WFFULL) != TRUE)
304 		return (FALSE);
305 	if (bp->b_fname[0] != 0)
306 		return (TRUE);
307 	return (readin(fname));
308 }
309 
310 /* ARGSUSED */
311 int
312 d_ffotherwindow(int f, int n)
313 {
314 	char		 fname[NFILEN];
315 	int		 s;
316 	struct buffer	*bp;
317 	struct mgwin	*wp;
318 
319 	if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
320 		return (FALSE);
321 	if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL)
322 		return (FALSE);
323 	if ((wp = popbuf(bp, WNONE)) == NULL)
324 		return (FALSE);
325 	curbp = bp;
326 	curwp = wp;
327 	if (bp->b_fname[0] != 0)
328 		return (TRUE);	/* never true for dired buffers */
329 	return (readin(fname));
330 }
331 
332 /* ARGSUSED */
333 int
334 d_expunge(int f, int n)
335 {
336 	struct line	*lp, *nlp;
337 	char		 fname[NFILEN];
338 
339 	for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) {
340 		nlp = lforw(lp);
341 		if (llength(lp) && lgetc(lp, 0) == 'D') {
342 			switch (d_makename(lp, fname, sizeof(fname))) {
343 			case ABORT:
344 				ewprintf("Bad line in dired buffer");
345 				return (FALSE);
346 			case FALSE:
347 				if (unlink(fname) < 0) {
348 					ewprintf("Could not delete '%s'",
349 					    basename(fname));
350 					return (FALSE);
351 				}
352 				break;
353 			case TRUE:
354 				if (rmdir(fname) < 0) {
355 					ewprintf("Could not delete directory '%s'",
356 					    basename(fname));
357 					return (FALSE);
358 				}
359 				break;
360 			}
361 			lfree(lp);
362 			curwp->w_bufp->b_lines--;
363 			curwp->w_rflag |= WFFULL;
364 		}
365 	}
366 	return (TRUE);
367 }
368 
369 /* ARGSUSED */
370 int
371 d_copy(int f, int n)
372 {
373 	char	frname[NFILEN], toname[NFILEN], *bufp;
374 	int	ret;
375 	size_t	off;
376 	struct buffer *bp;
377 
378 	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
379 		ewprintf("Not a file");
380 		return (FALSE);
381 	}
382 	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
383 	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
384 		ewprintf("Directory name too long");
385 		return (FALSE);
386 	}
387 	if ((bufp = eread("Copy %s to: ", toname, sizeof(toname),
388 	    EFDEF | EFNEW | EFCR, basename(frname))) == NULL)
389 		return (ABORT);
390 	else if (bufp[0] == '\0')
391 		return (FALSE);
392 	ret = (copy(frname, toname) >= 0) ? TRUE : FALSE;
393 	if (ret != TRUE)
394 		return (ret);
395 	bp = dired_(curbp->b_fname);
396 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
397 }
398 
399 /* ARGSUSED */
400 int
401 d_rename(int f, int n)
402 {
403 	char		 frname[NFILEN], toname[NFILEN], *bufp;
404 	int		 ret;
405 	size_t		 off;
406 	struct buffer	*bp;
407 
408 	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
409 		ewprintf("Not a file");
410 		return (FALSE);
411 	}
412 	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
413 	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
414 		ewprintf("Directory name too long");
415 		return (FALSE);
416 	}
417 	if ((bufp = eread("Rename %s to: ", toname,
418 	    sizeof(toname), EFDEF | EFNEW | EFCR, basename(frname))) == NULL)
419 		return (ABORT);
420 	else if (bufp[0] == '\0')
421 		return (FALSE);
422 	ret = (rename(frname, toname) >= 0) ? TRUE : FALSE;
423 	if (ret != TRUE)
424 		return (ret);
425 	bp = dired_(curbp->b_fname);
426 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
427 }
428 
429 /* ARGSUSED */
430 void
431 reaper(int signo __attribute__((unused)))
432 {
433 	int	save_errno = errno, status;
434 
435 	while (waitpid(-1, &status, WNOHANG) >= 0)
436 		;
437 	errno = save_errno;
438 }
439 
440 /*
441  * Pipe the currently selected file through a shell command.
442  */
443 /* ARGSUSED */
444 int
445 d_shell_command(int f, int n)
446 {
447 	char	 command[512], fname[MAXPATHLEN], buf[BUFSIZ], *bufp, *cp;
448 	int	 infd, fds[2];
449 	pid_t	 pid;
450 	struct	 sigaction olda, newa;
451 	struct buffer	*bp;
452 	struct mgwin	*wp;
453 	FILE	*fin;
454 
455 	bp = bfind("*Shell Command Output*", TRUE);
456 	if (bclear(bp) != TRUE)
457 		return (ABORT);
458 
459 	if (d_makename(curwp->w_dotp, fname, sizeof(fname)) != FALSE) {
460 		ewprintf("bad line");
461 		return (ABORT);
462 	}
463 
464 	command[0] = '\0';
465 	if ((bufp = eread("! on %s: ", command, sizeof(command), EFNEW,
466 	    basename(fname))) == NULL)
467 		return (ABORT);
468 	infd = open(fname, O_RDONLY);
469 	if (infd == -1) {
470 		ewprintf("Can't open input file : %s", strerror(errno));
471 		return (FALSE);
472 	}
473 	if (pipe(fds) == -1) {
474 		ewprintf("Can't create pipe : %s", strerror(errno));
475 		close(infd);
476 		return (FALSE);
477 	}
478 
479 	newa.sa_handler = reaper;
480 	newa.sa_flags = 0;
481 	if (sigaction(SIGCHLD, &newa, &olda) == -1) {
482 		close(infd);
483 		close(fds[0]);
484 		close(fds[1]);
485 		return (ABORT);
486 	}
487 	pid = fork();
488 	switch (pid) {
489 	case -1:
490 		ewprintf("Can't fork");
491 		return (ABORT);
492 	case 0:
493 		close(fds[0]);
494 		dup2(infd, STDIN_FILENO);
495 		dup2(fds[1], STDOUT_FILENO);
496 		dup2(fds[1], STDERR_FILENO);
497 		execl("/bin/sh", "sh", "-c", bufp, (char *)NULL);
498 		exit(1);
499 		break;
500 	default:
501 		close(infd);
502 		close(fds[1]);
503 		fin = fdopen(fds[0], "r");
504 		if (fin == NULL)	/* "r" is surely a valid mode! */
505 			panic("can't happen");
506 		while (fgets(buf, sizeof(buf), fin) != NULL) {
507 			cp = strrchr(buf, '\n');
508 			if (cp == NULL && !feof(fin)) {	/* too long a line */
509 				int c;
510 				addlinef(bp, "%s...", buf);
511 				while ((c = getc(fin)) != EOF && c != '\n')
512 					;
513 				continue;
514 			} else if (cp)
515 				*cp = '\0';
516 			addline(bp, buf);
517 		}
518 		fclose(fin);
519 		close(fds[0]);
520 		break;
521 	}
522 	wp = popbuf(bp, WNONE);
523 	if (wp == NULL)
524 		return (ABORT);	/* XXX - free the buffer?? */
525 	curwp = wp;
526 	curbp = wp->w_bufp;
527 	if (sigaction(SIGCHLD, &olda, NULL) == -1)
528 		ewprintf("Warning, couldn't reset previous signal handler");
529 	return (TRUE);
530 }
531 
532 /* ARGSUSED */
533 int
534 d_create_directory(int f, int n)
535 {
536 	char	 tocreate[MAXPATHLEN], *bufp;
537 	size_t  off;
538 	struct buffer	*bp;
539 
540 	off = strlcpy(tocreate, curbp->b_fname, sizeof(tocreate));
541 	if (off >= sizeof(tocreate) - 1)
542 		return (FALSE);
543 	if ((bufp = eread("Create directory: ", tocreate,
544 	    sizeof(tocreate), EFDEF | EFNEW | EFCR)) == NULL)
545 		return (ABORT);
546 	else if (bufp[0] == '\0')
547 		return (FALSE);
548 	if (mkdir(tocreate, 0755) == -1) {
549 		ewprintf("Creating directory: %s, %s", strerror(errno),
550 		    tocreate);
551 		return (FALSE);
552 	}
553 	bp = dired_(curbp->b_fname);
554 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
555 }
556 
557 #define NAME_FIELD	8
558 
559 static int
560 d_makename(struct line *lp, char *fn, size_t len)
561 {
562 	int	 i;
563 	char	*p, *ep;
564 
565 	if (strlcpy(fn, curbp->b_fname, len) >= len)
566 		return (FALSE);
567 	if ((p = lp->l_text) == NULL)
568 		return (ABORT);
569 	ep = lp->l_text + llength(lp);
570 	p++; /* skip action letter, if any */
571 	for (i = 0; i < NAME_FIELD; i++) {
572 		while (p < ep && isspace(*p))
573 			p++;
574 		while (p < ep && !isspace(*p))
575 			p++;
576 		while (p < ep && isspace(*p))
577 			p++;
578 		if (p == ep)
579 			return (ABORT);
580 	}
581 	if (strlcat(fn, p, len) >= len)
582 		return (FALSE);
583 	return ((lgetc(lp, 2) == 'd') ? TRUE : FALSE);
584 }
585 
586 /*
587  * XXX dname needs to have enough place to store an additional '/'.
588  */
589 struct buffer *
590 dired_(char *dname)
591 {
592 	struct buffer	*bp;
593 	FILE	*dirpipe;
594 	char	 line[256];
595 	int	 len, ret;
596 
597 	if ((dname = adjustname(dname, FALSE)) == NULL) {
598 		ewprintf("Bad directory name");
599 		return (NULL);
600 	}
601 	/* this should not be done, instead adjustname() should get a flag */
602 	len = strlen(dname);
603 	if (dname[len - 1] != '/') {
604 		dname[len++] = '/';
605 		dname[len] = '\0';
606 	}
607 	if ((bp = findbuffer(dname)) == NULL) {
608 		ewprintf("Could not create buffer");
609 		return (NULL);
610 	}
611 	if (bclear(bp) != TRUE)
612 		return (NULL);
613 	bp->b_flag |= BFREADONLY;
614 	ret = snprintf(line, sizeof(line), "ls -al %s", dname);
615 	if (ret < 0 || ret  >= sizeof(line)) {
616 		ewprintf("Path too long");
617 		return (NULL);
618 	}
619 	if ((dirpipe = popen(line, "r")) == NULL) {
620 		ewprintf("Problem opening pipe to ls");
621 		return (NULL);
622 	}
623 	line[0] = line[1] = ' ';
624 	while (fgets(&line[2], sizeof(line) - 2, dirpipe) != NULL) {
625 		line[strcspn(line, "\n")] = '\0'; /* remove ^J	 */
626 		(void) addline(bp, line);
627 	}
628 	if (pclose(dirpipe) == -1) {
629 		ewprintf("Problem closing pipe to ls : %s",
630 		    strerror(errno));
631 		return (NULL);
632 	}
633 	bp->b_dotp = bfirstlp(bp);
634 	(void)strlcpy(bp->b_fname, dname, sizeof(bp->b_fname));
635 	(void)strlcpy(bp->b_cwd, dname, sizeof(bp->b_cwd));
636 	if ((bp->b_modes[1] = name_mode("dired")) == NULL) {
637 		bp->b_modes[0] = name_mode("fundamental");
638 		ewprintf("Could not find mode dired");
639 		return (NULL);
640 	}
641 	bp->b_nmodes = 1;
642 	return (bp);
643 }
644