/* Copyright (c) 1990,1991,1992 Chris and John Downey */
#ifndef lint
static char *sccsid = "@(#)msdos_c.c	2.1 (Chris & John Downey) 7/29/92";
#endif

/***

* program name:
    xvi
* function:
    PD version of UNIX "vi" editor, with extensions.
* module name:
    msdos_c.c
* module function:
    System interface routines for MS-DOS.

    The system interface consists of this module & msdos_a.asm;
    the terminal interface for IBM PC compatibles consists of
    ibmpc_a.asm & ibmpc_c.c. Some comments on the demarcation
    between system interface and terminal interface modules are in
    the source.ms document.
* history:
    STEVIE - ST Editor for VI Enthusiasts, Version 3.10
    Originally by Tim Thompson (twitch!tjt)
    Extensive modifications by Tony Andrews (onecom!wldrdg!tony)
    Heavily modified by Chris & John Downey

***/

#include "xvi.h"

#ifdef __ZTC__
    /*
     * Zortech 3.0 has a really stupid method of specifying
     * filename expansion on the command line.
     */
#   ifdef WILDCARDS
	WILDCARDS
#   endif /* WILDCARDS */

    /*
     * Set default stack size. This seems a reasonable compromise.
     */
    unsigned _stack = 20480;
#endif		/* __ZTC__ */

void
ignore_signals(void)
{
    msdsignal(&kbdintr);
}

void
sys_init(void)
{
    char	*sh;

    /*
     * Set up a default value for Ps(P_shell). This can be overridden
     * by xvi_startup() (in startup.c) if %SHELL% is defined, or if a
     * value is given explicitly in the startup
     * script.
     *
     * %COMSPEC% should always be defined on MS-DOS. The system can't
     * really run without it.
     */
    if ((sh = getenv("COMSPEC")) != NULL) {
	set_param(P_shell, sh);
    }
    tty_open(&Rows, &Columns);
    sys_startv();
}

static	enum {
	m_SYS = 0,
	m_VI
}	curmode;

/*
 * Set up video state for editor.
 */
void
sys_startv(void)
{
    if (curmode == m_SYS) {
	tty_startv();
	set_colour(Pn(P_colour));
	curmode = m_VI;
    }
}

/*
 * Restore video state to what it was when we started.
 */
void
sys_endv(void)
{
    if (curmode == m_VI) {
	tty_goto(Rows - 1, 0);
	set_colour(Pn(P_systemcolour));
	erase_line();
	tty_endv();
	/*
	 * tty_endv() is responsible for calling
	 * flush_output().
	 */
	curmode = m_SYS;
    }
}

void
sys_exit(int r)
{
    sys_endv();
    tty_close();
    exit(r);
}

#ifndef W_OK
#	define	F_OK	0
#	define	W_OK	2
#endif

/*
 * Returns TRUE if file does not exist or exists and is writeable.
 */
bool_t
can_write(char* file)
{
#ifdef __ZTC__
    struct FIND	*p;

    p = findfirst(file, FA_HIDDEN | FA_SYSTEM | FA_DIREC);

    return(p == NULL ||
       (p->attribute & (FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_DIREC)) == 0);
#else	/* __ZTC__ */
    return access(file, F_OK) != 0 || access(file, W_OK) == 0;
#endif	/* __ZTC__ */
}

#ifndef __ZTC__

/*
 * The Zortech library has this anyway.
 */
void
sleep(n)
unsigned	n;
{
    /*
     * This works with Microsoft C.
     */
    time_t	start = time((time_t*) 0);

    flush_output();
    while (time((time_t*) 0) < start + n)
	;
}

#endif /* not __ZTC__ */

void
delay()
{
    clock_t start = clock();

    flush_output();
    while (clock() < start + CLK_TCK / 5)
	;
}

/*
 * This function is only used by tempfname(). It constructs a filename
 * suffix based on an index number.
 *
 * The suffix ".$$$" is commonly used for temporary file names on
 * MS-DOS & OS/2 systems. We also use the sequence ".$$1", ".$$2" ...
 * ".fff" (all digits are hexadecimal).
 */
static char * near
hexsuffix(i)
unsigned	i;
{
    static char	suffix [] = ".$$$";
    static char	hextab [] = "0123456789abcdef";
    char*		sp = &suffix[3];

    while (sp > suffix) {
	if (i > 0) {
	    *sp-- = hextab [i & 0xf];
	    i >>= 4;
	} else {
	    *sp-- = '$';
	}
    }
    return suffix;
}

