/*
 * This file contains the command processing functions for a number of random
 * commands. There is no functional grouping here, for sure.
 *
 * $Header: /usr/build/vile/vile/RCS/random.c,v 1.191 1998/04/28 10:18:29 tom Exp $
 *
 */

#ifdef _WIN32
# include <windows.h>
#endif

#include	"estruct.h"
#include	"edef.h"
#include	"nefunc.h"

#if SYS_UNIX
#if HAVE_POLL && HAVE_POLL_H
# include <poll.h>
#endif
#endif

#include	"dirstuff.h"

#if CC_TURBO
#include <dir.h>	/* for 'chdir()' */
#endif

#if SYS_VMS
#include <starlet.h>
#include <lib$routines.h>
#endif

/*--------------------------------------------------------------------------*/

/*
 * Set default parameters for an automatically-generated, view-only buffer.
 * This is invoked after buffer creation, but usually before the buffer is
 * loaded with text.
 * the "mode" argument should be MDVIEW or MDREADONLY
 */
void
set_rdonly(BUFFER *bp, const char *name, int mode)
{
	ch_fname(bp, name);

	b_clr_changed(bp);		/* assumes text is loaded... */
	bp->b_active = TRUE;

	make_local_b_val(bp, mode);
	set_b_val(bp,mode,TRUE);

	make_local_b_val(bp,VAL_TAB);
	set_b_val(bp,VAL_TAB,8);

	make_local_b_val(bp,MDDOS);
	set_b_val(bp, MDDOS, CRLF_LINES);

	fix_cmode(bp, FALSE);
}

/* generic "lister", which takes care of popping a window/buffer pair under
	the given name, and calling "func" with a couple of args to fill in
	the buffer */
int
liststuff(
const char *name,
int appendit,
void (*func) (LIST_ARGS),	/* ptr to function to execute */
int iarg,
void *vargp)
{
	register BUFFER *bp;
	register int	s;
	WINDOW  *wp;
	int alreadypopped;
	BUFFER *ocurbp = curbp;

	/* create the buffer list buffer   */
	bp = bfind(name, BFSCRTCH);
	if (bp == NULL)
		return FALSE;

	if (!appendit && (s=bclear(bp)) != TRUE) /* clear old text (?) */
		return (s);
	b_set_scratch(bp);
	alreadypopped = (bp->b_nwnd != 0);
	if (popupbuff(bp) == FALSE) {
		(void)zotbuf(bp);
		return (FALSE);
	}

	if ((wp = bp2any_wp(bp)) != NULL) {
		make_local_w_val(wp,WMDNUMBER);
		set_w_val(wp,WMDNUMBER,FALSE);
	}
	if (appendit) {
		(void)gotoeob(FALSE,1);
		MK = DOT;
	}
	/* call the passed in function, giving it both the integer and
		character pointer arguments */
	(*func)(iarg,vargp);
	if (alreadypopped && appendit) {
		(void)gomark(FALSE,1);
		(void)forwbline(FALSE,1);
		(void)reposition(FALSE,1);
	} else { /* if we're not appending, go to the top */
		(void)gotobob(FALSE,1);
	}
	set_rdonly(bp, non_filename(), MDVIEW);

	if (alreadypopped) /* don't switch to the popup if it wasn't there */
		swbuffer(ocurbp);
	else
		shrinkwrap(); /* only resize if it's fresh */

	return TRUE;
}

/*
 * Display the current position of the cursor, lines and columns, in the file,
 * the character that is under the cursor (in hex), and the fraction of the
 * text that is before the cursor. The displayed column is not the current
 * column, but the column that would be used on an infinite width display.
 */
