freebsd-skq/usr.bin/doscmd/dos.c
imp 7cdd4f6b5d o remove -f from doscmd man page, as it is not implemented.
o Remove init printf
o Don't set optind from return value for do_args.  getopt has already
  done this.
o Bump EMS memory from 1M to 10M (this really should be a command line
  option).
o Open /dev/null when requested to open emmxxxx0 to allow many programs
  checking to see if EMS is installed to find it.
o Route int67 to ems_entry to allow EMS to be used when DOS is emulated
  as well as when DOS is booted.  This may obviate the need for the
  driver doing anything at all in a really booted situation and won't
  hurt that case.

I can now run the DOS program I'm interested in running with enough
EMS memory that its "advanced" functions are enabled and working.
1998-07-16 23:54:26 +00:00

2529 lines
50 KiB
C

/*
* Copyright (c) 1996
* Michael Smith. All rights reserved.
* Copyright (c) 1992, 1993, 1996
* Berkeley Software Design, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Berkeley Software
* Design, Inc.
*
* THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* BSDI int21.c,v 2.2 1996/04/08 19:32:51 bostic Exp
*
* $Id: dos.c,v 1.3 1998/07/02 05:12:52 imp Exp $
*/
#include "doscmd.h"
#include <dirent.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <unistd.h>
#include <time.h>
#include <glob.h>
#include <errno.h>
#include <ctype.h>
#include <stddef.h>
#include "dispatch.h"
static u_long upcase_vector;
/* Country Info */
struct {
ushort ciDateFormat;
char ciCurrency[5];
char ciThousands[2];
char ciDecimal[2];
char ciDateSep[2];
char ciTimeSep[2];
char ciCurrencyFormat;
char ciCurrencyPlaces;
char ciTimeFormat;
ushort ciCaseMapOffset;
ushort ciCaseMapSegment;
char ciDataSep[2];
#if 0
char ciReserved[10];
#endif
} countryinfo = {
0, "$", ",", ".", "-", ":", 0, 2, 0, 0xffff, 0xffff, "?"
};
/* DOS File Control Block */
struct fcb {
u_char fcbMagic;
u_char fcbResoived[5];
u_char fcbAttribute;
u_char fcbDriveID;
u_char fcbFileName[8];
u_char fcbExtent[3];
u_short fcbCurBlockNo;
u_short fcbRecSize;
u_long fcbFileSize;
u_short fcbFileDate;
u_short fcbFileTime;
int fcbReserved;
int fcb_fd; /* hide UNIX FD here */
u_char fcbCurRecNo;
u_long fcbRandomRecNo;
}/* __attribute__((__packed__))*/;
/* exports */
void encode_dos_file_time (time_t, u_short *, u_short *);
int diskdrive = 2; /* C: */
char *InDOS;
unsigned long disk_transfer_addr;
/* locals */
static int ctrl_c_flag = 0;
static int return_status = 0;
static int doserrno = 0;
static int memory_strategy = 0; /* first fit (we ignore this) */
static u_char upc_table[0x80] = {
0x80, 0x9a, 'E', 'A', 0x8e, 'A', 0x8f, 0x80,
'E', 'E', 'E', 'I', 'I', 'I', 0x8e, 0x8f,
0x90, 0x92, 0x92, 'O', 0x99, 'O', 'U', 'U',
'Y', 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
'A', 'I', 'O', 'U', 0xa5, 0xa5, 0xa6, 0xa7,
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
};
/******************************************************************************
** utility functions
*/
static u_char
upcase(u_char c)
{
if (islower(c))
return (toupper(c));
else if (c >= 0x80)
return (upc_table[c - 0x80]);
else
return (c);
}
static void
upcase_entry(regcontext_t *REGS)
{
u_char c;
R_AL = upcase(R_AL);
}
/*
** Handle the DOS drive info/free space/etc. calls.
*/
static int
int21_free(regcontext_t *REGS)
{
fsstat_t fs;
struct statfs fsstat;
char fspath[PATH_MAX];
int junk;
int spc,fclus,bps,nclus;
long nsec;
int error;
int dd_save;
int drive;
/* work out drive */
switch (R_AH) {
case 0x1c:
case 0x36:
drive = R_DL;
if (drive)
break;
/* FALLTHROUGH */
case 0x1b:
drive = diskdrive;
break;
default:
fatal("int21_free called on unknown function %x\n",R_AH);
}
error = get_space(drive, &fs);
if (error)
return(error);
R_AL = fs.sectors_cluster; /* sectors per cluster */
R_CX = fs.bytes_sector; /* bytes per sector */
R_DX = fs.total_clusters; /* total clusters */
switch (R_AH) {
case 0x1b:
case 0x1c:
BIOSDATA[0xb4] = 0xf0; /* "reserved" area, "other media" FAT ID */
R_DX = 0x40; /* BIOS data area */
R_BX = 0xb4;
break;
case 0x36:
R_BX = fs.avail_clusters; /* number of available clusters */
break;
}
return(0);
}
static void
pack_name(u_char *p, u_char *q)
{
int i;
for (i = 8; i > 0 && *p != ' '; i--)
*q++ = *p++;
p += i;
if (*p != ' ') {
*q++ = '.';
for (i = 3; i > 0 && *p != ' '; i--)
*q++ = *p++;
p += i;
}
*q = '\0';
}
static void
dosdir_to_dta(dosdir_t *dosdir, find_block_t *dta)
{
u_char *p, *q;
dta->attr = dosdir->attr;
dta->time = dosdir->time;
dta->date = dosdir->date;
dta->size = dosdir->size;
pack_name(dosdir->name, dta->name);
}
/* exported */
void
encode_dos_file_time(time_t t, u_short *dosdatep, u_short *dostimep)
{
struct tm tm;
tm = *localtime(&t);
*dostimep = (tm.tm_hour << 11) |
(tm.tm_min << 5) |
((tm.tm_sec / 2) << 0);
*dosdatep = ((tm.tm_year - 80) << 9) |
((tm.tm_mon + 1) << 5) |
(tm.tm_mday << 0);
}
time_t
decode_dos_file_time(u_short dosdate, u_short dostime)
{
struct tm tm;
time_t then;
tm.tm_hour = (dostime >> 11) & 0x1f;
tm.tm_min = (dostime >> 5) & 0x3f;
tm.tm_sec = ((dostime >> 0) & 0x1f) * 2;
tm.tm_year = ((dosdate >> 9) & 0x7f) + 80;
tm.tm_mon = ((dosdate >> 5) & 0x0f) - 1;
tm.tm_mday = (dosdate >> 0) & 0x1f;
/* tm_wday and tm_yday are ignored. */
tm.tm_isdst = 0;
/* tm_gmtoff is ignored. */
then = mktime(&tm);
return (then);
}
int
translate_filename(u_char *dname, u_char *uname, int *drivep)
{
u_char newpath[1024];
int error;
if (!strcasecmp(dname, "con")) {
*drivep = -1;
strcpy(uname, "/dev/tty");
return (0);
}
/* XXX KLUDGE for EMS support w/o booting DOS */
/* Really need a better way to handle devices */
if (!strcasecmp(dname, "emmxxxx0")) {
*drivep = -1;
strcpy(uname, "/dev/null");
return (0);
}
error = dos_makepath(dname, newpath);
if (error)
return (error);
error = dos_to_real_path(newpath, uname, drivep);
if (error)
return (error);
return (0);
}
static u_char magic[0x7e] = {
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x08, 0x0f, 0x06, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x04, 0x04, 0x0f, 0x0e, 0x06,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x06, 0x06, 0x06, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
0x0f, 0x0f, 0x0f, 0x0f, 0x04, 0x0f,
};
#define isvalid(x) ((magic[x] & 0x01) != 0)
#define issep(x) ((magic[x] & 0x02) == 0)
#define iswhite(x) ((magic[x] & 0x04) == 0)
static char *
skipwhite(char *p)
{
while (iswhite(*p))
++p;
return (p);
}
#define get_drive_letter(x) ((x) - 0x40)
int
parse_filename(int flag, char *str, char *fcb, int *nb)
{
char *p;
int ret = 0;
int i;
p = str;
p = skipwhite(p);
if (flag & 1) {
if (issep(*p)) {
++p;
p = skipwhite(p);
}
}
if (isvalid(*p) && p[1] == ':') {
*fcb++ = get_drive_letter(upcase(*p));
p += 2;
} else if (flag & 2) {
fcb++;
} else {
*fcb++ = 0; /* default drive */
}
i = 8;
if (isvalid(*p)) {
for (;;) {
if (!isvalid(*p)) {
for (; i > 0; i--)
*fcb++ = ' ';
break;
}
if (i > 0) {
switch (*p) {
case '*':
ret = 1;
for (; i > 0; i--)
*fcb++ = '?';
break;
case '?':
ret = 1;
default:
*fcb++ = upcase(*p);
i--;
break;
}
}
++p;
}
} else if (flag & 4) {
fcb += i;
} else {
for (; i > 0; i--)
*fcb++ = ' ';
}
i = 3;
if (*p == '.') {
++p;
for (;;) {
if (!isvalid(*p)) {
for (; i > 0; i--)
*fcb++ = ' ';
break;
}
if (i > 0) {
switch (*p) {
case '*':
ret = 1;
for (; i > 0; i--)
*fcb++ = '?';
break;
case '?':
ret = 1;
default:
*fcb++ = upcase(*p);
i--;
break;
}
}
++p;
}
} else if (flag & 8) {
fcb += i;
} else {
for (; i > 0; i--)
*fcb++ = ' ';
}
for (i = 4; i > 0; i--)
*fcb++ = 0; /* filler */
*nb = p - str;
return (ret);
}
/******************************************************************************
** int21 functions
*/
/*
** 21:00
**
** terminate
*/
static int
int21_00(regcontext_t *REGS)
{
done(REGS,0);
}
/*
** 21:01
**
** read character with echo
*/
static int
int21_01(regcontext_t *REGS)
{
int n;
if ((n = tty_read(&REGS->sc, TTYF_BLOCKALL)) >= 0)
R_AL = n;
return(0);
}
/*
** 21:02
**
** write char to stdout
*/
static int
int21_02(regcontext_t *REGS)
{
tty_write(R_DL, TTYF_REDIRECT);
return(0);
}
/*
** 21:06
**
** direct console I/O
**
** (dl) is output char unless 0xff, when we read instead
*/
static int
int21_06(regcontext_t *REGS)
{
int n;
/* XXX - should be able to read a file */
if (R_DL == 0xff) {
n = tty_read(&REGS->sc, TTYF_ECHO|TTYF_REDIRECT);
if (n < 0) {
R_FLAGS |= PSL_Z; /* nothing available */
R_AL = 0;
} else {
R_AL = n; /* got character */
R_FLAGS &= ~PSL_Z;
}
} else {
/* write and return char in %al */
tty_write(R_DL, TTYF_REDIRECT);
R_AL = R_DL;
}
return(0);
}
/*
** 21:07
**
** direct console input with no echo
*/
static int
int21_07(regcontext_t *REGS)
{
R_AL = tty_read(&REGS->sc, TTYF_BLOCK|TTYF_REDIRECT) & 0xff;
return(0);
}
/*
** 21:08
**
** character input with no echo
*/
static int
int21_08(regcontext_t *REGS)
{
int n;
if ((n = tty_read(&REGS->sc, TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT)) >= 0)
R_AL = n;
return(0);
}
/*
** 21:09
**
** write string to standard out.
**
** We're a little paranoid here; if the string is very long, truncate it.
*/
static int
int21_09(regcontext_t *REGS)
{
char *addr;
int len;
/* pointer to string */
addr = (char *)N_GETPTR(R_DS, R_DX);
/* walk string looking for terminator or overlength */
for (len = 0; len < 10000; len++, addr++) {
if (*addr == '$')
break;
tty_write(*addr, TTYF_REDIRECT);
}
R_AL = 0x24;
return(0);
}
/*
** 21:0a
**
** buffered input
*/
static int
int21_0a(regcontext_t *REGS)
{
unsigned char *addr;
int nbytes,avail;
int n;
/* pointer to buffer */
addr = (unsigned char *)N_GETPTR(R_DS, R_DL);
/* capacity of buffer */
avail = addr[0];
if (avail == 0) /* no space */
return(0);
nbytes = 0; /* read nothing yet */
/* read loop */
while (1) {
n = tty_read(&REGS->sc, TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT);
if (n < 0) /* end of input */
n = '\r'; /* make like CR */
switch (n) {
case '\r': /* done */
addr[1] = nbytes;
addr[nbytes+2] = '\r';
addr[nbytes+3] = '\0'; /* XXX is this necessary? */
return (0);
case '\n': /* ignore */
case '\0':
break;
case '\b': /* backspace */
if (nbytes > 0) {
--nbytes;
tty_write('\b', TTYF_REDIRECT);
if (addr[nbytes+2] < ' ')
tty_write('\b', TTYF_REDIRECT);
}
break;
default:
if (nbytes >= (avail-2)) { /* buffer full */
tty_write('\007', TTYF_REDIRECT);
} else { /* add to end */
addr[(nbytes++) +2] = n;
if (n != '\t' && n < ' ') {
tty_write('^', TTYF_REDIRECT);
tty_write(n + '@', TTYF_REDIRECT);
} else
tty_write(n, TTYF_REDIRECT);
}
break;
}
}
}
/*
** 21:0b
**
** get stdin status
**
** This is a favorite for camping on, so we do some poll-counting
** here as well.
*/
static int
int21_0b(regcontext_t *REGS)
{
int n;
/* XXX this is pretty bogus, actually */
if (!xmode) {
R_AL = 0xff; /* no X mode, always claim data available */
return(0);
}
/* XXX tty_peek is broken */
n = tty_peek(&REGS->sc, poll_cnt ? 0 : TTYF_POLL) ? 0xff : 0;
if (n < 0) /* control-break */
return (0);
R_AL = n; /* will be 0 or 0xff */
if (poll_cnt)
--poll_cnt;
return(0);
}
/*
** 21:0c
**
** flush stdin and read using other function
*/
static int
int21_0c(regcontext_t *REGS)
{
if (xmode) /* XXX should always flush! */
tty_flush();
switch(R_AL) { /* which subfunction? */
case 0x01:
return(int21_01(REGS));
case 0x06:
return(int21_06(REGS));
case 0x07:
return(int21_07(REGS));
case 0x08:
return(int21_08(REGS));
case 0x0a:
return(int21_0a(REGS));
}
return(0);
}
/*
** 21:0e
**
** select default drive
*/
static int
int21_0e(regcontext_t *REGS)
{
diskdrive = R_DL; /* XXX rangecheck? */
R_AL = ndisks + 2; /* report actual limit */
return(0);
}
/*
** 21:19
**
** get default drive
*/
static int
int21_19(regcontext_t *REGS)
{
R_AL = diskdrive;
return(0);
}
/*
** 21:1a
**
** set DTA
*/
static int
int21_1a(regcontext_t *REGS)
{
debug(D_FILE_OPS, "set dta to %x:%x\n", R_DS, R_DX);
disk_transfer_addr = N_GETVEC(R_DS, R_DX);
return(0);
}
/*
** 21:25
**
** set interrupt vector
*/
static int
int21_25(regcontext_t *REGS)
{
debug(D_MEMORY, "%02x -> %04x:%04x\n", R_AL, R_DS, R_DX);
ivec[R_AL] = N_GETVEC(R_DS, R_DX);
return(0);
}
/*
** 21:26
**
** Create PSP
*/
static int
int21_26(regcontext_t *REGS)
{
unsigned char *addr;
/* address of new PSP */
addr = (unsigned char *)MAKEPTR(R_DX, 0);
/* copy this process' PSP - XXX needs some work 8( */
memcpy (addr, dosmem, 256);
return(0);
}
/*
** 21:2a
**
** Get date
*/
static int
int21_2a(regcontext_t *REGS)
{
struct timeval tv;
struct timezone tz;
struct tm tm;
time_t now;
gettimeofday(&tv, &tz); /* get time and apply DOS offset */
now = tv.tv_sec + delta_clock;
tm = *localtime(&now); /* deconstruct and timezoneify */
R_CX = tm.tm_year + 1900;
R_DH = tm.tm_mon + 1;
R_DL = tm.tm_mday;
R_AL = tm.tm_wday;
return(0);
}
/*
** 21:2b
**
** set date
*/
static int
int21_2b(regcontext_t *REGS)
{
struct timeval tv;
struct timezone tz;
struct tm tm;
time_t now;
gettimeofday(&tv, &tz); /* get time and apply DOS offset */
now = tv.tv_sec + delta_clock;
tm = *localtime(&now);
tm.tm_year = R_CX - 1900;
tm.tm_mon = R_DH - 1;
tm.tm_mday = R_DL;
tm.tm_wday = R_AL;
now = mktime(&tm);
if (now == -1)
return (DATA_INVALID);
delta_clock = now - tv.tv_sec; /* compute new offset? */
R_AL = 0;
return(0);
}
/*
** 21:2c
**
** Get time
*/
static int
int21_2c(regcontext_t *REGS)
{
struct timeval tv;
struct timezone tz;
struct tm tm;
time_t now;
gettimeofday(&tv, &tz);
now = tv.tv_sec + delta_clock;
tm = *localtime(&now);
R_CH = tm.tm_hour;
R_CL = tm.tm_min;
R_DH = tm.tm_sec;
R_DL = tv.tv_usec / 10000;
return(0);
}
/*
** 21:2d
**
** Set time
*/
static int
int21_2d(regcontext_t *REGS)
{
struct timeval tv;
struct timezone tz;
struct tm tm;
time_t now;
gettimeofday(&tv, &tz);
now = tv.tv_sec + delta_clock;
tm = *localtime(&now);
tm.tm_hour = R_CH;
tm.tm_min = R_CL;
tm.tm_sec = R_DH;
tv.tv_usec = R_DL * 10000;
now = mktime(&tm);
if (now == -1)
return (DATA_INVALID);
delta_clock = now - tv.tv_sec;
R_AL = 0;
return(0);
}
/*
** 21:2f
**
** get DTA
*/
static int
int21_2f(regcontext_t *REGS)
{
N_PUTVEC(R_ES, R_BX, disk_transfer_addr);
debug(D_FILE_OPS, "get dta at %x:%x\n", R_ES, R_BX);
return(0);
}
/*
** 21:30
**
** get DOS version number.
**
** XXX begging for a rewrite
*/
static int
int21_30(regcontext_t *REGS)
{
int v;
char *cmd = (char *)MAKEPTR(get_env(), 0);
/*
* retch. I think this skips the environment and looks for the name
* of the current command.
*/
do {
while (*cmd)
++cmd;
} while (*++cmd);
++cmd;
cmd += *(short *)cmd + 1;
while (cmd[-1] && cmd[-1] != '\\' && cmd[-1] != ':')
--cmd;
/* get the version we're pretending to be for this sucker */
v = getver(cmd);
R_AL = (v / 100) & 0xff;
R_AH = v % 100;
return(0);
}
/*
** 21:33:05
**
** Get boot drive
*/
static int
int21_33_5(regcontext_t *REGS)
{
R_DL = 3; /* always booted from C */
return(0);
}
/*
** 21:33:06
**
** get true DOS version
*/
static int
int21_33_6(regcontext_t *REGS)
{
int v;
v = getver(NULL);
R_BL = (v / 100) & 0xff;
R_BH = v % 100;
R_DH = 0;
R_DL = 0;
return(0);
}
/*
** 21:33
**
** extended break checking
*/
static int
int21_33(regcontext_t *REGS)
{
int ftemp;
switch (R_AL) {
case 0x00:
R_DL = ctrl_c_flag;
break;
case 0x01:
ctrl_c_flag = R_DL;
break;
case 0x02:
ftemp = ctrl_c_flag;
ctrl_c_flag = R_DL;
R_DL = ftemp;
break;
default:
unknown_int3(0x21, 0x33, R_AL, REGS);
return(FUNC_NUM_IVALID);
}
return(0);
}
/*
** 21:34
**
** Get address of InDos flag
**
** XXX check interrupt list WRT location of critical error flag too.
*/
static int
int21_34(regcontext_t *REGS)
{
N_PUTVEC(R_ES, R_BX, (u_long)InDOS);
return(0);
}
/*
** 21:35
**
** get interrupt vector
*/
static int
int21_35(regcontext_t *REGS)
{
N_PUTVEC(R_ES, R_BX, ivec[R_AL]);
debug(D_MEMORY, "%02x <- %04x:%04x\n", R_AL, R_ES, R_BX);
return(0);
}
/*
** 21:37
**
** switch character manipulation
**
*/
static int
int21_37(regcontext_t *REGS)
{
switch (R_AL) {
case 0: /* get switch character */
R_DL = '/';
break;
case 1: /* set switch character (normally /) */
/* ignored by most versions of DOS */
break;
default:
unknown_int3(0x21, 0x37, R_AL, REGS);
return (FUNC_NUM_IVALID);
}
return(0);
}
/*
** 21:38
**
** country code information
**
** XXX internat guru?
*/
static int
int21_38(regcontext_t *REGS)
{
char *addr;
if (R_DX == 0xffff) {
debug(D_HALF, "warning: set country code ignored");
return(0);
}
addr = (char *)N_GETPTR(R_DS, R_DX);
N_PUTVEC(countryinfo.ciCaseMapSegment, countryinfo.ciCaseMapOffset,
upcase_vector);
memcpy(addr, &countryinfo, sizeof(countryinfo));
return(0);
}
/*
** 21:39
** 21:3a
** 21:41
** 21:56
**
** mkdir, rmdir, unlink, rename
*/
static int
int21_dirfn(regcontext_t *REGS)
{
int error;
char fname[PATH_MAX],tname[PATH_MAX];
int drive;
error = translate_filename((u_char *)N_GETPTR(R_DS, R_DX), fname, &drive);
if (error)
return (error);
if (dos_readonly(drive))
return (WRITE_PROT_DISK);
switch(R_AH) {
case 0x39:
debug(D_FILE_OPS, "mkdir(%s)\n", fname);
error = mkdir(fname, 0777);
break;
case 0x3a:
debug(D_FILE_OPS, "rmdir(%s)\n", fname);
error = rmdir(fname);
break;
case 0x41:
debug(D_FILE_OPS, "unlink(%s)\n", fname);
error = unlink(fname);
break;
case 0x56: /* rename - some extra work */
error = translate_filename((u_char *)GETPTR(R_ES, R_DI), tname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "rename(%s, %s)\n", fname, tname);
error = rename(fname, tname);
break;
default:
fatal("call to int21_dirfn for unknown function %x\n",R_AH);
}
if (error < 0) {
switch (errno) {
case ENOTDIR:
case ENOENT:
return (PATH_NOT_FOUND);
case EXDEV:
return (NOT_SAME_DEV);
default:
return (ACCESS_DENIED);
}
}
return(0);
}
/*
** 21:3b
**
** chdir
*/
static int
int21_3b(regcontext_t *REGS)
{
debug(D_FILE_OPS, "chdir(%s)\n",(u_char *)N_GETPTR(R_DS, R_DX));
return(dos_setcwd((u_char *)N_GETPTR(R_DS, R_DX)));
}
/*
** 21:3c
** 21:5b
** 21:6c
**
** open, creat, creat new, multipurpose creat
*/
static int
int21_open(regcontext_t *REGS)
{
int error;
char fname[PATH_MAX];
struct stat sb;
int mode,action,status;
char *pname;
int drive;
int fd;
switch(R_AH) {
case 0x3c: /* creat */
pname = (char *)N_GETPTR(R_DS, R_DX);
action = 0x12; /* create/truncate regardless */
mode = O_RDWR;
debug(D_FILE_OPS, "creat");
break;
case 0x3d: /* open */
pname = (char *)N_GETPTR(R_DS, R_DX);
action = 0x01; /* fail if not exist, open if exists */
switch (R_AL & 3) {
case 0:
mode = O_RDONLY;
break;
case 1:
mode = O_WRONLY;
break;
case 2:
mode = O_RDWR;
break;
default:
return (FUNC_NUM_IVALID);
}
debug(D_FILE_OPS, "open");
break;
case 0x5b: /* creat new */
pname = (char *)N_GETPTR(R_DS, R_DL);
action = 0x10; /* create if not exist, fail if exists */
mode = O_RDWR;
debug(D_FILE_OPS, "creat_new");
break;
case 0x6c: /* multipurpose */
pname = (char *)N_GETPTR(R_DS, R_SI);
action = R_DX;
switch (R_BL & 3) {
case 0:
mode = O_RDONLY;
break;
case 1:
mode = O_WRONLY;
break;
case 2:
mode = O_RDWR;
break;
default:
return (FUNC_NUM_IVALID);
}
debug(D_FILE_OPS, "mopen");
break;
default:
fatal("called int21_creat for unknown function %x\n",R_AH);
}
if (action & 0x02) /* replace/open mode */
mode |= O_TRUNC;
/* consider proposed name */
error = translate_filename(pname, fname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "(%s)\n", fname);
if (dos_readonly(drive) && (mode != O_RDONLY))
return (WRITE_PROT_DISK);
if (ustat(fname, &sb) < 0) { /* file does not exist */
if (action & 0x10) { /* create? */
sb.st_ino = 0;
mode |= O_CREAT; /* have to create as we go */
status = 0x02; /* file created */
} else {
return(FILE_NOT_FOUND);
}
} else {
if (S_ISDIR(sb.st_mode))
return(ACCESS_DENIED);
if (action & 0x03) { /* exists, work with it */
if (action & 0x02) {
if (!S_ISREG(sb.st_mode)) { /* only allowed for files */
debug(D_FILE_OPS,"attempt to truncate non-regular file\n");
return(ACCESS_DENIED);
}
status = 0x03; /* we're going to truncate it */
} else {
status = 0x01; /* just open it */
}
} else {
return(FILE_ALREADY_EXISTS); /* exists, fail */
}
}
if ((fd = open(fname, mode, from_dos_attr(R_CX))) < 0) {
debug(D_FILE_OPS,"failed to open %s : %s\n",fname,strerror(errno));
return (ACCESS_DENIED);
}
if (R_AH == 0x6c) /* need to return status too */
R_CX = status;
R_AX = fd; /* return fd */
return(0);
}
/*
** 21:3e
**
** close
*/
static int
int21_3e(regcontext_t *REGS)
{
debug(D_FILE_OPS, "close(%d)\n", R_BX);
if (R_BX == fileno(debugf)) {
printf("attempt to close debugging fd\n");
return (HANDLE_INVALID);
}
if (close(R_BX) < 0)
return (HANDLE_INVALID);
return(0);
}
/*
** 21:3f
**
** read
*/
static int
int21_3f(regcontext_t *REGS)
{
int fd;
char *addr;
int nbytes,n;
int avail;
addr = (char *)N_GETPTR(R_DS, R_DX);
debug(D_FILE_OPS, "read(%d, %d)\n", R_BX, R_CX);
if (R_BX == 0) {
if (redirect0) {
n = read (R_BX, addr, R_CX);
} else {
n = 0;
while (n < R_CX) {
avail = tty_read(REGS, TTYF_BLOCK|TTYF_CTRL|TTYF_ECHONL);
if (avail < 0)
return (0);
if ((addr[n++] = avail) == '\r')
break;
}
}
} else {
n = read (R_BX, addr, R_CX);
}
if (n < 0)
return (READ_FAULT);
R_AX = n;
return(0);
}
/*
** 21:40
**
** write
*/
static int
int21_40(regcontext_t *REGS)
{
char *addr;
int nbytes,n;
addr = (char *)N_GETPTR(R_DS, R_DX);
nbytes = R_CX;
debug(D_FILE_OPS, "write(%d, %d)\n", R_BX, nbytes);
switch (R_BX) {
case 0:
if (redirect0) {
n = write (R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
case 1:
if (redirect1) {
n = write (R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
case 2:
if (redirect2) {
n = write (R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
default:
n = write (R_BX, addr, nbytes);
break;
}
if (n < 0)
return (WRITE_FAULT);
R_AX = n;
return(0);
}
/*
** 21:42
**
** seek
*/
static int
int21_42(regcontext_t *REGS)
{
int whence;
off_t offset;
offset = (off_t) ((int) (R_CX << 16) + R_DX);
switch (R_AL) {
case 0:
whence = SEEK_SET;
break;
case 1:
whence = SEEK_CUR;
break;
case 2:
whence = SEEK_END;
break;
default:
return (FUNC_NUM_IVALID);
}
debug(D_FILE_OPS, "seek(%d, 0x%qx, %d)\n", R_BX, offset, whence);
if ((offset = lseek(R_BX, offset, whence)) < 0) {
if (errno == EBADF)
return (HANDLE_INVALID);
else
return (SEEK_ERROR);
}
R_DX = (offset >> 16) & 0xffff;
R_AX = offset & 0xffff;
return(0);
}
/*
** 21:43
**
** get/set attributes
*/
static int
int21_43(regcontext_t *REGS)
{
int error;
char fname[PATH_MAX];
struct stat sb;
int mode;
int drive;
error = translate_filename((u_char *)N_GETPTR(R_DS, R_DX), fname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "get/set attributes: %s, cx=%x, al=%d\n",
fname, R_CX, R_AL);
if (stat(fname, &sb) < 0) {
debug(D_FILE_OPS, "stat failed for %s\n", fname);
return (FILE_NOT_FOUND);
}
switch (R_AL) {
case 0: /* get attributes */
mode = 0;
if (dos_readonly(drive) || access(fname, W_OK))
mode |= 0x01;
if (S_ISDIR(sb.st_mode))
mode |= 0x10;
R_CX = mode;
break;
case 1: /* set attributes - XXX ignored */
if (R_CX & 0x18)
return (ACCESS_DENIED);
break;
default:
return (FUNC_NUM_IVALID);
}
return(0);
}
/*
** 21:44:0
**
** ioctl - get device info
**
** XXX it would be nice to detect EOF.
*/
static int
int21_44_0(regcontext_t *REGS)
{
debug(D_FILE_OPS, "ioctl get %d\n", R_BX);
switch (R_BX) {
case 0:
R_DX = 0x80 | 0x01; /* is device, is standard output */
break;
case 1:
R_DX = 0x80 | 0x02; /* is device, is standard input */
break;
case 2:
R_DX = 0x80; /* is device */
break;
default:
if (isatty (R_BX))
R_DX = 0x80; /* is a device */
else
R_DX = 0; /* is a file */
break;
}
return(0);
}
/*
** 21:44:01
**
** ioctl - set device info
*/
static int
int21_44_1(regcontext_t *REGS)
{
debug(D_FILE_OPS, "ioctl set device info %d flags %x (ignored)\n",
R_BX, R_DX);
return(0);
}
/*
** 21:44:8
**
** test for removable block device
*/
static int
int21_44_8(regcontext_t *REGS)
{
R_AX = 1; /* fixed */
return(0);
}
/*
** 21:44:0
**
** test for remote device (disallow direct I/O)
*/
static int
int21_44_9(regcontext_t *REGS)
{
R_DX = 0x1200; /* disk is remote, direct I/O not allowed */
}
/*
** 21:45
**
** dup
*/
static int
int21_45(regcontext_t *REGS)
{
int nfd;
debug(D_FILE_OPS, "dup(%d)\n", R_BX);
if ((nfd = dup(R_BX)) < 0) {
if (errno == EBADF)
return (HANDLE_INVALID);
else
return (TOO_MANY_OPEN_FILES);
}
R_AX = nfd;
return(0);
}
/*
** 21:46
**
** dup2
*/
static int
int21_46(regcontext_t *REGS)
{
debug(D_FILE_OPS, "dup2(%d, %d)\n", R_BX, R_CX);
if (dup2(R_BX, R_CX) < 0) {
if (errno == EMFILE)
return (TOO_MANY_OPEN_FILES);
else
return (HANDLE_INVALID);
}
return(0);
}
/*
** 21:47
**
** getcwd
*/
static int
int21_47(regcontext_t *REGS)
{
int n,nbytes;
char *p,*addr;
n = R_DL;
if (!n--)
n = diskdrive;
p = (char *)dos_getcwd(n) + 1;
addr = (char *)N_GETPTR(R_DS, R_SI);
nbytes = strlen(p);
if (nbytes > 63)
nbytes = 63;
memcpy(addr, p, nbytes);
addr[nbytes] = 0;
return(0);
}
/*
** 21:48
**
** allocate memory
*/
static int
int21_48(regcontext_t *REGS)
{
int memseg,avail;
memseg = mem_alloc(R_BX, pspseg, &avail);
if (memseg == 0L) {
R_BX = avail;
return (INSUF_MEM);
}
R_AX = memseg;
return(0);
}
/*
** 21:49
**
** free memory
*/
static int
int21_49(regcontext_t *REGS)
{
if (mem_adjust(R_ES, 0, NULL) < 0)
return (MEM_BLK_ADDR_IVALID);
return(0);
}
/*
** 21:4a
**
** resize memory block
*/
static int
int21_4a(regcontext_t *REGS)
{
int n,avail;
if ((n = mem_adjust(R_ES, R_BX, &avail)) < 0) {
R_BX = avail;
if (n == -1)
return (INSUF_MEM);
else
return (MEM_BLK_ADDR_IVALID);
}
return(0);
}
/*
** 21:4b
**
** exec
**
** XXX verify!
*/
static int
int21_4b(regcontext_t *REGS)
{
int fd;
char *fname[PATH_MAX];
u_short *param;
debug(D_EXEC, "exec(%s)\n",(u_char *)N_GETPTR(R_DS, R_DX));
if ((fd = open_prog((u_char *)N_GETPTR(R_DS, R_DX))) < 0) {
debug(D_EXEC, "%s: command not found\n", fname);
return (FILE_NOT_FOUND);
}
/* child */
param = (u_short *)N_GETPTR(R_ES, R_BX);
switch (R_AL) {
case 0x00: /* load and execute */
exec_command(REGS, 1, fd, cmdname, param);
close(fd);
break;
case 0x01: /* just load */
exec_command(REGS, 0, fd, cmdname, param);
close(fd);
break;
case 0x03: /* load overlay */
load_overlay(fd, param[0], param[1]);
close(fd);
break;
default:
unknown_int3(0x21, 0x4b, R_AL, REGS);
return (FUNC_NUM_IVALID);
}
return(0);
}
/*
** 21:4c
**
** return with code
*/
static int
int21_4c(regcontext_t *REGS)
{
return_status = R_AL;
done(REGS, R_AL);
}
/*
** 21:4d
**
** get return code of child
*/
static int
int21_4d(regcontext_t *REGS)
{
R_AX = return_status;
return(0);
}
/*
** 21:4e
** 21:4f
**
** find first, find next
*/
static int
int21_find(regcontext_t *REGS)
{
find_block_t *dta;
dosdir_t dosdir;
int error;
dta = (find_block_t *)VECPTR(disk_transfer_addr);
switch (R_AH) {
case 0x4e: /* find first */
error = find_first((u_char *)N_GETPTR(R_DS, R_DX), R_CX, &dosdir, dta);
break;
case 0x4f:
error = find_next(&dosdir, dta);
break;
default:
fatal("called int21_find for unknown function %x\n",R_AH);
}
if (!error) {
dosdir_to_dta(&dosdir, dta);
R_AX = 0;
}
return(error);
}
/*
** 21:50
**
** set PSP
*/
static int
int21_50(regcontext_t *REGS)
{
pspseg = R_BX;
return(0);
}
/*
** 21:57:00
**
** get mtime for handle
*/
static int
int21_57_0(regcontext_t *REGS)
{
struct stat sb;
u_short date,time;
if (fstat(R_BX, &sb) < 0)
return (HANDLE_INVALID);
encode_dos_file_time(sb.st_mtime, &date, &time);
R_CX = time;
R_DX = date;
return(0);
}
/*
** 21:57:01
**
** set mtime for handle
*/
static int
int21_57_1(regcontext_t *REGS)
{
#ifdef __NetBSD__ /* XXX need futimes() */
struct stat sb;
struct timeval tv[2];
u_short date, time;
time = R_CX;
date = R_DX;
tv[0].tv_sec = tv[1].tv_sec = decode_dos_file_time(date, time);
tv[0].tv_usec = tv[1].tv_usec = 0;
if (futimes(R_BX, tv) < 0)
return (HANDLE_INVALID);
break;
#endif
return(0);
}
/*
** 21:58
**
** get/set memory strategy
** get/set UMB link state
*/
static int
int21_58(regcontext_t *REGS)
{
switch (R_AL) {
case 0x00: /* get memory strategy */
R_AX = memory_strategy;
break;
case 0x01: /* set memory strategy */
memory_strategy = R_BL;
if (memory_strategy > 2) /* higher make no sense without UMBs */
memory_strategy = 2;
break;
case 0x02: /* get UMB link state */
R_AL = 0; /* UMBs not in link chain */
break;
default: /* includes set, which is invalid */
unknown_int3(0x21, 0x58, R_AL, REGS);
return (FUNC_NUM_IVALID);
}
return(0);
}
/*
** 21:59
**
** get extended error information
*/
static int
int21_59(regcontext_t *REGS)
{
R_AX = doserrno;
switch (doserrno) {
case 1:
case 6:
case 9:
case 10:
case 11:
case 12:
case 13:
case 15:
R_BH = 7; /* application error */
break;
case 2:
case 3:
case 4:
case 5:
R_BH = 8; /* not found */
break;
case 7:
case 8:
R_BH = 1; /* out of resource */
break;
default:
R_BH = 12; /* already exists */
break;
}
R_BL = 6; /* always ignore! */
R_CH = 1; /* unknown/inappropriate */
return(0);
}
/*
** 21:5a
**
** create temporary file
*/
static int
int21_5a(regcontext_t *REGS)
{
char fname[PATH_MAX];
char *pname;
int error;
int n;
int drive;
int fd;
/* get and check proposed path */
pname = (char *)N_GETPTR(R_DS, R_DX);
error = translate_filename(pname, fname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "tempname(%s)\n", fname);
if (dos_readonly(drive))
return (WRITE_PROT_DISK);
n = strlen(fname);
strcat(fname,"__dostmp.XXX");
fd = mkstemp(fname);
if (fd < 0)
return (ACCESS_DENIED);
strcat(pname, fname + n); /* give back the full name */
R_AX = fd;
return(0);
}
/*
** 21:60
**
** canonicalise name
*/
static int
int21_60(regcontext_t *REGS)
{
return(dos_makepath((char *)GETPTR(R_DS, R_SI),
(char *)GETPTR(R_ES, R_DI)));
}
/*
** 21:62
**
** get current PSP
*/
static int
int21_62(regcontext_t *REGS)
{
R_BX = pspseg;
}
/*
** 21:65:23
**
** determine yes/no
** (mostly for humour value 8)
*/
static int
int21_65_23(regcontext_t *REGS)
{
switch (R_DL) {
case 'n': /* no, nein, non, nyet */
case 'N':
R_AX = 0;
break;
case 'y': /* yes */
case 'Y':
case 'j': /* ja */
case 'J':
case 'o': /* oui */
case 'O':
case 'd': /* da */
case 'D':
R_AX = 1;
break;
default: /* maybe */
R_AX = 2;
break;
}
return(0);
}
/*
** 21:68
** 21:6a
**
** fflush/commit file
*/
static int
int21_fflush(regcontext_t *REGS)
{
debug(D_FILE_OPS, "fsync(%d)\n", R_BX);
if (fsync(R_BX) < 0)
return (HANDLE_INVALID);
return(0);
}
/******************************************************************************
** 21:0f 21:10 21:11 21:12 21:16 21:27 21:28:21:29
**
** FCB functions
*/
static void
openfcb(struct fcb *fcbp)
{
struct stat statb;
fcbp->fcbDriveID = 3; /* drive C */
fcbp->fcbCurBlockNo = 0;
fcbp->fcbRecSize = 128;
if (fstat(fcbp->fcb_fd, &statb) < 0) {
debug(D_FILE_OPS, "open not complete with errno %d\n", errno);
return;
}
encode_dos_file_time(statb.st_mtime,
&fcbp->fcbFileDate, &fcbp->fcbFileTime);
fcbp->fcbFileSize = statb.st_size;
}
static int
getfcb_rec(struct fcb *fcbp, int nrec)
{
int n;
n = fcbp->fcbRandomRecNo;
if (fcbp->fcbRecSize >= 64)
n &= 0xffffff;
fcbp->fcbCurRecNo = n % 128;
fcbp->fcbCurBlockNo = n / 128;
if (lseek(fcbp->fcb_fd, n * fcbp->fcbRecSize, SEEK_SET) < 0)
return (-1);
return (nrec * fcbp->fcbRecSize);
}
static int
setfcb_rec(struct fcb *fcbp, int n)
{
int recs, total;
total = fcbp->fcbRandomRecNo;
if (fcbp->fcbRecSize >= 64)
total &= 0xffffff;
recs = (n+fcbp->fcbRecSize-1) / fcbp->fcbRecSize;
total += recs;
fcbp->fcbRandomRecNo = total;
fcbp->fcbCurRecNo = total % 128;
fcbp->fcbCurBlockNo = total / 128;
}
void
fcb_to_string(fcbp, buf)
struct fcb *fcbp;
u_char *buf;
{
if (fcbp->fcbDriveID != 0x00) {
*buf++ = drntol(fcbp->fcbDriveID - 1);
*buf++ = ':';
}
pack_name(fcbp->fcbFileName, buf);
}
static int
int21_fcb(regcontext_t *REGS)
{
char buf[PATH_MAX];
char fname[PATH_MAX];
struct stat sb;
dosdir_t dosdir;
struct fcb *fcbp;
find_block_t *dta;
u_char *addr;
int error;
int drive;
int fd;
int nbytes,n;
fcbp = (struct fcb *)GETPTR(R_DS, R_DX);
switch (R_AH) {
case 0x0f: /* open file with FCB */
fcb_to_string(fcbp, buf);
error = translate_filename(buf, fname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "open FCB(%s)\n", fname);
if (ustat(fname, &sb) < 0)
sb.st_ino = 0;
if (dos_readonly(drive))
return (WRITE_PROT_DISK);
if (sb.st_ino == 0 || S_ISDIR(sb.st_mode))
return (FILE_NOT_FOUND);
if ((fd = open(fname, O_RDWR)) < 0) {
if (errno == ENOENT)
return (FILE_NOT_FOUND);
else
return (ACCESS_DENIED);
}
fcbp->fcb_fd = fd;
openfcb(fcbp);
R_AL = 0;
break;
case 0x10: /* close file with FCB */
debug(D_FILE_OPS, "close FCB(%d)\n", fcbp->fcb_fd);
if (close(fcbp->fcb_fd) < 0)
return (HANDLE_INVALID);
fcbp->fcb_fd = -1;
R_AL = 0;
break;
case 0x11: /* find_first with FCB */
dta = (find_block_t *)VECPTR(disk_transfer_addr);
fcb_to_string(fcbp, buf);
error = find_first(buf, fcbp->fcbAttribute, &dosdir, dta);
if (error)
return (error);
dosdir_to_dta(&dosdir, dta);
R_AL = 0;
break;
case 0x12: /* find_next with FCB */
dta = (find_block_t *)VECPTR(disk_transfer_addr);
error = find_next(&dosdir, dta);
if (error)
return (error);
dosdir_to_dta(&dosdir, dta);
R_AL = 0;
break;
case 0x16: /* create file with FCB */
fcb_to_string(fcbp, buf);
error = translate_filename(buf, fname, &drive);
if (error)
return (error);
debug(D_FILE_OPS, "creat FCB(%s)\n", fname);
if (ustat(fname, &sb) < 0)
sb.st_ino = 0;
if (dos_readonly(drive))
return (WRITE_PROT_DISK);
if (sb.st_ino && !S_ISREG(sb.st_mode))
return (ACCESS_DENIED);
if ((fd = open(fname, O_CREAT|O_TRUNC|O_RDWR, 0666)) < 0)
return (ACCESS_DENIED);
fcbp->fcb_fd = fd;
openfcb(fcbp);
R_AL = 0;
break;
case 0x27: /* random block read */
addr = (u_char *)VECPTR(disk_transfer_addr);
nbytes = getfcb_rec(fcbp, R_CX);
if (nbytes < 0)
return (READ_FAULT);
n = read(fcbp->fcb_fd, addr, nbytes);
if (n < 0)
return (READ_FAULT);
R_CX = setfcb_rec(fcbp, n);
if (n < nbytes) {
nbytes = n % fcbp->fcbRecSize;
if (0 == nbytes) {
R_AL = 0x01;
} else {
bzero(addr + n, fcbp->fcbRecSize - nbytes);
R_AL = 0x03;
}
} else {
R_AL = 0;
}
break;
case 0x28: /* random block write */
addr = (u_char *)VECPTR(disk_transfer_addr);
nbytes = getfcb_rec(fcbp, R_CX);
if (nbytes < 0)
return (WRITE_FAULT);
n = write(fcbp->fcb_fd, addr, nbytes);
if (n < 0)
return (WRITE_FAULT);
R_CX = setfcb_rec(fcbp, n);
if (n < nbytes) {
R_AL = 0x01;
} else {
R_AL = 0;
}
break;
case 0x29: /* parse filename */
debug(D_FILE_OPS,"parse filename: flag=%d, ", R_AL);
R_AX = parse_filename(R_AL,
(char *)N_GETPTR(R_DS, R_SI),
(char *)N_GETPTR(R_ES, R_DI),
&nbytes);
debug(D_FILE_OPS, "%d %s, FCB: %d, %.11s\n",
nbytes,
N_GETPTR(R_DS, R_SI),
*(int *)N_GETPTR(R_ES, R_DI),
N_GETPTR(R_ES, R_DI) + 1);
R_SI += nbytes;
break;
default:
fatal("called int21_fcb with unknown function %x\n",R_AH);
}
return(0);
}
/*
** 21:5d
** 21:5e
** 21:5f
**
** network functions
** XXX relevant?
*/
static int
int21_net(regcontext_t *REGS)
{
switch(R_AH) {
case 0x5d:
switch(R_AL) {
case 0x06:
debug(D_HALF, "Get Swapable Area\n");
return (ACCESS_DENIED);
case 0x08: /* Set redirected printer mode */
debug(D_HALF, "Redirection is %s\n",
R_DL ? "seperate jobs" : "combined");
break;
case 0x09: /* Flush redirected printer output */
break;
default:
unknown_int3(0x21, 0x5d, R_AL, REGS);
return (FUNC_NUM_IVALID);
}
break;
case 0x5e:
case 0x5f:
unknown_int2(0x21, R_AH, REGS);
return (FUNC_NUM_IVALID);
default:
fatal("called int21_net with unknown function %x\n",R_AH);
}
return(0);
}
/*
** 21:??
**
** Unknown/unsupported
*/
static int
int21_NOFUNC(regcontext_t *REGS)
{
unknown_int2(0x21, R_AH, REGS);
return (FUNC_NUM_IVALID);
}
/*
** 21:??
**
** Null function; no error, no action
*/
static int
int21_NULLFUNC(regcontext_t *REGS)
{
R_AL = 0;
return(0);
}
static struct intfunc_table int21_table [] = {
{ 0x00, IFT_NOSUBFUNC, int21_00, "terminate"},
{ 0x01, IFT_NOSUBFUNC, int21_01, "read character with echo"},
{ 0x02, IFT_NOSUBFUNC, int21_02, "write char to stdout"},
{ 0x03, IFT_NOSUBFUNC, int21_NOFUNC, "read char from stdaux"},
{ 0x04, IFT_NOSUBFUNC, int21_NOFUNC, "write char to stdaux"},
{ 0x05, IFT_NOSUBFUNC, int21_NOFUNC, "write char to printer"},
{ 0x06, IFT_NOSUBFUNC, int21_06, "direct console I/O"},
{ 0x07, IFT_NOSUBFUNC, int21_07, "direct console in without echo"},
{ 0x08, IFT_NOSUBFUNC, int21_08, "read character, no echo"},
{ 0x09, IFT_NOSUBFUNC, int21_09, "write string to standard out"},
{ 0x0a, IFT_NOSUBFUNC, int21_0a, "buffered input"},
{ 0x0b, IFT_NOSUBFUNC, int21_0b, "get stdin status"},
{ 0x0c, IFT_NOSUBFUNC, int21_0c, "flush stdin and read"},
{ 0x0d, IFT_NOSUBFUNC, int21_NULLFUNC, "disk reset"},
{ 0x0e, IFT_NOSUBFUNC, int21_0e, "select default drive"},
{ 0x19, IFT_NOSUBFUNC, int21_19, "get default drive"},
{ 0x1a, IFT_NOSUBFUNC, int21_1a, "set DTA"},
{ 0x1b, IFT_NOSUBFUNC, int21_free, "get allocation for default drive"},
{ 0x1c, IFT_NOSUBFUNC, int21_free, "get allocation for specific drive"},
{ 0x1f, IFT_NOSUBFUNC, int21_NOFUNC, "get DPB for current drive"},
{ 0x25, IFT_NOSUBFUNC, int21_25, "set interrupt vector"},
{ 0x26, IFT_NOSUBFUNC, int21_26, "create new PSP"},
{ 0x2a, IFT_NOSUBFUNC, int21_2a, "get date"},
{ 0x2b, IFT_NOSUBFUNC, int21_2b, "set date"},
{ 0x2c, IFT_NOSUBFUNC, int21_2c, "get time"},
{ 0x2d, IFT_NOSUBFUNC, int21_2d, "set time"},
{ 0x2e, IFT_NOSUBFUNC, int21_NULLFUNC, "set verify flag"},
{ 0x2f, IFT_NOSUBFUNC, int21_2f, "get DTA"},
{ 0x30, IFT_NOSUBFUNC, int21_30, "get DOS version"},
{ 0x31, IFT_NOSUBFUNC, int21_NOFUNC, "terminate and stay resident"},
{ 0x32, IFT_NOSUBFUNC, int21_NOFUNC, "get DPB for specific drive"},
{ 0x33, 0x05, int21_33_5, "get boot drive"},
{ 0x33, 0x06, int21_33_6, "get true version number"},
{ 0x33, IFT_NOSUBFUNC, int21_33, "extended break checking"},
{ 0x34, IFT_NOSUBFUNC, int21_34, "get address of InDos flag"},
{ 0x35, IFT_NOSUBFUNC, int21_35, "get interrupt vector"},
{ 0x36, IFT_NOSUBFUNC, int21_free, "get disk free space"},
{ 0x37, IFT_NOSUBFUNC, int21_37, "switch character"},
{ 0x38, IFT_NOSUBFUNC, int21_38, "country code/information"},
{ 0x39, IFT_NOSUBFUNC, int21_dirfn, "mkdir"},
{ 0x3a, IFT_NOSUBFUNC, int21_dirfn, "rmdir"},
{ 0x3b, IFT_NOSUBFUNC, int21_3b, "chdir"},
{ 0x3c, IFT_NOSUBFUNC, int21_open, "creat"},
{ 0x3d, IFT_NOSUBFUNC, int21_open, "open"},
{ 0x3e, IFT_NOSUBFUNC, int21_3e, "close"},
{ 0x3f, IFT_NOSUBFUNC, int21_3f, "read"},
{ 0x40, IFT_NOSUBFUNC, int21_40, "write"},
{ 0x41, IFT_NOSUBFUNC, int21_dirfn, "unlink"},
{ 0x42, IFT_NOSUBFUNC, int21_42, "lseek"},
{ 0x43, IFT_NOSUBFUNC, int21_43, "get/set file attributes"},
{ 0x44, 0x00, int21_44_0, "ioctl(get)"},
{ 0x44, 0x01, int21_44_1, "ioctl(set)"},
{ 0x44, 0x08, int21_44_8, "ioctl(test removable)"},
{ 0x44, 0x09, int21_44_9, "ioctl(test remote)"},
{ 0x45, IFT_NOSUBFUNC, int21_45, "dup"},
{ 0x46, IFT_NOSUBFUNC, int21_46, "dup2"},
{ 0x47, IFT_NOSUBFUNC, int21_47, "getwd"},
{ 0x48, IFT_NOSUBFUNC, int21_48, "allocate memory"},
{ 0x49, IFT_NOSUBFUNC, int21_49, "free memory"},
{ 0x4a, IFT_NOSUBFUNC, int21_4a, "resize memory block"},
{ 0x4b, IFT_NOSUBFUNC, int21_4b, "exec"},
{ 0x4c, IFT_NOSUBFUNC, int21_4c, "exit with return code"},
{ 0x4d, IFT_NOSUBFUNC, int21_4d, "get return code from child"},
{ 0x4e, IFT_NOSUBFUNC, int21_find, "findfirst"},
{ 0x4f, IFT_NOSUBFUNC, int21_find, "findnext"},
{ 0x50, IFT_NOSUBFUNC, int21_50, "set psp"},
{ 0x50, IFT_NOSUBFUNC, int21_62, "get psp"},
{ 0x52, IFT_NOSUBFUNC, int21_NOFUNC, "get LoL"},
{ 0x53, IFT_NOSUBFUNC, int21_NOFUNC, "translate BPB to DPB"},
{ 0x54, IFT_NOSUBFUNC, int21_NULLFUNC, "get verify flag"},
{ 0x55, IFT_NOSUBFUNC, int21_NOFUNC, "create PSP"},
{ 0x56, IFT_NOSUBFUNC, int21_dirfn, "rename"},
{ 0x57, 0x00, int21_57_0, "get mtime"},
{ 0x57, 0x01, int21_57_1, "set mtime"},
{ 0x58, IFT_NOSUBFUNC, int21_58, "get/set memory strategy"},
{ 0x59, IFT_NOSUBFUNC, int21_59, "get extended error information"},
{ 0x5a, IFT_NOSUBFUNC, int21_5a, "create temporary file"},
{ 0x5b, IFT_NOSUBFUNC, int21_open, "create new file"},
{ 0x5c, IFT_NOSUBFUNC, int21_NOFUNC, "flock"},
{ 0x5d, IFT_NOSUBFUNC, int21_net, "network functions"},
{ 0x5e, IFT_NOSUBFUNC, int21_net, "network functions"},
{ 0x5f, IFT_NOSUBFUNC, int21_net, "network functions"},
{ 0x60, IFT_NOSUBFUNC, int21_60, "canonicalise name/path"},
{ 0x61, IFT_NOSUBFUNC, int21_NULLFUNC, "network functions (reserved)"},
{ 0x62, IFT_NOSUBFUNC, int21_62, "get current PSP"},
{ 0x63, IFT_NOSUBFUNC, int21_NOFUNC, "get DBCS lead-byte table"},
{ 0x64, IFT_NOSUBFUNC, int21_NOFUNC, "set device-driver lookahead"},
{ 0x65, 0x23, int21_65_23, "determine yes/no"},
{ 0x65, IFT_NOSUBFUNC, int21_NOFUNC, "get extended country information"},
{ 0x66, IFT_NOSUBFUNC, int21_NOFUNC, "get/set codepage table"},
{ 0x67, IFT_NOSUBFUNC, int21_NULLFUNC, "set handle count"},
{ 0x68, IFT_NOSUBFUNC, int21_fflush, "fflush"},
{ 0x69, IFT_NOSUBFUNC, int21_NOFUNC, "get/set disk serial number"},
{ 0x6a, IFT_NOSUBFUNC, int21_fflush, "commit file"},
{ 0x6b, IFT_NOSUBFUNC, int21_NULLFUNC, "IFS ioctl"},
{ 0x6c, IFT_NOSUBFUNC, int21_open, "extended open/create"},
/* FCB functions */
{ 0x0f, IFT_NOSUBFUNC, int21_fcb, "open file"},
{ 0x10, IFT_NOSUBFUNC, int21_fcb, "close file"},
{ 0x11, IFT_NOSUBFUNC, int21_fcb, "find first"},
{ 0x12, IFT_NOSUBFUNC, int21_fcb, "find next"},
{ 0x13, IFT_NOSUBFUNC, int21_NOFUNC, "delete"},
{ 0x14, IFT_NOSUBFUNC, int21_NOFUNC, "sequential read"},
{ 0x15, IFT_NOSUBFUNC, int21_NOFUNC, "sequential write"},
{ 0x16, IFT_NOSUBFUNC, int21_fcb, "create/truncate"},
{ 0x17, IFT_NOSUBFUNC, int21_NOFUNC, "rename"},
{ 0x21, IFT_NOSUBFUNC, int21_NOFUNC, "read random"},
{ 0x22, IFT_NOSUBFUNC, int21_NOFUNC, "write random"},
{ 0x23, IFT_NOSUBFUNC, int21_NOFUNC, "get file size"},
{ 0x24, IFT_NOSUBFUNC, int21_NOFUNC, "set random record number"},
{ 0x27, IFT_NOSUBFUNC, int21_fcb, "random block read"},
{ 0x28, IFT_NOSUBFUNC, int21_fcb, "random block write"},
{ 0x29, IFT_NOSUBFUNC, int21_fcb, "parse filename into FCB"},
/* CPM compactability */
{ 0x18, IFT_NOSUBFUNC, int21_NULLFUNC, "CPM"},
{ 0x1d, IFT_NOSUBFUNC, int21_NULLFUNC, "CPM"},
{ 0x1e, IFT_NOSUBFUNC, int21_NULLFUNC, "CPM"},
{ 0x20, IFT_NOSUBFUNC, int21_NULLFUNC, "CPM"},
{ -1, IFT_NOSUBFUNC, NULL, NULL} /* terminator */
};
static int int21_fastlookup[256];
char *dos_return[] = {
"OK",
"FUNC_NUM_IVALID",
"FILE_NOT_FOUND",
"PATH_NOT_FOUND",
"TOO_MANY_OPEN_FILES",
"ACCESS_DENIED",
"HANDLE_INVALID",
"MEM_CB_DEST",
"INSUF_MEM",
"MEM_BLK_ADDR_IVALID",
"ENV_INVALID",
"FORMAT_INVALID",
"ACCESS_CODE_INVALID",
"DATA_INVALID",
"UNKNOWN_UNIT",
"DISK_DRIVE_INVALID",
"ATT_REM_CUR_DIR",
"NOT_SAME_DEV",
"NO_MORE_FILES",
"WRITE_PROT_DISK",
"UNKNOWN_UNIT_CERR",
"DRIVE_NOT_READY",
"UNKNOWN_COMMAND",
"DATA_ERROR_CRC",
"BAD_REQ_STRUCT_LEN",
"SEEK_ERROR",
"UNKNOWN_MEDIA_TYPE",
"SECTOR_NOT_FOUND",
"PRINTER_OUT_OF_PAPER",
"WRITE_FAULT",
"READ_FAULT",
"GENERAL_FAILURE"
};
const int dos_ret_size = (sizeof(dos_return) / sizeof(char *));
/*
** for want of anywhere better to go
*/
static void
int20(regcontext_t *REGS)
{
/* int 20 = exit(0) */
done(REGS, 0);
}
static void
int29(regcontext_t *REGS)
{
tty_write(R_AL, TTYF_REDIRECT);
}
/******************************************************************************
** entrypoint for MS-DOS functions
*/
static void
int21(regcontext_t *REGS)
{
int error;
int index;
/* look for a handler */
index = intfunc_find(int21_table, int21_fastlookup, R_AH, R_AL);
if (index == -1) { /* no matching functions */
unknown_int2(0x21, R_AL, REGS);
return;
}
/* call the handler */
error = int21_table[index].handler(REGS);
debug(D_DOSCALL, "msdos call %02x (%s) returns %d (%s)\n",
int21_table[index].func, int21_table[index].desc, error,
((error >= 0) && (error <= dos_ret_size)) ? dos_return[error] : "unknown");
if (error) {
doserrno = error;
R_FLAGS |= PSL_C;
/* XXX is this entirely legitimate? */
if (R_AH >= 0x2f)
R_AX = error;
else
R_AX = 0xff;
} else {
R_FLAGS &= ~PSL_C;
}
return;
}
static void
int67(regcontext_t *REGS)
{
ems_entry(REGS);
}
static u_char upcase_trampoline[] = {
0xf4, /* HLT */
0xcb, /* RETF */
};
/*
** initialise thyself
*/
void
dos_init(void)
{
u_long vec;
int hn;
/* hook vectors */
vec = insert_softint_trampoline();
ivec[0x20] = vec;
register_callback(vec, int20, "int 20");
vec = insert_softint_trampoline();
ivec[0x21] = vec;
register_callback(vec, int21, "int 21");
vec = insert_softint_trampoline();
ivec[0x29] = vec;
register_callback(vec, int29, "int 29");
vec = insert_softint_trampoline();
ivec[0x67] = vec;
register_callback(vec, int67, "int 67 (EMS)");
vec = insert_null_trampoline();
ivec[0x28] = vec; /* dos idle */
ivec[0x2b] = vec; /* reserved */
ivec[0x2c] = vec; /* reserved */
ivec[0x2d] = vec; /* reserved */
upcase_vector = insert_generic_trampoline(
sizeof(upcase_trampoline), upcase_trampoline);
register_callback(upcase_vector, upcase_entry, "upcase");
/* build fastlookup index into the monster table of interrupts */
intfunc_init(int21_table, int21_fastlookup);
ems_init();
}