/*
 * Construct unique name for temporary file, to be used as a backup
 * file for the named file.
 */
char*
tempfname(srcname)
char		*srcname;
{
    char	*srctail,
		*srcdot,
		*endp,
		*retp;
    unsigned	indexnum = 0;
    unsigned	baselen;

    srctail = srcdot = NULL;
    endp = srcname;

    while (*endp) {
	switch (*endp++) {
	case '\\':
	case '/':
	    srctail = endp;
	    srcdot = (char*) 0;
	    continue;
	case '.':
	    srcdot = endp - 1;
	}
    }

    if (srctail == NULL) {
	/*
	 * We haven't found any directory separators ('/' or '\\').
	 */
	srctail = srcname;
	/*
	 * Check to see if there's a disk drive name. If there
	 * is, skip over it.
	 */
	if (*srcname && is_alpha(*srcname) && srcname[1] == ':') {
	    srctail = &srcname[2];
	}
    }

    /*
     * There isn't a dot in the trailing part of the filename:
     * just add it at the end.
     */
    if (srcdot == NULL) {
	srcdot = endp;
    }

    /*
     * Don't make name too long.
     */
    if (srcdot - srctail > MAXNAMLEN - 4)
	srcdot = srctail + MAXNAMLEN - 4;
    if (srcdot - srcname > MAXPATHLEN - 4)
	srcdot = srcname + MAXPATHLEN - 4;
    baselen = srcdot - srcname;
    /*
     * Allocate space for new temporary file name ...
     */
    if ((retp = alloc(baselen + 5)) == NULL)
	return NULL;
    if (baselen > 0)
	(void) memcpy(retp, srcname, baselen);
    do {
	/*
	 * Keep trying this until we get a unique file name.
	 */
	strcpy(&retp[baselen], hexsuffix(indexnum++));
    } while (exists(retp));
    return retp;
}

/*
 * Fake out a pipe by writing output to temp file, running a process with
 * i/o redirected from this file to another temp file, and then reading
 * the second temp file back in.
 */
bool_t
sys_pipe(cmd, writefunc, readfunc)
char	*cmd;
int	(*writefunc) P((FILE *));
long	(*readfunc) P((FILE *));
{
    char	*temp1;
    FILE	*fp;
    bool_t	retval;

    /*
     * Create first temporary file ...
     */
    if ((temp1 = tempfname("xvi_out")) == NULL ||
    				(fp = fopen(temp1, "w")) == NULL) {
	retval = FALSE;
    } else {
	char	*temp2 = NULL;
	int	savcon;
	int	fd1 = -1,
		fd2 = -1;

	/*
	 * ... then write to it & close it ...
	 */
	(void) (*writefunc)(fp);
	(void) fclose(fp);

	/*
	 * ... then re-open it for reading, open second one
	 * for writing & re-arrange file descriptors.
	 *
	 * Note that we assume that the editor's standard
	 * input, output & error files are the same device,
	 * since I can't imagine how any of them could
	 * usefully be redirected to anything else.
	 */

#ifndef O_BINARY
#	define O_BINARY 0
#endif
#ifndef O_EXCL
#	define O_EXCL 0
#endif
	if (
	    (savcon = dup(0)) < 3
	    ||
	    (fd1 = open(temp1, O_RDONLY | O_BINARY)) < 3
	    ||
	    (temp2 = tempfname("xvi_in")) == NULL 
	    ||
	    (fd2 = open(temp2, O_WRONLY|O_CREAT|O_EXCL|O_BINARY, 0600)) < 3
	) {
	    retval = FALSE;
	} else {
	    (void) dup2(fd1, 0);
	    (void) dup2(fd2, 1);
	    (void) dup2(fd2, 2);

	    (void) close(fd1);
	    (void) close(fd2);
	    fd1 = fd2 = -1;

	    /*
	     * Run the command.
	     */
	    (void) system(cmd);

	    /*
	     * Restore our standard input, output & error
	     * files.
	     */
	    (void) dup2(savcon, 0);
	    (void) dup2(savcon, 1);
	    (void) dup2(savcon, 2);

	    /*
	     * Now read from the second temporary file,
	     * close it, & we're done.
	     */
	    if ((fp = fopen(temp2, "r")) == NULL) {
		retval = FALSE;
	    } else {
		(void) (*readfunc)(fp);
		(void) fclose(fp);
		retval = TRUE;
	    }
	}
	/*
	 * Clean up.
	 */
	if (temp2) {
	    (void) remove(temp2);
	    free(temp2);
	}
	if (savcon > 2)
	    (void) close(savcon);
	if (fd1 > 2)
	    (void) close(fd1);
	if (fd2 > 2)
	    (void) close(fd2);
    }

    if (temp1) {
	(void) remove(temp1);
	free(temp1);
    }

    return(retval);
}

