/*	$OpenBSD: dired.c,v 1.105 2025/08/20 03:10:45 tb Exp $	*/

/* This file is in the public domain. */

/* dired module for mg 2a
 * by Robert A. Larson
 */

#include <sys/queue.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "def.h"
#include "funmap.h"
#include "kbd.h"

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_updirectory(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 int	 d_killbuffer_cmd(int, int);
static int	 d_refreshbuffer(int, int);
static int	 d_filevisitalt(int, int);
static int	 d_gotofile(int, int);
static void	 reaper(int);
static int	 gotofile(char*);
static struct buffer	*refreshbuffer(struct buffer *);
static int	 createlist(struct buffer *);
static void	 redelete(struct buffer *);
static char 	 *findfname(struct line *, char *);

extern struct KEYMAPE (2) helpmap;
extern struct KEYMAPE (6) cXmap;
extern struct KEYMAPE (8) metamap;

const char DDELCHAR = 'D';

/*
 * Structure which holds a linked list of file names marked for
 * deletion. Used to maintain dired buffer 'state' between refreshes.
 */
struct delentry {
	SLIST_ENTRY(delentry) entry;
	char   *fn;
};
SLIST_HEAD(slisthead, delentry) delhead = SLIST_HEAD_INITIALIZER(delhead);

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 diredcaret[] = {
	d_updirectory /* ^ */
};

static PF direda[] = {
	d_filevisitalt,		/* a */
	rescan,			/* b */
	d_copy,			/* c */
	d_del,			/* d */
	d_findfile,		/* e */
	d_findfile,		/* f */
	d_refreshbuffer,	/* g */
	rescan,			/* h */
	rescan,			/* i */
	d_gotofile		/* j */
};

static PF diredn[] = {
	d_forwline,		/* n */
	d_ffotherwindow,	/* o */
	d_backline,		/* p */
	d_killbuffer_cmd,	/* 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
};

static struct KEYMAPE (1) d_backpagemap = {
	1,
	1,
	rescan,
	{
		{
		'v', 'v', diredbp, NULL
		}
	}
};

static struct KEYMAPE (8) diredmap = {
	8,
	8,
	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
		},
		{
			'^', '^', diredcaret, NULL
		},
		{
			'a', 'j', direda, NULL
		},
		{
			'n', 'x', diredn, NULL
		},
		{
			CCHR('?'), CCHR('?'), direddl, NULL
		},
	}
};

void
dired_init(void)
{
	funmap_add(dired, "dired", 1);
	funmap_add(d_create_directory, "dired-create-directory", 1);
	funmap_add(d_copy, "dired-do-copy", 1);
	funmap_add(d_expunge, "dired-do-flagged-delete", 0);
	funmap_add(d_rename, "dired-do-rename", 1);
	funmap_add(d_findfile, "dired-find-file", 1);
	funmap_add(d_ffotherwindow, "dired-find-file-other-window", 1);
	funmap_add(d_del, "dired-flag-file-deletion", 0);
	funmap_add(d_gotofile, "dired-goto-file", 1);
	funmap_add(d_forwline, "dired-next-line", 0);
	funmap_add(d_otherwindow, "dired-other-window", 0);
	funmap_add(d_backline, "dired-previous-line", 0);
	funmap_add(d_refreshbuffer, "dired-revert", 0);
	funmap_add(d_backpage, "dired-scroll-down", 0);
	funmap_add(d_forwpage, "dired-scroll-up", 0);
	funmap_add(d_shell_command, "dired-shell-command", 1);
	funmap_add(d_undel, "dired-unmark", 0);
	funmap_add(d_undelbak, "dired-unmark-backward", 0);
	funmap_add(d_killbuffer_cmd, "quit-window", 0);
	funmap_add(d_updirectory, "dired-up-directory", 0);
	maps_add((KEYMAP *)&diredmap, "dired");
	dobindkey(fundamental_map, "dired", "^Xd");
}

int
dired(int f, int n)
{
	char		 dname[NFILEN], *bufp, *slash;
	struct buffer	*bp;

	if (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 (directory): ", 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));
}

int
d_otherwindow(int f, int n)
{
	char		 dname[NFILEN], *bufp, *slash;
	struct buffer	*bp;
	struct mgwin	*wp;

	if (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);
}

int
d_del(int f, int n)
{
	if (n < 0)
		return (FALSE);
	while (n--) {
		if (d_warpdot(curwp->w_dotp, &curwp->w_doto) == TRUE) {
			lputc(curwp->w_dotp, 0, DDELCHAR);
			curbp->b_flag |= BFDIREDDEL;
		}
		if (lforw(curwp->w_dotp) != curbp->b_headp) {
			curwp->w_dotp = lforw(curwp->w_dotp);
			curwp->w_dotline++;
		}
	}
	curwp->w_rflag |= WFEDIT | WFMOVE;
	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
}

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_dotline++;
		}
	}
	curwp->w_rflag |= WFEDIT | WFMOVE;
	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
}

int
d_undelbak(int f, int n)
{
	if (n < 0)
		return (d_undel(f, -n));
	while (n--) {
		if (lback(curwp->w_dotp) != curbp->b_headp) {
			curwp->w_dotp = lback(curwp->w_dotp);
			curwp->w_dotline--;
		}
		if (llength(curwp->w_dotp) > 0)
			lputc(curwp->w_dotp, 0, ' ');
	}
	curwp->w_rflag |= WFEDIT | WFMOVE;
	return (d_warpdot(curwp->w_dotp, &curwp->w_doto));
}

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));
}

int
d_updirectory(int f, int n)
{
	struct buffer	*bp;
	int		 ret;
	char		 fname[NFILEN];

	ret = snprintf(fname, sizeof(fname), "%s..", curbp->b_fname);
	if (ret < 0 || (size_t)ret >= sizeof(fname))
		return (ABORT); /* Name is too long. */

	bp = dired_(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));
}

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));
}

