7cdd4f6b5d
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.
2529 lines
50 KiB
C
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(®S->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(®S->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(®S->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(®S->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(®S->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(®S->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();
|
|
}
|