/******************************************************************************
 * NOTE: This file has been modified for use with MSDOS and the WATCOM C/386
 * compiler.  Darryl Okahata, March 1993, April 1994.
 *****************************************************************************/

/* $RCSfile: msdos.c,v $$Revision: 4.0.1.1 $$Date: 91/06/07 11:22:37 $
 *
 *    (C) Copyright 1989, 1990 Diomidis Spinellis.
 *
 *    You may distribute under the terms of either the GNU General Public
 *    License or the Artistic License, as specified in the README file.
 *
 * $Log:	msdos.c,v $
 * Revision 4.0.1.1  91/06/07  11:22:37  lwall
 * patch4: new copyright notice
 * 
 * Revision 4.0  91/03/20  01:34:46  lwall
 * 4.0 baseline.
 * 
 * Revision 3.0.1.1  90/03/27  16:10:41  lwall
 * patch16: MSDOS support
 * 
 * Revision 1.1  90/03/18  20:32:01  dds
 * Initial revision
 *
 */

/*
 * Various Unix compatibility functions for MS-DOS.
 */

#include "EXTERN.h"
#include "perl.h"

#include <stdio.h>
#include <ctype.h>
#include <dos.h>
#include <process.h>
#include <fcntl.h>

#include "dpmi.h"		/* sets DOS_EXTENDER, if appropriate */

#if defined(DOS_EXTENDER) || defined(__WINDOWS_386__)
# include <stdarg.h>
#endif

extern Malloc_t malloc(MEM_SIZE nbytes);


void msdos_init(int *p_argc, char *(*p_argv[]))
{
    if (!getenv("NOWILDEXPAND")) {
	/* Expand wildcards ... */
	__process_wildcards(p_argc, p_argv);
    }
    if (isatty(fileno(stdout))) {
	setbuf(stdout, NULL);		/* Turn off buffering */
    }
    if (isatty(fileno(stderr))) {
	setbuf(stderr, NULL);		/* Turn off buffering */
    }
    /*
     * There is no way we can refer to them from Perl so close them to save
     * space.  The other alternative would be to provide STDAUX and STDPRN
     * filehandles.
     */
    (void)fclose(stdaux);
    (void)fclose(stdprn);
    kill_c_break();
    atexit(msdos_cleanup);
}


#ifdef __WATCOMC__

#include <string.h>

sharing_active()
{
#ifdef __WINDOWS_386__
  return (1);
#else	/* not __WINDOWS_386__ */
  union REGS	inregs, outregs;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));
  inregs.h.ah = 0x10;
  inregs.h.al = 0;
  int386(0x2f, &inregs, &outregs);
  return (outregs.h.al);
#endif	/* not __WINDOWS_386__ */
}

#endif	/* __WATCOMC__ */


/*
 * Interface to the MS-DOS ioctl system call.
 * The function is encoded as follows:
 * The lowest nibble of the function code goes to AL
 * The two middle nibbles go to CL
 * The high nibble goes to CH
 *
 * The return code is -1 in the case of an error and if successful
 * for functions AL = 00, 09, 0a the value of the register DX
 * for functions AL = 02 - 08, 0e the value of the register AX
 * for functions AL = 01, 0b - 0f the number 0
 *
 * Notice that this restricts the ioctl subcodes stored in AL to 00-0f
 * In the Ralf Brown interrupt list 90.1 there are no subcodes above AL=0f
 * so we are ok.
 * Furthermore CH is also restriced in the same area.  Where CH is used as a
 * code it always is between 00-0f.  In the case where it forms a count
 * together with CL we arbitrarily set the highest count limit to 4095.  It
 * sounds reasonable for an ioctl.
 * The other alternative would have been to use the pointer argument to
 * point the the values of CX.  The problem with this approach is that
 * of accessing wild regions when DX is used as a number and not as a
 * pointer.
 */