int
d_expunge(int f, int n)
{
	struct line	*lp, *nlp;
	char		 fname[NFILEN], sname[NFILEN];
	int		 tmp;

	tmp = curwp->w_dotline;
	curwp->w_dotline = 0;

	for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) {
		curwp->w_dotline++;
		nlp = lforw(lp);
		if (llength(lp) && lgetc(lp, 0) == 'D') {
			switch (d_makename(lp, fname, sizeof(fname))) {
			case ABORT:
				dobeep();
				ewprintf("Bad line in dired buffer");
				curwp->w_dotline = tmp;
				return (FALSE);
			case FALSE:
				if (unlink(fname) == -1) {
					(void)xbasename(sname, fname, NFILEN);
					dobeep();
					ewprintf("Could not delete '%s'", sname);
					curwp->w_dotline = tmp;
					return (FALSE);
				}
				break;
			case TRUE:
				if (rmdir(fname) == -1) {
					(void)xbasename(sname, fname, NFILEN);
					dobeep();
					ewprintf("Could not delete directory "
					    "'%s'", sname);
					curwp->w_dotline = tmp;
					return (FALSE);
				}
				break;
			}
			lfree(lp);
			curwp->w_bufp->b_lines--;
			if (tmp > curwp->w_dotline)
				tmp--;
			curwp->w_rflag |= WFFULL;
		}
	}
	curwp->w_dotline = tmp;
	d_warpdot(curwp->w_dotp, &curwp->w_doto);

	/* we have deleted all items successfully, remove del flag */
	curbp->b_flag &= ~BFDIREDDEL;

	return (TRUE);
}

int
d_copy(int f, int n)
{
	struct stat      statbuf;
	char		 frname[NFILEN], toname[NFILEN], sname[NFILEN];
	char		*topath, *bufp;
	int		 ret;
	size_t		 off;
	struct buffer	*bp;

	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
		dobeep();
		ewprintf("Not a file");
		return (FALSE);
	}
	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
		dobeep();
		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);

	topath = adjustname(toname, TRUE);
	if (stat(topath, &statbuf) == 0) {
		if (S_ISDIR(statbuf.st_mode)) {
			ret = snprintf(toname, sizeof(toname), "%s/%s",
			    topath, sname);
			if (ret < 0 || ret >= sizeof(toname) - 1) {
				dobeep();
				ewprintf("Directory name too long");
				return (FALSE);
			}
			topath = adjustname(toname, TRUE);
		}
	}
	if (topath == NULL)
		return (FALSE);
	if (strcmp(frname, topath) == 0) {
		ewprintf("Cannot copy to same file: %s", frname);
		return (TRUE);
	}
	ret = (copy(frname, topath) >= 0) ? TRUE : FALSE;
	if (ret != TRUE)
		return (ret);
	if ((bp = refreshbuffer(curbp)) == NULL)
		return (FALSE);

	ewprintf("Copy: 1 file");
	return (showbuffer(bp, curwp, WFFULL | WFMODE));
}

