/* * 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 * * $FreeBSD: src/usr.bin/doscmd/dos.c,v 1.7.2.5 2002/04/25 11:04:50 tg Exp $ * $DragonFly: src/usr.bin/doscmd/dos.c,v 1.3 2008/10/16 01:52:32 swildner Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #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 = 0; /* 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 *)®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((regcontext_t *)®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((regcontext_t *)®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((regcontext_t *)®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 *)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 *)®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(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 = 0, action = 0, status; char *pname = NULL; 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 = 0; 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(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(); }