int
ioctl(int handle, unsigned int function, char *data)
{
	union REGS      srv;
	struct SREGS    segregs;

#if defined(DOS_EXTENDER) || defined(__WINDOWS_386__)
	croak("Unsupported function ioctl()");
	/*
	 * croak() should never return, but just in case ...
	 */
	return (-1);	/* not supported */
#else /* not DOS_EXTENDER */
	srv.h.ah = 0x44;
	srv.h.al = (unsigned char)(function & 0x0F);
	srv.x.bx = handle;
	srv.x.cx = function >> 4;
	segread(&segregs);
#if ( defined(M_I86LM) || defined(M_I86CM) || defined(M_I86HM) )
	segregs.ds = FP_SEG(data);
	srv.x.dx = FP_OFF(data);
#else
	srv.x.dx = (unsigned int) data;
#endif
	intdosx(&srv, &srv, &segregs);
	if (srv.x.cflag & 1) {
		switch(srv.x.ax ){
		case 1:
			errno = EINVAL;
			break;
		case 2:
		case 3:
			errno = ENOENT;
			break;
		case 4:
			errno = EMFILE;
			break;
		case 5:
			errno = EPERM;
			break;
		case 6:
			errno = EBADF;
			break;
		case 8:
			errno = ENOMEM;
			break;
		case 0xc:
		case 0xd:
		case 0xf:
			errno = EINVAL;
			break;
		case 0x11:
			errno = EXDEV;
			break;
		case 0x12:
			errno = ENFILE;
			break;
		default:
			errno = EZERO;
			break;
		}
		return -1;
	} else {
		switch (function & 0xf) {
		case 0: case 9: case 0xa:
			return srv.x.dx;
		case 2: case 3: case 4: case 5:
		case 6: case 7: case 8: case 0xe:
			return srv.x.ax;
		case 1: case 0xb: case 0xc: case 0xd:
		case 0xf:
		default:
			return 0;
		}
	}
#endif
}

/*
 * Watcom C has this function.
 * We use Watcom's function instead of this one just in case we run this
 * code on a true multitasking OS.  Maybe, just maybe, sleep() will be
 * intelligent enough to not run in a tight loop.
 */
#ifndef __WATCOMC__
/*
 * Sleep function.
 */
void
sleep(unsigned len)
{
	time_t end;

	end = time((time_t *)0) + len;
	while (time((time_t *)0) < end)
		;
}
#endif	/* !__WATCOMC__ */


/*
 * Just pretend that everyone is a superuser
 */
#define ROOT_UID	0
#define ROOT_GID	0
int
getuid(void)
{
	return ROOT_UID;
}

int
geteuid(void)
{
	return ROOT_UID;
}

int
getgid(void)
{
	return ROOT_GID;
}

int
getegid(void)
{
	return ROOT_GID;
}

int
setuid(int uid)
{ return (uid==ROOT_UID?0:-1); }

int
setgid(int gid)
{ return (gid==ROOT_GID?0:-1); }


int umask(int mask)
{
  return (0);
}


/*****************************************************************************/

char *msdos32_fix_cmd(cmd)
char	*cmd;
{
    char	*new_cmd, *cptr;

    /*
     * Replace forward slashes with backslashes in a command name
     */
    new_cmd = malloc(strlen(cmd) + 1);
    strcpy(new_cmd, cmd);
    for (cptr = new_cmd; *cptr; ++cptr) {
	if (*cptr == '/') {
	    *cptr = '\\';
	} else if (isspace(*cptr)) {
	    break;
	}
    }
    return (new_cmd);
}


#ifdef __WINDOWS_386__


int spawnl(int mode, char *prog, char *argv0,...)
{
    croak("Unsupported function spawnl(2)");
    /*
     * croak() should never return, but just in case ...
     */
    abort();
}

int spawnvp(int mode, char *prog, char **argv0)
{
    croak("Unsupported function spawnvp(2)");
    /*
     * croak() should never return, but just in case ...
     */
    abort();
}

#endif	/* __WINDOWS_386__ */


#if defined(DOS_EXTENDER) || defined(__WINDOWS_386__)

execl(const char *__path, const char *__arg0, ...)
{
    croak("Unsupported function execl(2)");
    /*
     * croak() should never return, but just in case ...
     */
    abort();
}