/* ARGSUSED */
int
showcpos(int f GCC_UNUSED, int n GCC_UNUSED)
{
	register LINE	*lp;		/* current line */
	register B_COUNT numchars = 0;	/* # of chars in file */
	register L_NUM	 numlines = 0;	/* # of lines in file */
	register B_COUNT predchars = 0;	/* # chars preceding point */
	register L_NUM	 predlines = 0;	/* # lines preceding point */
	register int	curchar = '\n';	/* character under cursor */
	long ratio;
	C_NUM col;
	C_NUM savepos;			/* temp save for current offset */
	C_NUM ecol;			/* column pos/end of current line */

#if CC_WATCOM || CC_DJGPP /* for testing interrupts */
	if (f && n == 11) {
		mlwrite("DOS interrupt test.  hit control-C or control-BREAK");
		while (!interrupted())
			;
		mlwrite("whew.  got interrupted");
		return ABORT;
	}
#endif
#if debug_undo_log
	extern int do_undolog;
	if (f && n == 11)
		do_undolog = !do_undolog;
#endif
	/* count chars and lines */
	for_each_line(lp, curbp) {
		/* if we are on the current line, record it */
		if (lp == DOT.l) {
			predlines = numlines;
			predchars = numchars + DOT.o;
			if (DOT.o == llength(lp))
				curchar = '\n';
			else
				curchar = char_at(DOT);
		}
		/* on to the next line */
		++numlines;
		numchars += llength(lp) + 1;
	}

	if (!b_val(curbp,MDNEWLINE))
		numchars--;

	/* if at end of file, record it */
	if (is_header_line(DOT,curbp)) {
		predlines = numlines;
		predchars = numchars;
	}

	/* Get real column and end-of-line column. */
	col = mk_to_vcol(DOT, FALSE, 0);
	savepos = DOT.o;
	DOT.o = llength(DOT.l);
	ecol = mk_to_vcol(DOT, FALSE, 0);
	DOT.o = savepos;

	ratio = 0;		/* Ratio before dot. */
	if (numchars != 0)
		ratio = (100L*predchars) / numchars;

	/* summarize and report the info */
	mlforce(
"Line %d of %d, Col %d of %d, Char %D of %D (%D%%) char is 0x%x or 0%o",
		predlines+1, numlines, col+1, ecol,
		predchars+1, numchars, ratio, curchar, curchar);
	return TRUE;
}

/* ARGSUSED */
int
showlength(int f GCC_UNUSED, int n GCC_UNUSED)
{
	/* actually, can be used to show any address-value */
	mlforce("%d", line_no(curbp, MK.l));
	return TRUE;
}

int
line_report(L_NUM before)
{
	L_NUM	after = line_count(curbp);

	if (do_report(before-after)) {
		if (before > after)
			mlwrite("[%d fewer lines]", before - after);
		else
			mlwrite("[%d more lines]", after - before);
		return TRUE;
	}
	return FALSE;
}

L_NUM
line_count(BUFFER *the_buffer)
{
    if (the_buffer == (BUFFER *) 0)
	return 0;
    else {
#if !SMALLER
	(void)bsizes(the_buffer);
	return the_buffer->b_linecount;
#else
	register LINE	*lp;		/* current line */
	register L_NUM	numlines = 0;	/* # of lines in file */

	for_each_line(lp, the_buffer)
		++numlines;

	return numlines;
#endif
    }
}

L_NUM
line_no(		/* return the number of the given line */
BUFFER *the_buffer,
LINEPTR the_line)
{
	if (the_line != null_ptr) {
#if !SMALLER
		L_NUM	it;
		(void)bsizes(the_buffer);
		if ((it = the_line->l_number) == 0)
			it = the_buffer->b_linecount + 1;
		return it;
#else
		register LINE	*lp;		/* current line */
		register L_NUM	numlines = 0;	/* # of lines before point */

		for_each_line(lp, the_buffer) {
			/* if we are on the specified line, record it */
			if (lp == the_line)
				break;
			++numlines;
		}

		/* and return the resulting count */
		return(numlines + 1);
#endif
	}
	return 0;
}

#if OPT_EVAL
L_NUM
getcline(void)	/* get the current line number */
{
	return line_no(curbp, DOT.l);
}
#endif

/*
 * Return the screen column in any line given an offset.
 * Assume the line is in curwp/curbp
 */