int
d_rename(int f, int n)
{
	struct stat      statbuf;
	char		 frname[NFILEN], toname[NFILEN];
	char		*topath, *bufp;
	int		 ret;
	size_t		 off;
	struct buffer	*bp;
	char		 sname[NFILEN];

	if (d_makename(curwp->w_dotp, frname, sizeof(frname)) != FALSE) {
		dobeep();
		ewprintf("Not a file");
		return (FALSE);
	}
	off = strlcpy(toname, curbp->b_fname, sizeof(toname));
	if (off >= sizeof(toname) - 1) {	/* can't happen, really */
		dobeep();
		ewprintf("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);

	topath = adjustname(toname, TRUE);
	if (stat(topath, &statbuf) == 0) {
		if (S_ISDIR(statbuf.st_mode)) {
			ret = snprintf(toname, sizeof(toname), "%s/%s",
			    topath, sname);
			if (ret < 0 || ret >= sizeof(toname) - 1) {
				dobeep();
				ewprintf("Directory name too long");
				return (FALSE);
			}
			topath = adjustname(toname, TRUE);
		}
	}
	if (topath == NULL)
		return (FALSE);
	if (strcmp(frname, topath) == 0) {
		ewprintf("Cannot move to same file: %s", frname);
		return (TRUE);
	}
	ret = (rename(frname, topath) >= 0) ? TRUE : FALSE;
	if (ret != TRUE)
		return (ret);
	if ((bp = refreshbuffer(curbp)) == NULL)
		return (FALSE);

	ewprintf("Move: 1 file");
	return (showbuffer(bp, curwp, WFFULL | WFMODE));
}

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.
 */
int
d_shell_command(int f, int n)
{
	char		 command[512], fname[PATH_MAX], *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) {
		dobeep();
		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) {
		dobeep();
		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) {
		dobeep();
		ewprintf("Can't open input file : %s", strerror(errno));
		goto out;
	}

	if (pipe(fds) == -1) {
		dobeep();
		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) {
		dobeep();
		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, *bp->b_nlchr);
			if (cp == NULL && !feof(fin)) {	/* too long a line */
				int c;
				addlinef(bp, "%*s%s...", space, "", buf);
				while ((c = getc(fin)) != EOF &&
				    c != *bp->b_nlchr)
					;
				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);
	free(argv);
	return ret;
}

int
d_create_directory(int f, int n)
{
	int ret;
	struct buffer	*bp;

	ret = ask_makedir();
	if (ret != TRUE)
		return(ret);

	if ((bp = refreshbuffer(curbp)) == NULL)
		return (FALSE);

	return (showbuffer(bp, curwp, WFFULL | WFMODE));
}

int
d_killbuffer_cmd(int f, int n)
{
	return(killbuffer_cmd(FFRAND, 0));
}

int
d_refreshbuffer(int f, int n)
{
	struct buffer *bp;

	if ((bp = refreshbuffer(curbp)) == NULL)
		return (FALSE);

	return (showbuffer(bp, curwp, WFFULL | WFMODE));
}

/*
 * Kill then re-open the requested dired buffer.
 * If required, take a note of any files marked for deletion. Then once
 * the buffer has been re-opened, remark the same files as deleted.
 */
struct buffer *
refreshbuffer(struct buffer *bp)
{
	char		*tmp_b_fname;
	int	 	 i, tmp_w_dotline, ddel = 0;

	/* remember directory path to open later */
	tmp_b_fname = strdup(bp->b_fname);
	if (tmp_b_fname == NULL) {
		dobeep();
		ewprintf("Out of memory");
		return (NULL);
	}
	tmp_w_dotline = curwp->w_dotline;

	/* create a list of files for deletion */
	if (bp->b_flag & BFDIREDDEL)
		ddel = createlist(bp);

	killbuffer(bp);

	/* dired_() uses findbuffer() to create new buffer */
	if ((bp = dired_(tmp_b_fname)) == NULL) {
		free(tmp_b_fname);
		return (NULL);
	}
	free(tmp_b_fname);

	/* remark any previously deleted files with a 'D' */
	if (ddel)
		redelete(bp);		

	/* find dot line */
	bp->b_dotp = bfirstlp(bp);
	if (tmp_w_dotline > bp->b_lines)
		tmp_w_dotline = bp->b_lines - 1;
	for (i = 1; i < tmp_w_dotline; i++)
		bp->b_dotp = lforw(bp->b_dotp);

	bp->b_dotline = i;
	bp->b_doto = 0;
	d_warpdot(bp->b_dotp, &bp->b_doto);

	curbp = bp;

	return (bp);
}

static int
d_makename(struct line *lp, char *fn, size_t len)
{
	int	 start, nlen, ret;
	char	*namep;

	if (d_warpdot(lp, &start) == FALSE)
		return (ABORT);
	namep = &lp->l_text[start];
	nlen = llength(lp) - start;

	ret = snprintf(fn, len, "%s%.*s", curbp->b_fname, nlen, namep);
	if (ret < 0 || ret >= (int)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));
}

