xref: /openbsd-src/usr.bin/mg/dired.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: dired.c,v 1.67 2014/04/03 20:17:12 lum 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 <err.h>
23 #include <errno.h>
24 #include <libgen.h>
25 #include <stdarg.h>
26 
27 void		 dired_init(void);
28 static int	 dired(int, int);
29 static int	 d_otherwindow(int, int);
30 static int	 d_undel(int, int);
31 static int	 d_undelbak(int, int);
32 static int	 d_findfile(int, int);
33 static int	 d_ffotherwindow(int, int);
34 static int	 d_expunge(int, int);
35 static int	 d_copy(int, int);
36 static int	 d_del(int, int);
37 static int	 d_rename(int, int);
38 static int	 d_exec(int, struct buffer *, const char *, const char *, ...);
39 static int	 d_shell_command(int, int);
40 static int	 d_create_directory(int, int);
41 static int	 d_makename(struct line *, char *, size_t);
42 static int	 d_warpdot(struct line *, int *);
43 static int	 d_forwpage(int, int);
44 static int	 d_backpage(int, int);
45 static int	 d_forwline(int, int);
46 static int	 d_backline(int, int);
47 static int	 d_killbuffer_cmd(int, int);
48 static int	 d_refreshbuffer(int, int);
49 static void	 reaper(int);
50 static struct buffer	*refreshbuffer(struct buffer *);
51 
52 extern struct keymap_s helpmap, cXmap, metamap;
53 
54 static PF dirednul[] = {
55 	setmark,		/* ^@ */
56 	gotobol,		/* ^A */
57 	backchar,		/* ^B */
58 	rescan,			/* ^C */
59 	d_del,			/* ^D */
60 	gotoeol,		/* ^E */
61 	forwchar,		/* ^F */
62 	ctrlg,			/* ^G */
63 	NULL,			/* ^H */
64 };
65 
66 static PF diredcl[] = {
67 	reposition,		/* ^L */
68 	d_findfile,		/* ^M */
69 	d_forwline,		/* ^N */
70 	rescan,			/* ^O */
71 	d_backline,		/* ^P */
72 	rescan,			/* ^Q */
73 	backisearch,		/* ^R */
74 	forwisearch,		/* ^S */
75 	rescan,			/* ^T */
76 	universal_argument,	/* ^U */
77 	d_forwpage,		/* ^V */
78 	rescan,			/* ^W */
79 	NULL			/* ^X */
80 };
81 
82 static PF diredcz[] = {
83 	spawncli,		/* ^Z */
84 	NULL,			/* esc */
85 	rescan,			/* ^\ */
86 	rescan,			/* ^] */
87 	rescan,			/* ^^ */
88 	rescan,			/* ^_ */
89 	d_forwline,		/* SP */
90 	d_shell_command,	/* ! */
91 	rescan,			/* " */
92 	rescan,			/* # */
93 	rescan,			/* $ */
94 	rescan,			/* % */
95 	rescan,			/* & */
96 	rescan,			/* ' */
97 	rescan,			/* ( */
98 	rescan,			/* ) */
99 	rescan,			/* * */
100 	d_create_directory	/* + */
101 };
102 
103 static PF diredc[] = {
104 	d_copy,			/* c */
105 	d_del,			/* d */
106 	d_findfile,		/* e */
107 	d_findfile,		/* f */
108 	d_refreshbuffer		/* g */
109 };
110 
111 static PF diredn[] = {
112 	d_forwline,		/* n */
113 	d_ffotherwindow,	/* o */
114 	d_backline,		/* p */
115 	d_killbuffer_cmd,	/* q */
116 	d_rename,		/* r */
117 	rescan,			/* s */
118 	rescan,			/* t */
119 	d_undel,		/* u */
120 	rescan,			/* v */
121 	rescan,			/* w */
122 	d_expunge		/* x */
123 };
124 
125 static PF direddl[] = {
126 	d_undelbak		/* del */
127 };
128 
129 static PF diredbp[] = {
130 	d_backpage		/* v */
131 };
132 
133 static PF dirednull[] = {
134 	NULL
135 };
136 
137 #ifndef	DIRED_XMAPS
138 #define	NDIRED_XMAPS	0	/* number of extra map sections */
139 #endif /* DIRED_XMAPS */
140 
141 static struct KEYMAPE (1 + IMAPEXT) d_backpagemap = {
142 	1,
143 	1 + IMAPEXT,
144 	rescan,
145 	{
146 		{
147 		'v', 'v', diredbp, NULL
148 		}
149 	}
150 };
151 
152 static struct KEYMAPE (7 + NDIRED_XMAPS + IMAPEXT) diredmap = {
153 	7 + NDIRED_XMAPS,
154 	7 + NDIRED_XMAPS + IMAPEXT,
155 	rescan,
156 	{
157 		{
158 			CCHR('@'), CCHR('H'), dirednul, (KEYMAP *) & helpmap
159 		},
160 		{
161 			CCHR('L'), CCHR('X'), diredcl, (KEYMAP *) & cXmap
162 		},
163 		{
164 			CCHR('['), CCHR('['), dirednull, (KEYMAP *) &
165 			d_backpagemap
166 		},
167 		{
168 			CCHR('Z'), '+', diredcz, (KEYMAP *) & metamap
169 		},
170 		{
171 			'c', 'g', diredc, NULL
172 		},
173 		{
174 			'n', 'x', diredn, NULL
175 		},
176 		{
177 			CCHR('?'), CCHR('?'), direddl, NULL
178 		},
179 #ifdef	DIRED_XMAPS
180 		DIRED_XMAPS,	/* map sections for dired mode keys	 */
181 #endif /* DIRED_XMAPS */
182 	}
183 };
184 
185 void
186 dired_init(void)
187 {
188 	funmap_add(dired, "dired");
189 	funmap_add(d_undelbak, "dired-unmark-backward");
190 	funmap_add(d_create_directory, "dired-create-directory");
191 	funmap_add(d_copy, "dired-do-copy");
192 	funmap_add(d_expunge, "dired-do-flagged-delete");
193 	funmap_add(d_findfile, "dired-find-file");
194 	funmap_add(d_ffotherwindow, "dired-find-file-other-window");
195 	funmap_add(d_del, "dired-flag-file-deletion");
196 	funmap_add(d_forwline, "dired-next-line");
197 	funmap_add(d_otherwindow, "dired-other-window");
198 	funmap_add(d_backline, "dired-previous-line");
199 	funmap_add(d_rename, "dired-do-rename");
200 	funmap_add(d_backpage, "dired-scroll-down");
201 	funmap_add(d_forwpage, "dired-scroll-up");
202 	funmap_add(d_undel, "dired-unmark");
203 	funmap_add(d_killbuffer_cmd, "quit-window");
204 	maps_add((KEYMAP *)&diredmap, "dired");
205 	dobindkey(fundamental_map, "dired", "^Xd");
206 }
207 
208 /* ARGSUSED */
209 int
210 dired(int f, int n)
211 {
212 	char		 dname[NFILEN], *bufp, *slash;
213 	struct buffer	*bp;
214 
215 	if (curbp->b_fname && curbp->b_fname[0] != '\0') {
216 		(void)strlcpy(dname, curbp->b_fname, sizeof(dname));
217 		if ((slash = strrchr(dname, '/')) != NULL) {
218 			*(slash + 1) = '\0';
219 		}
220 	} else {
221 		if (getcwd(dname, sizeof(dname)) == NULL)
222 			dname[0] = '\0';
223 	}
224 
225 	if ((bufp = eread("Dired: ", dname, NFILEN,
226 	    EFDEF | EFNEW | EFCR)) == NULL)
227 		return (ABORT);
228 	if (bufp[0] == '\0')
229 		return (FALSE);
230 	if ((bp = dired_(bufp)) == NULL)
231 		return (FALSE);
232 
233 	curbp = bp;
234 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
235 }
236 
237 /* ARGSUSED */
238 int
239 d_otherwindow(int f, int n)
240 {
241 	char		 dname[NFILEN], *bufp, *slash;
242 	struct buffer	*bp;
243 	struct mgwin	*wp;
244 
245 	if (curbp->b_fname && curbp->b_fname[0] != '\0') {
246 		(void)strlcpy(dname, curbp->b_fname, sizeof(dname));
247 		if ((slash = strrchr(dname, '/')) != NULL) {
248 			*(slash + 1) = '\0';
249 		}
250 	} else {
251 		if (getcwd(dname, sizeof(dname)) == NULL)
252 			dname[0] = '\0';
253 	}
254 
255 	if ((bufp = eread("Dired other window: ", dname, NFILEN,
256 	    EFDEF | EFNEW | EFCR)) == NULL)
257 		return (ABORT);
258 	else if (bufp[0] == '\0')
259 		return (FALSE);
260 	if ((bp = dired_(bufp)) == NULL)
261 		return (FALSE);
262 	if ((wp = popbuf(bp, WNONE)) == NULL)
263 		return (FALSE);
264 	curbp = bp;
265 	curwp = wp;
266 	return (TRUE);
267 }
268 
269 /* ARGSUSED */
270 int
271 d_del(int f, int n)
272 {
273 	if (n < 0)
274 		return (FALSE);
275 	while (n--) {
276 		if (llength(curwp->w_dotp) > 0)
277 			lputc(curwp->w_dotp, 0, 'D');
278 		if (lforw(curwp->w_dotp) != curbp->b_headp)
279 			curwp->w_dotp = lforw(curwp->w_dotp);
280 	}
281 	curwp->w_rflag |= WFEDIT | WFMOVE;
282 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
283 }
284 
285 /* ARGSUSED */
286 int
287 d_undel(int f, int n)
288 {
289 	if (n < 0)
290 		return (d_undelbak(f, -n));
291 	while (n--) {
292 		if (llength(curwp->w_dotp) > 0)
293 			lputc(curwp->w_dotp, 0, ' ');
294 		if (lforw(curwp->w_dotp) != curbp->b_headp)
295 			curwp->w_dotp = lforw(curwp->w_dotp);
296 	}
297 	curwp->w_rflag |= WFEDIT | WFMOVE;
298 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
299 }
300 
301 /* ARGSUSED */
302 int
303 d_undelbak(int f, int n)
304 {
305 	if (n < 0)
306 		return (d_undel(f, -n));
307 	while (n--) {
308 		if (lback(curwp->w_dotp) != curbp->b_headp)
309 			curwp->w_dotp = lback(curwp->w_dotp);
310 		if (llength(curwp->w_dotp) > 0)
311 			lputc(curwp->w_dotp, 0, ' ');
312 	}
313 	curwp->w_rflag |= WFEDIT | WFMOVE;
314 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
315 }
316 
317 /* ARGSUSED */
318 int
319 d_findfile(int f, int n)
320 {
321 	struct buffer	*bp;
322 	int		 s;
323 	char		 fname[NFILEN];
324 
325 	if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
326 		return (FALSE);
327 	if (s == TRUE)
328 		bp = dired_(fname);
329 	else
330 		bp = findbuffer(fname);
331 	if (bp == NULL)
332 		return (FALSE);
333 	curbp = bp;
334 	if (showbuffer(bp, curwp, WFFULL) != TRUE)
335 		return (FALSE);
336 	if (bp->b_fname[0] != 0)
337 		return (TRUE);
338 	return (readin(fname));
339 }
340 
341 /* ARGSUSED */
342 int
343 d_ffotherwindow(int f, int n)
344 {
345 	char		 fname[NFILEN];
346 	int		 s;
347 	struct buffer	*bp;
348 	struct mgwin	*wp;
349 
350 	if ((s = d_makename(curwp->w_dotp, fname, sizeof(fname))) == ABORT)
351 		return (FALSE);
352 	if ((bp = (s ? dired_(fname) : findbuffer(fname))) == NULL)
353 		return (FALSE);
354 	if ((wp = popbuf(bp, WNONE)) == NULL)
355 		return (FALSE);
356 	curbp = bp;
357 	curwp = wp;
358 	if (bp->b_fname[0] != 0)
359 		return (TRUE);	/* never true for dired buffers */
360 	return (readin(fname));
361 }
362 
363 /* ARGSUSED */
364 int
365 d_expunge(int f, int n)
366 {
367 	struct line	*lp, *nlp;
368 	char		 fname[NFILEN], sname[NFILEN];
369 
370 	for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) {
371 		nlp = lforw(lp);
372 		if (llength(lp) && lgetc(lp, 0) == 'D') {
373 			switch (d_makename(lp, fname, sizeof(fname))) {
374 			case ABORT:
375 				dobeep();
376 				ewprintf("Bad line in dired buffer");
377 				return (FALSE);
378 			case FALSE:
379 				if (unlink(fname) < 0) {
380 					(void)xbasename(sname, fname, NFILEN);
381 					dobeep();
382 					ewprintf("Could not delete '%s'", sname);
383 					return (FALSE);
384 				}
385 				break;
386 			case TRUE:
387 				if (rmdir(fname) < 0) {
388 					(void)xbasename(sname, fname, NFILEN);
389 					dobeep();
390 					ewprintf("Could not delete directory "
391 					    "'%s'", sname);
392 					return (FALSE);
393 				}
394 				break;
395 			}
396 			lfree(lp);
397 			curwp->w_bufp->b_lines--;
398 			curwp->w_rflag |= WFFULL;
399 		}
400 	}
401 	return (TRUE);
402 }
403 
404 /* ARGSUSED */
405 int
406 d_copy(int f, int n)
407 {
408 	char		 frname[NFILEN], toname[NFILEN], sname[NFILEN];
409 	char		*topath, *bufp;
410 	int		 ret;
411 	size_t		 off;
412 	struct buffer	*bp;
413 
414 	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
415 		dobeep();
416 		ewprintf("Not a file");
417 		return (FALSE);
418 	}
419 	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
420 	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
421 		dobeep();
422 		ewprintf("Directory name too long");
423 		return (FALSE);
424 	}
425 	(void)xbasename(sname, frname, NFILEN);
426 	bufp = eread("Copy %s to: ", toname, sizeof(toname),
427 	    EFDEF | EFNEW | EFCR, sname);
428 	if (bufp == NULL)
429 		return (ABORT);
430 	else if (bufp[0] == '\0')
431 		return (FALSE);
432 
433 	topath = adjustname(toname, TRUE);
434 	ret = (copy(frname, topath) >= 0) ? TRUE : FALSE;
435 	if (ret != TRUE)
436 		return (ret);
437 	if ((bp = refreshbuffer(curbp)) == NULL)
438 		return (FALSE);
439 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
440 }
441 
442 /* ARGSUSED */
443 int
444 d_rename(int f, int n)
445 {
446 	char		 frname[NFILEN], toname[NFILEN];
447 	char		*topath, *bufp;
448 	int		 ret;
449 	size_t		 off;
450 	struct buffer	*bp;
451 	char		 sname[NFILEN];
452 
453 	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
454 		dobeep();
455 		ewprintf("Not a file");
456 		return (FALSE);
457 	}
458 	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
459 	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
460 		dobeep();
461 		ewprintf("Directory name too long");
462 		return (FALSE);
463 	}
464 	(void)xbasename(sname, frname, NFILEN);
465 	bufp = eread("Rename %s to: ", toname,
466 	    sizeof(toname), EFDEF | EFNEW | EFCR, sname);
467 	if (bufp == NULL)
468 		return (ABORT);
469 	else if (bufp[0] == '\0')
470 		return (FALSE);
471 
472 	topath = adjustname(toname, TRUE);
473 	ret = (rename(frname, topath) >= 0) ? TRUE : FALSE;
474 	if (ret != TRUE)
475 		return (ret);
476 	if ((bp = refreshbuffer(curbp)) == NULL)
477 		return (FALSE);
478 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
479 }
480 
481 /* ARGSUSED */
482 void
483 reaper(int signo __attribute__((unused)))
484 {
485 	int	save_errno = errno, status;
486 
487 	while (waitpid(-1, &status, WNOHANG) >= 0)
488 		;
489 	errno = save_errno;
490 }
491 
492 /*
493  * Pipe the currently selected file through a shell command.
494  */
495 /* ARGSUSED */
496 int
497 d_shell_command(int f, int n)
498 {
499 	char		 command[512], fname[MAXPATHLEN], *bufp;
500 	struct buffer	*bp;
501 	struct mgwin	*wp;
502 	char		 sname[NFILEN];
503 
504 	bp = bfind("*Shell Command Output*", TRUE);
505 	if (bclear(bp) != TRUE)
506 		return (ABORT);
507 
508 	if (d_makename(curwp->w_dotp, fname, sizeof(fname)) != FALSE) {
509 		dobeep();
510 		ewprintf("bad line");
511 		return (ABORT);
512 	}
513 
514 	command[0] = '\0';
515 	(void)xbasename(sname, fname, NFILEN);
516 	bufp = eread("! on %s: ", command, sizeof(command), EFNEW, sname);
517 	if (bufp == NULL)
518 		return (ABORT);
519 
520 	if (d_exec(0, bp, fname, "sh", "-c", command, NULL) != TRUE)
521 		return (ABORT);
522 
523 	if ((wp = popbuf(bp, WNONE)) == NULL)
524 		return (ABORT);	/* XXX - free the buffer?? */
525 	curwp = wp;
526 	curbp = wp->w_bufp;
527 	return (TRUE);
528 }
529 
530 /*
531  * Pipe input file to cmd and insert the command's output in the
532  * given buffer.  Each line will be prefixed with the given
533  * number of spaces.
534  */
535 static int
536 d_exec(int space, struct buffer *bp, const char *input, const char *cmd, ...)
537 {
538 	char	 buf[BUFSIZ];
539 	va_list	 ap;
540 	struct	 sigaction olda, newa;
541 	char	**argv = NULL, *cp;
542 	FILE	*fin;
543 	int	 fds[2] = { -1, -1 };
544 	int	 infd = -1;
545 	int	 ret = (ABORT), n;
546 	pid_t	 pid;
547 
548 	if (sigaction(SIGCHLD, NULL, &olda) == -1)
549 		return (ABORT);
550 
551 	/* Find the number of arguments. */
552 	va_start(ap, cmd);
553 	for (n = 2; va_arg(ap, char *) != NULL; n++)
554 		;
555 	va_end(ap);
556 
557 	/* Allocate and build the argv. */
558 	if ((argv = calloc(n, sizeof(*argv))) == NULL) {
559 		dobeep();
560 		ewprintf("Can't allocate argv : %s", strerror(errno));
561 		goto out;
562 	}
563 
564 	n = 1;
565 	argv[0] = (char *)cmd;
566 	va_start(ap, cmd);
567 	while ((argv[n] = va_arg(ap, char *)) != NULL)
568 		n++;
569 	va_end(ap);
570 
571 	if (input == NULL)
572 		input = "/dev/null";
573 
574 	if ((infd = open(input, O_RDONLY)) == -1) {
575 		dobeep();
576 		ewprintf("Can't open input file : %s", strerror(errno));
577 		goto out;
578 	}
579 
580 	if (pipe(fds) == -1) {
581 		dobeep();
582 		ewprintf("Can't create pipe : %s", strerror(errno));
583 		goto out;
584 	}
585 
586 	newa.sa_handler = reaper;
587 	newa.sa_flags = 0;
588 	if (sigaction(SIGCHLD, &newa, NULL) == -1)
589 		goto out;
590 
591 	if ((pid = fork()) == -1) {
592 		dobeep();
593 		ewprintf("Can't fork");
594 		goto out;
595 	}
596 
597 	switch (pid) {
598 	case 0: /* Child */
599 		close(fds[0]);
600 		dup2(infd, STDIN_FILENO);
601 		dup2(fds[1], STDOUT_FILENO);
602 		dup2(fds[1], STDERR_FILENO);
603 		if (execvp(argv[0], argv) == -1)
604 			ewprintf("Can't exec %s: %s", argv[0], strerror(errno));
605 		exit(1);
606 		break;
607 	default: /* Parent */
608 		close(infd);
609 		close(fds[1]);
610 		infd = fds[1] = -1;
611 		if ((fin = fdopen(fds[0], "r")) == NULL)
612 			goto out;
613 		while (fgets(buf, sizeof(buf), fin) != NULL) {
614 			cp = strrchr(buf, '\n');
615 			if (cp == NULL && !feof(fin)) {	/* too long a line */
616 				int c;
617 				addlinef(bp, "%*s%s...", space, "", buf);
618 				while ((c = getc(fin)) != EOF && c != '\n')
619 					;
620 				continue;
621 			} else if (cp)
622 				*cp = '\0';
623 			addlinef(bp, "%*s%s", space, "", buf);
624 		}
625 		fclose(fin);
626 		break;
627 	}
628 	ret = (TRUE);
629 
630 out:
631 	if (sigaction(SIGCHLD, &olda, NULL) == -1)
632 		ewprintf("Warning, couldn't reset previous signal handler");
633 	if (fds[0] != -1)
634 		close(fds[0]);
635 	if (fds[1] != -1)
636 		close(fds[1]);
637 	if (infd != -1)
638 		close(infd);
639 	if (argv != NULL)
640 		free(argv);
641 	return ret;
642 }
643 
644 /* ARGSUSED */
645 int
646 d_create_directory(int f, int n)
647 {
648 	int ret;
649 	struct buffer	*bp;
650 
651 	ret = ask_makedir();
652 	if (ret != TRUE)
653 		return(ret);
654 
655 	if ((bp = refreshbuffer(curbp)) == NULL)
656 		return (FALSE);
657 
658 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
659 }
660 
661 /* ARGSUSED */
662 int
663 d_killbuffer_cmd(int f, int n)
664 {
665 	return(killbuffer_cmd(FFRAND, 0));
666 }
667 
668 int
669 d_refreshbuffer(int f, int n)
670 {
671 	struct buffer *bp;
672 
673 	if ((bp = refreshbuffer(curbp)) == NULL)
674 		return (FALSE);
675 
676 	return (showbuffer(bp, curwp, WFFULL | WFMODE));
677 }
678 
679 struct buffer *
680 refreshbuffer(struct buffer *bp)
681 {
682 	char	*tmp;
683 
684 	tmp = strdup(bp->b_fname);
685 	if (tmp == NULL) {
686 		dobeep();
687 		ewprintf("Out of memory");
688 		return (NULL);
689 	}
690 
691 	killbuffer(bp);
692 
693 	/* dired_() uses findbuffer() to create new buffer */
694 	if ((bp = dired_(tmp)) == NULL) {
695 		free(tmp);
696 		return (NULL);
697 	}
698 	free(tmp);
699 	curbp = bp;
700 
701 	return (bp);
702 }
703 
704 static int
705 d_makename(struct line *lp, char *fn, size_t len)
706 {
707 	int	 start, nlen;
708 	char	*namep;
709 
710 	if (d_warpdot(lp, &start) == FALSE)
711 		return (ABORT);
712 	namep = &lp->l_text[start];
713 	nlen = llength(lp) - start;
714 
715 	if (snprintf(fn, len, "%s%.*s", curbp->b_fname, nlen, namep) >= len)
716 		return (ABORT); /* Name is too long. */
717 
718 	/* Return TRUE if the entry is a directory. */
719 	return ((lgetc(lp, 2) == 'd') ? TRUE : FALSE);
720 }
721 
722 #define NAME_FIELD	9
723 
724 static int
725 d_warpdot(struct line *dotp, int *doto)
726 {
727 	char *tp = dotp->l_text;
728 	int off = 0, field = 0, len;
729 
730 	/*
731 	 * Find the byte offset to the (space-delimited) filename
732 	 * field in formatted ls output.
733 	 */
734 	len = llength(dotp);
735 	while (off < len) {
736 		if (tp[off++] == ' ') {
737 			if (++field == NAME_FIELD) {
738 				*doto = off;
739 				return (TRUE);
740 			}
741 			/* Skip the space. */
742 			while (off < len && tp[off] == ' ')
743 				off++;
744 		}
745 	}
746 	/* We didn't find the field. */
747 	*doto = 0;
748 	return (FALSE);
749 }
750 
751 static int
752 d_forwpage(int f, int n)
753 {
754 	forwpage(f | FFRAND, n);
755 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
756 }
757 
758 static int
759 d_backpage (int f, int n)
760 {
761 	backpage(f | FFRAND, n);
762 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
763 }
764 
765 static int
766 d_forwline (int f, int n)
767 {
768 	forwline(f | FFRAND, n);
769 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
770 }
771 
772 static int
773 d_backline (int f, int n)
774 {
775 	backline(f | FFRAND, n);
776 	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
777 }
778 
779 /*
780  * XXX dname needs to have enough place to store an additional '/'.
781  */
782 struct buffer *
783 dired_(char *dname)
784 {
785 	struct buffer	*bp;
786 	int		 i;
787 	size_t		 len;
788 
789 	if ((dname = adjustname(dname, FALSE)) == NULL) {
790 		dobeep();
791 		ewprintf("Bad directory name");
792 		return (NULL);
793 	}
794 	/* this should not be done, instead adjustname() should get a flag */
795 	len = strlen(dname);
796 	if (dname[len - 1] != '/') {
797 		dname[len++] = '/';
798 		dname[len] = '\0';
799 	}
800 	if ((access(dname, R_OK | X_OK)) == -1) {
801 		if (errno == EACCES) {
802 			dobeep();
803 			ewprintf("Permission denied");
804 		}
805 		return (NULL);
806 	}
807 	if ((bp = findbuffer(dname)) == NULL) {
808 		dobeep();
809 		ewprintf("Could not create buffer");
810 		return (NULL);
811 	}
812 	if (bclear(bp) != TRUE)
813 		return (NULL);
814 	bp->b_flag |= BFREADONLY | BFIGNDIRTY;
815 
816 	if ((d_exec(2, bp, NULL, "ls", "-al", dname, NULL)) != TRUE)
817 		return (NULL);
818 
819 	/* Find the line with ".." on it. */
820 	bp->b_dotp = bfirstlp(bp);
821 	for (i = 0; i < bp->b_lines; i++) {
822 		bp->b_dotp = lforw(bp->b_dotp);
823 		if (d_warpdot(bp->b_dotp, &bp->b_doto) == FALSE)
824 			continue;
825 		if (strcmp(ltext(bp->b_dotp) + bp->b_doto, "..") == 0)
826 			break;
827 	}
828 
829 	/* We want dot on the entry right after "..", if possible. */
830 	if (++i < bp->b_lines - 2)
831 		bp->b_dotp = lforw(bp->b_dotp);
832 	d_warpdot(bp->b_dotp, &bp->b_doto);
833 
834 	(void)strlcpy(bp->b_fname, dname, sizeof(bp->b_fname));
835 	(void)strlcpy(bp->b_cwd, dname, sizeof(bp->b_cwd));
836 	if ((bp->b_modes[1] = name_mode("dired")) == NULL) {
837 		bp->b_modes[0] = name_mode("fundamental");
838 		dobeep();
839 		ewprintf("Could not find mode dired");
840 		return (NULL);
841 	}
842 	bp->b_nmodes = 1;
843 	return (bp);
844 }
845