int
getcol(
MARK mark,
int actual)	/* false: effective column (expand tabs) */
		/* true: compute the actual column (depends on 'list' mode)*/
{
	register C_NUM c, i;
	register C_NUM col = 0;

	if (llength(mark.l) > 0) {
		if (actual) {
			col = offs2col(curwp, mark.l, mark.o) - nu_width(curwp);
		} else {
			C_NUM len = mark.o;
			if (len > llength(mark.l))
				len = llength(mark.l);
			for (i = w_left_margin(curwp); i < len; ++i) {
				c = lgetc(mark.l, i);
				col = next_column(c,col); /* assumes curbp */
			}
		}
	}
	return col;
}

/*
 * Return current screen column.  Stop at first non-blank given TRUE argument.
 */
int
getccol(int bflg)
{
	return getcol(DOT, bflg);
}


/*
 * Set current column, based on counting from 1
 */
int
gotocol(int f, int n)
{
	if (!f || n <= 0)
		n = 1;
	return gocol(n - 1);
}

/* given a column, return the offset */
/*  if there aren't that many columns, return how too few we were */
/*	also, if non-null, return the column we _did_ reach in *rcolp */
int
getoff(
C_NUM goal,
C_NUM *rcolp)
{
	register C_NUM c;		/* character being scanned */
	register C_NUM i;		/* index into current line */
	register C_NUM col;	/* current cursor column   */
	register C_NUM llen;	/* length of line in bytes */

	col = 0;
	llen = llength(DOT.l);

	/* scan the line until we are at or past the target column */
	for (i = w_left_margin(curwp); i < llen; ++i) {
		/* upon reaching the target, drop out */
		if (col >= goal)
			break;

		/* advance one character */
		c = lgetc(DOT.l, i);
		col = next_column(c,col);
	}

	if (rcolp)
		*rcolp = col;

	/* and tell whether we made it */
	if (col >= goal)
		return i;	/* we made it */
	else
		return col - goal; /* else how far short (in spaces) we were */
}

/* really set column, based on counting from 0, for internal use */
int
gocol(int n)
{
	register int offs;	/* current cursor column   */

	offs = getoff(n, (C_NUM *)0);

	if (offs >= 0) {
		DOT.o = offs;
		return TRUE;
	}

	DOT.o = llength(DOT.l) - 1;
	return FALSE;

}


#if ! SMALLER
/*
 * Twiddle the two characters on under and to the left of dot.  If dot is
 * at the end of the line twiddle the two characters before it.  This fixes
 * up a very common typo with a single stroke.  This always works within a
 * line, so "WFEDIT" is good enough.
 */
/* ARGSUSED */
int
twiddle(int f GCC_UNUSED, int n GCC_UNUSED)
{
	MARK		dot;
	register char	cl;
	register char	cr;

	dot = DOT;
	if (llength(dot.l) <= 1)
		return FALSE;
	if (is_at_end_of_line(dot))
		--dot.o;
	if (dot.o == 0)
		dot.o = 1;
	cr = char_at(dot);
	--dot.o;
	cl = char_at(dot);
	copy_for_undo(dot.l);
	put_char_at(dot, cr);
	++dot.o;
	put_char_at(dot, cl);
	chg_buff(curbp, WFEDIT);
	return (TRUE);
}
#endif



#if OPT_AEDIT
/*
 * Force zero or more blank lines at dot.  no argument leaves no blanks.
 * Lines will be deleted or added as needed to match the argument if it
 * is given.
 */