execv(const char *__path, char *const __argv[])
{
    croak("Unsupported function execv(2)");
    /*
     * croak() should never return, but just in case ...
     */
    abort();
}


execvp(const char *__file, char *const __argv[])
{
    croak("Unsupported function execvp(2)");
    /*
     * croak() should never return, but just in case ...
     */
    abort();
}



#define CTRL_BREAK_CALL		0x33

static unsigned char	original_break_status;

void kill_c_break()
{
    union REGS	in_regs, out_regs;

    /*
     * Get status of the Ctrl-break flag
     */
    in_regs.h.ah = CTRL_BREAK_CALL;
    in_regs.h.al = 0;
    intdos(&in_regs, &out_regs);
    original_break_status = out_regs.h.dl;
    /*
     * Turn off Ctrl-break
     */
    in_regs.h.ah = CTRL_BREAK_CALL;
    in_regs.h.al = 1;
    in_regs.h.dl = 0;
    intdos(&in_regs, &out_regs);
}

void reset_c_break()
{
    union REGS	in_regs, out_regs;

    /*
     * Reset Ctrl-break to original state
     */
    in_regs.h.ah = CTRL_BREAK_CALL;
    in_regs.h.al = 1;
    in_regs.h.dl = original_break_status;
    intdos(&in_regs, &out_regs);
}


#ifdef MSTATS
static char		*dos_mem_start;

void initialize_dos_mem_info()
{
    dos_mem_start = malloc(1);
}

void dump_dos_mem_info()
{
    char		*dos_mem_end;
    unsigned long	bytes_used;
    DPMI_MEMINFO	meminfo;
    int			kb_per_page;
    extern void		*sbrk(int);

    kb_per_page = 4;
    dos_mem_end = sbrk(16*1024);	/* Get estimated end of memory
					 * address.  Note that the block
					 * size passed to sbrk() here
					 * *MUST* be the same block size
					 * passed to sbrk() from malloc().
					 */
    bytes_used = (unsigned long) dos_mem_end - (unsigned long) dos_mem_start;
    dpmi_get_meminfo(&meminfo);
    fprintf(stderr, "\n***** Memory usage statistics:\n\n");
    fprintf(stderr, "Largest available block            = %lu KB\n",
	    (unsigned long) meminfo.largest_avail_block / 1024);
    fprintf(stderr, "Maximum unlocked page allocation   = %d pages (%d KB)\n",
	    meminfo.max_unlocked_page,
	    meminfo.max_unlocked_page * kb_per_page);
    fprintf(stderr, "Maximum locked page allocation     = %d pages (%d KB)\n",
	    meminfo.largest_lockable_page,
	    meminfo.largest_lockable_page * kb_per_page);
    fprintf(stderr, "Linear address space size          = %d pages (%d KB)\n",
	    meminfo.linear_addr_space,
	    meminfo.linear_addr_space * kb_per_page);
    fprintf(stderr, "Total unlocked pages               = %d pages (%d KB)\n",
	    meminfo.num_free_pages,
	    meminfo.num_free_pages * kb_per_page);
    fprintf(stderr, "Number of free pages               = %d pages (%d KB)\n",
	    meminfo.num_physical_free_pages,
	    meminfo.num_physical_free_pages * kb_per_page);
    fprintf(stderr, "Total number of physical pages     = %d pages (%d KB)\n",
	    meminfo.total_physical_pages,
	    meminfo.total_physical_pages * kb_per_page);
    fprintf(stderr, "Free linear address space          = %d pages (%d KB)\n",
	    meminfo.free_linear_addr_space,
	    meminfo.free_linear_addr_space * kb_per_page);
    fprintf(stderr, "Size of paging file/partition      = ");
    if (meminfo.pagefile_size > 0) {
	fprintf(stderr, "%d pages (%d KB)\n",
		meminfo.pagefile_size,
		meminfo.pagefile_size * kb_per_page);
    } else {
	fprintf(stderr, "0 (none)\n");
    }
    if (dos_mem_end > dos_mem_start) {
	fprintf(stderr, "\nEstimated incremental memory usage = %lu KB\n",
		bytes_used / 1024);
	fprintf(stderr,
		"        (This number may include the memory allocated for stack space.)\n");
    } else {
	fprintf(stderr, "\nEstimated incremental memory usage statistics do not make sense!\n");
    }
#if defined(MYMALLOC) && defined(MSTATS)
    /*
     * If we can, also display perl's malloc() info.
     */
    mstats("(bigperl)");
#endif	/* defined(MYMALLOC) && defined(MSTATS) */
}
#endif	/* MSTATS */