int
d_filevisitalt (int f, int n)
{
	char	 fname[NFILEN];

	if (d_makename(curwp->w_dotp, fname, sizeof(fname)) == ABORT)
		return (FALSE);

	return(do_filevisitalt(fname));
}

/*
 * XXX dname needs to have enough place to store an additional '/'.
 */
struct buffer *
dired_(char *dname)
{
	struct buffer	*bp;
	int		 i;
	size_t		 len;

	if ((dname = adjustname(dname, TRUE)) == NULL) {
		dobeep();
		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 ((access(dname, R_OK | X_OK)) == -1) {
		if (errno == EACCES) {
			dobeep();
			ewprintf("Permission denied: %s", dname);
		} else {
			dobeep();
			ewprintf("Error opening: %s", dname);
		}
		return (NULL);
	}
	for (bp = bheadp; bp != NULL; bp = bp->b_bufp) {
		if (strcmp(bp->b_fname, dname) == 0) {
			if (fchecktime(bp) != TRUE)
				ewprintf("Directory has changed on disk;"
				    " type g to update Dired");
			return (bp);
		}

	}
	bp = bfind(dname, TRUE);
	bp->b_flag |= BFREADONLY | BFIGNDIRTY;

	if ((d_exec(2, bp, NULL, "ls", "-al", dname, NULL)) != TRUE)
		return (NULL);

	/* Find the line with ".." on it. */
	bp->b_dotp = bfirstlp(bp);
	bp->b_dotline = 1;
	for (i = 0; i < bp->b_lines; i++) {
		bp->b_dotp = lforw(bp->b_dotp);
		bp->b_dotline++;
		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);
		bp->b_dotline++;
	}
	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");
		dobeep();
		ewprintf("Could not find mode dired");
		return (NULL);
	}
	(void)fupdstat(bp);
	bp->b_nmodes = 1;
	return (bp);
}

/*
 * Iterate through the lines of the dired buffer looking for files
 * collected in the linked list made in createlist(). If a line is found
 * replace 'D' as first char in a line. As lines are found, remove the
 * corresponding item from the linked list. Iterate for as long as there
 * are items in the linked list or until end of buffer is found.
 */