int
forceblank(int f, int n)
{
	register LINE	*lp1;
	register LINE	*lp2 = 0;
	B_COUNT nld;
	B_COUNT n_arg;
	C_NUM	nchar;
	int s = TRUE;

	if (!f || n < 0)
		n = 0;
	n_arg = n;

	lp1 = DOT.l;
	/* scan backward */
	while (firstchar(lp1) < 0 &&
			(lp2 = lback(lp1)) != buf_head(curbp))
		lp1 = lp2;
	lp2 = lp1;

	nld = 0;
	nchar = 0;

	/* scan forward */
	while ((lp2 = lforw(lp2)) != buf_head(curbp) &&
			firstchar(lp2) < 0) {
		++nld;
		if (nld > n_arg)
			nchar += llength(lp2) + 1;
	}

	DOT.l = lforw(lp1);
	DOT.o = 0;

	if (n_arg == nld) {		/* things are just right */
		/*EMPTY*/;
	} else if (n_arg < nld) {	/* delete (nld - n_arg) lines */
		DOT.l = lp2;
		DOT.o = 0;
		backchar(TRUE,nchar);
		s = ldelete((B_COUNT)nchar, FALSE);
	} else { 			/* insert (n_arg - nld) lines */
		n_arg = n_arg - nld;
		while (s && n_arg--)
			s = lnewline();
	}

	/* scan forward */
	while ((firstchar(DOT.l) < 0) &&
			(DOT.l != buf_head(curbp)))
		DOT.l = lforw(DOT.l);

	return s;
}

#endif

/* '~' is the traditional vi flip: flip a char and advance one */
int
flipchar(int f, int n)
{
	int s;

	havemotion = &f_forwchar_to_eol;
	s = operflip(f,n);
	if (s != TRUE)
		return s;
	return forwchar_to_eol(f,n);
}

/* 'x' is synonymous with 'd<space>' */
int
forwdelchar(int f, int n)
{
	havemotion = &f_forwchar_to_eol;
	return operdel(f,n);
}

/* 'X' is synonymous with 'd<backspace>' */
int
backdelchar(int f, int n)
{
	havemotion = &f_backchar_to_bol;
	return operdel(f,n);
}

/* 'D' is synonymous with 'd$' */
/* ARGSUSED */
int
deltoeol(int f GCC_UNUSED, int n GCC_UNUSED)
{
	if (is_empty_line(DOT))
		return TRUE;

	havemotion = &f_gotoeol;
	return operdel(FALSE,1);
}

/* 'C' is synonymous with 'c$' */
int
chgtoeol(int f, int n)
{
	if (is_empty_line(DOT)) {
		return ins();
	} else {
		havemotion = &f_gotoeol;
		return operchg(f,n);
	}
}

/* 'Y' is synonymous with 'yy' */
int
yankline(int f, int n)
{
	havemotion = &f_godotplus;
	return(operyank(f,n));
}

/* 'S' is synonymous with 'cc' */
int
chgline(int f, int n)
{
	havemotion = &f_godotplus;
	return(operchg(f,n));
}

/* 's' is synonymous with 'c<space>' */
int
chgchar(int f, int n)
{
	havemotion = &f_forwchar_to_eol;
	return(operchg(f,n));
}



/*	This function simply clears the message line,
		mainly for macro usage			*/

/* ARGSUSED */
int
clrmes(int f GCC_UNUSED, int n GCC_UNUSED)
{
	mlerase();
	return(TRUE);
}

#if ! SMALLER

/*	This function writes a string on the message line
		mainly for macro usage			*/

/* ARGSUSED */
int
writemsg(int f GCC_UNUSED, int n GCC_UNUSED)
{
	register int status;
	char buf[NPAT];		/* buffer to receive message into */

	buf[0] = EOS;
	if ((status = mlreply("Message to write: ", buf, sizeof(buf))) != TRUE)
		return(status);

	/* write the message out */
	mlforce("%s",buf);
	return(TRUE);
}

/* ARGSUSED */
int
userbeep(int f GCC_UNUSED, int n GCC_UNUSED)
{
	TTbeep();
	return TRUE;
}
#endif /* !SMALLER */


/* delay for the given number of milliseconds.  if "watchinput" is true,
	then user input will abort the delay */