#endif	/* DOS_EXTENDER || __WINDOWS_386__ */



#ifdef __WATCOMC__

extern char	*getcwd(char *buf, unsigned int size);

static unsigned int	start_drive;
static char		*start_directory;

save_dos_state()
{
    _dos_getdrive(&start_drive);
    start_directory = getcwd(NULL, 0);
    if (start_directory == NULL) {
	fprintf(stderr, "\nUnable to save DOS state -- aborting!\n");
    }
}


restore_dos_state()
{
    unsigned int	total_drives;

    _dos_setdrive(start_drive, &total_drives);
    chdir(start_directory);
}

#endif	/* __WATCOMC__ */


#undef open
#undef fopen
extern int open(const char *path, int access, ...);
extern FILE *fopen(const char *path, const char *mode);

static void remove_dots_from_basename(path)
char		*path;
{
    char	*cptr, *end;

    for (end = cptr = path + strlen(path) - 1; cptr >= path; --cptr) {
	/*
	 * Find beginning of basename
	 */
	if (*cptr == '.') {
	    /*
	     * Mark end of basename
	     */
	    end = cptr;
	} else if (*cptr == '/' || *cptr == '\\' || *cptr == ':') {
	    /*
	     * Found drive/directory separator.
	     * Make cptr point to start of basename and exit.
	     */
	    ++cptr;
	    break;
	}
    }
    if (cptr < path) {
	cptr = path;
    }
    /*
     * Don't process "." or ".."
     */
    if (cptr < end && !(cptr[0] == '.' &&
			(cptr[1] == '\0' ||
			 cptr[1] == '.' && cptr[2] == '\0'))) {
	for (; cptr < end; ++cptr) {
	    /*
	     * Remove any periods from the basename.
	     * Note that we allow periods in directory names, and that we
	     * currently do not check to see if there are multiple periods in a
	     * directory name (this case will still fail).
	     */
	    if (*cptr == '.') {
		*cptr = '_';
	    }
	}
    }
}

int msdos32_open(const char *path, int access, ...)
{
    va_list	ap;
    int		mode, fd;
    char	fbuf[PATH_MAX + 1];

    va_start(ap, access);
    if (strcmp(path, "/dev/null") == 0) {
	path = "nul";	/* substitute DOS null device name */
    } else {
	strcpy(fbuf, path);
	remove_dots_from_basename(fbuf);
	path = fbuf;
    }
    if (access & O_CREAT) {
	mode = va_arg(ap, int);
	fd = open(path, access, mode);
    } else {
	fd = open(path, access);
    }
    va_end(ap);
    return (fd);
}

FILE *msdos32_fopen(const char *path, const char *mode)
{
    FILE	*fp;
    char	fbuf[PATH_MAX + 1];

    if (strcmp(path, "/dev/null") == 0) {
	path = "nul";	/* substitute DOS null device name */
    } else {
	strcpy(fbuf, path);
	remove_dots_from_basename(fbuf);
	path = fbuf;
    }
    fp = fopen(path, mode);
    return (fp);
}


sys_stat(file, statbuf)
char		*file;
struct stat	*statbuf;
{
    int			status, len;
    char		*name, buf[PATH_MAX+1], *cwd;
    unsigned int	cur_drive, drives;
    extern char		*getcwd(char *buf, unsigned int size);

# undef stat	/* Let's not recurse infinitely ...
		   We put this here to not conflict with the above
		   definition of "struct stat". */

