freebsd-dev/usr.bin/doscmd/dos.c
2002-03-07 12:52:27 +00:00

2577 lines
51 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
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <glob.h>
#include <paths.h>
#include <stddef.h>
#include <time.h>
#include <unistd.h>
#include "doscmd.h"
#include "cwd.h"
#include "dispatch.h"
#include "tty.h"
/* 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 */
int diskdrive = 2; /* C: */
char *InDOS;
/* locals */
static void fcb_to_string(struct fcb *, u_char *);
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_long upcase_vector;
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)
{
R_AL = upcase(R_AL);
}
/*
** Handle the DOS drive info/free space/etc. calls.
*/
static int
int21_free(regcontext_t *REGS)
{
fsstat_t fs;
int error;
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)
{
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, _PATH_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, _PATH_DEVNULL);
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[(int)(x)] & 0x01) != 0)
#define issep(x) ((magic[(int)(x)] & 0x02) == 0)
#define iswhite(x) ((magic[(int)(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);
/* keep `gcc -Wall' happy */
return(0);
}
/*
** 21:01
**
** read character with echo
*/
static int
int21_01(regcontext_t *REGS)
{
int n;
if ((n = tty_read((regcontext_t *)&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((regcontext_t *)&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((regcontext_t *)&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((regcontext_t *)&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 *)MAKEPTR(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 *)MAKEPTR(R_DS, R_DX);
/* 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((regcontext_t *)&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, 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 = MAKEVEC(R_DS, R_DX);
return(0);
}
/*
** 21:23
**
** Get file size for fcb
** DS:DX -> unopened FCB, no wildcards.
** pcb random record field filled in with number of records, rounded up.
*/
static int
int21_23(regcontext_t *REGS)
{
debug(D_HALF, "Returning failure from get file size for fcb 21:23\n");
R_AL = 0xff;
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] = MAKEVEC(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)
{
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)
{
PUTVEC(R_ES, R_BX, (u_long)InDOS);
return(0);
}
/*
** 21:35
**
** get interrupt vector
*/
static int
int21_35(regcontext_t *REGS)
{
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 *)MAKEPTR(R_DS, R_DX);
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 *)MAKEPTR(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 *)MAKEPTR(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 *)MAKEPTR(R_DS, R_DX));
return(dos_setcwd((u_char *)MAKEPTR(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 *)MAKEPTR(R_DS, R_DX);
action = 0x12; /* create/truncate regardless */
mode = O_RDWR;
debug(D_FILE_OPS, "creat");
break;
case 0x3d: /* open */
pname = (char *)MAKEPTR(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 *)MAKEPTR(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 *)MAKEPTR(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)
{
char *addr;
int n;
int avail;
addr = (char *)MAKEPTR(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
write_or_truncate(int fd, char *addr, int len)
{
off_t offset;
if (len == 0) {
offset = lseek(fd, 0, SEEK_CUR);
if (offset < 0)
return -1;
else
return ftruncate(fd, offset);
} else {
return write(fd, addr, len);
}
}
static int
int21_40(regcontext_t *REGS)
{
char *addr;
int nbytes,n;
addr = (char *)MAKEPTR(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_or_truncate(R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
case 1:
if (redirect1) {
n = write_or_truncate(R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
case 2:
if (redirect2) {
n = write_or_truncate(R_BX, addr, nbytes);
break;
}
n = nbytes;
while (nbytes-- > 0)
tty_write(*addr++, -1);
break;
default:
n = write_or_truncate(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 *)MAKEPTR(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:7
**
** Get output status
*/
static int
int21_44_7(regcontext_t *REGS)
{
/* XXX Should really check to see if BX is open or not */
R_AX = 0xFF;
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 */
return (0);
}
/*
** 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 *)MAKEPTR(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;
u_short *param;
debug(D_EXEC, "exec(%s)\n",(u_char *)MAKEPTR(R_DS, R_DX));
if ((fd = open_prog((u_char *)MAKEPTR(R_DS, R_DX))) < 0) {
debug(D_EXEC, "%s: command not found\n",
(u_char *)MAKEPTR(R_DS, R_DX));
return (FILE_NOT_FOUND);
}
/* child */
param = (u_short *)MAKEPTR(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);
return 0;
}
/*
** 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 *)MAKEPTR(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, mtime;
if (fstat(R_BX, &sb) < 0)
return (HANDLE_INVALID);
encode_dos_file_time(sb.st_mtime, &date, &mtime);
R_CX = mtime;
R_DX = date;
return(0);
}
/*
** 21:57:01
**
** set mtime for handle
*/
static int
int21_57_1(regcontext_t *REGS __unused)
{
#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 *)MAKEPTR(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 *)MAKEPTR(R_DS, R_SI),
(char *)MAKEPTR(R_ES, R_DI)));
}
/*
** 21:62
**
** get current PSP
*/
static int
int21_62(regcontext_t *REGS)
{
R_BX = pspseg;
return(0);
}
/*
** 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;
return(0);
}
static 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 *)MAKEPTR(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 *)MAKEPTR(R_DS, R_SI),
(char *)MAKEPTR(R_ES, R_DI),
&nbytes);
debug(D_FILE_OPS, "%d %s, FCB: %d, %.11s\n",
nbytes,
(char *)MAKEPTR(R_DS, R_SI),
*(int *)MAKEPTR(R_ES, R_DI),
(char *)MAKEPTR(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 ? "separate 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"},
{ 0x23, IFT_NOSUBFUNC, int21_23, "Get file size (old)"},
{ 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, 0x07, int21_44_7, "ioctl(Check output status)"},
{ 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"},
{ 0x51, 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];
const 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 idx;
/* look for a handler */
idx = intfunc_find(int21_table, int21_fastlookup, R_AH, R_AL);
if (idx == -1) { /* no matching functions */
unknown_int3(0x21, R_AH, R_AL, REGS);
R_FLAGS |= PSL_C; /* Flag an error */
R_AX = 0xff;
return;
}
/* call the handler */
error = int21_table[idx].handler(REGS);
debug(D_DOSCALL, "msdos call %02x (%s) returns %d (%s)\n",
int21_table[idx].func, int21_table[idx].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;
/* 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();
}