#if 0

/*
 * The following functions are untested because neither of us has
 * access to an MS-DOS compiler at the moment.
 */

/*
 * Expand environment variables in filename.
 */

#define VMETACHAR	'$'

static char * near
vexpand(name)
    char		*name;
{
    static Flexbuf	b;
    register char	*p1, *p2;

    if ((p2 = strchr(name, VMETACHAR)) == (char *) NULL) {
	return name;
    }
    flexclear(&b);
    p1 = name;
    while (*p1) {
	register int	c;
	register char	*val;
	Flexbuf		vname;

	while (p1 < p2) {
	    (void) flexaddch(&b, *p1++);
	}
	flexnew(&vname);
	for (p2++; (c = *p2) != '\0' && (is_alnum(c) || c == '_'); p2++) {
	    (void) flexaddch(&vname, c);
	}
	if (!flexempty(&vname)
	    &&
	    (val = getenv(flexgetstr(&vname))) != (char *) NULL) {
	    while ((c = *val++) != '\0') {
		(void) flexaddch(&b, c);
	    }
	    p1 = p2;
	}
	flexdelete(&vname);
	if ((p2 = strchr(p1, VMETACHAR) == (char *) NULL) {
	    while ((c = *p1) != '\0') {
		(void) flexaddch(&b, c);
		p1++;
	    }
	}
    }
    return flexgetstr(&b);
}

static struct dstat	info;

char *
fexpand(name)
    char		*name;
{
    static Flexbuf	newname;
    char		*lastdelim;
    bool_t		has_meta;
    register char	*p;

    has_meta = FALSE;
    lastdelim = (char *) NULL;
    for (name = p = vexpand(name); *p; p++) {
	switch (*p) {
	    case ':':
	    case '\\':
	    case '/':
		lastdelim = p;
		/*
		 * DOS only understands metacharacters in the file's
		 * base name, not in any directory names.
		 */
		has_meta = FALSE;
		continue;
	    case '*':
	    case '?':
		has_meta = TRUE;
		continue;
	}
    }
    if (!has_meta) {
	return name;
    }
    if (statfirst(name, &info, dst_HIDDEN | dst_SYSTEM | dst_DIR) != 0) {
	return name;
    }
    flexclear(&newname);
    for (;;) {
	register int	c;

	if (lastdelim) {
	    for (p = name; p <= lastdelim;) {
		if (!flexaddch(&newname, *p++)) {
		    break;
		}
	    }
	}
	for (p = dst_BASENAME(info);
	     p < &dst_LAST(info) && (c = *p++) != '\0';) {
	    /*
	     * DOS gives us the file's base name in nasty CAPITAL
	     * LETTERS, so we convert them to lower-case here.
	     */
	    if (!flexaddch(&newname, is_upper(c) ? to_lower(c) : (c))) {
		break;
	    }
	}
	/*
	 * Note the order of events here; we only append a ' ' to
	 * newname if there's still at least one more file name to
	 * append after it.
	 */
	if (statnext(&info) != 0) {
	    break;
	}
	if (!flexaddch(&newname, ' ')) {
	    break;
	}
    }
    if (flexempty(&newname)) {
	return name;
    }
    return flexgetstr(&newname);
}

bool_t
exists(name)
    char * name;
{
    return statfirst(name, &info, dst_HIDDEN | dst_SYSTEM | dst_DIR) == 0;
}

bool_t
can_write(name)
    char * name;
{
    return statfirst(name, &info, dst_HIDDEN | dst_SYSTEM | dst_DIR) != 0 ||
	   (info.dst_mode &
	    (dst_READONLY | dst_HIDDEN | dst_SYSTEM | dst_DIR)) == 0;
}

#endif	/* 0 */