void
catnap(int milli, int watchinput)
{
    if (milli == 0)
    	return;
#if DISP_X11
    if (watchinput)
	x_typahead(milli);
    else
#endif
    {
#if SYS_UNIX
# if HAVE_SELECT

	struct timeval tval;
	fd_set read_bits;
#  if HAVE_SIGPROCMASK
	sigset_t newset, oldset;
	sigemptyset(&newset);
	sigaddset(&newset, SIGALRM);
	sigprocmask(SIG_BLOCK, &newset, &oldset);
#  endif

	FD_ZERO(&read_bits);
	if (watchinput) {
		FD_SET(0, &read_bits);
	}
	tval.tv_sec = milli / 1000;
	tval.tv_usec = (milli % 1000) * 1000;	/* microseconds */
	(void)select (1, &read_bits, (fd_set*)0, (fd_set*)0, &tval);

#  if HAVE_SIGPROCMASK
	sigprocmask(SIG_SETMASK, &oldset, (sigset_t*)0);
#  endif

# else
#  if HAVE_POLL && HAVE_POLL_H

	struct pollfd pfd;
	int fdcnt;
	if (watchinput) {
		pfd.fd = 0;
		pfd.events = POLLIN;
		fdcnt = 1;
	} else {
		fdcnt = 0;
	}
	(void)poll(&pfd, fdcnt, milli); /* milliseconds */

#  else

	int seconds = (milli + 999) / 1000;
	if (watchinput)  {
		while(seconds > 0) {
			sleep(1);
			if (TTtypahead())
				return;
			seconds -= 1;
		}
	} else {
		sleep(seconds);
	}

#  endif
# endif
#define have_slept 1
#endif

#if SYS_VMS
	float	seconds = milli/1000.;
	if (watchinput)  {
		float tenth = .1;
		while(seconds > 0.1) {
			lib$wait(&tenth);
			if (TTtypahead())
				return;
			seconds -= tenth;
		}
	}
	lib$wait(&seconds);
#define have_slept 1
#endif

#if SYS_WINNT || (CC_NEWDOSCC && SYS_MSDOS) || (CC_WATCOM && (SYS_OS2 || SYS_MSDOS))
#if SYS_WINNT
#define delay(a) Sleep(a)
#endif
	if (watchinput)  {
		while(milli > 100) {
			delay(100);
			if (TTtypahead())
				return;
			milli -= 100;
		}
	}
	delay(milli);
#define have_slept 1
#endif

#ifndef have_slept
	long i;
	for (i = 0; i < term.t_pause; i++)
		;
#endif
    }
}

static char	current_dirname[NFILEN];

/* Return a string naming the current directory.  If we're unable to read the
 * directory name, return "."; otherwise we'll expect a fully-resolved path.
 */
char *
current_directory(int force)
{
	char *s;
	static char *cwd;

	if (!force && cwd)
		return cwd;
#if HAVE_GETCWD
	cwd = getcwd(current_dirname, NFILEN);
#else
# if HAVE_GETWD
	cwd = getwd(current_dirname);
# else
	{
		FILE *f;
		int n;
		f = npopen("pwd", "r");
		if (f != NULL) {
			n = fread(current_dirname, 1, NFILEN, f);
			current_dirname[n] = EOS;
			npclose(f);
			cwd = current_dirname;
		} else
			cwd = 0;
	}
# endif
#endif
	if (cwd == NULL) {
		cwd = current_dirname;
		current_dirname[0] = '.';
		current_dirname[1] = EOS;
	} else {
		s = strchr(cwd, '\n');
		if (s)
			*s = EOS;
	}
#if OPT_MSDOS_PATH
	lengthen_path(cwd);
#if !OPT_CASELESS
	mklower(cwd);
#endif
#endif

#if SYS_MSDOS || SYS_OS2
	update_dos_drv_dir(cwd);
#endif

	TRACE(("current_directory(%s)\n", cwd))
	return cwd;
}

#if SYS_MSDOS || SYS_OS2

/* convert drive index to _letter_ */
static int
drive2char(unsigned d)
{
	if (d >= 26) {
		mlforce("[Illegal drive index %d]", d);
		d = 0;
	}
	return (d + 'A');
}

/* convert drive _letter_ to index */
static unsigned
char2drive(int d)
{
	if (isAlpha(d)) {
		if (isLower(d))
			d = toUpper(d);
	} else {
		mlforce("[Not a drive '%c']", d);
		d = curdrive();
	}
	return (d - 'A');
}

