Merge branch 'vendor/OPENSSL'
[dragonfly.git] / usr.bin / doscmd / dos.c
1 /*
2  * Copyright (c) 1996
3  *      Michael Smith.  All rights reserved.
4  * Copyright (c) 1992, 1993, 1996
5  *      Berkeley Software Design, Inc.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed by Berkeley Software
18  *      Design, Inc.
19  *
20  * THIS SOFTWARE IS PROVIDED BY Berkeley Software Design, Inc. ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL Berkeley Software Design, Inc. BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  *      BSDI int21.c,v 2.2 1996/04/08 19:32:51 bostic Exp
33  *
34  * $FreeBSD: src/usr.bin/doscmd/dos.c,v 1.7.2.5 2002/04/25 11:04:50 tg Exp $
35  * $DragonFly: src/usr.bin/doscmd/dos.c,v 1.3 2008/10/16 01:52:32 swildner Exp $
36  */
37
38 #include <sys/ioctl.h>
39 #include <sys/param.h>
40 #include <sys/mount.h>
41 #include <ctype.h>
42 #include <dirent.h>
43 #include <errno.h>
44 #include <glob.h>
45 #include <paths.h>
46 #include <stddef.h>
47 #include <time.h>
48 #include <unistd.h>
49
50 #include "doscmd.h"
51 #include "cwd.h"
52 #include "dispatch.h"
53 #include "tty.h"
54
55 /* Country Info */
56 struct {
57         ushort  ciDateFormat;
58         char    ciCurrency[5];
59         char    ciThousands[2];
60         char    ciDecimal[2];
61         char    ciDateSep[2];
62         char    ciTimeSep[2];
63         char    ciCurrencyFormat;
64         char    ciCurrencyPlaces;
65         char    ciTimeFormat;
66         ushort  ciCaseMapOffset;
67         ushort  ciCaseMapSegment;
68         char    ciDataSep[2];
69 #if 0
70         char    ciReserved[10];
71 #endif
72 } countryinfo = {
73         0, "$", ",", ".", "-", ":", 0, 2, 0, 0xffff, 0xffff, "?"
74 };
75
76 /* DOS File Control Block */
77 struct fcb {
78         u_char  fcbMagic;
79         u_char  fcbResoived[5];
80         u_char  fcbAttribute;
81         u_char  fcbDriveID;
82         u_char  fcbFileName[8];
83         u_char  fcbExtent[3];
84         u_short fcbCurBlockNo;
85         u_short fcbRecSize;
86         u_long  fcbFileSize;
87         u_short fcbFileDate;     
88         u_short fcbFileTime;
89         int     fcbReserved;
90         int     fcb_fd;                 /* hide UNIX FD here */
91         u_char  fcbCurRecNo; 
92         u_long  fcbRandomRecNo;
93 }/* __attribute__((__packed__))*/;
94
95 /* exports */
96 int             diskdrive = 2;  /* C: */
97 char            *InDOS;
98
99 /* locals */
100 static void     fcb_to_string(struct fcb *, u_char *);
101
102 static int      ctrl_c_flag = 0;
103 static int      return_status = 0;
104 static int      doserrno = 0;
105 static int      memory_strategy = 0;    /* first fit (we ignore this) */
106 static u_long   upcase_vector;
107
108 static u_char upc_table[0x80] = {
109         0x80, 0x9a, 'E',  'A',  0x8e, 'A',  0x8f, 0x80,
110         'E',  'E',  'E',  'I',  'I',  'I',  0x8e, 0x8f,
111         0x90, 0x92, 0x92, 'O',  0x99, 'O',  'U',  'U', 
112         'Y',  0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
113         'A',  'I',  'O',  'U',  0xa5, 0xa5, 0xa6, 0xa7,
114         0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
115         0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
116         0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
117         0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
118         0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
119         0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
120         0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
121         0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
122         0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
123         0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
124         0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
125 };
126
127
128 /******************************************************************************
129 ** utility functions
130 */
131
132 static u_char
133 upcase(u_char c)
134 {
135
136     if (islower(c))
137         return (toupper(c));
138     else if (c >= 0x80)
139         return (upc_table[c - 0x80]);
140     else
141         return (c);
142 }
143
144 static void
145 upcase_entry(regcontext_t *REGS)
146 {
147     R_AL = upcase(R_AL);
148 }
149
150
151 /*
152 ** Handle the DOS drive info/free space/etc. calls.
153 */
154 static int
155 int21_free(regcontext_t *REGS)
156 {
157     fsstat_t            fs;
158     int                 error;
159     int                 drive = 0;
160
161     /* work out drive */
162     switch (R_AH) {
163     case 0x1c:
164     case 0x36:
165         drive = R_DL;
166         if (drive)
167             break;
168         /* FALLTHROUGH */
169     case 0x1b:
170         drive = diskdrive;
171         break;
172     default:
173         fatal("int21_free called on unknown function %x\n",R_AH);
174     }
175     
176     error = get_space(drive, &fs);
177     if (error)
178         return(error);
179
180     R_AL = fs.sectors_cluster;                  /* sectors per cluster */
181     R_CX = fs.bytes_sector;                     /* bytes per sector */
182     R_DX = fs.total_clusters;                   /* total clusters */
183
184     switch (R_AH) {
185     case 0x1b:
186     case 0x1c:
187         BIOSDATA[0xb4] = 0xf0;                  /* "reserved" area, "other media" FAT ID */
188         R_DX = 0x40;                            /* BIOS data area */
189         R_BX = 0xb4;
190         break;
191         
192     case 0x36:
193         R_BX = fs.avail_clusters;               /* number of available clusters */
194         break;
195     }
196     return(0);
197 }
198
199 static void
200 pack_name(u_char *p, u_char *q)
201 {
202     int i;
203
204     for (i = 8; i > 0 && *p != ' '; i--)
205         *q++ = *p++;
206     p += i;
207     if (*p != ' ') {
208         *q++ = '.';
209         for (i = 3; i > 0 && *p != ' '; i--)
210             *q++ = *p++;
211         p += i;
212     }
213     *q = '\0';
214 }
215
216 static void
217 dosdir_to_dta(dosdir_t *dosdir, find_block_t *dta)
218 {
219     dta->attr = dosdir->attr;
220     dta->time = dosdir->time;
221     dta->date = dosdir->date;
222     dta->size = dosdir->size;
223     pack_name(dosdir->name, dta->name);
224 }
225
226 /* exported */
227 void
228 encode_dos_file_time(time_t t, u_short *dosdatep, u_short *dostimep)
229 {
230     struct tm tm;
231     
232     tm = *localtime(&t);
233     *dostimep = (tm.tm_hour << 11) |
234         (tm.tm_min << 5) |
235         ((tm.tm_sec / 2) << 0);
236     *dosdatep = ((tm.tm_year - 80) << 9) |
237         ((tm.tm_mon + 1) << 5) |
238         (tm.tm_mday << 0);
239 }
240
241 time_t
242 decode_dos_file_time(u_short dosdate, u_short dostime)
243 {
244     struct tm tm;
245     time_t then;
246
247     tm.tm_hour = (dostime >> 11) & 0x1f;
248     tm.tm_min  = (dostime >> 5) & 0x3f;
249     tm.tm_sec  = ((dostime >> 0) & 0x1f) * 2;
250     tm.tm_year = ((dosdate >> 9) & 0x7f) + 80;
251     tm.tm_mon  = ((dosdate >> 5) & 0x0f) - 1;
252     tm.tm_mday = (dosdate >> 0) & 0x1f;
253     /* tm_wday and tm_yday are ignored. */
254     tm.tm_isdst = 0;
255     /* tm_gmtoff is ignored. */
256     then = mktime(&tm);
257     return (then);
258 }
259
260 int
261 translate_filename(u_char *dname, u_char *uname, int *drivep)
262 {
263     u_char newpath[1024];
264     int error;
265
266     if (!strcasecmp(dname, "con")) {
267         *drivep = -1;
268         strcpy(uname, _PATH_TTY);
269         return (0);
270     }
271
272     /* XXX KLUDGE for EMS support w/o booting DOS */
273     /* Really need a better way to handle devices */
274     if (!strcasecmp(dname, "emmxxxx0")) {
275         *drivep = -1;
276         strcpy(uname, _PATH_DEVNULL);
277         return (0);
278     }
279
280     error = dos_makepath(dname, newpath);
281     if (error)
282         return (error);
283
284     error = dos_to_real_path(newpath, uname, drivep);
285     if (error)
286         return (error);
287
288     return (0);
289 }
290
291 static u_char magic[0x7e] = {
292         0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
293         0x06, 0x00, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
294         0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
295         0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
296
297         0x08, 0x0f, 0x06, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
298         0x0f, 0x0f, 0x0f, 0x04, 0x04, 0x0f, 0x0e, 0x06,
299         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
300         0x0f, 0x0f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0f,
301
302         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
303         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
304         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
305         0x0f, 0x0f, 0x0f, 0x06, 0x06, 0x06, 0x0f, 0x0f,
306
307         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
308         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
309         0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f,
310         0x0f, 0x0f, 0x0f, 0x0f, 0x04, 0x0f,
311 };
312
313 #define isvalid(x)      ((magic[(int)(x)] & 0x01) != 0)
314 #define issep(x)        ((magic[(int)(x)] & 0x02) == 0)
315 #define iswhite(x)      ((magic[(int)(x)] & 0x04) == 0)
316
317 static char *
318 skipwhite(char *p)
319 {
320         while (iswhite(*p))
321                 ++p;
322         return (p);
323 }
324
325 #define get_drive_letter(x)     ((x) - 0x40)
326
327 int
328 parse_filename(int flag, char *str, char *fcb, int *nb)
329 {
330     char *p;
331     int ret = 0;
332     int i;
333
334     p = str;
335
336     p = skipwhite(p);
337     if (flag & 1) {
338         if (issep(*p)) {
339             ++p;
340             p = skipwhite(p);
341         }
342     }
343
344     if (isvalid(*p) && p[1] == ':') {
345         *fcb++ = get_drive_letter(upcase(*p));
346         p += 2;
347     } else if (flag & 2) {
348         fcb++;
349     } else {
350         *fcb++ = 0;     /* default drive */
351     }
352
353     i = 8;
354     if (isvalid(*p)) {
355         for (;;) {
356             if (!isvalid(*p)) {
357                 for (; i > 0; i--)
358                     *fcb++ = ' ';
359                 break;
360             }
361             if (i > 0) {
362                 switch (*p) {
363                 case '*':
364                     ret = 1;
365                     for (; i > 0; i--)
366                         *fcb++ = '?';
367                     break;
368                 case '?':
369                     ret = 1;
370                 default:
371                     *fcb++ = upcase(*p);
372                     i--;
373                     break;
374                 }
375             }
376             ++p;
377         }
378     } else if (flag & 4) {
379         fcb += i;
380     } else {
381         for (; i > 0; i--)
382             *fcb++ = ' ';
383     }
384
385     i = 3;
386     if (*p == '.') {
387         ++p;
388         for (;;) {
389             if (!isvalid(*p)) {
390                 for (; i > 0; i--)
391                     *fcb++ = ' ';
392                 break;
393             }
394             if (i > 0) {
395                 switch (*p) {
396                 case '*':
397                     ret = 1;
398                     for (; i > 0; i--)
399                         *fcb++ = '?';
400                     break;
401                 case '?':
402                     ret = 1;
403                 default:
404                     *fcb++ = upcase(*p);
405                     i--;
406                     break;
407                 }
408             }
409             ++p;
410         }
411     } else if (flag & 8) {
412         fcb += i;
413     } else {
414         for (; i > 0; i--)
415             *fcb++ = ' ';
416     }
417
418     for (i = 4; i > 0; i--)
419         *fcb++ = 0;     /* filler */
420
421     *nb = p - str;
422     return (ret);
423 }
424
425 /******************************************************************************
426 ** int21 functions
427 */
428
429 /*
430 ** 21:00
431 ** 
432 ** terminate
433 */
434 static int
435 int21_00(regcontext_t *REGS)
436 {
437     done(REGS,0);
438     /* keep `gcc -Wall' happy */
439     return(0);
440 }
441
442 /*
443 ** 21:01
444 **
445 ** read character with echo
446 */
447 static int
448 int21_01(regcontext_t *REGS)
449 {
450     int         n;
451     
452     if ((n = tty_read((regcontext_t *)&REGS->sc, TTYF_BLOCKALL)) >= 0)
453         R_AL = n;
454     return(0);
455 }
456
457 /*
458 ** 21:02
459 **
460 ** write char to stdout
461 */
462 static int
463 int21_02(regcontext_t *REGS)
464 {
465     tty_write(R_DL, TTYF_REDIRECT);
466     return(0);
467 }
468
469 /*
470 ** 21:06
471 **
472 ** direct console I/O
473 **
474 ** (dl) is output char unless 0xff, when we read instead
475 */
476 static int
477 int21_06(regcontext_t *REGS)
478 {
479     int         n;
480     
481     /* XXX - should be able to read a file */
482     if (R_DL == 0xff) {
483         n = tty_read((regcontext_t *)&REGS->sc, TTYF_ECHO|TTYF_REDIRECT);
484         if (n < 0) {
485             R_FLAGS |= PSL_Z;           /* nothing available */
486             R_AL = 0;
487         } else {
488             R_AL = n;                   /* got character */
489             R_FLAGS &= ~PSL_Z;
490         }
491     } else {
492         /* write and return char in %al */
493         tty_write(R_DL, TTYF_REDIRECT);
494         R_AL = R_DL;
495     }
496     return(0);
497 }
498
499 /*
500 ** 21:07
501 **
502 ** direct console input with no echo
503 */
504 static int
505 int21_07(regcontext_t *REGS)
506 {
507     R_AL = tty_read((regcontext_t *)&REGS->sc,
508                     TTYF_BLOCK|TTYF_REDIRECT) & 0xff;
509     return(0);
510 }
511
512 /*
513 ** 21:08
514 **
515 ** character input with no echo
516 */
517 static int
518 int21_08(regcontext_t *REGS)
519 {
520     int         n;
521     
522     if ((n = tty_read((regcontext_t *)&REGS->sc,
523                       TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT)) >= 0)
524         R_AL = n;
525     return(0);
526 }
527
528 /*
529 ** 21:09
530 **
531 ** write string to standard out.
532 **
533 ** We're a little paranoid here; if the string is very long, truncate it.
534 */
535 static int
536 int21_09(regcontext_t *REGS)
537 {
538     char        *addr;
539     int         len;
540     
541     /* pointer to string */
542     addr = (char *)MAKEPTR(R_DS, R_DX);
543
544     /* walk string looking for terminator or overlength */
545     for (len = 0; len < 10000; len++, addr++) {
546         if (*addr == '$')
547             break;
548         tty_write(*addr, TTYF_REDIRECT);
549     }
550     R_AL = 0x24;
551     return(0);
552 }
553
554 /*
555 ** 21:0a
556 **
557 ** buffered input
558 */
559 static int
560 int21_0a(regcontext_t *REGS)
561 {
562     unsigned char       *addr;
563     int                 nbytes,avail;
564     int                 n;
565     
566     /* pointer to buffer */
567     addr = (unsigned char *)MAKEPTR(R_DS, R_DX);
568
569     /* capacity of buffer */
570     avail = addr[0];
571     if (avail == 0)             /* no space */
572         return(0);
573     nbytes = 0;                 /* read nothing yet */
574
575     /* read loop */
576     while (1) {
577         n = tty_read((regcontext_t *)&REGS->sc,
578                      TTYF_BLOCK|TTYF_CTRL|TTYF_REDIRECT);
579         if (n < 0)                      /* end of input */
580             n = '\r';                   /* make like CR */
581         
582         switch (n) {
583         case '\r':                      /* done */
584             addr[1] = nbytes;
585             addr[nbytes+2] = '\r';
586             addr[nbytes+3] = '\0';      /* XXX is this necessary? */
587             return (0);
588         case '\n':                      /* ignore */
589         case '\0':
590             break;
591         case '\b':                      /* backspace */
592             if (nbytes > 0) {
593                 --nbytes;
594                 tty_write('\b', TTYF_REDIRECT);
595                 if (addr[nbytes+2] < ' ')
596                     tty_write('\b', TTYF_REDIRECT);
597             }   
598             break;
599         default:
600             if (nbytes >= (avail-2)) {  /* buffer full */
601                 tty_write('\007', TTYF_REDIRECT);
602             } else {                    /* add to end */
603                 addr[(nbytes++) +2] = n;
604                 if (n != '\t' && n < ' ') {
605                     tty_write('^', TTYF_REDIRECT);
606                     tty_write(n + '@', TTYF_REDIRECT);
607                 } else
608                     tty_write(n, TTYF_REDIRECT);
609             }
610             break;
611         }
612     }
613 }
614
615 /*
616 ** 21:0b
617 **
618 ** get stdin status
619 **
620 ** This is a favorite for camping on, so we do some poll-counting
621 ** here as well.
622 */
623 static int
624 int21_0b(regcontext_t *REGS)
625 {
626     int         n;
627     
628     /* XXX this is pretty bogus, actually */
629     if (!xmode) {
630         R_AL = 0xff;            /* no X mode, always claim data available */
631         return(0);
632     }
633     /* XXX tty_peek is broken */
634     n = tty_peek(REGS, poll_cnt ? 0 : TTYF_POLL) ? 0xff : 0;
635     if (n < 0)                  /* control-break */
636         return (0);
637     R_AL = n;                   /* will be 0 or 0xff */
638     if (poll_cnt)
639         --poll_cnt;
640     return(0);
641 }
642
643 /*
644 ** 21:0c
645 **
646 ** flush stdin and read using other function
647 */
648 static int
649 int21_0c(regcontext_t *REGS)
650 {
651     if (xmode)                  /* XXX should always flush! */
652         tty_flush();
653     
654     switch(R_AL) {              /* which subfunction? */
655     case 0x01:
656         return(int21_01(REGS));
657     case 0x06:
658         return(int21_06(REGS));
659     case 0x07:
660         return(int21_07(REGS));
661     case 0x08:
662         return(int21_08(REGS));
663     case 0x0a:
664         return(int21_0a(REGS));
665     }
666     return(0);
667 }
668
669 /*
670 ** 21:0e
671 **
672 ** select default drive
673 */
674 static int
675 int21_0e(regcontext_t *REGS)
676 {
677     diskdrive = R_DL;           /* XXX rangecheck? */
678     R_AL = ndisks + 2;          /* report actual limit */
679     return(0);
680 }
681
682 /*
683 ** 21:19
684 **
685 ** get default drive
686 */
687 static int
688 int21_19(regcontext_t *REGS)
689 {
690     R_AL = diskdrive;
691     return(0);
692 }
693
694 /*
695 ** 21:1a
696 **
697 ** set DTA
698 */
699 static int
700 int21_1a(regcontext_t *REGS)
701 {
702     debug(D_FILE_OPS, "set dta to %x:%x\n", R_DS, R_DX);
703     disk_transfer_addr = MAKEVEC(R_DS, R_DX);
704     return(0);
705 }
706
707 /*
708 ** 21:23
709 **
710 ** Get file size for fcb
711 ** DS:DX -> unopened FCB, no wildcards.
712 ** pcb random record field filled in with number of records, rounded up.
713 */
714 static int
715 int21_23(regcontext_t *REGS)
716 {
717     debug(D_HALF, "Returning failure from get file size for fcb 21:23\n");
718     R_AL = 0xff;
719     return(0);
720 }
721
722 /*
723 ** 21:25
724 **
725 ** set interrupt vector
726 */
727 static int
728 int21_25(regcontext_t *REGS)
729 {
730     debug(D_MEMORY, "%02x -> %04x:%04x\n", R_AL, R_DS, R_DX);
731     ivec[R_AL] = MAKEVEC(R_DS, R_DX);
732     return(0);
733 }
734
735 /*
736 ** 21:26
737 **
738 ** Create PSP
739 */
740 static int
741 int21_26(regcontext_t *REGS)
742 {
743     unsigned char       *addr;
744
745     /* address of new PSP */
746     addr = (unsigned char *)MAKEPTR(R_DX, 0);
747
748     /* copy this process' PSP - XXX needs some work 8( */
749     memcpy (addr, dosmem, 256);
750     return(0);
751 }
752
753 /*
754 ** 21:2a
755 **
756 ** Get date
757 */
758 static int
759 int21_2a(regcontext_t *REGS)
760 {
761     struct timeval      tv;
762     struct timezone     tz;
763     struct tm           tm;
764     time_t              now;
765     
766     gettimeofday(&tv, &tz);     /* get time and apply DOS offset */
767     now = tv.tv_sec + delta_clock;
768     
769     tm = *localtime(&now);      /* deconstruct and timezoneify */
770     R_CX = tm.tm_year + 1900;
771     R_DH =  tm.tm_mon + 1;
772     R_DL = tm.tm_mday;
773     R_AL = tm.tm_wday;
774     return(0);
775 }
776
777 /*
778 ** 21:2b
779 **
780 ** set date
781 */
782 static int
783 int21_2b(regcontext_t *REGS)
784 {
785     struct timeval      tv;
786     struct timezone     tz;
787     struct tm           tm;
788     time_t              now;
789     
790     gettimeofday(&tv, &tz);     /* get time and apply DOS offset */
791     now = tv.tv_sec + delta_clock;
792     tm = *localtime(&now);
793
794     tm.tm_year = R_CX - 1900;
795     tm.tm_mon = R_DH - 1;
796     tm.tm_mday = R_DL;
797     tm.tm_wday = R_AL;
798     
799     now = mktime(&tm);
800     if (now == -1)
801         return (DATA_INVALID);
802
803     delta_clock = now - tv.tv_sec;      /* compute new offset? */
804     R_AL = 0;
805     return(0);
806 }
807
808 /*
809 ** 21:2c
810 **
811 ** Get time
812 */
813 static int
814 int21_2c(regcontext_t *REGS)
815 {
816     struct timeval      tv;
817     struct timezone     tz;
818     struct tm           tm;
819     time_t              now;
820
821     gettimeofday(&tv, &tz);
822     now = tv.tv_sec + delta_clock;
823     tm = *localtime(&now);
824
825     R_CH = tm.tm_hour;
826     R_CL = tm.tm_min;
827     R_DH = tm.tm_sec;
828     R_DL = tv.tv_usec / 10000;
829     return(0);
830 }
831
832 /*
833 ** 21:2d
834 **
835 ** Set time
836 */
837 static int
838 int21_2d(regcontext_t *REGS)
839 {
840     struct timeval      tv;
841     struct timezone     tz;
842     struct tm           tm;
843     time_t              now;
844
845     gettimeofday(&tv, &tz);
846     now = tv.tv_sec + delta_clock;
847     tm = *localtime(&now);
848
849     tm.tm_hour = R_CH;
850     tm.tm_min = R_CL;
851     tm.tm_sec = R_DH;
852     tv.tv_usec = R_DL * 10000;
853
854     now = mktime(&tm);
855     if (now == -1)
856         return (DATA_INVALID);
857     
858     delta_clock = now - tv.tv_sec;
859     R_AL = 0;
860     return(0);
861 }
862
863 /*
864 ** 21:2f
865 **
866 ** get DTA
867 */
868 static int
869 int21_2f(regcontext_t *REGS)
870 {
871     PUTVEC(R_ES, R_BX, disk_transfer_addr);
872     debug(D_FILE_OPS, "get dta at %x:%x\n", R_ES, R_BX);
873     return(0);
874 }
875
876 /*
877 ** 21:30
878 **
879 ** get DOS version number.
880 **
881 ** XXX begging for a rewrite
882 */
883 static int
884 int21_30(regcontext_t *REGS)
885 {
886     int         v;
887     
888     char *cmd = (char *)MAKEPTR(get_env(), 0);
889
890     /* 
891      * retch.  I think this skips the environment and looks for the name
892      * of the current command.
893      */
894     do {
895         while (*cmd)
896             ++cmd;
897     } while (*++cmd);
898     ++cmd;
899     cmd += *(short *)cmd + 1;
900     while (cmd[-1] && cmd[-1] != '\\' && cmd[-1] != ':')
901         --cmd;
902
903     /* get the version we're pretending to be for this sucker */
904     v = getver(cmd);
905     R_AL = (v / 100) & 0xff;
906     R_AH = v % 100;
907     return(0);
908 }
909
910 /*
911 ** 21:33:05
912 **
913 ** Get boot drive
914 */
915 static int
916 int21_33_5(regcontext_t *REGS)
917 {
918     R_DL = 3;                   /* always booted from C */
919     return(0);
920 }
921
922 /*
923 ** 21:33:06
924 **
925 ** get true DOS version
926 */
927 static int
928 int21_33_6(regcontext_t *REGS)
929 {
930     int         v;
931     
932     v = getver(NULL);
933     R_BL = (v / 100) & 0xff;
934     R_BH = v % 100;
935     R_DH = 0;
936     R_DL = 0;
937     return(0);
938 }
939
940 /*
941 ** 21:33
942 **
943 ** extended break checking
944 */
945 static int
946 int21_33(regcontext_t *REGS)
947 {
948     int         ftemp;
949     
950     switch (R_AL) {
951     case 0x00:
952         R_DL = ctrl_c_flag;
953         break;
954     case 0x01:
955         ctrl_c_flag = R_DL;
956         break;
957     case 0x02:
958         ftemp = ctrl_c_flag;
959         ctrl_c_flag = R_DL;
960         R_DL = ftemp;
961         break;
962         
963     default:
964         unknown_int3(0x21, 0x33, R_AL, REGS);
965         return(FUNC_NUM_IVALID);
966     }
967     return(0);
968 }
969
970 /*
971 ** 21:34
972 **
973 ** Get address of InDos flag
974 **
975 ** XXX check interrupt list WRT location of critical error flag too.
976 */
977 static int
978 int21_34(regcontext_t *REGS)
979 {
980     PUTVEC(R_ES, R_BX, (u_long)InDOS);
981     return(0);
982 }
983
984 /*
985 ** 21:35
986 **
987 ** get interrupt vector
988 */
989 static int
990 int21_35(regcontext_t *REGS)
991 {
992     PUTVEC(R_ES, R_BX, ivec[R_AL]);
993     debug(D_MEMORY, "%02x <- %04x:%04x\n", R_AL, R_ES, R_BX);
994     return(0);
995 }
996
997 /*
998 ** 21:37
999 **
1000 ** switch character manipulation
1001 **
1002 */
1003 static int
1004 int21_37(regcontext_t *REGS)
1005 {
1006     switch (R_AL) {
1007     case 0: /* get switch character */
1008         R_DL = '/';
1009         break;
1010         
1011     case 1: /* set switch character (normally /) */
1012         /* ignored by most versions of DOS */
1013         break;
1014     default:
1015         unknown_int3(0x21, 0x37, R_AL, REGS);
1016         return (FUNC_NUM_IVALID);
1017     }
1018     return(0);
1019     
1020 }
1021
1022 /*
1023 ** 21:38
1024 **
1025 ** country code information
1026 **
1027 ** XXX internat guru?
1028 */
1029 static int
1030 int21_38(regcontext_t *REGS)
1031 {
1032     char        *addr;
1033     
1034     if (R_DX == 0xffff) {
1035         debug(D_HALF, "warning: set country code ignored");
1036         return(0);
1037     }
1038     addr = (char *)MAKEPTR(R_DS, R_DX);
1039     PUTVEC(countryinfo.ciCaseMapSegment, countryinfo.ciCaseMapOffset,
1040            upcase_vector);
1041     memcpy(addr, &countryinfo, sizeof(countryinfo));
1042     return(0);
1043 }
1044
1045 /*
1046 ** 21:39
1047 ** 21:3a
1048 ** 21:41
1049 ** 21:56
1050 **
1051 ** mkdir, rmdir, unlink, rename
1052 */
1053 static int
1054 int21_dirfn(regcontext_t *REGS)
1055 {
1056     int         error;
1057     char        fname[PATH_MAX],tname[PATH_MAX];
1058     int         drive;
1059
1060     error = translate_filename((u_char *)MAKEPTR(R_DS, R_DX), fname, &drive);
1061     if (error)
1062         return (error);
1063
1064     if (dos_readonly(drive))
1065         return (WRITE_PROT_DISK);
1066
1067     switch(R_AH) {
1068     case 0x39:
1069         debug(D_FILE_OPS, "mkdir(%s)\n", fname);
1070         error = mkdir(fname, 0777);
1071         break;
1072     case 0x3a:
1073         debug(D_FILE_OPS, "rmdir(%s)\n", fname);
1074         error = rmdir(fname);
1075         break;
1076     case 0x41:
1077         debug(D_FILE_OPS, "unlink(%s)\n", fname);
1078         error = unlink(fname);
1079         break;
1080     case 0x56:          /* rename - some extra work */
1081         error = translate_filename((u_char *)MAKEPTR(R_ES, R_DI), tname, &drive);
1082         if (error)
1083             return (error);
1084
1085         debug(D_FILE_OPS, "rename(%s, %s)\n", fname, tname);
1086         error = rename(fname, tname);
1087         break;
1088
1089     default:
1090         fatal("call to int21_dirfn for unknown function %x\n",R_AH);
1091     }
1092     if (error < 0) {
1093         switch (errno) {
1094         case ENOTDIR:
1095         case ENOENT:
1096             return (PATH_NOT_FOUND);
1097         case EXDEV:
1098             return (NOT_SAME_DEV);
1099         default:
1100             return (ACCESS_DENIED);
1101         }
1102     }
1103     return(0);
1104 }
1105
1106 /*
1107 ** 21:3b
1108 **
1109 ** chdir
1110 */
1111 static int
1112 int21_3b(regcontext_t *REGS)
1113 {
1114     debug(D_FILE_OPS, "chdir(%s)\n",(u_char *)MAKEPTR(R_DS, R_DX));
1115     return(dos_setcwd((u_char *)MAKEPTR(R_DS, R_DX)));
1116 }
1117
1118 /*
1119 ** 21:3c
1120 ** 21:5b
1121 ** 21:6c
1122 **
1123 ** open, creat, creat new, multipurpose creat
1124 */
1125 static int
1126 int21_open(regcontext_t *REGS)
1127 {
1128     int         error;
1129     char        fname[PATH_MAX];
1130     struct stat sb;
1131     int         mode = 0, action = 0, status;
1132     char        *pname = NULL;
1133     int         drive;
1134     int         fd;
1135     
1136     switch(R_AH) {
1137     case 0x3c:                  /* creat */
1138         pname = (char *)MAKEPTR(R_DS, R_DX);
1139         action = 0x12;          /* create/truncate regardless */
1140         mode = O_RDWR;
1141         debug(D_FILE_OPS, "creat");
1142         break;
1143
1144     case 0x3d:                  /* open */
1145         pname = (char *)MAKEPTR(R_DS, R_DX);
1146         action = 0x01;          /* fail if not exist, open if exists */
1147         switch (R_AL & 3) {
1148         case 0:
1149             mode = O_RDONLY;
1150             break;
1151         case 1:
1152             mode = O_WRONLY;
1153             break;
1154         case 2:
1155             mode = O_RDWR;
1156             break;
1157         default:
1158             return (FUNC_NUM_IVALID);
1159         }
1160         debug(D_FILE_OPS, "open");
1161         break;
1162         
1163     case 0x5b:                  /* creat new */
1164         pname = (char *)MAKEPTR(R_DS, R_DL);
1165         action = 0x10;          /* create if not exist, fail if exists */
1166         mode = O_RDWR;
1167         debug(D_FILE_OPS, "creat_new");
1168         break;
1169
1170     case 0x6c:                  /* multipurpose */
1171         pname = (char *)MAKEPTR(R_DS, R_SI);
1172         action = R_DX;
1173         switch (R_BL & 3) {
1174         case 0:
1175             mode = O_RDONLY;
1176             break;
1177         case 1:
1178             mode = O_WRONLY;
1179             break;
1180         case 2:
1181             mode = O_RDWR;
1182             break;
1183         default:
1184             return (FUNC_NUM_IVALID);
1185         }
1186         debug(D_FILE_OPS, "mopen");
1187         break;
1188
1189     default:
1190         fatal("called int21_creat for unknown function %x\n",R_AH);
1191     }
1192     if (action & 0x02)  /* replace/open mode */
1193         mode |= O_TRUNC;
1194
1195     /* consider proposed name */
1196     error = translate_filename(pname, fname, &drive);
1197     if (error)
1198         return (error);
1199
1200     debug(D_FILE_OPS, "(%s)\n", fname);
1201     
1202     if (dos_readonly(drive) && (mode != O_RDONLY))
1203         return (WRITE_PROT_DISK);
1204
1205     if (ustat(fname, &sb) < 0) {                /* file does not exist */
1206         if (action & 0x10) {                    /* create? */
1207             sb.st_ino = 0;
1208             mode |= O_CREAT;                    /* have to create as we go */
1209             status = 0x02;                      /* file created */
1210         } else {
1211             return(FILE_NOT_FOUND);
1212         }
1213     } else {
1214         if (S_ISDIR(sb.st_mode))
1215             return(ACCESS_DENIED);
1216         if (action & 0x03) {                    /* exists, work with it */
1217             if (action & 0x02) {
1218                 if (!S_ISREG(sb.st_mode)) {     /* only allowed for files */
1219                     debug(D_FILE_OPS,"attempt to truncate non-regular file\n");
1220                     return(ACCESS_DENIED);
1221                 }
1222                 status = 0x03;                  /* we're going to truncate it */
1223             } else {
1224                 status = 0x01;                  /* just open it */
1225             }
1226         } else {
1227             return(FILE_ALREADY_EXISTS);        /* exists, fail */
1228         }
1229     }
1230         
1231     if ((fd = open(fname, mode, from_dos_attr(R_CX))) < 0) {
1232         debug(D_FILE_OPS,"failed to open %s : %s\n",fname,strerror(errno));
1233         return (ACCESS_DENIED);
1234     }
1235     
1236     if (R_AH == 0x6c)                   /* need to return status too */
1237         R_CX = status;
1238     R_AX = fd;                          /* return fd */
1239     return(0);
1240 }
1241
1242 /*
1243 ** 21:3e
1244 **
1245 ** close
1246 */
1247 static int
1248 int21_3e(regcontext_t *REGS)
1249 {
1250     debug(D_FILE_OPS, "close(%d)\n", R_BX);
1251
1252     if (R_BX == fileno(debugf)) {
1253         printf("attempt to close debugging fd\n");
1254         return (HANDLE_INVALID);
1255     }
1256
1257     if (close(R_BX) < 0)
1258         return (HANDLE_INVALID);
1259     return(0);
1260 }
1261
1262 /*
1263 ** 21:3f
1264 **
1265 ** read
1266 */
1267 static int
1268 int21_3f(regcontext_t *REGS)
1269 {
1270     char        *addr;
1271     int         n;
1272     int         avail;
1273     
1274     addr = (char *)MAKEPTR(R_DS, R_DX);
1275
1276     debug(D_FILE_OPS, "read(%d, %d)\n", R_BX, R_CX);
1277         
1278     if (R_BX == 0) {
1279         if (redirect0) {
1280             n = read (R_BX, addr, R_CX);
1281         } else {
1282             n = 0;
1283             while (n < R_CX) {
1284                 avail = tty_read(REGS, TTYF_BLOCK|TTYF_CTRL|TTYF_ECHONL);
1285                 if (avail < 0)
1286                     return (0);
1287                 if ((addr[n++] = avail) == '\r')
1288                     break;
1289             }
1290         }
1291     } else {
1292         n = read (R_BX, addr, R_CX);
1293     }
1294     if (n < 0)
1295         return (READ_FAULT);
1296
1297     R_AX = n;
1298     return(0);
1299 }
1300
1301 /*
1302 ** 21:40
1303 **
1304 ** write
1305 */
1306 static int
1307 write_or_truncate(int fd, char *addr, int len)
1308 {
1309     off_t offset;
1310
1311     if (len == 0) {
1312         offset = lseek(fd, 0, SEEK_CUR);
1313         if (offset < 0)
1314             return -1;
1315         else
1316             return ftruncate(fd, offset);
1317     } else {
1318         return write(fd, addr, len);
1319     }
1320 }
1321
1322 static int
1323 int21_40(regcontext_t *REGS)
1324 {
1325     char        *addr;
1326     int         nbytes,n;
1327     
1328     addr = (char *)MAKEPTR(R_DS, R_DX);
1329     nbytes = R_CX;
1330
1331     debug(D_FILE_OPS, "write(%d, %d)\n", R_BX, nbytes);
1332
1333     switch (R_BX) {
1334     case 0:
1335         if (redirect0) {
1336             n = write_or_truncate(R_BX, addr, nbytes);
1337             break;
1338         }
1339         n = nbytes;
1340         while (nbytes-- > 0)
1341             tty_write(*addr++, -1);
1342         break;
1343     case 1:
1344         if (redirect1) {
1345             n = write_or_truncate(R_BX, addr, nbytes);
1346             break;
1347         }
1348         n = nbytes;
1349         while (nbytes-- > 0)
1350             tty_write(*addr++, -1);
1351         break;
1352     case 2:
1353         if (redirect2) {
1354             n = write_or_truncate(R_BX, addr, nbytes);
1355             break;
1356         }
1357         n = nbytes;
1358         while (nbytes-- > 0)
1359             tty_write(*addr++, -1);
1360         break;
1361     default:
1362         n = write_or_truncate(R_BX, addr, nbytes);
1363         break;
1364     }
1365     if (n < 0)
1366         return (WRITE_FAULT);
1367     
1368     R_AX = n;
1369     return(0);
1370 }
1371
1372 /*
1373 ** 21:42
1374 **
1375 ** seek
1376 */
1377 static int
1378 int21_42(regcontext_t *REGS)
1379 {
1380     int         whence;
1381     off_t       offset;
1382     
1383     offset = (off_t) ((int) (R_CX << 16) + R_DX);
1384     switch (R_AL) {
1385     case 0:
1386         whence = SEEK_SET;
1387         break;
1388     case 1:
1389         whence = SEEK_CUR;
1390         break;
1391     case 2:
1392         whence = SEEK_END;
1393         break;
1394     default:
1395         return (FUNC_NUM_IVALID);
1396     }
1397
1398     debug(D_FILE_OPS, "seek(%d, 0x%qx, %d)\n", R_BX, offset, whence);
1399
1400     if ((offset = lseek(R_BX, offset, whence)) < 0) {
1401         if (errno == EBADF)
1402             return (HANDLE_INVALID);
1403         else
1404             return (SEEK_ERROR);
1405     }   
1406
1407     R_DX = (offset >> 16) & 0xffff;
1408     R_AX = offset & 0xffff;
1409     return(0);
1410 }
1411
1412 /*
1413 ** 21:43
1414 **
1415 ** get/set attributes
1416 */
1417 static int
1418 int21_43(regcontext_t *REGS)
1419 {
1420     int         error;
1421     char        fname[PATH_MAX];
1422     struct stat sb;
1423     int         mode;
1424     int         drive;
1425     
1426     error = translate_filename((u_char *)MAKEPTR(R_DS, R_DX), fname, &drive);
1427     if (error)
1428         return (error);
1429
1430     debug(D_FILE_OPS, "get/set attributes: %s, cx=%x, al=%d\n",
1431           fname, R_CX, R_AL);
1432
1433     if (stat(fname, &sb) < 0) {
1434         debug(D_FILE_OPS, "stat failed for %s\n", fname);
1435         return (FILE_NOT_FOUND);
1436     }
1437     
1438     switch (R_AL) {
1439     case 0:                     /* get attributes */
1440         mode = 0;
1441         if (dos_readonly(drive) || access(fname, W_OK))
1442             mode |= 0x01;
1443         if (S_ISDIR(sb.st_mode))
1444             mode |= 0x10;
1445         R_CX = mode;
1446         break;
1447
1448     case 1:                     /* set attributes - XXX ignored */
1449         if (R_CX & 0x18)
1450             return (ACCESS_DENIED);
1451         break;
1452
1453     default:
1454         return (FUNC_NUM_IVALID);
1455     }
1456     return(0);
1457 }
1458
1459 /*
1460 ** 21:44:0
1461 **
1462 ** ioctl - get device info
1463 **
1464 ** XXX it would be nice to detect EOF.
1465 */
1466 static int
1467 int21_44_0(regcontext_t *REGS)
1468 {
1469     debug(D_FILE_OPS, "ioctl get %d\n", R_BX);
1470
1471     switch (R_BX) {
1472     case 0:
1473         R_DX = 0x80 | 0x01;             /* is device, is standard output */
1474         break;
1475     case 1:
1476         R_DX =  0x80 | 0x02;            /* is device, is standard input */
1477         break;
1478     case 2:
1479         R_DX = 0x80;                    /* is device */
1480         break;
1481     default:
1482         if (isatty (R_BX))
1483             R_DX = 0x80;                /* is a device */
1484         else
1485             R_DX = 0;                   /* is a file */
1486         break;
1487     }
1488     return(0);
1489 }
1490
1491 /*
1492 ** 21:44:01
1493 **
1494 ** ioctl - set device info
1495 */
1496 static int
1497 int21_44_1(regcontext_t *REGS)
1498 {
1499     debug(D_FILE_OPS, "ioctl set device info %d flags %x (ignored)\n",
1500           R_BX, R_DX);
1501     return(0);
1502 }
1503
1504 /*
1505 ** 21:44:7
1506 **
1507 ** Get output status
1508 */
1509 static int
1510 int21_44_7(regcontext_t *REGS)
1511 {
1512     /* XXX Should really check to see if BX is open or not */
1513     R_AX = 0xFF;
1514     return(0);
1515 }
1516
1517 /*
1518 ** 21:44:8
1519 **
1520 ** test for removable block device
1521 */
1522 static int
1523 int21_44_8(regcontext_t *REGS)
1524 {
1525     R_AX = 1;                   /* fixed */
1526     return(0);
1527 }
1528
1529 /*
1530 ** 21:44:0
1531 **
1532 ** test for remote device (disallow direct I/O)
1533 */
1534 static int
1535 int21_44_9(regcontext_t *REGS)
1536 {
1537     R_DX = 0x1200;              /* disk is remote, direct I/O not allowed */
1538     return (0);
1539 }
1540
1541 /*
1542 ** 21:45
1543 **
1544 ** dup
1545 */
1546 static int
1547 int21_45(regcontext_t *REGS)
1548 {
1549     int         nfd;
1550     
1551     debug(D_FILE_OPS, "dup(%d)\n", R_BX);
1552
1553     if ((nfd = dup(R_BX)) < 0) {
1554         if (errno == EBADF)
1555             return (HANDLE_INVALID);
1556         else
1557             return (TOO_MANY_OPEN_FILES);
1558     }
1559     R_AX = nfd;
1560     return(0);
1561 }
1562
1563 /*
1564 ** 21:46
1565 **
1566 ** dup2
1567 */
1568 static int
1569 int21_46(regcontext_t *REGS)
1570 {
1571     debug(D_FILE_OPS, "dup2(%d, %d)\n", R_BX, R_CX);
1572
1573     if (dup2(R_BX, R_CX) < 0) {
1574         if (errno == EMFILE)
1575             return (TOO_MANY_OPEN_FILES);
1576         else
1577             return (HANDLE_INVALID);
1578     }
1579     return(0);
1580 }
1581
1582 /*
1583 ** 21:47
1584 **
1585 ** getcwd
1586 */
1587 static int
1588 int21_47(regcontext_t *REGS)
1589 {
1590     int         n,nbytes;
1591     char        *p,*addr;
1592
1593     n = R_DL;
1594     if (!n--)
1595         n = diskdrive;
1596
1597     p = (char *)dos_getcwd(n) + 1;
1598     addr = (char *)MAKEPTR(R_DS, R_SI);
1599
1600     nbytes = strlen(p);
1601     if (nbytes > 63)
1602         nbytes = 63;
1603     
1604     memcpy(addr, p, nbytes);
1605     addr[nbytes] = 0;
1606     return(0);
1607 }
1608
1609 /*
1610 ** 21:48
1611 **
1612 ** allocate memory
1613 */
1614 static int
1615 int21_48(regcontext_t *REGS)
1616 {
1617     int         memseg,avail;
1618     
1619     memseg = mem_alloc(R_BX, pspseg, &avail);
1620
1621     if (memseg == 0L) {
1622         R_BX = avail;
1623         return (INSUF_MEM);
1624     }
1625
1626     R_AX = memseg;
1627     return(0);
1628 }
1629
1630 /*
1631 ** 21:49
1632 **
1633 ** free memory
1634 */
1635 static int
1636 int21_49(regcontext_t *REGS)
1637 {
1638     if (mem_adjust(R_ES, 0, NULL) < 0)
1639         return (MEM_BLK_ADDR_IVALID);
1640     return(0);
1641 }
1642
1643 /*
1644 ** 21:4a
1645 **
1646 ** resize memory block
1647 */
1648 static int
1649 int21_4a(regcontext_t *REGS)
1650 {
1651     int         n,avail;
1652     
1653     if ((n = mem_adjust(R_ES, R_BX, &avail)) < 0) {
1654         R_BX = avail;
1655         if (n == -1)
1656             return (INSUF_MEM);
1657         else
1658             return (MEM_BLK_ADDR_IVALID);
1659     }
1660     return(0);
1661 }
1662
1663 /*
1664 ** 21:4b
1665 **
1666 ** exec
1667 **
1668 ** XXX verify!
1669 */
1670 static int
1671 int21_4b(regcontext_t *REGS)
1672 {
1673     int         fd;
1674     u_short     *param;
1675     
1676     debug(D_EXEC, "exec(%s)\n",(u_char *)MAKEPTR(R_DS, R_DX));
1677
1678     if ((fd = open_prog((u_char *)MAKEPTR(R_DS, R_DX))) < 0) {
1679         debug(D_EXEC, "%s: command not found\n",
1680               (u_char *)MAKEPTR(R_DS, R_DX));
1681         return (FILE_NOT_FOUND);
1682     }
1683
1684     /* child */
1685     param = (u_short *)MAKEPTR(R_ES, R_BX);
1686
1687     switch (R_AL) {
1688     case 0x00: /* load and execute */
1689         exec_command(REGS, 1, fd, cmdname, param);
1690         close(fd);
1691         break;
1692
1693     case 0x01: /* just load */
1694         exec_command(REGS, 0, fd, cmdname, param);
1695         close(fd);
1696         break;
1697
1698     case 0x03: /* load overlay */
1699         load_overlay(fd, param[0], param[1]);
1700         close(fd);
1701         break;
1702
1703     default:
1704         unknown_int3(0x21, 0x4b, R_AL, REGS);
1705         return (FUNC_NUM_IVALID);
1706     }
1707     return(0);
1708 }
1709
1710 /*
1711 ** 21:4c
1712 **
1713 ** return with code
1714 */
1715 static int
1716 int21_4c(regcontext_t *REGS)
1717 {
1718     return_status = R_AL;
1719     done(REGS, R_AL);
1720     return 0;
1721 }
1722
1723 /*
1724 ** 21:4d
1725 **
1726 ** get return code of child
1727 */
1728 static int
1729 int21_4d(regcontext_t *REGS)
1730 {
1731     R_AX = return_status;
1732     return(0);
1733 }
1734
1735 /*
1736 ** 21:4e
1737 ** 21:4f
1738 **
1739 ** find first, find next
1740 */
1741 static int
1742 int21_find(regcontext_t *REGS)
1743 {
1744     find_block_t        *dta;
1745     dosdir_t            dosdir;
1746     int                 error = 0;
1747     
1748     dta = (find_block_t *)VECPTR(disk_transfer_addr);
1749         
1750     switch (R_AH) {
1751     case 0x4e:          /* find first */
1752         error = find_first((u_char *)MAKEPTR(R_DS, R_DX), R_CX, &dosdir, dta);
1753         break;
1754     case 0x4f:
1755         error = find_next(&dosdir, dta);
1756         break;
1757     default:
1758         fatal("called int21_find for unknown function %x\n",R_AH);
1759     }
1760     if (!error) {
1761         dosdir_to_dta(&dosdir, dta);
1762         R_AX = 0;
1763     }
1764     return(error);
1765 }
1766
1767 /*
1768 ** 21:50
1769 **
1770 ** set PSP
1771 */
1772 static int
1773 int21_50(regcontext_t *REGS)
1774 {
1775     pspseg = R_BX;
1776     return(0);
1777 }
1778
1779 /*
1780 ** 21:57:00
1781 **
1782 ** get mtime for handle
1783 */
1784 static int
1785 int21_57_0(regcontext_t *REGS)
1786 {
1787     struct stat sb;
1788     u_short     date, mtime;
1789     
1790     if (fstat(R_BX, &sb) < 0)
1791         return (HANDLE_INVALID);
1792     encode_dos_file_time(sb.st_mtime, &date, &mtime);
1793     R_CX = mtime;
1794     R_DX = date;
1795     return(0);
1796 }
1797
1798 /*
1799 ** 21:57:01
1800 **
1801 ** set mtime for handle
1802 */
1803 static int
1804 int21_57_1(regcontext_t *REGS __unused)
1805 {
1806 #ifdef __NetBSD__       /* XXX need futimes() */
1807         struct stat sb;
1808         struct timeval tv[2];
1809         u_short date, time;
1810
1811         time = R_CX;
1812         date = R_DX;
1813         tv[0].tv_sec = tv[1].tv_sec = decode_dos_file_time(date, time);
1814         tv[0].tv_usec = tv[1].tv_usec = 0;
1815         if (futimes(R_BX, tv) < 0)
1816             return (HANDLE_INVALID);
1817         break;
1818 #endif
1819         return(0);
1820 }
1821
1822 /*
1823 ** 21:58
1824 **
1825 ** get/set memory strategy
1826 ** get/set UMB link state
1827 */
1828 static int
1829 int21_58(regcontext_t *REGS)
1830 {
1831     switch (R_AL) {
1832     case 0x00:                          /* get memory strategy */
1833         R_AX = memory_strategy;
1834         break;
1835     case 0x01:                          /* set memory strategy */
1836         memory_strategy = R_BL;
1837         if (memory_strategy > 2)        /* higher make no sense without UMBs */
1838             memory_strategy = 2;        
1839         break;
1840     case 0x02:                  /* get UMB link state */
1841         R_AL = 0;               /* UMBs not in link chain */
1842         break;
1843     default:                    /* includes set, which is invalid */
1844         unknown_int3(0x21, 0x58, R_AL, REGS);
1845         return (FUNC_NUM_IVALID);
1846     }
1847     return(0);
1848 }
1849
1850 /*
1851 ** 21:59
1852 **
1853 ** get extended error information
1854 */
1855 static int
1856 int21_59(regcontext_t *REGS)
1857 {
1858     R_AX = doserrno;
1859     switch (doserrno) {
1860     case 1:
1861     case 6:
1862     case 9:
1863     case 10:
1864     case 11:
1865     case 12:
1866     case 13:
1867     case 15:
1868         R_BH = 7;               /* application error */
1869         break;
1870
1871     case 2:
1872     case 3:
1873     case 4:
1874     case 5:
1875         R_BH = 8;               /* not found */
1876         break;
1877
1878     case 7:
1879     case 8:
1880         R_BH = 1;               /* out of resource */
1881         break;
1882         
1883     default:
1884         R_BH = 12;              /* already exists */
1885         break;
1886     }
1887     R_BL = 6;                   /* always ignore! */
1888     R_CH = 1;                   /* unknown/inappropriate */
1889     return(0);
1890 }
1891
1892 /*
1893 ** 21:5a
1894 **
1895 ** create temporary file
1896 */
1897 static int
1898 int21_5a(regcontext_t *REGS)
1899 {
1900     char        fname[PATH_MAX];
1901     char        *pname;
1902     int         error;
1903     int         n;
1904     int         drive;
1905     int         fd;
1906     
1907     /* get and check proposed path */
1908     pname = (char *)MAKEPTR(R_DS, R_DX);
1909     error = translate_filename(pname, fname, &drive);
1910     if (error)
1911         return (error);
1912
1913     debug(D_FILE_OPS, "tempname(%s)\n", fname);
1914
1915     if (dos_readonly(drive))
1916         return (WRITE_PROT_DISK);
1917
1918     n = strlen(fname);
1919     strcat(fname,"__dostmp.XXX");
1920     fd = mkstemp(fname);
1921     if (fd < 0)
1922         return (ACCESS_DENIED);
1923
1924     strcat(pname, fname + n);   /* give back the full name */
1925     R_AX = fd;
1926     return(0);
1927 }
1928
1929 /*
1930 ** 21:60
1931 **
1932 ** canonicalise name
1933 */
1934 static int
1935 int21_60(regcontext_t *REGS)
1936 {
1937     return(dos_makepath((char *)MAKEPTR(R_DS, R_SI),
1938                         (char *)MAKEPTR(R_ES, R_DI)));
1939 }
1940
1941 /*
1942 ** 21:62
1943 **
1944 ** get current PSP
1945 */
1946 static int
1947 int21_62(regcontext_t *REGS)
1948 {
1949     R_BX = pspseg;
1950     return(0);
1951 }
1952
1953 /*
1954 ** 21:65:23
1955 **
1956 ** determine yes/no
1957 ** (mostly for humour value 8)
1958 */
1959 static int
1960 int21_65_23(regcontext_t *REGS)
1961 {
1962     switch (R_DL) {
1963     case 'n':   /* no, nein, non, nyet */
1964     case 'N':
1965         R_AX = 0;
1966         break;
1967     case 'y':   /* yes */
1968     case 'Y':
1969     case 'j':   /* ja */
1970     case 'J':
1971     case 'o':   /* oui */
1972     case 'O':
1973     case 'd':   /* da */
1974     case 'D':
1975         R_AX = 1;
1976         break;
1977     default:    /* maybe */
1978         R_AX = 2;
1979         break;
1980     }
1981     return(0);
1982 }
1983
1984 /*
1985 ** 21:68
1986 ** 21:6a
1987 **
1988 ** fflush/commit file
1989 */
1990 static int
1991 int21_fflush(regcontext_t *REGS)
1992 {
1993     debug(D_FILE_OPS, "fsync(%d)\n", R_BX);
1994         
1995     if (fsync(R_BX) < 0)
1996         return (HANDLE_INVALID);
1997     return(0);
1998 }
1999
2000 /******************************************************************************
2001 ** 21:0f 21:10 21:11 21:12 21:16 21:27 21:28:21:29
2002 **
2003 ** FCB functions
2004 */
2005 static void
2006 openfcb(struct fcb *fcbp)
2007 {
2008         struct stat statb;
2009
2010         fcbp->fcbDriveID = 3;           /* drive C */
2011         fcbp->fcbCurBlockNo = 0;
2012         fcbp->fcbRecSize = 128;
2013         if (fstat(fcbp->fcb_fd, &statb) < 0) {
2014                 debug(D_FILE_OPS, "open not complete with errno %d\n", errno);
2015                 return;
2016         }
2017         encode_dos_file_time(statb.st_mtime,
2018                                 &fcbp->fcbFileDate, &fcbp->fcbFileTime);
2019         fcbp->fcbFileSize = statb.st_size;
2020 }
2021
2022 static int
2023 getfcb_rec(struct fcb *fcbp, int nrec)
2024 {
2025         int n;
2026
2027         n = fcbp->fcbRandomRecNo;
2028         if (fcbp->fcbRecSize >= 64)
2029                 n &= 0xffffff;
2030         fcbp->fcbCurRecNo = n % 128;
2031         fcbp->fcbCurBlockNo = n / 128;
2032         if (lseek(fcbp->fcb_fd, n * fcbp->fcbRecSize, SEEK_SET) < 0)
2033                 return (-1);
2034         return (nrec * fcbp->fcbRecSize);
2035 }
2036
2037
2038 static int
2039 setfcb_rec(struct fcb *fcbp, int n)
2040 {
2041         int recs, total;
2042
2043         total = fcbp->fcbRandomRecNo;
2044         if (fcbp->fcbRecSize >= 64)
2045                 total &= 0xffffff;
2046         recs = (n+fcbp->fcbRecSize-1) / fcbp->fcbRecSize;
2047         total += recs;
2048
2049         fcbp->fcbRandomRecNo = total;
2050         fcbp->fcbCurRecNo = total % 128;
2051         fcbp->fcbCurBlockNo = total / 128;
2052
2053         return(0);
2054 }
2055
2056 static void
2057 fcb_to_string(struct fcb *fcbp, u_char *buf)
2058 {
2059
2060         if (fcbp->fcbDriveID != 0x00) {
2061                 *buf++ = drntol(fcbp->fcbDriveID - 1);
2062                 *buf++ = ':';
2063         }
2064         pack_name(fcbp->fcbFileName, buf);
2065 }
2066
2067
2068 static int
2069 int21_fcb(regcontext_t *REGS)
2070 {       
2071     char                buf[PATH_MAX];
2072     char                fname[PATH_MAX];
2073     struct stat         sb;
2074     dosdir_t            dosdir;
2075     struct fcb          *fcbp;
2076     find_block_t        *dta;
2077     u_char              *addr;
2078     int                 error;
2079     int                 drive;
2080     int                 fd;
2081     int                 nbytes,n;
2082
2083
2084     fcbp = (struct fcb *)MAKEPTR(R_DS, R_DX);
2085
2086     switch (R_AH) {
2087         
2088     case 0x0f: /* open file with FCB */
2089         fcb_to_string(fcbp, buf);
2090         error = translate_filename(buf, fname, &drive); 
2091         if (error)
2092             return (error);
2093
2094         debug(D_FILE_OPS, "open FCB(%s)\n", fname);
2095
2096         if (ustat(fname, &sb) < 0) 
2097             sb.st_ino = 0;
2098
2099         if (dos_readonly(drive))
2100             return (WRITE_PROT_DISK);
2101
2102         if (sb.st_ino == 0 || S_ISDIR(sb.st_mode))
2103             return (FILE_NOT_FOUND);
2104
2105         if ((fd = open(fname, O_RDWR)) < 0) {
2106             if (errno == ENOENT)
2107                 return (FILE_NOT_FOUND);
2108             else
2109                 return (ACCESS_DENIED);
2110         }
2111
2112         fcbp->fcb_fd = fd;
2113         openfcb(fcbp);
2114         R_AL = 0;
2115         break;
2116
2117     case 0x10: /* close file with FCB */
2118         debug(D_FILE_OPS, "close FCB(%d)\n", fcbp->fcb_fd);
2119
2120         if (close(fcbp->fcb_fd) < 0)
2121             return (HANDLE_INVALID);
2122
2123         fcbp->fcb_fd = -1;
2124         R_AL = 0;
2125         break;
2126
2127     case 0x11: /* find_first with FCB */
2128         dta = (find_block_t *)VECPTR(disk_transfer_addr);
2129
2130         fcb_to_string(fcbp, buf);
2131         error = find_first(buf, fcbp->fcbAttribute, &dosdir, dta);
2132         if (error)
2133             return (error);
2134         
2135         dosdir_to_dta(&dosdir, dta);
2136         R_AL = 0;
2137         break;
2138
2139     case 0x12: /* find_next with FCB */
2140         dta = (find_block_t *)VECPTR(disk_transfer_addr);
2141
2142         error = find_next(&dosdir, dta);
2143         if (error)
2144             return (error);
2145
2146         dosdir_to_dta(&dosdir, dta);
2147         R_AL = 0;
2148         break;
2149
2150     case 0x16: /* create file with FCB */
2151         fcb_to_string(fcbp, buf);
2152         error = translate_filename(buf, fname, &drive); 
2153         if (error)
2154             return (error);
2155
2156         debug(D_FILE_OPS, "creat FCB(%s)\n", fname);
2157         
2158         if (ustat(fname, &sb) < 0)
2159             sb.st_ino = 0;
2160
2161         if (dos_readonly(drive))
2162             return (WRITE_PROT_DISK);
2163
2164         if (sb.st_ino && !S_ISREG(sb.st_mode))
2165             return (ACCESS_DENIED);
2166
2167         if ((fd = open(fname, O_CREAT|O_TRUNC|O_RDWR, 0666)) < 0)
2168             return (ACCESS_DENIED);
2169         
2170         fcbp->fcb_fd = fd;
2171         openfcb(fcbp);
2172         R_AL = 0;
2173         break;
2174
2175     case 0x27: /* random block read */
2176         addr = (u_char *)VECPTR(disk_transfer_addr);
2177         nbytes = getfcb_rec(fcbp, R_CX);
2178
2179         if (nbytes < 0)
2180             return (READ_FAULT);
2181         n = read(fcbp->fcb_fd, addr, nbytes);
2182         if (n < 0)
2183             return (READ_FAULT);
2184         R_CX = setfcb_rec(fcbp, n);
2185         if (n < nbytes) {
2186             nbytes = n % fcbp->fcbRecSize;
2187             if (0 == nbytes) {
2188                 R_AL = 0x01;
2189             } else {
2190                 bzero(addr + n, fcbp->fcbRecSize - nbytes);
2191                 R_AL = 0x03;
2192             }
2193         } else {
2194             R_AL = 0;
2195         }
2196         break;
2197
2198     case 0x28: /* random block write */
2199         addr = (u_char *)VECPTR(disk_transfer_addr);
2200         nbytes = getfcb_rec(fcbp, R_CX);
2201
2202         if (nbytes < 0)
2203             return (WRITE_FAULT);
2204         n = write(fcbp->fcb_fd, addr, nbytes);
2205         if (n < 0)
2206             return (WRITE_FAULT);
2207         R_CX = setfcb_rec(fcbp, n);
2208         if (n < nbytes) {
2209             R_AL = 0x01;
2210         } else {
2211             R_AL = 0;
2212         }
2213         break;
2214
2215     case 0x29: /* parse filename */
2216         debug(D_FILE_OPS,"parse filename: flag=%d, ", R_AL);
2217
2218         R_AX = parse_filename(R_AL,
2219                               (char *)MAKEPTR(R_DS, R_SI),
2220                               (char *)MAKEPTR(R_ES, R_DI),
2221                               &nbytes);
2222         debug(D_FILE_OPS, "%d %s, FCB: %d, %.11s\n",
2223               nbytes,
2224               (char *)MAKEPTR(R_DS, R_SI),
2225               *(int *)MAKEPTR(R_ES, R_DI),
2226               (char *)MAKEPTR(R_ES, R_DI) + 1);
2227         
2228         R_SI += nbytes;
2229         break;
2230
2231     default:
2232         fatal("called int21_fcb with unknown function %x\n",R_AH);
2233     }
2234     return(0);
2235 }
2236
2237 /*
2238 ** 21:5d
2239 ** 21:5e
2240 ** 21:5f
2241 **
2242 ** network functions
2243 ** XXX relevant?
2244 */
2245 static int
2246 int21_net(regcontext_t *REGS)
2247 {
2248     switch(R_AH) {
2249     case 0x5d:
2250         switch(R_AL) {
2251         case 0x06:
2252             debug(D_HALF, "Get Swapable Area\n");
2253             return (ACCESS_DENIED);
2254         case 0x08: /* Set redirected printer mode */
2255             debug(D_HALF, "Redirection is %s\n",
2256                   R_DL ? "separate jobs" : "combined");
2257             break;
2258         case 0x09: /* Flush redirected printer output */
2259             break;
2260         default:
2261             unknown_int3(0x21, 0x5d, R_AL, REGS);
2262             return (FUNC_NUM_IVALID);
2263         }
2264         break;
2265         
2266     case 0x5e:
2267     case 0x5f:
2268         unknown_int2(0x21, R_AH, REGS);
2269         return (FUNC_NUM_IVALID);
2270     default:
2271         fatal("called int21_net with unknown function %x\n",R_AH);
2272     }
2273     return(0);
2274 }
2275
2276 /*
2277 ** 21:??
2278 **
2279 ** Unknown/unsupported
2280 */
2281 static int
2282 int21_NOFUNC(regcontext_t *REGS)
2283 {
2284     unknown_int2(0x21, R_AH, REGS);
2285     return (FUNC_NUM_IVALID);
2286 }
2287
2288 /*
2289 ** 21:??
2290 **
2291 ** Null function; no error, no action
2292 */
2293 static int
2294 int21_NULLFUNC(regcontext_t *REGS)
2295 {
2296     R_AL = 0;
2297     return(0);
2298 }
2299
2300
2301 static struct intfunc_table int21_table [] = {
2302     { 0x00,     IFT_NOSUBFUNC,  int21_00,       "terminate"},
2303     { 0x01,     IFT_NOSUBFUNC,  int21_01,       "read character with echo"},
2304     { 0x02,     IFT_NOSUBFUNC,  int21_02,       "write char to stdout"},
2305     { 0x03,     IFT_NOSUBFUNC,  int21_NOFUNC,   "read char from stdaux"},
2306     { 0x04,     IFT_NOSUBFUNC,  int21_NOFUNC,   "write char to stdaux"},
2307     { 0x05,     IFT_NOSUBFUNC,  int21_NOFUNC,   "write char to printer"},
2308     { 0x06,     IFT_NOSUBFUNC,  int21_06,       "direct console I/O"},
2309     { 0x07,     IFT_NOSUBFUNC,  int21_07,       "direct console in without echo"},
2310     { 0x08,     IFT_NOSUBFUNC,  int21_08,       "read character, no echo"},
2311     { 0x09,     IFT_NOSUBFUNC,  int21_09,       "write string to standard out"},
2312     { 0x0a,     IFT_NOSUBFUNC,  int21_0a,       "buffered input"},
2313     { 0x0b,     IFT_NOSUBFUNC,  int21_0b,       "get stdin status"},
2314     { 0x0c,     IFT_NOSUBFUNC,  int21_0c,       "flush stdin and read"},
2315     { 0x0d,     IFT_NOSUBFUNC,  int21_NULLFUNC, "disk reset"},
2316     { 0x0e,     IFT_NOSUBFUNC,  int21_0e,       "select default drive"},
2317     { 0x19,     IFT_NOSUBFUNC,  int21_19,       "get default drive"},
2318     { 0x1a,     IFT_NOSUBFUNC,  int21_1a,       "set DTA"},
2319     { 0x1b,     IFT_NOSUBFUNC,  int21_free,     "get allocation for default drive"},
2320     { 0x1c,     IFT_NOSUBFUNC,  int21_free,     "get allocation for specific drive"},
2321     { 0x1f,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get DPB for current drive"},
2322     { 0x23,     IFT_NOSUBFUNC,  int21_23,       "Get file size (old)"},
2323     { 0x25,     IFT_NOSUBFUNC,  int21_25,       "set interrupt vector"},
2324     { 0x26,     IFT_NOSUBFUNC,  int21_26,       "create new PSP"},
2325     { 0x2a,     IFT_NOSUBFUNC,  int21_2a,       "get date"},
2326     { 0x2b,     IFT_NOSUBFUNC,  int21_2b,       "set date"},
2327     { 0x2c,     IFT_NOSUBFUNC,  int21_2c,       "get time"},
2328     { 0x2d,     IFT_NOSUBFUNC,  int21_2d,       "set time"},
2329     { 0x2e,     IFT_NOSUBFUNC,  int21_NULLFUNC, "set verify flag"},
2330     { 0x2f,     IFT_NOSUBFUNC,  int21_2f,       "get DTA"},
2331     { 0x30,     IFT_NOSUBFUNC,  int21_30,       "get DOS version"},
2332     { 0x31,     IFT_NOSUBFUNC,  int21_NOFUNC,   "terminate and stay resident"},
2333     { 0x32,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get DPB for specific drive"},
2334     { 0x33,     0x05,           int21_33_5,     "get boot drive"},
2335     { 0x33,     0x06,           int21_33_6,     "get true version number"},
2336     { 0x33,     IFT_NOSUBFUNC,  int21_33,       "extended break checking"},
2337     { 0x34,     IFT_NOSUBFUNC,  int21_34,       "get address of InDos flag"},
2338     { 0x35,     IFT_NOSUBFUNC,  int21_35,       "get interrupt vector"},
2339     { 0x36,     IFT_NOSUBFUNC,  int21_free,     "get disk free space"},
2340     { 0x37,     IFT_NOSUBFUNC,  int21_37,       "switch character"},
2341     { 0x38,     IFT_NOSUBFUNC,  int21_38,       "country code/information"},
2342     { 0x39,     IFT_NOSUBFUNC,  int21_dirfn,    "mkdir"},
2343     { 0x3a,     IFT_NOSUBFUNC,  int21_dirfn,    "rmdir"},
2344     { 0x3b,     IFT_NOSUBFUNC,  int21_3b,       "chdir"},
2345     { 0x3c,     IFT_NOSUBFUNC,  int21_open,     "creat"},
2346     { 0x3d,     IFT_NOSUBFUNC,  int21_open,     "open"},
2347     { 0x3e,     IFT_NOSUBFUNC,  int21_3e,       "close"},
2348     { 0x3f,     IFT_NOSUBFUNC,  int21_3f,       "read"},
2349     { 0x40,     IFT_NOSUBFUNC,  int21_40,       "write"},
2350     { 0x41,     IFT_NOSUBFUNC,  int21_dirfn,    "unlink"},
2351     { 0x42,     IFT_NOSUBFUNC,  int21_42,       "lseek"},
2352     { 0x43,     IFT_NOSUBFUNC,  int21_43,       "get/set file attributes"},
2353     { 0x44,     0x00,           int21_44_0,     "ioctl(get)"},
2354     { 0x44,     0x01,           int21_44_1,     "ioctl(set)"},
2355     { 0x44,     0x07,           int21_44_7,     "ioctl(Check output status)"},
2356     { 0x44,     0x08,           int21_44_8,     "ioctl(test removable)"},
2357     { 0x44,     0x09,           int21_44_9,     "ioctl(test remote)"},
2358     { 0x45,     IFT_NOSUBFUNC,  int21_45,       "dup"},
2359     { 0x46,     IFT_NOSUBFUNC,  int21_46,       "dup2"},
2360     { 0x47,     IFT_NOSUBFUNC,  int21_47,       "getwd"},
2361     { 0x48,     IFT_NOSUBFUNC,  int21_48,       "allocate memory"},
2362     { 0x49,     IFT_NOSUBFUNC,  int21_49,       "free memory"},
2363     { 0x4a,     IFT_NOSUBFUNC,  int21_4a,       "resize memory block"},
2364     { 0x4b,     IFT_NOSUBFUNC,  int21_4b,       "exec"},
2365     { 0x4c,     IFT_NOSUBFUNC,  int21_4c,       "exit with return code"},
2366     { 0x4d,     IFT_NOSUBFUNC,  int21_4d,       "get return code from child"},
2367     { 0x4e,     IFT_NOSUBFUNC,  int21_find,     "findfirst"},
2368     { 0x4f,     IFT_NOSUBFUNC,  int21_find,     "findnext"},
2369     { 0x50,     IFT_NOSUBFUNC,  int21_50,       "set psp"},
2370     { 0x51,     IFT_NOSUBFUNC,  int21_62,       "get psp"},
2371     { 0x52,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get LoL"},
2372     { 0x53,     IFT_NOSUBFUNC,  int21_NOFUNC,   "translate BPB to DPB"},
2373     { 0x54,     IFT_NOSUBFUNC,  int21_NULLFUNC, "get verify flag"},
2374     { 0x55,     IFT_NOSUBFUNC,  int21_NOFUNC,   "create PSP"},
2375     { 0x56,     IFT_NOSUBFUNC,  int21_dirfn,    "rename"},
2376     { 0x57,     0x00,           int21_57_0,     "get mtime"},
2377     { 0x57,     0x01,           int21_57_1,     "set mtime"},
2378     { 0x58,     IFT_NOSUBFUNC,  int21_58,       "get/set memory strategy"},
2379     { 0x59,     IFT_NOSUBFUNC,  int21_59,       "get extended error information"},
2380     { 0x5a,     IFT_NOSUBFUNC,  int21_5a,       "create temporary file"},
2381     { 0x5b,     IFT_NOSUBFUNC,  int21_open,     "create new file"},
2382     { 0x5c,     IFT_NOSUBFUNC,  int21_NOFUNC,   "flock"},
2383     { 0x5d,     IFT_NOSUBFUNC,  int21_net,      "network functions"},
2384     { 0x5e,     IFT_NOSUBFUNC,  int21_net,      "network functions"},
2385     { 0x5f,     IFT_NOSUBFUNC,  int21_net,      "network functions"},
2386     { 0x60,     IFT_NOSUBFUNC,  int21_60,       "canonicalise name/path"},
2387     { 0x61,     IFT_NOSUBFUNC,  int21_NULLFUNC, "network functions (reserved)"},
2388     { 0x62,     IFT_NOSUBFUNC,  int21_62,       "get current PSP"},
2389     { 0x63,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get DBCS lead-byte table"},
2390     { 0x64,     IFT_NOSUBFUNC,  int21_NOFUNC,   "set device-driver lookahead"},
2391     { 0x65,     0x23,           int21_65_23,    "determine yes/no"},
2392     { 0x65,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get extended country information"},
2393     { 0x66,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get/set codepage table"},
2394     { 0x67,     IFT_NOSUBFUNC,  int21_NULLFUNC, "set handle count"},
2395     { 0x68,     IFT_NOSUBFUNC,  int21_fflush,   "fflush"},
2396     { 0x69,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get/set disk serial number"},
2397     { 0x6a,     IFT_NOSUBFUNC,  int21_fflush,   "commit file"},
2398     { 0x6b,     IFT_NOSUBFUNC,  int21_NULLFUNC, "IFS ioctl"},
2399     { 0x6c,     IFT_NOSUBFUNC,  int21_open,     "extended open/create"},
2400
2401 /* FCB functions */
2402     { 0x0f,     IFT_NOSUBFUNC,  int21_fcb,      "open file"},
2403     { 0x10,     IFT_NOSUBFUNC,  int21_fcb,      "close file"},
2404     { 0x11,     IFT_NOSUBFUNC,  int21_fcb,      "find first"},
2405     { 0x12,     IFT_NOSUBFUNC,  int21_fcb,      "find next"},
2406     { 0x13,     IFT_NOSUBFUNC,  int21_NOFUNC,   "delete"},
2407     { 0x14,     IFT_NOSUBFUNC,  int21_NOFUNC,   "sequential read"},
2408     { 0x15,     IFT_NOSUBFUNC,  int21_NOFUNC,   "sequential write"},
2409     { 0x16,     IFT_NOSUBFUNC,  int21_fcb,      "create/truncate"},
2410     { 0x17,     IFT_NOSUBFUNC,  int21_NOFUNC,   "rename"},
2411     { 0x21,     IFT_NOSUBFUNC,  int21_NOFUNC,   "read random"},
2412     { 0x22,     IFT_NOSUBFUNC,  int21_NOFUNC,   "write random"},
2413     { 0x23,     IFT_NOSUBFUNC,  int21_NOFUNC,   "get file size"},
2414     { 0x24,     IFT_NOSUBFUNC,  int21_NOFUNC,   "set random record number"},
2415     { 0x27,     IFT_NOSUBFUNC,  int21_fcb,      "random block read"},
2416     { 0x28,     IFT_NOSUBFUNC,  int21_fcb,      "random block write"},
2417     { 0x29,     IFT_NOSUBFUNC,  int21_fcb,      "parse filename into FCB"},
2418
2419 /* CPM compactability */
2420     { 0x18,     IFT_NOSUBFUNC,  int21_NULLFUNC, "CPM"},
2421     { 0x1d,     IFT_NOSUBFUNC,  int21_NULLFUNC, "CPM"},
2422     { 0x1e,     IFT_NOSUBFUNC,  int21_NULLFUNC, "CPM"},
2423     { 0x20,     IFT_NOSUBFUNC,  int21_NULLFUNC, "CPM"},
2424
2425     { -1,       IFT_NOSUBFUNC,  NULL,           NULL}   /* terminator */
2426
2427 };
2428
2429 static int int21_fastlookup[256];
2430
2431 const char *dos_return[] = {
2432     "OK",
2433     "FUNC_NUM_IVALID",
2434     "FILE_NOT_FOUND",
2435     "PATH_NOT_FOUND",
2436     "TOO_MANY_OPEN_FILES",
2437     "ACCESS_DENIED",
2438     "HANDLE_INVALID",
2439     "MEM_CB_DEST",
2440     "INSUF_MEM",
2441     "MEM_BLK_ADDR_IVALID",
2442     "ENV_INVALID",
2443     "FORMAT_INVALID",
2444     "ACCESS_CODE_INVALID",
2445     "DATA_INVALID",
2446     "UNKNOWN_UNIT",
2447     "DISK_DRIVE_INVALID",
2448     "ATT_REM_CUR_DIR",
2449     "NOT_SAME_DEV",
2450     "NO_MORE_FILES",
2451     "WRITE_PROT_DISK",
2452     "UNKNOWN_UNIT_CERR",
2453     "DRIVE_NOT_READY",
2454     "UNKNOWN_COMMAND",
2455     "DATA_ERROR_CRC",
2456     "BAD_REQ_STRUCT_LEN",
2457     "SEEK_ERROR",
2458     "UNKNOWN_MEDIA_TYPE",
2459     "SECTOR_NOT_FOUND",
2460     "PRINTER_OUT_OF_PAPER",
2461     "WRITE_FAULT",
2462     "READ_FAULT",
2463     "GENERAL_FAILURE"
2464 };
2465
2466 const int dos_ret_size = (sizeof(dos_return) / sizeof(char *));
2467
2468 /*
2469 ** for want of anywhere better to go
2470 */
2471 static void
2472 int20(regcontext_t *REGS)
2473 {
2474     /* int 20 = exit(0) */
2475     done(REGS, 0);
2476 }
2477
2478 static void
2479 int29(regcontext_t *REGS)
2480 {
2481     tty_write(R_AL, TTYF_REDIRECT);
2482 }
2483
2484 /******************************************************************************
2485 ** entrypoint for MS-DOS functions
2486 */
2487 static void
2488 int21(regcontext_t *REGS)
2489 {
2490     int error;
2491     int idx;
2492     
2493     /* look for a handler */
2494     idx = intfunc_find(int21_table, int21_fastlookup, R_AH, R_AL);
2495
2496     if (idx == -1) {                    /* no matching functions */
2497         unknown_int3(0x21, R_AH, R_AL, REGS);
2498         R_FLAGS |= PSL_C;               /* Flag an error */
2499         R_AX = 0xff;
2500         return;
2501     }
2502
2503     /* call the handler */
2504     error = int21_table[idx].handler(REGS);
2505     debug(D_DOSCALL, "msdos call %02x (%s) returns %d (%s)\n", 
2506           int21_table[idx].func, int21_table[idx].desc,  error,
2507           ((error >= 0) && (error <= dos_ret_size)) ? dos_return[error] : "unknown");
2508
2509     if (error) {
2510         doserrno = error;
2511         R_FLAGS |= PSL_C;
2512         
2513         /* XXX is this entirely legitimate? */
2514         if (R_AH >= 0x2f)
2515             R_AX = error;
2516         else
2517             R_AX = 0xff;
2518     } else {
2519         R_FLAGS &= ~PSL_C;
2520     }
2521     return;
2522 }
2523
2524 static void
2525 int67(regcontext_t *REGS)
2526 {
2527     ems_entry(REGS);
2528 }
2529
2530 static u_char upcase_trampoline[] = {
2531         0xf4,   /* HLT */
2532         0xcb,   /* RETF */
2533 };
2534
2535 /*
2536 ** initialise thyself
2537 */
2538 void
2539 dos_init(void)
2540 {
2541     u_long      vec;
2542
2543     /* hook vectors */
2544     vec = insert_softint_trampoline();
2545     ivec[0x20] = vec;
2546     register_callback(vec, int20, "int 20");
2547
2548     vec = insert_softint_trampoline();
2549     ivec[0x21] = vec;
2550     register_callback(vec, int21, "int 21");
2551
2552     vec = insert_softint_trampoline();
2553     ivec[0x29] = vec;
2554     register_callback(vec, int29, "int 29");
2555
2556     vec = insert_softint_trampoline();
2557     ivec[0x67] = vec;
2558     register_callback(vec, int67, "int 67 (EMS)");
2559
2560     vec = insert_null_trampoline();
2561     ivec[0x28] = vec;   /* dos idle */
2562     ivec[0x2b] = vec;   /* reserved */
2563     ivec[0x2c] = vec;   /* reserved */
2564     ivec[0x2d] = vec;   /* reserved */
2565
2566     upcase_vector = insert_generic_trampoline(
2567         sizeof(upcase_trampoline), upcase_trampoline);
2568     register_callback(upcase_vector, upcase_entry, "upcase");
2569
2570     /* build fastlookup index into the monster table of interrupts */
2571     intfunc_init(int21_table, int21_fastlookup);
2572
2573     ems_init();
2574 }