void
redelete(struct buffer *bp)
{
	struct delentry	*dt, *d1 = NULL;
	struct line	*lp, *nlp;
	char		 fname[NFILEN];
	char		*p = fname;
	size_t		 plen, fnlen;
	int		 finished = 0;

	/* reset the deleted file buffer flag until a deleted file is found */
	bp->b_flag &= ~BFDIREDDEL;

	for (lp = bfirstlp(bp); lp != bp->b_headp; lp = nlp) {	
		bp->b_dotp = lp;
		if ((p = findfname(lp, p)) == NULL) {
			nlp = lforw(lp);
			continue;
		}
		plen = strlen(p);
#ifdef __DragonFly__
		SLIST_FOREACH_MUTABLE(d1, &delhead, entry, dt) {
#else
		SLIST_FOREACH_SAFE(d1, &delhead, entry, dt) {
#endif
			fnlen = strlen(d1->fn);
			if ((plen == fnlen) && 
			    (strncmp(p, d1->fn, plen) == 0)) {
				lputc(bp->b_dotp, 0, DDELCHAR);
				bp->b_flag |= BFDIREDDEL;
				SLIST_REMOVE(&delhead, d1, delentry, entry);
				if (SLIST_EMPTY(&delhead)) {
					finished = 1;
					break;
				}	
			}
		}
		if (finished)
			break;
		nlp = lforw(lp);
	}
	while (!SLIST_EMPTY(&delhead)) {
		d1 = SLIST_FIRST(&delhead);
		SLIST_REMOVE_HEAD(&delhead, entry);
		free(d1->fn);
		free(d1);
	}
	return;
}

/*
 * Create a list of files marked for deletion.
 */
int
createlist(struct buffer *bp)
{
	struct delentry	*d1 = NULL, *d2;
	struct line	*lp, *nlp;
	char		 fname[NFILEN];
	char		*p = fname;
	int		 ret = FALSE;

	for (lp = bfirstlp(bp); lp != bp->b_headp; lp = nlp) {
		/* 
		 * Check if the line has 'D' on the first char and if a valid
		 * filename can be extracted from it.
		 */
		if (((lp->l_text[0] != DDELCHAR)) ||
		    ((p = findfname(lp, p)) == NULL)) {
			nlp = lforw(lp);
			continue;
		}
		if (SLIST_EMPTY(&delhead)) {
			if ((d1 = malloc(sizeof(struct delentry)))
			     == NULL)
				return (ABORT);
			if ((d1->fn = strdup(p)) == NULL) {
				free(d1);
				return (ABORT);
			}
			SLIST_INSERT_HEAD(&delhead, d1, entry);
		} else {
			if ((d2 = malloc(sizeof(struct delentry)))
			     == NULL) {
				free(d1->fn);
				free(d1);
				return (ABORT);
			}
			if ((d2->fn = strdup(p)) == NULL) {
				free(d1->fn);
				free(d1);
				free(d2);
				return (ABORT);
			}
			if (!d1)
				SLIST_INSERT_HEAD(&delhead, d2, entry);
			else
				SLIST_INSERT_AFTER(d1, d2, entry);
			d1 = d2;				
		}
		ret = TRUE;
		nlp = lforw(lp);
	}
	return (ret);
}

int
dired_jump(int f, int n)
{
	struct buffer   *bp;
	const char	*modename;
	char             dname[NFILEN], *fname;
	int              ret, i;

	/*
	 * We use fundamental mode in dired, so just check we aren't in
	 * dired mode for this specific function. Seems like a corner
	 * case at the moment.
	 */
	for (i = 0; i <= curbp->b_nmodes; i++) {
		modename = curbp->b_modes[i]->p_name;
		if (strncmp(modename, "dired", 5) == 0)
			return (d_updirectory(f, n));
	}

	if (getbufcwd(dname, sizeof(dname)) != TRUE)
		return (FALSE);

	fname = curbp->b_fname;

	if ((bp = dired_(dname)) == NULL)
		return (FALSE);
	curbp = bp;

	ret = showbuffer(bp, curwp, WFFULL | WFMODE);
	if (ret != TRUE)
		return ret;

	fname = adjustname(fname, TRUE);
	if (fname != NULL)
		gotofile(fname);

	return (TRUE);
}

int
d_gotofile(int f, int n)
{
	size_t		 lenfpath;
	char		 fpath[NFILEN];
	char		*fpth, *fnp = NULL;

	if (getbufcwd(fpath, sizeof(fpath)) != TRUE)
		fpath[0] = '\0';
	lenfpath = strlen(fpath);
	fnp = eread("Goto file: ", fpath, NFILEN,
	    EFNEW | EFCR | EFFILE | EFDEF);
	if (fnp == NULL)
		return (ABORT);
	else if (fnp[0] == '\0')
		return (FALSE);

	fpth = adjustname(fpath, TRUE);		/* Removes last '/' if dir...  */
	if (fpth == NULL || strlen(fpth) == lenfpath - 1) { /* ...hence -1.    */
		ewprintf("No file to find");	/* Current directory given so  */
		return (TRUE);			/* return at present location. */
	}
	return gotofile(fpth);
}

int
gotofile(char *fpth)
{
	struct line	*lp, *nlp;
	char		 fname[NFILEN];
	char		*p;
	int		 tmp;

	(void)xbasename(fname, fpth, NFILEN);
	tmp = 0;
	for (lp = bfirstlp(curbp); lp != curbp->b_headp; lp = nlp) {
		tmp++;
		if ((p = findfname(lp, p)) == NULL) {
			nlp = lforw(lp);
			continue;
		}
		if (strcmp(fname, p) == 0) {
			curwp->w_dotp = lp;
			curwp->w_dotline = tmp;
			(void)d_warpdot(curwp->w_dotp, &curwp->w_doto);
			tmp--;
			break;
		}
		nlp = lforw(lp);
	}
	if (tmp == curbp->b_lines - 1) {
		ewprintf("File not found %s", fname);
		return (FALSE);
	} else {
		eerase();
		return (TRUE);
	}
}

/*
 * Look for and extract a file name on a dired buffer line.
 */
char *
findfname(struct line *lp, char *fn)
{
	int start;

	(void)d_warpdot(lp, &start);
	if (start < 1)
		return NULL;
	fn = &lp->l_text[start];
	return fn;
}