    strcpy(buf, file);
    name = buf;
    remove_dots_from_basename(name);
    if (isalpha(name[0]) && name[1] == ':' && name[2] == '\0') {
	/*
	 * Translate strings with only a drive specification into a drive
	 * specification with the "." directory (unless it's the root
	 * directory).
	 */
	_dos_getdrive(&cur_drive);
	_dos_setdrive(tolower(name[0]) - 'a' + 1, &drives);
	cwd = getcwd(NULL, 0);
	if (cwd == NULL) {
	    croak("\nERROR -- unable to get CWD for drive '%d' (1=A, 2=B, 3=C, etc.)\n",
		  cur_drive);
	    exit(1);
	}
	_dos_setdrive(cur_drive, &drives);
	if (strlen(cwd) == 3) {		/* root directory? */
	    buf[2] = '/';		/* Can't use "." if root directory */
	} else {
	    buf[2] = '.';
	}
	buf[3] = '\0';
	free(cwd);
    }
    if ((status = stat(name, statbuf)) == 0) {
	if ((len = strlen(name)) > 4) {
	    if (stricmp(&name[len - 4], ".bat") == 0) {
		statbuf->st_mode |= 0111;
	    }
	}
    } else {
	/*
	 * stat() failed -- check to see if we're trying to stat "." and cwd
	 * is some root directory.
	 */
	if (name[0] == '.' && name[1] == '\0') {
	    if ((cwd = getcwd(NULL, 0)) != NULL) {
		status = stat(cwd, statbuf);
		free(cwd);
	    }
	} else if (isalpha(name[0]) && name[1] == ':' && name[2] == '.' &&
		   name[3] == '\0') {
	    _dos_getdrive(&cur_drive);
	    _dos_setdrive(tolower(name[0]) - 'a' + 1, &drives);
	    if ((cwd = getcwd(NULL, 0)) != NULL) {
		status = stat(cwd, statbuf);
		free(cwd);
	    }
	    _dos_setdrive(cur_drive, &drives);
	}
    }
    return (status);
}

#ifdef LSEEK_BE_BROKEN
/*
 * Gah!  As of WATCOM C/C++^32 10.0A, the lseek() syscall has problems with
 * properly extending the length of a file.  The extended part may or may
 * not always be zero-filled, and so we have to resort to a kludgy
 * subterfuge workaround.
 */

# undef lseek

#define BLKSIZE		4096


long msdos32_lseek(int fd, long offset, int loc)
{
    long	current_offset, new_offset, end_offset, work_offset;
    int		done;
    static char	lseek_zeros[4096];	/* is all zeros by default */

    done = 0;
    while (1) {
	;
	if (((current_offset = lseek(fd, 0L, SEEK_CUR)) == -1) ||
	    ((end_offset = lseek(fd, 0L, SEEK_END)) == -1)) {
	    /*
	     * Panic -- just jump out
	     */
	    break;
	}
	switch (loc) {
	case SEEK_SET:
	    new_offset = offset;
	    break;
	case SEEK_CUR:
	    new_offset = current_offset + offset;
	    break;
	case SEEK_END:
	    new_offset = end_offset + offset;
	    break;
	default:
	    new_offset = -1;
	    break;
	}
	if (new_offset < 0) {
	    break;
	}
	if (new_offset <= end_offset) {
	    if (loc == SEEK_CUR) {
		lseek(fd, current_offset, SEEK_SET);
	    }
	    break;
	}
	for (work_offset = end_offset; work_offset < new_offset;
	     work_offset += sizeof(lseek_zeros)) {
	    long	status, len;

	    if ((len = new_offset - work_offset) > sizeof(lseek_zeros)) {
		len = sizeof(lseek_zeros);
	    }
	    status = write(fd, lseek_zeros, len);
	    if (status != len) {
		/*
		 * Panic!
		 */
		break;
	    }
	}
	if (loc == SEEK_CUR) {
	    lseek(fd, current_offset, SEEK_SET);
	}
	break;
    }
    return (lseek(fd, offset, loc));
}

#endif	/* LSEEK_BE_BROKEN */