/* returns drive _letter_ */
int
curdrive(void)
{
#if SYS_OS2
	unsigned d;
# if CC_CSETPP
	d = _getdrive();
# else
	_dos_getdrive(&d);  	/* A=1 B=2 ... */
# endif
	return drive2char((d-1) & 0xff);
#else
	return drive2char(bdos(0x19, 0, 0) & 0xff);
#endif
}

/* take drive _letter_ as arg. */
int
setdrive(int d)
{
	if (isAlpha(d)) {
#if SYS_OS2
# if CC_CSETPP
		_chdrive(char2drive(d) + 1);
# else
		unsigned maxdrives;
		_dos_setdrive(char2drive(d)+1, &maxdrives); /* 1 based */
# endif
#else
		bdos(0x0e, char2drive(d), 0); /* 0 based */
#endif
		return TRUE;
	}
	mlforce("[Bad drive specifier]");
	return FALSE;
}


static int curd;		/* current drive-letter */
static char *cwds[26];		/* list of current dirs on each drive */

const char *
curr_dir_on_drive(int drive)
{
	int	n = char2drive(drive);

	if (n != 0) {
		if (curd == 0)
			curd = curdrive();

		if (cwds[n])
			return cwds[n];
		else {
			cwds[n] = castalloc(char,NFILEN);

			if (cwds[n]) {
				if (setdrive(drive) == TRUE) {
					(void)strcpy(cwds[n], current_directory(TRUE));
					(void)setdrive(curd);
					(void)current_directory(TRUE);
					return cwds[n];
				}
			}
		}
	}
	return current_directory(FALSE);
}

void
update_dos_drv_dir(char *cwd)
{
	char	*s;

	if ((s = is_msdos_drive(cwd)) != 0) {
		int n = char2drive(*cwd);

		if (!cwds[n])
			cwds[n] = castalloc(char,NFILEN);

		if (cwds[n])
			(void)strcpy(cwds[n], s);
	}
}
#endif

#if OPT_SHELL
/* ARGSUSED */
int
cd(int f GCC_UNUSED, int n GCC_UNUSED)
{
	register int status;
	static	TBUFF	*last;
	char cdirname[NFILEN];

	status = mlreply_dir("Change to directory: ", &last, cdirname);
#if SYS_UNIX || SYS_VMS
	if (status == FALSE) {		/* empty reply, go HOME */
		(void)strcpy(cdirname, "~");
		status = TRUE;
	}
#endif
	if (status == TRUE)
		status = set_directory(cdirname);
	return status;
}

/* ARGSUSED */
int
pwd(int f GCC_UNUSED, int n GCC_UNUSED)
{
	mlforce("%s",current_directory(f));
	return TRUE;
}

static char prevdir[NFILEN];

static int
cd_and_pwd(char *path)
{
#if OPT_PROCEDURES
	static int cdhooking;
#endif
#if CC_CSETPP
	if (_chdir(SL_TO_BSL(path)) == 0)
#else
	if (chdir(SL_TO_BSL(path)) == 0)
#endif
	{
#if SYS_UNIX
		const char *p = current_directory(TRUE);
		if (!is_slashc(*p)) {
			if (is_slashc(*prevdir))
				p = lengthen_path(pathcat(current_dirname, prevdir, path));
			sgarbf = TRUE;	/* some shells print to stderr */
			update(TRUE);
		}

		mlforce("%s", p);
#else
		(void)pwd(TRUE,1);
#endif
#if OPT_PROCEDURES
		if (!cdhooking && *cdhook) {
			cdhooking = TRUE;
			run_procedure(cdhook);
			cdhooking = FALSE;
		}
#endif
		updatelistbuffers();
		return TRUE;
	}
	return FALSE;
}

#if OPT_EVAL
char *
previous_directory(void)
{
	if (*prevdir)
		return prevdir;
	else
		return current_directory(FALSE);
}
#endif

/* move to the named directory.  (Dave Lemke) */
int
set_directory(const char *dir)
{
    char       exdir[NFILEN];
#if SYS_UNIX
    const char *cdpath = 0;
    char       cdpathdir[NFILEN];
#endif
    char *exdp;
#if SYS_MSDOS || SYS_OS2
    int curd = curdrive();
#endif

    upmode();

    TRACE(("set_directory(%s)\n", dir))
    exdp = strcpy(exdir, dir);

    if (doglob(exdp)) {
#if SYS_MSDOS || SYS_OS2
	char	*s;
	if ((s = is_msdos_drive(exdp)) != 0) {
		if (setdrive(*exdp) == TRUE) {
			exdp = s;	/* skip device-part */
			if (!*exdp) {
				return pwd(TRUE,1);
			}
		} else {
			return FALSE;
		}
	}
#endif
	/*
	** "cd -" switches to the previous directory.
	*/
	if (!strcmp(exdp, "-"))
	{
		if (*prevdir)
			(void) strcpy(exdp, prevdir);
		else
		{
		    mlforce("[No previous directory");
		    return FALSE;
		}
	}

	/* Save current directory for subsequent "cd -". */
	(void) strcpy(prevdir, current_directory(FALSE));

#if OPT_VMS_PATH
	if (!*exdp)
		strcpy(exdp, "~");
	if (!strcmp(exdp, "/"))		/* cannot really "cd /" on vms */
		lengthen_path(exdp);
	if (!is_vms_pathname(exdp,TRUE)) {
		int end = strlen(exdp);
		if (exdp[end-1] != SLASHC) {
			exdp[end++]  = SLASHC;
			exdp[end]    = EOS;
		}
		unix2vms_path(exdp,exdp);
	}
#endif

	if (cd_and_pwd(exdp))
		return TRUE;

#if SYS_UNIX && OPT_PATHLOOKUP
	/*
	** chdir failed.  If the directory name doesn't begin with any of
	** "/", "./", or "../", get the CDPATH environment variable and check
	** if the specified directory name is a subdirectory of a
	** directory in CDPATH.
	*/
	if (!is_pathname(exdp)
	 && (cdpath = getenv("CDPATH")) != 0) {
		/* For each colon-separated component in CDPATH */
		while ((cdpath = parse_pathlist(cdpath, cdpathdir)) != 0) {
			if (cd_and_pwd(pathcat(cdpathdir, cdpathdir, exdp))) {
				return TRUE;
			}
		}
	}
#endif
    }
#if SYS_MSDOS || SYS_OS2
    setdrive(curd);
    current_directory(TRUE);
#endif
    mlforce("[Couldn't change to \"%s\"]", exdir);
    return FALSE;
}
#endif /* OPT_SHELL */

void
ch_fname(BUFFER *bp, const char *fname)
{
	int len;
	char nfilen[NFILEN];
	char *np;
	char *holdp = NULL;

	np = strcpy(nfilen, fname);

	/* produce a full pathname, unless already absolute or "internal" */
	if (!isInternalName(np))
		(void) lengthen_path(nfilen);

	len = strlen(np)+1;

	if (bp->b_fname == 0 || strcmp(bp->b_fname, np)) {

		if (bp->b_fname && bp->b_fnlen < len ) {
			/* don't free it yet -- it _may_ have been passed in as
			 * the current file-name
			 */
			holdp = bp->b_fname;
			bp->b_fname = NULL;
		}

		if (!bp->b_fname) {
			bp->b_fname = strmalloc(np);
			if (!bp->b_fname) {
				bp->b_fname = out_of_mem;
				bp->b_fnlen = strlen(bp->b_fname);
				return;
			}
			bp->b_fnlen = len;
		}

		/* it'll fit, leave len untouched */
		(void)strcpy(bp->b_fname, np);

		if (holdp != out_of_mem)
			FreeIfNeeded(holdp);
		updatelistbuffers();
	}
#ifdef	MDCHK_MODTIME
	(void)get_modtime(bp, &(bp->b_modtime));
	bp->b_modtime_at_warn = 0;
#endif
}
